pyre-cli 9.4.0 → 9.5.1
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/dist/checkCmd-EWFNLBWE.js +74 -0
- package/dist/{chunk-QOLZ7T32.js → chunk-HGRBBPYX.js} +65 -3
- package/dist/chunk-MOAEGMFP.js +1042 -0
- package/dist/chunk-SG26Y3WF.js +0 -0
- package/dist/{collectors-VACLPHRP.js → collectors-WD6VKH6M.js} +1 -1
- package/dist/index.js +343 -1271
- package/dist/pipeCmd-7THXEGDH.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/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 +77 -7
- package/src/monitors/collectors.ts +50 -3
- package/src/monitors/smc.ts +27 -0
- 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-HGRBBPYX.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,52 +4088,57 @@ 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
|
|
@@ -5145,7 +4163,7 @@ Commands:
|
|
|
5145
4163
|
program.parse(process.argv);
|
|
5146
4164
|
var opts = program.opts();
|
|
5147
4165
|
if (opts.plain || opts.a11y) {
|
|
5148
|
-
|
|
4166
|
+
chalk10.level = 0;
|
|
5149
4167
|
}
|
|
5150
4168
|
function isExportMode() {
|
|
5151
4169
|
return opts.json || opts.csv || opts.tsv || opts.html || opts.md;
|
|
@@ -5157,7 +4175,7 @@ function sanitizeHost2(host) {
|
|
|
5157
4175
|
return host.trim().replace(/%$/, "");
|
|
5158
4176
|
}
|
|
5159
4177
|
function detectLocalIP() {
|
|
5160
|
-
const ifaces =
|
|
4178
|
+
const ifaces = os2.networkInterfaces();
|
|
5161
4179
|
const prioritizedNames = ["en0", "en1", "wlan0", "eth0"];
|
|
5162
4180
|
for (const name of prioritizedNames) {
|
|
5163
4181
|
const addrs = ifaces[name];
|
|
@@ -5182,26 +4200,56 @@ function detectLocalIP() {
|
|
|
5182
4200
|
async function runServerCommand() {
|
|
5183
4201
|
const ip = detectLocalIP();
|
|
5184
4202
|
if (!ip) {
|
|
5185
|
-
console.log(
|
|
4203
|
+
console.log(chalk10.red("Could not detect a non-loopback IP address. Connect to a network first."));
|
|
5186
4204
|
process.exit(1);
|
|
5187
4205
|
}
|
|
5188
4206
|
const port = 9876;
|
|
5189
4207
|
const password = "mysecret";
|
|
5190
|
-
console.log(
|
|
4208
|
+
console.log(chalk10.bold(`
|
|
5191
4209
|
pyre server`));
|
|
5192
|
-
console.log(
|
|
5193
|
-
console.log(
|
|
5194
|
-
console.log(
|
|
4210
|
+
console.log(chalk10.dim(` Detected IP: ${ip}`));
|
|
4211
|
+
console.log(chalk10.dim(` Port: ${port}`));
|
|
4212
|
+
console.log(chalk10.dim(` Password: ${password}`));
|
|
5195
4213
|
console.log();
|
|
5196
|
-
console.log(
|
|
5197
|
-
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}`));
|
|
5198
4216
|
console.log();
|
|
5199
|
-
console.log(
|
|
5200
|
-
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}`));
|
|
5201
4219
|
console.log();
|
|
5202
4220
|
}
|
|
5203
4221
|
async function main() {
|
|
4222
|
+
const { runSetupWizardIfNeeded } = await import("./wizard-GVIAHIVV.js");
|
|
4223
|
+
await runSetupWizardIfNeeded(!opts.wizard);
|
|
5204
4224
|
const cmd = program.args[0];
|
|
4225
|
+
if (cmd === "check") {
|
|
4226
|
+
const { runCheckCommand } = await import("./checkCmd-EWFNLBWE.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-7THXEGDH.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
|
+
}
|
|
5205
4253
|
if (cmd === "anomalies") {
|
|
5206
4254
|
const { runAnomaliesDigest } = await import("./anomaliesCmd-25OFKYPK.js");
|
|
5207
4255
|
runAnomaliesDigest({ since: opts.since || "7d", dir: opts.exportDir });
|
|
@@ -5228,19 +4276,19 @@ async function main() {
|
|
|
5228
4276
|
const { saveProfile, loadProfile, listProfiles } = await import("./config-4ZLF4HTS.js");
|
|
5229
4277
|
if (sub === "save" && name) {
|
|
5230
4278
|
saveProfile(name);
|
|
5231
|
-
console.log(
|
|
4279
|
+
console.log(chalk10.green(` \u2714 Profile '${name}' saved successfully.`));
|
|
5232
4280
|
} else if (sub === "load" && name) {
|
|
5233
4281
|
try {
|
|
5234
4282
|
loadProfile(name);
|
|
5235
|
-
console.log(
|
|
4283
|
+
console.log(chalk10.green(` \u2714 Profile '${name}' loaded successfully.`));
|
|
5236
4284
|
} catch (err) {
|
|
5237
|
-
console.log(
|
|
4285
|
+
console.log(chalk10.red(` \u2716 ${err.message}`));
|
|
5238
4286
|
}
|
|
5239
4287
|
} else if (sub === "list") {
|
|
5240
4288
|
const profiles = listProfiles();
|
|
5241
|
-
console.log(
|
|
4289
|
+
console.log(chalk10.bold("\n pyre Profiles:\n"));
|
|
5242
4290
|
if (profiles.length === 0) {
|
|
5243
|
-
console.log(
|
|
4291
|
+
console.log(chalk10.dim(" No saved profiles found. Save one with `pyre profile save <name>`."));
|
|
5244
4292
|
} else {
|
|
5245
4293
|
for (const p of profiles) {
|
|
5246
4294
|
console.log(` \u2022 ${p}`);
|
|
@@ -5262,7 +4310,7 @@ async function main() {
|
|
|
5262
4310
|
const file1 = program.args[1];
|
|
5263
4311
|
const file2 = program.args[2];
|
|
5264
4312
|
if (!file1 || !file2) {
|
|
5265
|
-
console.log(
|
|
4313
|
+
console.log(chalk10.red("Usage: pyre diff <snapshot1.json> <snapshot2.json>"));
|
|
5266
4314
|
process.exit(1);
|
|
5267
4315
|
}
|
|
5268
4316
|
runDiffCommand(file1, file2);
|
|
@@ -5271,13 +4319,24 @@ async function main() {
|
|
|
5271
4319
|
if (cmd === "ssh") {
|
|
5272
4320
|
const host = program.args[1];
|
|
5273
4321
|
if (!host) {
|
|
5274
|
-
console.log(
|
|
4322
|
+
console.log(chalk10.red("Usage: pyre ssh <host>"));
|
|
5275
4323
|
process.exit(1);
|
|
5276
4324
|
}
|
|
5277
4325
|
await runSshCommand(host);
|
|
5278
4326
|
return;
|
|
5279
4327
|
}
|
|
5280
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
|
+
}
|
|
5281
4340
|
await runWebCommand();
|
|
5282
4341
|
return;
|
|
5283
4342
|
}
|
|
@@ -5288,7 +4347,7 @@ async function main() {
|
|
|
5288
4347
|
if (cmd === "bench") {
|
|
5289
4348
|
const benchCmd = program.args.slice(1).join(" ");
|
|
5290
4349
|
if (!benchCmd) {
|
|
5291
|
-
console.log(
|
|
4350
|
+
console.log(chalk10.red("Usage: pyre bench <command>"));
|
|
5292
4351
|
process.exit(1);
|
|
5293
4352
|
}
|
|
5294
4353
|
await runBenchCommand(benchCmd);
|
|
@@ -5305,8 +4364,8 @@ async function main() {
|
|
|
5305
4364
|
const cfg = readConfig();
|
|
5306
4365
|
console.log(JSON.stringify(cfg, null, 2));
|
|
5307
4366
|
} else if (sub === "reset") {
|
|
5308
|
-
|
|
5309
|
-
console.log(
|
|
4367
|
+
fs8.rmSync(CONFIG_FILE, { force: true });
|
|
4368
|
+
console.log(chalk10.green("Config reset to defaults"));
|
|
5310
4369
|
} else {
|
|
5311
4370
|
console.log("Usage: pyre config <show|reset>");
|
|
5312
4371
|
}
|
|
@@ -5339,7 +4398,7 @@ async function main() {
|
|
|
5339
4398
|
if (cmd === "fleet") {
|
|
5340
4399
|
const hosts = program.args.slice(1);
|
|
5341
4400
|
if (hosts.length === 0) {
|
|
5342
|
-
console.error(
|
|
4401
|
+
console.error(chalk10.red("Usage: pyre fleet <host1> [host2] ..."));
|
|
5343
4402
|
process.exit(1);
|
|
5344
4403
|
}
|
|
5345
4404
|
runFleetCommand(hosts);
|
|
@@ -5386,7 +4445,7 @@ async function main() {
|
|
|
5386
4445
|
await runP2PConnect();
|
|
5387
4446
|
return;
|
|
5388
4447
|
} else {
|
|
5389
|
-
console.log(
|
|
4448
|
+
console.log(chalk10.red("Usage: pyre p2p <server|connect> [options]"));
|
|
5390
4449
|
process.exit(1);
|
|
5391
4450
|
}
|
|
5392
4451
|
}
|
|
@@ -5399,16 +4458,16 @@ async function main() {
|
|
|
5399
4458
|
else if (opts.md) output = formatMarkdown(data);
|
|
5400
4459
|
else if (opts.csv) output = formatCsv(data);
|
|
5401
4460
|
else if (opts.tsv) output = formatTsv(data);
|
|
5402
|
-
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 } });
|
|
5403
4462
|
console.log(output);
|
|
5404
4463
|
if (opts.out) {
|
|
5405
|
-
|
|
5406
|
-
console.log(
|
|
4464
|
+
fs8.writeFileSync(opts.out, stripAnsi(output));
|
|
4465
|
+
console.log(chalk10.dim(`
|
|
5407
4466
|
Saved to ${opts.out}`));
|
|
5408
4467
|
}
|
|
5409
4468
|
}
|
|
5410
4469
|
main().catch((err) => {
|
|
5411
|
-
console.error(
|
|
4470
|
+
console.error(chalk10.red(`Error: ${err.message}`));
|
|
5412
4471
|
process.exit(1);
|
|
5413
4472
|
});
|
|
5414
4473
|
async function runP2PServer() {
|
|
@@ -5424,15 +4483,15 @@ async function runP2PServer() {
|
|
|
5424
4483
|
const allowList = opts.p2pAllow ? opts.p2pAllow.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
5425
4484
|
const denyList = opts.p2pDeny ? opts.p2pDeny.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
5426
4485
|
if (!password) {
|
|
5427
|
-
console.log(
|
|
4486
|
+
console.log(chalk10.red("Error: a password is required for P2P server mode. Use --p2p-password <password>"));
|
|
5428
4487
|
process.exit(1);
|
|
5429
4488
|
}
|
|
5430
4489
|
if (tlsCert && !tlsKey) {
|
|
5431
|
-
console.log(
|
|
4490
|
+
console.log(chalk10.red("Error: --p2p-key is required when --p2p-cert is specified"));
|
|
5432
4491
|
process.exit(1);
|
|
5433
4492
|
}
|
|
5434
4493
|
if (tlsKey && !tlsCert) {
|
|
5435
|
-
console.log(
|
|
4494
|
+
console.log(chalk10.red("Error: --p2p-cert is required when --p2p-key is specified"));
|
|
5436
4495
|
process.exit(1);
|
|
5437
4496
|
}
|
|
5438
4497
|
const server = new P2PServer({
|
|
@@ -5452,18 +4511,18 @@ async function runP2PServer() {
|
|
|
5452
4511
|
hmacKey: opts.p2pHmacKey
|
|
5453
4512
|
});
|
|
5454
4513
|
const tlsEnabled = !!(tlsCert && tlsKey);
|
|
5455
|
-
console.log(
|
|
4514
|
+
console.log(chalk10.bold(`
|
|
5456
4515
|
pyre P2P Server`));
|
|
5457
|
-
console.log(
|
|
5458
|
-
console.log(
|
|
5459
|
-
console.log(
|
|
5460
|
-
console.log(
|
|
5461
|
-
console.log(
|
|
5462
|
-
console.log(
|
|
5463
|
-
console.log(
|
|
5464
|
-
if (allowList.length > 0) console.log(
|
|
5465
|
-
if (denyList.length > 0) console.log(
|
|
5466
|
-
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...
|
|
5467
4526
|
`));
|
|
5468
4527
|
await server.start();
|
|
5469
4528
|
const rl = readline2.createInterface({ input: process.stdin });
|
|
@@ -5475,7 +4534,7 @@ async function runP2PServer() {
|
|
|
5475
4534
|
process.exit(0);
|
|
5476
4535
|
} else if (trimmed === "status") {
|
|
5477
4536
|
const info = server.info;
|
|
5478
|
-
console.log(
|
|
4537
|
+
console.log(chalk10.dim(` Peers connected: ${info.peersConnected}`));
|
|
5479
4538
|
}
|
|
5480
4539
|
});
|
|
5481
4540
|
process.once("SIGINT", () => {
|
|
@@ -5508,7 +4567,7 @@ async function runP2PConnect() {
|
|
|
5508
4567
|
const tlsCA = opts.p2pCA;
|
|
5509
4568
|
const tlsInsecure = !!opts.p2pInsecure;
|
|
5510
4569
|
if (!password) {
|
|
5511
|
-
console.log(
|
|
4570
|
+
console.log(chalk10.red("Error: a password is required for P2P connect mode. Use --p2p-password <password>"));
|
|
5512
4571
|
process.exit(1);
|
|
5513
4572
|
}
|
|
5514
4573
|
const client = new P2PClient({
|
|
@@ -5522,12 +4581,12 @@ async function runP2PConnect() {
|
|
|
5522
4581
|
hmacKey: opts.p2pHmacKey
|
|
5523
4582
|
});
|
|
5524
4583
|
client.setOnStatus((msg) => {
|
|
5525
|
-
process.stdout.write(`\r${
|
|
4584
|
+
process.stdout.write(`\r${chalk10.dim(msg)}${" ".repeat(40)}\r`);
|
|
5526
4585
|
});
|
|
5527
4586
|
client.setOnData((data) => {
|
|
5528
4587
|
const lines = [];
|
|
5529
|
-
lines.push(
|
|
5530
|
-
lines.push(
|
|
4588
|
+
lines.push(chalk10.bold(` pyre P2P Client \u2014 ${data.header.hostname}`));
|
|
4589
|
+
lines.push(chalk10.dim(` ${data.timestamp}`));
|
|
5531
4590
|
lines.push("");
|
|
5532
4591
|
lines.push(...formatTable(data, {
|
|
5533
4592
|
width: process.stdout.columns || 80,
|
|
@@ -5544,11 +4603,11 @@ async function runP2PConnect() {
|
|
|
5544
4603
|
writeFrame2(lines);
|
|
5545
4604
|
});
|
|
5546
4605
|
const protocolLabel = useTLS ? "TLS" : "TCP";
|
|
5547
|
-
console.log(
|
|
4606
|
+
console.log(chalk10.bold(`
|
|
5548
4607
|
pyre P2P Client`));
|
|
5549
|
-
console.log(
|
|
5550
|
-
console.log(
|
|
5551
|
-
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
|
|
5552
4611
|
`));
|
|
5553
4612
|
process.stdout.write("\x1B[?25l");
|
|
5554
4613
|
try {
|
|
@@ -5556,23 +4615,23 @@ async function runP2PConnect() {
|
|
|
5556
4615
|
} catch (err) {
|
|
5557
4616
|
const code = err.code;
|
|
5558
4617
|
const msg = err.message || String(err);
|
|
5559
|
-
console.log(
|
|
4618
|
+
console.log(chalk10.red(`Connection failed: ${msg}`));
|
|
5560
4619
|
if (code === "ECONNREFUSED") {
|
|
5561
|
-
console.log(
|
|
5562
|
-
console.log(
|
|
5563
|
-
console.log(
|
|
5564
|
-
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"));
|
|
5565
4624
|
} else if (code === "ETIMEDOUT") {
|
|
5566
|
-
console.log(
|
|
5567
|
-
console.log(
|
|
5568
|
-
console.log(
|
|
5569
|
-
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"));
|
|
5570
4629
|
} else if (code === "EHOSTDOWN" || code === "EHOSTUNREACH") {
|
|
5571
|
-
console.log(
|
|
5572
|
-
console.log(
|
|
5573
|
-
console.log(
|
|
5574
|
-
console.log(
|
|
5575
|
-
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)"));
|
|
5576
4635
|
}
|
|
5577
4636
|
process.stdout.write("\x1B[?25h");
|
|
5578
4637
|
process.exit(1);
|
|
@@ -5586,11 +4645,11 @@ async function runP2PConnect() {
|
|
|
5586
4645
|
}
|
|
5587
4646
|
async function runInfoCommand() {
|
|
5588
4647
|
const data = await collectAll({ detailed: true });
|
|
5589
|
-
const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-
|
|
4648
|
+
const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-WD6VKH6M.js");
|
|
5590
4649
|
const displays = await getDisplayInfo();
|
|
5591
4650
|
const tmStatus = await getTimeMachineStatus();
|
|
5592
4651
|
const lines = [];
|
|
5593
|
-
lines.push(
|
|
4652
|
+
lines.push(chalk10.bold(" pyre info \u2014 Mac System Information"));
|
|
5594
4653
|
lines.push("");
|
|
5595
4654
|
lines.push(` Hostname: ${data.header.hostname}`);
|
|
5596
4655
|
lines.push(` OS: ${data.header.os}`);
|
|
@@ -5610,17 +4669,17 @@ async function runInfoCommand() {
|
|
|
5610
4669
|
lines.push(` Thermal: ${data.thermal.state}`);
|
|
5611
4670
|
if (displays.length > 0) {
|
|
5612
4671
|
lines.push("");
|
|
5613
|
-
lines.push(
|
|
4672
|
+
lines.push(chalk10.cyan.bold(" \u{1F5A5} Displays:"));
|
|
5614
4673
|
for (const d of displays) {
|
|
5615
4674
|
lines.push(` \u2022 ${d.name}: ${d.resolution}${d.isMain ? " (Main)" : ""}${d.connectionType ? ` [${d.connectionType}]` : ""}`);
|
|
5616
4675
|
}
|
|
5617
4676
|
}
|
|
5618
4677
|
lines.push("");
|
|
5619
|
-
lines.push(
|
|
4678
|
+
lines.push(chalk10.cyan.bold(" \u23F1 Time Machine Backup:"));
|
|
5620
4679
|
if (!tmStatus.configured) {
|
|
5621
4680
|
lines.push(" \u2022 Not configured");
|
|
5622
4681
|
} else {
|
|
5623
|
-
lines.push(` \u2022 Status: ${tmStatus.running ?
|
|
4682
|
+
lines.push(` \u2022 Status: ${tmStatus.running ? chalk10.yellow(`Backup in progress (${tmStatus.percent || 0}%)`) : chalk10.green("Idle")}`);
|
|
5624
4683
|
lines.push(` \u2022 Latest Backup: ${tmStatus.latestBackup}`);
|
|
5625
4684
|
}
|
|
5626
4685
|
console.log(lines.join("\n"));
|
|
@@ -5628,18 +4687,18 @@ async function runInfoCommand() {
|
|
|
5628
4687
|
async function runSshCommand(host) {
|
|
5629
4688
|
const password = opts.p2pPassword || "mysecret";
|
|
5630
4689
|
const cmd = `ssh -o LogLevel=QUIET ${host} "pyre --json --once"`;
|
|
5631
|
-
console.log(
|
|
4690
|
+
console.log(chalk10.bold(`
|
|
5632
4691
|
pyre ssh \u2014 ${host}`));
|
|
5633
|
-
console.log(
|
|
4692
|
+
console.log(chalk10.dim(` Running: ${cmd}
|
|
5634
4693
|
`));
|
|
5635
4694
|
const { execFile: execFile2 } = await import("child_process");
|
|
5636
4695
|
const child = execFile2("ssh", ["-o", "LogLevel=QUIET", host, "pyre", "--json", "--once"], (err, stdout, stderr) => {
|
|
5637
4696
|
if (err) {
|
|
5638
|
-
console.log(
|
|
5639
|
-
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."));
|
|
5640
4699
|
process.exit(1);
|
|
5641
4700
|
}
|
|
5642
|
-
if (stderr) console.log(
|
|
4701
|
+
if (stderr) console.log(chalk10.dim(stderr));
|
|
5643
4702
|
try {
|
|
5644
4703
|
const remote = JSON.parse(stdout);
|
|
5645
4704
|
console.log(formatTable(remote, { width: process.stdout.columns || 80, sortBy: "cpu", treeView: false, visible: { packets: true } }));
|
|
@@ -5664,6 +4723,13 @@ async function runWebCommand() {
|
|
|
5664
4723
|
res.end(JSON.stringify({ status: "ok", uptime: process.uptime(), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
5665
4724
|
return;
|
|
5666
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
|
+
}
|
|
5667
4733
|
if (url === "/api/stream") {
|
|
5668
4734
|
res.writeHead(200, {
|
|
5669
4735
|
"Content-Type": "text/event-stream",
|
|
@@ -5692,9 +4758,9 @@ async function runWebCommand() {
|
|
|
5692
4758
|
res.end(getDashboardHtml());
|
|
5693
4759
|
} else if (url === "/setup") {
|
|
5694
4760
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
5695
|
-
const indexPath =
|
|
5696
|
-
if (
|
|
5697
|
-
res.end(
|
|
4761
|
+
const indexPath = path5.join(process.cwd(), "index.html");
|
|
4762
|
+
if (fs8.existsSync(indexPath)) {
|
|
4763
|
+
res.end(fs8.readFileSync(indexPath, "utf-8"));
|
|
5698
4764
|
} else {
|
|
5699
4765
|
res.end(getDashboardHtml());
|
|
5700
4766
|
}
|
|
@@ -5713,14 +4779,14 @@ async function runWebCommand() {
|
|
|
5713
4779
|
});
|
|
5714
4780
|
server.on("error", (err) => {
|
|
5715
4781
|
if (err.code === "EADDRINUSE") {
|
|
5716
|
-
console.log(
|
|
4782
|
+
console.log(chalk10.red(`
|
|
5717
4783
|
Error: Port ${port} is already in use.`));
|
|
5718
|
-
console.log(
|
|
5719
|
-
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}
|
|
5720
4786
|
`));
|
|
5721
4787
|
process.exit(1);
|
|
5722
4788
|
} else {
|
|
5723
|
-
console.log(
|
|
4789
|
+
console.log(chalk10.red(`Server error: ${err.message}`));
|
|
5724
4790
|
process.exit(1);
|
|
5725
4791
|
}
|
|
5726
4792
|
});
|
|
@@ -5729,12 +4795,13 @@ Error: Port ${port} is already in use.`));
|
|
|
5729
4795
|
server.listen(port, "0.0.0.0", () => {
|
|
5730
4796
|
const address = server.address();
|
|
5731
4797
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
5732
|
-
console.log(
|
|
4798
|
+
console.log(chalk10.bold(`
|
|
5733
4799
|
pyre web server running on port ${actualPort}`));
|
|
5734
|
-
console.log(
|
|
5735
|
-
console.log(
|
|
5736
|
-
console.log(
|
|
5737
|
-
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`));
|
|
5738
4805
|
resolve(actualPort);
|
|
5739
4806
|
});
|
|
5740
4807
|
process.once("SIGINT", () => {
|
|
@@ -5763,9 +4830,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|
|
5763
4830
|
|
|
5764
4831
|
func applicationDidFinishLaunching(_ n: Notification) {
|
|
5765
4832
|
// Window
|
|
4833
|
+
let isCompact = ${!!opts.compact}
|
|
4834
|
+
let isAlwaysOnTop = ${!!opts.ontop}
|
|
5766
4835
|
let screen = NSScreen.main?.visibleFrame ?? NSRect(x:0, y:0, width:1280, height:800)
|
|
5767
|
-
let W = min(1180.0, screen.width - 80)
|
|
5768
|
-
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)
|
|
5769
4838
|
let rect = NSRect(x: screen.minX + (screen.width - W) / 2,
|
|
5770
4839
|
y: screen.minY + (screen.height - H) / 2,
|
|
5771
4840
|
width: W, height: H)
|
|
@@ -5773,8 +4842,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|
|
5773
4842
|
window = NSWindow(contentRect: rect,
|
|
5774
4843
|
styleMask: [.titled, .closable, .miniaturizable, .resizable],
|
|
5775
4844
|
backing: .buffered, defer: false)
|
|
5776
|
-
window.title = "Pyre"
|
|
5777
|
-
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
|
+
}
|
|
5778
4850
|
window.appearance = NSAppearance(named: .darkAqua)
|
|
5779
4851
|
window.backgroundColor = NSColor(white: 0, alpha: 1)
|
|
5780
4852
|
window.isReleasedWhenClosed = false
|
|
@@ -5843,14 +4915,14 @@ let delegate = AppDelegate()
|
|
|
5843
4915
|
app.delegate = delegate
|
|
5844
4916
|
app.run()
|
|
5845
4917
|
`;
|
|
5846
|
-
const scriptPath =
|
|
5847
|
-
|
|
4918
|
+
const scriptPath = path5.join(os2.tmpdir(), "pyre-ui.swift");
|
|
4919
|
+
fs8.writeFileSync(scriptPath, swiftScript);
|
|
5848
4920
|
const { spawn } = await import("child_process");
|
|
5849
|
-
console.log(
|
|
4921
|
+
console.log(chalk10.cyan(`
|
|
5850
4922
|
Launching native UI window...`));
|
|
5851
4923
|
const ui = spawn("swift", [scriptPath], { stdio: "inherit" });
|
|
5852
4924
|
ui.on("close", () => {
|
|
5853
|
-
console.log(
|
|
4925
|
+
console.log(chalk10.dim("UI window closed, shutting down server..."));
|
|
5854
4926
|
process.exit(0);
|
|
5855
4927
|
});
|
|
5856
4928
|
process.once("SIGINT", () => {
|
|
@@ -5861,14 +4933,14 @@ app.run()
|
|
|
5861
4933
|
async function runBenchCommand(benchCmd) {
|
|
5862
4934
|
const { spawn } = await import("child_process");
|
|
5863
4935
|
const logDir = opts.exportDir || "./pyre-exports";
|
|
5864
|
-
const file =
|
|
5865
|
-
if (!
|
|
5866
|
-
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" });
|
|
5867
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");
|
|
5868
|
-
console.log(
|
|
4940
|
+
console.log(chalk10.bold(`
|
|
5869
4941
|
pyre bench`));
|
|
5870
|
-
console.log(
|
|
5871
|
-
console.log(
|
|
4942
|
+
console.log(chalk10.dim(` Command: ${benchCmd}`));
|
|
4943
|
+
console.log(chalk10.dim(` Log: ${file}
|
|
5872
4944
|
`));
|
|
5873
4945
|
const startTime = Date.now();
|
|
5874
4946
|
let accumulatedWattSeconds = 0;
|
|
@@ -5898,13 +4970,13 @@ async function runBenchCommand(benchCmd) {
|
|
|
5898
4970
|
clearInterval(handle);
|
|
5899
4971
|
stream.end();
|
|
5900
4972
|
const durationSec = Math.max(0.1, (Date.now() - startTime) / 1e3);
|
|
5901
|
-
console.log(
|
|
4973
|
+
console.log(chalk10.green(`
|
|
5902
4974
|
\u2714 Bench complete (${durationSec.toFixed(2)}s). Log saved to ${file}`));
|
|
5903
4975
|
if (powerSamplesCount > 0 && accumulatedWattSeconds > 0) {
|
|
5904
4976
|
const kWh = accumulatedWattSeconds / (3600 * 1e3);
|
|
5905
4977
|
const rate = config2.electricityRateKwH ?? 0.15;
|
|
5906
4978
|
const cost = kWh * rate;
|
|
5907
|
-
console.log(
|
|
4979
|
+
console.log(chalk10.cyan(` \u26A1 Energy Consumption: ${kWh.toFixed(6)} kWh | Est. Cost: $${cost.toFixed(6)} (@ $${rate}/kWh)`));
|
|
5908
4980
|
}
|
|
5909
4981
|
};
|
|
5910
4982
|
child.on("close", () => {
|