metwatch 0.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/LICENSE +21 -0
- package/README.md +336 -0
- package/bin/mw.ts +20 -0
- package/index.ts +174 -0
- package/metwatch.config.json +9 -0
- package/package.json +50 -0
- package/src/cli/args.ts +115 -0
- package/src/cli/commands/list.ts +77 -0
- package/src/cli/commands/logs.ts +69 -0
- package/src/cli/commands/monitor.ts +12 -0
- package/src/cli/commands/start.ts +124 -0
- package/src/cli/commands/stop.ts +33 -0
- package/src/cli/help.ts +96 -0
- package/src/core/event-bus.ts +113 -0
- package/src/core/launcher.ts +280 -0
- package/src/core/log-manager.ts +116 -0
- package/src/core/metrics-manager.ts +115 -0
- package/src/core/process-manager.ts +79 -0
- package/src/core/runtime-manager.ts +299 -0
- package/src/core/state-manager.ts +88 -0
- package/src/services/disk.service.ts +76 -0
- package/src/services/network.service.ts +131 -0
- package/src/services/process.service.ts +49 -0
- package/src/services/system.service.ts +63 -0
- package/src/types/blessed.d.ts +332 -0
- package/src/types/config.types.ts +77 -0
- package/src/types/managed-process.types.ts +55 -0
- package/src/types/metrics.types.ts +182 -0
- package/src/types/process.types.ts +49 -0
- package/src/ui/layout.ts +318 -0
- package/src/ui/screen.ts +45 -0
- package/src/ui/widgets/cpu.widget.ts +98 -0
- package/src/ui/widgets/disk.widget.ts +134 -0
- package/src/ui/widgets/logs.widget.ts +168 -0
- package/src/ui/widgets/memory.widget.ts +94 -0
- package/src/ui/widgets/network.widget.ts +185 -0
- package/src/ui/widgets/process-table.widget.ts +293 -0
- package/src/ui/widgets/runtime.widget.ts +119 -0
- package/src/utils/formatters.ts +74 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// System Service
|
|
3
|
+
//
|
|
4
|
+
// Pure async functions that fetch raw data from systeminformation and
|
|
5
|
+
// normalize it into MetWatch domain types. This is the ONLY place in the
|
|
6
|
+
// codebase that imports systeminformation. Swap the data source here without
|
|
7
|
+
// touching anything else.
|
|
8
|
+
//
|
|
9
|
+
// All functions are stateless and have no side-effects beyond network/OS I/O.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
import si from 'systeminformation';
|
|
13
|
+
import type { CpuMetrics, MemoryMetrics } from '../types/metrics.types.ts';
|
|
14
|
+
|
|
15
|
+
// Cache CPU model to avoid redundant si.cpu() calls on every tick
|
|
16
|
+
let _cpuModelCache: string | null = null;
|
|
17
|
+
|
|
18
|
+
async function resolveCpuModel(): Promise<string> {
|
|
19
|
+
if (_cpuModelCache) return _cpuModelCache;
|
|
20
|
+
try {
|
|
21
|
+
const info = await si.cpu();
|
|
22
|
+
_cpuModelCache = `${info.manufacturer} ${info.brand}`.trim() || 'Unknown CPU';
|
|
23
|
+
} catch {
|
|
24
|
+
_cpuModelCache = 'Unknown CPU';
|
|
25
|
+
}
|
|
26
|
+
return _cpuModelCache;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── CPU ───────────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export async function fetchCpuMetrics(): Promise<CpuMetrics> {
|
|
32
|
+
const [load, model] = await Promise.all([si.currentLoad(), resolveCpuModel()]);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
usage: Math.round(load.currentLoad * 10) / 10,
|
|
36
|
+
cores: load.cpus.map((core, index) => ({
|
|
37
|
+
index,
|
|
38
|
+
usage: Math.round(core.load * 10) / 10,
|
|
39
|
+
})),
|
|
40
|
+
model,
|
|
41
|
+
coreCount: load.cpus.length,
|
|
42
|
+
timestamp: Date.now(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Memory ────────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export async function fetchMemoryMetrics(): Promise<MemoryMetrics> {
|
|
49
|
+
const mem = await si.mem();
|
|
50
|
+
|
|
51
|
+
const used = mem.active; // active is more accurate than total - free
|
|
52
|
+
const percent = mem.total > 0 ? Math.round((used / mem.total) * 1000) / 10 : 0;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
total: mem.total,
|
|
56
|
+
used,
|
|
57
|
+
free: mem.available,
|
|
58
|
+
percent,
|
|
59
|
+
active: mem.active,
|
|
60
|
+
cached: mem.cached,
|
|
61
|
+
timestamp: Date.now(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Ambient declarations for blessed and blessed-contrib
|
|
3
|
+
//
|
|
4
|
+
// We use a local declaration file instead of @types/blessed because:
|
|
5
|
+
// 1. @types/blessed (0.1.20, 2019) predates many blessed APIs and conflicts
|
|
6
|
+
// with Bun's bundler module resolution.
|
|
7
|
+
// 2. We only need the subset of the API that MetWatch actually uses.
|
|
8
|
+
// 3. This file is authoritative and won't drift when npm packages change.
|
|
9
|
+
//
|
|
10
|
+
// Add declarations here as new blessed features are consumed.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
declare module 'blessed' {
|
|
14
|
+
import { EventEmitter } from 'events';
|
|
15
|
+
|
|
16
|
+
// ── Style objects ─────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
interface BorderStyle {
|
|
19
|
+
type?: 'line' | 'bg';
|
|
20
|
+
fg?: string;
|
|
21
|
+
bg?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ScrollbarStyle {
|
|
25
|
+
ch?: string;
|
|
26
|
+
fg?: string;
|
|
27
|
+
bg?: string;
|
|
28
|
+
track?: { bg?: string; fg?: string };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ElementStyle {
|
|
32
|
+
fg?: string;
|
|
33
|
+
bg?: string;
|
|
34
|
+
bold?: boolean;
|
|
35
|
+
underline?: boolean;
|
|
36
|
+
border?: BorderStyle;
|
|
37
|
+
scrollbar?: ScrollbarStyle;
|
|
38
|
+
label?: Partial<ElementStyle>;
|
|
39
|
+
focus?: Partial<ElementStyle>;
|
|
40
|
+
hover?: Partial<ElementStyle>;
|
|
41
|
+
selected?: Partial<ElementStyle>;
|
|
42
|
+
item?: Partial<ElementStyle>;
|
|
43
|
+
header?: Partial<ElementStyle>;
|
|
44
|
+
cell?: Partial<ElementStyle>;
|
|
45
|
+
bar?: Partial<ElementStyle>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Options ───────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
interface ScreenOptions {
|
|
51
|
+
smartCSR?: boolean;
|
|
52
|
+
fastCSR?: boolean;
|
|
53
|
+
fullUnicode?: boolean;
|
|
54
|
+
dockBorders?: boolean;
|
|
55
|
+
title?: string;
|
|
56
|
+
debug?: boolean;
|
|
57
|
+
ignoreLocked?: string[];
|
|
58
|
+
autoPadding?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface ElementOptions {
|
|
62
|
+
parent?: BlessedElement | BlessedScreen;
|
|
63
|
+
top?: number | string;
|
|
64
|
+
left?: number | string;
|
|
65
|
+
width?: number | string;
|
|
66
|
+
height?: number | string;
|
|
67
|
+
right?: number | string;
|
|
68
|
+
bottom?: number | string;
|
|
69
|
+
content?: string;
|
|
70
|
+
label?: string;
|
|
71
|
+
tags?: boolean;
|
|
72
|
+
border?: BorderStyle | { type: string };
|
|
73
|
+
style?: ElementStyle;
|
|
74
|
+
scrollable?: boolean;
|
|
75
|
+
alwaysScroll?: boolean;
|
|
76
|
+
scrollbar?: ScrollbarStyle | boolean;
|
|
77
|
+
keys?: boolean;
|
|
78
|
+
vi?: boolean;
|
|
79
|
+
mouse?: boolean;
|
|
80
|
+
hidden?: boolean;
|
|
81
|
+
padding?: number | { top?: number; right?: number; bottom?: number; left?: number };
|
|
82
|
+
shrink?: boolean;
|
|
83
|
+
focusable?: boolean;
|
|
84
|
+
input?: boolean;
|
|
85
|
+
clickable?: boolean;
|
|
86
|
+
interactive?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface BoxOptions extends ElementOptions {}
|
|
90
|
+
|
|
91
|
+
interface ListOptions extends ElementOptions {
|
|
92
|
+
items?: string[];
|
|
93
|
+
invertSelected?: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface ListTableOptions extends ElementOptions {
|
|
97
|
+
data?: string[][];
|
|
98
|
+
rows?: string[][];
|
|
99
|
+
pad?: number;
|
|
100
|
+
noCellBorders?: boolean;
|
|
101
|
+
fillCellBorders?: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface LogOptions extends ElementOptions {
|
|
105
|
+
scrollback?: number;
|
|
106
|
+
scrollOnInput?: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface ProgressBarOptions extends ElementOptions {
|
|
110
|
+
orientation?: 'horizontal' | 'vertical';
|
|
111
|
+
pch?: string;
|
|
112
|
+
filled?: number;
|
|
113
|
+
value?: number;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface QuestionOptions extends ElementOptions {}
|
|
117
|
+
interface MessageOptions extends ElementOptions {}
|
|
118
|
+
|
|
119
|
+
// ── Elements ──────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
class BlessedElement extends EventEmitter {
|
|
122
|
+
width: number;
|
|
123
|
+
height: number;
|
|
124
|
+
top: number;
|
|
125
|
+
left: number;
|
|
126
|
+
content: string;
|
|
127
|
+
hidden: boolean;
|
|
128
|
+
|
|
129
|
+
setContent(text: string): void;
|
|
130
|
+
getContent(): string;
|
|
131
|
+
setText(text: string): void;
|
|
132
|
+
setLabel(text: string): void;
|
|
133
|
+
show(): void;
|
|
134
|
+
hide(): void;
|
|
135
|
+
toggle(): void;
|
|
136
|
+
focus(): void;
|
|
137
|
+
render(): void;
|
|
138
|
+
destroy(): void;
|
|
139
|
+
key(keys: string | string[], handler: (ch: string, key: KeyEvent) => void): void;
|
|
140
|
+
onceKey(keys: string | string[], handler: (ch: string, key: KeyEvent) => void): void;
|
|
141
|
+
unkey(keys: string | string[], handler: (ch: string, key: KeyEvent) => void): void;
|
|
142
|
+
on(event: string, handler: (...args: unknown[]) => void): this;
|
|
143
|
+
append(child: BlessedElement): void;
|
|
144
|
+
remove(child: BlessedElement): void;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
class BlessedScreen extends BlessedElement {
|
|
148
|
+
cols: number;
|
|
149
|
+
rows: number;
|
|
150
|
+
focused: BlessedElement;
|
|
151
|
+
|
|
152
|
+
render(): void;
|
|
153
|
+
destroy(): void;
|
|
154
|
+
key(keys: string | string[], handler: (ch: string, key: KeyEvent) => void): void;
|
|
155
|
+
append(child: BlessedElement): void;
|
|
156
|
+
remove(child: BlessedElement): void;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
class Box extends BlessedElement {
|
|
160
|
+
constructor(options?: BoxOptions);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
class Log extends BlessedElement {
|
|
164
|
+
constructor(options?: LogOptions);
|
|
165
|
+
log(text: string): void;
|
|
166
|
+
add(text: string): void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
class ListTable extends BlessedElement {
|
|
170
|
+
constructor(options?: ListTableOptions);
|
|
171
|
+
setData(data: string[][]): void;
|
|
172
|
+
setRows(rows: string[][]): void;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
class ProgressBar extends BlessedElement {
|
|
176
|
+
constructor(options?: ProgressBarOptions);
|
|
177
|
+
setProgress(amount: number): void;
|
|
178
|
+
progress(amount: number): void;
|
|
179
|
+
reset(): void;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
class Question extends BlessedElement {
|
|
183
|
+
constructor(options?: QuestionOptions);
|
|
184
|
+
ask(question: string, callback: (err: Error | null, value: boolean) => void): void;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
class Message extends BlessedElement {
|
|
188
|
+
constructor(options?: MessageOptions);
|
|
189
|
+
display(text: string, time: number, callback?: () => void): void;
|
|
190
|
+
error(text: string, time: number, callback?: () => void): void;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── Keyboard event ────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
interface KeyEvent {
|
|
196
|
+
name: string;
|
|
197
|
+
ctrl: boolean;
|
|
198
|
+
meta: boolean;
|
|
199
|
+
shift: boolean;
|
|
200
|
+
sequence: string;
|
|
201
|
+
full: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Factory functions ─────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
function screen(options?: ScreenOptions): BlessedScreen;
|
|
207
|
+
function box(options?: BoxOptions): Box;
|
|
208
|
+
function log(options?: LogOptions): Log;
|
|
209
|
+
function listtable(options?: ListTableOptions): ListTable;
|
|
210
|
+
function progressbar(options?: ProgressBarOptions): ProgressBar;
|
|
211
|
+
function question(options?: QuestionOptions): Question;
|
|
212
|
+
function message(options?: MessageOptions): Message;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
declare module 'blessed-contrib' {
|
|
216
|
+
import type { BlessedElement, BlessedScreen, ElementOptions } from 'blessed';
|
|
217
|
+
|
|
218
|
+
// ── Grid ──────────────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
interface GridOptions {
|
|
221
|
+
rows: number;
|
|
222
|
+
cols: number;
|
|
223
|
+
screen: BlessedScreen;
|
|
224
|
+
hideBorder?: boolean;
|
|
225
|
+
color?: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
type WidgetConstructor<T> = new (options: ElementOptions) => T;
|
|
229
|
+
|
|
230
|
+
class grid {
|
|
231
|
+
constructor(options: GridOptions);
|
|
232
|
+
set<T extends BlessedElement>(
|
|
233
|
+
row: number,
|
|
234
|
+
col: number,
|
|
235
|
+
rowSpan: number,
|
|
236
|
+
colSpan: number,
|
|
237
|
+
widget: WidgetConstructor<T> | ((options: ElementOptions) => T),
|
|
238
|
+
options: Record<string, unknown>
|
|
239
|
+
): T;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ── Gauge ─────────────────────────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
interface GaugeOptions extends ElementOptions {
|
|
245
|
+
label?: string;
|
|
246
|
+
stroke?: string;
|
|
247
|
+
fill?: string;
|
|
248
|
+
percent?: number;
|
|
249
|
+
showLabel?: boolean;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
class gauge extends BlessedElement {
|
|
253
|
+
constructor(options?: GaugeOptions);
|
|
254
|
+
setPercent(percent: number): void;
|
|
255
|
+
setData(data: { percent: number; label?: string }): void;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── GaugeList (stacked gauges) ────────────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
interface GaugeListOptions extends ElementOptions {
|
|
261
|
+
label?: string;
|
|
262
|
+
gauges?: Array<{ percent: number; stroke?: string; label?: string }>;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
class gaugeList extends BlessedElement {
|
|
266
|
+
constructor(options?: GaugeListOptions);
|
|
267
|
+
setGauges(gauges: Array<{ percent: number; stroke?: string; label?: string }>): void;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── Table ─────────────────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
interface TableOptions extends ElementOptions {
|
|
273
|
+
keys?: boolean;
|
|
274
|
+
vi?: boolean;
|
|
275
|
+
mouse?: boolean;
|
|
276
|
+
interactive?: boolean;
|
|
277
|
+
label?: string;
|
|
278
|
+
columnSpacing?: number;
|
|
279
|
+
columnWidth?: number[];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
class table extends BlessedElement {
|
|
283
|
+
constructor(options?: TableOptions);
|
|
284
|
+
setData(data: { headers: string[]; data: string[][] }): void;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ── Log ───────────────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
interface ContribLogOptions extends ElementOptions {
|
|
290
|
+
label?: string;
|
|
291
|
+
bufferLength?: number;
|
|
292
|
+
scrollbar?: { ch?: string };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
class log extends BlessedElement {
|
|
296
|
+
constructor(options?: ContribLogOptions);
|
|
297
|
+
log(text: string): void;
|
|
298
|
+
add(text: string): void;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ── Line chart ────────────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
interface LineSeries {
|
|
304
|
+
title?: string;
|
|
305
|
+
x: string[];
|
|
306
|
+
y: number[];
|
|
307
|
+
style?: { line?: string };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
interface LineOptions extends ElementOptions {
|
|
311
|
+
label?: string;
|
|
312
|
+
showLegend?: boolean;
|
|
313
|
+
legend?: { width?: number };
|
|
314
|
+
xLabelPadding?: number;
|
|
315
|
+
xPadding?: number;
|
|
316
|
+
numYLabels?: number;
|
|
317
|
+
showNthLabel?: number;
|
|
318
|
+
wholeNumbersOnly?: boolean;
|
|
319
|
+
minY?: number;
|
|
320
|
+
maxY?: number;
|
|
321
|
+
style?: {
|
|
322
|
+
line?: string | string[];
|
|
323
|
+
text?: string;
|
|
324
|
+
baseline?: string;
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
class line extends BlessedElement {
|
|
329
|
+
constructor(options?: LineOptions);
|
|
330
|
+
setData(data: LineSeries | LineSeries[]): void;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Config Types
|
|
3
|
+
// Shape of metwatch.config.json. Loaded once at startup; changes require
|
|
4
|
+
// restart. The config is intentionally flat and simple — no nested schemas.
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
import type { ManagedProcessDef } from './managed-process.types.ts';
|
|
8
|
+
import type { PanelName } from '../core/event-bus.ts';
|
|
9
|
+
|
|
10
|
+
export interface WatchedProcess {
|
|
11
|
+
/** Process name to match (substring match against ProcessInfo.name) */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Optional display label override shown in the Watched view */
|
|
14
|
+
label?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Controls which panels are visible by default.
|
|
19
|
+
* Missing keys default to true (all panels shown on first launch).
|
|
20
|
+
* User can toggle panels with keyboard shortcuts at runtime.
|
|
21
|
+
*/
|
|
22
|
+
export type PanelVisibility = Partial<Record<PanelName, boolean>>;
|
|
23
|
+
|
|
24
|
+
export interface MetWatchConfig {
|
|
25
|
+
/**
|
|
26
|
+
* Processes to highlight in the "Watched" view (f key).
|
|
27
|
+
* If empty, the watched view will show nothing until the user adds entries.
|
|
28
|
+
*/
|
|
29
|
+
watchedProcesses: WatchedProcess[];
|
|
30
|
+
/**
|
|
31
|
+
* Polling interval in milliseconds.
|
|
32
|
+
* Minimum: 250ms. Default: 1000ms.
|
|
33
|
+
*/
|
|
34
|
+
refreshInterval: number;
|
|
35
|
+
/**
|
|
36
|
+
* Maximum number of processes to display in the "All" view.
|
|
37
|
+
* Sorted by CPU descending before truncation. Default: 50.
|
|
38
|
+
*/
|
|
39
|
+
maxProcesses: number;
|
|
40
|
+
/**
|
|
41
|
+
* Processes that MetWatch should launch and manage (like PM2).
|
|
42
|
+
* These are started automatically on TUI boot.
|
|
43
|
+
* CLI flags (--run) are merged in at runtime and take precedence.
|
|
44
|
+
*/
|
|
45
|
+
managedProcesses: ManagedProcessDef[];
|
|
46
|
+
/**
|
|
47
|
+
* Number of log lines to keep in the circular buffer per managed process.
|
|
48
|
+
* Default: 500.
|
|
49
|
+
*/
|
|
50
|
+
logScrollback: number;
|
|
51
|
+
/**
|
|
52
|
+
* Which panels are visible on startup.
|
|
53
|
+
* All panels default to visible if this key is omitted.
|
|
54
|
+
* Example: { "disk": false, "runtime": false }
|
|
55
|
+
*/
|
|
56
|
+
panels: PanelVisibility;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Resolved config — all fields guaranteed (defaults merged in) */
|
|
60
|
+
export type ResolvedConfig = Required<MetWatchConfig>;
|
|
61
|
+
|
|
62
|
+
export const DEFAULT_CONFIG: ResolvedConfig = {
|
|
63
|
+
watchedProcesses: [],
|
|
64
|
+
refreshInterval: 1000,
|
|
65
|
+
maxProcesses: 50,
|
|
66
|
+
managedProcesses: [],
|
|
67
|
+
logScrollback: 500,
|
|
68
|
+
panels: {
|
|
69
|
+
cpu: true,
|
|
70
|
+
memory: true,
|
|
71
|
+
disk: true,
|
|
72
|
+
network: true,
|
|
73
|
+
runtime: true,
|
|
74
|
+
processes: true,
|
|
75
|
+
logs: true,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Managed Process Types
|
|
3
|
+
//
|
|
4
|
+
// A "managed process" is a child process that MetWatch launched and owns —
|
|
5
|
+
// distinct from the passive observation of system processes (ProcessInfo).
|
|
6
|
+
//
|
|
7
|
+
// Lifecycle:
|
|
8
|
+
// ManagedProcessDef → created at config / CLI parse time (static intent)
|
|
9
|
+
// ManagedProcess → runtime state wrapping the def (mutable, in launcher)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export type ManagedProcessStatus =
|
|
13
|
+
| 'running'
|
|
14
|
+
| 'stopped'
|
|
15
|
+
| 'crashed'
|
|
16
|
+
| 'restarting';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Static definition of a process MetWatch should launch and manage.
|
|
20
|
+
* Comes from metwatch.config.json `managedProcesses[]` or CLI flags.
|
|
21
|
+
*/
|
|
22
|
+
export interface ManagedProcessDef {
|
|
23
|
+
/** Display label shown in the TUI. Defaults to command basename. */
|
|
24
|
+
name: string;
|
|
25
|
+
/** Executable to run: "bun", "node", "python", etc. */
|
|
26
|
+
command: string;
|
|
27
|
+
/** Arguments passed to the executable. */
|
|
28
|
+
args: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Restart the process automatically on crash.
|
|
31
|
+
* Uses exponential back-off: 1s → 2s → 4s → 8s → 16s → 30s (cap).
|
|
32
|
+
* Counter resets if the process lives > 10s consecutively.
|
|
33
|
+
* Default: true
|
|
34
|
+
*/
|
|
35
|
+
autoRestart: boolean;
|
|
36
|
+
/** Working directory for the child process. Defaults to process.cwd(). */
|
|
37
|
+
cwd?: string;
|
|
38
|
+
/** Additional environment variables merged into process.env. */
|
|
39
|
+
env?: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Runtime state for a managed process. Extends the static def with live data.
|
|
44
|
+
* Mutated by the launcher; never mutated by widgets or managers.
|
|
45
|
+
*/
|
|
46
|
+
export interface ManagedProcess extends ManagedProcessDef {
|
|
47
|
+
pid: number | null;
|
|
48
|
+
status: ManagedProcessStatus;
|
|
49
|
+
/** Total number of times this process has been restarted (crash or manual). */
|
|
50
|
+
restarts: number;
|
|
51
|
+
/** Unix ms when the current (or most recent) run started. */
|
|
52
|
+
startedAt: number | null;
|
|
53
|
+
/** Exit code of the most recent run. null if still running. */
|
|
54
|
+
exitCode: number | null;
|
|
55
|
+
}
|