pyre-cli 10.3.0 → 10.4.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 (88) hide show
  1. package/README.md +41 -0
  2. package/dist/{alertChannels-TPSQIJ7S.js → alertChannels-MNX4XH6J.js} +1 -0
  3. package/dist/{anomaliesCmd-25OFKYPK.js → anomaliesCmd-HHTY5LYL.js} +2 -0
  4. package/dist/benchGitCmd-HIXUC5EK.js +87 -0
  5. package/dist/{benchmarkCmd-A3ETK6VY.js → benchmarkCmd-B7DLP2VC.js} +2 -0
  6. package/dist/{blender-FGH3ASEQ.js → blender-6ZX5SPYC.js} +3 -1
  7. package/dist/{brewHealth-P2GW5Y5C.js → brewHealth-VFW33QB3.js} +1 -0
  8. package/dist/budgetCmd-TGO3RYLN.js +11 -0
  9. package/dist/{buildCmd-CDPZ2ZFA.js → buildCmd-E7KUCFYG.js} +3 -1
  10. package/dist/{checkCmd-WQAHAUWO.js → checkCmd-QLYTP77Y.js} +5 -3
  11. package/dist/chunk-3RG5ZIWI.js +10 -0
  12. package/dist/{chunk-VIACN2RU.js → chunk-FYCBGUTW.js} +41 -19
  13. package/dist/chunk-JH7P6KLN.js +143 -0
  14. package/dist/{chunk-QM5L43EK.js → chunk-LDJB5V4N.js} +5 -1
  15. package/dist/chunk-NMGQVRCA.js +111 -0
  16. package/dist/chunk-OCL7P7N7.js +936 -0
  17. package/dist/{chunk-23YWCI3O.js → chunk-QEH4LR6P.js} +28 -686
  18. package/dist/{chunk-KVUMOA6C.js → chunk-RKPGPVTO.js} +77 -4
  19. package/dist/chunk-UF3XQ3R3.js +3800 -0
  20. package/dist/{collectors-JDN5Y7EV.js → collectors-V7SCIIDH.js} +5 -3
  21. package/dist/{completions-ZVTNE52B.js → completions-FTPXDSR2.js} +44 -3
  22. package/dist/{config-J4YMADEF.js → config-5WW5M63G.js} +2 -1
  23. package/dist/containersCmd-FSMYOBG2.js +84 -0
  24. package/dist/{doctor-LHFW4UJP.js → doctor-O4DIAMHK.js} +3 -2
  25. package/dist/{explainCmd-MF6PIMLW.js → explainCmd-7PBPA7O5.js} +1 -0
  26. package/dist/{extensions-XJGHG7X3.js → extensions-QQEV7U3D.js} +1 -0
  27. package/dist/{firebaseSync-ON3RZ6KX.js → firebaseSync-LRN67UTV.js} +2 -0
  28. package/dist/flightRecorder-GJIAQOXC.js +162 -0
  29. package/dist/index.js +736 -4125
  30. package/dist/inspectCmd-5FXJBJTK.js +137 -0
  31. package/dist/menu-AJFGDLBP.js +1233 -0
  32. package/dist/monitors/platform/windows-ui.ps1 +31 -29
  33. package/dist/{netUsageCmd-67IBZZYN.js → netUsageCmd-JP6YKBPF.js} +7 -5
  34. package/dist/optimizeCmd-WZKLFYZU.js +1110 -0
  35. package/dist/{pipeCmd-2N4JPKYE.js → pipeCmd-CPK6FVKB.js} +5 -3
  36. package/dist/playbookCmd-FRROIEXR.js +145 -0
  37. package/dist/{profileProcCmd-N4H6MCBT.js → profileProcCmd-5Y3733E7.js} +64 -13
  38. package/dist/{prometheus-5BL5PVLC.js → prometheus-CYNUR3QT.js} +1 -0
  39. package/dist/{prometheusCmd-LEUM5WCZ.js → prometheusCmd-G2CSEZRQ.js} +5 -3
  40. package/dist/{replayCmd-Z3PYXY7V.js → replayCmd-ODB5SPOQ.js} +25 -6
  41. package/dist/{run-HOGQDDTA.js → run-UJQYT2TO.js} +1 -0
  42. package/dist/{serveCmd-L7LVJOZN.js → serveCmd-KDPWLVQI.js} +5 -3
  43. package/dist/{service-7GIX3YNC.js → service-LIYYR3UI.js} +1 -0
  44. package/dist/{smartCmd-ODWKSFNG.js → smartCmd-2BJJ37GV.js} +130 -5
  45. package/dist/statuslineCmd-EUKYRR2G.js +75 -0
  46. package/dist/{stressCmd-MFXMFXYX.js → stressCmd-GKHGY26X.js} +2 -0
  47. package/dist/{syncCmd-42IRF633.js → syncCmd-MIBFKX24.js} +16 -5
  48. package/dist/topoCmd-RTXQJ6XA.js +189 -0
  49. package/dist/{updateCheck-52X6LJG5.js → updateCheck-LJHEMG3G.js} +2 -0
  50. package/dist/{watchdogCmd-6GR4UN6B.js → watchdogCmd-XM6OPTKM.js} +41 -6
  51. package/dist/{wizard-JQMQWFAW.js → wizard-PRDOJROQ.js} +2 -1
  52. package/package.json +2 -2
  53. package/src/benchGitCmd.ts +110 -0
  54. package/src/budgetCmd.ts +199 -0
  55. package/src/completions.ts +42 -3
  56. package/src/containersCmd.ts +106 -0
  57. package/src/doctor.ts +86 -6
  58. package/src/easy_features.test.ts +83 -0
  59. package/src/flightRecorder.ts +214 -0
  60. package/src/index.ts +181 -8
  61. package/src/inspectCmd.ts +165 -0
  62. package/src/live/input.ts +70 -3
  63. package/src/live/render.ts +1 -1
  64. package/src/live/state.ts +2 -1
  65. package/src/menu/commands.ts +754 -0
  66. package/src/menu/index.ts +358 -0
  67. package/src/menu/render.ts +272 -0
  68. package/src/menu/types.ts +63 -0
  69. package/src/menu.test.ts +110 -0
  70. package/src/monitors/blender.ts +49 -19
  71. package/src/monitors/buildTracker.ts +72 -54
  72. package/src/monitors/platform/windows-ui.ps1 +31 -29
  73. package/src/monitors/platform/windows.ts +613 -280
  74. package/src/optimizeCmd.ts +1248 -0
  75. package/src/platform.test.ts +135 -0
  76. package/src/playbookCmd.ts +156 -0
  77. package/src/power_features.test.ts +251 -0
  78. package/src/profileProcCmd.ts +68 -21
  79. package/src/replayCmd.ts +15 -6
  80. package/src/smartCmd.ts +149 -10
  81. package/src/state/config.ts +13 -0
  82. package/src/statuslineCmd.ts +87 -0
  83. package/src/syncCmd.ts +12 -0
  84. package/src/topoCmd.ts +83 -50
  85. package/src/watchdogCmd.ts +40 -3
  86. package/src/xbar.ts +195 -17
  87. package/dist/chunk-M73HCQD2.js +0 -95
  88. package/dist/topoCmd-DZ3YS5IL.js +0 -148
package/README.md CHANGED
@@ -107,6 +107,7 @@ Launch with `pyre live` for a responsive, full-terminal dashboard featuring:
107
107
  | **Fleet & SSH Aggregation** | Monitor remote nodes over SSH (`pyre ssh <host>`) or aggregate hundreds of servers into an auto-tiled overview (`pyre fleet`). |
108
108
  | **Socket & Packet Forensics** | Track socket connection states (`ESTABLISHED`, `LISTENING`, `TIME_WAIT`), top remote hosts, and per-process network bandwidth. |
109
109
  | **AI Anomaly Explanation** | Explain performance spikes and thermal throttles in plain English using local Ollama models or OpenAI (`pyre explain`). |
110
+ | **macOS System Optimizer** | Reclaim memory and disk space: quit idle/forgotten apps, flush DNS, purge inactive RAM, clean developer caches, empty trash, and audit broken startup agents (`pyre optimize`). |
110
111
  | **Multi-Format Export** | Export instant snapshots to JSON, CSV, TSV, HTML, or Markdown reports, plus an importable Grafana dashboard template. |
111
112
 
112
113
  ---
@@ -239,6 +240,7 @@ pyre alert test # Send a test notification to all configured ale
239
240
  pyre profile-proc <pid> # CPU call-tree profiler and hot function sampler
240
241
  pyre sync <push|pull|status> # Synchronize config profiles across machines
241
242
  pyre xbar # Generate an xbar / SwiftBar menu bar plugin script
243
+ pyre optimize [subcommand] # macOS optimizer: quit idle apps, memory, caches, trash, startup, dns
242
244
  pyre update # Check for pyre-cli updates
243
245
  ```
244
246
 
@@ -264,6 +266,45 @@ Security and networking capabilities:
264
266
 
265
267
  ---
266
268
 
269
+ ## macOS System Optimizer (`pyre optimize`)
270
+
271
+ Active memory, disk, and application lifecycle optimization for macOS:
272
+
273
+ ```bash
274
+ # 1. System health & optimization overview report (safe, dry-run by default)
275
+ pyre optimize
276
+
277
+ # 2. Find and gracefully quit apps open but unused recently
278
+ pyre optimize apps # Interactive prompt with memory & CPU summary
279
+ pyre optimize apps --idle 30m --keep 3 # Custom idle duration & recent apps to keep
280
+ pyre optimize apps --dry-run # Preview without quitting
281
+
282
+ # 3. Background app activity tracker (records precise frontmost app usage)
283
+ pyre optimize track # Run tracker in current terminal
284
+ pyre optimize track --install # Install lightweight background launchd agent
285
+ pyre optimize track --auto-quit 2h # Daemon auto-quits idle apps & sends desktop alert
286
+ pyre optimize track --uninstall # Remove background tracker
287
+
288
+ # 4. Memory pressure & inactive purge
289
+ pyre optimize memory # Shows pressure, swap, compressed RAM, top consumers
290
+ pyre optimize memory -y # Flushes inactive file cache via `sudo purge`
291
+
292
+ # 5. Developer & system caches
293
+ pyre optimize caches # Measure Xcode DerivedData, Homebrew, npm/yarn/pnpm
294
+ pyre optimize caches --clean # Clean selected caches (safely skips running apps)
295
+
296
+ # 6. Trash & startup maintenance
297
+ pyre optimize trash --empty # Measure and empty macOS Trash
298
+ pyre optimize startup # Audit Login Items & LaunchAgents, flag broken leftovers
299
+ pyre optimize dns # Flush macOS DNS cache via dscacheutil & mDNSResponder
300
+
301
+ # 7. Protect whitelist
302
+ pyre optimize protect "Spotify" # Whitelist app so it is never automatically quit
303
+ pyre optimize unprotect "Spotify" # Remove app from custom whitelist
304
+ ```
305
+
306
+ ---
307
+
267
308
  ## Requirements
268
309
 
269
310
  | Platform | Minimum Requirement | Notes |
@@ -2,6 +2,7 @@ import {
2
2
  sendAlert,
3
3
  sendTestAlert
4
4
  } from "./chunk-Z2YV5HKV.js";
5
+ import "./chunk-3RG5ZIWI.js";
5
6
  export {
6
7
  sendAlert,
7
8
  sendTestAlert
@@ -1,3 +1,5 @@
1
+ import "./chunk-3RG5ZIWI.js";
2
+
1
3
  // src/anomaliesCmd.ts
2
4
  import fs from "fs";
3
5
  import path from "path";
@@ -0,0 +1,87 @@
1
+ import {
2
+ formatBytes,
3
+ runWithBudget
4
+ } from "./chunk-JH7P6KLN.js";
5
+ import "./chunk-3RG5ZIWI.js";
6
+
7
+ // src/benchGitCmd.ts
8
+ import { execSync } from "child_process";
9
+ import chalk from "chalk";
10
+ async function runBenchGitCommand(branchA, branchB, commandStr) {
11
+ try {
12
+ const status = execSync("git status --porcelain", { encoding: "utf-8" }).trim();
13
+ if (status) {
14
+ console.error(chalk.red("\n \u2716 Working directory contains uncommitted changes."));
15
+ console.log(chalk.yellow(" Please commit or stash your changes before running `pyre bench-git`.\n"));
16
+ return;
17
+ }
18
+ } catch (err) {
19
+ console.error(chalk.red(`
20
+ \u2716 Not a git repository: ${err.message}`));
21
+ return;
22
+ }
23
+ const startingBranch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
24
+ console.log(chalk.bold(`
25
+ \u{1F525} pyre bench-git \u2014 Cross-Branch Performance Differ
26
+ `));
27
+ console.log(chalk.dim(` Command: ${commandStr}`));
28
+ console.log(chalk.dim(` Baseline Branch: ${branchA}`));
29
+ console.log(chalk.dim(` Target Branch: ${branchB}`));
30
+ console.log(chalk.dim(` Current Branch: ${startingBranch}
31
+ `));
32
+ const tokens = commandStr.split(/\s+/).filter(Boolean);
33
+ try {
34
+ console.log(chalk.bold.cyan(`\u25B6 [1/2] Checking out '${branchA}' and benchmarking...`));
35
+ execSync(`git checkout ${branchA}`, { stdio: "pipe" });
36
+ const resA = await runWithBudget(tokens, {});
37
+ console.log(chalk.bold.cyan(`
38
+ \u25B6 [2/2] Checking out '${branchB}' and benchmarking...`));
39
+ execSync(`git checkout ${branchB}`, { stdio: "pipe" });
40
+ const resB = await runWithBudget(tokens, {});
41
+ execSync(`git checkout ${startingBranch}`, { stdio: "pipe" });
42
+ console.log(chalk.dim(`
43
+ Restored workspace to '${startingBranch}'.`));
44
+ printGitComparisonTable(branchA, branchB, resA, resB);
45
+ } catch (err) {
46
+ try {
47
+ execSync(`git checkout ${startingBranch}`, { stdio: "pipe" });
48
+ } catch {
49
+ }
50
+ console.error(chalk.red(`
51
+ \u2716 Benchmark failed: ${err.message}`));
52
+ }
53
+ }
54
+ function printGitComparisonTable(branchA, branchB, resA, resB) {
55
+ const diffPct = (valA, valB, invertGood = false) => {
56
+ if (valA === 0) return "0%";
57
+ const delta = (valB - valA) / valA * 100;
58
+ const sign = delta > 0 ? "+" : "";
59
+ const formatted = `${sign}${delta.toFixed(1)}%`;
60
+ const isGood = invertGood ? delta > 0 : delta < 0;
61
+ const color = delta === 0 ? chalk.dim : isGood ? chalk.green : chalk.red;
62
+ return color(formatted);
63
+ };
64
+ console.log(chalk.bold("\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
65
+ console.log(chalk.bold(" \u{1F525} Cross-Branch Performance Comparison Results"));
66
+ console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
67
+ const colBranchA = branchA.padEnd(16);
68
+ const colBranchB = branchB.padEnd(16);
69
+ console.log(` ${"Metric".padEnd(18)} ${colBranchA} ${colBranchB} Delta`);
70
+ console.log(chalk.dim(" \u2500".repeat(30)));
71
+ const durA = `${resA.durationSec.toFixed(2)}s`.padEnd(16);
72
+ const durB = `${resB.durationSec.toFixed(2)}s`.padEnd(16);
73
+ console.log(` ${"Duration".padEnd(18)} ${durA} ${durB} ${diffPct(resA.durationSec, resB.durationSec)}`);
74
+ const ramA = formatBytes(resA.peakMemoryBytes).padEnd(16);
75
+ const ramB = formatBytes(resB.peakMemoryBytes).padEnd(16);
76
+ console.log(` ${"Peak Memory".padEnd(18)} ${ramA} ${ramB} ${diffPct(resA.peakMemoryBytes, resB.peakMemoryBytes)}`);
77
+ const cpuA = `${resA.peakCpuPct.toFixed(1)}%`.padEnd(16);
78
+ const cpuB = `${resB.peakCpuPct.toFixed(1)}%`.padEnd(16);
79
+ console.log(` ${"Peak CPU".padEnd(18)} ${cpuA} ${cpuB} ${diffPct(resA.peakCpuPct, resB.peakCpuPct)}`);
80
+ const codeA = `${resA.exitCode}`.padEnd(16);
81
+ const codeB = `${resB.exitCode}`.padEnd(16);
82
+ console.log(` ${"Exit Code".padEnd(18)} ${codeA} ${codeB}`);
83
+ console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
84
+ }
85
+ export {
86
+ runBenchGitCommand
87
+ };
@@ -1,3 +1,5 @@
1
+ import "./chunk-3RG5ZIWI.js";
2
+
1
3
  // src/benchmarkCmd.ts
2
4
  import chalk from "chalk";
3
5
  async function runBenchmarkCommand() {
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  collectBlenderRenders
3
- } from "./chunk-VIACN2RU.js";
3
+ } from "./chunk-FYCBGUTW.js";
4
+ import "./chunk-OCL7P7N7.js";
4
5
  import "./chunk-MZO5QKUR.js";
6
+ import "./chunk-3RG5ZIWI.js";
5
7
  export {
6
8
  collectBlenderRenders
7
9
  };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  run
3
3
  } from "./chunk-MZO5QKUR.js";
4
+ import "./chunk-3RG5ZIWI.js";
4
5
 
5
6
  // src/brewHealth.ts
6
7
  import chalk from "chalk";
@@ -0,0 +1,11 @@
1
+ import {
2
+ formatBytes,
3
+ parseBytesString,
4
+ runWithBudget
5
+ } from "./chunk-JH7P6KLN.js";
6
+ import "./chunk-3RG5ZIWI.js";
7
+ export {
8
+ formatBytes,
9
+ parseBytesString,
10
+ runWithBudget
11
+ };
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  collectBuilds
3
- } from "./chunk-M73HCQD2.js";
3
+ } from "./chunk-NMGQVRCA.js";
4
+ import "./chunk-OCL7P7N7.js";
4
5
  import "./chunk-MZO5QKUR.js";
6
+ import "./chunk-3RG5ZIWI.js";
5
7
 
6
8
  // src/buildCmd.ts
7
9
  import chalk from "chalk";
@@ -1,10 +1,12 @@
1
1
  import "./chunk-IM3IQMMT.js";
2
2
  import {
3
3
  collectAll
4
- } from "./chunk-23YWCI3O.js";
5
- import "./chunk-M73HCQD2.js";
6
- import "./chunk-VIACN2RU.js";
4
+ } from "./chunk-QEH4LR6P.js";
5
+ import "./chunk-NMGQVRCA.js";
6
+ import "./chunk-FYCBGUTW.js";
7
+ import "./chunk-OCL7P7N7.js";
7
8
  import "./chunk-MZO5QKUR.js";
9
+ import "./chunk-3RG5ZIWI.js";
8
10
 
9
11
  // src/checkCmd.ts
10
12
  import chalk from "chalk";
@@ -0,0 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export {
9
+ __require
10
+ };
@@ -1,14 +1,20 @@
1
+ import {
2
+ collectWindowsProcesses
3
+ } from "./chunk-OCL7P7N7.js";
1
4
  import {
2
5
  run
3
6
  } from "./chunk-MZO5QKUR.js";
4
7
 
5
8
  // src/monitors/blender.ts
6
- var RENDER_LOG_DIR = "/tmp/blender_render_logs";
9
+ import fs from "fs";
10
+ import os from "os";
11
+ import path from "path";
12
+ var RENDER_LOG_DIR = path.join(os.tmpdir(), "blender_render_logs");
7
13
  function blendFileFromArgs(args) {
8
14
  const nonFlag = args.filter((a) => !a.startsWith("-"));
9
15
  for (const a of nonFlag) {
10
16
  if (a.endsWith(".blend") || a.includes(".blend")) {
11
- return a.split("/").pop() || a;
17
+ return a.split(/[/\\]/).pop() || a;
12
18
  }
13
19
  }
14
20
  return "unknown.blend";
@@ -24,15 +30,17 @@ function outputPathFromArgs(args) {
24
30
  const oIdx = args.indexOf("-o");
25
31
  if (oIdx !== -1 && args[oIdx + 1]) {
26
32
  let p = args[oIdx + 1];
27
- if (p.endsWith("/")) p = p.slice(0, -1);
28
- return p.split("/").slice(0, -1).join("/") || "/tmp";
33
+ if (p.endsWith("/") || p.endsWith("\\")) p = p.slice(0, -1);
34
+ const parts = p.split(/[/\\]/);
35
+ return parts.slice(0, -1).join("/") || os.tmpdir();
29
36
  }
30
37
  const fFlag = args.find((a) => a.startsWith("-o="));
31
38
  if (fFlag) {
32
39
  const p = fFlag.slice(3);
33
- return p.endsWith("/") ? p.slice(0, -1).split("/").slice(0, -1).join("/") || "/tmp" : p.split("/").slice(0, -1).join("/") || "/tmp";
40
+ const parts = p.split(/[/\\]/);
41
+ return parts.slice(0, -1).join("/") || os.tmpdir();
34
42
  }
35
- return "/tmp";
43
+ return os.tmpdir();
36
44
  }
37
45
  function frameRangeFromArgs(args) {
38
46
  const fIdx = args.indexOf("-f");
@@ -54,11 +62,12 @@ function frameRangeFromArgs(args) {
54
62
  async function parseRenderLog(blendName) {
55
63
  const baseName = blendName.replace(/\.blend$/i, "");
56
64
  try {
57
- const entries = (await run(`ls -t "${RENDER_LOG_DIR}" 2>/dev/null | grep "^${baseName}_" | head -5`, "")).trim().split("\n").filter(Boolean);
65
+ if (!fs.existsSync(RENDER_LOG_DIR)) return null;
66
+ const entries = fs.readdirSync(RENDER_LOG_DIR).filter((f) => f.startsWith(`${baseName}_`)).map((f) => ({ name: f, time: fs.statSync(path.join(RENDER_LOG_DIR, f)).mtimeMs })).sort((a, b) => b.time - a.time).slice(0, 5);
58
67
  for (const entry of entries) {
59
- const logPath = `${RENDER_LOG_DIR}/${entry}`;
68
+ const logPath = path.join(RENDER_LOG_DIR, entry.name);
60
69
  try {
61
- const content = await run(`cat "${logPath}" 2>/dev/null`, "", 500);
70
+ const content = fs.readFileSync(logPath, "utf-8");
62
71
  if (!content) continue;
63
72
  const lines = content.split("\n");
64
73
  const rendered = lines.filter((l) => /Fra:(\d+)/i.test(l)).length;
@@ -80,18 +89,31 @@ async function parseRenderLog(blendName) {
80
89
  async function collectBlenderRenders() {
81
90
  const renders = [];
82
91
  try {
83
- const psRaw = await run("ps -eo pid,pcpu,pmem,state,command 2>/dev/null | grep -iE '[b]lender'", "", 2e3);
84
- if (!psRaw) return renders;
85
- const lines = psRaw.split("\n");
86
- for (const line of lines) {
87
- const parts = line.trim().match(/\s*(\d+)\s+([\d.]+)\s+([\d.]+)\s+(\S+)\s+(.+)/);
88
- if (!parts) continue;
89
- const pid = parseInt(parts[1], 10);
90
- const cpuPct = parseFloat(parts[2]);
91
- const command = parts[5];
92
+ const procList = [];
93
+ if (process.platform === "win32") {
94
+ const procs = await collectWindowsProcesses();
95
+ for (const p of procs) {
96
+ if (/blender/i.test(p.command)) {
97
+ procList.push({ pid: p.pid, cpuPct: p.cpu, command: p.command });
98
+ }
99
+ }
100
+ } else {
101
+ const psRaw = await run("ps -eo pid,pcpu,pmem,state,command 2>/dev/null | grep -iE '[b]lender'", "", 2e3);
102
+ if (psRaw) {
103
+ const lines = psRaw.split("\n");
104
+ for (const line of lines) {
105
+ const parts = line.trim().match(/\s*(\d+)\s+([\d.]+)\s+([\d.]+)\s+(\S+)\s+(.+)/);
106
+ if (parts) {
107
+ procList.push({ pid: parseInt(parts[1], 10), cpuPct: parseFloat(parts[2]), command: parts[5] });
108
+ }
109
+ }
110
+ }
111
+ }
112
+ for (const p of procList) {
113
+ const { pid, cpuPct, command } = p;
92
114
  const args = command.split(/\s+/);
93
115
  const isRender = /-b|--background|-f\s/.test(command) || /render/i.test(command);
94
- if (!isRender && !command.includes("blender")) continue;
116
+ if (!isRender && !command.toLowerCase().includes("blender")) continue;
95
117
  const blendFile = blendFileFromArgs(args);
96
118
  const engine = engineFromArgs(args);
97
119
  const outputPath = outputPathFromArgs(args);
@@ -0,0 +1,143 @@
1
+ import {
2
+ __require
3
+ } from "./chunk-3RG5ZIWI.js";
4
+
5
+ // src/budgetCmd.ts
6
+ import fs from "fs";
7
+ import { spawn } from "child_process";
8
+ import chalk from "chalk";
9
+ function parseBytesString(s) {
10
+ const trimmed = s.trim().toUpperCase();
11
+ const num = parseFloat(trimmed);
12
+ if (isNaN(num)) return 0;
13
+ if (trimmed.endsWith("G") || trimmed.endsWith("GB")) return num * 1024 * 1024 * 1024;
14
+ if (trimmed.endsWith("M") || trimmed.endsWith("MB")) return num * 1024 * 1024;
15
+ if (trimmed.endsWith("K") || trimmed.endsWith("KB")) return num * 1024;
16
+ return num;
17
+ }
18
+ function formatBytes(b) {
19
+ if (b >= 1024 * 1024 * 1024) return `${(b / (1024 * 1024 * 1024)).toFixed(2)} GB`;
20
+ if (b >= 1024 * 1024) return `${(b / (1024 * 1024)).toFixed(2)} MB`;
21
+ if (b >= 1024) return `${(b / 1024).toFixed(1)} KB`;
22
+ return `${b} B`;
23
+ }
24
+ async function runWithBudget(commandArgs, budgets, opts) {
25
+ if (commandArgs.length === 0) {
26
+ throw new Error("No command specified. Usage: pyre run [budget flags] -- <command>");
27
+ }
28
+ const cmdStr = commandArgs.join(" ");
29
+ const [bin, ...args] = commandArgs;
30
+ const startTime = Date.now();
31
+ const cpuSamples = [];
32
+ let peakMemBytes = 0;
33
+ let peakCpu = 0;
34
+ const child = spawn(bin, args, {
35
+ stdio: "inherit",
36
+ env: process.env
37
+ });
38
+ const pid = child.pid;
39
+ let sampleInterval = null;
40
+ if (pid) {
41
+ sampleInterval = setInterval(() => {
42
+ try {
43
+ const { execSync } = __require("child_process");
44
+ const output = execSync(`ps -o %cpu,rss -p ${pid} 2>/dev/null`, { encoding: "utf-8" });
45
+ const lines = output.trim().split("\n");
46
+ if (lines.length > 1) {
47
+ const parts = lines[1].trim().split(/\s+/);
48
+ const cpu = parseFloat(parts[0]) || 0;
49
+ const rssKb = parseInt(parts[1], 10) || 0;
50
+ const memBytes = rssKb * 1024;
51
+ cpuSamples.push(cpu);
52
+ if (cpu > peakCpu) peakCpu = cpu;
53
+ if (memBytes > peakMemBytes) peakMemBytes = memBytes;
54
+ }
55
+ } catch {
56
+ }
57
+ }, 100);
58
+ }
59
+ const exitCode = await new Promise((resolve) => {
60
+ child.on("close", (code) => {
61
+ if (sampleInterval) clearInterval(sampleInterval);
62
+ resolve(code ?? 0);
63
+ });
64
+ child.on("error", () => {
65
+ if (sampleInterval) clearInterval(sampleInterval);
66
+ resolve(1);
67
+ });
68
+ });
69
+ const durationSec = (Date.now() - startTime) / 1e3;
70
+ const avgCpu = cpuSamples.length > 0 ? cpuSamples.reduce((a, b) => a + b, 0) / cpuSamples.length : peakCpu;
71
+ const violations = [];
72
+ if (budgets.maxTimeSec !== void 0 && durationSec > budgets.maxTimeSec) {
73
+ violations.push(`Runtime exceeded budget: ${durationSec.toFixed(2)}s > ${budgets.maxTimeSec}s limit`);
74
+ }
75
+ if (budgets.maxMemoryBytes !== void 0 && peakMemBytes > budgets.maxMemoryBytes) {
76
+ violations.push(`Memory exceeded budget: ${formatBytes(peakMemBytes)} > ${formatBytes(budgets.maxMemoryBytes)} limit`);
77
+ }
78
+ if (budgets.maxCpuPct !== void 0 && peakCpu > budgets.maxCpuPct) {
79
+ violations.push(`CPU exceeded budget: ${peakCpu.toFixed(1)}% > ${budgets.maxCpuPct}% limit`);
80
+ }
81
+ const result = {
82
+ command: cmdStr,
83
+ exitCode,
84
+ durationSec,
85
+ peakCpuPct: peakCpu,
86
+ avgCpuPct: avgCpu,
87
+ peakMemoryBytes: peakMemBytes,
88
+ violations
89
+ };
90
+ printBudgetReport(result, budgets, opts);
91
+ return result;
92
+ }
93
+ function printBudgetReport(result, budgets, opts) {
94
+ const isViolated = result.violations.length > 0;
95
+ const isFailed = result.exitCode !== 0 || isViolated;
96
+ console.log(chalk.bold("\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
97
+ console.log(chalk.bold(` \u{1F525} pyre run \u2014 Performance Budget Report`));
98
+ console.log(chalk.dim(` Command: ${result.command}`));
99
+ console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
100
+ const statusTime = budgets.maxTimeSec === void 0 ? chalk.dim("unconstrained") : result.durationSec <= budgets.maxTimeSec ? chalk.green(`\u2714 Pass (<= ${budgets.maxTimeSec}s)`) : chalk.red(`\u2716 FAIL (> ${budgets.maxTimeSec}s)`);
101
+ const statusMem = budgets.maxMemoryBytes === void 0 ? chalk.dim("unconstrained") : result.peakMemoryBytes <= budgets.maxMemoryBytes ? chalk.green(`\u2714 Pass (<= ${formatBytes(budgets.maxMemoryBytes)})`) : chalk.red(`\u2716 FAIL (> ${formatBytes(budgets.maxMemoryBytes)})`);
102
+ const statusCpu = budgets.maxCpuPct === void 0 ? chalk.dim("unconstrained") : result.peakCpuPct <= budgets.maxCpuPct ? chalk.green(`\u2714 Pass (<= ${budgets.maxCpuPct}%)`) : chalk.red(`\u2716 FAIL (> ${budgets.maxCpuPct}%)`);
103
+ console.log(` Duration: ${result.durationSec.toFixed(2)}s`.padEnd(28) + statusTime);
104
+ console.log(` Peak Memory: ${formatBytes(result.peakMemoryBytes)}`.padEnd(28) + statusMem);
105
+ console.log(` Peak CPU: ${result.peakCpuPct.toFixed(1)}% (avg ${result.avgCpuPct.toFixed(1)}%)`.padEnd(28) + statusCpu);
106
+ console.log(` Exit Code: ${result.exitCode === 0 ? chalk.green("0") : chalk.red(result.exitCode)}`);
107
+ if (isViolated) {
108
+ console.log(chalk.bold.red("\n \u2716 Performance Budget Violations:"));
109
+ result.violations.forEach((v) => console.log(chalk.red(` \u2022 ${v}`)));
110
+ }
111
+ console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
112
+ if (opts?.outFile || opts?.markdown) {
113
+ const md = generateMarkdownBudgetReport(result, budgets);
114
+ if (opts.outFile) {
115
+ fs.writeFileSync(opts.outFile, md, "utf-8");
116
+ console.log(chalk.dim(` Saved report to ${opts.outFile}
117
+ `));
118
+ }
119
+ }
120
+ }
121
+ function generateMarkdownBudgetReport(result, budgets) {
122
+ return [
123
+ `# Pyre Performance Budget Report`,
124
+ ``,
125
+ `**Command**: \`${result.command}\` `,
126
+ `**Status**: ${result.violations.length === 0 && result.exitCode === 0 ? "\u2705 PASSED" : "\u274C FAILED"} `,
127
+ `**Exit Code**: \`${result.exitCode}\` `,
128
+ ``,
129
+ `| Metric | Measured | Limit | Status |`,
130
+ `| :--- | :--- | :--- | :--- |`,
131
+ `| Duration | ${result.durationSec.toFixed(2)}s | ${budgets.maxTimeSec ? budgets.maxTimeSec + "s" : "N/A"} | ${budgets.maxTimeSec && result.durationSec > budgets.maxTimeSec ? "\u274C FAIL" : "\u2705 PASS"} |`,
132
+ `| Peak Memory | ${formatBytes(result.peakMemoryBytes)} | ${budgets.maxMemoryBytes ? formatBytes(budgets.maxMemoryBytes) : "N/A"} | ${budgets.maxMemoryBytes && result.peakMemoryBytes > budgets.maxMemoryBytes ? "\u274C FAIL" : "\u2705 PASS"} |`,
133
+ `| Peak CPU | ${result.peakCpuPct.toFixed(1)}% | ${budgets.maxCpuPct ? budgets.maxCpuPct + "%" : "N/A"} | ${budgets.maxCpuPct && result.peakCpuPct > budgets.maxCpuPct ? "\u274C FAIL" : "\u2705 PASS"} |`,
134
+ ``,
135
+ ...result.violations.length > 0 ? [`### Violations`, ...result.violations.map((v) => `- ${v}`)] : []
136
+ ].join("\n");
137
+ }
138
+
139
+ export {
140
+ parseBytesString,
141
+ formatBytes,
142
+ runWithBudget
143
+ };
@@ -64,7 +64,11 @@ var DEFAULT_CONFIG = {
64
64
  historyDuration: 60,
65
65
  tableDensity: "standard",
66
66
  defaultFilter: "all",
67
- defaultSort: "cpu"
67
+ defaultSort: "cpu",
68
+ optimizeProtectedApps: [],
69
+ optimizeIdleThreshold: "2h",
70
+ optimizeKeepRecent: 5,
71
+ defaultAction: "live"
68
72
  };
69
73
  function getConfigDir() {
70
74
  return process.env.PYRE_CONFIG_DIR || path.join(os.homedir(), ".config", "pyre");
@@ -0,0 +1,111 @@
1
+ import {
2
+ collectWindowsProcesses
3
+ } from "./chunk-OCL7P7N7.js";
4
+ import {
5
+ run
6
+ } from "./chunk-MZO5QKUR.js";
7
+
8
+ // src/monitors/buildTracker.ts
9
+ async function collectBuilds() {
10
+ try {
11
+ const builds = /* @__PURE__ */ new Map();
12
+ const buildToolPatterns = [
13
+ { name: "msbuild", match: /\b(msbuild|devenv)\b/i },
14
+ { name: "xcodebuild", match: /xcodebuild/i },
15
+ { name: "cargo", match: /\bcargo\s+(build|check|test|run)?\b/i },
16
+ { name: "swift", match: /\bswift\s+(build|test)\b/i },
17
+ { name: "make", match: /\b(make|nmake)\b/i },
18
+ { name: "ninja", match: /\bninja\b/i },
19
+ { name: "cmake", match: /\bcmake\b/i },
20
+ { name: "bazel", match: /\bbazel\b/i },
21
+ { name: "go", match: /\bgo\s+(build|test|run)\b/i }
22
+ ];
23
+ const compilerMatch = /(clang|swift-frontend|rustc|go tool compile|gcc|g\+\+|ghc|cl\.exe|csc\.exe|link\.exe)/i;
24
+ const procList = [];
25
+ if (process.platform === "win32") {
26
+ const winProcs = await collectWindowsProcesses();
27
+ for (const p of winProcs) {
28
+ procList.push({
29
+ pid: p.pid,
30
+ cpu: p.cpu,
31
+ mem: p.mem,
32
+ elapsedSec: p.runtime || 0,
33
+ cmd: p.command
34
+ });
35
+ }
36
+ } else {
37
+ const raw = await run("ps -eo pid,ppid,pcpu,pmem,etime,command -r 2>/dev/null", "");
38
+ if (!raw) return [];
39
+ const lines = raw.split("\n").slice(1);
40
+ for (const line of lines) {
41
+ if (!line.trim()) continue;
42
+ const parts = line.trim().split(/\s+/);
43
+ if (parts.length < 6) continue;
44
+ const pid = parseInt(parts[0], 10);
45
+ const cpu = parseFloat(parts[2]) || 0;
46
+ const mem = parseFloat(parts[3]) || 0;
47
+ const etimeStr = parts[4];
48
+ const cmd = parts.slice(5).join(" ");
49
+ let elapsedSec = 0;
50
+ const etimeParts = etimeStr.split(":").map(Number);
51
+ if (etimeParts.length === 2) elapsedSec = etimeParts[0] * 60 + etimeParts[1];
52
+ else if (etimeParts.length === 3) elapsedSec = etimeParts[0] * 3600 + etimeParts[1] * 60 + etimeParts[2];
53
+ procList.push({ pid, cpu, mem, elapsedSec, cmd });
54
+ }
55
+ }
56
+ for (const p of procList) {
57
+ for (const pattern of buildToolPatterns) {
58
+ if (pattern.match.test(p.cmd)) {
59
+ builds.set(pattern.name, {
60
+ tool: pattern.name,
61
+ rootPid: p.pid,
62
+ command: p.cmd.slice(0, 80),
63
+ compilerProcesses: 1,
64
+ totalCpu: p.cpu,
65
+ totalMem: p.mem,
66
+ elapsedSec: p.elapsedSec,
67
+ target: p.cmd.split(" ").slice(1, 4).join(" ")
68
+ });
69
+ break;
70
+ }
71
+ }
72
+ }
73
+ for (const p of procList) {
74
+ if (compilerMatch.test(p.cmd)) {
75
+ if (builds.size > 0) {
76
+ const firstBuild = builds.values().next().value;
77
+ if (firstBuild) {
78
+ firstBuild.compilerProcesses += 1;
79
+ firstBuild.totalCpu += p.cpu;
80
+ firstBuild.totalMem += p.mem;
81
+ }
82
+ } else {
83
+ const key = "compiler";
84
+ const existing = builds.get(key);
85
+ if (existing) {
86
+ existing.compilerProcesses += 1;
87
+ existing.totalCpu += p.cpu;
88
+ existing.totalMem += p.mem;
89
+ } else {
90
+ builds.set(key, {
91
+ tool: "compiler",
92
+ rootPid: p.pid,
93
+ command: p.cmd.slice(0, 80),
94
+ compilerProcesses: 1,
95
+ totalCpu: p.cpu,
96
+ totalMem: p.mem,
97
+ elapsedSec: p.elapsedSec
98
+ });
99
+ }
100
+ }
101
+ }
102
+ }
103
+ return Array.from(builds.values());
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ export {
110
+ collectBuilds
111
+ };