pyre-cli 5.0.0 → 6.0.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 +197 -255
- package/dist/index.js +1454 -281
- package/package.json +1 -1
- package/src/anomalies.ts +102 -0
- package/src/formatters/index.ts +3 -3
- package/src/formatters/output.ts +355 -21
- package/src/formatters/render.ts +316 -97
- package/src/formatters/types.ts +33 -17
- package/src/history.ts +57 -13
- package/src/index.ts +226 -13
- package/src/live/export.ts +64 -7
- package/src/live/input.ts +84 -39
- package/src/live/render.ts +175 -118
- package/src/live/state.ts +76 -70
- package/src/live/types.ts +10 -2
- package/src/monitors/collectors.ts +142 -35
- package/src/monitors/smc.ts +60 -12
- package/src/monitors/types.ts +1 -0
- package/src/p2p/server.test.ts +2 -2
- package/src/sparkline.ts +82 -13
- package/src/state/config.ts +130 -0
- package/Formula/pyre.rb +0 -19
package/src/live/render.ts
CHANGED
|
@@ -6,11 +6,28 @@
|
|
|
6
6
|
* alert checker. All state variables are imported from
|
|
7
7
|
* `state.ts`.
|
|
8
8
|
*/
|
|
9
|
+
import { execFile } from 'node:child_process';
|
|
9
10
|
import chalk from 'chalk';
|
|
10
|
-
import { formatTable, formatGraphs, gridColumns, THEMES, type ThemeName, panel } from '../formatters/index.js';
|
|
11
|
+
import { formatTable, formatGraphs, gridColumns, THEMES, type ThemeName, panel, fitVisible } from '../formatters/index.js';
|
|
12
|
+
import { detectAnomalies } from '../anomalies.js';
|
|
11
13
|
import { state, setStatus, SIGNAL_OPTIONS, getToggleKey } from './state.js';
|
|
12
14
|
import type { StatsData } from '../monitors/index.js';
|
|
13
15
|
|
|
16
|
+
function formatTempForUnit(c: number): string {
|
|
17
|
+
if (state.tempUnit === 'f') {
|
|
18
|
+
const f = c * (9 / 5) + 32;
|
|
19
|
+
return `${f.toFixed(1)}°F`;
|
|
20
|
+
}
|
|
21
|
+
return `${c.toFixed(1)}°C`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function applyTempUnit(text: string): string {
|
|
25
|
+
if (state.tempUnit === 'f') {
|
|
26
|
+
return text.replace(/([\d.]+)°C \/ ([\d.]+)°F/g, '$2°F');
|
|
27
|
+
}
|
|
28
|
+
return text.replace(/([\d.]+)°C \/ ([\d.]+)°F/g, '$1°C');
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
// --- render caching -----------------------------------------------------------
|
|
15
32
|
|
|
16
33
|
let _cachedTable = '';
|
|
@@ -35,6 +52,7 @@ function tableCacheParams(): string {
|
|
|
35
52
|
state.currentTheme,
|
|
36
53
|
state.activePanel,
|
|
37
54
|
state.treeView ? '1' : '0',
|
|
55
|
+
state.history.version,
|
|
38
56
|
JSON.stringify(state.visiblePanels),
|
|
39
57
|
].join('|');
|
|
40
58
|
}
|
|
@@ -47,29 +65,21 @@ function graphsCacheParams(): string {
|
|
|
47
65
|
].join('|');
|
|
48
66
|
}
|
|
49
67
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
*/
|
|
56
|
-
function processRowBudget(): number {
|
|
57
|
-
let overlayRows = 0;
|
|
58
|
-
if (state.inputMode === 'customizer') overlayRows = state.CUSTOMIZER_OPTIONS.length + 3;
|
|
59
|
-
else if (state.inputMode === 'signal') overlayRows = 6;
|
|
60
|
-
else if (state.inputMode === 'filter' || state.inputMode === 'kill' || state.inputMode === 'p2p') overlayRows = 3;
|
|
61
|
-
|
|
62
|
-
if (state.activePanel === 'process') {
|
|
63
|
-
return Math.max(10, state.termHeight - 12 - overlayRows);
|
|
64
|
-
}
|
|
68
|
+
function processRowBudget(): number {
|
|
69
|
+
let overlayRows = 0;
|
|
70
|
+
if (state.inputMode === 'customizer') overlayRows = state.CUSTOMIZER_OPTIONS.length + 3;
|
|
71
|
+
else if (state.inputMode === 'signal') overlayRows = 6;
|
|
72
|
+
else if (state.inputMode === 'filter' || state.inputMode === 'kill' || state.inputMode === 'p2p') overlayRows = 3;
|
|
65
73
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const cardRows = Math.ceil(visibleCards / columns);
|
|
69
|
-
const reserved = 4 + cardRows * 8 + 8 + (state.showGraphs ? 11 : 0) + 5 + overlayRows;
|
|
70
|
-
return Math.max(5, state.termHeight - reserved);
|
|
74
|
+
if (state.activePanel === 'process') {
|
|
75
|
+
return Math.max(10, state.termHeight - 12 - overlayRows);
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
const reserved = 16 + overlayRows;
|
|
79
|
+
return Math.max(4, state.termHeight - reserved);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
73
83
|
function p2pPanelLines(): { title: string; body: string[] } {
|
|
74
84
|
const events = state.p2pServer ? state.p2pServer.peerEventHistory : state.p2pEvents;
|
|
75
85
|
const body: string[] = [];
|
|
@@ -103,28 +113,33 @@ function renderCustomizerOverlay(): string {
|
|
|
103
113
|
chalk.bgCyan.black.bold(' UI CUSTOMIZER (Use ↑/↓ or J/K to navigate, Enter/Space to toggle, Esc to exit) '),
|
|
104
114
|
];
|
|
105
115
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
state.CUSTOMIZER_OPTIONS.forEach((opt, idx) => {
|
|
117
|
+
const isSelected = idx === state.customizerIndex;
|
|
118
|
+
const prefix = isSelected ? chalk.yellow('▶ ') : ' ';
|
|
119
|
+
|
|
120
|
+
if (opt === 'Theme') {
|
|
121
|
+
lines.push(`${prefix}${opt}: ${chalk.bold.green(state.currentTheme)} [${themesList.join(', ')}]`);
|
|
122
|
+
} else if (opt === 'Graph Mode') {
|
|
123
|
+
lines.push(`${prefix}${opt}: ${chalk.bold.green(state.graphMode)} [spark, bar]`);
|
|
124
|
+
} else if (opt === 'Splash Screen') {
|
|
125
|
+
const status = state.splashEnabled ? chalk.green('[ON]') : chalk.red('[OFF]');
|
|
126
|
+
lines.push(`${prefix}${opt}: ${status}`);
|
|
127
|
+
} else if (opt === 'Splash Color') {
|
|
128
|
+
lines.push(`${prefix}${opt}: ${chalk.bold.green(state.splashColorScheme)} [fire, ocean, forest, purple, monochrome]`);
|
|
129
|
+
} else if (opt === 'Splash Animation') {
|
|
130
|
+
lines.push(`${prefix}${opt}: ${chalk.bold.green(state.splashAnimation)} [classic, wave, sparks]`);
|
|
131
|
+
} else if (opt === 'Notifications') {
|
|
132
|
+
const status = state.notificationsEnabled ? chalk.green('[ON]') : chalk.red('[OFF]');
|
|
133
|
+
lines.push(`${prefix}${opt}: ${status}`);
|
|
134
|
+
} else if (opt === 'Temperature Unit') {
|
|
135
|
+
lines.push(`${prefix}${opt}: ${chalk.bold.green(state.tempUnit.toUpperCase())} [C, F]`);
|
|
136
|
+
} else {
|
|
137
|
+
const toggleKey = getToggleKey(opt);
|
|
138
|
+
const isVisible = toggleKey ? (toggleKey === 'tree' ? state.treeView : state.visiblePanels[toggleKey] !== false) : true;
|
|
139
|
+
const status = isVisible ? chalk.green('[VISIBLE]') : chalk.red('[HIDDEN]');
|
|
140
|
+
lines.push(`${prefix}${opt}: ${status}`);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
128
143
|
|
|
129
144
|
return lines.map(l => ` ${l}`).join('\n');
|
|
130
145
|
}
|
|
@@ -167,29 +182,48 @@ function writeFrame(lines: string[]) {
|
|
|
167
182
|
|
|
168
183
|
function render() {
|
|
169
184
|
if (!state.lastData) return;
|
|
185
|
+
const rows = state.termHeight || 24;
|
|
186
|
+
const cols = state.termWidth || 80;
|
|
187
|
+
if (cols < 50 || rows < 15) {
|
|
188
|
+
const msg = chalk.yellow(`pyre ${rows}x${cols} too small. Resize terminal to >= 50x20.`);
|
|
189
|
+
const centered = Math.max(0, Math.floor((rows - 2) / 2));
|
|
190
|
+
const out: string[] = [];
|
|
191
|
+
for (let i = 0; i < centered; i++) out.push('');
|
|
192
|
+
out.push(msg.padEnd(cols));
|
|
193
|
+
while (out.length < rows) out.push('');
|
|
194
|
+
writeFrame(out);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
170
197
|
const lines: string[] = [];
|
|
171
198
|
const theme = THEMES[state.currentTheme] || THEMES.default;
|
|
172
199
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
200
|
+
const params = tableCacheParams();
|
|
201
|
+
const anomalies = detectAnomalies(state.lastData, state.history);
|
|
202
|
+
const anomalyCacheKey = anomalies.map(a => `${a.metric}:${a.zScore.toFixed(2)}`).join(',');
|
|
203
|
+
const cacheParams = params + '|' + anomalyCacheKey;
|
|
204
|
+
const tableStr = _tableDataDirty || state.lastData !== _cachedTableData || cacheParams !== _cachedTableParams
|
|
205
|
+
? (() => {
|
|
206
|
+
const out = formatTable(state.lastData, {
|
|
207
|
+
width: state.termWidth,
|
|
208
|
+
sortBy: state.sortMode,
|
|
209
|
+
filter: state.processFilter || undefined,
|
|
210
|
+
processLimit: processRowBudget(),
|
|
211
|
+
theme: state.currentTheme,
|
|
212
|
+
visible: state.visiblePanels,
|
|
213
|
+
treeView: state.treeView,
|
|
214
|
+
activePanel: state.activePanel,
|
|
215
|
+
anomalies,
|
|
216
|
+
tempUnit: state.tempUnit,
|
|
217
|
+
history: state.history,
|
|
218
|
+
graphMode: state.graphMode,
|
|
219
|
+
});
|
|
220
|
+
_cachedTableData = state.lastData;
|
|
221
|
+
_cachedTableParams = cacheParams;
|
|
222
|
+
_cachedTable = applyTempUnit(out);
|
|
223
|
+
_tableDataDirty = false;
|
|
224
|
+
return _cachedTable;
|
|
225
|
+
})()
|
|
226
|
+
: _cachedTable;
|
|
193
227
|
|
|
194
228
|
const bodyLines: string[] = [];
|
|
195
229
|
if (state.activePanel === 'p2p') {
|
|
@@ -206,8 +240,8 @@ function render() {
|
|
|
206
240
|
const out = formatGraphs(state.history, state.termWidth, state.currentTheme, state.graphMode);
|
|
207
241
|
_cachedGraphsVersion = state.history.version;
|
|
208
242
|
_cachedGraphsParams = graphsParams;
|
|
209
|
-
_cachedGraphs = out;
|
|
210
|
-
return
|
|
243
|
+
_cachedGraphs = applyTempUnit(out);
|
|
244
|
+
return _cachedGraphs;
|
|
211
245
|
})()
|
|
212
246
|
: _cachedGraphs;
|
|
213
247
|
|
|
@@ -216,54 +250,57 @@ function render() {
|
|
|
216
250
|
bodyLines.push(...graphsStr.split('\n'));
|
|
217
251
|
}
|
|
218
252
|
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
253
|
+
const footerLine1 = footerLine()[0] || '';
|
|
254
|
+
const footerLine2 = footerLine()[1] || '';
|
|
255
|
+
const footerLine3 = footerLine()[2] || ' ';
|
|
256
|
+
const footerLines = [footerLine1, footerLine2, footerLine3];
|
|
222
257
|
|
|
223
258
|
if (state.inputMode === 'customizer') {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
if (bodyLines.length >
|
|
245
|
-
bodyLines.length =
|
|
259
|
+
bodyLines.push('');
|
|
260
|
+
bodyLines.push(...renderCustomizerOverlay().split('\n'));
|
|
261
|
+
} else if (state.inputMode === 'filter') {
|
|
262
|
+
bodyLines.push(chalk.cyan(` Filter processes: ${state.inputBuffer}_`));
|
|
263
|
+
} else if (state.inputMode === 'kill') {
|
|
264
|
+
bodyLines.push(chalk.cyan(` Kill PID: ${state.inputBuffer}_ (enter to confirm, esc to cancel)`));
|
|
265
|
+
} else if (state.inputMode === 'signal') {
|
|
266
|
+
const sigIdx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer as typeof state.SIGNAL_OPTIONS[number]);
|
|
267
|
+
const sigList = state.SIGNAL_OPTIONS.map((s, i) => i === sigIdx ? chalk.yellow(s) : chalk.dim(s)).join(' ');
|
|
268
|
+
bodyLines.push(chalk.cyan(` Signal: ${state.inputBuffer}_`));
|
|
269
|
+
bodyLines.push(chalk.dim(` ${sigList}`));
|
|
270
|
+
bodyLines.push(chalk.dim(' ↑/↓ to select, Enter to confirm, Esc to cancel'));
|
|
271
|
+
} else if (state.inputMode === 'p2p') {
|
|
272
|
+
bodyLines.push(chalk.cyan(` P2P password (${state.p2pPort}): ${state.inputBuffer}_ (enter to start, esc to cancel)`));
|
|
273
|
+
} else if (state.statusMessage) {
|
|
274
|
+
bodyLines.push(` ${state.statusMessage}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const footerHeight = 3;
|
|
278
|
+
const targetBodyHeight = Math.max(1, state.termHeight - footerHeight);
|
|
279
|
+
if (bodyLines.length > targetBodyHeight) {
|
|
280
|
+
bodyLines.length = targetBodyHeight;
|
|
246
281
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
frameLines.length = state.termHeight;
|
|
282
|
+
const frameLines = [...bodyLines];
|
|
283
|
+
while (frameLines.length < targetBodyHeight) {
|
|
284
|
+
frameLines.push('');
|
|
251
285
|
}
|
|
252
|
-
|
|
286
|
+
frameLines.push(...footerLines);
|
|
287
|
+
const fittedLines = frameLines.map(l => fitVisible(l, cols));
|
|
288
|
+
writeFrame(fittedLines);
|
|
253
289
|
}
|
|
254
290
|
|
|
255
|
-
function footerLine(): string {
|
|
291
|
+
function footerLine(): string[] {
|
|
256
292
|
const row1: [string, string][] = [
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
293
|
+
['q', 'quit'],
|
|
294
|
+
['c', 'customize UI'],
|
|
295
|
+
['←/→', 'tab'],
|
|
296
|
+
['1-9', 'panels'],
|
|
297
|
+
['g', state.showGraphs ? 'hide graphs' : 'show graphs'],
|
|
298
|
+
['b', `graph:${state.graphMode}`],
|
|
299
|
+
['t', state.treeView ? 'flat' : 'tree'],
|
|
300
|
+
['d', state.detailed ? 'basic' : 'detailed'],
|
|
301
|
+
['T', `temp:${state.tempUnit}`],
|
|
302
|
+
['p', state.paused ? 'resume' : 'pause'],
|
|
303
|
+
];
|
|
267
304
|
const row2: [string, string][] = [
|
|
268
305
|
['s', `sort:${state.sortMode}`],
|
|
269
306
|
['/', 'filter'],
|
|
@@ -284,22 +321,42 @@ function footerLine(): string {
|
|
|
284
321
|
if (state.logging) badges.push(chalk.red.bold('● REC'));
|
|
285
322
|
if (state.activePanel !== 'grid') badges.push(chalk.cyan.bold(`◉ ${state.activePanel.toUpperCase()}`));
|
|
286
323
|
if (state.p2pServerRunning) badges.push(chalk.green.bold(`P2P ${state.p2pBind || '0.0.0.0'}:${state.p2pPort}`));
|
|
287
|
-
const badgeStr = badges.join(' ');
|
|
324
|
+
const badgeStr = badges.join(' ') || ' ';
|
|
288
325
|
|
|
289
|
-
return
|
|
326
|
+
return [str1, str2, badgeStr];
|
|
290
327
|
}
|
|
291
328
|
|
|
329
|
+
function sendNotification(title: string, message: string) {
|
|
330
|
+
if (!state.notificationsEnabled) return;
|
|
331
|
+
execFile('osascript', ['-e', `display notification "${message}" with title "${title}"`], (err) => {
|
|
332
|
+
if (err) {
|
|
333
|
+
// Silently ignore notification failures (e.g., terminal not in focus, no notification permission)
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
292
338
|
function checkAlerts(data: StatsData) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
339
|
+
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|
|
340
|
+
const hot = data.cpu.usage >= state.CPU_ALERT_PCT || (temp !== null && temp >= state.TEMP_ALERT_C);
|
|
341
|
+
const anomalies = detectAnomalies(data, state.history);
|
|
342
|
+
const hasCriticalAnomaly = anomalies.some(a => a.severity === 'critical');
|
|
343
|
+
const hasWarningAnomaly = anomalies.some(a => a.severity === 'warning');
|
|
344
|
+
const anomalyTriggered = hasCriticalAnomaly || hasWarningAnomaly;
|
|
345
|
+
|
|
346
|
+
if ((hot || anomalyTriggered) && !state.alerted) {
|
|
347
|
+
state.alerted = true;
|
|
348
|
+
process.stdout.write('\x07');
|
|
349
|
+
const reasons: string[] = [];
|
|
350
|
+
if (data.cpu.usage >= state.CPU_ALERT_PCT) reasons.push(`CPU at ${data.cpu.usage}%`);
|
|
351
|
+
if (temp !== null && temp >= state.TEMP_ALERT_C) reasons.push(`Temp at ${formatTempForUnit(temp)}`);
|
|
352
|
+
for (const a of anomalies) {
|
|
353
|
+
reasons.push(`${a.metric} anomaly (σ=${a.zScore.toFixed(1)})`);
|
|
354
|
+
}
|
|
355
|
+
setStatus(chalk.red(`⚠ Alert: ${reasons.join('; ')}`), 5000);
|
|
356
|
+
sendNotification('pyre Alert', reasons.join('; '));
|
|
357
|
+
} else if (!hot && !anomalyTriggered) {
|
|
358
|
+
state.alerted = false;
|
|
359
|
+
}
|
|
302
360
|
}
|
|
303
|
-
}
|
|
304
361
|
|
|
305
362
|
export { render, footerLine, renderCustomizerOverlay, checkAlerts, processRowBudget, invalidateTableCache, invalidateFrame };
|
package/src/live/state.ts
CHANGED
|
@@ -11,6 +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
15
|
|
|
15
16
|
export const SIGNAL_OPTIONS = ['SIGTERM', 'SIGKILL', 'SIGINT', 'SIGHUP', 'SIGSTOP', 'SIGCONT'] as const;
|
|
16
17
|
|
|
@@ -18,54 +19,61 @@ import { type SplashColorScheme, type SplashAnimation } from '../splash.js';
|
|
|
18
19
|
|
|
19
20
|
export { type SplashColorScheme, type SplashAnimation };
|
|
20
21
|
|
|
22
|
+
const config = readConfig();
|
|
23
|
+
|
|
21
24
|
const state = {
|
|
22
25
|
intervalHandle: null as NodeJS.Timeout | null,
|
|
23
26
|
uiIntervalHandle: null as NodeJS.Timeout | null,
|
|
24
27
|
running: false,
|
|
25
28
|
paused: false,
|
|
26
|
-
detailed:
|
|
27
|
-
interval:
|
|
28
|
-
showGraphs:
|
|
29
|
-
graphMode:
|
|
29
|
+
detailed: config.detailed,
|
|
30
|
+
interval: config.interval,
|
|
31
|
+
showGraphs: config.showGraphs,
|
|
32
|
+
graphMode: config.graphMode as GraphMode,
|
|
30
33
|
exportFormat: 'json' as ExportFormat,
|
|
34
|
+
tempUnit: 'c' as 'c' | 'f',
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
customizerIndex: 0,
|
|
36
|
+
exportDir: config.exportDir,
|
|
37
|
+
currentTheme: config.theme as ThemeName,
|
|
38
|
+
visiblePanels: {
|
|
39
|
+
cpu: config.visiblePanels.cpu,
|
|
40
|
+
mem: config.visiblePanels.mem,
|
|
41
|
+
gpu: config.visiblePanels.gpu,
|
|
42
|
+
power: config.visiblePanels.power,
|
|
43
|
+
battery: config.visiblePanels.battery,
|
|
44
|
+
thermal: config.visiblePanels.thermal,
|
|
45
|
+
network: config.visiblePanels.network,
|
|
46
|
+
packets: config.visiblePanels.packets,
|
|
47
|
+
tasks: config.visiblePanels.tasks,
|
|
48
|
+
disk: config.visiblePanels.disk,
|
|
49
|
+
process: config.visiblePanels.process,
|
|
50
|
+
} as VisibleItems,
|
|
51
|
+
logging: false,
|
|
52
|
+
logStream: null as fs.WriteStream | null,
|
|
53
|
+
statusMessage: '',
|
|
54
|
+
statusTimer: null as NodeJS.Timeout | null,
|
|
55
|
+
lastData: null as StatsData | null,
|
|
56
|
+
history: new History(40),
|
|
57
|
+
keypressHandler: null as ((str: string, key: readline.Key) => void) | null,
|
|
58
|
+
termWidth: process.stdout.columns || 80,
|
|
59
|
+
termHeight: process.stdout.rows || 24,
|
|
60
|
+
sortMode: config.sortMode as SortMode,
|
|
61
|
+
processFilter: '',
|
|
62
|
+
inputMode: null as InputMode,
|
|
63
|
+
inputBuffer: '',
|
|
64
|
+
targetPid: '',
|
|
65
|
+
selectedSignal: 'SIGTERM' as typeof SIGNAL_OPTIONS[number],
|
|
66
|
+
treeView: config.treeView,
|
|
67
|
+
SIGNAL_OPTIONS,
|
|
68
|
+
customizerIndex: 0,
|
|
66
69
|
CUSTOMIZER_OPTIONS: [
|
|
67
70
|
'Theme',
|
|
68
71
|
'Graph Mode',
|
|
72
|
+
'Splash Screen',
|
|
73
|
+
'Splash Color',
|
|
74
|
+
'Splash Animation',
|
|
75
|
+
'Notifications',
|
|
76
|
+
'Temperature Unit',
|
|
69
77
|
'Toggle CPU',
|
|
70
78
|
'Toggle Memory',
|
|
71
79
|
'Toggle GPU',
|
|
@@ -78,39 +86,37 @@ const state = {
|
|
|
78
86
|
'Toggle Disk',
|
|
79
87
|
'Toggle Processes',
|
|
80
88
|
'Toggle Tree View',
|
|
81
|
-
'Splash Screen',
|
|
82
|
-
'Splash Color',
|
|
83
|
-
'Splash Animation',
|
|
84
89
|
],
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
90
|
+
CPU_ALERT_PCT: config.cpuAlertPct,
|
|
91
|
+
TEMP_ALERT_C: config.tempAlertC,
|
|
92
|
+
notificationsEnabled: config.notificationsEnabled,
|
|
93
|
+
alerted: false,
|
|
94
|
+
activePanel: 'grid' as 'grid' | 'cpu' | 'mem' | 'gpu' | 'power' | 'battery' | 'thermal' | 'network' | 'packets' | 'tasks' | 'disk' | 'process' | 'p2p',
|
|
95
|
+
mouseEnabled: config.mouseEnabled,
|
|
96
|
+
PANEL_TABS: [
|
|
97
|
+
{ id: 'cpu', label: 'CPU', key: '1' },
|
|
98
|
+
{ id: 'mem', label: 'Memory', key: '2' },
|
|
99
|
+
{ id: 'gpu', label: 'GPU', key: '3' },
|
|
100
|
+
{ id: 'power', label: 'Power', key: '4' },
|
|
101
|
+
{ id: 'battery', label: 'Battery', key: '5' },
|
|
102
|
+
{ id: 'thermal', label: 'Thermal', key: '6' },
|
|
103
|
+
{ id: 'network', label: 'Network', key: '7' },
|
|
104
|
+
{ id: 'packets', label: 'Packets', key: '8' },
|
|
105
|
+
{ id: 'tasks', label: 'Tasks', key: '9' },
|
|
106
|
+
{ id: 'disk', label: 'Disk', key: '0' },
|
|
107
|
+
{ id: 'process', label: 'Process', key: 'P' },
|
|
108
|
+
{ id: 'p2p', label: 'P2P', key: 'R' },
|
|
109
|
+
] as const,
|
|
110
|
+
p2pEvents: [] as { type: string; detail: string; ts: number }[],
|
|
111
|
+
p2pBind: '',
|
|
112
|
+
p2pServer: null as { stop: () => void; peerEventHistory: { type: string; detail: string; ts: number }[] } | null,
|
|
113
|
+
p2pServerRunning: false,
|
|
114
|
+
p2pPort: config.p2pPort,
|
|
115
|
+
p2pPassword: config.p2pPassword,
|
|
116
|
+
splashEnabled: config.splashEnabled,
|
|
117
|
+
splashColorScheme: config.splashColorScheme as SplashColorScheme,
|
|
118
|
+
splashAnimation: config.splashAnimation as SplashAnimation,
|
|
119
|
+
};
|
|
114
120
|
|
|
115
121
|
function setStatus(msg: string, ms = 3000) {
|
|
116
122
|
state.statusMessage = msg;
|
package/src/live/types.ts
CHANGED
|
@@ -5,15 +5,20 @@
|
|
|
5
5
|
* export format enum, and the input-mode union used
|
|
6
6
|
* throughout the interactive session.
|
|
7
7
|
*/
|
|
8
|
+
import type { ThemeName } from '../formatters/themes.js';
|
|
9
|
+
|
|
8
10
|
export interface LiveOptions {
|
|
9
11
|
interval: number;
|
|
10
12
|
detailed?: boolean;
|
|
11
13
|
exportDir?: string;
|
|
12
14
|
autoLog?: boolean;
|
|
13
15
|
theme?: ThemeName;
|
|
16
|
+
alertCpu?: number;
|
|
17
|
+
alertTemp?: number;
|
|
18
|
+
tempUnit?: 'c' | 'f';
|
|
14
19
|
}
|
|
15
20
|
|
|
16
|
-
export type ExportFormat = 'json' | 'csv' | 'tsv';
|
|
21
|
+
export type ExportFormat = 'json' | 'csv' | 'tsv' | 'html' | 'md';
|
|
17
22
|
|
|
18
23
|
type InputMode = null | 'filter' | 'kill' | 'signal' | 'customizer' | 'p2p';
|
|
19
24
|
|
|
@@ -21,4 +26,7 @@ type SortMode = 'cpu' | 'mem' | 'pid' | 'user' | 'command' | 'state' | 'threads'
|
|
|
21
26
|
|
|
22
27
|
type GraphMode = 'spark' | 'bar';
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
type ActivePanel = 'grid' | 'cpu' | 'mem' | 'gpu' | 'power' | 'battery' | 'thermal' | 'network' | 'packets' | 'tasks' | 'disk' | 'process' | 'p2p';
|
|
30
|
+
|
|
31
|
+
export type { InputMode, SortMode, GraphMode, ActivePanel, ThemeName };
|
|
32
|
+
|