pyre-cli 10.1.0 → 10.2.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 (46) hide show
  1. package/README.md +195 -494
  2. package/dist/{checkCmd-RKW6QGDR.js → checkCmd-WQAHAUWO.js} +1 -1
  3. package/dist/{chunk-GHRDW646.js → chunk-23YWCI3O.js} +375 -135
  4. package/dist/{service-Q74BBILO.js → chunk-KFPTD266.js} +43 -3
  5. package/dist/{doctor-GYHFTNZQ.js → chunk-KVUMOA6C.js} +7 -6
  6. package/dist/{chunk-53O2RGSL.js → chunk-QM5L43EK.js} +19 -2
  7. package/dist/{chunk-6UHZP4QP.js → chunk-XTOOEKLM.js} +24 -12
  8. package/dist/{collectors-H43PN4PS.js → collectors-JDN5Y7EV.js} +1 -1
  9. package/dist/{completions-JKYEJNGR.js → completions-ZVTNE52B.js} +15 -11
  10. package/dist/{config-GYGJXU2Q.js → config-J4YMADEF.js} +3 -1
  11. package/dist/doctor-LHFW4UJP.js +10 -0
  12. package/dist/firebaseSync-ON3RZ6KX.js +91 -0
  13. package/dist/index.js +4243 -735
  14. package/dist/monitors/platform/windows-ui.ps1 +42 -0
  15. package/dist/{netUsageCmd-WKBTW2YG.js → netUsageCmd-67IBZZYN.js} +2 -2
  16. package/dist/{pipeCmd-7REDBLBU.js → pipeCmd-2N4JPKYE.js} +1 -1
  17. package/dist/{prometheusCmd-PL64BLN5.js → prometheusCmd-LEUM5WCZ.js} +1 -1
  18. package/dist/serveCmd-L7LVJOZN.js +353 -0
  19. package/dist/service-7GIX3YNC.js +16 -0
  20. package/dist/syncCmd-42IRF633.js +246 -0
  21. package/dist/{topoCmd-DU2HHZNH.js → topoCmd-DZ3YS5IL.js} +1 -1
  22. package/dist/{watchdogCmd-UF53JPZB.js → watchdogCmd-6GR4UN6B.js} +7 -7
  23. package/dist/{wizard-TD7BGX6O.js → wizard-JQMQWFAW.js} +1 -1
  24. package/package.json +4 -3
  25. package/parity.md +2 -0
  26. package/src/completions.ts +15 -11
  27. package/src/easy_features.test.ts +149 -0
  28. package/src/firebaseSync.ts +157 -0
  29. package/src/formatters/html_template.ts +3086 -598
  30. package/src/formatters/render.ts +25 -12
  31. package/src/index.ts +516 -23
  32. package/src/live/input.ts +501 -148
  33. package/src/live/render.ts +163 -18
  34. package/src/live/state.ts +52 -2
  35. package/src/live/types.ts +1 -1
  36. package/src/monitors/collectors.ts +3 -10
  37. package/src/monitors/platform/windows-ui.ps1 +42 -0
  38. package/src/monitors/platform/windows.ts +442 -147
  39. package/src/monitors/types.ts +8 -0
  40. package/src/serveCmd.ts +17 -2
  41. package/src/service.ts +49 -0
  42. package/src/state/config.ts +48 -0
  43. package/src/syncCmd.ts +159 -11
  44. package/src/web/dashboard.ts +205 -0
  45. package/dist/serveCmd-2H5VLF3P.js +0 -153
  46. package/dist/syncCmd-A2W3QB3S.js +0 -126
@@ -50,23 +50,26 @@ let _cachedGraphsVersion = -1;
50
50
  let _cachedGraphsParams = '';
51
51
 
52
52
  function tableCacheParams(): string {
53
- const limit = processRowBudget();
54
- return [
55
- state.sortMode,
56
- state.processFilter || '',
57
- state.processSelectionIndex,
58
- state.trackedPid || '',
59
- state.inspectingProcess ? state.inspectingProcess.pid : '',
60
- limit,
61
- state.currentTheme,
62
- state.activePanel,
63
- state.treeView ? '1' : '0',
64
- state.history.version,
65
- JSON.stringify(state.visiblePanels),
66
- JSON.stringify(state.panelLayout),
67
- state.aiModel,
68
- ].join('|');
69
- }
53
+ const limit = processRowBudget();
54
+ return [
55
+ state.sortMode,
56
+ state.processFilter || '',
57
+ state.processSelectionIndex,
58
+ state.trackedPid || '',
59
+ state.inspectingProcess ? state.inspectingProcess.pid : '',
60
+ limit,
61
+ state.currentTheme,
62
+ state.activePanel,
63
+ state.treeView ? '1' : '0',
64
+ state.history.version,
65
+ JSON.stringify(state.visiblePanels),
66
+ JSON.stringify(state.panelLayout),
67
+ state.aiModel,
68
+ state.graphZoomLevel,
69
+ JSON.stringify(state.panelHistory.slice(-5)),
70
+ JSON.stringify(state.bookmarks.map(b => b.name)),
71
+ ].join('|');
72
+ }
70
73
 
71
74
  function graphsCacheParams(): string {
72
75
  return [
@@ -162,6 +165,121 @@ function renderCustomizerOverlay(scrollOffset: number, maxVisibleItems: number):
162
165
  return lines.map(l => ` ${l}`).join('\n');
163
166
  }
164
167
 
168
+ function renderQuickRefOverlay(): string {
169
+ const headerLine = chalk.bgGreen.black.bold(' ⚡ PYRE KEYBOARD SHORTCUTS (Press ? or Esc to close) ');
170
+
171
+ const categories: Record<string, [string, string][]> = {
172
+ 'Navigation': [
173
+ ['1-9, 0', 'Switch to CPU/MEM/GPU/PWR/BAT/THM/NET/CONNS/TASKS/DISK panel'],
174
+ ['P', 'Process panel'],
175
+ ['C', 'Containers panel'],
176
+ ['R', 'P2P panel'],
177
+ ['A', 'Anomalies panel'],
178
+ ['B', 'Blender renders panel'],
179
+ ['←/→ or Tab', 'Cycle panels'],
180
+ ['Esc', 'Back to grid / menu'],
181
+ ],
182
+ 'View Controls': [
183
+ ['g', 'Toggle graphs on/off'],
184
+ ['b', 'Cycle graph mode: spark ↔ bar'],
185
+ ['p', 'Pause / Resume live updates'],
186
+ ['d', 'Toggle detailed sensor mode'],
187
+ ['t', 'Toggle tree / flat process view'],
188
+ ['T', 'Toggle temperature unit: C ↔ F'],
189
+ ['c', 'Open UI Customizer'],
190
+ ],
191
+ 'Process Actions': [
192
+ ['/ or f', 'Filter processes (type to search)'],
193
+ ['s', 'Cycle sort: cpu → mem → pid → user → command → state → threads → runtime'],
194
+ ['k', 'Kill process by PID'],
195
+ ['S', 'Send signal to process'],
196
+ ['Space', 'Follow / unfollow selected process'],
197
+ ['↑/↓', 'Navigate process list'],
198
+ ['Enter', 'Inspect selected process'],
199
+ ],
200
+ 'Data & Export': [
201
+ ['e', 'Export current snapshot'],
202
+ ['l', 'Start / stop CSV logging'],
203
+ ['f', 'Cycle export format: json → csv → tsv → html → md'],
204
+ ['+/-', 'Increase / decrease refresh interval'],
205
+ ],
206
+ 'Graph Controls': [
207
+ ['[', 'Zoom in history graph (more resolution)'],
208
+ [']', 'Zoom out history graph (wider window)'],
209
+ ],
210
+ 'Bookmarks': [
211
+ ['Shift+M', 'Save current view as named bookmark'],
212
+ ['M', 'Recall next bookmark (cycles through saved)'],
213
+ ],
214
+ 'Settings & Help': [
215
+ ['m', 'Change AI anomaly model'],
216
+ ['r', 'Start/stop P2P server'],
217
+ ['?', 'Show this quick reference'],
218
+ [':', 'Command palette (type to run any command)'],
219
+ ['q', 'Quit pyre'],
220
+ ],
221
+ };
222
+
223
+ const lines: string[] = [headerLine, ''];
224
+
225
+ for (const [category, items] of Object.entries(categories)) {
226
+ lines.push(chalk.bold.cyan(` ${category}`));
227
+ for (const [key, desc] of items) {
228
+ lines.push(` ${chalk.hex('#50fa7b').bold(key.padEnd(14))} ${chalk.dim(desc)}`);
229
+ }
230
+ lines.push('');
231
+ }
232
+
233
+ return lines.join('\n');
234
+ }
235
+
236
+ function renderCommandPalette(): string {
237
+ const headerLine = chalk.bgMagenta.black.bold(' COMMAND PALETTE (Type to search · ↑/↓ navigate · Enter run · Esc close) ');
238
+ const cmds = state.paletteFilteredCommands;
239
+ const totalItems = cmds.length;
240
+ const maxVisible = Math.min(12, state.termHeight - 8);
241
+ const start = Math.max(0, state.paletteIndex - Math.floor(maxVisible / 2));
242
+ const end = Math.min(totalItems, start + maxVisible);
243
+
244
+ const lines: string[] = [headerLine, ''];
245
+
246
+ if (cmds.length === 0) {
247
+ lines.push(chalk.dim(' No matching commands found.'));
248
+ } else {
249
+ // Group by category
250
+ const byCategory = new Map<string, typeof cmds>();
251
+ for (const cmd of cmds.slice(start, end)) {
252
+ const cat = cmd.category;
253
+ if (!byCategory.has(cat)) byCategory.set(cat, []);
254
+ byCategory.get(cat)!.push(cmd);
255
+ }
256
+
257
+ let globalIdx = start;
258
+ for (const [cat, catCmds] of byCategory) {
259
+ lines.push(chalk.bold.dim(` ${cat}`));
260
+ for (const cmd of catCmds) {
261
+ const isSelected = globalIdx === state.paletteIndex;
262
+ const prefix = isSelected ? chalk.yellow('▶ ') : ' ';
263
+ const keyHint = chalk.dim(`[${cmd.keys}]`);
264
+ const label = isSelected ? chalk.bold.white(cmd.label) : chalk.dim(cmd.label);
265
+ lines.push(`${prefix}${keyHint} ${label}`);
266
+ globalIdx++;
267
+ }
268
+ lines.push('');
269
+ }
270
+ }
271
+
272
+ const filterLine = chalk.cyan(` Filter: ${state.inputBuffer}${chalk.dim('_')}`);
273
+ lines.push(filterLine);
274
+
275
+ return lines.join('\n');
276
+ }
277
+
278
+ function renderBookmarkSaveOverlay(): string {
279
+ const headerLine = chalk.bgYellow.black.bold(' SAVE BOOKMARK (Type a name and press Enter · Esc to cancel) ');
280
+ return [headerLine, '', chalk.cyan(` Bookmark name: ${state.inputBuffer}${chalk.dim('_')}`)].join('\n');
281
+ }
282
+
165
283
  // --- frame diffing -------------------------------------------------------
166
284
  // Instead of erasing the whole screen (`\x1b[2J`) and repainting everything
167
285
  // on every keypress/tick, we keep the previous frame's lines around and
@@ -340,6 +458,19 @@ function render() {
340
458
 
341
459
  bodyLines.push('');
342
460
  bodyLines.push(...renderCustomizerOverlay(scrollOffset, maxVisibleItems).split('\n'));
461
+ } else if (state.inputMode === 'quick-ref') {
462
+ const refLines = renderQuickRefOverlay().split('\n');
463
+ const maxRefLines = Math.min(refLines.length, Math.max(10, targetBodyHeight - 2));
464
+ bodyLines.push('');
465
+ for (let i = 0; i < maxRefLines; i++) {
466
+ bodyLines.push(refLines[i] || '');
467
+ }
468
+ } else if (state.inputMode === 'command-palette') {
469
+ bodyLines.push('');
470
+ bodyLines.push(...renderCommandPalette().split('\n'));
471
+ } else if (state.inputMode === 'bookmark-save') {
472
+ bodyLines.push('');
473
+ bodyLines.push(...renderBookmarkSaveOverlay().split('\n'));
343
474
  } else if (state.inputMode === 'filter') {
344
475
  bodyLines.push(chalk.cyan(` Filter processes: ${state.inputBuffer}_`));
345
476
  } else if (state.inputMode === 'kill') {
@@ -405,7 +536,21 @@ function footerLine(): string[] {
405
536
  badges.push(chalk.magenta.bold(`AI:${state.aiModel}`));
406
537
  const badgeStr = badges.join(' ') || ' ';
407
538
 
408
- return [str1, str2, badgeStr];
539
+ // Power user footer extras
540
+ const powerExtras: string[] = [];
541
+ if (state.bookmarks.length > 0) {
542
+ powerExtras.push(chalk.cyan.bold(`★ ${state.bookmarks.length} bookmarks`));
543
+ }
544
+ if (state.graphZoomLevel > 1) {
545
+ powerExtras.push(chalk.yellow.bold(`🔍 ${state.graphZoomLevel}x zoom`));
546
+ }
547
+ if (state.panelHistory.length > 0) {
548
+ powerExtras.push(chalk.green.bold(`↩ ${state.panelHistory.length} panels in history`));
549
+ }
550
+
551
+ const extraStr = powerExtras.join(' ') || ' ';
552
+
553
+ return [str1, str2, badgeStr + ' ' + extraStr];
409
554
  }
410
555
 
411
556
  function sendNotification(title: string, message: string) {
package/src/live/state.ts CHANGED
@@ -11,7 +11,7 @@ import { History } from '../history.js';
11
11
  import type { StatsData } from '../monitors/index.js';
12
12
  import type { ExportFormat, InputMode, SortMode, GraphMode } from './types.js';
13
13
  import type { ThemeName, VisibleItems } from '../formatters/types.js';
14
- import { readConfig, type PyreConfig } from '../state/config.js';
14
+ import { readConfig, type PyreConfig, type PyreBookmark } from '../state/config.js';
15
15
 
16
16
  export const SIGNAL_OPTIONS = ['SIGTERM', 'SIGKILL', 'SIGINT', 'SIGHUP', 'SIGSTOP', 'SIGCONT'] as const;
17
17
 
@@ -21,6 +21,49 @@ export { type SplashColorScheme, type SplashAnimation };
21
21
 
22
22
  export const MENU_OPTIONS = ['Resume Dashboard', 'Settings (Customizer)', 'Readme', 'Credits', 'Quit pyre'] as const;
23
23
 
24
+ const PALETTE_COMMANDS = [
25
+ { id: 'cpu', label: 'Switch to CPU panel', keys: '1', category: 'Navigation' },
26
+ { id: 'mem', label: 'Switch to Memory panel', keys: '2', category: 'Navigation' },
27
+ { id: 'gpu', label: 'Switch to GPU panel', keys: '3', category: 'Navigation' },
28
+ { id: 'power', label: 'Switch to Power panel', keys: '4', category: 'Navigation' },
29
+ { id: 'battery', label: 'Switch to Battery panel', keys: '5', category: 'Navigation' },
30
+ { id: 'thermal', label: 'Switch to Thermal panel', keys: '6', category: 'Navigation' },
31
+ { id: 'network', label: 'Switch to Network panel', keys: '7', category: 'Navigation' },
32
+ { id: 'packets', label: 'Switch to Connections panel', keys: '8', category: 'Navigation' },
33
+ { id: 'tasks', label: 'Switch to Tasks panel', keys: '9', category: 'Navigation' },
34
+ { id: 'disk', label: 'Switch to Disk panel', keys: '0', category: 'Navigation' },
35
+ { id: 'process', label: 'Switch to Process panel', keys: 'P', category: 'Navigation' },
36
+ { id: 'containers', label: 'Switch to Containers panel', keys: 'C', category: 'Navigation' },
37
+ { id: 'p2p', label: 'Switch to P2P panel', keys: 'R', category: 'Navigation' },
38
+ { id: 'anomalies', label: 'Switch to Anomalies panel', keys: 'A', category: 'Navigation' },
39
+ { id: 'blender', label: 'Switch to Blender panel', keys: 'B', category: 'Navigation' },
40
+ { id: 'grid', label: 'Switch to Grid dashboard', keys: 'Esc', category: 'Navigation' },
41
+ { id: 'graph-toggle', label: 'Toggle graphs on/off', keys: 'g', category: 'View' },
42
+ { id: 'graph-mode', label: 'Cycle graph mode (spark/bar)', keys: 'b', category: 'View' },
43
+ { id: 'pause', label: 'Pause / Resume dashboard', keys: 'p', category: 'View' },
44
+ { id: 'detailed', label: 'Toggle detailed sensor mode', keys: 'd', category: 'View' },
45
+ { id: 'tree', label: 'Toggle tree/flat process view', keys: 't', category: 'View' },
46
+ { id: 'temp-unit', label: 'Toggle temperature unit (C/F)', keys: 'T', category: 'View' },
47
+ { id: 'filter', label: 'Filter processes by name', keys: '/', category: 'Process' },
48
+ { id: 'sort', label: 'Cycle process sort mode', keys: 's', category: 'Process' },
49
+ { id: 'kill', label: 'Kill a process by PID', keys: 'k', category: 'Process' },
50
+ { id: 'signal', label: 'Send signal to a process', keys: 'S', category: 'Process' },
51
+ { id: 'follow', label: 'Follow selected process', keys: 'Space', category: 'Process' },
52
+ { id: 'inspect', label: 'Inspect selected process', keys: 'Enter', category: 'Process' },
53
+ { id: 'export', label: 'Export current snapshot', keys: 'e', category: 'Data' },
54
+ { id: 'log', label: 'Start / stop CSV logging', keys: 'l', category: 'Data' },
55
+ { id: 'format-cycle', label: 'Cycle export format', keys: 'f', category: 'Data' },
56
+ { id: 'interval-inc', label: 'Increase refresh interval', keys: '+', category: 'Data' },
57
+ { id: 'interval-dec', label: 'Decrease refresh interval', keys: '-', category: 'Data' },
58
+ { id: 'customize', label: 'Open UI customizer', keys: 'c', category: 'Settings' },
59
+ { id: 'ai-model', label: 'Change AI anomaly model', keys: 'm', category: 'Settings' },
60
+ { id: 'bookmark-save', label: 'Save current view as bookmark', keys: 'Shift+M', category: 'Bookmarks' },
61
+ { id: 'bookmark-recall', label: 'Recall next bookmark', keys: 'M', category: 'Bookmarks' },
62
+ { id: 'zoom-in', label: 'Zoom in history graph', keys: '[', category: 'Graphs' },
63
+ { id: 'zoom-out', label: 'Zoom out history graph', keys: ']', category: 'Graphs' },
64
+ { id: 'quick-ref', label: 'Show keyboard shortcuts', keys: '?', category: 'Help' },
65
+ ];
66
+
24
67
  const config = readConfig();
25
68
 
26
69
  const isDockerEnv = fs.existsSync('/.dockerenv') || fs.existsSync('/run/.containerenv');
@@ -150,6 +193,13 @@ const state = {
150
193
  aiModel: config.aiModel || 'expert-rules-v1',
151
194
  aiBackend: config.aiBackend || 'builtin',
152
195
  modelSelectionIndex: 0,
196
+ // Power user tools state
197
+ bookmarks: [] as PyreBookmark[],
198
+ bookmarkRecallIndex: 0,
199
+ graphZoomLevel: 1,
200
+ panelHistory: [] as string[],
201
+ paletteIndex: 0,
202
+ paletteFilteredCommands: [...PALETTE_COMMANDS],
153
203
  };
154
204
 
155
205
  function setStatus(msg: string, ms = 3000) {
@@ -180,4 +230,4 @@ function getToggleKey(opt: string): keyof VisibleItems | null {
180
230
  return map[opt] ?? null;
181
231
  }
182
232
 
183
- export { state, setStatus, getToggleKey };
233
+ export { state, setStatus, getToggleKey, PALETTE_COMMANDS };
package/src/live/types.ts CHANGED
@@ -20,7 +20,7 @@ export interface LiveOptions {
20
20
 
21
21
  export type ExportFormat = 'json' | 'csv' | 'tsv' | 'html' | 'md';
22
22
 
23
- type InputMode = null | 'filter' | 'kill' | 'signal' | 'customizer' | 'p2p' | 'menu' | 'readme' | 'credits' | 'docker-alert' | 'ai-model';
23
+ type InputMode = null | 'filter' | 'kill' | 'signal' | 'customizer' | 'p2p' | 'menu' | 'readme' | 'credits' | 'docker-alert' | 'ai-model' | 'quick-ref' | 'bookmark-save' | 'command-palette';
24
24
 
25
25
  type SortMode = 'cpu' | 'mem' | 'pid' | 'user' | 'command' | 'state' | 'threads' | 'runtime';
26
26
 
@@ -19,7 +19,7 @@ import fs from 'node:fs';
19
19
  import { run } from './run.js';
20
20
  import { getSmcMetrics, parseSuffix } from './smc.js';
21
21
  import { resolveIp } from './dns_cache.js';
22
- import type { StatsData, CpuData, MemoryData, ThermalData, BatteryData, PowerData, DiskData, NetworkData, ProcessData, GpuData, PacketData, NetworkProcess, TaskData, ContainerData, NetworkConnection, BlenderRenderData, ProtocolStats, ConnectionStateStats, RemoteHostInfo } from './types.js';
22
+ import type { StatsData, CpuData, MemoryData, ThermalData, BatteryData, PowerData, DiskData, NetworkData, ProcessData, GpuData, PacketData, NetworkProcess, TaskData, ContainerData, NetworkConnection, BlenderRenderData, ProtocolStats, ConnectionStateStats, RemoteHostInfo, DisplayInfo } from './types.js';
23
23
  import { collectBlenderRenders } from './blender.js';
24
24
  import {
25
25
  collectLinuxSystem,
@@ -1350,15 +1350,8 @@ export async function collectTasks(limit = 12): Promise<TaskData[]> {
1350
1350
  .filter((t): t is TaskData => t !== null && t.pid > 0);
1351
1351
  } catch {
1352
1352
  return [];
1353
- }
1354
- }
1355
-
1356
- export interface DisplayInfo {
1357
- name: string;
1358
- resolution: string;
1359
- connectionType?: string;
1360
- isMain?: boolean;
1361
- }
1353
+ }
1354
+ }
1362
1355
 
1363
1356
  export async function getDisplayInfo(): Promise<DisplayInfo[]> {
1364
1357
  if (process.platform === 'linux') return getLinuxDisplayInfo();
@@ -0,0 +1,42 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Launches a native Pyre UI window on Windows using WPF WebBrowser control.
4
+ #>
5
+ param(
6
+ [Parameter(Mandatory=$true)]
7
+ [int]$Port
8
+ )
9
+
10
+ $ErrorActionPreference = 'Stop'
11
+
12
+ Add-Type -AssemblyName System.Windows.Forms
13
+ Add-Type -AssemblyName System.Drawing
14
+ Add-Type -AssemblyName PresentationFramework
15
+
16
+ $regPath = "HKCU:\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION"
17
+ if (-not (Test-Path $regPath)) {
18
+ New-Item -Path $regPath -Force | Out-Null
19
+ }
20
+ $scriptName = [System.IO.Path]::GetFileName($PSCommandPath)
21
+ Set-ItemProperty -Path $regPath -Name $scriptName -Value 11001 -Type DWord -ErrorAction SilentlyContinue
22
+
23
+ $form = New-Object System.Windows.Forms.Form
24
+ $form.Text = "Pyre Live Dashboard"
25
+ $form.Width = 1200
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)
31
+
32
+ $browser = New-Object System.Windows.Forms.WebBrowser
33
+ $browser.Dock = "Fill"
34
+ $browser.ScriptErrorsSuppressed = $true
35
+ $browser.Navigate("http://localhost:$Port/")
36
+ $form.Controls.Add($browser)
37
+
38
+ $form.Add_Shown({
39
+ $form.Activate()
40
+ })
41
+
42
+ $form.ShowDialog()