everything-dev 0.2.1 → 0.3.1
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 +80 -79
- package/src/cli.ts +1491 -1198
- package/src/components/monitor-view.tsx +423 -419
- package/src/config.ts +529 -241
- package/src/contract.ts +381 -364
- package/src/lib/env.ts +83 -65
- package/src/lib/nova.ts +207 -195
- package/src/lib/orchestrator.ts +232 -199
- package/src/lib/process-registry.ts +141 -132
- package/src/lib/process.ts +499 -409
- package/src/lib/resource-monitor/diff.ts +27 -9
- package/src/lib/resource-monitor/platform/darwin.ts +31 -18
- package/src/lib/resource-monitor/snapshot.ts +164 -151
- package/src/plugin.ts +2281 -1841
- package/src/types.ts +182 -83
- package/src/ui/head.ts +37 -26
- package/src/utils/banner.ts +7 -9
- package/src/utils/run.ts +27 -16
- package/src/lib/secrets.ts +0 -29
|
@@ -1,471 +1,475 @@
|
|
|
1
|
+
import { Effect, Logger, LogLevel } from "effect";
|
|
1
2
|
import { Box, render, Text, useApp, useInput } from "ink";
|
|
2
3
|
import { useEffect, useState } from "react";
|
|
3
|
-
import {
|
|
4
|
+
import { getConfig, getProjectRoot } from "../config";
|
|
4
5
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
createSnapshotWithPlatform,
|
|
7
|
+
diffSnapshots,
|
|
8
|
+
type MonitorConfig,
|
|
9
|
+
PlatformLive,
|
|
10
|
+
ResourceMonitor,
|
|
11
|
+
type Snapshot,
|
|
12
|
+
type SnapshotDiff,
|
|
12
13
|
} from "../lib/resource-monitor";
|
|
13
|
-
import {
|
|
14
|
-
import { colors, divider, gradients, icons, frames } from "../utils/theme";
|
|
14
|
+
import { colors, divider, frames, gradients, icons } from "../utils/theme";
|
|
15
15
|
|
|
16
16
|
type Phase = "baseline" | "running" | "stopped";
|
|
17
17
|
|
|
18
18
|
interface MonitorViewProps {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
baseline: Snapshot | null;
|
|
20
|
+
current: Snapshot | null;
|
|
21
|
+
diff: SnapshotDiff | null;
|
|
22
|
+
phase: Phase;
|
|
23
|
+
refreshing: boolean;
|
|
24
|
+
onRefresh: () => void;
|
|
25
|
+
onSnapshot: () => void;
|
|
26
|
+
onExport: () => void;
|
|
27
|
+
onExit: () => void;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function formatBytes(bytes: number): string {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
32
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
33
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function PortRow({
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
port,
|
|
38
|
+
info,
|
|
39
39
|
}: {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
port: number;
|
|
41
|
+
info: { pid: number | null; command: string | null; state: string };
|
|
42
42
|
}) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
const isFree = info.state === "FREE";
|
|
44
|
+
const icon = isFree ? "○" : "●";
|
|
45
|
+
const statusColor = isFree ? "gray" : "#00ff41";
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Box>
|
|
49
|
+
<Text color={statusColor}> {icon} </Text>
|
|
50
|
+
<Text color="#00ffff">:{port.toString().padEnd(5)}</Text>
|
|
51
|
+
{isFree ? (
|
|
52
|
+
<Text color="gray">free</Text>
|
|
53
|
+
) : (
|
|
54
|
+
<Text>
|
|
55
|
+
<Text color="#ff00ff">{info.pid}</Text>
|
|
56
|
+
<Text color="gray"> {info.command}</Text>
|
|
57
|
+
</Text>
|
|
58
|
+
)}
|
|
59
|
+
</Box>
|
|
60
|
+
);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function ProcessRow({
|
|
64
|
-
|
|
64
|
+
proc,
|
|
65
65
|
}: {
|
|
66
|
-
|
|
66
|
+
proc: { pid: number; command: string; rss: number; children: number[] };
|
|
67
67
|
}) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
const childCount = proc.children.length;
|
|
69
|
+
const childText = childCount > 0 ? ` [${childCount}]` : "";
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Box>
|
|
73
|
+
<Text color="#00ffff"> {proc.pid.toString().padEnd(7)}</Text>
|
|
74
|
+
<Text>{proc.command.slice(0, 20).padEnd(20)}</Text>
|
|
75
|
+
<Text color="#ff00ff">{formatBytes(proc.rss).padStart(10)}</Text>
|
|
76
|
+
<Text color="gray">{childText}</Text>
|
|
77
|
+
</Box>
|
|
78
|
+
);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function SnapshotSection({
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
title,
|
|
83
|
+
snapshot,
|
|
84
84
|
}: {
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
title: string;
|
|
86
|
+
snapshot: Snapshot | null;
|
|
87
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
</Box>
|
|
137
|
-
</Box>
|
|
138
|
-
);
|
|
88
|
+
if (!snapshot) {
|
|
89
|
+
return (
|
|
90
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
91
|
+
<Text color="#00ffff"> {title}</Text>
|
|
92
|
+
<Text color="gray"> (waiting for snapshot...)</Text>
|
|
93
|
+
</Box>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const ports = Object.entries(snapshot.ports);
|
|
98
|
+
const boundPorts = ports.filter(([, info]) => info.state !== "FREE").length;
|
|
99
|
+
const totalRss = formatBytes(snapshot.memory.processRss);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
103
|
+
<Text color="#00ffff"> {title}</Text>
|
|
104
|
+
<Text color="gray">{divider(50)}</Text>
|
|
105
|
+
|
|
106
|
+
<Text color="gray">
|
|
107
|
+
{" "}
|
|
108
|
+
PORTS ({boundPorts}/{ports.length} bound)
|
|
109
|
+
</Text>
|
|
110
|
+
{ports.map(([port, info]) => (
|
|
111
|
+
<PortRow key={port} port={parseInt(port, 10)} info={info} />
|
|
112
|
+
))}
|
|
113
|
+
|
|
114
|
+
{snapshot.processes.length > 0 && (
|
|
115
|
+
<>
|
|
116
|
+
<Box marginTop={1}>
|
|
117
|
+
<Text color="gray"> PROCESSES ({snapshot.processes.length})</Text>
|
|
118
|
+
</Box>
|
|
119
|
+
{snapshot.processes.slice(0, 8).map((proc) => (
|
|
120
|
+
<ProcessRow key={proc.pid} proc={proc} />
|
|
121
|
+
))}
|
|
122
|
+
{snapshot.processes.length > 8 && (
|
|
123
|
+
<Text color="gray">
|
|
124
|
+
{" "}
|
|
125
|
+
... and {snapshot.processes.length - 8} more
|
|
126
|
+
</Text>
|
|
127
|
+
)}
|
|
128
|
+
</>
|
|
129
|
+
)}
|
|
130
|
+
|
|
131
|
+
<Box marginTop={1}>
|
|
132
|
+
<Text color="gray"> Memory: {totalRss}</Text>
|
|
133
|
+
</Box>
|
|
134
|
+
</Box>
|
|
135
|
+
);
|
|
139
136
|
}
|
|
140
137
|
|
|
141
138
|
function DiffSection({ diff }: { diff: SnapshotDiff | null }) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
139
|
+
if (!diff) return null;
|
|
140
|
+
|
|
141
|
+
const hasLeaks =
|
|
142
|
+
diff.orphanedProcesses.length > 0 || diff.stillBoundPorts.length > 0;
|
|
143
|
+
const memDelta = diff.memoryDeltaBytes;
|
|
144
|
+
const memSign = memDelta >= 0 ? "+" : "";
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
148
|
+
<Text color={hasLeaks ? "#ff3366" : "#00ff41"}>
|
|
149
|
+
{hasLeaks ? ` ${icons.err} LEAKS DETECTED` : ` ${icons.ok} CLEAN`}
|
|
150
|
+
</Text>
|
|
151
|
+
|
|
152
|
+
{diff.stillBoundPorts.length > 0 && (
|
|
153
|
+
<>
|
|
154
|
+
<Text color="#ff3366"> Still Bound:</Text>
|
|
155
|
+
{diff.stillBoundPorts.map((port) => (
|
|
156
|
+
<Text key={port.port} color="#ff3366">
|
|
157
|
+
{" "}
|
|
158
|
+
:{port.port} ← PID {port.pid}
|
|
159
|
+
</Text>
|
|
160
|
+
))}
|
|
161
|
+
</>
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
{diff.orphanedProcesses.length > 0 && (
|
|
165
|
+
<>
|
|
166
|
+
<Text color="#ff3366"> Orphaned Processes:</Text>
|
|
167
|
+
{diff.orphanedProcesses.map((proc) => (
|
|
168
|
+
<Text key={proc.pid} color="#ff3366">
|
|
169
|
+
{" "}
|
|
170
|
+
{proc.pid} {proc.command}
|
|
171
|
+
</Text>
|
|
172
|
+
))}
|
|
173
|
+
</>
|
|
174
|
+
)}
|
|
175
|
+
|
|
176
|
+
{diff.freedPorts.length > 0 && (
|
|
177
|
+
<Text color="#00ff41"> Freed: {diff.freedPorts.join(", ")}</Text>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
<Text color={memDelta > 50 * 1024 * 1024 ? "#ff3366" : "gray"}>
|
|
181
|
+
Memory Delta: {memSign}
|
|
182
|
+
{formatBytes(memDelta)}
|
|
183
|
+
</Text>
|
|
184
|
+
</Box>
|
|
185
|
+
);
|
|
189
186
|
}
|
|
190
187
|
|
|
191
188
|
function MonitorView({
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
189
|
+
baseline,
|
|
190
|
+
current,
|
|
191
|
+
diff,
|
|
192
|
+
phase,
|
|
193
|
+
refreshing,
|
|
194
|
+
onRefresh,
|
|
195
|
+
onSnapshot,
|
|
196
|
+
onExport,
|
|
197
|
+
onExit,
|
|
201
198
|
}: MonitorViewProps) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
199
|
+
const { exit } = useApp();
|
|
200
|
+
|
|
201
|
+
useInput((input, key) => {
|
|
202
|
+
if (input === "q" || (key.ctrl && input === "c")) {
|
|
203
|
+
onExit();
|
|
204
|
+
exit();
|
|
205
|
+
}
|
|
206
|
+
if (input === "r") onRefresh();
|
|
207
|
+
if (input === "s") onSnapshot();
|
|
208
|
+
if (input === "e") onExport();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
let account = "unknown";
|
|
212
|
+
let configPath = "";
|
|
213
|
+
try {
|
|
214
|
+
const config = getConfig();
|
|
215
|
+
if (config) {
|
|
216
|
+
account = config.account;
|
|
217
|
+
configPath = `${getProjectRoot()}/bos.config.json`;
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
// No config
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const phaseLabel =
|
|
224
|
+
phase === "baseline"
|
|
225
|
+
? "BASELINE"
|
|
226
|
+
: phase === "running"
|
|
227
|
+
? "RUNNING"
|
|
228
|
+
: "STOPPED";
|
|
229
|
+
const phaseColor =
|
|
230
|
+
phase === "baseline" ? "gray" : phase === "running" ? "#00ffff" : "#ff00ff";
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<Box flexDirection="column">
|
|
234
|
+
<Box marginBottom={0}>
|
|
235
|
+
<Text color="#00ffff">{frames.top(56)}</Text>
|
|
236
|
+
</Box>
|
|
237
|
+
<Box>
|
|
238
|
+
<Text>
|
|
239
|
+
{" "}
|
|
240
|
+
{icons.scan} {gradients.cyber("BOS RESOURCE MONITOR")}
|
|
241
|
+
</Text>
|
|
242
|
+
</Box>
|
|
243
|
+
<Box marginBottom={1}>
|
|
244
|
+
<Text color="#00ffff">{frames.bottom(56)}</Text>
|
|
245
|
+
</Box>
|
|
246
|
+
|
|
247
|
+
<Box marginBottom={1}>
|
|
248
|
+
<Text color="gray"> Account: </Text>
|
|
249
|
+
<Text color="#00ffff">{account}</Text>
|
|
250
|
+
</Box>
|
|
251
|
+
{configPath && (
|
|
252
|
+
<Box marginBottom={1}>
|
|
253
|
+
<Text color="gray"> Config: </Text>
|
|
254
|
+
<Text color="gray">{configPath}</Text>
|
|
255
|
+
</Box>
|
|
256
|
+
)}
|
|
257
|
+
|
|
258
|
+
<Box marginBottom={1}>
|
|
259
|
+
<Text color="gray"> Phase: </Text>
|
|
260
|
+
<Text color={phaseColor}>{phaseLabel}</Text>
|
|
261
|
+
{refreshing && <Text color="gray"> (refreshing...)</Text>}
|
|
262
|
+
</Box>
|
|
263
|
+
|
|
264
|
+
<Text>{colors.dim(divider(56))}</Text>
|
|
265
|
+
|
|
266
|
+
{phase === "baseline" && (
|
|
267
|
+
<SnapshotSection title="📊 BASELINE" snapshot={baseline} />
|
|
268
|
+
)}
|
|
269
|
+
|
|
270
|
+
{phase === "running" && (
|
|
271
|
+
<>
|
|
272
|
+
<SnapshotSection title="📊 BASELINE" snapshot={baseline} />
|
|
273
|
+
<SnapshotSection title="🔄 CURRENT" snapshot={current} />
|
|
274
|
+
</>
|
|
275
|
+
)}
|
|
276
|
+
|
|
277
|
+
{phase === "stopped" && (
|
|
278
|
+
<>
|
|
279
|
+
<SnapshotSection title="🔄 AFTER STOP" snapshot={current} />
|
|
280
|
+
<DiffSection diff={diff} />
|
|
281
|
+
</>
|
|
282
|
+
)}
|
|
283
|
+
|
|
284
|
+
<Text>{colors.dim(divider(56))}</Text>
|
|
285
|
+
<Box marginTop={1}>
|
|
286
|
+
<Text color="gray"> [r] refresh [s] snapshot [e] export [q] quit</Text>
|
|
287
|
+
</Box>
|
|
288
|
+
</Box>
|
|
289
|
+
);
|
|
286
290
|
}
|
|
287
291
|
|
|
288
292
|
export interface MonitorViewHandle {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
293
|
+
setPhase: (phase: Phase) => void;
|
|
294
|
+
setBaseline: (snapshot: Snapshot) => void;
|
|
295
|
+
setCurrent: (snapshot: Snapshot) => void;
|
|
296
|
+
setDiff: (diff: SnapshotDiff) => void;
|
|
297
|
+
unmount: () => void;
|
|
294
298
|
}
|
|
295
299
|
|
|
296
300
|
export interface MonitorViewOptions {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
301
|
+
ports?: number[];
|
|
302
|
+
onExit?: () => void;
|
|
303
|
+
onExport?: (data: unknown) => void;
|
|
300
304
|
}
|
|
301
305
|
|
|
302
306
|
const runEffect = <A,>(effect: Effect.Effect<A, unknown, never>): Promise<A> =>
|
|
303
|
-
|
|
307
|
+
effect.pipe(Logger.withMinimumLogLevel(LogLevel.Info), Effect.runPromise);
|
|
304
308
|
|
|
305
309
|
const runSnapshotEffect = (config?: MonitorConfig): Promise<Snapshot> =>
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
310
|
+
createSnapshotWithPlatform(config).pipe(
|
|
311
|
+
Effect.provide(PlatformLive),
|
|
312
|
+
Logger.withMinimumLogLevel(LogLevel.Info),
|
|
313
|
+
Effect.runPromise,
|
|
314
|
+
);
|
|
311
315
|
|
|
312
316
|
export function renderMonitorView(
|
|
313
|
-
|
|
317
|
+
options: MonitorViewOptions = {},
|
|
314
318
|
): MonitorViewHandle {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
319
|
+
let phase: Phase = "baseline";
|
|
320
|
+
let baseline: Snapshot | null = null;
|
|
321
|
+
let current: Snapshot | null = null;
|
|
322
|
+
let diff: SnapshotDiff | null = null;
|
|
323
|
+
let refreshing = false;
|
|
324
|
+
let rerender: (() => void) | null = null;
|
|
325
|
+
let monitor: ResourceMonitor | null = null;
|
|
326
|
+
const config: MonitorConfig | undefined = options.ports
|
|
327
|
+
? { ports: options.ports }
|
|
328
|
+
: undefined;
|
|
329
|
+
|
|
330
|
+
const initMonitor = async () => {
|
|
331
|
+
monitor = await runEffect(ResourceMonitor.createWithPlatform(config));
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
initMonitor();
|
|
335
|
+
|
|
336
|
+
const setPhase = (p: Phase) => {
|
|
337
|
+
phase = p;
|
|
338
|
+
rerender?.();
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const setBaseline = (snap: Snapshot) => {
|
|
342
|
+
baseline = snap;
|
|
343
|
+
rerender?.();
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const setCurrent = (snap: Snapshot) => {
|
|
347
|
+
current = snap;
|
|
348
|
+
if (baseline && phase === "stopped") {
|
|
349
|
+
diff = diffSnapshots(baseline, snap);
|
|
350
|
+
}
|
|
351
|
+
rerender?.();
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const setDiff = (d: SnapshotDiff) => {
|
|
355
|
+
diff = d;
|
|
356
|
+
rerender?.();
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const handleRefresh = async () => {
|
|
360
|
+
if (!monitor) return;
|
|
361
|
+
refreshing = true;
|
|
362
|
+
rerender?.();
|
|
363
|
+
|
|
364
|
+
const snap = await runEffect(monitor.snapshotWithPlatform());
|
|
365
|
+
if (phase === "baseline") {
|
|
366
|
+
baseline = snap;
|
|
367
|
+
} else {
|
|
368
|
+
current = snap;
|
|
369
|
+
if (baseline && phase === "stopped") {
|
|
370
|
+
diff = diffSnapshots(baseline, snap);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
refreshing = false;
|
|
375
|
+
rerender?.();
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const handleSnapshot = async () => {
|
|
379
|
+
if (!monitor) return;
|
|
380
|
+
const snap = await runEffect(monitor.snapshotWithPlatform());
|
|
381
|
+
|
|
382
|
+
if (!baseline) {
|
|
383
|
+
baseline = snap;
|
|
384
|
+
phase = "running";
|
|
385
|
+
} else if (phase === "running") {
|
|
386
|
+
current = snap;
|
|
387
|
+
} else {
|
|
388
|
+
current = snap;
|
|
389
|
+
diff = diffSnapshots(baseline, snap);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
rerender?.();
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const handleExport = async () => {
|
|
396
|
+
if (!monitor) return;
|
|
397
|
+
const exportPath = `.bos/monitor-export-${Date.now()}.json`;
|
|
398
|
+
await runEffect(monitor.export(exportPath));
|
|
399
|
+
options.onExport?.({ path: exportPath });
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const handleExit = () => {
|
|
403
|
+
options.onExit?.();
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
function MonitorViewWrapper() {
|
|
407
|
+
const [, forceUpdate] = useState(0);
|
|
408
|
+
|
|
409
|
+
useEffect(() => {
|
|
410
|
+
rerender = () => forceUpdate((n) => n + 1);
|
|
411
|
+
|
|
412
|
+
handleRefresh();
|
|
413
|
+
|
|
414
|
+
return () => {
|
|
415
|
+
rerender = null;
|
|
416
|
+
};
|
|
417
|
+
}, []);
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<MonitorView
|
|
421
|
+
baseline={baseline}
|
|
422
|
+
current={current}
|
|
423
|
+
diff={diff}
|
|
424
|
+
phase={phase}
|
|
425
|
+
refreshing={refreshing}
|
|
426
|
+
onRefresh={handleRefresh}
|
|
427
|
+
onSnapshot={handleSnapshot}
|
|
428
|
+
onExport={handleExport}
|
|
429
|
+
onExit={handleExit}
|
|
430
|
+
/>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const { unmount } = render(<MonitorViewWrapper />);
|
|
435
|
+
|
|
436
|
+
return { setPhase, setBaseline, setCurrent, setDiff, unmount };
|
|
433
437
|
}
|
|
434
438
|
|
|
435
439
|
export async function runMonitorCli(
|
|
436
|
-
|
|
440
|
+
options: { ports?: number[]; json?: boolean } = {},
|
|
437
441
|
) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
442
|
+
const config: MonitorConfig | undefined = options.ports
|
|
443
|
+
? { ports: options.ports }
|
|
444
|
+
: undefined;
|
|
445
|
+
|
|
446
|
+
if (options.json) {
|
|
447
|
+
const snapshot = await runSnapshotEffect(config);
|
|
448
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const monitor = await runEffect(ResourceMonitor.createWithPlatform(config));
|
|
453
|
+
|
|
454
|
+
const view = renderMonitorView({
|
|
455
|
+
ports: options.ports,
|
|
456
|
+
onExit: () => process.exit(0),
|
|
457
|
+
onExport: (data) =>
|
|
458
|
+
console.log("Exported to:", (data as { path: string }).path),
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const baseline = await runEffect(monitor.setBaselineWithPlatform());
|
|
462
|
+
view.setBaseline(baseline);
|
|
463
|
+
view.setPhase("baseline");
|
|
464
|
+
|
|
465
|
+
const interval = setInterval(async () => {
|
|
466
|
+
const snap = await runEffect(monitor.snapshotWithPlatform());
|
|
467
|
+
view.setCurrent(snap);
|
|
468
|
+
}, 2000);
|
|
469
|
+
|
|
470
|
+
process.on("SIGINT", () => {
|
|
471
|
+
clearInterval(interval);
|
|
472
|
+
view.unmount();
|
|
473
|
+
process.exit(0);
|
|
474
|
+
});
|
|
471
475
|
}
|