pyre-cli 9.3.0 → 9.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1042 @@
1
+ // src/formatters/render.ts
2
+ import chalk2 from "chalk";
3
+
4
+ // src/sparkline.ts
5
+ var BLOCKS = " \u2582\u2583\u2584\u2585\u2586\u2587\u2588";
6
+ function sparkline(values, opts = {}) {
7
+ if (!values.length) return "";
8
+ if (opts.plainText) {
9
+ const first = values[0];
10
+ const last = values[values.length - 1];
11
+ const diff = last - first;
12
+ const sign = diff >= 0 ? "+" : "";
13
+ return `[${first.toFixed(1)} -> ${last.toFixed(1)} (\u0394:${sign}${diff.toFixed(1)})]`;
14
+ }
15
+ let min = opts.min ?? Infinity;
16
+ let max = opts.max ?? -Infinity;
17
+ if (opts.min === void 0 || opts.max === void 0) {
18
+ for (const v of values) {
19
+ if (v < min) min = v;
20
+ if (v > max) max = v;
21
+ }
22
+ }
23
+ const range = max - min || 1;
24
+ return values.map((v) => {
25
+ const idx = Math.round((v - min) / range * (BLOCKS.length - 1));
26
+ const clamped = Math.min(BLOCKS.length - 1, Math.max(0, idx));
27
+ return BLOCKS[clamped];
28
+ }).join("");
29
+ }
30
+ function multiRowBrailleGraph(values, height, opts = {}) {
31
+ if (!values.length || height <= 0) return Array(height).fill("");
32
+ let min = opts.min ?? Infinity;
33
+ let max = opts.max ?? -Infinity;
34
+ if (opts.min === void 0 || opts.max === void 0) {
35
+ for (const v of values) {
36
+ if (v < min) min = v;
37
+ if (v > max) max = v;
38
+ }
39
+ }
40
+ const range = max - min || 1;
41
+ const levels = height * 4;
42
+ const dotsCol0 = [64, 4, 2, 1];
43
+ const dotsCol1 = [128, 32, 16, 8];
44
+ const rows = Array.from({ length: height }, () => []);
45
+ for (let i = 0; i < values.length; i += 2) {
46
+ const v0 = values[i];
47
+ const v1 = i + 1 < values.length ? values[i + 1] : v0;
48
+ const rawSteps0 = Math.round((v0 - min) / range * levels);
49
+ const totalSteps0 = Math.min(levels, Math.max(0, rawSteps0));
50
+ const rawSteps1 = Math.round((v1 - min) / range * levels);
51
+ const totalSteps1 = Math.min(levels, Math.max(0, rawSteps1));
52
+ for (let r = 0; r < height; r++) {
53
+ const rowFromBottom = height - 1 - r;
54
+ const minForThisRow = rowFromBottom * 4;
55
+ const stepsInThisRow0 = Math.min(4, Math.max(0, totalSteps0 - minForThisRow));
56
+ const stepsInThisRow1 = Math.min(4, Math.max(0, totalSteps1 - minForThisRow));
57
+ let code = 10240;
58
+ for (let d = 0; d < stepsInThisRow0; d++) code |= dotsCol0[d];
59
+ for (let d = 0; d < stepsInThisRow1; d++) code |= dotsCol1[d];
60
+ rows[r].push(String.fromCharCode(code));
61
+ }
62
+ }
63
+ return rows.map((r) => r.join(""));
64
+ }
65
+ function brailleGraph(values, opts = {}) {
66
+ if (!values.length) return "";
67
+ let min = opts.min ?? Infinity;
68
+ let max = opts.max ?? -Infinity;
69
+ if (opts.min === void 0 || opts.max === void 0) {
70
+ for (const v of values) {
71
+ if (v < min) min = v;
72
+ if (v > max) max = v;
73
+ }
74
+ }
75
+ const range = max - min || 1;
76
+ const dotsCol0 = [64, 4, 2, 1];
77
+ const dotsCol1 = [128, 32, 16, 8];
78
+ let result = "";
79
+ for (let i = 0; i < values.length; i += 2) {
80
+ const v0 = values[i];
81
+ const v1 = i + 1 < values.length ? values[i + 1] : v0;
82
+ const fill0 = Math.min(4, Math.max(0, Math.round((v0 - min) / range * 4)));
83
+ const fill1 = Math.min(4, Math.max(0, Math.round((v1 - min) / range * 4)));
84
+ let code = 10240;
85
+ for (let d = 0; d < fill0; d++) code |= dotsCol0[d];
86
+ for (let d = 0; d < fill1; d++) code |= dotsCol1[d];
87
+ result += String.fromCharCode(code);
88
+ }
89
+ return result;
90
+ }
91
+
92
+ // src/formatters/themes.ts
93
+ import chalk from "chalk";
94
+ import fs from "fs";
95
+ import os from "os";
96
+ import path from "path";
97
+ var BUILTIN_THEMES = {
98
+ default: {
99
+ border: chalk.hex("#4b5563"),
100
+ cpu: chalk.hex("#22d3ee"),
101
+ mem: chalk.hex("#a78bfa"),
102
+ gpu: chalk.hex("#f472b6"),
103
+ power: chalk.hex("#fbbf24"),
104
+ battery: chalk.hex("#34d399"),
105
+ thermal: chalk.hex("#fb923c"),
106
+ network: chalk.hex("#60a5fa"),
107
+ disk: chalk.hex("#f472b6"),
108
+ graphs: chalk.hex("#22d3ee"),
109
+ process: chalk.hex("#e5e7eb")
110
+ },
111
+ dracula: {
112
+ border: chalk.hex("#6272a4"),
113
+ cpu: chalk.hex("#8be9fd"),
114
+ mem: chalk.hex("#bd93f9"),
115
+ gpu: chalk.hex("#ff5555"),
116
+ power: chalk.hex("#f1fa8c"),
117
+ battery: chalk.hex("#50fa7b"),
118
+ thermal: chalk.hex("#ffb86c"),
119
+ network: chalk.hex("#ff79c6"),
120
+ disk: chalk.hex("#ff5555"),
121
+ graphs: chalk.hex("#8be9fd"),
122
+ process: chalk.hex("#f8f8f2")
123
+ },
124
+ cyberpunk: {
125
+ border: chalk.hex("#ff0055"),
126
+ cpu: chalk.hex("#00ffcc"),
127
+ mem: chalk.hex("#ff00ff"),
128
+ gpu: chalk.hex("#ffff00"),
129
+ power: chalk.hex("#ffff00"),
130
+ battery: chalk.hex("#00ff00"),
131
+ thermal: chalk.hex("#ff6600"),
132
+ network: chalk.hex("#00ffff"),
133
+ disk: chalk.hex("#ff007f"),
134
+ graphs: chalk.hex("#00ffcc"),
135
+ process: chalk.hex("#ffffff")
136
+ },
137
+ monochrome: {
138
+ border: chalk.gray,
139
+ cpu: chalk.white.bold,
140
+ mem: chalk.white.bold,
141
+ gpu: chalk.white.bold,
142
+ power: chalk.white.bold,
143
+ battery: chalk.white.bold,
144
+ thermal: chalk.white.bold,
145
+ network: chalk.white.bold,
146
+ disk: chalk.white.bold,
147
+ graphs: chalk.white.bold,
148
+ process: chalk.white.bold
149
+ },
150
+ nord: {
151
+ border: chalk.hex("#4c566a"),
152
+ cpu: chalk.hex("#88c0d0"),
153
+ mem: chalk.hex("#b48ead"),
154
+ gpu: chalk.hex("#bf616a"),
155
+ power: chalk.hex("#ebcb8b"),
156
+ battery: chalk.hex("#a3be8c"),
157
+ thermal: chalk.hex("#d08770"),
158
+ network: chalk.hex("#81a1c1"),
159
+ disk: chalk.hex("#b48ead"),
160
+ graphs: chalk.hex("#88c0d0"),
161
+ process: chalk.hex("#eceff4")
162
+ },
163
+ gruvbox: {
164
+ border: chalk.hex("#928374"),
165
+ cpu: chalk.hex("#fb4934"),
166
+ mem: chalk.hex("#b8bb26"),
167
+ gpu: chalk.hex("#d3869b"),
168
+ power: chalk.hex("#fabd2f"),
169
+ battery: chalk.hex("#b8bb26"),
170
+ thermal: chalk.hex("#fb4934"),
171
+ network: chalk.hex("#83a598"),
172
+ disk: chalk.hex("#d3869b"),
173
+ graphs: chalk.hex("#fb4934"),
174
+ process: chalk.hex("#ebdbb2")
175
+ }
176
+ };
177
+ function loadCustomThemes() {
178
+ const themes = { ...BUILTIN_THEMES };
179
+ const themesDir = path.join(os.homedir(), ".config", "pyre", "themes");
180
+ try {
181
+ if (fs.existsSync(themesDir)) {
182
+ const files = fs.readdirSync(themesDir);
183
+ for (const file of files) {
184
+ if (file.endsWith(".json")) {
185
+ const name = path.basename(file, ".json");
186
+ try {
187
+ const raw = fs.readFileSync(path.join(themesDir, file), "utf-8");
188
+ const data = JSON.parse(raw);
189
+ const buildColor = (val) => val ? val.startsWith("#") ? chalk.hex(val) : chalk[val] || chalk.white : chalk.white;
190
+ themes[name] = {
191
+ border: buildColor(data.border),
192
+ cpu: buildColor(data.cpu),
193
+ mem: buildColor(data.mem),
194
+ gpu: buildColor(data.gpu),
195
+ power: buildColor(data.power),
196
+ battery: buildColor(data.battery),
197
+ thermal: buildColor(data.thermal),
198
+ network: buildColor(data.network),
199
+ disk: buildColor(data.disk),
200
+ graphs: buildColor(data.graphs),
201
+ process: buildColor(data.process)
202
+ };
203
+ } catch {
204
+ }
205
+ }
206
+ }
207
+ }
208
+ } catch {
209
+ }
210
+ return themes;
211
+ }
212
+ var THEMES = loadCustomThemes();
213
+
214
+ // src/formatters/render.ts
215
+ function visLen(s) {
216
+ return s.replace(/\x1b\[[0-9;]*m/g, "").length;
217
+ }
218
+ function fitVisible(s, width) {
219
+ const w = Math.max(0, width);
220
+ const len = visLen(s);
221
+ if (len === w) return s;
222
+ if (len < w) return s + " ".repeat(w - len);
223
+ let visibleCount = 0;
224
+ let out = "";
225
+ let inAnsi = false;
226
+ for (let i = 0; i < s.length; i++) {
227
+ const char = s[i];
228
+ if (char === "\x1B") {
229
+ inAnsi = true;
230
+ out += char;
231
+ } else if (inAnsi) {
232
+ out += char;
233
+ if (char >= "a" && char <= "z" || char >= "A" && char <= "Z") {
234
+ inAnsi = false;
235
+ }
236
+ } else {
237
+ if (visibleCount < w) {
238
+ out += char;
239
+ visibleCount++;
240
+ } else {
241
+ break;
242
+ }
243
+ }
244
+ }
245
+ out += "\x1B[0m";
246
+ const resLen = visLen(out);
247
+ if (resLen < w) {
248
+ out += " ".repeat(w - resLen);
249
+ }
250
+ return out;
251
+ }
252
+ function truncatePlain(s, width) {
253
+ if (s.length <= width) return s;
254
+ return width <= 1 ? s.slice(0, width) : s.slice(0, width - 1) + "\u2026";
255
+ }
256
+ function panel(title, lines, width, accent, borderAccent, height, rightTitle) {
257
+ const h = height ?? lines.length;
258
+ const contentWidth = Math.max(1, width - 4);
259
+ const leftLabel = title ? ` ${title} ` : "";
260
+ const rightLabel = rightTitle ? ` ${rightTitle} ` : "";
261
+ const innerW = Math.max(0, width - 2);
262
+ let topInner = leftLabel + "\u2500".repeat(Math.max(0, innerW - visLen(leftLabel) - visLen(rightLabel))) + rightLabel;
263
+ if (visLen(topInner) > innerW) {
264
+ topInner = fitVisible(topInner, innerW);
265
+ } else if (visLen(topInner) < innerW) {
266
+ topInner = topInner + "\u2500".repeat(innerW - visLen(topInner));
267
+ }
268
+ const top = borderAccent("\u256D" + topInner + "\u256E");
269
+ const bottom = borderAccent("\u2570" + "\u2500".repeat(innerW) + "\u256F");
270
+ const body = [];
271
+ for (let i = 0; i < h; i++) {
272
+ const line = lines[i] ?? "";
273
+ body.push(`${borderAccent("\u2502")} ${fitVisible(line, contentWidth)} ${borderAccent("\u2502")}`);
274
+ }
275
+ return [top, ...body, bottom];
276
+ }
277
+ function gaugeBar(percent, width = 12) {
278
+ const w = Math.max(1, width);
279
+ const pct = Math.min(100, Math.max(0, percent));
280
+ const totalEighths = Math.round(pct / 100 * w * 8);
281
+ const fullBlocks = Math.floor(totalEighths / 8);
282
+ const remainder = totalEighths % 8;
283
+ const blocks = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589", "\u2588"];
284
+ let str = "\u2588".repeat(fullBlocks);
285
+ if (fullBlocks < w && remainder > 0) {
286
+ str += blocks[remainder];
287
+ }
288
+ const empty = Math.max(0, w - visLen(str));
289
+ const color = pct > 85 ? chalk2.red : pct > 65 ? chalk2.yellow : chalk2.cyan;
290
+ return color(str) + chalk2.dim("\u2500".repeat(empty));
291
+ }
292
+ function hstack(blocks, gap = 2, targetWidth) {
293
+ if (!blocks.length) return [];
294
+ const height = Math.max(...blocks.map((b) => b.length));
295
+ const widths = blocks.map((b) => Math.max(0, ...b.map((l) => visLen(l))));
296
+ const padded = blocks.map((b, i) => {
297
+ const w = widths[i];
298
+ const filler = " ".repeat(w);
299
+ const arr = b.map((line) => fitVisible(line, w));
300
+ while (arr.length < height) arr.push(filler);
301
+ return arr;
302
+ });
303
+ const out = [];
304
+ for (let row = 0; row < height; row++) {
305
+ let line = padded.map((a) => a[row]).join(" ".repeat(gap));
306
+ if (targetWidth !== void 0) {
307
+ line = fitVisible(line, targetWidth);
308
+ }
309
+ out.push(line);
310
+ }
311
+ return out;
312
+ }
313
+ function statRow(label, value, width = 9) {
314
+ const w = Math.max(width, label.length + 1);
315
+ return `${chalk2.dim(label.padEnd(w))}${value}`;
316
+ }
317
+ function gaugeRow(label, percent, contentWidth) {
318
+ const labelW = 9;
319
+ const pctText = `${Math.round(percent)}%`;
320
+ const barW = Math.max(6, contentWidth - labelW - pctText.length - 1);
321
+ const pct = Math.min(100, Math.max(0, percent));
322
+ const totalEighths = Math.round(pct / 100 * barW * 8);
323
+ const fullBlocks = Math.floor(totalEighths / 8);
324
+ const remainder = totalEighths % 8;
325
+ const blocks = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589", "\u2588"];
326
+ let str = "\u2588".repeat(fullBlocks);
327
+ if (fullBlocks < barW && remainder > 0) {
328
+ str += blocks[remainder];
329
+ }
330
+ const empty = Math.max(0, barW - visLen(str));
331
+ const color = pct > 90 ? chalk2.red : pct > 70 ? chalk2.yellow : chalk2.green;
332
+ return `${chalk2.dim(label.padEnd(labelW))}${color(str)}${chalk2.dim("\u2500".repeat(empty))} ${pctText}`;
333
+ }
334
+ function thermalColor(pressureLevel) {
335
+ const symbol = pressureLevel >= 3 ? "\u2715 " : pressureLevel >= 1 ? "\u26A0 " : "\u2713 ";
336
+ if (pressureLevel >= 3) return (s) => chalk2.red(symbol + s);
337
+ if (pressureLevel === 2) return (s) => chalk2.yellow(symbol + s);
338
+ if (pressureLevel === 1) return (s) => chalk2.yellowBright(symbol + s);
339
+ return (s) => chalk2.green(symbol + s);
340
+ }
341
+ function capacityColor(percent) {
342
+ const symbol = percent < 80 ? "\u2715 " : percent < 90 ? "\u26A0 " : "\u2713 ";
343
+ if (percent < 80) return (s) => chalk2.red(symbol + s);
344
+ if (percent < 90) return (s) => chalk2.yellow(symbol + s);
345
+ return (s) => chalk2.green(symbol + s);
346
+ }
347
+ function formatBytes(bytes) {
348
+ if (isNaN(bytes) || bytes < 0) return "0 B";
349
+ if (bytes < 1024) return `${Math.round(bytes)} B`;
350
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
351
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
352
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
353
+ }
354
+ function celsiusToFahrenheit(c) {
355
+ return c * (9 / 5) + 32;
356
+ }
357
+ function formatTemp(c) {
358
+ return `${c.toFixed(1)}\xB0C / ${celsiusToFahrenheit(c).toFixed(1)}\xB0F`;
359
+ }
360
+ function formatTempUnit(c, unit = "c") {
361
+ if (unit === "f") {
362
+ return `${(c * (9 / 5) + 32).toFixed(1)}\xB0F`;
363
+ }
364
+ return `${c.toFixed(1)}\xB0C`;
365
+ }
366
+ function cpuCard(data, contentWidth) {
367
+ const lines = [
368
+ statRow("Brand", truncatePlain(data.cpu.brand, Math.max(4, contentWidth - 8))),
369
+ statRow("Cores", `${data.cpu.physicalCores}/${data.cpu.cores} phys/log`),
370
+ statRow("Freq", `${data.cpu.frequency} MHz`),
371
+ gaugeRow("Usage", data.cpu.usage, contentWidth),
372
+ statRow("Load", data.cpu.loadAvg.map((l) => l.toFixed(2)).join(" "))
373
+ ];
374
+ if (data.cpu.clusters && data.cpu.clusters.length) {
375
+ for (const cl of data.cpu.clusters) {
376
+ lines.push(statRow(cl.name, `${cl.frequencyMhz} MHz \xB7 eff ${cl.effectiveUsagePercent}% \xB7 act ${cl.activeResidencyPercent}%`));
377
+ }
378
+ }
379
+ if (data.cpu.coreUsage && data.cpu.coreUsage.length) {
380
+ const slotW = 2;
381
+ const maxCores = Math.max(1, Math.floor((contentWidth - 10) / (slotW + 1)));
382
+ const cores = data.cpu.coreUsage.slice(0, maxCores);
383
+ const bars = cores.map((u) => {
384
+ const fill = Math.round(Math.min(100, u) / 100 * slotW);
385
+ const str = "\u2588".repeat(Math.max(0, fill)) + "\u2591".repeat(slotW - fill);
386
+ const color = u > 90 ? chalk2.red : u > 70 ? chalk2.yellow : chalk2.green;
387
+ return color(str);
388
+ });
389
+ lines.push(statRow("Cores", bars.join(" ")));
390
+ }
391
+ if (data.cpu.temperature) lines.push(statRow("Temp", formatTemp(data.cpu.temperature)));
392
+ return lines;
393
+ }
394
+ function memCard(data, contentWidth) {
395
+ return [
396
+ statRow("Total", formatBytes(data.memory.total)),
397
+ statRow("Used", formatBytes(data.memory.used)),
398
+ statRow("Cached", formatBytes(data.memory.total - data.memory.free - data.memory.used)),
399
+ statRow("Free", formatBytes(data.memory.free)),
400
+ gaugeRow("Usage", data.memory.usagePercent, contentWidth),
401
+ statRow("Swap", `${formatBytes(data.memory.swapUsed)} / ${formatBytes(data.memory.swapTotal)}`)
402
+ ];
403
+ }
404
+ function gpuCard(data, contentWidth) {
405
+ if (!data.gpu) return null;
406
+ const lines = [];
407
+ lines.push(statRow("Model", truncatePlain(data.gpu.model, Math.max(4, contentWidth - 8))));
408
+ lines.push(statRow("Memory", formatBytes(data.gpu.memory)));
409
+ lines.push(statRow("Util", `${data.gpu.utilization}%`));
410
+ if (data.gpu.temperature) lines.push(statRow("Temp", formatTemp(data.gpu.temperature)));
411
+ lines.push(statRow("Procs", String(data.gpu.processes)));
412
+ return lines;
413
+ }
414
+ function powerCard(data) {
415
+ const lines = [];
416
+ if (data.power) {
417
+ if (data.power.cpuWatts !== void 0) lines.push(statRow("CPU", `${data.power.cpuWatts.toFixed(2)} W`));
418
+ if (data.power.gpuWatts !== void 0) lines.push(statRow("GPU", `${data.power.gpuWatts.toFixed(2)} W`));
419
+ lines.push(statRow("ANE", `${(data.power.aneWatts ?? 0).toFixed(2)} W`));
420
+ if (data.power.combinedWatts !== void 0) lines.push(statRow("Total", `${data.power.combinedWatts.toFixed(2)} W`));
421
+ }
422
+ if (data.battery?.powerWatts !== void 0 && data.battery.powerWatts > 0) {
423
+ lines.push(statRow("Battery", `${data.battery.powerWatts.toFixed(2)} W`));
424
+ }
425
+ return lines.length ? lines : null;
426
+ }
427
+ function batteryCard(data, contentWidth) {
428
+ if (!data.battery) return null;
429
+ const valW = Math.max(6, contentWidth - 10);
430
+ const lines = [
431
+ statRow("Level", `${data.battery.level}% (${data.battery.state})`),
432
+ statRow("Time", data.battery.timeRemaining),
433
+ statRow("Cycles", String(data.battery.cycles ?? "N/A")),
434
+ statRow("Condition", data.battery.condition ?? "N/A")
435
+ ];
436
+ if (data.battery.maxCapacityPercent !== void 0) {
437
+ lines.push(statRow("Capacity", capacityColor(data.battery.maxCapacityPercent)(`${data.battery.maxCapacityPercent}%`)));
438
+ }
439
+ lines.push(statRow("Source", data.battery.powerSource));
440
+ if (data.battery.estimatedTimeToEmpty && data.battery.state !== "charged") {
441
+ lines.push(statRow("Est. Empty", data.battery.estimatedTimeToEmpty));
442
+ }
443
+ if (data.battery.dischargeRatePerHour !== void 0 && data.battery.state !== "charged") {
444
+ lines.push(statRow("Disch. Rate", `${data.battery.dischargeRatePerHour}%/h`));
445
+ }
446
+ return lines;
447
+ }
448
+ function thermalCard(data, contentWidth) {
449
+ const valW = Math.max(6, contentWidth - 10);
450
+ const lines = [statRow("State", thermalColor(data.thermal.pressureLevel)(truncatePlain(data.thermal.state, valW)))];
451
+ if (data.thermal.temperatures) {
452
+ for (const [k, v] of Object.entries(data.thermal.temperatures)) {
453
+ lines.push(statRow(k.replace("_", " "), v !== null && v !== void 0 ? formatTemp(v) : "N/A"));
454
+ }
455
+ }
456
+ if (data.thermal.error) lines.push(statRow("Error", truncatePlain(data.thermal.error, valW)));
457
+ return lines;
458
+ }
459
+ function networkCard(data, contentWidth) {
460
+ const valW = Math.max(6, contentWidth - 8);
461
+ return [
462
+ statRow("Iface", truncatePlain(`${data.network.interface} (${data.network.ip})`, valW)),
463
+ statRow("RX", `${formatBytes(data.network.rxBytes)} / ${data.network.rxPackets} pkt`),
464
+ statRow("TX", `${formatBytes(data.network.txBytes)} / ${data.network.txPackets} pkt`)
465
+ ];
466
+ }
467
+ function tasksCard(data, contentWidth) {
468
+ if (!data.tasks || !data.tasks.length) return null;
469
+ const lines = [];
470
+ for (const task of data.tasks.slice(0, 6)) {
471
+ const cmd = truncatePlain(task.command, Math.max(4, contentWidth - 28));
472
+ lines.push(statRow(`${task.pid}`, `${task.user.padEnd(8)} ${task.cpu.toFixed(1).padStart(5)}% ${task.mem.toFixed(1).padStart(5)}% ${task.state} ${cmd}`));
473
+ }
474
+ return lines;
475
+ }
476
+ function header(data, width, badges = [], borderAccent = THEMES.default.border) {
477
+ const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
478
+ const left = `${chalk2.hex("#ff5500").bold(" PYRE ")} ${chalk2.bold(data.header.hostname)} ${chalk2.dim(data.header.os)} ${chalk2.dim("up " + data.header.uptime)}`;
479
+ const right = `${chalk2.dim(now)} ${badges.join(" ")}`;
480
+ const gap = Math.max(1, width - visLen(left) - visLen(right));
481
+ const line = fitVisible(left + " ".repeat(gap) + right, width);
482
+ return [line, borderAccent("\u2500".repeat(width))];
483
+ }
484
+ var TAB_DEFS = [
485
+ { id: "cpu", label: "CPU", key: "1" },
486
+ { id: "mem", label: "Memory", key: "2" },
487
+ { id: "gpu", label: "GPU", key: "3" },
488
+ { id: "power", label: "Power", key: "4" },
489
+ { id: "battery", label: "Battery", key: "5" },
490
+ { id: "thermal", label: "Thermal", key: "6" },
491
+ { id: "network", label: "Network", key: "7" },
492
+ { id: "packets", label: "Packets", key: "8" },
493
+ { id: "tasks", label: "Tasks", key: "9" },
494
+ { id: "disk", label: "Disk", key: "0" },
495
+ { id: "process", label: "Process", key: "P" },
496
+ { id: "p2p", label: "P2P", key: "R" },
497
+ { id: "anomalies", label: "Anomalies", key: "A" }
498
+ ];
499
+ var TAB_BAR_INDENT = 2;
500
+ var TAB_SEPARATOR_WIDTH = 3;
501
+ function getTabHitboxes() {
502
+ const boxes = [];
503
+ let col = TAB_BAR_INDENT;
504
+ TAB_DEFS.forEach((t, i) => {
505
+ const text = `[${t.key}]${t.label}`;
506
+ const start = col;
507
+ const end = col + text.length;
508
+ boxes.push({ id: t.id, start, end });
509
+ col = end;
510
+ if (i < TAB_DEFS.length - 1) col += TAB_SEPARATOR_WIDTH;
511
+ });
512
+ return boxes;
513
+ }
514
+ var TAB_BAR_ROW = 4;
515
+ function tabBar(activePanel, width, theme) {
516
+ const rendered = TAB_DEFS.map((t) => {
517
+ const isActive = activePanel === t.id;
518
+ const label = isActive ? chalk2.bold(t.label) : chalk2.dim(t.label);
519
+ const keyHint = chalk2.dim(`[${t.key}]`);
520
+ return `${keyHint}${label}`;
521
+ });
522
+ const separator = chalk2.dim(" \u2502 ");
523
+ const joined = rendered.join(separator);
524
+ const indent = " ";
525
+ const available = width - visLen(indent);
526
+ let result = joined;
527
+ if (visLen(joined) > available) {
528
+ let used = 0;
529
+ const kept = [];
530
+ for (let i = 0; i < rendered.length; i++) {
531
+ const tabW = visLen(rendered[i]) + (kept.length > 0 ? visLen(separator) : 0);
532
+ if (used + tabW > available && kept.length > 0) break;
533
+ kept.push(rendered[i]);
534
+ used += tabW;
535
+ }
536
+ if (kept.length < rendered.length) {
537
+ result = kept.join(separator) + chalk2.dim("\u22EF");
538
+ }
539
+ }
540
+ const pad = Math.max(0, available - visLen(result));
541
+ return fitVisible(chalk2.dim(indent) + result + " ".repeat(pad), width);
542
+ }
543
+ function sortProcesses(processes, sortBy) {
544
+ const copy = [...processes];
545
+ switch (sortBy) {
546
+ case "pid":
547
+ return copy.sort((a, b) => a.pid - b.pid);
548
+ case "mem":
549
+ return copy.sort((a, b) => b.mem - a.mem);
550
+ case "user":
551
+ return copy.sort((a, b) => a.user.localeCompare(b.user));
552
+ case "command":
553
+ return copy.sort((a, b) => a.command.localeCompare(b.command));
554
+ case "state":
555
+ return copy.sort((a, b) => a.state.localeCompare(b.state));
556
+ case "threads":
557
+ return copy.sort((a, b) => b.threads - a.threads);
558
+ case "runtime":
559
+ return copy.sort((a, b) => b.runtime - a.runtime);
560
+ default:
561
+ return copy.sort((a, b) => b.cpu - a.cpu);
562
+ }
563
+ }
564
+ function pctColor(v) {
565
+ return v > 80 ? chalk2.red : v > 50 ? chalk2.yellow : chalk2.dim;
566
+ }
567
+ function processTableLines(processes, contentWidth, borderAccent = THEMES.default.border, selectedIdx = -1) {
568
+ const pidW = 8, ppidW = 8, userW = 12, cpuW = 8, memW = 8, stateW = 8, thrW = 6, rtW = 10;
569
+ const cmdW = Math.max(10, contentWidth - pidW - ppidW - userW - cpuW - memW - stateW - thrW - rtW - 8);
570
+ 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));
571
+ const rows = processes.map((p, idx) => {
572
+ const isSelected = idx === selectedIdx;
573
+ const cpuStr = pctColor(p.cpu)(`${p.cpu.toFixed(1)}`.padEnd(cpuW));
574
+ const memStr = pctColor(p.mem)(`${p.mem.toFixed(1)}`.padEnd(memW));
575
+ const stateStr = chalk2.dim(p.state.padEnd(stateW));
576
+ const thrStr = String(p.threads).padEnd(thrW);
577
+ const rtStr = formatRuntime(p.runtime).padEnd(rtW);
578
+ 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);
579
+ return isSelected ? chalk2.bgCyan.black(line) : line;
580
+ });
581
+ return [head, borderAccent("\u2500".repeat(contentWidth)), ...rows];
582
+ }
583
+ function formatRuntime(seconds) {
584
+ if (seconds < 60) return `${seconds}s`;
585
+ const mins = Math.floor(seconds / 60);
586
+ if (mins < 60) return `${mins}m ${seconds % 60}s`;
587
+ const hrs = Math.floor(mins / 60);
588
+ return `${hrs}h ${mins % 60}m`;
589
+ }
590
+ function buildProcessTree(processes) {
591
+ const map = /* @__PURE__ */ new Map();
592
+ const roots = [];
593
+ for (const p of processes) map.set(p.pid, p);
594
+ for (const p of processes) {
595
+ if (p.ppid === 0 || !map.has(p.ppid)) roots.push(p);
596
+ }
597
+ const childrenMap = /* @__PURE__ */ new Map();
598
+ for (const p of processes) {
599
+ const list = childrenMap.get(p.ppid) || [];
600
+ list.push(p);
601
+ childrenMap.set(p.ppid, list);
602
+ }
603
+ for (const list of childrenMap.values()) {
604
+ list.sort((a, b) => b.cpu - a.cpu);
605
+ }
606
+ const lines = [];
607
+ const visited = /* @__PURE__ */ new Set();
608
+ function renderNode(proc, prefix, isLast) {
609
+ if (visited.has(proc.pid)) return;
610
+ visited.add(proc.pid);
611
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
612
+ const pidStr = String(proc.pid).padEnd(8);
613
+ const cpuStr = pctColor(proc.cpu)(`${proc.cpu.toFixed(1)}%`.padEnd(8));
614
+ const memStr = pctColor(proc.mem)(`${proc.mem.toFixed(1)}%`.padEnd(8));
615
+ const stateStr = chalk2.dim(proc.state.padEnd(8));
616
+ const thrStr = String(proc.threads).padEnd(6);
617
+ const cmd = truncatePlain(proc.command, Math.max(10, 100 - pidStr.length - cpuStr.length - memStr.length - stateStr.length - thrStr.length - 10));
618
+ lines.push(`${prefix}${connector}${pidStr}${cpuStr}${memStr}${stateStr}${thrStr} ${cmd}`);
619
+ const children = childrenMap.get(proc.pid);
620
+ if (children) {
621
+ let idx = 0;
622
+ let total = 0;
623
+ for (const c of children) {
624
+ if (!visited.has(c.pid)) total++;
625
+ }
626
+ for (const child of children) {
627
+ if (visited.has(child.pid)) continue;
628
+ const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
629
+ renderNode(child, nextPrefix, ++idx === total);
630
+ }
631
+ }
632
+ }
633
+ roots.sort((a, b) => b.cpu - a.cpu);
634
+ roots.forEach((root, i) => renderNode(root, "", i === roots.length - 1));
635
+ return lines;
636
+ }
637
+ function diskTableLines(disks, contentWidth, borderAccent = THEMES.default.border) {
638
+ const fsW = 20, sizeW = 10, usedW = 10, availW = 10, capW = 8;
639
+ const mountW = Math.max(8, contentWidth - fsW - sizeW - usedW - availW - capW - 5);
640
+ 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));
641
+ const rows = disks.map((d) => {
642
+ const capNum = parseInt(d.capacity, 10) || 0;
643
+ const capStr = pctColor(capNum)(d.capacity.padEnd(capW));
644
+ 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);
645
+ });
646
+ return [head, borderAccent("\u2500".repeat(contentWidth)), ...rows];
647
+ }
648
+ function detailPanelTitle(panel2, trackedPid) {
649
+ const map = {
650
+ cpu: "CPU",
651
+ mem: "Memory",
652
+ gpu: "GPU",
653
+ power: "Power",
654
+ battery: "Battery",
655
+ thermal: "Thermal",
656
+ network: "Network",
657
+ packets: "Packets",
658
+ tasks: "Tasks",
659
+ disk: "Disk",
660
+ process: trackedPid !== void 0 && trackedPid !== null ? `Processes (Following PID ${trackedPid})` : "Processes"
661
+ };
662
+ return map[panel2] || panel2;
663
+ }
664
+ function activeDetailLines(data, activePanel, width, opts = {}) {
665
+ const contentWidth = width - 4;
666
+ const themeName = opts.theme || "default";
667
+ const theme = THEMES[themeName] || THEMES.default;
668
+ switch (activePanel) {
669
+ case "cpu":
670
+ return cpuCard(data, contentWidth);
671
+ case "mem":
672
+ return memCard(data, contentWidth);
673
+ case "gpu": {
674
+ const gpu = gpuCard(data, contentWidth);
675
+ return gpu ?? ["No GPU data available"];
676
+ }
677
+ case "power": {
678
+ const power = powerCard(data);
679
+ if (!power && !data.battery) return ["No power data available"];
680
+ const lines = [];
681
+ if (power) lines.push(...power);
682
+ if (data.battery?.powerWatts !== void 0 && data.battery.powerWatts > 0) {
683
+ lines.push(statRow("Battery", `${data.battery.powerWatts.toFixed(2)} W`));
684
+ }
685
+ return lines.length ? lines : ["No power data available"];
686
+ }
687
+ case "battery":
688
+ return data.battery ? batteryCard(data, contentWidth) : ["No battery data available"];
689
+ case "thermal":
690
+ return thermalCard(data, contentWidth);
691
+ case "network":
692
+ return networkCard(data, contentWidth);
693
+ case "packets": {
694
+ if (!data.packets) return ["No packet data available"];
695
+ const lines = [];
696
+ lines.push(statRow("Total", `${data.packets.totalPackets} pkt`));
697
+ lines.push(statRow("RX", `${data.packets.rxPackets} pkt`));
698
+ lines.push(statRow("TX", `${data.packets.txPackets} pkt`));
699
+ lines.push(statRow("Conns", `${data.packets.connections} TCP`));
700
+ if (data.packets.interfaces && data.packets.interfaces.length) {
701
+ lines.push(statRow("Ifaces", `${data.packets.interfaces.length} active`));
702
+ for (const iface of data.packets.interfaces.slice(0, 8)) {
703
+ lines.push(statRow(` ${iface.iface}`, `RX ${formatBytes(iface.rxBytes)} / TX ${formatBytes(iface.txBytes)}`));
704
+ }
705
+ }
706
+ if (data.packets.allProcesses && data.packets.allProcesses.length) {
707
+ lines.push(statRow("Net Procs", `${data.packets.allProcesses.length} total`));
708
+ for (const proc of data.packets.allProcesses.slice(0, 10)) {
709
+ lines.push(statRow(` ${proc.pid}`, `${truncatePlain(proc.command, 20)} TX ${formatBytes(proc.txBytes)}`));
710
+ }
711
+ }
712
+ return lines;
713
+ }
714
+ case "tasks":
715
+ return data.tasks?.length ? tasksCard(data, contentWidth) : ["No task data available"];
716
+ case "disk":
717
+ return data.disk.length ? diskTableLines(data.disk, width - 4, theme.border) : ["No disk data available"];
718
+ case "process": {
719
+ const filtered = opts.filter ? data.processes.filter((p) => p.command.toLowerCase().includes(opts.filter.toLowerCase())) : data.processes;
720
+ const sorted = sortProcesses(filtered, opts.sortBy ?? "cpu");
721
+ let selectedIdx = opts.selectedProcessIndex ?? -1;
722
+ const isTracking = opts.trackedPid !== void 0 && opts.trackedPid !== null;
723
+ if (isTracking) {
724
+ const trackedIdx = sorted.findIndex((p) => p.pid === opts.trackedPid);
725
+ if (trackedIdx >= 0) {
726
+ selectedIdx = trackedIdx;
727
+ }
728
+ }
729
+ const trackingLines = [];
730
+ if (isTracking) {
731
+ const tp = sorted.find((p) => p.pid === opts.trackedPid);
732
+ if (tp) {
733
+ const cpuColor = tp.cpu > 50 ? chalk2.red.bold : tp.cpu > 20 ? chalk2.yellow : chalk2.green;
734
+ const memColor = tp.mem > 50 ? chalk2.red.bold : tp.mem > 20 ? chalk2.yellow : chalk2.green;
735
+ trackingLines.push(chalk2.bgHex("#1a1a2e").hex("#e94560").bold(` \u25C9 FOLLOWING PID ${tp.pid} \u2014 ${truncatePlain(tp.command, Math.max(10, contentWidth - 30))} `));
736
+ trackingLines.push(
737
+ ` ${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}`
738
+ );
739
+ trackingLines.push(chalk2.dim(` Full: ${tp.command}`));
740
+ trackingLines.push(theme.border("\u2500".repeat(contentWidth)));
741
+ } else {
742
+ trackingLines.push(chalk2.yellow(` \u25C9 PID ${opts.trackedPid} exited \u2014 no longer in process list`));
743
+ trackingLines.push(theme.border("\u2500".repeat(contentWidth)));
744
+ }
745
+ }
746
+ const availableRows = Math.max(1, (opts.processLimit || sorted.length) - trackingLines.length);
747
+ let offset = 0;
748
+ if (selectedIdx >= 0) {
749
+ if (selectedIdx >= availableRows) {
750
+ offset = selectedIdx - availableRows + 1;
751
+ }
752
+ }
753
+ const visible = sorted.slice(offset, offset + availableRows);
754
+ if (opts.treeView) {
755
+ return [...trackingLines, ...buildProcessTree(visible)];
756
+ }
757
+ return [...trackingLines, ...processTableLines(visible, width - 4, theme.border, selectedIdx - offset)];
758
+ }
759
+ case "anomalies": {
760
+ if (!opts.anomalyHistory || opts.anomalyHistory.length === 0) return ["No anomalies detected yet."];
761
+ const lines = [];
762
+ const sorted = [...opts.anomalyHistory].sort((a, b) => (b.timestamp?.getTime() || 0) - (a.timestamp?.getTime() || 0));
763
+ for (const a of sorted) {
764
+ const color = a.severity === "critical" ? chalk2.red.bold : chalk2.yellow;
765
+ const direction = a.zScore > 0 ? "\u2191 spike" : "\u2193 drop";
766
+ const valStr = a.metric.toLowerCase().includes("net") ? `${formatBytes(a.value)}/s` : `${a.value.toFixed(1)}%`;
767
+ const timeStr = a.timestamp ? a.timestamp.toLocaleTimeString() : "";
768
+ lines.push(color(` ${timeStr.padEnd(10)} \u26A0 ${a.metric.padEnd(8)}: ${valStr.padEnd(12)} (${direction}, \u03C3=${a.zScore.toFixed(1)})`));
769
+ }
770
+ return lines;
771
+ }
772
+ default:
773
+ return null;
774
+ }
775
+ }
776
+ function anomalyPanel(anomalies, width, theme) {
777
+ const lines = [];
778
+ for (const a of anomalies) {
779
+ const color = a.severity === "critical" ? chalk2.red.bold : chalk2.yellow;
780
+ const direction = a.zScore > 0 ? "\u2191 spike" : "\u2193 drop";
781
+ const valStr = a.metric.toLowerCase().includes("net") ? `${formatBytes(a.value)}/s` : `${a.value.toFixed(1)}%`;
782
+ lines.push(color(` \u26A0 ${a.metric}: ${valStr} (${direction}, \u03C3=${a.zScore.toFixed(1)})`));
783
+ }
784
+ return lines;
785
+ }
786
+ function btopCpuBox(data, width, theme, opts) {
787
+ const contentWidth = width - 4;
788
+ const rightTitle = `${data.header.hostname} \xB7 up ${data.header.uptime} \xB7 ${data.cpu.usage.toFixed(0)}%`;
789
+ if (contentWidth >= 65) {
790
+ const leftW = Math.floor(contentWidth * 0.48);
791
+ const rightW = contentWidth - leftW - 2;
792
+ const leftLines = [];
793
+ const cpuHistory = opts.history?.cpuUsage || [];
794
+ let historyLabel = "CPU Usage History";
795
+ if (cpuHistory.length > 0) {
796
+ const avg = Math.round(cpuHistory.reduce((a, b) => a + b, 0) / cpuHistory.length);
797
+ const max = Math.round(Math.max(...cpuHistory));
798
+ historyLabel += ` (avg ${avg}% \xB7 max ${max}%)`;
799
+ }
800
+ leftLines.push(fitVisible(theme.cpu(historyLabel), leftW));
801
+ if (cpuHistory.length > 1) {
802
+ const graphRows = multiRowBrailleGraph(cpuHistory, 3, { min: 0, max: 100 });
803
+ for (const gr of graphRows) {
804
+ leftLines.push(theme.cpu(fitVisible(gr, leftW)));
805
+ }
806
+ } else {
807
+ leftLines.push(theme.cpu(fitVisible(gaugeBar(data.cpu.usage, leftW), leftW)));
808
+ leftLines.push(" ".repeat(leftW));
809
+ leftLines.push(" ".repeat(leftW));
810
+ }
811
+ const rightLines = [];
812
+ rightLines.push(fitVisible(chalk2.bold(truncatePlain(data.cpu.brand, rightW)), rightW));
813
+ const cpuBarW = Math.max(6, rightW - 18);
814
+ const tempStr = data.cpu.temperature ? formatTempUnit(data.cpu.temperature, opts.tempUnit) : "";
815
+ rightLines.push(fitVisible(`CPU ${gaugeBar(data.cpu.usage, cpuBarW)} ${data.cpu.usage.toFixed(0).padStart(3)}% ${tempStr}`, rightW));
816
+ const loadStr = data.cpu.loadAvg.map((l) => l.toFixed(2)).join(" ");
817
+ rightLines.push(fitVisible(`${chalk2.dim("Load:")} ${loadStr}`, rightW));
818
+ if (data.cpu.coreUsage && data.cpu.coreUsage.length) {
819
+ const coreColW = Math.floor((rightW - 1) / 2);
820
+ const half = Math.ceil(data.cpu.coreUsage.length / 2);
821
+ for (let i = 0; i < half; i++) {
822
+ const u1 = data.cpu.coreUsage[i];
823
+ const u2 = data.cpu.coreUsage[i + half];
824
+ const c1BarW = Math.max(3, coreColW - 8);
825
+ const c1Str = `C${i} ${gaugeBar(u1, c1BarW)} ${Math.round(u1)}%`;
826
+ const c2Str = u2 !== void 0 ? `C${i + half} ${gaugeBar(u2, c1BarW)} ${Math.round(u2)}%` : "";
827
+ rightLines.push(fitVisible(fitVisible(c1Str, coreColW) + " " + fitVisible(c2Str, coreColW), rightW));
828
+ }
829
+ }
830
+ if (data.gpu) {
831
+ rightLines.push(fitVisible(`GPU ${gaugeBar(data.gpu.utilization, cpuBarW)} ${data.gpu.utilization}% ${formatBytes(data.gpu.memory)}`, rightW));
832
+ }
833
+ const combinedBody = hstack([leftLines, rightLines], 2, contentWidth);
834
+ return panel("1 cpu", combinedBody, width, theme.cpu, theme.border, combinedBody.length, rightTitle);
835
+ } else {
836
+ const lines = [];
837
+ lines.push(chalk2.bold(truncatePlain(data.cpu.brand, contentWidth)));
838
+ lines.push(`CPU ${gaugeBar(data.cpu.usage, Math.max(4, contentWidth - 12))} ${data.cpu.usage.toFixed(0)}%`);
839
+ if (opts.history?.cpuUsage.length) {
840
+ const gMode = opts.graphMode || "spark";
841
+ const gStr = gMode === "bar" ? sparkline(opts.history.cpuUsage, { min: 0, max: 100 }) : brailleGraph(opts.history.cpuUsage, { min: 0, max: 100 });
842
+ lines.push(theme.cpu(gStr));
843
+ }
844
+ lines.push(`Load: ${data.cpu.loadAvg.map((l) => l.toFixed(2)).join(" ")}`);
845
+ return panel("1 cpu", lines, width, theme.cpu, theme.border, lines.length, rightTitle);
846
+ }
847
+ }
848
+ function btopLeftColumn(data, width, theme, opts) {
849
+ const contentWidth = width - 4;
850
+ const out = [];
851
+ const layout = opts.panelLayout || ["mem", "disk", "net"];
852
+ for (const panelName of layout) {
853
+ if (panelName === "mem") {
854
+ const memLines = [];
855
+ memLines.push(`RAM [${gaugeBar(data.memory.usagePercent, Math.max(4, contentWidth - 12))}] ${data.memory.usagePercent}%`);
856
+ memLines.push(`Used: ${formatBytes(data.memory.used)} / ${formatBytes(data.memory.total)}`);
857
+ memLines.push(`Free: ${formatBytes(data.memory.free)} \xB7 Cache: ${formatBytes(data.memory.total - data.memory.free - data.memory.used)}`);
858
+ if (data.memory.swapTotal > 0) {
859
+ const swapPct = Math.round(data.memory.swapUsed / data.memory.swapTotal * 100);
860
+ memLines.push(`Swap [${gaugeBar(swapPct, Math.max(4, contentWidth - 12))}] ${swapPct}%`);
861
+ }
862
+ out.push(...panel("2 mem", memLines, width, theme.mem, theme.border, memLines.length, `${formatBytes(data.memory.used)}`));
863
+ } else if (panelName === "disk" && data.disk.length) {
864
+ const diskLines = [];
865
+ for (const d of data.disk.slice(0, 2)) {
866
+ const capNum = parseInt(d.capacity, 10) || 0;
867
+ const mountShort = truncatePlain(d.mountpoint, 8);
868
+ let ioStr = "";
869
+ if (d.readBytesSec !== void 0 || d.writeBytesSec !== void 0) {
870
+ const rStr = formatBytes(d.readBytesSec || 0);
871
+ const wStr = formatBytes(d.writeBytesSec || 0);
872
+ ioStr = ` R:${rStr}/s W:${wStr}/s`;
873
+ }
874
+ diskLines.push(`${mountShort} ${d.used}/${d.size} [${gaugeBar(capNum, Math.max(3, contentWidth - 18 - ioStr.length))}] ${d.capacity}${ioStr}`);
875
+ }
876
+ const totalRead = data.disk.reduce((acc, d) => acc + (d.readBytesSec || 0), 0);
877
+ const totalWrite = data.disk.reduce((acc, d) => acc + (d.writeBytesSec || 0), 0);
878
+ const diskHeaderRight = `R:${formatBytes(totalRead)}/s W:${formatBytes(totalWrite)}/s`;
879
+ out.push(...panel("0 disk io", diskLines, width, theme.disk, theme.border, diskLines.length, diskHeaderRight));
880
+ } else if (panelName === "net") {
881
+ const netLines = [];
882
+ const rxRate = opts.history?.netRxRate.length ? opts.history.netRxRate[opts.history.netRxRate.length - 1] : 0;
883
+ const txRate = opts.history?.netTxRate.length ? opts.history.netTxRate[opts.history.netTxRate.length - 1] : 0;
884
+ netLines.push(`down \u25BC ${formatBytes(rxRate)}/s (${formatBytes(data.network.rxBytes)})`);
885
+ if (opts.history?.netRxRate.length) {
886
+ const gMode = opts.graphMode || "spark";
887
+ const gStr = gMode === "bar" ? sparkline(opts.history.netRxRate, {}) : brailleGraph(opts.history.netRxRate, {});
888
+ netLines.push(chalk2.cyan(fitVisible(gStr, contentWidth)));
889
+ }
890
+ netLines.push(`up \u25B2 ${formatBytes(txRate)}/s (${formatBytes(data.network.txBytes)})`);
891
+ if (opts.history?.netTxRate.length) {
892
+ const gMode = opts.graphMode || "spark";
893
+ const gStr = gMode === "bar" ? sparkline(opts.history.netTxRate, {}) : brailleGraph(opts.history.netTxRate, {});
894
+ netLines.push(theme.network(fitVisible(gStr, contentWidth)));
895
+ }
896
+ out.push(...panel("7 net", netLines, width, theme.network, theme.border, netLines.length, `${data.network.interface}`));
897
+ }
898
+ }
899
+ return out;
900
+ }
901
+ function btopProcBox(data, width, targetHeight, theme, opts) {
902
+ const contentWidth = width - 4;
903
+ const filtered = opts.filter ? data.processes.filter((p) => p.command.toLowerCase().includes(opts.filter.toLowerCase())) : data.processes;
904
+ const sorted = sortProcesses(filtered, opts.sortBy ?? "cpu");
905
+ const headerRowOverhead = 2;
906
+ const tableHeaderOverhead = 2;
907
+ const isTracking = opts.trackedPid !== void 0 && opts.trackedPid !== null;
908
+ const trackingLines = [];
909
+ if (isTracking) {
910
+ const tp = sorted.find((p) => p.pid === opts.trackedPid);
911
+ if (tp) {
912
+ const cpuColor = tp.cpu > 50 ? chalk2.red.bold : tp.cpu > 20 ? chalk2.yellow : chalk2.green;
913
+ const memColor = tp.mem > 50 ? chalk2.red.bold : tp.mem > 20 ? chalk2.yellow : chalk2.green;
914
+ trackingLines.push(chalk2.bgHex("#1a1a2e").hex("#e94560").bold(` \u25C9 PID ${tp.pid} ${truncatePlain(tp.command, Math.max(6, contentWidth - 20))} `));
915
+ trackingLines.push(
916
+ ` ${cpuColor("CPU " + tp.cpu.toFixed(1) + "%")} ${memColor("MEM " + tp.mem.toFixed(1) + "%")} ${chalk2.dim(tp.state)} THR:${tp.threads} ${formatRuntime(tp.runtime)}`
917
+ );
918
+ trackingLines.push(theme.border("\u2500".repeat(contentWidth)));
919
+ } else {
920
+ trackingLines.push(chalk2.yellow(` \u25C9 PID ${opts.trackedPid} exited`));
921
+ trackingLines.push(theme.border("\u2500".repeat(contentWidth)));
922
+ }
923
+ }
924
+ const availableRows = Math.max(1, targetHeight - headerRowOverhead - tableHeaderOverhead - trackingLines.length);
925
+ let selectedIdx = opts.selectedProcessIndex ?? -1;
926
+ if (isTracking) {
927
+ const trackedIdx = sorted.findIndex((p) => p.pid === opts.trackedPid);
928
+ if (trackedIdx >= 0) {
929
+ selectedIdx = trackedIdx;
930
+ }
931
+ }
932
+ let offset = 0;
933
+ if (selectedIdx >= 0) {
934
+ if (selectedIdx >= availableRows) {
935
+ offset = selectedIdx - availableRows + 1;
936
+ }
937
+ }
938
+ const visible = sorted.slice(offset, offset + availableRows);
939
+ const pidW = 6, userW = 7, thrW = 3, memW = 6, cpuW = 6;
940
+ const fixedW = pidW + userW + thrW + memW + cpuW + 5;
941
+ const cmdW = Math.max(8, contentWidth - fixedW);
942
+ 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));
943
+ if (opts.treeView) {
944
+ const treeLines = buildProcessTree(visible);
945
+ let rightTitle2 = opts.filter ? `filter: "${opts.filter}"` : `tree \xB7 sort: ${opts.sortBy || "cpu"}`;
946
+ if (isTracking) rightTitle2 += ` \xB7 following PID ${opts.trackedPid}`;
947
+ return panel("P proc", [...trackingLines, head, theme.border("\u2500".repeat(contentWidth)), ...treeLines], width, theme.process, theme.border, targetHeight - 2, rightTitle2);
948
+ }
949
+ const rows = visible.map((p, idx) => {
950
+ const isSelected = offset + idx === selectedIdx;
951
+ const cpuVal = p.cpu.toFixed(1) + "%";
952
+ const cpuStr = p.cpu > 50 ? chalk2.red.bold(cpuVal.padStart(cpuW)) : p.cpu > 20 ? chalk2.yellow(cpuVal.padStart(cpuW)) : chalk2.green(cpuVal.padStart(cpuW));
953
+ const memStr = (p.mem.toFixed(1) + "%").padStart(memW);
954
+ const userStr = truncatePlain(p.user, userW).padEnd(userW);
955
+ const thrStr = String(p.threads).padStart(thrW);
956
+ const pidStr = String(p.pid).padEnd(pidW);
957
+ const cmdStr = truncatePlain(p.command, cmdW).padEnd(cmdW);
958
+ const line = `${pidStr} ${cmdStr} ${userStr} ${thrStr} ${memStr} ${cpuStr}`;
959
+ return isSelected ? chalk2.bgCyan.black(line) : line;
960
+ });
961
+ const lines = [...trackingLines, head, theme.border("\u2500".repeat(contentWidth)), ...rows];
962
+ if (rows.length === 0) {
963
+ lines.push(chalk2.dim(`No processes match "${opts.filter || ""}"`));
964
+ }
965
+ let rightTitle = opts.filter ? `filter: "${opts.filter}"` : `sort: ${opts.sortBy || "cpu"}`;
966
+ if (isTracking) {
967
+ rightTitle += ` \xB7 following PID ${opts.trackedPid}`;
968
+ }
969
+ return panel("P proc", lines, width, theme.process, theme.border, targetHeight - 2, rightTitle);
970
+ }
971
+ function formatTable(data, opts = {}) {
972
+ const width = clampWidth(opts.width);
973
+ const activePanel = opts.activePanel || "grid";
974
+ const themeName = opts.theme || "default";
975
+ const theme = THEMES[themeName] || THEMES.default;
976
+ const out = [];
977
+ out.push(...header(data, width, [], theme.border));
978
+ out.push("");
979
+ const anomalies = opts.anomalies ?? [];
980
+ if (anomalies.length && activePanel === "grid") {
981
+ out.push(...anomalyPanel(anomalies, width, theme));
982
+ out.push("");
983
+ }
984
+ out.push(tabBar(activePanel, width, theme));
985
+ out.push("");
986
+ if (opts.minimal) {
987
+ const contentWidth = width - 4;
988
+ const lines = [];
989
+ lines.push(gaugeRow("CPU", data.cpu.usage, contentWidth));
990
+ lines.push(gaugeRow("Memory", data.memory.usagePercent, contentWidth));
991
+ if (data.gpu) {
992
+ lines.push(gaugeRow("GPU", data.gpu.utilization, contentWidth));
993
+ }
994
+ const tempC = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die;
995
+ if (tempC !== void 0) {
996
+ const tempStr = formatTempUnit(tempC, opts.tempUnit);
997
+ lines.push(statRow("Temp", thermalColor(data.thermal.pressureLevel)(tempStr)));
998
+ }
999
+ out.push(...panel("macmon mode", lines, width, theme.cpu, theme.border));
1000
+ return out.join("\n");
1001
+ }
1002
+ if (activePanel !== "grid") {
1003
+ const lines = activeDetailLines(data, activePanel, width, opts);
1004
+ if (lines) {
1005
+ out.push(...panel(detailPanelTitle(activePanel, opts.trackedPid), lines, width, theme[activePanel] ?? theme.cpu, theme.border));
1006
+ }
1007
+ } else {
1008
+ out.push(...btopCpuBox(data, width, theme, opts));
1009
+ out.push("");
1010
+ if (width >= 85) {
1011
+ const leftColW = Math.max(32, Math.floor(width * 0.38));
1012
+ const rightColW = width - leftColW - 2;
1013
+ const leftLines = btopLeftColumn(data, leftColW, theme, opts);
1014
+ const procLines = btopProcBox(data, rightColW, leftLines.length, theme, opts);
1015
+ out.push(...hstack([leftLines, procLines], 2, width));
1016
+ } else {
1017
+ const leftLines = btopLeftColumn(data, width, theme, opts);
1018
+ out.push(...leftLines);
1019
+ out.push("");
1020
+ const procLines = btopProcBox(data, width, 12, theme, opts);
1021
+ out.push(...procLines);
1022
+ }
1023
+ }
1024
+ return out.join("\n");
1025
+ }
1026
+ function clampWidth(width) {
1027
+ if (!width || Number.isNaN(width)) return 80;
1028
+ return Math.max(40, Math.min(width, 240));
1029
+ }
1030
+
1031
+ export {
1032
+ sparkline,
1033
+ THEMES,
1034
+ visLen,
1035
+ fitVisible,
1036
+ panel,
1037
+ formatBytes,
1038
+ celsiusToFahrenheit,
1039
+ getTabHitboxes,
1040
+ TAB_BAR_ROW,
1041
+ formatTable
1042
+ };