pyre-cli 10.3.0 → 10.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/{alertChannels-TPSQIJ7S.js → alertChannels-MNX4XH6J.js} +1 -0
- package/dist/{anomaliesCmd-25OFKYPK.js → anomaliesCmd-HHTY5LYL.js} +2 -0
- package/dist/benchGitCmd-HIXUC5EK.js +87 -0
- package/dist/{benchmarkCmd-A3ETK6VY.js → benchmarkCmd-B7DLP2VC.js} +2 -0
- package/dist/{blender-FGH3ASEQ.js → blender-6ZX5SPYC.js} +3 -1
- package/dist/{brewHealth-P2GW5Y5C.js → brewHealth-VFW33QB3.js} +1 -0
- package/dist/budgetCmd-TGO3RYLN.js +11 -0
- package/dist/{buildCmd-CDPZ2ZFA.js → buildCmd-E7KUCFYG.js} +3 -1
- package/dist/{checkCmd-WQAHAUWO.js → checkCmd-QLYTP77Y.js} +5 -3
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/{chunk-VIACN2RU.js → chunk-FYCBGUTW.js} +41 -19
- package/dist/chunk-JH7P6KLN.js +143 -0
- package/dist/{chunk-QM5L43EK.js → chunk-LDJB5V4N.js} +5 -1
- package/dist/chunk-NMGQVRCA.js +111 -0
- package/dist/chunk-OCL7P7N7.js +936 -0
- package/dist/{chunk-23YWCI3O.js → chunk-QEH4LR6P.js} +28 -686
- package/dist/{chunk-KVUMOA6C.js → chunk-RKPGPVTO.js} +77 -4
- package/dist/chunk-UF3XQ3R3.js +3800 -0
- package/dist/{collectors-JDN5Y7EV.js → collectors-V7SCIIDH.js} +5 -3
- package/dist/{completions-ZVTNE52B.js → completions-FTPXDSR2.js} +44 -3
- package/dist/{config-J4YMADEF.js → config-5WW5M63G.js} +2 -1
- package/dist/containersCmd-FSMYOBG2.js +84 -0
- package/dist/{doctor-LHFW4UJP.js → doctor-O4DIAMHK.js} +3 -2
- package/dist/{explainCmd-MF6PIMLW.js → explainCmd-7PBPA7O5.js} +1 -0
- package/dist/{extensions-XJGHG7X3.js → extensions-QQEV7U3D.js} +1 -0
- package/dist/{firebaseSync-ON3RZ6KX.js → firebaseSync-LRN67UTV.js} +2 -0
- package/dist/flightRecorder-GJIAQOXC.js +162 -0
- package/dist/index.js +736 -4125
- package/dist/inspectCmd-5FXJBJTK.js +137 -0
- package/dist/menu-AJFGDLBP.js +1233 -0
- package/dist/monitors/platform/windows-ui.ps1 +31 -29
- package/dist/{netUsageCmd-67IBZZYN.js → netUsageCmd-JP6YKBPF.js} +7 -5
- package/dist/optimizeCmd-WZKLFYZU.js +1110 -0
- package/dist/{pipeCmd-2N4JPKYE.js → pipeCmd-CPK6FVKB.js} +5 -3
- package/dist/playbookCmd-FRROIEXR.js +145 -0
- package/dist/{profileProcCmd-N4H6MCBT.js → profileProcCmd-5Y3733E7.js} +64 -13
- package/dist/{prometheus-5BL5PVLC.js → prometheus-CYNUR3QT.js} +1 -0
- package/dist/{prometheusCmd-LEUM5WCZ.js → prometheusCmd-G2CSEZRQ.js} +5 -3
- package/dist/{replayCmd-Z3PYXY7V.js → replayCmd-ODB5SPOQ.js} +25 -6
- package/dist/{run-HOGQDDTA.js → run-UJQYT2TO.js} +1 -0
- package/dist/{serveCmd-L7LVJOZN.js → serveCmd-KDPWLVQI.js} +5 -3
- package/dist/{service-7GIX3YNC.js → service-LIYYR3UI.js} +1 -0
- package/dist/{smartCmd-ODWKSFNG.js → smartCmd-2BJJ37GV.js} +130 -5
- package/dist/statuslineCmd-EUKYRR2G.js +75 -0
- package/dist/{stressCmd-MFXMFXYX.js → stressCmd-GKHGY26X.js} +2 -0
- package/dist/{syncCmd-42IRF633.js → syncCmd-MIBFKX24.js} +16 -5
- package/dist/topoCmd-RTXQJ6XA.js +189 -0
- package/dist/{updateCheck-52X6LJG5.js → updateCheck-LJHEMG3G.js} +2 -0
- package/dist/{watchdogCmd-6GR4UN6B.js → watchdogCmd-XM6OPTKM.js} +41 -6
- package/dist/{wizard-JQMQWFAW.js → wizard-PRDOJROQ.js} +2 -1
- package/package.json +2 -2
- package/src/benchGitCmd.ts +110 -0
- package/src/budgetCmd.ts +199 -0
- package/src/completions.ts +42 -3
- package/src/containersCmd.ts +106 -0
- package/src/doctor.ts +86 -6
- package/src/easy_features.test.ts +83 -0
- package/src/flightRecorder.ts +214 -0
- package/src/index.ts +181 -8
- package/src/inspectCmd.ts +165 -0
- package/src/live/input.ts +70 -3
- package/src/live/render.ts +1 -1
- package/src/live/state.ts +2 -1
- package/src/menu/commands.ts +754 -0
- package/src/menu/index.ts +358 -0
- package/src/menu/render.ts +272 -0
- package/src/menu/types.ts +63 -0
- package/src/menu.test.ts +110 -0
- package/src/monitors/blender.ts +49 -19
- package/src/monitors/buildTracker.ts +72 -54
- package/src/monitors/platform/windows-ui.ps1 +31 -29
- package/src/monitors/platform/windows.ts +636 -280
- package/src/optimizeCmd.ts +1248 -0
- package/src/platform.test.ts +146 -0
- package/src/playbookCmd.ts +156 -0
- package/src/power_features.test.ts +251 -0
- package/src/profileProcCmd.ts +68 -21
- package/src/replayCmd.ts +15 -6
- package/src/smartCmd.ts +149 -10
- package/src/state/config.ts +13 -0
- package/src/statuslineCmd.ts +87 -0
- package/src/syncCmd.ts +12 -0
- package/src/topoCmd.ts +83 -50
- package/src/watchdogCmd.ts +40 -3
- package/src/xbar.ts +195 -17
- package/dist/chunk-M73HCQD2.js +0 -95
- package/dist/topoCmd-DZ3YS5IL.js +0 -148
package/src/menu.test.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { ALL_COMMANDS, CATEGORIES, filterCommands } from './menu/commands.js';
|
|
4
|
+
import { renderMenuScreen } from './menu/render.js';
|
|
5
|
+
import type { MenuState } from './menu/types.js';
|
|
6
|
+
|
|
7
|
+
describe('TUI Command Center & Tool Navigator', () => {
|
|
8
|
+
it('should define a comprehensive catalog of Pyre commands', () => {
|
|
9
|
+
assert.ok(ALL_COMMANDS.length >= 30, `Expected at least 30 commands, got ${ALL_COMMANDS.length}`);
|
|
10
|
+
const ids = ALL_COMMANDS.map((c) => c.id);
|
|
11
|
+
assert.ok(ids.includes('live'));
|
|
12
|
+
assert.ok(ids.includes('check'));
|
|
13
|
+
assert.ok(ids.includes('doctor'));
|
|
14
|
+
assert.ok(ids.includes('smart'));
|
|
15
|
+
assert.ok(ids.includes('anomalies'));
|
|
16
|
+
assert.ok(ids.includes('benchmark'));
|
|
17
|
+
assert.ok(ids.includes('optimize'));
|
|
18
|
+
assert.ok(ids.includes('brew'));
|
|
19
|
+
assert.ok(ids.includes('netusage'));
|
|
20
|
+
assert.ok(ids.includes('ui'));
|
|
21
|
+
assert.ok(ids.includes('web'));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should categorize every command into a valid category', () => {
|
|
25
|
+
const validCatIds = CATEGORIES.map((c) => c.id);
|
|
26
|
+
for (const cmd of ALL_COMMANDS) {
|
|
27
|
+
assert.ok(
|
|
28
|
+
validCatIds.includes(cmd.category),
|
|
29
|
+
`Command ${cmd.id} has invalid category: ${cmd.category}`
|
|
30
|
+
);
|
|
31
|
+
assert.ok(cmd.name.length > 0);
|
|
32
|
+
assert.ok(cmd.cli.startsWith('pyre '));
|
|
33
|
+
assert.ok(cmd.description.length > 0);
|
|
34
|
+
assert.ok(Array.isArray(cmd.tags));
|
|
35
|
+
assert.ok(cmd.tags.length > 0);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should filter commands by category', () => {
|
|
40
|
+
const healthCmds = filterCommands(ALL_COMMANDS, 'health', '');
|
|
41
|
+
assert.ok(healthCmds.length >= 4);
|
|
42
|
+
assert.ok(healthCmds.some((c) => c.id === 'doctor'));
|
|
43
|
+
assert.ok(healthCmds.some((c) => c.id === 'check'));
|
|
44
|
+
assert.ok(healthCmds.some((c) => c.id === 'smart'));
|
|
45
|
+
assert.ok(healthCmds.every((c) => c.category === 'health'));
|
|
46
|
+
|
|
47
|
+
const perfCmds = filterCommands(ALL_COMMANDS, 'performance', '');
|
|
48
|
+
assert.ok(perfCmds.some((c) => c.id === 'benchmark'));
|
|
49
|
+
assert.ok(perfCmds.some((c) => c.id === 'stress'));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should fuzzy search commands by name, tag, or description', () => {
|
|
53
|
+
// Search by exact name
|
|
54
|
+
const docResults = filterCommands(ALL_COMMANDS, 'all', 'doctor');
|
|
55
|
+
assert.strictEqual(docResults[0].id, 'doctor');
|
|
56
|
+
|
|
57
|
+
// Search by tag
|
|
58
|
+
const nvmeResults = filterCommands(ALL_COMMANDS, 'all', 'nvme');
|
|
59
|
+
assert.ok(nvmeResults.some((c) => c.id === 'smart'));
|
|
60
|
+
|
|
61
|
+
// Search by keyword in description
|
|
62
|
+
const idleResults = filterCommands(ALL_COMMANDS, 'all', 'idle');
|
|
63
|
+
assert.ok(idleResults.some((c) => c.id === 'optimize'));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should render menu screen without crashing', () => {
|
|
67
|
+
const state: MenuState = {
|
|
68
|
+
category: 'all',
|
|
69
|
+
categoryIndex: 0,
|
|
70
|
+
searchQuery: '',
|
|
71
|
+
selectedIndex: 0,
|
|
72
|
+
scrollOffset: 0,
|
|
73
|
+
inputMode: 'search',
|
|
74
|
+
argPromptValue: '',
|
|
75
|
+
activeCommand: null,
|
|
76
|
+
statusMessage: '',
|
|
77
|
+
termWidth: 100,
|
|
78
|
+
termHeight: 30,
|
|
79
|
+
theme: 'default',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const rendered = renderMenuScreen(state, ALL_COMMANDS);
|
|
83
|
+
assert.ok(typeof rendered === 'string');
|
|
84
|
+
assert.ok(rendered.includes('Command Center'));
|
|
85
|
+
assert.ok(rendered.includes('Search:'));
|
|
86
|
+
assert.ok(rendered.includes('pyre live'));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should render argument prompt modal when inputMode is prompt-args', () => {
|
|
90
|
+
const benchCmd = ALL_COMMANDS.find((c) => c.id === 'bench')!;
|
|
91
|
+
const state: MenuState = {
|
|
92
|
+
category: 'performance',
|
|
93
|
+
categoryIndex: 3,
|
|
94
|
+
searchQuery: '',
|
|
95
|
+
selectedIndex: 0,
|
|
96
|
+
scrollOffset: 0,
|
|
97
|
+
inputMode: 'prompt-args',
|
|
98
|
+
argPromptValue: 'npm test',
|
|
99
|
+
activeCommand: benchCmd,
|
|
100
|
+
statusMessage: '',
|
|
101
|
+
termWidth: 100,
|
|
102
|
+
termHeight: 30,
|
|
103
|
+
theme: 'default',
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const rendered = renderMenuScreen(state, [benchCmd]);
|
|
107
|
+
assert.ok(rendered.includes('Execute:'));
|
|
108
|
+
assert.ok(rendered.includes('npm test'));
|
|
109
|
+
});
|
|
110
|
+
});
|
package/src/monitors/blender.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
1
4
|
import { run } from './run.js';
|
|
2
5
|
import type { BlenderRenderData } from './types.js';
|
|
6
|
+
import { collectWindowsProcesses } from './platform/windows.js';
|
|
3
7
|
|
|
4
|
-
const RENDER_LOG_DIR = '
|
|
8
|
+
const RENDER_LOG_DIR = path.join(os.tmpdir(), 'blender_render_logs');
|
|
5
9
|
|
|
6
10
|
function blendFileFromArgs(args: string[]): string {
|
|
7
11
|
const nonFlag = args.filter(a => !a.startsWith('-'));
|
|
8
12
|
for (const a of nonFlag) {
|
|
9
13
|
if (a.endsWith('.blend') || a.includes('.blend')) {
|
|
10
|
-
return a.split(
|
|
14
|
+
return a.split(/[/\\]/).pop() || a;
|
|
11
15
|
}
|
|
12
16
|
}
|
|
13
17
|
return 'unknown.blend';
|
|
@@ -25,15 +29,17 @@ function outputPathFromArgs(args: string[]): string {
|
|
|
25
29
|
const oIdx = args.indexOf('-o');
|
|
26
30
|
if (oIdx !== -1 && args[oIdx + 1]) {
|
|
27
31
|
let p = args[oIdx + 1];
|
|
28
|
-
if (p.endsWith('/')) p = p.slice(0, -1);
|
|
29
|
-
|
|
32
|
+
if (p.endsWith('/') || p.endsWith('\\')) p = p.slice(0, -1);
|
|
33
|
+
const parts = p.split(/[/\\]/);
|
|
34
|
+
return parts.slice(0, -1).join('/') || os.tmpdir();
|
|
30
35
|
}
|
|
31
36
|
const fFlag = args.find(a => a.startsWith('-o='));
|
|
32
37
|
if (fFlag) {
|
|
33
38
|
const p = fFlag.slice(3);
|
|
34
|
-
|
|
39
|
+
const parts = p.split(/[/\\]/);
|
|
40
|
+
return parts.slice(0, -1).join('/') || os.tmpdir();
|
|
35
41
|
}
|
|
36
|
-
return
|
|
42
|
+
return os.tmpdir();
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
function frameRangeFromArgs(args: string[]): { start: number; end: number } {
|
|
@@ -57,11 +63,17 @@ function frameRangeFromArgs(args: string[]): { start: number; end: number } {
|
|
|
57
63
|
async function parseRenderLog(blendName: string): Promise<{ framesRendered: number; totalFrames: number; startTime: number; samples?: number } | null> {
|
|
58
64
|
const baseName = blendName.replace(/\.blend$/i, '');
|
|
59
65
|
try {
|
|
60
|
-
|
|
66
|
+
if (!fs.existsSync(RENDER_LOG_DIR)) return null;
|
|
67
|
+
const entries = fs.readdirSync(RENDER_LOG_DIR)
|
|
68
|
+
.filter(f => f.startsWith(`${baseName}_`))
|
|
69
|
+
.map(f => ({ name: f, time: fs.statSync(path.join(RENDER_LOG_DIR, f)).mtimeMs }))
|
|
70
|
+
.sort((a, b) => b.time - a.time)
|
|
71
|
+
.slice(0, 5);
|
|
72
|
+
|
|
61
73
|
for (const entry of entries) {
|
|
62
|
-
const logPath =
|
|
74
|
+
const logPath = path.join(RENDER_LOG_DIR, entry.name);
|
|
63
75
|
try {
|
|
64
|
-
const content =
|
|
76
|
+
const content = fs.readFileSync(logPath, 'utf-8');
|
|
65
77
|
if (!content) continue;
|
|
66
78
|
const lines = content.split('\n');
|
|
67
79
|
const rendered = lines.filter(l => /Fra:(\d+)/i.test(l)).length;
|
|
@@ -86,21 +98,39 @@ async function parseRenderLog(blendName: string): Promise<{ framesRendered: numb
|
|
|
86
98
|
export async function collectBlenderRenders(): Promise<BlenderRenderData[]> {
|
|
87
99
|
const renders: BlenderRenderData[] = [];
|
|
88
100
|
try {
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
interface BlenderProc {
|
|
102
|
+
pid: number;
|
|
103
|
+
cpuPct: number;
|
|
104
|
+
command: string;
|
|
105
|
+
}
|
|
106
|
+
const procList: BlenderProc[] = [];
|
|
91
107
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
108
|
+
if (process.platform === 'win32') {
|
|
109
|
+
const procs = await collectWindowsProcesses();
|
|
110
|
+
for (const p of procs) {
|
|
111
|
+
if (/blender/i.test(p.command)) {
|
|
112
|
+
procList.push({ pid: p.pid, cpuPct: p.cpu, command: p.command });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
const psRaw = await run("ps -eo pid,pcpu,pmem,state,command 2>/dev/null | grep -iE '[b]lender'", '', 2000);
|
|
117
|
+
if (psRaw) {
|
|
118
|
+
const lines = psRaw.split('\n');
|
|
119
|
+
for (const line of lines) {
|
|
120
|
+
const parts = line.trim().match(/\s*(\d+)\s+([\d.]+)\s+([\d.]+)\s+(\S+)\s+(.+)/);
|
|
121
|
+
if (parts) {
|
|
122
|
+
procList.push({ pid: parseInt(parts[1], 10), cpuPct: parseFloat(parts[2]), command: parts[5] });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
96
127
|
|
|
97
|
-
|
|
98
|
-
const cpuPct =
|
|
99
|
-
const command = parts[5];
|
|
128
|
+
for (const p of procList) {
|
|
129
|
+
const { pid, cpuPct, command } = p;
|
|
100
130
|
const args = command.split(/\s+/);
|
|
101
131
|
|
|
102
132
|
const isRender = /-b|--background|-f\s/.test(command) || /render/i.test(command);
|
|
103
|
-
if (!isRender && !command.includes('blender')) continue;
|
|
133
|
+
if (!isRender && !command.toLowerCase().includes('blender')) continue;
|
|
104
134
|
|
|
105
135
|
const blendFile = blendFileFromArgs(args);
|
|
106
136
|
const engine = engineFromArgs(args);
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build System Tracker.
|
|
3
3
|
*
|
|
4
|
-
* Scans active processes for compiler invocations (xcodebuild, cargo, rustc,
|
|
5
|
-
* clang,
|
|
6
|
-
* parallelism, CPU/RAM usage, and estimated elapsed time.
|
|
4
|
+
* Scans active processes for compiler invocations (xcodebuild, msbuild, cargo, rustc,
|
|
5
|
+
* clang, cl.exe, make, ninja, bazel, cmake, go build), tracking compiler
|
|
6
|
+
* parallelism, CPU/RAM usage, and estimated elapsed time across macOS, Linux, and Windows.
|
|
7
7
|
*/
|
|
8
8
|
import { run } from './run.js';
|
|
9
|
+
import { collectWindowsProcesses } from './platform/windows.js';
|
|
9
10
|
|
|
10
11
|
export interface ActiveBuild {
|
|
11
|
-
tool: 'xcodebuild' | 'cargo' | 'swift' | 'make' | 'ninja' | 'bazel' | 'go' | 'clang' | 'compiler';
|
|
12
|
+
tool: 'xcodebuild' | 'cargo' | 'swift' | 'make' | 'ninja' | 'bazel' | 'go' | 'clang' | 'compiler' | 'msbuild' | 'cmake';
|
|
12
13
|
rootPid: number;
|
|
13
14
|
command: string;
|
|
14
15
|
compilerProcesses: number;
|
|
@@ -20,52 +21,79 @@ export interface ActiveBuild {
|
|
|
20
21
|
|
|
21
22
|
export async function collectBuilds(): Promise<ActiveBuild[]> {
|
|
22
23
|
try {
|
|
23
|
-
const raw = await run('ps -eo pid,ppid,pcpu,pmem,etime,command -r', '');
|
|
24
|
-
if (!raw) return [];
|
|
25
|
-
|
|
26
|
-
const lines = raw.split('\n').slice(1);
|
|
27
24
|
const builds: Map<string, ActiveBuild> = new Map();
|
|
28
25
|
|
|
29
26
|
const buildToolPatterns: { name: ActiveBuild['tool']; match: RegExp }[] = [
|
|
27
|
+
{ name: 'msbuild', match: /\b(msbuild|devenv)\b/i },
|
|
30
28
|
{ name: 'xcodebuild', match: /xcodebuild/i },
|
|
31
|
-
{ name: 'cargo', match: /\bcargo\s+(build|check|test|run)
|
|
29
|
+
{ name: 'cargo', match: /\bcargo\s+(build|check|test|run)?\b/i },
|
|
32
30
|
{ name: 'swift', match: /\bswift\s+(build|test)\b/i },
|
|
33
|
-
{ name: 'make', match: /\
|
|
31
|
+
{ name: 'make', match: /\b(make|nmake)\b/i },
|
|
34
32
|
{ name: 'ninja', match: /\bninja\b/i },
|
|
33
|
+
{ name: 'cmake', match: /\bcmake\b/i },
|
|
35
34
|
{ name: 'bazel', match: /\bbazel\b/i },
|
|
36
35
|
{ name: 'go', match: /\bgo\s+(build|test|run)\b/i },
|
|
37
36
|
];
|
|
38
37
|
|
|
39
|
-
const compilerMatch = /(clang|swift-frontend|rustc|go tool compile|gcc|g\+\+|ghc)/i;
|
|
38
|
+
const compilerMatch = /(clang|swift-frontend|rustc|go tool compile|gcc|g\+\+|ghc|cl\.exe|csc\.exe|link\.exe)/i;
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
interface ProcEntry {
|
|
41
|
+
pid: number;
|
|
42
|
+
cpu: number;
|
|
43
|
+
mem: number;
|
|
44
|
+
elapsedSec: number;
|
|
45
|
+
cmd: string;
|
|
46
|
+
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
const cpu = parseFloat(parts[2]) || 0;
|
|
49
|
-
const mem = parseFloat(parts[3]) || 0;
|
|
50
|
-
const etimeStr = parts[4];
|
|
51
|
-
const cmd = parts.slice(5).join(' ');
|
|
48
|
+
const procList: ProcEntry[] = [];
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
if (process.platform === 'win32') {
|
|
51
|
+
const winProcs = await collectWindowsProcesses();
|
|
52
|
+
for (const p of winProcs) {
|
|
53
|
+
procList.push({
|
|
54
|
+
pid: p.pid,
|
|
55
|
+
cpu: p.cpu,
|
|
56
|
+
mem: p.mem,
|
|
57
|
+
elapsedSec: p.runtime || 0,
|
|
58
|
+
cmd: p.command,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
const raw = await run('ps -eo pid,ppid,pcpu,pmem,etime,command -r 2>/dev/null', '');
|
|
63
|
+
if (!raw) return [];
|
|
64
|
+
const lines = raw.split('\n').slice(1);
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
if (!line.trim()) continue;
|
|
67
|
+
const parts = line.trim().split(/\s+/);
|
|
68
|
+
if (parts.length < 6) continue;
|
|
69
|
+
const pid = parseInt(parts[0], 10);
|
|
70
|
+
const cpu = parseFloat(parts[2]) || 0;
|
|
71
|
+
const mem = parseFloat(parts[3]) || 0;
|
|
72
|
+
const etimeStr = parts[4];
|
|
73
|
+
const cmd = parts.slice(5).join(' ');
|
|
74
|
+
|
|
75
|
+
let elapsedSec = 0;
|
|
76
|
+
const etimeParts = etimeStr.split(':').map(Number);
|
|
77
|
+
if (etimeParts.length === 2) elapsedSec = etimeParts[0] * 60 + etimeParts[1];
|
|
78
|
+
else if (etimeParts.length === 3) elapsedSec = etimeParts[0] * 3600 + etimeParts[1] * 60 + etimeParts[2];
|
|
59
79
|
|
|
80
|
+
procList.push({ pid, cpu, mem, elapsedSec, cmd });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// First pass: identify build roots
|
|
85
|
+
for (const p of procList) {
|
|
86
|
+
for (const pattern of buildToolPatterns) {
|
|
87
|
+
if (pattern.match.test(p.cmd)) {
|
|
60
88
|
builds.set(pattern.name, {
|
|
61
89
|
tool: pattern.name,
|
|
62
|
-
rootPid: pid,
|
|
63
|
-
command: cmd.slice(0, 80),
|
|
90
|
+
rootPid: p.pid,
|
|
91
|
+
command: p.cmd.slice(0, 80),
|
|
64
92
|
compilerProcesses: 1,
|
|
65
|
-
totalCpu: cpu,
|
|
66
|
-
totalMem: mem,
|
|
67
|
-
elapsedSec,
|
|
68
|
-
target: cmd.split(' ').slice(1, 4).join(' '),
|
|
93
|
+
totalCpu: p.cpu,
|
|
94
|
+
totalMem: p.mem,
|
|
95
|
+
elapsedSec: p.elapsedSec,
|
|
96
|
+
target: p.cmd.split(' ').slice(1, 4).join(' '),
|
|
69
97
|
});
|
|
70
98
|
break;
|
|
71
99
|
}
|
|
@@ -73,41 +101,31 @@ export async function collectBuilds(): Promise<ActiveBuild[]> {
|
|
|
73
101
|
}
|
|
74
102
|
|
|
75
103
|
// Second pass: associate child compiler processes
|
|
76
|
-
for (const
|
|
77
|
-
if (
|
|
78
|
-
const parts = line.trim().split(/\s+/);
|
|
79
|
-
if (parts.length < 6) continue;
|
|
80
|
-
|
|
81
|
-
const cpu = parseFloat(parts[2]) || 0;
|
|
82
|
-
const mem = parseFloat(parts[3]) || 0;
|
|
83
|
-
const cmd = parts.slice(5).join(' ');
|
|
84
|
-
|
|
85
|
-
if (compilerMatch.test(cmd)) {
|
|
86
|
-
// If an active build was found, associate this compiler worker
|
|
104
|
+
for (const p of procList) {
|
|
105
|
+
if (compilerMatch.test(p.cmd)) {
|
|
87
106
|
if (builds.size > 0) {
|
|
88
107
|
const firstBuild = builds.values().next().value;
|
|
89
108
|
if (firstBuild) {
|
|
90
109
|
firstBuild.compilerProcesses += 1;
|
|
91
|
-
firstBuild.totalCpu += cpu;
|
|
92
|
-
firstBuild.totalMem += mem;
|
|
110
|
+
firstBuild.totalCpu += p.cpu;
|
|
111
|
+
firstBuild.totalMem += p.mem;
|
|
93
112
|
}
|
|
94
113
|
} else {
|
|
95
|
-
// Standalone compiler invocation
|
|
96
114
|
const key = 'compiler';
|
|
97
115
|
const existing = builds.get(key);
|
|
98
116
|
if (existing) {
|
|
99
117
|
existing.compilerProcesses += 1;
|
|
100
|
-
existing.totalCpu += cpu;
|
|
101
|
-
existing.totalMem += mem;
|
|
118
|
+
existing.totalCpu += p.cpu;
|
|
119
|
+
existing.totalMem += p.mem;
|
|
102
120
|
} else {
|
|
103
121
|
builds.set(key, {
|
|
104
122
|
tool: 'compiler',
|
|
105
|
-
rootPid:
|
|
106
|
-
command: cmd.slice(0, 80),
|
|
123
|
+
rootPid: p.pid,
|
|
124
|
+
command: p.cmd.slice(0, 80),
|
|
107
125
|
compilerProcesses: 1,
|
|
108
|
-
totalCpu: cpu,
|
|
109
|
-
totalMem: mem,
|
|
110
|
-
elapsedSec:
|
|
126
|
+
totalCpu: p.cpu,
|
|
127
|
+
totalMem: p.mem,
|
|
128
|
+
elapsedSec: p.elapsedSec,
|
|
111
129
|
});
|
|
112
130
|
}
|
|
113
131
|
}
|
|
@@ -1,42 +1,44 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Launches a native Pyre UI window on Windows
|
|
3
|
+
Launches a native standalone Pyre UI window on Windows.
|
|
4
|
+
Prefers Microsoft Edge or Chromium in standalone desktop application mode (--app)
|
|
5
|
+
for modern HTML5/ES6/Chart.js rendering without browser tabs or address bars.
|
|
4
6
|
#>
|
|
5
7
|
param(
|
|
6
8
|
[Parameter(Mandatory=$true)]
|
|
7
9
|
[int]$Port
|
|
8
10
|
)
|
|
9
11
|
|
|
10
|
-
$ErrorActionPreference = '
|
|
12
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
Add-Type -AssemblyName System.Drawing
|
|
14
|
-
Add-Type -AssemblyName PresentationFramework
|
|
14
|
+
$url = "http://localhost:$Port/"
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
$
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
$
|
|
24
|
-
$
|
|
25
|
-
|
|
26
|
-
$form.Height = 800
|
|
27
|
-
$form.StartPosition = "CenterScreen"
|
|
28
|
-
$form.MinimumSize = New-Object System.Drawing.Size(860, 600)
|
|
29
|
-
$form.BackColor = [System.Drawing.Color]::FromArgb(10, 10, 15)
|
|
30
|
-
$form.ForeColor = [System.Drawing.Color]::FromArgb(245, 245, 247)
|
|
16
|
+
# 1. Search for Edge or Chrome executable paths
|
|
17
|
+
$browsers = @(
|
|
18
|
+
"$env:ProgramFiles (x86)\Microsoft\Edge\Application\msedge.exe",
|
|
19
|
+
"$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe",
|
|
20
|
+
"$env:LocalAppData\Microsoft\Edge\Application\msedge.exe",
|
|
21
|
+
"$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
|
|
22
|
+
"$env:ProgramFiles (x86)\Google\Chrome\Application\chrome.exe",
|
|
23
|
+
"$env:LocalAppData\Google\Chrome\Application\chrome.exe",
|
|
24
|
+
"$env:ProgramFiles\BraveSoftware\Brave-Browser\Application\brave.exe"
|
|
25
|
+
)
|
|
31
26
|
|
|
32
|
-
$
|
|
33
|
-
$
|
|
34
|
-
|
|
35
|
-
$
|
|
36
|
-
|
|
27
|
+
$targetBrowser = $null
|
|
28
|
+
foreach ($b in $browsers) {
|
|
29
|
+
if (Test-Path $b) {
|
|
30
|
+
$targetBrowser = $b
|
|
31
|
+
break
|
|
32
|
+
}
|
|
33
|
+
}
|
|
37
34
|
|
|
38
|
-
$
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
if ($targetBrowser) {
|
|
36
|
+
# Launch in standalone application window mode (frameless app window)
|
|
37
|
+
$args = "--app=$url --window-size=1200,800"
|
|
38
|
+
$proc = Start-Process -FilePath $targetBrowser -ArgumentList $args -PassThru -Wait
|
|
39
|
+
exit 0
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
# 2. Fallback: Launch default system browser
|
|
43
|
+
Start-Process $url
|
|
44
|
+
exit 0
|