pyre-cli 2.0.0 → 3.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyre-cli",
3
- "version": "2.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "CLI Mac monitoring tool: temps, system stats, live dashboard, packet monitor, battery predictor, export",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -11,7 +11,7 @@
11
11
  * - `render.ts` — dashboard layout, cards, tables, and the main `formatTable`
12
12
  * - `output.ts` — JSON/CSV/TSV export and sparkline graph rendering
13
13
  */
14
- export { formatTable, clampWidth, gridColumns, thermalColor, capacityColor, getTabHitboxes, TAB_BAR_ROW, TAB_DEFS } from './render.js';
14
+ export { formatTable, clampWidth, gridColumns, thermalColor, capacityColor, getTabHitboxes, TAB_BAR_ROW, TAB_DEFS, panel } from './render.js';
15
15
  export { formatJson, formatCsv, formatTsv, formatGraphs } from './output.js';
16
16
  export type { ThemeName, ThemeColors, VisibleItems, TableOptions } from './types.js';
17
17
  export { THEMES } from './themes.js';
@@ -275,7 +275,8 @@ export const TAB_DEFS: { id: string; label: string; key: string }[] = [
275
275
  { id: 'packets', label: 'Packets', key: '8' },
276
276
  { id: 'tasks', label: 'Tasks', key: '9' },
277
277
  { id: 'disk', label: 'Disk', key: '0' },
278
- { id: 'process', label: 'Process', key: 'p' },
278
+ { id: 'process', label: 'Process', key: 'P' },
279
+ { id: 'p2p', label: 'P2P', key: 'R' },
279
280
  ];
280
281
 
281
282
  /** The number of leading columns of indent before the tab bar's first character (see `tabBar`). */
package/src/index.ts CHANGED
@@ -37,7 +37,17 @@ program
37
37
  .option('--limit <n>', 'Max number of processes to include in --once/--json/--csv/--tsv snapshots (0 = all)', '10')
38
38
  .option('--p2p-host <host>', 'P2P host address (server: bind address, client: server address)', '0.0.0.0')
39
39
  .option('--p2p-port <port>', 'P2P port number', '9876')
40
- .option('--p2p-password <password>', 'Password for P2P authentication');
40
+ .option('--p2p-password <password>', 'Password for P2P authentication')
41
+ .option('--p2p-tls', 'Enable TLS encryption for P2P connections')
42
+ .option('--p2p-cert <file>', 'TLS certificate file (PEM) for P2P server')
43
+ .option('--p2p-key <file>', 'TLS private key file (PEM) for P2P server')
44
+ .option('--p2p-ca <file>', 'TLS CA certificate file (PEM) for P2P client')
45
+ .option('--p2p-insecure', 'Skip TLS certificate verification (client only)')
46
+ .option('--p2p-rate-limit <n>', 'Max auth attempts per IP per minute (default: 5)', '5')
47
+ .option('--p2p-allow <ips>', 'Comma-separated list of allowed IPs (empty = all)')
48
+ .option('--p2p-deny <ips>', 'Comma-separated list of denied IPs')
49
+ .option('--p2p-audit-log <dir>', 'Directory for P2P audit logs')
50
+ .option('--p2p-hmac-key <key>', 'HMAC key for message signing (default: derived from password)');
41
51
 
42
52
  program.parse(process.argv);
43
53
 
@@ -116,20 +126,56 @@ async function runP2PServer(): Promise<void> {
116
126
  const password = opts.p2pPassword || '';
117
127
  const interval = parseFloat(opts.interval) || 2;
118
128
  const detailed = !!opts.detailed;
129
+ const tlsCert = opts.p2pCert;
130
+ const tlsKey = opts.p2pKey;
131
+ const tlsCA = opts.p2pCA;
132
+ const rateLimit = parseInt(opts.p2pRateLimit, 10) || 5;
133
+ const allowList = opts.p2pAllow ? opts.p2pAllow.split(',').map((s: string) => s.trim()).filter(Boolean) : [];
134
+ const denyList = opts.p2pDeny ? opts.p2pDeny.split(',').map((s: string) => s.trim()).filter(Boolean) : [];
119
135
 
120
136
  if (!password) {
121
137
  console.log(chalk.red('Error: a password is required for P2P server mode. Use --p2p-password <password>'));
122
138
  process.exit(1);
123
139
  }
124
140
 
125
- const server = new P2PServer({ host, port, password, intervalMs: interval * 1000, detailed });
141
+ if (tlsCert && !tlsKey) {
142
+ console.log(chalk.red('Error: --p2p-key is required when --p2p-cert is specified'));
143
+ process.exit(1);
144
+ }
145
+ if (tlsKey && !tlsCert) {
146
+ console.log(chalk.red('Error: --p2p-cert is required when --p2p-key is specified'));
147
+ process.exit(1);
148
+ }
149
+
150
+ const server = new P2PServer({
151
+ host,
152
+ port,
153
+ password,
154
+ intervalMs: interval * 1000,
155
+ detailed,
156
+ tlsCert,
157
+ tlsKey,
158
+ tlsCA,
159
+ rateLimitMaxAttempts: rateLimit,
160
+ rateLimitWindowMs: 60000,
161
+ allowedIPs: allowList,
162
+ deniedIPs: denyList,
163
+ auditLog: opts.p2pAuditLog,
164
+ hmacKey: opts.p2pHmacKey,
165
+ });
166
+
167
+ const tlsEnabled = !!(tlsCert && tlsKey);
126
168
 
127
169
  console.log(chalk.bold(`\n pyre P2P Server`));
128
170
  console.log(chalk.dim(` Host: ${host}`));
129
171
  console.log(chalk.dim(` Port: ${port}`));
172
+ console.log(chalk.dim(` Protocol: ${tlsEnabled ? 'TLS' : 'TCP'}`));
130
173
  console.log(chalk.dim(` Password: ${password}`));
131
174
  console.log(chalk.dim(` Interval: ${interval}s`));
132
175
  console.log(chalk.dim(` Detailed: ${detailed ? 'on' : 'off'}`));
176
+ console.log(chalk.dim(` Rate limit: ${rateLimit} attempts/min`));
177
+ if (allowList.length > 0) console.log(chalk.dim(` Allowed IPs: ${allowList.join(', ')}`));
178
+ if (denyList.length > 0) console.log(chalk.dim(` Denied IPs: ${denyList.join(', ')}`));
133
179
  console.log(chalk.dim(` Waiting for peers...\n`));
134
180
 
135
181
  await server.start();
@@ -158,13 +204,25 @@ async function runP2PConnect(): Promise<void> {
158
204
  const host = opts.p2pHost || '127.0.0.1';
159
205
  const port = parseInt(opts.p2pPort, 10) || 9876;
160
206
  const password = opts.p2pPassword || '';
207
+ const useTLS = !!opts.p2pTls;
208
+ const tlsCA = opts.p2pCA;
209
+ const tlsInsecure = !!opts.p2pInsecure;
161
210
 
162
211
  if (!password) {
163
212
  console.log(chalk.red('Error: a password is required for P2P connect mode. Use --p2p-password <password>'));
164
213
  process.exit(1);
165
214
  }
166
215
 
167
- const client = new P2PClient({ host, port, password });
216
+ const client = new P2PClient({
217
+ host,
218
+ port,
219
+ password,
220
+ tls: useTLS,
221
+ tlsCA,
222
+ tlsInsecure,
223
+ auditLog: opts.p2pAuditLog,
224
+ hmacKey: opts.p2pHmacKey,
225
+ });
168
226
 
169
227
  client.setOnStatus((msg) => {
170
228
  process.stdout.write(`\r${chalk.dim(msg)}${' '.repeat(40)}\r`);
@@ -183,7 +241,9 @@ async function runP2PConnect(): Promise<void> {
183
241
  }));
184
242
  });
185
243
 
244
+ const protocolLabel = useTLS ? 'TLS' : 'TCP';
186
245
  console.log(chalk.bold(`\n pyre P2P Client`));
246
+ console.log(chalk.dim(` Protocol: ${protocolLabel}`));
187
247
  console.log(chalk.dim(` Connecting to ${host}:${port}...`));
188
248
  console.log(chalk.dim(` Press Ctrl+C to disconnect\n`));
189
249
 
package/src/live/input.ts CHANGED
@@ -14,6 +14,7 @@ import { state, setStatus } from './state.js';
14
14
  import { exportSnapshot, startLogging, stopLogging, toggleLogging, writeLogRow } from './export.js';
15
15
  import { render, footerLine, checkAlerts, processRowBudget } from './render.js';
16
16
  import type { LiveOptions, ExportFormat, InputMode, SortMode, GraphMode } from './types.js';
17
+ import { startP2PServer } from '../p2p/index.js';
17
18
 
18
19
  function handleInputModeKey(str: string, key: readline.Key) {
19
20
  if (key.name === 'escape') {
@@ -67,6 +68,36 @@ function handleInputModeKey(str: string, key: readline.Key) {
67
68
  return;
68
69
  }
69
70
 
71
+ if (state.inputMode === 'p2p') {
72
+ if (key.name === 'escape') {
73
+ state.inputMode = null;
74
+ state.inputBuffer = '';
75
+ render();
76
+ return;
77
+ }
78
+ if (key.name === 'return') {
79
+ const password = state.inputBuffer.trim();
80
+ if (password) {
81
+ state.p2pPassword = password;
82
+ startP2PServerFromTUI();
83
+ }
84
+ state.inputMode = null;
85
+ state.inputBuffer = '';
86
+ render();
87
+ return;
88
+ }
89
+ if (key.name === 'backspace') {
90
+ state.inputBuffer = state.inputBuffer.slice(0, -1);
91
+ render();
92
+ return;
93
+ }
94
+ if (str && str.length === 1 && !key.ctrl && !key.meta) {
95
+ state.inputBuffer += str;
96
+ render();
97
+ }
98
+ return;
99
+ }
100
+
70
101
  if (key.name === 'return') {
71
102
  if (state.inputMode === 'filter') {
72
103
  state.processFilter = state.inputBuffer.trim();
@@ -106,6 +137,56 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
106
137
  }
107
138
  }
108
139
 
140
+ async function startP2PServerFromTUI() {
141
+ if (state.p2pServerRunning) return;
142
+ try {
143
+ const server = await startP2PServer({
144
+ host: '0.0.0.0',
145
+ port: state.p2pPort,
146
+ password: state.p2pPassword,
147
+ intervalMs: state.interval * 1000,
148
+ detailed: state.detailed,
149
+ onLog: (msg: string) => setStatus(msg, 5000),
150
+ onPeerEvent: (evt) => {
151
+ state.p2pEvents.push(evt);
152
+ if (state.p2pEvents.length > 200) state.p2pEvents.shift();
153
+ },
154
+ });
155
+ state.p2pServer = server as any;
156
+ state.p2pServerRunning = true;
157
+ state.p2pBind = (server as any).boundAddress || '0.0.0.0';
158
+ state.p2pEvents = [];
159
+ setStatus(`P2P server started on ${state.p2pBind}:${state.p2pPort}`);
160
+ render();
161
+ } catch (err: any) {
162
+ setStatus(`P2P server failed: ${err.message}`);
163
+ state.p2pServerRunning = false;
164
+ state.p2pServer = null;
165
+ state.p2pBind = '';
166
+ render();
167
+ }
168
+ }
169
+
170
+ function syncP2PEvents() {
171
+ if (!state.p2pServer) return;
172
+ const history = state.p2pServer.peerEventHistory;
173
+ if (history && history !== state.p2pEvents) {
174
+ state.p2pEvents = history;
175
+ }
176
+ }
177
+
178
+ function stopP2PServer() {
179
+ if (!state.p2pServer) return;
180
+ try {
181
+ state.p2pServer.stop();
182
+ } catch {
183
+ // ignore stop errors
184
+ }
185
+ state.p2pServer = null;
186
+ state.p2pServerRunning = false;
187
+ setStatus('P2P server stopped');
188
+ }
189
+
109
190
  function onResize() {
110
191
  state.termWidth = process.stdout.columns || 80;
111
192
  state.termHeight = process.stdout.rows || 24;
@@ -140,6 +221,7 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
140
221
 
141
222
  writeLogRow(data);
142
223
  checkAlerts(data);
224
+ syncP2PEvents();
143
225
  render();
144
226
  } catch {
145
227
  // skip bad tick
@@ -252,6 +334,17 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
252
334
  }
253
335
 
254
336
  switch (key.name) {
337
+ case 'r':
338
+ if (state.p2pServerRunning) {
339
+ stopP2PServer();
340
+ setStatus('P2P server stopped');
341
+ render();
342
+ } else {
343
+ state.inputMode = 'p2p';
344
+ state.inputBuffer = state.p2pPassword;
345
+ render();
346
+ }
347
+ return;
255
348
  case 'q':
256
349
  stopLive();
257
350
  break;
@@ -362,7 +455,7 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
362
455
  }
363
456
 
364
457
  function tabKeyToId(str: string): string | null {
365
- const map: Record<string, string> = { '1': 'cpu', '2': 'mem', '3': 'gpu', '4': 'power', '5': 'battery', '6': 'thermal', '7': 'network', '8': 'packets', '9': 'tasks', '0': 'disk', 'p': 'process' };
458
+ const map: Record<string, string> = { '1': 'cpu', '2': 'mem', '3': 'gpu', '4': 'power', '5': 'battery', '6': 'thermal', '7': 'network', '8': 'packets', '9': 'tasks', '0': 'disk', 'p': 'process', 'r': 'p2p' };
366
459
  return map[str] ?? null;
367
460
  }
368
461
 
@@ -420,6 +513,11 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
420
513
  state.logStream.end();
421
514
  state.logStream = null;
422
515
  }
516
+ if (state.p2pServer) {
517
+ state.p2pServer.stop();
518
+ state.p2pServer = null;
519
+ state.p2pServerRunning = false;
520
+ }
423
521
  if (state.keypressHandler) {
424
522
  process.stdin.removeListener('keypress', state.keypressHandler);
425
523
  state.keypressHandler = null;
@@ -7,7 +7,7 @@
7
7
  * `state.ts`.
8
8
  */
9
9
  import chalk from 'chalk';
10
- import { formatTable, formatGraphs, gridColumns, THEMES, type ThemeName } from '../formatters/index.js';
10
+ import { formatTable, formatGraphs, gridColumns, THEMES, type ThemeName, panel } from '../formatters/index.js';
11
11
  import { state, setStatus, SIGNAL_OPTIONS } from './state.js';
12
12
  import type { StatsData } from '../monitors/index.js';
13
13
 
@@ -25,6 +25,33 @@ function processRowBudget(): number {
25
25
  return Math.max(3, Math.min(40, state.termHeight - reserved));
26
26
  }
27
27
 
28
+ function p2pPanelLines(): { title: string; body: string[] } {
29
+ const events = state.p2pServer ? state.p2pServer.peerEventHistory : state.p2pEvents;
30
+ const body: string[] = [];
31
+ const bindAddr = state.p2pBind || `0.0.0.0:${state.p2pPort}`;
32
+
33
+ body.push(`Status: ${state.p2pServerRunning ? chalk.green('Running') : chalk.red('Stopped')}`);
34
+ body.push(`Bind: ${bindAddr}`);
35
+ body.push(`Password: ${state.p2pPassword}`);
36
+ body.push('');
37
+
38
+ const typeColor = (t: string) => {
39
+ if (t === 'auth') return chalk.green;
40
+ if (t === 'disconnect') return chalk.yellow;
41
+ if (t === 'error' || t === 'blocked' || t === 'rate_limited') return chalk.red;
42
+ return chalk.cyan;
43
+ };
44
+ body.push(chalk.dim('Events (most recent):'));
45
+ const recent = events.slice(-20).reverse();
46
+ for (const evt of recent) {
47
+ const time = new Date(evt.ts).toLocaleTimeString();
48
+ body.push(`${chalk.dim(time)} ${typeColor(evt.type)(evt.type.padEnd(14))} ${evt.detail}`);
49
+ }
50
+
51
+ const title = `P2P · ${state.p2pServerRunning ? 'ON' : 'OFF'} · ${events.length} events`;
52
+ return { title, body };
53
+ }
54
+
28
55
  function renderCustomizerOverlay(): string {
29
56
  const themesList = Object.keys(THEMES) as ThemeName[];
30
57
  const lines = [
@@ -57,20 +84,27 @@ function renderCustomizerOverlay(): string {
57
84
  function render() {
58
85
  if (!state.lastData) return;
59
86
  const lines: string[] = [];
60
- lines.push(
61
- formatTable(state.lastData, {
62
- width: state.termWidth,
63
- sortBy: state.sortMode,
64
- filter: state.processFilter || undefined,
65
- processLimit: processRowBudget(),
66
- theme: state.currentTheme,
67
- visible: state.visiblePanels,
68
- treeView: state.treeView,
69
- activePanel: state.activePanel,
70
- })
71
- );
72
-
73
- if (state.showGraphs) {
87
+ const theme = THEMES[state.currentTheme] || THEMES.default;
88
+
89
+ if (state.activePanel === 'p2p') {
90
+ const panelLines = p2pPanelLines();
91
+ lines.push(...panel(panelLines.title, panelLines.body, state.termWidth, theme.process, theme.border));
92
+ } else {
93
+ lines.push(
94
+ formatTable(state.lastData, {
95
+ width: state.termWidth,
96
+ sortBy: state.sortMode,
97
+ filter: state.processFilter || undefined,
98
+ processLimit: processRowBudget(),
99
+ theme: state.currentTheme,
100
+ visible: state.visiblePanels,
101
+ treeView: state.treeView,
102
+ activePanel: state.activePanel,
103
+ })
104
+ );
105
+ }
106
+
107
+ if (state.showGraphs && state.activePanel !== 'p2p') {
74
108
  lines.push('');
75
109
  lines.push(formatGraphs(state.history, state.termWidth, state.currentTheme, state.graphMode));
76
110
  }
@@ -90,9 +124,11 @@ function render() {
90
124
  lines.push(chalk.cyan(` Signal: ${state.inputBuffer}_`));
91
125
  lines.push(chalk.dim(` ${sigList}`));
92
126
  lines.push(chalk.dim(' ↑/↓ to select, Enter to confirm, Esc to cancel'));
127
+ } else if (state.inputMode === 'p2p') {
128
+ lines.push(chalk.cyan(` P2P password (${state.p2pPort}): ${state.inputBuffer}_ (enter to start, esc to cancel)`));
93
129
  } else if (state.statusMessage) {
94
130
  lines.push(` ${state.statusMessage}`);
95
- }
131
+ }
96
132
 
97
133
  process.stdout.write('\x1b[2J\x1b[H');
98
134
  process.stdout.write(lines.join('\n'));
@@ -102,22 +138,23 @@ function footerLine(): string {
102
138
  const keys: [string, string][] = [
103
139
  ['q', 'quit'],
104
140
  ['c', 'customize UI'],
105
- ['p', state.paused ? 'resume' : 'pause'],
106
- ['g', state.showGraphs ? 'hide graphs' : 'show graphs'],
107
- ['b', `graph:${state.graphMode}`],
108
- ['d', state.detailed ? 'basic' : 'detailed'],
109
- ['←/→', 'tab'],
110
- ['1-9', 'panels'],
111
- ['s', `sort:${state.sortMode}`],
112
- ['/', 'filter'],
113
- ['k', 'kill'],
114
- ['S', 'signal'],
115
- ['t', state.treeView ? 'flat' : 'tree'],
116
- ['e', 'export'],
117
- ['l', state.logging ? 'stop log' : 'log'],
118
- ['f', state.exportFormat],
119
- ['+/-', `${state.interval}s`],
120
- ];
141
+ ['r', state.p2pServerRunning ? 'stop p2p' : 'start p2p'],
142
+ ['p', state.paused ? 'resume' : 'pause'],
143
+ ['g', state.showGraphs ? 'hide graphs' : 'show graphs'],
144
+ ['b', `graph:${state.graphMode}`],
145
+ ['d', state.detailed ? 'basic' : 'detailed'],
146
+ ['←/→', 'tab'],
147
+ ['1-9', 'panels'],
148
+ ['s', `sort:${state.sortMode}`],
149
+ ['/', 'filter'],
150
+ ['k', 'kill'],
151
+ ['S', 'signal'],
152
+ ['t', state.treeView ? 'flat' : 'tree'],
153
+ ['e', 'export'],
154
+ ['l', state.logging ? 'stop log' : 'log'],
155
+ ['f', state.exportFormat],
156
+ ['+/-', `${state.interval}s`],
157
+ ];
121
158
  const keyStr = keys.map(([k, label]) => `${chalk.hex('#50fa7b').bold(k)} ${chalk.dim(label)}`).join(chalk.dim(' | '));
122
159
 
123
160
 
@@ -125,6 +162,7 @@ function footerLine(): string {
125
162
  if (state.paused) badges.push(chalk.yellow.bold('⏸ PAUSED'));
126
163
  if (state.logging) badges.push(chalk.red.bold('● REC'));
127
164
  if (state.activePanel !== 'grid') badges.push(chalk.cyan.bold(`◉ ${state.activePanel.toUpperCase()}`));
165
+ if (state.p2pServerRunning) badges.push(chalk.green.bold(`P2P ${state.p2pBind || '0.0.0.0'}:${state.p2pPort}`));
128
166
  const badgeStr = badges.join(' ');
129
167
 
130
168
  return badgeStr ? `${keyStr} ${badgeStr}` : keyStr;
package/src/live/state.ts CHANGED
@@ -75,21 +75,28 @@ const state = {
75
75
  CPU_ALERT_PCT: 90,
76
76
  TEMP_ALERT_C: 95,
77
77
  alerted: false,
78
- activePanel: 'grid' as 'grid' | 'cpu' | 'mem' | 'gpu' | 'power' | 'battery' | 'thermal' | 'network' | 'packets' | 'tasks' | 'disk' | 'process',
79
- mouseEnabled: true,
80
- PANEL_TABS: [
81
- { id: 'cpu', label: 'CPU', key: '1' },
82
- { id: 'mem', label: 'Memory', key: '2' },
83
- { id: 'gpu', label: 'GPU', key: '3' },
84
- { id: 'power', label: 'Power', key: '4' },
85
- { id: 'battery', label: 'Battery', key: '5' },
86
- { id: 'thermal', label: 'Thermal', key: '6' },
87
- { id: 'network', label: 'Network', key: '7' },
88
- { id: 'packets', label: 'Packets', key: '8' },
89
- { id: 'tasks', label: 'Tasks', key: '9' },
90
- { id: 'disk', label: 'Disk', key: '0' },
91
- { id: 'process', label: 'Process', key: 'P' },
92
- ] as const,
78
+ activePanel: 'grid' as 'grid' | 'cpu' | 'mem' | 'gpu' | 'power' | 'battery' | 'thermal' | 'network' | 'packets' | 'tasks' | 'disk' | 'process' | 'p2p',
79
+ mouseEnabled: true,
80
+ PANEL_TABS: [
81
+ { id: 'cpu', label: 'CPU', key: '1' },
82
+ { id: 'mem', label: 'Memory', key: '2' },
83
+ { id: 'gpu', label: 'GPU', key: '3' },
84
+ { id: 'power', label: 'Power', key: '4' },
85
+ { id: 'battery', label: 'Battery', key: '5' },
86
+ { id: 'thermal', label: 'Thermal', key: '6' },
87
+ { id: 'network', label: 'Network', key: '7' },
88
+ { id: 'packets', label: 'Packets', key: '8' },
89
+ { id: 'tasks', label: 'Tasks', key: '9' },
90
+ { id: 'disk', label: 'Disk', key: '0' },
91
+ { id: 'process', label: 'Process', key: 'P' },
92
+ { id: 'p2p', label: 'P2P', key: 'R' },
93
+ ] as const,
94
+ p2pEvents: [] as { type: string; detail: string; ts: number }[],
95
+ p2pBind: '',
96
+ p2pServer: null as { stop: () => void; peerEventHistory: { type: string; detail: string; ts: number }[] } | null,
97
+ p2pServerRunning: false,
98
+ p2pPort: 9876,
99
+ p2pPassword: 'pyre',
93
100
  };
94
101
 
95
102
  function setStatus(msg: string, ms = 3000) {
package/src/live/types.ts CHANGED
@@ -14,7 +14,7 @@ export interface LiveOptions {
14
14
 
15
15
  export type ExportFormat = 'json' | 'csv' | 'tsv';
16
16
 
17
- type InputMode = null | 'filter' | 'kill' | 'signal' | 'customizer';
17
+ type InputMode = null | 'filter' | 'kill' | 'signal' | 'customizer' | 'p2p';
18
18
 
19
19
  type SortMode = 'cpu' | 'mem' | 'pid' | 'user' | 'command' | 'state' | 'threads' | 'runtime';
20
20
 
@@ -627,7 +627,7 @@ export async function collectPackets(): Promise<PacketData | null> {
627
627
  let topProcesses: { pid: number; command: string; rxBytes: number; txBytes: number }[] = [];
628
628
  let allProcesses: { pid: number; command: string; rxBytes: number; txBytes: number; rxPackets?: number; txPackets?: number }[] = [];
629
629
  try {
630
- const lsof = (await run('lsof -i -n -P -r 1 2>/dev/null | tail -n +2 | awk \'{print $1, $2}\' | sort | uniq -c | sort -rn', '')).trim();
630
+ const lsof = (await run('lsof -i -n -P 2>/dev/null | tail -n +2 | awk \'{print $1, $2}\' | sort | uniq -c | sort -rn', '')).trim();
631
631
  const lsofLines = lsof.split('\n');
632
632
  topProcesses = lsofLines.map(line => {
633
633
  const parts = line.trim().split(/\s+/);