pyre-cli 8.2.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  collectAll
4
- } from "./chunk-KBKXU2ZV.js";
4
+ } from "./chunk-QOLZ7T32.js";
5
5
  import {
6
6
  CONFIG_FILE,
7
7
  readConfig,
8
8
  writeConfig
9
9
  } from "./chunk-RVGKEJXT.js";
10
- import "./chunk-GLH7EKAX.js";
10
+ import "./chunk-FH5WDJBF.js";
11
11
 
12
12
  // src/index.ts
13
- import fs8 from "fs";
13
+ import fs9 from "fs";
14
14
  import os3 from "os";
15
- import path5 from "path";
15
+ import path6 from "path";
16
16
  import readline2 from "readline";
17
17
  import { Command } from "commander";
18
18
  import chalk12 from "chalk";
@@ -575,17 +575,19 @@ function sortProcesses(processes, sortBy) {
575
575
  function pctColor(v) {
576
576
  return v > 80 ? chalk2.red : v > 50 ? chalk2.yellow : chalk2.dim;
577
577
  }
578
- function processTableLines(processes, contentWidth, borderAccent = THEMES.default.border) {
578
+ function processTableLines(processes, contentWidth, borderAccent = THEMES.default.border, selectedIdx = -1) {
579
579
  const pidW = 8, ppidW = 8, userW = 12, cpuW = 8, memW = 8, stateW = 8, thrW = 6, rtW = 10;
580
580
  const cmdW = Math.max(10, contentWidth - pidW - ppidW - userW - cpuW - memW - stateW - thrW - rtW - 8);
581
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) => {
582
+ const rows = processes.map((p, idx) => {
583
+ const isSelected = idx === selectedIdx;
583
584
  const cpuStr = pctColor(p.cpu)(`${p.cpu.toFixed(1)}`.padEnd(cpuW));
584
585
  const memStr = pctColor(p.mem)(`${p.mem.toFixed(1)}`.padEnd(memW));
585
586
  const stateStr = chalk2.dim(p.state.padEnd(stateW));
586
587
  const thrStr = String(p.threads).padEnd(thrW);
587
588
  const rtStr = formatRuntime(p.runtime).padEnd(rtW);
588
- return 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);
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;
589
591
  });
590
592
  return [head, borderAccent("\u2500".repeat(contentWidth)), ...rows];
591
593
  }
@@ -654,7 +656,7 @@ function diskTableLines(disks, contentWidth, borderAccent = THEMES.default.borde
654
656
  });
655
657
  return [head, borderAccent("\u2500".repeat(contentWidth)), ...rows];
656
658
  }
657
- function detailPanelTitle(panel2) {
659
+ function detailPanelTitle(panel2, trackedPid) {
658
660
  const map = {
659
661
  cpu: "CPU",
660
662
  mem: "Memory",
@@ -666,7 +668,7 @@ function detailPanelTitle(panel2) {
666
668
  packets: "Packets",
667
669
  tasks: "Tasks",
668
670
  disk: "Disk",
669
- process: "Processes"
671
+ process: trackedPid !== void 0 && trackedPid !== null ? `Processes (Following PID ${trackedPid})` : "Processes"
670
672
  };
671
673
  return map[panel2] || panel2;
672
674
  }
@@ -727,11 +729,43 @@ function activeDetailLines(data, activePanel, width, opts2 = {}) {
727
729
  case "process": {
728
730
  const filtered = opts2.filter ? data.processes.filter((p) => p.command.toLowerCase().includes(opts2.filter.toLowerCase())) : data.processes;
729
731
  const sorted = sortProcesses(filtered, opts2.sortBy ?? "cpu");
730
- const limited = opts2.processLimit ? sorted.slice(0, opts2.processLimit) : sorted;
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);
731
765
  if (opts2.treeView) {
732
- return buildProcessTree(limited);
766
+ return [...trackingLines, ...buildProcessTree(visible)];
733
767
  }
734
- return processTableLines(limited, width - 4, theme.border);
768
+ return [...trackingLines, ...processTableLines(visible, width - 4, theme.border, selectedIdx - offset)];
735
769
  }
736
770
  case "anomalies": {
737
771
  if (!opts2.anomalyHistory || opts2.anomalyHistory.length === 0) return ["No anomalies detected yet."];
@@ -875,18 +909,50 @@ function btopProcBox(data, width, targetHeight, theme, opts2) {
875
909
  const sorted = sortProcesses(filtered, opts2.sortBy ?? "cpu");
876
910
  const headerRowOverhead = 2;
877
911
  const tableHeaderOverhead = 2;
878
- const availableRows = Math.max(1, targetHeight - headerRowOverhead - tableHeaderOverhead);
879
- const limited = sorted.slice(0, availableRows);
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);
880
944
  const pidW = 6, userW = 7, thrW = 3, memW = 6, cpuW = 6;
881
945
  const fixedW = pidW + userW + thrW + memW + cpuW + 5;
882
946
  const cmdW = Math.max(8, contentWidth - fixedW);
883
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));
884
948
  if (opts2.treeView) {
885
- const treeLines = buildProcessTree(limited);
886
- const rightTitle2 = opts2.filter ? `filter: "${opts2.filter}"` : `tree \xB7 sort: ${opts2.sortBy || "cpu"}`;
887
- return panel("P proc", [head, theme.border("\u2500".repeat(contentWidth)), ...treeLines], width, theme.process, theme.border, targetHeight - 2, rightTitle2);
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);
888
953
  }
889
- const rows = limited.map((p) => {
954
+ const rows = visible.map((p, idx) => {
955
+ const isSelected = offset + idx === selectedIdx;
890
956
  const cpuVal = p.cpu.toFixed(1) + "%";
891
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));
892
958
  const memStr = (p.mem.toFixed(1) + "%").padStart(memW);
@@ -894,13 +960,17 @@ function btopProcBox(data, width, targetHeight, theme, opts2) {
894
960
  const thrStr = String(p.threads).padStart(thrW);
895
961
  const pidStr = String(p.pid).padEnd(pidW);
896
962
  const cmdStr = truncatePlain(p.command, cmdW).padEnd(cmdW);
897
- return `${pidStr} ${cmdStr} ${userStr} ${thrStr} ${memStr} ${cpuStr}`;
963
+ const line = `${pidStr} ${cmdStr} ${userStr} ${thrStr} ${memStr} ${cpuStr}`;
964
+ return isSelected ? chalk2.bgCyan.black(line) : line;
898
965
  });
899
- const lines = [head, theme.border("\u2500".repeat(contentWidth)), ...rows];
966
+ const lines = [...trackingLines, head, theme.border("\u2500".repeat(contentWidth)), ...rows];
900
967
  if (rows.length === 0) {
901
968
  lines.push(chalk2.dim(`No processes match "${opts2.filter || ""}"`));
902
969
  }
903
- const rightTitle = opts2.filter ? `filter: "${opts2.filter}"` : `sort: ${opts2.sortBy || "cpu"}`;
970
+ let rightTitle = opts2.filter ? `filter: "${opts2.filter}"` : `sort: ${opts2.sortBy || "cpu"}`;
971
+ if (isTracking) {
972
+ rightTitle += ` \xB7 following PID ${opts2.trackedPid}`;
973
+ }
904
974
  return panel("P proc", lines, width, theme.process, theme.border, targetHeight - 2, rightTitle);
905
975
  }
906
976
  function formatTable(data, opts2 = {}) {
@@ -921,24 +991,24 @@ function formatTable(data, opts2 = {}) {
921
991
  if (activePanel !== "grid") {
922
992
  const lines = activeDetailLines(data, activePanel, width, opts2);
923
993
  if (lines) {
924
- out.push(...panel(detailPanelTitle(activePanel), lines, width, theme[activePanel] ?? theme.cpu, theme.border));
994
+ out.push(...panel(detailPanelTitle(activePanel, opts2.trackedPid), lines, width, theme[activePanel] ?? theme.cpu, theme.border));
925
995
  }
926
- return out.join("\n");
927
- }
928
- out.push(...btopCpuBox(data, width, theme, opts2));
929
- out.push("");
930
- if (width >= 85) {
931
- const leftColW = Math.max(32, Math.floor(width * 0.38));
932
- const rightColW = width - leftColW - 2;
933
- const leftLines = btopLeftColumn(data, leftColW, theme, opts2);
934
- const procLines = btopProcBox(data, rightColW, leftLines.length, theme, opts2);
935
- out.push(...hstack([leftLines, procLines], 2, width));
936
996
  } else {
937
- const leftLines = btopLeftColumn(data, width, theme, opts2);
938
- out.push(...leftLines);
997
+ out.push(...btopCpuBox(data, width, theme, opts2));
939
998
  out.push("");
940
- const procLines = btopProcBox(data, width, 12, theme, opts2);
941
- out.push(...procLines);
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
+ }
942
1012
  }
943
1013
  return out.join("\n");
944
1014
  }
@@ -2311,6 +2381,7 @@ var History = class _History {
2311
2381
 
2312
2382
  // src/live/state.ts
2313
2383
  var SIGNAL_OPTIONS = ["SIGTERM", "SIGKILL", "SIGINT", "SIGHUP", "SIGSTOP", "SIGCONT"];
2384
+ var MENU_OPTIONS = ["Resume Dashboard", "Settings (Customizer)", "Readme", "Credits", "Quit pyre"];
2314
2385
  var config = readConfig();
2315
2386
  var state = {
2316
2387
  intervalHandle: null,
@@ -2350,6 +2421,10 @@ var state = {
2350
2421
  termHeight: process.stdout.rows || 24,
2351
2422
  sortMode: config.sortMode,
2352
2423
  processFilter: "",
2424
+ processSelectionIndex: 0,
2425
+ processScrollOffset: 0,
2426
+ trackedPid: null,
2427
+ inspectingProcess: null,
2353
2428
  inputMode: null,
2354
2429
  inputBuffer: "",
2355
2430
  targetPid: "",
@@ -2415,7 +2490,8 @@ var state = {
2415
2490
  anomalyHistory: [],
2416
2491
  splashEnabled: config.splashEnabled,
2417
2492
  splashColorScheme: config.splashColorScheme,
2418
- splashAnimation: config.splashAnimation
2493
+ splashAnimation: config.splashAnimation,
2494
+ readmeScrollOffset: 0
2419
2495
  };
2420
2496
  function setStatus(msg, ms = 3e3) {
2421
2497
  state.statusMessage = msg;
@@ -2570,6 +2646,9 @@ function writeLogRow(data) {
2570
2646
 
2571
2647
  // src/live/render.ts
2572
2648
  import { execFile, exec } from "child_process";
2649
+ import fs3 from "fs";
2650
+ import path3 from "path";
2651
+ import { fileURLToPath } from "url";
2573
2652
  import chalk4 from "chalk";
2574
2653
 
2575
2654
  // src/anomalies.ts
@@ -2679,6 +2758,9 @@ function tableCacheParams() {
2679
2758
  return [
2680
2759
  state.sortMode,
2681
2760
  state.processFilter || "",
2761
+ state.processSelectionIndex,
2762
+ state.trackedPid || "",
2763
+ state.inspectingProcess ? state.inspectingProcess.pid : "",
2682
2764
  limit,
2683
2765
  state.currentTheme,
2684
2766
  state.activePanel,
@@ -2697,8 +2779,7 @@ function graphsCacheParams() {
2697
2779
  }
2698
2780
  function processRowBudget() {
2699
2781
  let overlayRows = 0;
2700
- if (state.inputMode === "customizer") overlayRows = state.CUSTOMIZER_OPTIONS.length + 3;
2701
- else if (state.inputMode === "signal") overlayRows = 6;
2782
+ if (state.inputMode === "signal") overlayRows = 6;
2702
2783
  else if (state.inputMode === "filter" || state.inputMode === "kill" || state.inputMode === "p2p") overlayRows = 3;
2703
2784
  if (state.activePanel === "process") {
2704
2785
  return Math.max(10, state.termHeight - 12 - overlayRows);
@@ -2799,12 +2880,39 @@ function render() {
2799
2880
  writeFrame(out);
2800
2881
  return;
2801
2882
  }
2883
+ if (state.inputMode === "menu") {
2884
+ writeFrame(renderSplashMenu(cols, rows));
2885
+ return;
2886
+ }
2887
+ if (state.inputMode === "readme") {
2888
+ writeFrame(renderReadme(cols, rows));
2889
+ return;
2890
+ }
2891
+ if (state.inputMode === "credits") {
2892
+ writeFrame(renderCredits(cols, rows));
2893
+ return;
2894
+ }
2802
2895
  const lines = [];
2803
2896
  const theme = THEMES[state.currentTheme] || THEMES.default;
2804
2897
  const params = tableCacheParams();
2805
2898
  const anomalies = detectAnomalies(state.lastData, state.history);
2899
+ if (anomalies.length > 0) {
2900
+ for (const a of anomalies) {
2901
+ const now = /* @__PURE__ */ new Date();
2902
+ const alertWithTime = { ...a, timestamp: now };
2903
+ const exists = state.anomalyHistory.some(
2904
+ (h) => h.metric === a.metric && Math.abs(h.zScore - a.zScore) < 0.1 && now.getTime() - (h.timestamp?.getTime() || 0) < 1e4
2905
+ );
2906
+ if (!exists) {
2907
+ state.anomalyHistory.unshift(alertWithTime);
2908
+ }
2909
+ }
2910
+ if (state.anomalyHistory.length > 100) {
2911
+ state.anomalyHistory.length = 100;
2912
+ }
2913
+ }
2806
2914
  const anomalyCacheKey = anomalies.map((a) => `${a.metric}:${a.zScore.toFixed(2)}`).join(",");
2807
- const cacheParams = params + "|" + anomalyCacheKey;
2915
+ const cacheParams = params + "|" + anomalyCacheKey + "|" + state.anomalyHistory.length;
2808
2916
  const tableStr = _tableDataDirty || state.lastData !== _cachedTableData || cacheParams !== _cachedTableParams ? (() => {
2809
2917
  const out = formatTable(state.lastData, {
2810
2918
  width: state.termWidth,
@@ -2820,7 +2928,10 @@ function render() {
2820
2928
  tempUnit: state.tempUnit,
2821
2929
  history: state.history,
2822
2930
  graphMode: state.graphMode,
2823
- panelLayout: state.panelLayout
2931
+ panelLayout: state.panelLayout,
2932
+ selectedProcessIndex: state.processSelectionIndex,
2933
+ trackedPid: state.trackedPid,
2934
+ inspectProcess: state.inspectingProcess
2824
2935
  });
2825
2936
  _cachedTableData = state.lastData;
2826
2937
  _cachedTableParams = cacheParams;
@@ -2854,14 +2965,20 @@ function render() {
2854
2965
  const footerHeight = 3;
2855
2966
  const targetBodyHeight = Math.max(1, state.termHeight - footerHeight);
2856
2967
  if (state.inputMode === "customizer") {
2857
- const preOverlayCount = bodyLines.length;
2858
- const availableForOverlay = targetBodyHeight - preOverlayCount;
2859
2968
  const totalItems = state.CUSTOMIZER_OPTIONS.length;
2860
2969
  const headerLines = 1;
2861
- const blankLines = availableForOverlay >= 2 ? 1 : 0;
2862
- const maxVisibleItems = Math.max(0, availableForOverlay - headerLines - blankLines);
2970
+ const blankLines = 1;
2971
+ const idealOverlayHeight = totalItems + headerLines + blankLines;
2972
+ const minOverlayHeight = Math.min(10 + headerLines + blankLines, Math.floor(targetBodyHeight / 2));
2973
+ const overlayHeight = Math.min(idealOverlayHeight, Math.max(minOverlayHeight, Math.floor(targetBodyHeight * 0.6)));
2974
+ const maxBodyForTable = Math.max(3, targetBodyHeight - overlayHeight);
2975
+ if (bodyLines.length > maxBodyForTable) {
2976
+ bodyLines.length = maxBodyForTable;
2977
+ }
2978
+ const availableForOverlay = targetBodyHeight - bodyLines.length;
2979
+ const maxVisibleItems = Math.max(1, availableForOverlay - headerLines - blankLines);
2863
2980
  const scrollOffset = totalItems > maxVisibleItems ? Math.max(0, Math.min(state.customizerIndex, totalItems - maxVisibleItems)) : 0;
2864
- if (blankLines > 0) bodyLines.push("");
2981
+ bodyLines.push("");
2865
2982
  bodyLines.push(...renderCustomizerOverlay(scrollOffset, maxVisibleItems).split("\n"));
2866
2983
  } else if (state.inputMode === "filter") {
2867
2984
  bodyLines.push(chalk4.cyan(` Filter processes: ${state.inputBuffer}_`));
@@ -2986,11 +3103,98 @@ function checkAlerts(data) {
2986
3103
  state.alerted = false;
2987
3104
  }
2988
3105
  }
3106
+ function renderSplashMenu(cols, rows) {
3107
+ const out = [];
3108
+ const logo = [
3109
+ 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"),
3110
+ 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"),
3111
+ 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 "),
3112
+ 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 "),
3113
+ chalk4.hex("#ffe004").bold(" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"),
3114
+ chalk4.hex("#ffff40").bold(" \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D")
3115
+ ];
3116
+ const menuTop = Math.max(0, Math.floor(rows / 2) - 8);
3117
+ for (let i = 0; i < menuTop; i++) out.push("");
3118
+ const padding = Math.max(0, Math.floor((cols - 50) / 2));
3119
+ const padStr = " ".repeat(padding);
3120
+ for (const line of logo) {
3121
+ out.push(padStr + line);
3122
+ }
3123
+ out.push("");
3124
+ out.push(padStr + chalk4.dim(" system monitor v9.0.0"));
3125
+ out.push("");
3126
+ out.push("");
3127
+ for (let i = 0; i < MENU_OPTIONS.length; i++) {
3128
+ const isSelected = i === state.menuSelectionIndex;
3129
+ const opt = MENU_OPTIONS[i];
3130
+ const prefix = isSelected ? chalk4.cyan.bold(" \u25B6 ") : " ";
3131
+ const text = isSelected ? chalk4.bgCyan.black(` ${opt} `) : chalk4.dim(` ${opt} `);
3132
+ out.push(padStr + " " + prefix + text);
3133
+ }
3134
+ out.push("");
3135
+ out.push("");
3136
+ out.push(padStr + chalk4.dim(" Use \u2191/\u2193 to navigate, Enter to select"));
3137
+ while (out.length < rows) out.push("");
3138
+ return out.map((l) => fitVisible(l, cols));
3139
+ }
3140
+ var cachedReadmeLines = null;
3141
+ function getReadmeLines() {
3142
+ if (cachedReadmeLines) return cachedReadmeLines;
3143
+ try {
3144
+ const __filename = fileURLToPath(import.meta.url);
3145
+ const __dirname = path3.dirname(__filename);
3146
+ const rootDir = path3.resolve(__dirname, "..", "..");
3147
+ const readmePath = path3.join(rootDir, "README.md");
3148
+ const content = fs3.readFileSync(readmePath, "utf8");
3149
+ cachedReadmeLines = content.split("\n");
3150
+ return cachedReadmeLines;
3151
+ } catch (err) {
3152
+ return [chalk4.red("Failed to load README.md")];
3153
+ }
3154
+ }
3155
+ function renderReadme(cols, rows) {
3156
+ const lines = getReadmeLines();
3157
+ const availableLines = Math.max(1, rows - 2);
3158
+ const startIdx = Math.min(state.readmeScrollOffset, Math.max(0, lines.length - availableLines));
3159
+ state.readmeScrollOffset = startIdx;
3160
+ const content = lines.slice(startIdx, startIdx + availableLines).map((l) => {
3161
+ let out2 = l;
3162
+ if (out2.startsWith("#")) {
3163
+ out2 = chalk4.cyan.bold(out2);
3164
+ } else if (out2.startsWith("```")) {
3165
+ out2 = chalk4.dim(out2);
3166
+ } else if (out2.startsWith("- ") || out2.startsWith("* ")) {
3167
+ out2 = " " + chalk4.yellow(out2.substring(0, 2)) + out2.substring(2);
3168
+ }
3169
+ return out2;
3170
+ });
3171
+ const out = [];
3172
+ const pct = Math.round(startIdx / Math.max(1, lines.length - availableLines) * 100);
3173
+ const title = lines.length > availableLines ? `README (${pct}%)` : "README";
3174
+ out.push(...panel(title, content, cols, chalk4.cyan, chalk4.dim, availableLines));
3175
+ return out;
3176
+ }
3177
+ function renderCredits(cols, rows) {
3178
+ const out = [];
3179
+ const content = [
3180
+ chalk4.bold.hex("#ff6004")("Pyre - The Terminal System Monitor"),
3181
+ "",
3182
+ "Created with \u2764\uFE0F using Node.js",
3183
+ "",
3184
+ chalk4.cyan("Libraries used:"),
3185
+ " - chalk (Terminal styling)",
3186
+ " - systeminformation (System metrics)",
3187
+ "",
3188
+ chalk4.dim("Press Esc or Enter to return to menu.")
3189
+ ];
3190
+ out.push(...panel("CREDITS", content, cols, chalk4.magenta, chalk4.dim, rows - 2));
3191
+ return out;
3192
+ }
2989
3193
 
2990
3194
  // src/p2p/server.ts
2991
3195
  import net from "net";
2992
3196
  import tls from "tls";
2993
- import fs3 from "fs";
3197
+ import fs4 from "fs";
2994
3198
  import crypto2 from "crypto";
2995
3199
  import chalk5 from "chalk";
2996
3200
 
@@ -3135,9 +3339,9 @@ var P2PServer = class {
3135
3339
  async start() {
3136
3340
  if (this.options.auditLog) {
3137
3341
  try {
3138
- fs3.mkdirSync(this.options.auditLog, { recursive: true });
3342
+ fs4.mkdirSync(this.options.auditLog, { recursive: true });
3139
3343
  const logFile = `${this.options.auditLog}/p2p-audit.log`;
3140
- this.auditStream = fs3.createWriteStream(logFile, { flags: "a" });
3344
+ this.auditStream = fs4.createWriteStream(logFile, { flags: "a" });
3141
3345
  } catch {
3142
3346
  this.log(chalk5.yellow("Warning: could not open audit log"));
3143
3347
  }
@@ -3146,11 +3350,11 @@ var P2PServer = class {
3146
3350
  const useTLS = !!(this.options.tlsCert && this.options.tlsKey);
3147
3351
  if (useTLS) {
3148
3352
  const tlsOptions = {
3149
- key: fs3.readFileSync(this.options.tlsKey),
3150
- cert: fs3.readFileSync(this.options.tlsCert)
3353
+ key: fs4.readFileSync(this.options.tlsKey),
3354
+ cert: fs4.readFileSync(this.options.tlsCert)
3151
3355
  };
3152
3356
  if (this.options.tlsCA) {
3153
- tlsOptions.ca = fs3.readFileSync(this.options.tlsCA);
3357
+ tlsOptions.ca = fs4.readFileSync(this.options.tlsCA);
3154
3358
  }
3155
3359
  this.server = tls.createServer(tlsOptions, (socket) => this.onConnection(socket));
3156
3360
  } else {
@@ -3418,7 +3622,7 @@ async function startP2PServer(options) {
3418
3622
  // src/p2p/client.ts
3419
3623
  import net2 from "net";
3420
3624
  import tls2 from "tls";
3421
- import fs4 from "fs";
3625
+ import fs5 from "fs";
3422
3626
  import crypto3 from "crypto";
3423
3627
  import chalk6 from "chalk";
3424
3628
  var P2PClient = class {
@@ -3446,9 +3650,9 @@ var P2PClient = class {
3446
3650
  async connect() {
3447
3651
  if (this.options.auditLog) {
3448
3652
  try {
3449
- fs4.mkdirSync(this.options.auditLog, { recursive: true });
3653
+ fs5.mkdirSync(this.options.auditLog, { recursive: true });
3450
3654
  const logFile = `${this.options.auditLog}/p2p-client-audit.log`;
3451
- this.auditStream = fs4.createWriteStream(logFile, { flags: "a" });
3655
+ this.auditStream = fs5.createWriteStream(logFile, { flags: "a" });
3452
3656
  } catch {
3453
3657
  console.log(chalk6.yellow("Warning: could not open audit log"));
3454
3658
  }
@@ -3463,7 +3667,7 @@ var P2PClient = class {
3463
3667
  rejectUnauthorized: !this.options.tlsInsecure
3464
3668
  };
3465
3669
  if (this.options.tlsCA) {
3466
- tlsOptions.ca = fs4.readFileSync(this.options.tlsCA);
3670
+ tlsOptions.ca = fs5.readFileSync(this.options.tlsCA);
3467
3671
  }
3468
3672
  this.socket = tls2.connect(tlsOptions, () => {
3469
3673
  this.running = true;
@@ -3746,6 +3950,68 @@ function handleInputModeKey(str, key) {
3746
3950
  render();
3747
3951
  return;
3748
3952
  }
3953
+ if (state.inputMode === "menu") {
3954
+ if (isEscKey(key, str)) {
3955
+ state.inputMode = null;
3956
+ render();
3957
+ return;
3958
+ }
3959
+ if (isUpKey(key, str)) {
3960
+ state.menuSelectionIndex = (state.menuSelectionIndex - 1 + MENU_OPTIONS.length) % MENU_OPTIONS.length;
3961
+ render();
3962
+ return;
3963
+ }
3964
+ if (isDownKey(key, str)) {
3965
+ state.menuSelectionIndex = (state.menuSelectionIndex + 1) % MENU_OPTIONS.length;
3966
+ render();
3967
+ return;
3968
+ }
3969
+ if (isEnterKey(key, str) || str === " ") {
3970
+ const option = MENU_OPTIONS[state.menuSelectionIndex];
3971
+ switch (option) {
3972
+ case "Resume Dashboard":
3973
+ state.inputMode = null;
3974
+ break;
3975
+ case "Settings (Customizer)":
3976
+ state.inputMode = "customizer";
3977
+ state.customizerIndex = 0;
3978
+ break;
3979
+ case "Readme":
3980
+ state.inputMode = "readme";
3981
+ break;
3982
+ case "Credits":
3983
+ state.inputMode = "credits";
3984
+ break;
3985
+ case "Quit pyre":
3986
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
3987
+ process.exit(0);
3988
+ }
3989
+ render();
3990
+ return;
3991
+ }
3992
+ render();
3993
+ return;
3994
+ }
3995
+ if (state.inputMode === "readme" || state.inputMode === "credits") {
3996
+ if (isEscKey(key, str) || isEnterKey(key, str)) {
3997
+ state.inputMode = "menu";
3998
+ render();
3999
+ return;
4000
+ }
4001
+ if (state.inputMode === "readme") {
4002
+ if (isUpKey(key, str)) {
4003
+ state.readmeScrollOffset = Math.max(0, state.readmeScrollOffset - 1);
4004
+ render();
4005
+ return;
4006
+ }
4007
+ if (isDownKey(key, str)) {
4008
+ state.readmeScrollOffset += 1;
4009
+ render();
4010
+ return;
4011
+ }
4012
+ }
4013
+ return;
4014
+ }
3749
4015
  if (state.inputMode === "signal") {
3750
4016
  if (isUpKey(key, str)) {
3751
4017
  const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
@@ -4167,11 +4433,72 @@ async function startLive(opts2, splashPromise) {
4167
4433
  case "tab":
4168
4434
  cycleTab(1);
4169
4435
  break;
4436
+ case "up":
4437
+ if (state.lastData?.processes?.length) {
4438
+ state.trackedPid = null;
4439
+ state.processSelectionIndex = Math.max(0, state.processSelectionIndex - 1);
4440
+ render();
4441
+ }
4442
+ break;
4443
+ case "down":
4444
+ if (state.lastData?.processes?.length) {
4445
+ state.trackedPid = null;
4446
+ const count = state.lastData.processes.length;
4447
+ state.processSelectionIndex = Math.min(count - 1, state.processSelectionIndex + 1);
4448
+ render();
4449
+ }
4450
+ break;
4451
+ case "space":
4452
+ case " ":
4453
+ if (state.lastData?.processes?.length && state.processSelectionIndex >= 0) {
4454
+ const sorted = state.lastData.processes;
4455
+ const proc = sorted[state.processSelectionIndex];
4456
+ if (proc) {
4457
+ if (state.trackedPid === proc.pid) {
4458
+ state.trackedPid = null;
4459
+ setStatus(`Stopped following PID ${proc.pid}`);
4460
+ } else {
4461
+ state.trackedPid = proc.pid;
4462
+ setStatus(`Following PID ${proc.pid} (${proc.command})`);
4463
+ }
4464
+ render();
4465
+ }
4466
+ }
4467
+ break;
4468
+ case "return":
4469
+ case "enter":
4470
+ if (state.inspectingProcess) {
4471
+ state.inspectingProcess = null;
4472
+ render();
4473
+ } else if (state.lastData?.processes?.length && state.processSelectionIndex >= 0) {
4474
+ const sorted = state.lastData.processes;
4475
+ const proc = sorted[state.processSelectionIndex];
4476
+ if (proc) {
4477
+ state.inspectingProcess = proc;
4478
+ state.trackedPid = proc.pid;
4479
+ setStatus(`Inspecting & following PID ${proc.pid}`);
4480
+ render();
4481
+ }
4482
+ }
4483
+ break;
4170
4484
  case "escape":
4171
4485
  case "esc":
4172
- state.activePanel = "grid";
4173
- setStatus("Grid view");
4174
- render();
4486
+ if (state.inspectingProcess) {
4487
+ state.inspectingProcess = null;
4488
+ render();
4489
+ } else if (state.trackedPid !== null) {
4490
+ state.trackedPid = null;
4491
+ setStatus("Stopped following process");
4492
+ render();
4493
+ } else if (state.activePanel !== "grid") {
4494
+ state.activePanel = "grid";
4495
+ setStatus("Grid view");
4496
+ render();
4497
+ } else {
4498
+ state.inputMode = "menu";
4499
+ state.menuSelectionIndex = 0;
4500
+ render();
4501
+ }
4175
4502
  break;
4176
4503
  default:
4177
4504
  if (str && str.length === 1) {
@@ -4188,7 +4515,7 @@ async function startLive(opts2, splashPromise) {
4188
4515
  process.once("SIGINT", () => stopLive());
4189
4516
  }
4190
4517
  function tabKeyToId(str) {
4191
- const map = { "1": "cpu", "2": "mem", "3": "gpu", "4": "power", "5": "battery", "6": "thermal", "7": "network", "8": "packets", "9": "tasks", "0": "disk", "p": "process", "r": "p2p" };
4518
+ const map = { "1": "cpu", "2": "mem", "3": "gpu", "4": "power", "5": "battery", "6": "thermal", "7": "network", "8": "packets", "9": "tasks", "0": "disk", "p": "process", "r": "p2p", "A": "anomalies" };
4192
4519
  return map[str] ?? null;
4193
4520
  }
4194
4521
  function cycleTab(direction) {
@@ -4206,6 +4533,10 @@ function handleNormalMouse(seq) {
4206
4533
  const cy = seq.charCodeAt(5) - 32;
4207
4534
  if (cb === 0 || cb === 1) {
4208
4535
  handleMouseClick(cy, cx);
4536
+ } else if (cb === 64) {
4537
+ scrollProcessSelection(-1);
4538
+ } else if (cb === 65) {
4539
+ scrollProcessSelection(1);
4209
4540
  }
4210
4541
  }
4211
4542
  function handleSGRMouse(seq) {
@@ -4216,6 +4547,18 @@ function handleSGRMouse(seq) {
4216
4547
  const cy = parseInt(match[3]) - 1;
4217
4548
  if (button === 0 || button === 1) {
4218
4549
  handleMouseClick(cy, cx);
4550
+ } else if (button === 64) {
4551
+ scrollProcessSelection(-1);
4552
+ } else if (button === 65) {
4553
+ scrollProcessSelection(1);
4554
+ }
4555
+ }
4556
+ function scrollProcessSelection(dir) {
4557
+ if (state.lastData?.processes?.length) {
4558
+ state.trackedPid = null;
4559
+ const count = state.lastData.processes.length;
4560
+ state.processSelectionIndex = Math.max(0, Math.min(count - 1, state.processSelectionIndex + dir));
4561
+ render();
4219
4562
  }
4220
4563
  }
4221
4564
  function handleMouseClick(y, x) {
@@ -4591,13 +4934,13 @@ async function showSplash(config3) {
4591
4934
  process.stdin.setRawMode(true);
4592
4935
  process.stdin.resume();
4593
4936
  process.stdin.on("data", onEnter);
4594
- setTimeout(onEnter, 5e3);
4937
+ setTimeout(onEnter, 1500);
4595
4938
  });
4596
4939
  }
4597
4940
 
4598
4941
  // src/historyCmd.ts
4599
- import fs5 from "fs";
4600
- import path3 from "path";
4942
+ import fs6 from "fs";
4943
+ import path4 from "path";
4601
4944
  import chalk8 from "chalk";
4602
4945
  function runHistoryCommand(opts2 = {}) {
4603
4946
  const days = opts2.days || 7;
@@ -4605,11 +4948,11 @@ function runHistoryCommand(opts2 = {}) {
4605
4948
  console.log(chalk8.bold(`
4606
4949
  pyre history \u2014 Past ${days} Days Trend
4607
4950
  `));
4608
- if (!fs5.existsSync(dir)) {
4951
+ if (!fs6.existsSync(dir)) {
4609
4952
  console.log(chalk8.yellow(` No logs directory found at ${dir}`));
4610
4953
  return;
4611
4954
  }
4612
- const files = fs5.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
4955
+ const files = fs6.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
4613
4956
  if (files.length === 0) {
4614
4957
  console.log(chalk8.yellow(` No CSV logs found in ${dir}`));
4615
4958
  return;
@@ -4619,9 +4962,9 @@ function runHistoryCommand(opts2 = {}) {
4619
4962
  const memPoints = [];
4620
4963
  const tempPoints = [];
4621
4964
  for (const file of files.sort()) {
4622
- const fullPath = path3.join(dir, file);
4965
+ const fullPath = path4.join(dir, file);
4623
4966
  try {
4624
- const content = fs5.readFileSync(fullPath, "utf-8");
4967
+ const content = fs6.readFileSync(fullPath, "utf-8");
4625
4968
  const lines = content.split("\n").slice(1);
4626
4969
  for (const line of lines) {
4627
4970
  if (!line.trim()) continue;
@@ -4656,7 +4999,7 @@ function runHistoryCommand(opts2 = {}) {
4656
4999
  }
4657
5000
 
4658
5001
  // src/diffCmd.ts
4659
- import fs6 from "fs";
5002
+ import fs7 from "fs";
4660
5003
  import chalk9 from "chalk";
4661
5004
  function runDiffCommand(file1, file2) {
4662
5005
  console.log(chalk9.bold(`
@@ -4666,8 +5009,8 @@ function runDiffCommand(file1, file2) {
4666
5009
  console.log(chalk9.dim(` Snapshot B: ${file2}
4667
5010
  `));
4668
5011
  try {
4669
- const dataA = JSON.parse(fs6.readFileSync(file1, "utf-8"));
4670
- const dataB = JSON.parse(fs6.readFileSync(file2, "utf-8"));
5012
+ const dataA = JSON.parse(fs7.readFileSync(file1, "utf-8"));
5013
+ const dataB = JSON.parse(fs7.readFileSync(file2, "utf-8"));
4671
5014
  const diffNum = (label, valA, valB, unit = "") => {
4672
5015
  const delta = valB - valA;
4673
5016
  const sign = delta > 0 ? "+" : "";
@@ -4691,8 +5034,8 @@ function runDiffCommand(file1, file2) {
4691
5034
  }
4692
5035
 
4693
5036
  // src/xbar.ts
4694
- import fs7 from "fs";
4695
- import path4 from "path";
5037
+ import fs8 from "fs";
5038
+ import path5 from "path";
4696
5039
  import os2 from "os";
4697
5040
  import chalk10 from "chalk";
4698
5041
  function generateXbarPlugin() {
@@ -4729,11 +5072,11 @@ echo "\u{1F525} C:\${CPU}% M:\${MEM}% T:\${TEMP}\xB0C | font=Menlo"
4729
5072
  echo "---"
4730
5073
  echo "Open Dashboard | bash='${nodePath}' param1='${pyrePath}' param2='live' terminal=true"
4731
5074
  `;
4732
- const pluginDir = path4.join(os2.homedir(), "Library", "Application Support", "xbar", "plugins");
5075
+ const pluginDir = path5.join(os2.homedir(), "Library", "Application Support", "xbar", "plugins");
4733
5076
  const filename = "pyre-monitor.2s.sh";
4734
- const outPath = path4.join(process.cwd(), filename);
4735
- fs7.writeFileSync(outPath, script);
4736
- fs7.chmodSync(outPath, "755");
5077
+ const outPath = path5.join(process.cwd(), filename);
5078
+ fs8.writeFileSync(outPath, script);
5079
+ fs8.chmodSync(outPath, "755");
4737
5080
  console.log(chalk10.green(`Generated xbar plugin script at ${outPath}`));
4738
5081
  console.log(`To install, move it to your xbar plugins directory:`);
4739
5082
  console.log(chalk10.cyan(` mv ${filename} "${pluginDir}/"`));
@@ -4769,7 +5112,7 @@ function runFleetCommand(hosts) {
4769
5112
 
4770
5113
  // src/index.ts
4771
5114
  var config2 = readConfig();
4772
- var pkg = JSON.parse(fs8.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
5115
+ var pkg = JSON.parse(fs9.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
4773
5116
  var program = new Command();
4774
5117
  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");
4775
5118
  program.addHelpText("after", `
@@ -4856,17 +5199,17 @@ async function runServerCommand() {
4856
5199
  async function main() {
4857
5200
  const cmd = program.args[0];
4858
5201
  if (cmd === "anomalies") {
4859
- const { runAnomaliesDigest } = await import("./anomaliesCmd-TO52YY2B.js");
5202
+ const { runAnomaliesDigest } = await import("./anomaliesCmd-25OFKYPK.js");
4860
5203
  runAnomaliesDigest({ since: opts.since || "7d", dir: opts.exportDir });
4861
5204
  return;
4862
5205
  }
4863
5206
  if (cmd === "extensions") {
4864
- const { printExtensionsReport } = await import("./extensions-QCFSVCL5.js");
5207
+ const { printExtensionsReport } = await import("./extensions-DVA5YLGH.js");
4865
5208
  await printExtensionsReport();
4866
5209
  return;
4867
5210
  }
4868
5211
  if (cmd === "brew") {
4869
- const { printBrewHealthReport } = await import("./brewHealth-6NE37NAU.js");
5212
+ const { printBrewHealthReport } = await import("./brewHealth-F2BXF5M6.js");
4870
5213
  await printBrewHealthReport();
4871
5214
  return;
4872
5215
  }
@@ -4953,7 +5296,7 @@ async function main() {
4953
5296
  const cfg = readConfig();
4954
5297
  console.log(JSON.stringify(cfg, null, 2));
4955
5298
  } else if (sub === "reset") {
4956
- fs8.rmSync(CONFIG_FILE, { force: true });
5299
+ fs9.rmSync(CONFIG_FILE, { force: true });
4957
5300
  console.log(chalk12.green("Config reset to defaults"));
4958
5301
  } else {
4959
5302
  console.log("Usage: pyre config <show|reset>");
@@ -4969,7 +5312,7 @@ async function main() {
4969
5312
  return;
4970
5313
  }
4971
5314
  if (cmd === "doctor") {
4972
- const { runDoctor, printDoctorReport } = await import("./doctor-LJQAABZ4.js");
5315
+ const { runDoctor, printDoctorReport } = await import("./doctor-L6FZYA7F.js");
4973
5316
  const checks = await runDoctor();
4974
5317
  printDoctorReport(checks);
4975
5318
  return;
@@ -5050,7 +5393,7 @@ async function main() {
5050
5393
  else output = formatTable(data, { width: process.stdout.columns || 80, sortBy: opts.sort, treeView: opts.tree ?? config2.treeView, visible: { packets: opts.packets ? true : void 0 } });
5051
5394
  console.log(output);
5052
5395
  if (opts.out) {
5053
- fs8.writeFileSync(opts.out, stripAnsi(output));
5396
+ fs9.writeFileSync(opts.out, stripAnsi(output));
5054
5397
  console.log(chalk12.dim(`
5055
5398
  Saved to ${opts.out}`));
5056
5399
  }
@@ -5234,7 +5577,7 @@ async function runP2PConnect() {
5234
5577
  }
5235
5578
  async function runInfoCommand() {
5236
5579
  const data = await collectAll({ detailed: true });
5237
- const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-4VYRJVRO.js");
5580
+ const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-VACLPHRP.js");
5238
5581
  const displays = await getDisplayInfo();
5239
5582
  const tmStatus = await getTimeMachineStatus();
5240
5583
  const lines = [];
@@ -5340,9 +5683,9 @@ async function runWebCommand() {
5340
5683
  res.end(getDashboardHtml());
5341
5684
  } else if (url === "/setup") {
5342
5685
  res.writeHead(200, { "Content-Type": "text/html" });
5343
- const indexPath = path5.join(process.cwd(), "index.html");
5344
- if (fs8.existsSync(indexPath)) {
5345
- res.end(fs8.readFileSync(indexPath, "utf-8"));
5686
+ const indexPath = path6.join(process.cwd(), "index.html");
5687
+ if (fs9.existsSync(indexPath)) {
5688
+ res.end(fs9.readFileSync(indexPath, "utf-8"));
5346
5689
  } else {
5347
5690
  res.end(getDashboardHtml());
5348
5691
  }
@@ -5491,8 +5834,8 @@ let delegate = AppDelegate()
5491
5834
  app.delegate = delegate
5492
5835
  app.run()
5493
5836
  `;
5494
- const scriptPath = path5.join(os3.tmpdir(), "pyre-ui.swift");
5495
- fs8.writeFileSync(scriptPath, swiftScript);
5837
+ const scriptPath = path6.join(os3.tmpdir(), "pyre-ui.swift");
5838
+ fs9.writeFileSync(scriptPath, swiftScript);
5496
5839
  const { spawn } = await import("child_process");
5497
5840
  console.log(chalk12.cyan(`
5498
5841
  Launching native UI window...`));
@@ -5509,9 +5852,9 @@ app.run()
5509
5852
  async function runBenchCommand(benchCmd) {
5510
5853
  const { spawn } = await import("child_process");
5511
5854
  const logDir = opts.exportDir || "./pyre-exports";
5512
- const file = path5.join(logDir, `pyre-bench-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.csv`);
5513
- if (!fs8.existsSync(logDir)) fs8.mkdirSync(logDir, { recursive: true });
5514
- const stream = fs8.createWriteStream(file, { flags: "a" });
5855
+ const file = path6.join(logDir, `pyre-bench-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.csv`);
5856
+ if (!fs9.existsSync(logDir)) fs9.mkdirSync(logDir, { recursive: true });
5857
+ const stream = fs9.createWriteStream(file, { flags: "a" });
5515
5858
  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");
5516
5859
  console.log(chalk12.bold(`
5517
5860
  pyre bench`));