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.
- 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 +613 -280
- package/src/optimizeCmd.ts +1248 -0
- package/src/platform.test.ts +135 -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/index.ts
CHANGED
|
@@ -102,10 +102,34 @@ program
|
|
|
102
102
|
.option('--top <n>', 'Number of items to display in netusage command (default: 15)')
|
|
103
103
|
.option('--raw', 'Print unparsed output for profiler')
|
|
104
104
|
.option('--sync-dir <dir>', 'Custom directory path for cloud profile/config sync')
|
|
105
|
-
.option('--scan', 'Perform active network sweep on LAN neighbors for topology map')
|
|
105
|
+
.option('--scan', 'Perform active network sweep on LAN neighbors for topology map')
|
|
106
|
+
.option('--idle <duration>', 'Idle threshold for optimize apps (e.g. 30m, 2h)')
|
|
107
|
+
.option('--keep <n>', 'Always keep the N most recently used apps open (optimize)')
|
|
108
|
+
.option('-y, --yes', 'Skip confirmation prompts (optimize)')
|
|
109
|
+
.option('--force', 'Terminate apps that do not quit gracefully (optimize apps)')
|
|
110
|
+
.option('--clean', 'Delete measured caches after confirmation (optimize caches)')
|
|
111
|
+
.option('--empty', 'Empty the Trash after confirmation (optimize trash)')
|
|
112
|
+
.option('--auto-quit <duration>', 'Let the optimize tracker automatically quit apps idle this long')
|
|
113
|
+
.option('-m, --menu', 'Launch interactive TUI command center & tools navigator')
|
|
114
|
+
.option('-i, --interactive', 'Alias for --menu')
|
|
115
|
+
.option('--budget-cpu <pct>', 'Maximum allowable peak CPU % for pyre run')
|
|
116
|
+
.option('--budget-mem <bytes>', 'Maximum allowable peak memory for pyre run (e.g. 500MB, 2GB)')
|
|
117
|
+
.option('--budget-time <sec>', 'Maximum allowable duration for pyre run in seconds')
|
|
118
|
+
.option('--nice <val>', 'Nice value for pyre inspect --nice (-20 to 19)')
|
|
119
|
+
.option('--format <format>', 'Output format for statusline (tmux, starship, json, pango, plain)');
|
|
106
120
|
|
|
107
121
|
program.addHelpText('after', `
|
|
122
|
+
💡 Tip: Can't remember all commands? Run 'pyre menu' to browse and run all tools interactively!
|
|
123
|
+
|
|
108
124
|
Commands:
|
|
125
|
+
menu, tui, launcher Interactive TUI command center & tools navigator
|
|
126
|
+
playbook [run|list] Execute sequenced multi-step macro recipes (audit, clean, triage)
|
|
127
|
+
flight [dump|status|record] Forensic flight recorder black box in RAM
|
|
128
|
+
run [options] -- <cmd...> Performance budgeting runner for CI/CD gates
|
|
129
|
+
bench-git <b1> <b2> <cmd> Cross-branch git performance differ
|
|
130
|
+
statusline Headless terminal statusline string (tmux, starship, neovim)
|
|
131
|
+
inspect <pid> Process deep-dive inspector (lsof, sockets, nice)
|
|
132
|
+
containers Container and micro-VM telemetry (Docker, Podman, OrbStack)
|
|
109
133
|
live Interactive live dashboard
|
|
110
134
|
check One-line plain-English health summary
|
|
111
135
|
pipe Continuous newline-delimited JSON stream for scripting
|
|
@@ -143,6 +167,8 @@ program.addHelpText('after', `
|
|
|
143
167
|
build Active compiler and build system tracker
|
|
144
168
|
topo Network topology mapper and pyre peer auto-discovery
|
|
145
169
|
sync <push|pull|status> Synchronize config profiles across Macs via iCloud/Drive
|
|
170
|
+
optimize [apps|track|memory|caches|trash|startup|dns]
|
|
171
|
+
Mac optimizer: quit idle apps, free memory, clean caches
|
|
146
172
|
`);
|
|
147
173
|
|
|
148
174
|
program.parse(process.argv);
|
|
@@ -226,6 +252,106 @@ async function main() {
|
|
|
226
252
|
|
|
227
253
|
const cmd = program.args[0];
|
|
228
254
|
|
|
255
|
+
if (cmd === 'menu' || cmd === 'tui' || cmd === 'launcher' || cmd === 'interactive' || opts.menu || opts.interactive) {
|
|
256
|
+
const { runMenu } = await import('./menu/index.js');
|
|
257
|
+
await runMenu({
|
|
258
|
+
theme: opts.theme || config.theme,
|
|
259
|
+
});
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!cmd && config.defaultAction === 'menu' && !isExportMode() && !opts.once && process.stdout.isTTY) {
|
|
264
|
+
const { runMenu } = await import('./menu/index.js');
|
|
265
|
+
await runMenu({
|
|
266
|
+
theme: opts.theme || config.theme,
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (cmd === 'playbook') {
|
|
272
|
+
const sub = program.args[1];
|
|
273
|
+
const { runPlaybook, listPlaybooks } = await import('./playbookCmd.js');
|
|
274
|
+
if (sub === 'list' || !sub) {
|
|
275
|
+
listPlaybooks();
|
|
276
|
+
} else if (sub === 'run') {
|
|
277
|
+
const id = program.args[2] || 'audit';
|
|
278
|
+
await runPlaybook(id);
|
|
279
|
+
} else {
|
|
280
|
+
await runPlaybook(sub);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (cmd === 'flight') {
|
|
286
|
+
const sub = program.args[1] || 'dump';
|
|
287
|
+
const { runFlightCommand } = await import('./flightRecorder.js');
|
|
288
|
+
await runFlightCommand(sub, {
|
|
289
|
+
out: opts.out,
|
|
290
|
+
duration: opts.duration ? parseInt(opts.duration, 10) : undefined,
|
|
291
|
+
});
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (cmd === 'run') {
|
|
296
|
+
const rawArgs = process.argv;
|
|
297
|
+
const dashDashIdx = rawArgs.indexOf('--');
|
|
298
|
+
const targetCommand = dashDashIdx !== -1 ? rawArgs.slice(dashDashIdx + 1) : program.args.slice(1);
|
|
299
|
+
const { runWithBudget, parseBytesString } = await import('./budgetCmd.js');
|
|
300
|
+
const res = await runWithBudget(targetCommand, {
|
|
301
|
+
maxCpuPct: opts.budgetCpu ? parseFloat(opts.budgetCpu) : undefined,
|
|
302
|
+
maxMemoryBytes: opts.budgetMem ? parseBytesString(opts.budgetMem) : undefined,
|
|
303
|
+
maxTimeSec: opts.budgetTime ? parseFloat(opts.budgetTime) : undefined,
|
|
304
|
+
}, {
|
|
305
|
+
markdown: !!opts.markdown || !!opts.md,
|
|
306
|
+
outFile: opts.out,
|
|
307
|
+
});
|
|
308
|
+
if (res.exitCode !== 0) {
|
|
309
|
+
process.exit(res.exitCode);
|
|
310
|
+
}
|
|
311
|
+
if (res.violations.length > 0) {
|
|
312
|
+
process.exit(124);
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (cmd === 'bench-git') {
|
|
318
|
+
const branchA = program.args[1];
|
|
319
|
+
const branchB = program.args[2];
|
|
320
|
+
const commandStr = program.args.slice(3).join(' ');
|
|
321
|
+
if (!branchA || !branchB || !commandStr) {
|
|
322
|
+
console.log(chalk.red(' ✖ Usage: pyre bench-git <branchA> <branchB> "<command>"'));
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
const { runBenchGitCommand } = await import('./benchGitCmd.js');
|
|
326
|
+
await runBenchGitCommand(branchA, branchB, commandStr);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (cmd === 'statusline') {
|
|
331
|
+
const { runStatuslineCommand } = await import('./statuslineCmd.js');
|
|
332
|
+
await runStatuslineCommand(opts.format || 'plain');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (cmd === 'inspect') {
|
|
337
|
+
const targetPid = parseInt(program.args[1], 10);
|
|
338
|
+
const { runInspectCommand } = await import('./inspectCmd.js');
|
|
339
|
+
const niceVal = opts.nice !== undefined ? parseInt(opts.nice, 10) : undefined;
|
|
340
|
+
await runInspectCommand(targetPid, {
|
|
341
|
+
nice: niceVal,
|
|
342
|
+
json: !!opts.json,
|
|
343
|
+
});
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (cmd === 'containers') {
|
|
348
|
+
const { runContainersCommand } = await import('./containersCmd.js');
|
|
349
|
+
await runContainersCommand({
|
|
350
|
+
json: !!opts.json,
|
|
351
|
+
});
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
229
355
|
if (cmd === 'check') {
|
|
230
356
|
const { runCheckCommand } = await import('./checkCmd.js');
|
|
231
357
|
await runCheckCommand({
|
|
@@ -309,6 +435,23 @@ async function main() {
|
|
|
309
435
|
return;
|
|
310
436
|
}
|
|
311
437
|
|
|
438
|
+
if (cmd === 'optimize') {
|
|
439
|
+
const { runOptimizeCommand } = await import('./optimizeCmd.js');
|
|
440
|
+
await runOptimizeCommand(program.args[1], program.args.slice(2), {
|
|
441
|
+
idle: opts.idle,
|
|
442
|
+
keep: opts.keep,
|
|
443
|
+
yes: !!opts.yes,
|
|
444
|
+
force: !!opts.force,
|
|
445
|
+
clean: !!opts.clean,
|
|
446
|
+
empty: !!opts.empty,
|
|
447
|
+
dryRun: !!opts.dryRun,
|
|
448
|
+
install: !!opts.install,
|
|
449
|
+
uninstall: !!opts.uninstall,
|
|
450
|
+
autoQuit: opts.autoQuit,
|
|
451
|
+
});
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
312
455
|
if (cmd === 'replay') {
|
|
313
456
|
const file = program.args[1];
|
|
314
457
|
if (!file) {
|
|
@@ -580,7 +723,12 @@ async function main() {
|
|
|
580
723
|
}
|
|
581
724
|
|
|
582
725
|
if (cmd === 'xbar') {
|
|
583
|
-
|
|
726
|
+
const sub = program.args[1];
|
|
727
|
+
await generateXbarPlugin({
|
|
728
|
+
install: !!opts.install || sub === 'install',
|
|
729
|
+
yes: !!opts.yes,
|
|
730
|
+
dir: opts.dir,
|
|
731
|
+
});
|
|
584
732
|
return;
|
|
585
733
|
}
|
|
586
734
|
|
|
@@ -624,9 +772,13 @@ async function main() {
|
|
|
624
772
|
if (opts.alertPushoverToken) config.pushoverToken = opts.alertPushoverToken;
|
|
625
773
|
if (opts.alertPushoverUser) config.pushoverUser = opts.alertPushoverUser;
|
|
626
774
|
if (opts.alertNtfy) config.ntfyUrl = opts.alertNtfy;
|
|
627
|
-
|
|
775
|
+
if (cmd && cmd !== 'live' && cmd !== 'p2p') {
|
|
776
|
+
console.error(chalk.red(`\n ✖ Unknown command: '${cmd}'`));
|
|
777
|
+
console.log(chalk.yellow(` 💡 Tip: Can't remember a command? Run 'pyre menu' to browse and run all tools interactively!\n`));
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
628
780
|
|
|
629
|
-
if (cmd === 'live' || (!isExportMode() && !opts.once
|
|
781
|
+
if (cmd === 'live' || (!cmd && !isExportMode() && !opts.once)) {
|
|
630
782
|
if (!process.stdout.isTTY && cmd !== 'live') {
|
|
631
783
|
await runWebCommand();
|
|
632
784
|
return;
|
|
@@ -640,6 +792,7 @@ async function main() {
|
|
|
640
792
|
animation: config.splashAnimation as any,
|
|
641
793
|
});
|
|
642
794
|
const interval = parseFloat(opts.interval) || 2;
|
|
795
|
+
const tempUnit: 'c' | 'f' = (opts.tempUnit?.toLowerCase() === 'f' || config.tempUnit === 'f') ? 'f' : 'c';
|
|
643
796
|
await startLive({
|
|
644
797
|
interval,
|
|
645
798
|
detailed: opts.detailed ?? config.detailed,
|
|
@@ -674,13 +827,14 @@ async function main() {
|
|
|
674
827
|
|
|
675
828
|
const data = await collectAll({ detailed: opts.detailed, processLimit });
|
|
676
829
|
|
|
830
|
+
const tempUnit: 'c' | 'f' = (opts.tempUnit?.toLowerCase() === 'f' || config.tempUnit === 'f') ? 'f' : 'c';
|
|
677
831
|
let output: string;
|
|
678
832
|
if (opts.json) output = formatJson(data);
|
|
679
833
|
else if (opts.html) output = formatHtml(data);
|
|
680
834
|
else if (opts.md) output = formatMarkdown(data);
|
|
681
835
|
else if (opts.csv) output = formatCsv(data);
|
|
682
836
|
else if (opts.tsv) output = formatTsv(data);
|
|
683
|
-
else output = formatTable(data, { width: process.stdout.columns || 80, sortBy: opts.sort, treeView: opts.tree ?? config.treeView, minimal: !!opts.minimal, visible: { packets: opts.packets ? true : undefined } });
|
|
837
|
+
else output = formatTable(data, { width: process.stdout.columns || 80, sortBy: opts.sort, treeView: opts.tree ?? config.treeView, minimal: !!opts.minimal, visible: { packets: opts.packets ? true : undefined }, tempUnit });
|
|
684
838
|
|
|
685
839
|
console.log(output);
|
|
686
840
|
|
|
@@ -932,6 +1086,25 @@ async function runInfoCommand(): Promise<void> {
|
|
|
932
1086
|
}
|
|
933
1087
|
}
|
|
934
1088
|
|
|
1089
|
+
if (process.platform === 'win32') {
|
|
1090
|
+
lines.push('');
|
|
1091
|
+
lines.push(chalk.cyan.bold(' ⏱ System Restore & Recovery:'));
|
|
1092
|
+
try {
|
|
1093
|
+
const { runPowerShell } = await import('./monitors/run.js');
|
|
1094
|
+
const srRaw = await runPowerShell(
|
|
1095
|
+
`(Get-CimInstance -Namespace root/default -ClassName SystemRestore -ErrorAction SilentlyContinue | Select-Object -Last 1 -ExpandProperty Description) -join ''`,
|
|
1096
|
+
''
|
|
1097
|
+
);
|
|
1098
|
+
if (srRaw.trim()) {
|
|
1099
|
+
lines.push(` • Latest Restore Point: ${chalk.green(srRaw.trim())}`);
|
|
1100
|
+
} else {
|
|
1101
|
+
lines.push(' • System Restore active (no custom checkpoints)');
|
|
1102
|
+
}
|
|
1103
|
+
} catch {
|
|
1104
|
+
lines.push(' • System Restore available');
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
935
1108
|
console.log(lines.join('\n'));
|
|
936
1109
|
}
|
|
937
1110
|
|
|
@@ -1729,10 +1902,10 @@ async function runBenchCommand(benchCmd: string): Promise<void> {
|
|
|
1729
1902
|
let powerSamplesCount = 0;
|
|
1730
1903
|
|
|
1731
1904
|
const child = process.platform === 'win32'
|
|
1732
|
-
? spawn('cmd.exe', ['/c', benchCmd]
|
|
1905
|
+
? spawn('cmd.exe', ['/c', benchCmd])
|
|
1733
1906
|
: spawn('sh', ['-c', benchCmd]);
|
|
1734
|
-
child.stdout.on('data', d => process.stdout.write(d));
|
|
1735
|
-
child.stderr.on('data', d => process.stdout.write(d));
|
|
1907
|
+
if (child.stdout) child.stdout.on('data', d => process.stdout.write(d));
|
|
1908
|
+
if (child.stderr) child.stderr.on('data', d => process.stdout.write(d));
|
|
1736
1909
|
|
|
1737
1910
|
const interval = parseFloat(opts.interval) || 2;
|
|
1738
1911
|
const handle = setInterval(async () => {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pyre inspect — Deep-Dive Process Inspector.
|
|
3
|
+
*
|
|
4
|
+
* Inspects low-level attributes for a running PID:
|
|
5
|
+
* open file handles (lsof), active sockets, thread count,
|
|
6
|
+
* nice priority, and memory regions.
|
|
7
|
+
*/
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { formatBytes } from './budgetCmd.js';
|
|
12
|
+
|
|
13
|
+
export interface ProcessDetails {
|
|
14
|
+
pid: number;
|
|
15
|
+
name: string;
|
|
16
|
+
user: string;
|
|
17
|
+
ppid: number;
|
|
18
|
+
threads: number;
|
|
19
|
+
nice: number;
|
|
20
|
+
cpuPct: number;
|
|
21
|
+
memRssBytes: number;
|
|
22
|
+
openFiles: string[];
|
|
23
|
+
sockets: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function inspectProcess(pid: number): ProcessDetails {
|
|
27
|
+
// Check if process exists
|
|
28
|
+
try {
|
|
29
|
+
process.kill(pid, 0);
|
|
30
|
+
} catch (err: any) {
|
|
31
|
+
if (err.code === 'ESRCH') {
|
|
32
|
+
throw new Error(`Process with PID ${pid} not found or permission denied.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Query ps metadata
|
|
37
|
+
let name = 'unknown';
|
|
38
|
+
let user = 'unknown';
|
|
39
|
+
let ppid = 0;
|
|
40
|
+
let threads = 1;
|
|
41
|
+
let nice = 0;
|
|
42
|
+
let cpuPct = 0;
|
|
43
|
+
let memRssBytes = 0;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const psOut = execSync(`ps -p ${pid} -o user,ppid,nice,%cpu,rss,comm 2>/dev/null`, { encoding: 'utf-8' });
|
|
47
|
+
const lines = psOut.trim().split('\n');
|
|
48
|
+
if (lines.length > 1) {
|
|
49
|
+
const parts = lines[1].trim().split(/\s+/);
|
|
50
|
+
user = parts[0] || 'unknown';
|
|
51
|
+
ppid = parseInt(parts[1], 10) || 0;
|
|
52
|
+
nice = parseInt(parts[2], 10) || 0;
|
|
53
|
+
cpuPct = parseFloat(parts[3]) || 0;
|
|
54
|
+
memRssBytes = (parseInt(parts[4], 10) || 0) * 1024;
|
|
55
|
+
name = parts.slice(5).join(' ') || 'unknown';
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Fallback if ps is restricted or unavailable
|
|
59
|
+
if (pid === process.pid) {
|
|
60
|
+
try {
|
|
61
|
+
user = os.userInfo().username || 'current';
|
|
62
|
+
} catch {
|
|
63
|
+
user = process.env.USER || 'current';
|
|
64
|
+
}
|
|
65
|
+
ppid = process.ppid || 0;
|
|
66
|
+
name = process.title || 'node';
|
|
67
|
+
memRssBytes = process.memoryUsage().rss;
|
|
68
|
+
threads = 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Query lsof for open files and network sockets
|
|
73
|
+
const openFiles: string[] = [];
|
|
74
|
+
const sockets: string[] = [];
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const lsofOut = execSync(`lsof -p ${pid} -F tn 2>/dev/null`, { encoding: 'utf-8' });
|
|
78
|
+
const lines = lsofOut.split('\n');
|
|
79
|
+
let curType = '';
|
|
80
|
+
|
|
81
|
+
for (const l of lines) {
|
|
82
|
+
if (l.startsWith('t')) {
|
|
83
|
+
curType = l.slice(1).trim();
|
|
84
|
+
} else if (l.startsWith('n')) {
|
|
85
|
+
const nameVal = l.slice(1).trim();
|
|
86
|
+
if (curType === 'IPv4' || curType === 'IPv6' || nameVal.includes(':') || nameVal.includes('->')) {
|
|
87
|
+
sockets.push(nameVal);
|
|
88
|
+
} else if (nameVal.startsWith('/') && !openFiles.includes(nameVal)) {
|
|
89
|
+
openFiles.push(nameVal);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// lsof may not have permissions for system PIDs without sudo
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
pid,
|
|
99
|
+
name,
|
|
100
|
+
user,
|
|
101
|
+
ppid,
|
|
102
|
+
threads,
|
|
103
|
+
nice,
|
|
104
|
+
cpuPct,
|
|
105
|
+
memRssBytes,
|
|
106
|
+
openFiles: openFiles.slice(0, 15),
|
|
107
|
+
sockets: sockets.slice(0, 15),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function runInspectCommand(pid: number, opts?: { nice?: number; json?: boolean }): Promise<void> {
|
|
112
|
+
if (isNaN(pid) || pid <= 0) {
|
|
113
|
+
console.error(chalk.red(' ✖ Invalid PID. Usage: pyre inspect <pid>'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If renicing requested
|
|
118
|
+
if (opts?.nice !== undefined) {
|
|
119
|
+
try {
|
|
120
|
+
execSync(`renice ${opts.nice} -p ${pid}`, { stdio: 'inherit' });
|
|
121
|
+
console.log(chalk.green(` ✔ Successfully set nice priority for PID ${pid} to ${opts.nice}\n`));
|
|
122
|
+
} catch (err: any) {
|
|
123
|
+
console.error(chalk.red(` ✖ Failed to renice PID ${pid}: ${err.message}`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const details = inspectProcess(pid);
|
|
129
|
+
|
|
130
|
+
if (opts?.json) {
|
|
131
|
+
console.log(JSON.stringify(details, null, 2));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log(chalk.bold(`\n 🔥 pyre inspect — Process Deep Dive (PID ${details.pid})\n`));
|
|
136
|
+
console.log(` Process: ${chalk.bold.hex('#ff6a39')(details.name)}`);
|
|
137
|
+
console.log(` User: ${details.user}`);
|
|
138
|
+
console.log(` PPID: ${details.ppid}`);
|
|
139
|
+
console.log(` CPU Usage: ${details.cpuPct.toFixed(1)}%`);
|
|
140
|
+
console.log(` Memory RSS: ${formatBytes(details.memRssBytes)}`);
|
|
141
|
+
console.log(` Nice Value: ${details.nice} (Priority: ${details.nice < 0 ? 'High' : details.nice > 0 ? 'Low' : 'Normal'})`);
|
|
142
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────'));
|
|
143
|
+
|
|
144
|
+
// Open sockets
|
|
145
|
+
console.log(chalk.bold.cyan('\n Active Network Sockets:'));
|
|
146
|
+
if (details.sockets.length === 0) {
|
|
147
|
+
console.log(chalk.dim(' No open sockets detected (or permission denied).'));
|
|
148
|
+
} else {
|
|
149
|
+
details.sockets.forEach((s) => console.log(` • ${s}`));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Open file descriptors
|
|
153
|
+
console.log(chalk.bold.yellow('\n Open File Descriptors:'));
|
|
154
|
+
if (details.openFiles.length === 0) {
|
|
155
|
+
console.log(chalk.dim(' No open file handles detected.'));
|
|
156
|
+
} else {
|
|
157
|
+
details.openFiles.forEach((f) => console.log(` • ${chalk.dim(f)}`));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(chalk.dim('\n ─────────────────────────────────────────────────────────────'));
|
|
161
|
+
console.log(chalk.dim(` Adjust priority: pyre inspect ${pid} --nice <value -20 to 19>\n`));
|
|
162
|
+
} catch (err: any) {
|
|
163
|
+
console.error(chalk.red(`\n ✖ ${err.message}\n`));
|
|
164
|
+
}
|
|
165
|
+
}
|
package/src/live/input.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Imported by `index.ts` as the public API of the live module.
|
|
8
8
|
*/
|
|
9
9
|
import readline from 'node:readline';
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
10
11
|
import chalk from 'chalk';
|
|
11
12
|
import { collectAll, StatsData } from '../monitors/index.js';
|
|
12
13
|
import { THEMES, type ThemeName, type VisibleItems, getTabHitboxes, TAB_BAR_ROW } from '../formatters/index.js';
|
|
@@ -218,6 +219,9 @@ function isEscKey(key: readline.Key, str?: string): boolean {
|
|
|
218
219
|
case 'Resume Dashboard':
|
|
219
220
|
state.inputMode = null;
|
|
220
221
|
break;
|
|
222
|
+
case 'Command Center (All Tools)':
|
|
223
|
+
openCommandCenter();
|
|
224
|
+
return;
|
|
221
225
|
case 'Settings (Customizer)':
|
|
222
226
|
state.inputMode = 'customizer';
|
|
223
227
|
state.customizerIndex = 0;
|
|
@@ -437,20 +441,31 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
|
|
|
437
441
|
return;
|
|
438
442
|
}
|
|
439
443
|
|
|
440
|
-
const isProtected = pid === 1 || pid === process.pid || pid === process.ppid;
|
|
444
|
+
const isProtected = pid === 1 || pid === 0 || pid === 4 || pid === process.pid || pid === process.ppid;
|
|
441
445
|
if (isProtected && confirmKillPid !== pid) {
|
|
442
446
|
confirmKillPid = pid;
|
|
443
|
-
const name = pid ===
|
|
447
|
+
const name = (pid === 0 || pid === 4) ? 'Windows System Kernel' : (pid === 1 ? 'init/launchd' : (pid === process.pid ? 'pyre itself' : 'parent process'));
|
|
444
448
|
setStatus(`⚠️ PROTECTED PID ${pid} (${name})! Re-enter PID ${pid} to confirm termination.`, 8000);
|
|
445
449
|
return;
|
|
446
450
|
}
|
|
447
451
|
|
|
448
452
|
confirmKillPid = null;
|
|
453
|
+
|
|
454
|
+
if (process.platform === 'win32') {
|
|
455
|
+
try {
|
|
456
|
+
execSync(`taskkill /F /PID ${pid} /T`, { stdio: 'pipe' });
|
|
457
|
+
setStatus(`Terminated PID ${pid} (taskkill /F /T)`);
|
|
458
|
+
return;
|
|
459
|
+
} catch (err: any) {
|
|
460
|
+
// fallback to process.kill
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
449
464
|
try {
|
|
450
465
|
process.kill(pid, signal as NodeJS.Signals);
|
|
451
466
|
setStatus(`Sent ${signal} to PID ${pid}`);
|
|
452
467
|
} catch (err: any) {
|
|
453
|
-
setStatus(`Failed to
|
|
468
|
+
setStatus(`Failed to terminate PID ${pid}: ${err.message}`);
|
|
454
469
|
}
|
|
455
470
|
}
|
|
456
471
|
|
|
@@ -576,6 +591,51 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
|
|
|
576
591
|
}
|
|
577
592
|
}
|
|
578
593
|
|
|
594
|
+
async function openCommandCenter() {
|
|
595
|
+
state.paused = true;
|
|
596
|
+
if (state.intervalHandle) {
|
|
597
|
+
clearInterval(state.intervalHandle);
|
|
598
|
+
state.intervalHandle = null;
|
|
599
|
+
}
|
|
600
|
+
if (state.uiIntervalHandle) {
|
|
601
|
+
clearInterval(state.uiIntervalHandle);
|
|
602
|
+
state.uiIntervalHandle = null;
|
|
603
|
+
}
|
|
604
|
+
if (state.keypressHandler) {
|
|
605
|
+
process.stdin.removeListener('keypress', state.keypressHandler);
|
|
606
|
+
}
|
|
607
|
+
process.stdout.removeListener('resize', onResize);
|
|
608
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
609
|
+
process.stdin.pause();
|
|
610
|
+
process.stdout.write('\x1b[?1000l\x1b[?25h\x1b[?1049l\x1b[2J\x1b[H');
|
|
611
|
+
|
|
612
|
+
const { runMenu } = await import('../menu/index.js');
|
|
613
|
+
await runMenu({
|
|
614
|
+
theme: state.currentTheme,
|
|
615
|
+
onExit: () => {},
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
process.stdout.write('\x1b[?1049h\x1b[?25l\x1b[2J\x1b[H');
|
|
619
|
+
if (state.mouseEnabled) {
|
|
620
|
+
process.stdout.write('\x1b[?1000h');
|
|
621
|
+
}
|
|
622
|
+
readline.emitKeypressEvents(process.stdin);
|
|
623
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
624
|
+
process.stdin.resume();
|
|
625
|
+
if (state.keypressHandler) {
|
|
626
|
+
process.stdin.on('keypress', state.keypressHandler);
|
|
627
|
+
}
|
|
628
|
+
process.stdout.on('resize', onResize);
|
|
629
|
+
state.paused = false;
|
|
630
|
+
state.inputMode = null;
|
|
631
|
+
restartTicker();
|
|
632
|
+
if (state.uiIntervalHandle) clearInterval(state.uiIntervalHandle);
|
|
633
|
+
state.uiIntervalHandle = setInterval(() => {
|
|
634
|
+
if (state.running && !state.paused) render();
|
|
635
|
+
}, 250);
|
|
636
|
+
render();
|
|
637
|
+
}
|
|
638
|
+
|
|
579
639
|
/**
|
|
580
640
|
* Start the interactive live dashboard.
|
|
581
641
|
*
|
|
@@ -766,6 +826,10 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
|
|
|
766
826
|
case 'right':
|
|
767
827
|
cycleTab(1);
|
|
768
828
|
break;
|
|
829
|
+
case 'o':
|
|
830
|
+
case 'O':
|
|
831
|
+
openCommandCenter();
|
|
832
|
+
return;
|
|
769
833
|
case 'c':
|
|
770
834
|
state.inputMode = 'customizer';
|
|
771
835
|
state.customizerIndex = 0;
|
|
@@ -1047,6 +1111,9 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
|
|
|
1047
1111
|
|
|
1048
1112
|
// Execute the command
|
|
1049
1113
|
switch (cmd.id) {
|
|
1114
|
+
case 'command-center':
|
|
1115
|
+
openCommandCenter();
|
|
1116
|
+
break;
|
|
1050
1117
|
case 'cpu': case 'mem': case 'gpu': case 'power': case 'battery':
|
|
1051
1118
|
case 'thermal': case 'network': case 'packets': case 'tasks': case 'disk':
|
|
1052
1119
|
case 'process': case 'containers': case 'p2p': case 'anomalies': case 'blender':
|
package/src/live/render.ts
CHANGED
|
@@ -502,6 +502,7 @@ function render() {
|
|
|
502
502
|
function footerLine(): string[] {
|
|
503
503
|
const row1: [string, string][] = [
|
|
504
504
|
['q', 'quit'],
|
|
505
|
+
['o', 'tools menu'],
|
|
505
506
|
['c', 'customize UI'],
|
|
506
507
|
['←/→', 'tab'],
|
|
507
508
|
['1-9', 'panels'],
|
|
@@ -509,7 +510,6 @@ function footerLine(): string[] {
|
|
|
509
510
|
['b', `graph:${state.graphMode}`],
|
|
510
511
|
['t', state.treeView ? 'flat' : 'tree'],
|
|
511
512
|
['d', state.detailed ? 'basic' : 'detailed'],
|
|
512
|
-
['T', `temp:${state.tempUnit}`],
|
|
513
513
|
['p', state.paused ? 'resume' : 'pause'],
|
|
514
514
|
];
|
|
515
515
|
const row2: [string, string][] = [
|
package/src/live/state.ts
CHANGED
|
@@ -19,9 +19,10 @@ import { type SplashColorScheme, type SplashAnimation } from '../splash.js';
|
|
|
19
19
|
|
|
20
20
|
export { type SplashColorScheme, type SplashAnimation };
|
|
21
21
|
|
|
22
|
-
export const MENU_OPTIONS = ['Resume Dashboard', 'Settings (Customizer)', 'Readme', 'Credits', 'Quit pyre'] as const;
|
|
22
|
+
export const MENU_OPTIONS = ['Resume Dashboard', 'Command Center (All Tools)', 'Settings (Customizer)', 'Readme', 'Credits', 'Quit pyre'] as const;
|
|
23
23
|
|
|
24
24
|
const PALETTE_COMMANDS = [
|
|
25
|
+
{ id: 'command-center', label: 'Command Center (Browse all tools)', keys: 'o', category: 'Navigation' },
|
|
25
26
|
{ id: 'cpu', label: 'Switch to CPU panel', keys: '1', category: 'Navigation' },
|
|
26
27
|
{ id: 'mem', label: 'Switch to Memory panel', keys: '2', category: 'Navigation' },
|
|
27
28
|
{ id: 'gpu', label: 'Switch to GPU panel', keys: '3', category: 'Navigation' },
|