pyre-cli 9.5.2 → 10.0.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.
Files changed (86) hide show
  1. package/README.md +73 -16
  2. package/dist/alertChannels-TPSQIJ7S.js +8 -0
  3. package/dist/{blender-7UT5J7IP.js → blender-FGH3ASEQ.js} +2 -2
  4. package/dist/brewHealth-P2GW5Y5C.js +176 -0
  5. package/dist/buildCmd-CDPZ2ZFA.js +38 -0
  6. package/dist/{checkCmd-T7O4KXXA.js → checkCmd-RKW6QGDR.js} +9 -11
  7. package/dist/{chunk-CXL4GYGQ.js → chunk-53O2RGSL.js} +44 -17
  8. package/dist/{chunk-3TE7MJLS.js → chunk-6UHZP4QP.js} +36 -10
  9. package/dist/chunk-GHRDW646.js +2506 -0
  10. package/dist/{prometheus-LFYHF6VO.js → chunk-HT4VQ4OH.js} +1 -0
  11. package/dist/chunk-M73HCQD2.js +95 -0
  12. package/dist/chunk-MZO5QKUR.js +32 -0
  13. package/dist/{chunk-25I533OF.js → chunk-VIACN2RU.js} +1 -1
  14. package/dist/chunk-Z2YV5HKV.js +87 -0
  15. package/dist/chunk-ZLVIVFSO.js +110 -0
  16. package/dist/{collectors-JB4OGUEF.js → collectors-H43PN4PS.js} +6 -3
  17. package/dist/{completions-VQMHQCXE.js → completions-JKYEJNGR.js} +34 -1
  18. package/dist/{config-KS5ZZ3VN.js → config-GYGJXU2Q.js} +9 -1
  19. package/dist/doctor-GYHFTNZQ.js +387 -0
  20. package/dist/explainCmd-MF6PIMLW.js +222 -0
  21. package/dist/extensions-XJGHG7X3.js +134 -0
  22. package/dist/index.js +460 -88
  23. package/dist/ioreport.py +72 -0
  24. package/dist/netUsageCmd-WKBTW2YG.js +90 -0
  25. package/dist/{pipeCmd-TYLJHQ5B.js → pipeCmd-7REDBLBU.js} +4 -3
  26. package/dist/profileProcCmd-N4H6MCBT.js +107 -0
  27. package/dist/prometheus-5BL5PVLC.js +6 -0
  28. package/dist/prometheusCmd-PL64BLN5.js +93 -0
  29. package/dist/replayCmd-Z3PYXY7V.js +164 -0
  30. package/dist/run-HOGQDDTA.js +8 -0
  31. package/dist/serveCmd-2H5VLF3P.js +153 -0
  32. package/dist/service-Q74BBILO.js +181 -0
  33. package/dist/smartCmd-ODWKSFNG.js +166 -0
  34. package/dist/syncCmd-A2W3QB3S.js +126 -0
  35. package/dist/topoCmd-DU2HHZNH.js +148 -0
  36. package/dist/watchdogCmd-UF53JPZB.js +161 -0
  37. package/dist/{wizard-Z3RGA3VB.js → wizard-TD7BGX6O.js} +1 -1
  38. package/install.ps1 +29 -0
  39. package/install.sh +10 -9
  40. package/package.json +7 -3
  41. package/src/ai/models.ts +138 -0
  42. package/src/alertChannels.ts +119 -0
  43. package/src/brewHealth.ts +157 -40
  44. package/src/buildCmd.ts +48 -0
  45. package/src/checkCmd.ts +5 -5
  46. package/src/completions.ts +35 -0
  47. package/src/doctor.ts +235 -7
  48. package/src/easy_features.test.ts +40 -0
  49. package/src/explainCmd.ts +261 -0
  50. package/src/extensions.ts +71 -7
  51. package/src/formatters/render.ts +34 -10
  52. package/src/formatters/types.ts +2 -0
  53. package/src/index.ts +251 -36
  54. package/src/live/input.ts +60 -0
  55. package/src/live/render.ts +67 -0
  56. package/src/live/state.ts +5 -0
  57. package/src/live/types.ts +1 -1
  58. package/src/monitors/batteryPredictor.ts +143 -0
  59. package/src/monitors/buildTracker.ts +121 -0
  60. package/src/monitors/collectors.ts +342 -178
  61. package/src/monitors/ioreport.py +72 -0
  62. package/src/monitors/platform/linux.ts +756 -0
  63. package/src/monitors/platform/windows.ts +519 -0
  64. package/src/monitors/run.ts +27 -3
  65. package/src/monitors/smc.ts +21 -1
  66. package/src/monitors/types.ts +15 -0
  67. package/src/netUsageCmd.ts +121 -0
  68. package/src/platform.test.ts +99 -0
  69. package/src/plugins/index.ts +143 -0
  70. package/src/profileProcCmd.ts +133 -0
  71. package/src/prometheusCmd.ts +107 -0
  72. package/src/replayCmd.ts +186 -0
  73. package/src/serveCmd.ts +180 -0
  74. package/src/service.ts +125 -7
  75. package/src/smartCmd.ts +205 -0
  76. package/src/state/config.ts +58 -17
  77. package/src/syncCmd.ts +166 -0
  78. package/src/topoCmd.ts +176 -0
  79. package/src/watchdogCmd.ts +198 -0
  80. package/src/xbar.ts +22 -11
  81. package/dist/brewHealth-F2BXF5M6.js +0 -74
  82. package/dist/chunk-FH5WDJBF.js +0 -15
  83. package/dist/chunk-N4WJVF5M.js +0 -1184
  84. package/dist/doctor-UV7TWIHX.js +0 -184
  85. package/dist/extensions-DVA5YLGH.js +0 -74
  86. package/dist/service-WTS3NBQU.js +0 -73
package/dist/index.js CHANGED
@@ -1,9 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- CONFIG_FILE,
4
- readConfig,
5
- writeConfig
6
- } from "./chunk-CXL4GYGQ.js";
7
2
  import {
8
3
  TAB_BAR_ROW,
9
4
  THEMES,
@@ -15,18 +10,31 @@ import {
15
10
  panel,
16
11
  sparkline,
17
12
  visLen
18
- } from "./chunk-3TE7MJLS.js";
13
+ } from "./chunk-6UHZP4QP.js";
14
+ import {
15
+ AVAILABLE_AI_MODELS
16
+ } from "./chunk-ZLVIVFSO.js";
17
+ import {
18
+ sendAlert
19
+ } from "./chunk-Z2YV5HKV.js";
20
+ import {
21
+ CONFIG_FILE,
22
+ getConfigDir,
23
+ readConfig,
24
+ writeConfig
25
+ } from "./chunk-53O2RGSL.js";
19
26
  import "./chunk-IM3IQMMT.js";
20
27
  import {
21
28
  collectAll
22
- } from "./chunk-N4WJVF5M.js";
23
- import "./chunk-25I533OF.js";
24
- import "./chunk-FH5WDJBF.js";
29
+ } from "./chunk-GHRDW646.js";
30
+ import "./chunk-M73HCQD2.js";
31
+ import "./chunk-VIACN2RU.js";
32
+ import "./chunk-MZO5QKUR.js";
25
33
 
26
34
  // src/index.ts
27
- import fs9 from "fs";
35
+ import fs10 from "fs";
28
36
  import os2 from "os";
29
- import path5 from "path";
37
+ import path6 from "path";
30
38
  import readline2 from "readline";
31
39
  import { Command } from "commander";
32
40
  import chalk10 from "chalk";
@@ -1550,6 +1558,8 @@ var state = {
1550
1558
  "Splash Animation",
1551
1559
  "Notifications",
1552
1560
  "Temperature Unit",
1561
+ "AI Model",
1562
+ "Grid Panel Order",
1553
1563
  "Toggle CPU",
1554
1564
  "Toggle Memory",
1555
1565
  "Toggle GPU",
@@ -1609,7 +1619,10 @@ var state = {
1609
1619
  readmeScrollOffset: 0,
1610
1620
  isDocker: isDockerEnv,
1611
1621
  dockerModeConfirmed: config.dockerModeConfirmed || false,
1612
- dockerSelectionIndex: 0
1622
+ dockerSelectionIndex: 0,
1623
+ aiModel: config.aiModel || "expert-rules-v1",
1624
+ aiBackend: config.aiBackend || "builtin",
1625
+ modelSelectionIndex: 0
1613
1626
  };
1614
1627
  function setStatus(msg, ms = 3e3) {
1615
1628
  state.statusMessage = msg;
@@ -1938,7 +1951,8 @@ function tableCacheParams() {
1938
1951
  state.treeView ? "1" : "0",
1939
1952
  state.history.version,
1940
1953
  JSON.stringify(state.visiblePanels),
1941
- JSON.stringify(state.panelLayout)
1954
+ JSON.stringify(state.panelLayout),
1955
+ state.aiModel
1942
1956
  ].join("|");
1943
1957
  }
1944
1958
  function graphsCacheParams() {
@@ -2007,6 +2021,11 @@ function renderCustomizerOverlay(scrollOffset, maxVisibleItems) {
2007
2021
  lines.push(`${prefix}${opt}: ${status}`);
2008
2022
  } else if (opt === "Temperature Unit") {
2009
2023
  lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.tempUnit.toUpperCase())} [C, F]`);
2024
+ } else if (opt === "AI Model") {
2025
+ const curModel = AVAILABLE_AI_MODELS.find((m) => m.id === state.aiModel) || AVAILABLE_AI_MODELS[0];
2026
+ lines.push(`${prefix}${opt}: ${chalk2.bold.cyan(curModel.name)} ${chalk2.dim(`[${curModel.badge}]`)}`);
2027
+ } else if (opt === "Grid Panel Order") {
2028
+ lines.push(`${prefix}${opt}: ${chalk2.bold.green(state.panelLayout.join(" \u2192 "))}`);
2010
2029
  } else {
2011
2030
  const toggleKey = getToggleKey(opt);
2012
2031
  const isVisible = toggleKey ? toggleKey === "tree" ? state.treeView : state.visiblePanels[toggleKey] !== false : true;
@@ -2067,6 +2086,10 @@ function render() {
2067
2086
  writeFrame(renderDockerAlert(cols, rows));
2068
2087
  return;
2069
2088
  }
2089
+ if (state.inputMode === "ai-model") {
2090
+ writeFrame(renderAiModelSelector(cols, rows));
2091
+ return;
2092
+ }
2070
2093
  const lines = [];
2071
2094
  const theme = THEMES[state.currentTheme] || THEMES.default;
2072
2095
  const params = tableCacheParams();
@@ -2106,7 +2129,8 @@ function render() {
2106
2129
  panelLayout: state.panelLayout,
2107
2130
  selectedProcessIndex: state.processSelectionIndex,
2108
2131
  trackedPid: state.trackedPid,
2109
- inspectProcess: state.inspectingProcess
2132
+ inspectProcess: state.inspectingProcess,
2133
+ aiModel: state.aiModel
2110
2134
  });
2111
2135
  _cachedTableData = state.lastData;
2112
2136
  _cachedTableParams = cacheParams;
@@ -2203,7 +2227,8 @@ function footerLine() {
2203
2227
  ["l", state.logging ? "stop log" : "log"],
2204
2228
  ["f", state.exportFormat],
2205
2229
  ["+/-", `${state.interval}s`],
2206
- ["r", state.p2pServerRunning ? "stop p2p" : "start p2p"]
2230
+ ["r", state.p2pServerRunning ? "stop p2p" : "start p2p"],
2231
+ ["m", `ai:${state.aiModel.split("-")[0]}`]
2207
2232
  ];
2208
2233
  const fmt = (list) => list.map(([k, label]) => `${chalk2.hex("#50fa7b").bold(k)} ${chalk2.dim(label)}`).join(chalk2.dim(" | "));
2209
2234
  const str1 = fmt(row1);
@@ -2213,6 +2238,7 @@ function footerLine() {
2213
2238
  if (state.logging) badges.push(chalk2.red.bold("\u25CF REC"));
2214
2239
  if (state.activePanel !== "grid") badges.push(chalk2.cyan.bold(`\u25C9 ${state.activePanel.toUpperCase()}`));
2215
2240
  if (state.p2pServerRunning) badges.push(chalk2.green.bold(`P2P ${state.p2pBind || "0.0.0.0"}:${state.p2pPort}`));
2241
+ badges.push(chalk2.magenta.bold(`AI:${state.aiModel}`));
2216
2242
  const badgeStr = badges.join(" ") || " ";
2217
2243
  return [str1, str2, badgeStr];
2218
2244
  }
@@ -2274,6 +2300,28 @@ function checkAlerts(data) {
2274
2300
  exec(state.alertCmd, { env: { ...process.env, PYRE_ALERT: alertMsg } }, () => {
2275
2301
  });
2276
2302
  }
2303
+ const cfg = readConfig();
2304
+ const channelConfig = {
2305
+ slackUrl: cfg.slackAlertUrl || void 0,
2306
+ discordUrl: cfg.discordAlertUrl || void 0,
2307
+ pushoverToken: cfg.pushoverToken || void 0,
2308
+ pushoverUser: cfg.pushoverUser || void 0,
2309
+ ntfyUrl: cfg.ntfyUrl || void 0
2310
+ };
2311
+ const hasChannels = Object.values(channelConfig).some((v) => !!v);
2312
+ if (hasChannels) {
2313
+ const severity = hasCriticalAnomaly ? "critical" : "warning";
2314
+ sendAlert({
2315
+ metric: reasons[0]?.split(" ")[0] ?? "System",
2316
+ value: data.cpu.usage,
2317
+ threshold: state.CPU_ALERT_PCT,
2318
+ unit: "%",
2319
+ host: data.header.hostname,
2320
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2321
+ severity
2322
+ }, channelConfig).catch(() => {
2323
+ });
2324
+ }
2277
2325
  } else if (!hot && !anomalyTriggered && !watchdogTriggered) {
2278
2326
  state.alerted = false;
2279
2327
  }
@@ -2390,6 +2438,29 @@ function renderDockerAlert(cols, rows) {
2390
2438
  out.push(...panel("DOCKER DETECTED", content, cols, chalk2.red, chalk2.dim, rows - 2));
2391
2439
  return out;
2392
2440
  }
2441
+ function renderAiModelSelector(cols, rows) {
2442
+ const out = [];
2443
+ const content = [
2444
+ chalk2.bold.hex("#ff6004")("Choose AI Anomaly Explanation Model"),
2445
+ chalk2.dim("Select your preferred diagnosis engine. Offline models run entirely in-process."),
2446
+ ""
2447
+ ];
2448
+ for (let i = 0; i < AVAILABLE_AI_MODELS.length; i++) {
2449
+ const m = AVAILABLE_AI_MODELS[i];
2450
+ const isSelected = i === state.modelSelectionIndex;
2451
+ const isCurrent = m.id === state.aiModel;
2452
+ const prefix = isSelected ? chalk2.cyan.bold(" \u25B6 ") : " ";
2453
+ const curBadge = isCurrent ? chalk2.bgGreen.black.bold(" ACTIVE ") : "";
2454
+ const badgeStr = m.offline ? chalk2.green(`[${m.badge}]`) : chalk2.yellow(`[${m.badge}]`);
2455
+ const nameStr = isSelected ? chalk2.bgCyan.black.bold(` ${m.name} `) : chalk2.bold.white(m.name);
2456
+ content.push(`${prefix}${nameStr} ${badgeStr} ${curBadge}`);
2457
+ content.push(chalk2.dim(` ${m.description}`));
2458
+ content.push("");
2459
+ }
2460
+ content.push(chalk2.dim("Use \u2191/\u2193 or J/K to navigate \xB7 Enter/Space to select \xB7 Esc to cancel"));
2461
+ out.push(...panel("AI MODEL SELECTOR", content, cols, chalk2.cyan, chalk2.dim, Math.max(1, rows - 2)));
2462
+ return out;
2463
+ }
2393
2464
 
2394
2465
  // src/p2p/server.ts
2395
2466
  import net from "net";
@@ -3056,6 +3127,58 @@ function deriveHMACKey2(password) {
3056
3127
  return crypto3.createHash("sha256").update(password).digest("hex").slice(0, 32);
3057
3128
  }
3058
3129
 
3130
+ // src/plugins/index.ts
3131
+ import fs6 from "fs";
3132
+ import path3 from "path";
3133
+ import { pathToFileURL } from "url";
3134
+ var loadedPlugins = /* @__PURE__ */ new Map();
3135
+ function getPluginsDir() {
3136
+ return path3.join(getConfigDir(), "plugins");
3137
+ }
3138
+ async function loadPlugins() {
3139
+ const pluginsDir = getPluginsDir();
3140
+ if (!fs6.existsSync(pluginsDir)) {
3141
+ try {
3142
+ fs6.mkdirSync(pluginsDir, { recursive: true });
3143
+ } catch {
3144
+ return loadedPlugins;
3145
+ }
3146
+ }
3147
+ let files = [];
3148
+ try {
3149
+ files = fs6.readdirSync(pluginsDir).filter((f) => f.endsWith(".js") || f.endsWith(".mjs"));
3150
+ } catch {
3151
+ return loadedPlugins;
3152
+ }
3153
+ for (const file of files) {
3154
+ const filePath = path3.join(pluginsDir, file);
3155
+ try {
3156
+ const fileUrl = pathToFileURL(filePath).href;
3157
+ const mod = await import(`${fileUrl}?t=${Date.now()}`);
3158
+ const plugin = mod.default || mod;
3159
+ if (plugin && typeof plugin.collect === "function") {
3160
+ const name = plugin.name || path3.basename(file, path3.extname(file));
3161
+ if (!loadedPlugins.has(name)) {
3162
+ loadedPlugins.set(name, {
3163
+ plugin: { ...plugin, name },
3164
+ lastRun: 0,
3165
+ lastOutput: { title: plugin.title || name, lines: ["Collecting..."] }
3166
+ });
3167
+ }
3168
+ }
3169
+ } catch (err) {
3170
+ const name = path3.basename(file, path3.extname(file));
3171
+ loadedPlugins.set(name, {
3172
+ plugin: { name, collect: async () => ({ lines: [`Error loading plugin: ${err.message}`] }) },
3173
+ lastRun: Date.now(),
3174
+ lastOutput: { title: name, lines: [`Failed to load: ${err.message}`] },
3175
+ error: err.message
3176
+ });
3177
+ }
3178
+ }
3179
+ return loadedPlugins;
3180
+ }
3181
+
3059
3182
  // src/live/input.ts
3060
3183
  function persistConfig() {
3061
3184
  writeConfig({
@@ -3078,7 +3201,9 @@ function persistConfig() {
3078
3201
  splashAnimation: state.splashAnimation,
3079
3202
  visiblePanels: { ...state.visiblePanels },
3080
3203
  notificationsEnabled: state.notificationsEnabled,
3081
- dockerModeConfirmed: state.dockerModeConfirmed
3204
+ dockerModeConfirmed: state.dockerModeConfirmed,
3205
+ aiModel: state.aiModel,
3206
+ aiBackend: state.aiBackend
3082
3207
  });
3083
3208
  }
3084
3209
  function isUpKey(key, str) {
@@ -3132,6 +3257,24 @@ function handleInputModeKey(str, key) {
3132
3257
  } else if (selected === "Temperature Unit") {
3133
3258
  state.tempUnit = state.tempUnit === "c" ? "f" : "c";
3134
3259
  setStatus(`Temperature unit: ${state.tempUnit.toUpperCase()}`);
3260
+ } else if (selected === "AI Model") {
3261
+ const curIdx = AVAILABLE_AI_MODELS.findIndex((m) => m.id === state.aiModel);
3262
+ const nextIdx = (curIdx + 1) % AVAILABLE_AI_MODELS.length;
3263
+ state.aiModel = AVAILABLE_AI_MODELS[nextIdx].id;
3264
+ state.aiBackend = AVAILABLE_AI_MODELS[nextIdx].backend;
3265
+ setStatus(`AI Model: ${AVAILABLE_AI_MODELS[nextIdx].name}`);
3266
+ } else if (selected === "Grid Panel Order") {
3267
+ const layouts = [
3268
+ ["mem", "disk", "net"],
3269
+ ["disk", "mem", "net"],
3270
+ ["net", "mem", "disk"],
3271
+ ["mem", "net", "disk"]
3272
+ ];
3273
+ const currentStr = state.panelLayout.join(",");
3274
+ const currentIdx = layouts.findIndex((l) => l.join(",") === currentStr);
3275
+ const nextIdx = (currentIdx + 1) % layouts.length;
3276
+ state.panelLayout = layouts[nextIdx];
3277
+ setStatus(`Panel layout: ${state.panelLayout.join(" \u2192 ")}`);
3135
3278
  } else {
3136
3279
  const toggleKey = getToggleKey(selected);
3137
3280
  if (toggleKey) {
@@ -3253,6 +3396,36 @@ function handleInputModeKey(str, key) {
3253
3396
  }
3254
3397
  return;
3255
3398
  }
3399
+ if (state.inputMode === "ai-model") {
3400
+ if (isEscKey(key, str)) {
3401
+ state.inputMode = null;
3402
+ render();
3403
+ return;
3404
+ }
3405
+ if (isUpKey(key, str) || str === "k") {
3406
+ state.modelSelectionIndex = (state.modelSelectionIndex - 1 + AVAILABLE_AI_MODELS.length) % AVAILABLE_AI_MODELS.length;
3407
+ render();
3408
+ return;
3409
+ }
3410
+ if (isDownKey(key, str) || str === "j") {
3411
+ state.modelSelectionIndex = (state.modelSelectionIndex + 1) % AVAILABLE_AI_MODELS.length;
3412
+ render();
3413
+ return;
3414
+ }
3415
+ if (isEnterKey(key, str) || str === " ") {
3416
+ const chosen = AVAILABLE_AI_MODELS[state.modelSelectionIndex];
3417
+ state.aiModel = chosen.id;
3418
+ state.aiBackend = chosen.backend;
3419
+ state.inputMode = null;
3420
+ setStatus(`Active AI Model: ${chosen.name}`, 4e3);
3421
+ invalidateTableCache();
3422
+ invalidateFrame();
3423
+ persistConfig();
3424
+ render();
3425
+ return;
3426
+ }
3427
+ return;
3428
+ }
3256
3429
  if (state.inputMode === "signal") {
3257
3430
  if (isUpKey(key, str)) {
3258
3431
  const idx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer);
@@ -3485,6 +3658,8 @@ async function startLive(opts2, splashPromise) {
3485
3658
  state.termHeight = process.stdout.rows || 24;
3486
3659
  state.history.reset();
3487
3660
  state.history.setMaxLen(Math.max(20, Math.min(200, state.termWidth - 30)));
3661
+ await loadPlugins().catch(() => {
3662
+ });
3488
3663
  const warmupPromise = doWarmup();
3489
3664
  if (splashPromise) {
3490
3665
  await splashPromise;
@@ -3622,6 +3797,11 @@ async function startLive(opts2, splashPromise) {
3622
3797
  state.customizerIndex = 0;
3623
3798
  render();
3624
3799
  break;
3800
+ case "m":
3801
+ state.inputMode = "ai-model";
3802
+ state.modelSelectionIndex = Math.max(0, AVAILABLE_AI_MODELS.findIndex((m) => m.id === state.aiModel));
3803
+ render();
3804
+ break;
3625
3805
  case "p":
3626
3806
  state.paused = !state.paused;
3627
3807
  setStatus(state.paused ? "Paused" : "Resumed");
@@ -4192,8 +4372,8 @@ async function showSplash(config3) {
4192
4372
  }
4193
4373
 
4194
4374
  // src/historyCmd.ts
4195
- import fs6 from "fs";
4196
- import path3 from "path";
4375
+ import fs7 from "fs";
4376
+ import path4 from "path";
4197
4377
  import chalk6 from "chalk";
4198
4378
  function runHistoryCommand(opts2 = {}) {
4199
4379
  const days = opts2.days || 7;
@@ -4201,11 +4381,11 @@ function runHistoryCommand(opts2 = {}) {
4201
4381
  console.log(chalk6.bold(`
4202
4382
  pyre history \u2014 Past ${days} Days Trend
4203
4383
  `));
4204
- if (!fs6.existsSync(dir)) {
4384
+ if (!fs7.existsSync(dir)) {
4205
4385
  console.log(chalk6.yellow(` No logs directory found at ${dir}`));
4206
4386
  return;
4207
4387
  }
4208
- const files = fs6.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
4388
+ const files = fs7.readdirSync(dir).filter((f) => f.startsWith("pyre-log-") && f.endsWith(".csv"));
4209
4389
  if (files.length === 0) {
4210
4390
  console.log(chalk6.yellow(` No CSV logs found in ${dir}`));
4211
4391
  return;
@@ -4215,9 +4395,9 @@ function runHistoryCommand(opts2 = {}) {
4215
4395
  const memPoints = [];
4216
4396
  const tempPoints = [];
4217
4397
  for (const file of files.sort()) {
4218
- const fullPath = path3.join(dir, file);
4398
+ const fullPath = path4.join(dir, file);
4219
4399
  try {
4220
- const content = fs6.readFileSync(fullPath, "utf-8");
4400
+ const content = fs7.readFileSync(fullPath, "utf-8");
4221
4401
  const lines = content.split("\n").slice(1);
4222
4402
  for (const line of lines) {
4223
4403
  if (!line.trim()) continue;
@@ -4252,7 +4432,7 @@ function runHistoryCommand(opts2 = {}) {
4252
4432
  }
4253
4433
 
4254
4434
  // src/diffCmd.ts
4255
- import fs7 from "fs";
4435
+ import fs8 from "fs";
4256
4436
  import chalk7 from "chalk";
4257
4437
  function runDiffCommand(file1, file2) {
4258
4438
  console.log(chalk7.bold(`
@@ -4262,8 +4442,8 @@ function runDiffCommand(file1, file2) {
4262
4442
  console.log(chalk7.dim(` Snapshot B: ${file2}
4263
4443
  `));
4264
4444
  try {
4265
- const dataA = JSON.parse(fs7.readFileSync(file1, "utf-8"));
4266
- const dataB = JSON.parse(fs7.readFileSync(file2, "utf-8"));
4445
+ const dataA = JSON.parse(fs8.readFileSync(file1, "utf-8"));
4446
+ const dataB = JSON.parse(fs8.readFileSync(file2, "utf-8"));
4267
4447
  const diffNum = (label, valA, valB, unit = "") => {
4268
4448
  const delta = valB - valA;
4269
4449
  const sign = delta > 0 ? "+" : "";
@@ -4287,8 +4467,8 @@ function runDiffCommand(file1, file2) {
4287
4467
  }
4288
4468
 
4289
4469
  // src/xbar.ts
4290
- import fs8 from "fs";
4291
- import path4 from "path";
4470
+ import fs9 from "fs";
4471
+ import path5 from "path";
4292
4472
  import os from "os";
4293
4473
  import chalk8 from "chalk";
4294
4474
  function generateXbarPlugin() {
@@ -4299,10 +4479,9 @@ function generateXbarPlugin() {
4299
4479
  # <xbar.version>v1.0</xbar.version>
4300
4480
  # <xbar.author>somalip</xbar.author>
4301
4481
  # <xbar.author.github>somalip</xbar.author.github>
4302
- # <xbar.desc>Mac system monitoring powered by pyre.</xbar.desc>
4482
+ # <xbar.desc>Cross-platform system monitoring powered by pyre.</xbar.desc>
4303
4483
 
4304
4484
  # Run pyre to get a JSON snapshot
4305
- # We use node directly to avoid env issues in xbar
4306
4485
  DATA=$("${nodePath}" "${pyrePath}" --once --json)
4307
4486
 
4308
4487
  if [ -z "$DATA" ]; then
@@ -4325,15 +4504,25 @@ echo "\u{1F525} C:\${CPU}% M:\${MEM}% T:\${TEMP}\xB0C | font=Menlo"
4325
4504
  echo "---"
4326
4505
  echo "Open Dashboard | bash='${nodePath}' param1='${pyrePath}' param2='live' terminal=true"
4327
4506
  `;
4328
- const pluginDir = path4.join(os.homedir(), "Library", "Application Support", "xbar", "plugins");
4329
4507
  const filename = "pyre-monitor.2s.sh";
4330
- const outPath = path4.join(process.cwd(), filename);
4331
- fs8.writeFileSync(outPath, script);
4332
- fs8.chmodSync(outPath, "755");
4333
- console.log(chalk8.green(`Generated xbar plugin script at ${outPath}`));
4334
- console.log(`To install, move it to your xbar plugins directory:`);
4335
- console.log(chalk8.cyan(` mv ${filename} "${pluginDir}/"`));
4336
- console.log(`Then refresh xbar.`);
4508
+ const outPath = path5.join(process.cwd(), filename);
4509
+ fs9.writeFileSync(outPath, script);
4510
+ try {
4511
+ fs9.chmodSync(outPath, 493);
4512
+ } catch {
4513
+ }
4514
+ console.log(chalk8.green(`Generated menu bar plugin script at ${outPath}`));
4515
+ if (process.platform === "darwin") {
4516
+ const pluginDir = path5.join(os.homedir(), "Library", "Application Support", "xbar", "plugins");
4517
+ console.log(`To install in xbar or SwiftBar on macOS:`);
4518
+ console.log(chalk8.cyan(` mv ${filename} "${pluginDir}/"`));
4519
+ } else if (process.platform === "linux") {
4520
+ const argosDir = path5.join(os.homedir(), ".config", "argos");
4521
+ console.log(`To install in Argos (GNOME Shell) on Linux:`);
4522
+ console.log(chalk8.cyan(` mkdir -p "${argosDir}" && mv ${filename} "${argosDir}/"`));
4523
+ } else {
4524
+ console.log(chalk8.dim(`Compatible with BitBar, xbar, and Argos shell plugins.`));
4525
+ }
4337
4526
  }
4338
4527
 
4339
4528
  // src/fleet.ts
@@ -4367,36 +4556,48 @@ async function runFleetCommand(hosts) {
4367
4556
 
4368
4557
  // src/index.ts
4369
4558
  var config2 = readConfig();
4370
- var pkg = JSON.parse(fs9.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
4559
+ var pkg = JSON.parse(fs10.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
4371
4560
  var program = new Command();
4372
- program.name("pyre").version(pkg.version).description("Mac system monitoring CLI: interactive live dashboard, stats, graphs, export, packet monitor, battery predictor").option("-j, --json", "Output as JSON").option("--html", "Output as HTML").option("--md", "Output as Markdown").option("-c, --csv", "Output as CSV").option("-t, --tsv", "Output as TSV").option("--detailed", "Include detailed system info and sensor readings").option("--theme <name>", "Default theme for live mode (default, dracula, cyberpunk, monochrome, nord, gruvbox)", config2.theme).option("--interval <seconds>", "Refresh interval for live mode", String(config2.interval)).option("--once", "Show a single static snapshot instead of live feed").option("--out <file>", "Also write the snapshot output to a file (--once/--json/--csv/--tsv modes)").option("--export-dir <dir>", "Directory used for live-mode snapshot exports and logs", config2.exportDir).option("--log", "Start continuous CSV logging immediately when live mode starts").option("--tree", "Show process tree view instead of flat list").option("--sort <key>", "Sort processes by: cpu, mem, pid, user, command, state, threads, runtime", config2.sortMode).option("--packets", "Include packet monitor panel in output").option("--limit <n>", "Max number of processes to include in --once/--json/--csv/--tsv snapshots (0 = all)", "10").option("--alert-cpu <pct>", "CPU usage alert threshold (default: 90)", String(config2.cpuAlertPct)).option("--alert-temp <c>", "CPU temperature alert threshold in Celsius (default: 95)", String(config2.tempAlertC)).option("--minimal", "Render minimal macmon-style glanceable view (CPU, GPU, Memory, Temp)").option("--compact", "Launch pyre ui in compact widget mode").option("--ontop", "Keep pyre ui window always on top").option("--temp-unit <unit>", "Temperature display unit: c or f (default: c)", "c").option("--p2p-host <host>", "P2P host address (server: bind address, client: server address)").option("--p2p-port <port>", "P2P port number", "9876").option("--p2p-password <password>", "Password for P2P authentication").option("--p2p-tls", "Enable TLS encryption for P2P connections").option("--p2p-cert <file>", "TLS certificate file (PEM) for P2P server").option("--p2p-key <file>", "TLS private key file (PEM) for P2P server").option("--p2p-ca <file>", "TLS CA certificate file (PEM) for P2P client").option("--p2p-insecure", "Skip TLS certificate verification (client only)").option("--p2p-rate-limit <n>", "Max auth attempts per IP per minute (default: 5)", "5").option("--p2p-allow <ips>", "Comma-separated list of allowed IPs (empty = all)").option("--p2p-deny <ips>", "Comma-separated list of denied IPs").option("--p2p-audit-log <dir>", "Directory for P2P audit logs").option("--p2p-hmac-key <key>", "HMAC key for message signing (default: derived from password)").option("--since <range>", "Time range for anomalies digest (e.g. 1d, 7d, yesterday)").option("--plain", "Output in plain accessible text without ANSI colors or line-drawing characters").option("--a11y", "Alias for --plain").option("-d, --duration <seconds>", "Duration in seconds for stress/bench commands", "30").option("--cores <n>", "Number of worker threads for stress command").option("--exit-code", "Exit non-zero if system health checks trigger alerts (for check command)").option("--no-wizard", "Skip first-run interactive setup wizard").option("--install", "Install pyre web as a background launchd agent").option("--uninstall", "Uninstall pyre web launchd agent").option("--port <port>", "Port number for web server mode", "3000");
4561
+ program.name("pyre").version(pkg.version).description("Cross-platform system monitoring CLI: interactive live dashboard, stats, graphs, export, packet monitor, battery predictor").option("-j, --json", "Output as JSON").option("--html", "Output as HTML").option("--md", "Output as Markdown").option("-c, --csv", "Output as CSV").option("-t, --tsv", "Output as TSV").option("--detailed", "Include detailed system info and sensor readings").option("--theme <name>", "Default theme for live mode (default, dracula, cyberpunk, monochrome, nord, gruvbox)", config2.theme).option("--interval <seconds>", "Refresh interval for live mode", String(config2.interval)).option("--once", "Show a single static snapshot instead of live feed").option("--out <file>", "Also write the snapshot output to a file (--once/--json/--csv/--tsv modes)").option("--export-dir <dir>", "Directory used for live-mode snapshot exports and logs", config2.exportDir).option("--log", "Start continuous CSV logging immediately when live mode starts").option("--tree", "Show process tree view instead of flat list").option("--sort <key>", "Sort processes by: cpu, mem, pid, user, command, state, threads, runtime", config2.sortMode).option("--packets", "Include packet monitor panel in output").option("--limit <n>", "Max number of processes to include in --once/--json/--csv/--tsv snapshots (0 = all)", "10").option("--alert-cpu <pct>", "CPU usage alert threshold (default: 90)", String(config2.cpuAlertPct)).option("--alert-temp <c>", "CPU temperature alert threshold in Celsius (default: 95)", String(config2.tempAlertC)).option("--minimal", "Render minimal macmon-style glanceable view (CPU, GPU, Memory, Temp)").option("--compact", "Launch pyre ui in compact widget mode").option("--ontop", "Keep pyre ui window always on top").option("--temp-unit <unit>", "Temperature display unit: c or f (default: c)", "c").option("--p2p-host <host>", "P2P host address (server: bind address, client: server address)").option("--p2p-port <port>", "P2P port number", "9876").option("--p2p-password <password>", "Password for P2P authentication").option("--p2p-tls", "Enable TLS encryption for P2P connections").option("--p2p-cert <file>", "TLS certificate file (PEM) for P2P server").option("--p2p-key <file>", "TLS private key file (PEM) for P2P server").option("--p2p-ca <file>", "TLS CA certificate file (PEM) for P2P client").option("--p2p-insecure", "Skip TLS certificate verification (client only)").option("--p2p-rate-limit <n>", "Max auth attempts per IP per minute (default: 5)", "5").option("--p2p-allow <ips>", "Comma-separated list of allowed IPs (empty = all)").option("--p2p-deny <ips>", "Comma-separated list of denied IPs").option("--p2p-audit-log <dir>", "Directory for P2P audit logs").option("--p2p-hmac-key <key>", "HMAC key for message signing (default: derived from password)").option("--since <range>", "Time range for anomalies digest (e.g. 1d, 7d, yesterday)").option("--plain", "Output in plain accessible text without ANSI colors or line-drawing characters").option("--a11y", "Alias for --plain").option("-d, --duration <seconds>", "Duration in seconds for stress/bench commands", "30").option("--cores <n>", "Number of worker threads for stress command").option("--exit-code", "Exit non-zero if system health checks trigger alerts (for check command)").option("--no-wizard", "Skip first-run interactive setup wizard").option("--install", "Install pyre web as a background launchd agent").option("--uninstall", "Uninstall pyre web launchd agent").option("--port <port>", "Port number for web server mode", "3000").option("--prometheus-port <port>", "Port number for Prometheus exporter (default: 9090)", "9090").option("--speed <n>", "Playback speed multiplier for pyre replay (default: 1)", "1").option("--llm-backend <backend>", "AI backend: builtin (local zero-setup), ollama, or openai (default: builtin)", "builtin").option("--llm-model <model>", "AI model name for pyre explain (default: expert-rules-v1 / llama3.2 / gpt-4o-mini)").option("--api-key <key>", "API key for OpenAI-compatible LLM backend (or set OPENAI_API_KEY env)").option("--alert-slack <url>", "Slack webhook URL for alert notifications").option("--alert-discord <url>", "Discord webhook URL for alert notifications").option("--alert-pushover-token <token>", "Pushover application token for alert notifications").option("--alert-pushover-user <user>", "Pushover user key for alert notifications").option("--alert-ntfy <url>", "ntfy.sh topic URL for alert notifications (e.g. https://ntfy.sh/my-topic)").option("--watchdog-config <file>", "Path to custom watchdog JSON configuration rules").option("--dry-run", "Simulate actions in watchdog mode without executing kills").option("--top <n>", "Number of items to display in netusage command (default: 15)").option("--raw", "Print unparsed output for profiler").option("--sync-dir <dir>", "Custom directory path for cloud profile/config sync").option("--scan", "Perform active network sweep on LAN neighbors for topology map");
4373
4562
  program.addHelpText("after", `
4374
4563
  Commands:
4375
4564
  live Interactive live dashboard
4376
4565
  check One-line plain-English health summary
4377
4566
  pipe Continuous newline-delimited JSON stream for scripting
4378
4567
  stress Synthetic CPU/GPU load generator
4379
- ui Launch the native macOS UI dashboard
4568
+ ui Launch the live UI dashboard window
4380
4569
  web Serve an auto-refreshing live web portal
4381
- ssh <host> Stream live stats from a remote Mac over SSH
4382
- fleet <host1> [host2]... Multi-host live dashboard monitoring multiple Macs
4570
+ ssh <host> Stream live stats from a remote host over SSH
4571
+ fleet <host1> [host2]... Multi-host live dashboard monitoring multiple hosts
4383
4572
  bench <cmd> Log CPU, memory, network, power draw, and estimate kWh cost
4384
4573
  benchmark Run a 1-minute CPU benchmark calculating digits of PI
4385
4574
  anomalies Compute z-score anomalies and print plain-language digest
4386
4575
  doctor Run system diagnostics
4387
- extensions System Extensions inspector
4388
- brew Homebrew health panel
4576
+ extensions System Extensions / Driver inspector
4577
+ brew Package manager health panel (brew/apt/pacman/winget)
4389
4578
  update Check for pyre-cli updates
4390
4579
  profile <save|load|list> Atomic configuration profile management
4391
4580
  config <show|reset> View or reset persistent configuration file
4392
4581
  history Graph historical resource trends from CSV logs
4393
4582
  diff <file1> <file2> Compare two saved snapshot files side-by-side
4394
4583
  info Concise hardware summary
4395
- completions <shell> Generate shell auto-completion scripts
4584
+ completions <shell> Generate shell auto-completion scripts (zsh, bash, fish, powershell)
4396
4585
  xbar Generate an xbar / SwiftBar menu bar plugin script
4397
4586
  p2p <server|connect> Start a P2P server or connect to one
4398
4587
  server Print commands for starting P2P server/client
4399
4588
  blender Track active Blender render jobs and their progress
4589
+ prometheus Start a Prometheus metrics exporter on localhost
4590
+ smart S.M.A.R.T. disk health report (requires smartmontools)
4591
+ replay <file> Play back a pyre CSV log file in the TUI
4592
+ explain AI-powered plain-English anomaly explanation (Ollama/OpenAI)
4593
+ alert test Send a test notification to all configured alert channels
4594
+ netusage Real-time per-application network usage breakdown
4595
+ serve Lightweight structured JSON REST API server
4596
+ profile-proc <pid> CPU call-tree profiler and hot function sampler
4597
+ watchdog Persistent autonomous monitoring daemon and rules engine
4598
+ build Active compiler and build system tracker
4599
+ topo Network topology mapper and pyre peer auto-discovery
4600
+ sync <push|pull|status> Synchronize config profiles across Macs via iCloud/Drive
4400
4601
  `);
4401
4602
  program.parse(process.argv);
4402
4603
  var opts = program.opts();
@@ -4457,11 +4658,11 @@ async function runServerCommand() {
4457
4658
  console.log();
4458
4659
  }
4459
4660
  async function main() {
4460
- const { runSetupWizardIfNeeded } = await import("./wizard-Z3RGA3VB.js");
4661
+ const { runSetupWizardIfNeeded } = await import("./wizard-TD7BGX6O.js");
4461
4662
  await runSetupWizardIfNeeded(!opts.wizard);
4462
4663
  const cmd = program.args[0];
4463
4664
  if (cmd === "check") {
4464
- const { runCheckCommand } = await import("./checkCmd-T7O4KXXA.js");
4665
+ const { runCheckCommand } = await import("./checkCmd-RKW6QGDR.js");
4465
4666
  await runCheckCommand({
4466
4667
  exitCode: !!opts.exitCode,
4467
4668
  cpuAlertPct: config2.cpuAlertPct,
@@ -4471,7 +4672,7 @@ async function main() {
4471
4672
  return;
4472
4673
  }
4473
4674
  if (cmd === "pipe") {
4474
- const { runPipeCommand } = await import("./pipeCmd-TYLJHQ5B.js");
4675
+ const { runPipeCommand } = await import("./pipeCmd-7REDBLBU.js");
4475
4676
  const intervalSec = parseFloat(opts.interval) || 1;
4476
4677
  const samples = parseInt(opts.samples, 10) || 0;
4477
4678
  await runPipeCommand({
@@ -4494,12 +4695,12 @@ async function main() {
4494
4695
  return;
4495
4696
  }
4496
4697
  if (cmd === "extensions") {
4497
- const { printExtensionsReport } = await import("./extensions-DVA5YLGH.js");
4698
+ const { printExtensionsReport } = await import("./extensions-XJGHG7X3.js");
4498
4699
  await printExtensionsReport();
4499
4700
  return;
4500
4701
  }
4501
4702
  if (cmd === "blender") {
4502
- const { collectBlenderRenders } = await import("./blender-7UT5J7IP.js");
4703
+ const { collectBlenderRenders } = await import("./blender-FGH3ASEQ.js");
4503
4704
  const renders = await collectBlenderRenders();
4504
4705
  if (!renders.length) {
4505
4706
  console.log(chalk10.dim("No active Blender renders detected."));
@@ -4523,8 +4724,147 @@ async function main() {
4523
4724
  console.log(lines.join("\n"));
4524
4725
  return;
4525
4726
  }
4727
+ if (cmd === "prometheus") {
4728
+ const { runPrometheusServer } = await import("./prometheusCmd-PL64BLN5.js");
4729
+ const prometheusPort = parseInt(opts.prometheusPort, 10) || 9090;
4730
+ const intervalSec = parseFloat(opts.interval) || 15;
4731
+ await runPrometheusServer({ port: prometheusPort, interval: intervalSec, detailed: opts.detailed });
4732
+ return;
4733
+ }
4734
+ if (cmd === "smart") {
4735
+ const { runSmartCommand } = await import("./smartCmd-ODWKSFNG.js");
4736
+ await runSmartCommand();
4737
+ return;
4738
+ }
4739
+ if (cmd === "replay") {
4740
+ const file = program.args[1];
4741
+ if (!file) {
4742
+ console.error(chalk10.red(" \u2716 Usage: pyre replay <logfile.csv>"));
4743
+ process.exit(1);
4744
+ }
4745
+ const { runReplayCommand } = await import("./replayCmd-Z3PYXY7V.js");
4746
+ await runReplayCommand({
4747
+ file,
4748
+ speed: parseFloat(opts.speed) || 1,
4749
+ plain: opts.plain || opts.a11y
4750
+ });
4751
+ return;
4752
+ }
4753
+ if (cmd === "explain") {
4754
+ const { runExplainCommand } = await import("./explainCmd-MF6PIMLW.js");
4755
+ const backend = opts.llmBackend === "openai" ? "openai" : opts.llmBackend === "ollama" ? "ollama" : "builtin";
4756
+ await runExplainCommand({
4757
+ since: opts.since || "7d",
4758
+ backend,
4759
+ model: opts.llmModel,
4760
+ apiKey: opts.apiKey,
4761
+ dir: opts.exportDir
4762
+ });
4763
+ return;
4764
+ }
4765
+ if (cmd === "alert") {
4766
+ const sub = program.args[1];
4767
+ if (sub === "test") {
4768
+ const { sendTestAlert } = await import("./alertChannels-TPSQIJ7S.js");
4769
+ const { writeConfig: writeConfig2 } = await import("./config-GYGJXU2Q.js");
4770
+ if (opts.alertSlack || opts.alertDiscord || opts.alertPushoverToken || opts.alertPushoverUser || opts.alertNtfy) {
4771
+ writeConfig2({
4772
+ slackAlertUrl: opts.alertSlack || config2.slackAlertUrl,
4773
+ discordAlertUrl: opts.alertDiscord || config2.discordAlertUrl,
4774
+ pushoverToken: opts.alertPushoverToken || config2.pushoverToken,
4775
+ pushoverUser: opts.alertPushoverUser || config2.pushoverUser,
4776
+ ntfyUrl: opts.alertNtfy || config2.ntfyUrl
4777
+ });
4778
+ }
4779
+ const testConfig = {
4780
+ slackUrl: opts.alertSlack || config2.slackAlertUrl || void 0,
4781
+ discordUrl: opts.alertDiscord || config2.discordAlertUrl || void 0,
4782
+ pushoverToken: opts.alertPushoverToken || config2.pushoverToken || void 0,
4783
+ pushoverUser: opts.alertPushoverUser || config2.pushoverUser || void 0,
4784
+ ntfyUrl: opts.alertNtfy || config2.ntfyUrl || void 0
4785
+ };
4786
+ const hasAny = Object.values(testConfig).some((v) => !!v);
4787
+ if (!hasAny) {
4788
+ console.log(chalk10.yellow("\n \u26A0 No alert channels configured."));
4789
+ console.log(chalk10.dim(" Use --alert-slack <url>, --alert-discord <url>, --alert-ntfy <url>, etc.\n"));
4790
+ return;
4791
+ }
4792
+ console.log(chalk10.bold("\n \u{1F525} pyre alert test \u2014 firing test notification...\n"));
4793
+ await sendTestAlert(testConfig);
4794
+ console.log(chalk10.green(" \u2714 Test alert sent to all configured channels.\n"));
4795
+ } else {
4796
+ console.log(chalk10.yellow(" Usage: pyre alert test"));
4797
+ }
4798
+ return;
4799
+ }
4800
+ if (cmd === "netusage") {
4801
+ const { runNetUsageCommand } = await import("./netUsageCmd-WKBTW2YG.js");
4802
+ await runNetUsageCommand({
4803
+ top: opts.top ? parseInt(opts.top, 10) : 15,
4804
+ sort: opts.sort === "rx" || opts.sort === "tx" ? opts.sort : "total",
4805
+ json: !!opts.json
4806
+ });
4807
+ return;
4808
+ }
4809
+ if (cmd === "serve") {
4810
+ const { runServeCommand } = await import("./serveCmd-2H5VLF3P.js");
4811
+ const port = parseInt(opts.port, 10) || 8080;
4812
+ await runServeCommand({
4813
+ port,
4814
+ apiKey: opts.apiKey,
4815
+ detailed: opts.detailed
4816
+ });
4817
+ return;
4818
+ }
4819
+ if (cmd === "profile-proc") {
4820
+ const targetPid = parseInt(program.args[1], 10);
4821
+ if (isNaN(targetPid)) {
4822
+ console.error(chalk10.red(" \u2716 Usage: pyre profile-proc <pid> [--duration <sec>]"));
4823
+ process.exit(1);
4824
+ }
4825
+ const { runProfileProcCommand } = await import("./profileProcCmd-N4H6MCBT.js");
4826
+ const durationSec = parseFloat(opts.duration) || 5;
4827
+ await runProfileProcCommand({
4828
+ pid: targetPid,
4829
+ durationSec,
4830
+ raw: !!opts.raw
4831
+ });
4832
+ return;
4833
+ }
4834
+ if (cmd === "watchdog") {
4835
+ const { runWatchdogCommand } = await import("./watchdogCmd-UF53JPZB.js");
4836
+ await runWatchdogCommand({
4837
+ configFile: opts.watchdogConfig,
4838
+ dryRun: !!opts.dryRun,
4839
+ interval: parseFloat(opts.interval) || 2
4840
+ });
4841
+ return;
4842
+ }
4843
+ if (cmd === "build") {
4844
+ const { runBuildCommand } = await import("./buildCmd-CDPZ2ZFA.js");
4845
+ await runBuildCommand({
4846
+ json: !!opts.json
4847
+ });
4848
+ return;
4849
+ }
4850
+ if (cmd === "topo") {
4851
+ const { runTopoCommand } = await import("./topoCmd-DU2HHZNH.js");
4852
+ await runTopoCommand({
4853
+ scan: !!opts.scan,
4854
+ json: !!opts.json
4855
+ });
4856
+ return;
4857
+ }
4858
+ if (cmd === "sync") {
4859
+ const subcommand = program.args[1] || "status";
4860
+ const { runSyncCommand } = await import("./syncCmd-A2W3QB3S.js");
4861
+ await runSyncCommand(subcommand, {
4862
+ syncDir: opts.syncDir
4863
+ });
4864
+ return;
4865
+ }
4526
4866
  if (cmd === "brew") {
4527
- const { printBrewHealthReport } = await import("./brewHealth-F2BXF5M6.js");
4867
+ const { printBrewHealthReport } = await import("./brewHealth-P2GW5Y5C.js");
4528
4868
  await printBrewHealthReport();
4529
4869
  return;
4530
4870
  }
@@ -4536,7 +4876,7 @@ async function main() {
4536
4876
  if (cmd === "profile") {
4537
4877
  const sub = program.args[1];
4538
4878
  const name = program.args[2];
4539
- const { saveProfile, loadProfile, listProfiles } = await import("./config-KS5ZZ3VN.js");
4879
+ const { saveProfile, loadProfile, listProfiles } = await import("./config-GYGJXU2Q.js");
4540
4880
  if (sub === "save" && name) {
4541
4881
  saveProfile(name);
4542
4882
  console.log(chalk10.green(` \u2714 Profile '${name}' saved successfully.`));
@@ -4590,13 +4930,13 @@ async function main() {
4590
4930
  }
4591
4931
  if (cmd === "web") {
4592
4932
  if (opts.install) {
4593
- const { installLaunchdAgent } = await import("./service-WTS3NBQU.js");
4933
+ const { installLaunchdAgent } = await import("./service-Q74BBILO.js");
4594
4934
  const port = parseInt(opts.port || "3000", 10) || 3e3;
4595
4935
  installLaunchdAgent(port);
4596
4936
  return;
4597
4937
  }
4598
4938
  if (opts.uninstall) {
4599
- const { uninstallLaunchdAgent } = await import("./service-WTS3NBQU.js");
4939
+ const { uninstallLaunchdAgent } = await import("./service-Q74BBILO.js");
4600
4940
  uninstallLaunchdAgent();
4601
4941
  return;
4602
4942
  }
@@ -4627,7 +4967,7 @@ async function main() {
4627
4967
  const cfg = readConfig();
4628
4968
  console.log(JSON.stringify(cfg, null, 2));
4629
4969
  } else if (sub === "reset") {
4630
- fs9.rmSync(CONFIG_FILE, { force: true });
4970
+ fs10.rmSync(CONFIG_FILE, { force: true });
4631
4971
  console.log(chalk10.green("Config reset to defaults"));
4632
4972
  } else {
4633
4973
  console.log("Usage: pyre config <show|reset>");
@@ -4643,7 +4983,7 @@ async function main() {
4643
4983
  return;
4644
4984
  }
4645
4985
  if (cmd === "doctor") {
4646
- const { runDoctor, printDoctorReport } = await import("./doctor-UV7TWIHX.js");
4986
+ const { runDoctor, printDoctorReport } = await import("./doctor-GYHFTNZQ.js");
4647
4987
  const checks = await runDoctor();
4648
4988
  printDoctorReport(checks);
4649
4989
  return;
@@ -4653,7 +4993,7 @@ async function main() {
4653
4993
  return;
4654
4994
  }
4655
4995
  if (cmd === "completions") {
4656
- const { printCompletions } = await import("./completions-VQMHQCXE.js");
4996
+ const { printCompletions } = await import("./completions-JKYEJNGR.js");
4657
4997
  const shell = program.args[1] || "zsh";
4658
4998
  printCompletions(shell);
4659
4999
  return;
@@ -4672,6 +5012,11 @@ async function main() {
4672
5012
  if (opts.webhookUrl) config2.webhookUrl = opts.webhookUrl;
4673
5013
  if (opts.alertCmd) config2.alertCmd = opts.alertCmd;
4674
5014
  if (opts.exportDir) config2.exportDir = opts.exportDir;
5015
+ if (opts.alertSlack) config2.slackAlertUrl = opts.alertSlack;
5016
+ if (opts.alertDiscord) config2.discordAlertUrl = opts.alertDiscord;
5017
+ if (opts.alertPushoverToken) config2.pushoverToken = opts.alertPushoverToken;
5018
+ if (opts.alertPushoverUser) config2.pushoverUser = opts.alertPushoverUser;
5019
+ if (opts.alertNtfy) config2.ntfyUrl = opts.alertNtfy;
4675
5020
  const tempUnit = opts.tempUnit === "f" ? "f" : "c";
4676
5021
  if (cmd === "live" || !isExportMode() && !opts.once && cmd !== "p2p") {
4677
5022
  if (!process.stdout.isTTY && cmd !== "live") {
@@ -4724,7 +5069,7 @@ async function main() {
4724
5069
  else output = formatTable(data, { width: process.stdout.columns || 80, sortBy: opts.sort, treeView: opts.tree ?? config2.treeView, minimal: !!opts.minimal, visible: { packets: opts.packets ? true : void 0 } });
4725
5070
  console.log(output);
4726
5071
  if (opts.out) {
4727
- fs9.writeFileSync(opts.out, stripAnsi(output));
5072
+ fs10.writeFileSync(opts.out, stripAnsi(output));
4728
5073
  console.log(chalk10.dim(`
4729
5074
  Saved to ${opts.out}`));
4730
5075
  }
@@ -4908,11 +5253,11 @@ async function runP2PConnect() {
4908
5253
  }
4909
5254
  async function runInfoCommand() {
4910
5255
  const data = await collectAll({ detailed: true });
4911
- const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-JB4OGUEF.js");
5256
+ const { getDisplayInfo, getTimeMachineStatus } = await import("./collectors-H43PN4PS.js");
4912
5257
  const displays = await getDisplayInfo();
4913
5258
  const tmStatus = await getTimeMachineStatus();
4914
5259
  const lines = [];
4915
- lines.push(chalk10.bold(" pyre info \u2014 Mac System Information"));
5260
+ lines.push(chalk10.bold(` pyre info \u2014 System Information (${data.header.os})`));
4916
5261
  lines.push("");
4917
5262
  lines.push(` Hostname: ${data.header.hostname}`);
4918
5263
  lines.push(` OS: ${data.header.os}`);
@@ -4937,13 +5282,15 @@ async function runInfoCommand() {
4937
5282
  lines.push(` \u2022 ${d.name}: ${d.resolution}${d.isMain ? " (Main)" : ""}${d.connectionType ? ` [${d.connectionType}]` : ""}`);
4938
5283
  }
4939
5284
  }
4940
- lines.push("");
4941
- lines.push(chalk10.cyan.bold(" \u23F1 Time Machine Backup:"));
4942
- if (!tmStatus.configured) {
4943
- lines.push(" \u2022 Not configured");
4944
- } else {
4945
- lines.push(` \u2022 Status: ${tmStatus.running ? chalk10.yellow(`Backup in progress (${tmStatus.percent || 0}%)`) : chalk10.green("Idle")}`);
4946
- lines.push(` \u2022 Latest Backup: ${tmStatus.latestBackup}`);
5285
+ if (process.platform === "darwin") {
5286
+ lines.push("");
5287
+ lines.push(chalk10.cyan.bold(" \u23F1 Time Machine Backup:"));
5288
+ if (!tmStatus.configured) {
5289
+ lines.push(" \u2022 Not configured");
5290
+ } else {
5291
+ lines.push(` \u2022 Status: ${tmStatus.running ? chalk10.yellow(`Backup in progress (${tmStatus.percent || 0}%)`) : chalk10.green("Idle")}`);
5292
+ lines.push(` \u2022 Latest Backup: ${tmStatus.latestBackup}`);
5293
+ }
4947
5294
  }
4948
5295
  console.log(lines.join("\n"));
4949
5296
  }
@@ -4987,7 +5334,7 @@ async function runWebCommand() {
4987
5334
  return;
4988
5335
  }
4989
5336
  if (url === "/metrics") {
4990
- const { formatPrometheusMetrics } = await import("./prometheus-LFYHF6VO.js");
5337
+ const { formatPrometheusMetrics } = await import("./prometheus-5BL5PVLC.js");
4991
5338
  const data = await collectAll({ detailed: true });
4992
5339
  res.writeHead(200, { "Content-Type": "text/plain; version=0.0.4; charset=utf-8" });
4993
5340
  res.end(formatPrometheusMetrics(data));
@@ -5021,9 +5368,9 @@ async function runWebCommand() {
5021
5368
  res.end(getDashboardHtml());
5022
5369
  } else if (url === "/setup") {
5023
5370
  res.writeHead(200, { "Content-Type": "text/html" });
5024
- const indexPath = path5.join(process.cwd(), "index.html");
5025
- if (fs9.existsSync(indexPath)) {
5026
- res.end(fs9.readFileSync(indexPath, "utf-8"));
5371
+ const indexPath = path6.join(process.cwd(), "index.html");
5372
+ if (fs10.existsSync(indexPath)) {
5373
+ res.end(fs10.readFileSync(indexPath, "utf-8"));
5027
5374
  } else {
5028
5375
  res.end(getDashboardHtml());
5029
5376
  }
@@ -5073,10 +5420,26 @@ Error: Port ${port} is already in use.`));
5073
5420
  });
5074
5421
  });
5075
5422
  }
5423
+ async function openBrowserUrl(url) {
5424
+ const { spawn } = await import("child_process");
5425
+ const platform = process.platform;
5426
+ if (platform === "win32") {
5427
+ spawn("cmd.exe", ["/c", "start", "", url], { detached: true, stdio: "ignore" });
5428
+ } else if (platform === "darwin") {
5429
+ spawn("open", [url], { detached: true, stdio: "ignore" });
5430
+ } else {
5431
+ spawn("xdg-open", [url], { detached: true, stdio: "ignore" });
5432
+ }
5433
+ }
5076
5434
  async function runUiCommand() {
5077
5435
  opts.port = "0";
5078
5436
  const port = await runWebCommand();
5079
- const swiftScript = `
5437
+ const url = `http://localhost:${port}`;
5438
+ if (process.platform === "darwin") {
5439
+ const { run } = await import("./run-HOGQDDTA.js");
5440
+ const hasSwift = (await run("which swift 2>/dev/null", "")).trim().length > 0;
5441
+ if (hasSwift) {
5442
+ const swiftScript = `
5080
5443
  import Cocoa
5081
5444
  import WebKit
5082
5445
 
@@ -5178,27 +5541,36 @@ let delegate = AppDelegate()
5178
5541
  app.delegate = delegate
5179
5542
  app.run()
5180
5543
  `;
5181
- const scriptPath = path5.join(os2.tmpdir(), "pyre-ui.swift");
5182
- fs9.writeFileSync(scriptPath, swiftScript);
5183
- const { spawn } = await import("child_process");
5184
- console.log(chalk10.cyan(`
5544
+ const scriptPath = path6.join(os2.tmpdir(), "pyre-ui.swift");
5545
+ fs10.writeFileSync(scriptPath, swiftScript);
5546
+ const { spawn } = await import("child_process");
5547
+ console.log(chalk10.cyan(`
5185
5548
  Launching native UI window...`));
5186
- const ui = spawn("swift", [scriptPath], { stdio: "inherit" });
5187
- ui.on("close", () => {
5188
- console.log(chalk10.dim("UI window closed, shutting down server..."));
5189
- process.exit(0);
5190
- });
5191
- process.once("SIGINT", () => {
5192
- ui.kill();
5193
- process.exit(0);
5549
+ const ui = spawn("swift", [scriptPath], { stdio: "inherit" });
5550
+ ui.on("close", () => {
5551
+ console.log(chalk10.dim("UI window closed, shutting down server..."));
5552
+ process.exit(0);
5553
+ });
5554
+ process.once("SIGINT", () => {
5555
+ ui.kill();
5556
+ process.exit(0);
5557
+ });
5558
+ return;
5559
+ }
5560
+ }
5561
+ console.log(chalk10.cyan(`
5562
+ Opening live UI dashboard at ${url}...`));
5563
+ await openBrowserUrl(url);
5564
+ console.log(chalk10.dim(" Press Ctrl+C to stop server.\n"));
5565
+ await new Promise(() => {
5194
5566
  });
5195
5567
  }
5196
5568
  async function runBenchCommand(benchCmd) {
5197
5569
  const { spawn } = await import("child_process");
5198
5570
  const logDir = opts.exportDir || "./pyre-exports";
5199
- const file = path5.join(logDir, `pyre-bench-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.csv`);
5200
- if (!fs9.existsSync(logDir)) fs9.mkdirSync(logDir, { recursive: true });
5201
- const stream = fs9.createWriteStream(file, { flags: "a" });
5571
+ const file = path6.join(logDir, `pyre-bench-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.csv`);
5572
+ if (!fs10.existsSync(logDir)) fs10.mkdirSync(logDir, { recursive: true });
5573
+ const stream = fs10.createWriteStream(file, { flags: "a" });
5202
5574
  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");
5203
5575
  console.log(chalk10.bold(`
5204
5576
  pyre bench`));
@@ -5208,7 +5580,7 @@ async function runBenchCommand(benchCmd) {
5208
5580
  const startTime = Date.now();
5209
5581
  let accumulatedWattSeconds = 0;
5210
5582
  let powerSamplesCount = 0;
5211
- const child = spawn("sh", ["-c", benchCmd]);
5583
+ const child = process.platform === "win32" ? spawn("cmd.exe", ["/c", benchCmd], { stdio: "inherit" }) : spawn("sh", ["-c", benchCmd]);
5212
5584
  child.stdout.on("data", (d) => process.stdout.write(d));
5213
5585
  child.stderr.on("data", (d) => process.stdout.write(d));
5214
5586
  const interval = parseFloat(opts.interval) || 2;