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/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 fs9 from "fs";
14
- import os3 from "os";
15
- import path6 from "path";
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 chalk12 from "chalk";
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 chalk3 from "chalk";
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 `${chalk3.dim(label.padEnd(10))} ${chalk3.dim(emptyMessage)}`;
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 = chalk3.cyan;
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 ? chalk3.red : pct > 70 ? chalk3.yellow : chalk3.green;
555
+ color = pct > 90 ? chalk.red : pct > 70 ? chalk.yellow : chalk.green;
1543
556
  }
1544
- const raw = `${chalk3.dim(label.padEnd(10))}${color(spark)} ${chalk3.bold(fmt(current))}`;
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 fs2 from "fs";
2524
- import path2 from "path";
1536
+ import fs from "fs";
1537
+ import path from "path";
2525
1538
  function ensureDir(dir) {
2526
- if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
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 = path2.join(state.exportDir, `pyre-${timestampForFile()}.${ext}`);
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
- fs2.writeFileSync(file, content);
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 = path2.join(state.exportDir, `pyre-log-${timestampForFile()}.csv`);
2569
- state.logStream = fs2.createWriteStream(file, { flags: "a" });
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 (!fs2.existsSync(dir)) return;
2584
- const files = fs2.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv")).map((f) => ({ name: f, path: path2.join(dir, f), mtime: fs2.statSync(path2.join(dir, f)).mtimeMs, size: fs2.statSync(path2.join(dir, f)).size })).sort((a, b) => a.mtime - b.mtime);
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
- fs2.unlinkSync(f.path);
1602
+ fs.unlinkSync(f.path);
2590
1603
  } catch {
2591
1604
  }
2592
1605
  }
2593
1606
  }
2594
- const remaining = fs2.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
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
- fs2.unlinkSync(path2.join(dir, f));
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 += fs2.statSync(path2.join(dir, f)).size;
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 = fs2.statSync(path2.join(dir, f)).size;
1624
+ const sz = fs.statSync(path.join(dir, f)).size;
2612
1625
  try {
2613
- fs2.unlinkSync(path2.join(dir, f));
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 fs3 from "fs";
2651
- import path3 from "path";
1663
+ import fs2 from "fs";
1664
+ import path2 from "path";
2652
1665
  import { fileURLToPath } from "url";
2653
- import chalk4 from "chalk";
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 ? chalk4.green("Running") : chalk4.red("Stopped")}`);
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 chalk4.green;
2801
- if (t === "disconnect") return chalk4.yellow;
2802
- if (t === "error" || t === "blocked" || t === "rate_limited") return chalk4.red;
2803
- return chalk4.cyan;
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(chalk4.dim("Events (most recent):"));
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(`${chalk4.dim(time)} ${typeColor(evt.type)(evt.type.padEnd(14))} ${evt.detail}`);
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 = chalk4.bgCyan.black.bold(" UI CUSTOMIZER (Use \u2191/\u2193 or J/K to navigate, Enter/Space to toggle, Esc to exit) ");
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 ? chalk4.yellow("\u25B6 ") : " ";
1836
+ const prefix = isSelected ? chalk2.yellow("\u25B6 ") : " ";
2824
1837
  if (opt === "Theme") {
2825
- lines.push(`${prefix}${opt}: ${chalk4.bold.green(state.currentTheme)} [${themesList.join(", ")}]`);
1838
+ lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.currentTheme)} [${themesList.join(", ")}]`);
2826
1839
  } else if (opt === "Graph Mode") {
2827
- lines.push(`${prefix}${opt}: ${chalk4.bold.green(state.graphMode)} [spark, bar]`);
1840
+ lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.graphMode)} [spark, bar]`);
2828
1841
  } else if (opt === "Splash Screen") {
2829
- const status = state.splashEnabled ? chalk4.green("[ON]") : chalk4.red("[OFF]");
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}: ${chalk4.bold.green(state.splashColorScheme)} [fire, ocean, forest, purple, monochrome]`);
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}: ${chalk4.bold.green(state.splashAnimation)} [classic, wave, sparks]`);
1847
+ lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.splashAnimation)} [classic, wave, sparks]`);
2835
1848
  } else if (opt === "Notifications") {
2836
- const status = state.notificationsEnabled ? chalk4.green("[ON]") : chalk4.red("[OFF]");
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}: ${chalk4.bold.green(state.tempUnit.toUpperCase())} [C, F]`);
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 ? chalk4.green("[VISIBLE]") : chalk4.red("[HIDDEN]");
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 = chalk4.yellow(`pyre ${rows}x${cols} too small. Resize terminal to >= 50x20.`);
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(chalk4.cyan(` Filter processes: ${state.inputBuffer}_`));
1998
+ bodyLines.push(chalk2.cyan(` Filter processes: ${state.inputBuffer}_`));
2986
1999
  } else if (state.inputMode === "kill") {
2987
- bodyLines.push(chalk4.cyan(` Kill PID: ${state.inputBuffer}_ (enter to confirm, esc to cancel)`));
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 ? chalk4.yellow(s) : chalk4.dim(s)).join(" ");
2991
- bodyLines.push(chalk4.cyan(` Signal: ${state.inputBuffer}_`));
2992
- bodyLines.push(chalk4.dim(` ${sigList}`));
2993
- bodyLines.push(chalk4.dim(" \u2191/\u2193 to select, Enter to confirm, Esc to cancel"));
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(chalk4.cyan(` P2P password (${state.p2pPort}): ${state.inputBuffer}_ (enter to start, esc to cancel)`));
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]) => `${chalk4.hex("#50fa7b").bold(k)} ${chalk4.dim(label)}`).join(chalk4.dim(" | "));
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(chalk4.yellow.bold("\u23F8 PAUSED"));
3039
- if (state.logging) badges.push(chalk4.red.bold("\u25CF REC"));
3040
- if (state.activePanel !== "grid") badges.push(chalk4.cyan.bold(`\u25C9 ${state.activePanel.toUpperCase()}`));
3041
- if (state.p2pServerRunning) badges.push(chalk4.green.bold(`P2P ${state.p2pBind || "0.0.0.0"}:${state.p2pPort}`));
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(chalk4.red(`\u26A0 Alert: ${reasons.join("; ")}`), 5e3);
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
- chalk4.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"),
3112
- chalk4.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"),
3113
- chalk4.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 "),
3114
- chalk4.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 "),
3115
- chalk4.hex("#ffe004").bold("\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"),
3116
- chalk4.hex("#ffff40").bold("\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D")
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(chalk4.dim("system monitor v9.0.0"));
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 ? chalk4.cyan.bold(" \u25B6 ") : " ";
3129
- const text = isSelected ? chalk4.bgCyan.black(` ${opt} `) : chalk4.dim(` ${opt} `);
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(chalk4.dim("Use \u2191/\u2193 to navigate, Enter to select"));
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 = path3.dirname(__filename);
3149
- const rootDir = path3.resolve(__dirname, "..", "..");
3150
- const readmePath = path3.join(rootDir, "README.md");
3151
- const content = fs3.readFileSync(readmePath, "utf8");
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 [chalk4.red("Failed to load README.md")];
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 = chalk4.cyan.bold(out2);
2179
+ out2 = chalk2.cyan.bold(out2);
3167
2180
  } else if (out2.startsWith("```")) {
3168
- out2 = chalk4.dim(out2);
2181
+ out2 = chalk2.dim(out2);
3169
2182
  } else if (out2.startsWith("- ") || out2.startsWith("* ")) {
3170
- out2 = " " + chalk4.yellow(out2.substring(0, 2)) + out2.substring(2);
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, chalk4.cyan, chalk4.dim, availableLines));
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
- chalk4.bold.hex("#ff6004")("Pyre - The Terminal System Monitor"),
2196
+ chalk2.bold.hex("#ff6004")("Pyre - The Terminal System Monitor"),
3184
2197
  "",
3185
2198
  "Created with \u2764\uFE0F using Node.js",
3186
2199
  "",
3187
- chalk4.cyan("Libraries used:"),
2200
+ chalk2.cyan("Libraries used:"),
3188
2201
  " - chalk (Terminal styling)",
3189
2202
  " - systeminformation (System metrics)",
3190
2203
  "",
3191
- chalk4.dim("Press Esc or Enter to return to menu.")
2204
+ chalk2.dim("Press Esc or Enter to return to menu.")
3192
2205
  ];
3193
- out.push(...panel("CREDITS", content, cols, chalk4.magenta, chalk4.dim, rows - 2));
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 fs4 from "fs";
2213
+ import fs3 from "fs";
3201
2214
  import crypto2 from "crypto";
3202
- import chalk5 from "chalk";
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 header2 = Buffer.alloc(HEADER_SIZE);
3214
- header2.writeUInt32BE(body.length, 0);
3215
- return Buffer.concat([header2, body]);
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
- fs4.mkdirSync(this.options.auditLog, { recursive: true });
2358
+ fs3.mkdirSync(this.options.auditLog, { recursive: true });
3346
2359
  const logFile = `${this.options.auditLog}/p2p-audit.log`;
3347
- this.auditStream = fs4.createWriteStream(logFile, { flags: "a" });
2360
+ this.auditStream = fs3.createWriteStream(logFile, { flags: "a" });
3348
2361
  } catch {
3349
- this.log(chalk5.yellow("Warning: could not open audit 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: fs4.readFileSync(this.options.tlsKey),
3357
- cert: fs4.readFileSync(this.options.tlsCert)
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 = fs4.readFileSync(this.options.tlsCA);
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(chalk5.green(`P2P server listening on ${bound} (${protocol})`));
3374
- this.log(chalk5.dim(` Password: ${this.options.password}`));
3375
- this.log(chalk5.dim(` Interval: ${this.options.intervalMs}ms`));
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(chalk5.dim(` Allowed IPs: ${allowedIPs.join(", ")}`));
3379
- if (deniedIPs.length > 0) this.log(chalk5.dim(` Denied IPs: ${deniedIPs.join(", ")}`));
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(chalk5.yellow(`Blocked IP: ${ip}`));
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(chalk5.yellow(`Rate limit exceeded for IP: ${ip}`));
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(chalk5.cyan(`Peer connected: ${remoteAddr}`));
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(chalk5.red(`Auth failed from ${peer.ip}: expected auth message first`));
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(chalk5.red(`Auth failed from ${peer.ip}: invalid auth payload`));
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(chalk5.red(`Auth failed from ${peer.ip}: invalid nonce`));
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(chalk5.red(`Auth failed from ${peer.ip}: invalid password`));
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(chalk5.green(`Peer authenticated: ${peer.ip}`));
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(chalk5.red(`Auth failed from ${peer.ip}: invalid message format`));
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(chalk5.yellow(`Peer disconnected: ${peer.ip}`));
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(chalk5.yellow(`Peer connection reset: ${addr}`));
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(chalk5.red(`Peer error: ${err.message}`));
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 fs5 from "fs";
2641
+ import fs4 from "fs";
3629
2642
  import crypto3 from "crypto";
3630
- import chalk6 from "chalk";
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
- fs5.mkdirSync(this.options.auditLog, { recursive: true });
2669
+ fs4.mkdirSync(this.options.auditLog, { recursive: true });
3657
2670
  const logFile = `${this.options.auditLog}/p2p-client-audit.log`;
3658
- this.auditStream = fs5.createWriteStream(logFile, { flags: "a" });
2671
+ this.auditStream = fs4.createWriteStream(logFile, { flags: "a" });
3659
2672
  } catch {
3660
- console.log(chalk6.yellow("Warning: could not open audit 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 = fs5.readFileSync(this.options.tlsCA);
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 chalk7 from "chalk";
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 += chalk7.rgb(color.r, color.g, color.b)(getChar(v));
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)) + chalk7.bold.white("\u{1F525} pyre \u2014 system monitor (warming up sensors...)\n");
4928
- output += " ".repeat(Math.max(0, startCol + (cols - 25) / 2)) + chalk7.dim("[Press Enter to continue]");
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 fs6 from "fs";
4946
- import path4 from "path";
4947
- import chalk8 from "chalk";
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(chalk8.bold(`
3964
+ console.log(chalk6.bold(`
4952
3965
  pyre history \u2014 Past ${days} Days Trend
4953
3966
  `));
4954
- if (!fs6.existsSync(dir)) {
4955
- console.log(chalk8.yellow(` No logs directory found at ${dir}`));
3967
+ if (!fs5.existsSync(dir)) {
3968
+ console.log(chalk6.yellow(` No logs directory found at ${dir}`));
4956
3969
  return;
4957
3970
  }
4958
- const files = fs6.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
3971
+ const files = fs5.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
4959
3972
  if (files.length === 0) {
4960
- console.log(chalk8.yellow(` No CSV logs found in ${dir}`));
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 = path4.join(dir, file);
3981
+ const fullPath = path3.join(dir, file);
4969
3982
  try {
4970
- const content = fs6.readFileSync(fullPath, "utf-8");
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(chalk8.yellow(` No historical entries recorded within the last ${days} days.`));
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 fs7 from "fs";
5006
- import chalk9 from "chalk";
4018
+ import fs6 from "fs";
4019
+ import chalk7 from "chalk";
5007
4020
  function runDiffCommand(file1, file2) {
5008
- console.log(chalk9.bold(`
4021
+ console.log(chalk7.bold(`
5009
4022
  pyre diff \u2014 Snapshot Comparison
5010
4023
  `));
5011
- console.log(chalk9.dim(` Snapshot A: ${file1}`));
5012
- console.log(chalk9.dim(` Snapshot B: ${file2}
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(fs7.readFileSync(file1, "utf-8"));
5016
- const dataB = JSON.parse(fs7.readFileSync(file2, "utf-8"));
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 ? chalk9.red : delta < 0 ? chalk9.green : chalk9.dim;
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(chalk9.red(`Failed to compare snapshots: ${err.message}`));
4048
+ console.log(chalk7.red(`Failed to compare snapshots: ${err.message}`));
5036
4049
  }
5037
4050
  }
5038
4051
 
5039
4052
  // src/xbar.ts
5040
- import fs8 from "fs";
5041
- import path5 from "path";
5042
- import os2 from "os";
5043
- import chalk10 from "chalk";
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 = path5.join(os2.homedir(), "Library", "Application Support", "xbar", "plugins");
4091
+ const pluginDir = path4.join(os.homedir(), "Library", "Application Support", "xbar", "plugins");
5079
4092
  const filename = "pyre-monitor.2s.sh";
5080
- const outPath = path5.join(process.cwd(), filename);
5081
- fs8.writeFileSync(outPath, script);
5082
- fs8.chmodSync(outPath, "755");
5083
- console.log(chalk10.green(`Generated xbar plugin script at ${outPath}`));
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(chalk10.cyan(` mv ${filename} "${pluginDir}/"`));
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 { execSync } from "child_process";
5091
- import chalk11 from "chalk";
5092
- function runFleetCommand(hosts) {
5093
- console.log(chalk11.cyan(`Fetching data from ${hosts.length} hosts...`));
5094
- const results = [];
5095
- for (const host of hosts) {
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 out = execSync(`ssh ${host} "pyre --once --json"`, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] });
5098
- const data = JSON.parse(out);
5099
- results.push({ host, data });
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
- results.push({ host, error: e.message });
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(chalk11.bold.blue(`
5106
- === ${result.host} ===
4120
+ console.log(chalk9.bold.blue(`
4121
+ === Host: ${result.host} ===
5107
4122
  `));
5108
4123
  if (result.error) {
5109
- console.log(chalk11.red(`Error connecting to host: ${result.error}`));
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, activePanel: "grid" }));
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(fs9.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
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("--webhook-url <url>", "URL to POST to on alert").option("--alert-cmd <cmd>", "Command to execute on alert").option("--port <port>", "Port number for web server mode", "3000");
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
- chalk12.level = 0;
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 = os3.networkInterfaces();
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(chalk12.red("Could not detect a non-loopback IP address. Connect to a network first."));
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(chalk12.bold(`
4208
+ console.log(chalk10.bold(`
5191
4209
  pyre server`));
5192
- console.log(chalk12.dim(` Detected IP: ${ip}`));
5193
- console.log(chalk12.dim(` Port: ${port}`));
5194
- console.log(chalk12.dim(` Password: ${password}`));
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(chalk12.bold("Run this on THIS machine (server):"));
5197
- console.log(chalk12.hex("#ff6a39")(`pyre p2p server --p2p-host 0.0.0.0 --p2p-port ${port} --p2p-password ${password}`));
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(chalk12.bold("Run this on the OTHER machine (client):"));
5200
- console.log(chalk12.hex("#ff6a39")(`pyre p2p connect --p2p-host ${ip} --p2p-port ${port} --p2p-password ${password}`));
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(chalk12.green(` \u2714 Profile '${name}' saved successfully.`));
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(chalk12.green(` \u2714 Profile '${name}' loaded successfully.`));
4283
+ console.log(chalk10.green(` \u2714 Profile '${name}' loaded successfully.`));
5236
4284
  } catch (err) {
5237
- console.log(chalk12.red(` \u2716 ${err.message}`));
4285
+ console.log(chalk10.red(` \u2716 ${err.message}`));
5238
4286
  }
5239
4287
  } else if (sub === "list") {
5240
4288
  const profiles = listProfiles();
5241
- console.log(chalk12.bold("\n pyre Profiles:\n"));
4289
+ console.log(chalk10.bold("\n pyre Profiles:\n"));
5242
4290
  if (profiles.length === 0) {
5243
- console.log(chalk12.dim(" No saved profiles found. Save one with `pyre profile save <name>`."));
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(chalk12.red("Usage: pyre diff <snapshot1.json> <snapshot2.json>"));
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(chalk12.red("Usage: pyre ssh <host>"));
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(chalk12.red("Usage: pyre bench <command>"));
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
- fs9.rmSync(CONFIG_FILE, { force: true });
5309
- console.log(chalk12.green("Config reset to defaults"));
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(chalk12.red("Usage: pyre fleet <host1> [host2] ..."));
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(chalk12.red("Usage: pyre p2p <server|connect> [options]"));
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
- fs9.writeFileSync(opts.out, stripAnsi(output));
5406
- console.log(chalk12.dim(`
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(chalk12.red(`Error: ${err.message}`));
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(chalk12.red("Error: a password is required for P2P server mode. Use --p2p-password <password>"));
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(chalk12.red("Error: --p2p-key is required when --p2p-cert is specified"));
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(chalk12.red("Error: --p2p-cert is required when --p2p-key is specified"));
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(chalk12.bold(`
4514
+ console.log(chalk10.bold(`
5456
4515
  pyre P2P Server`));
5457
- console.log(chalk12.dim(` Host: ${host}`));
5458
- console.log(chalk12.dim(` Port: ${port}`));
5459
- console.log(chalk12.dim(` Protocol: ${tlsEnabled ? "TLS" : "TCP"}`));
5460
- console.log(chalk12.dim(` Password: ${password}`));
5461
- console.log(chalk12.dim(` Interval: ${interval}s`));
5462
- console.log(chalk12.dim(` Detailed: ${detailed ? "on" : "off"}`));
5463
- console.log(chalk12.dim(` Rate limit: ${rateLimit} attempts/min`));
5464
- if (allowList.length > 0) console.log(chalk12.dim(` Allowed IPs: ${allowList.join(", ")}`));
5465
- if (denyList.length > 0) console.log(chalk12.dim(` Denied IPs: ${denyList.join(", ")}`));
5466
- console.log(chalk12.dim(` Waiting for peers...
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(chalk12.dim(` Peers connected: ${info.peersConnected}`));
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(chalk12.red("Error: a password is required for P2P connect mode. Use --p2p-password <password>"));
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${chalk12.dim(msg)}${" ".repeat(40)}\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(chalk12.bold(` pyre P2P Client \u2014 ${data.header.hostname}`));
5530
- lines.push(chalk12.dim(` ${data.timestamp}`));
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(chalk12.bold(`
4606
+ console.log(chalk10.bold(`
5548
4607
  pyre P2P Client`));
5549
- console.log(chalk12.dim(` Protocol: ${protocolLabel}`));
5550
- console.log(chalk12.dim(` Connecting to ${host}:${port}...`));
5551
- console.log(chalk12.dim(` Press Ctrl+C to disconnect
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(chalk12.red(`Connection failed: ${msg}`));
4618
+ console.log(chalk10.red(`Connection failed: ${msg}`));
5560
4619
  if (code === "ECONNREFUSED") {
5561
- console.log(chalk12.yellow(" Hints:"));
5562
- console.log(chalk12.dim(" 1. Start the server: pyre p2p server --p2p-host 0.0.0.0 --p2p-port " + port + " --p2p-password <password>"));
5563
- console.log(chalk12.dim(" 2. Check the server IP with: hostname -I"));
5564
- console.log(chalk12.dim(" 3. Open the port on the server firewall: sudo ufw allow " + port + "/tcp"));
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(chalk12.yellow(" Hints:"));
5567
- console.log(chalk12.dim(" 1. Verify the server IP is correct"));
5568
- console.log(chalk12.dim(" 2. Check that both machines are on the same network/subnet"));
5569
- console.log(chalk12.dim(" 3. Open the port on the server firewall"));
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(chalk12.yellow(" Hints:"));
5572
- console.log(chalk12.dim(" 1. The server at " + host + " is not responding"));
5573
- console.log(chalk12.dim(" 2. Start the server first: pyre p2p server --p2p-host 0.0.0.0 --p2p-port " + port + " --p2p-password <password>"));
5574
- console.log(chalk12.dim(" 3. Check connectivity: ping " + host));
5575
- console.log(chalk12.dim(" 4. Ensure both machines are on the same LAN (ping each other first)"));
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-VACLPHRP.js");
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(chalk12.bold(" pyre info \u2014 Mac System Information"));
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(chalk12.cyan.bold(" \u{1F5A5} Displays:"));
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(chalk12.cyan.bold(" \u23F1 Time Machine Backup:"));
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 ? chalk12.yellow(`Backup in progress (${tmStatus.percent || 0}%)`) : chalk12.green("Idle")}`);
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(chalk12.bold(`
4690
+ console.log(chalk10.bold(`
5632
4691
  pyre ssh \u2014 ${host}`));
5633
- console.log(chalk12.dim(` Running: ${cmd}
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(chalk12.red(` SSH failed: ${err.message}`));
5639
- console.log(chalk12.dim(" Ensure SSH keys are set up or the host is reachable."));
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(chalk12.dim(stderr));
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 = path6.join(process.cwd(), "index.html");
5696
- if (fs9.existsSync(indexPath)) {
5697
- res.end(fs9.readFileSync(indexPath, "utf-8"));
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(chalk12.red(`
4782
+ console.log(chalk10.red(`
5717
4783
  Error: Port ${port} is already in use.`));
5718
- console.log(chalk12.yellow(` Try specifying a different port using --port <port>, e.g.:`));
5719
- console.log(chalk12.dim(` npx tsx src/index.ts web --port ${port + 1}
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(chalk12.red(`Server error: ${err.message}`));
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(chalk12.bold(`
4798
+ console.log(chalk10.bold(`
5733
4799
  pyre web server running on port ${actualPort}`));
5734
- console.log(chalk12.dim(` Local URL: http://localhost:${actualPort}/`));
5735
- console.log(chalk12.hex("#ff6a39").bold(` Network URL: http://${ip}:${actualPort}/ (Accessible on same Wi-Fi / LAN)`));
5736
- console.log(chalk12.dim(` API: http://${ip}:${actualPort}/api`));
5737
- console.log(chalk12.dim(` SSE Stream: http://${ip}:${actualPort}/api/stream`));
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 = path6.join(os3.tmpdir(), "pyre-ui.swift");
5847
- fs9.writeFileSync(scriptPath, swiftScript);
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(chalk12.cyan(`
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(chalk12.dim("UI window closed, shutting down server..."));
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 = path6.join(logDir, `pyre-bench-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.csv`);
5865
- if (!fs9.existsSync(logDir)) fs9.mkdirSync(logDir, { recursive: true });
5866
- const stream = fs9.createWriteStream(file, { flags: "a" });
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(chalk12.bold(`
4940
+ console.log(chalk10.bold(`
5869
4941
  pyre bench`));
5870
- console.log(chalk12.dim(` Command: ${benchCmd}`));
5871
- console.log(chalk12.dim(` Log: ${file}
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(chalk12.green(`
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(chalk12.cyan(` \u26A1 Energy Consumption: ${kWh.toFixed(6)} kWh | Est. Cost: $${cost.toFixed(6)} (@ $${rate}/kWh)`));
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", () => {