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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 elhuguito
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# MetWatch
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/metwatch)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://bun.sh)
|
|
6
|
+
[](#requirements)
|
|
7
|
+
[](https://github.com/victorhhh/metwatch/pulls)
|
|
8
|
+
|
|
9
|
+
**MetWatch** is a terminal-based (TUI) process monitoring and management tool — like `htop` and PM2 combined in a single dashboard. It gives you realtime CPU, memory, disk, and network metrics alongside a full process table and managed process lifecycle (start / restart / stop / logs) without leaving your terminal.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **CPU panel** — overall usage percentage + per-core bars, color-coded by load
|
|
18
|
+
- **Memory panel** — RAM used / free / cached / total with visual gauge
|
|
19
|
+
- **Disk panel** — per-mount usage bars with read/write IO rates
|
|
20
|
+
- **Network panel** — realtime RX/TX throughput graph (last 30 s) + per-interface stats
|
|
21
|
+
- **Process table** — all system processes sorted by CPU or memory; dual-mode (All / Watched)
|
|
22
|
+
- **Managed processes** — launch scripts with `mw start`, get auto-restart with exponential back-off
|
|
23
|
+
- **Runtime metrics** — Node.js / Bun heap, RSS, event-loop lag and GC stats via Chrome DevTools Protocol
|
|
24
|
+
- **Log streaming** — stdout/stderr of managed processes buffered and displayed in the Logs panel
|
|
25
|
+
- **Collapsible panels** — toggle any panel on/off with a single key to reclaim screen space
|
|
26
|
+
- **Config file** — define watched processes and refresh interval in `metwatch.config.json`
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
| Requirement | Version |
|
|
33
|
+
|---|---|
|
|
34
|
+
| [Bun](https://bun.sh) | ≥ 1.0.0 |
|
|
35
|
+
| Terminal | Real TTY required (does not work piped) |
|
|
36
|
+
| OS | macOS, Linux, Windows |
|
|
37
|
+
|
|
38
|
+
Install Bun if you haven't already:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
curl -fsSL https://bun.sh/install | bash
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
### Global install (recommended)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
bun add -g metwatch
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then run from anywhere:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
mw
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Run without installing
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bunx metwatch
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### From source
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/victorhhh/metwatch.git
|
|
70
|
+
cd metwatch
|
|
71
|
+
bun install
|
|
72
|
+
bun run start
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Quick Start
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Open the live dashboard (monitor all system processes)
|
|
81
|
+
mw
|
|
82
|
+
|
|
83
|
+
# Launch a TypeScript server and watch it in the dashboard
|
|
84
|
+
mw start server.ts
|
|
85
|
+
|
|
86
|
+
# Launch a Python script, give it a name, and disable auto-restart
|
|
87
|
+
mw start app.py --name api --no-restart
|
|
88
|
+
|
|
89
|
+
# Launch a Node.js worker with a custom env variable
|
|
90
|
+
mw start worker.js --env PORT=4000
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Usage
|
|
96
|
+
|
|
97
|
+
### `mw monitor`
|
|
98
|
+
|
|
99
|
+
Open the TUI dashboard without launching any managed process. Useful when you just want to observe system metrics.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
mw monitor
|
|
103
|
+
mw # same — monitor is the default command
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### `mw start <file>`
|
|
109
|
+
|
|
110
|
+
Launch a script as a MetWatch-managed process and open the TUI dashboard. The runtime is inferred from the file extension:
|
|
111
|
+
|
|
112
|
+
| Extension | Runtime |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `.ts` / `.tsx` | `bun` |
|
|
115
|
+
| `.js` / `.mjs` / `.cjs` | `node` |
|
|
116
|
+
| `.py` | `python` |
|
|
117
|
+
| other | executed directly |
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
mw start <file> [options]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Options:**
|
|
124
|
+
|
|
125
|
+
| Flag | Description | Default |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `--name <label>` | Display name in the TUI | basename of `<file>` |
|
|
128
|
+
| `--runtime <cmd>` | Override the inferred runtime | auto-detected |
|
|
129
|
+
| `--no-restart` | Disable auto-restart on crash | auto-restart enabled |
|
|
130
|
+
| `--cwd <dir>` | Working directory for the child process | `process.cwd()` |
|
|
131
|
+
| `--env KEY=VALUE` | Set an environment variable (repeatable) | — |
|
|
132
|
+
|
|
133
|
+
**Examples:**
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Run a Bun TypeScript server
|
|
137
|
+
mw start server.ts
|
|
138
|
+
|
|
139
|
+
# Run a Python API with a custom name
|
|
140
|
+
mw start app.py --name api
|
|
141
|
+
|
|
142
|
+
# Run a Node.js worker in a subdirectory with an env var
|
|
143
|
+
mw start worker.js --cwd ./workers --env PORT=4000 --env NODE_ENV=production
|
|
144
|
+
|
|
145
|
+
# Run a binary directly without auto-restart
|
|
146
|
+
mw start ./dist/server --no-restart
|
|
147
|
+
|
|
148
|
+
# Override the runtime explicitly
|
|
149
|
+
mw start main.ts --runtime tsx
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### `mw list`
|
|
155
|
+
|
|
156
|
+
Print the current state of all managed processes to stdout.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
mw list
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Example output:**
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
NAME PID STATUS RESTARTS UPTIME
|
|
166
|
+
api 12345 running 0 2m 34s
|
|
167
|
+
worker 12346 stopped 2 —
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### `mw logs <name>`
|
|
173
|
+
|
|
174
|
+
Print buffered stdout/stderr for a managed process.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
mw logs <name> [options]
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Options:**
|
|
181
|
+
|
|
182
|
+
| Flag | Description | Default |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| `--follow`, `-f` | Tail live output (Ctrl+C to exit) | off |
|
|
185
|
+
| `--lines <n>` | Number of lines to show | 50 |
|
|
186
|
+
|
|
187
|
+
**Examples:**
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Show last 50 lines of logs for "api"
|
|
191
|
+
mw logs api
|
|
192
|
+
|
|
193
|
+
# Tail live logs
|
|
194
|
+
mw logs api --follow
|
|
195
|
+
|
|
196
|
+
# Show last 200 lines
|
|
197
|
+
mw logs api --lines 200
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### `mw stop <name|all>`
|
|
203
|
+
|
|
204
|
+
Gracefully stop a managed process with `SIGTERM`.
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
mw stop <name> # stop a specific process
|
|
208
|
+
mw stop all # stop every managed process
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Examples:**
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
mw stop api
|
|
215
|
+
mw stop all
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Configuration
|
|
221
|
+
|
|
222
|
+
MetWatch reads `metwatch.config.json` from the current working directory at startup. If the file is not found, built-in defaults are used. Bad JSON falls back to defaults without crashing.
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"watchedProcesses": [
|
|
227
|
+
{ "name": "node", "label": "Node Apps" },
|
|
228
|
+
{ "name": "bun", "label": "Bun Apps" },
|
|
229
|
+
{ "name": "python", "label": "Python" }
|
|
230
|
+
],
|
|
231
|
+
"refreshInterval": 1000,
|
|
232
|
+
"maxProcesses": 50
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Fields:**
|
|
237
|
+
|
|
238
|
+
| Field | Type | Default | Description |
|
|
239
|
+
|---|---|---|---|
|
|
240
|
+
| `watchedProcesses` | `array` | `[]` | Processes to highlight in Watched mode |
|
|
241
|
+
| `watchedProcesses[].name` | `string` | — | Substring matched against process name (case-insensitive) |
|
|
242
|
+
| `watchedProcesses[].label` | `string` | same as `name` | Display label in the Watched view |
|
|
243
|
+
| `refreshInterval` | `number` (ms) | `1000` | Poll interval — minimum `250` ms |
|
|
244
|
+
| `maxProcesses` | `number` | `50` | Max processes shown in the All view (sorted by CPU desc) |
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Dashboard Panels
|
|
249
|
+
|
|
250
|
+
| Panel | Toggle key | What it shows |
|
|
251
|
+
|---|---|---|
|
|
252
|
+
| **CPU** | always on | Overall CPU% + per-core bars colored by load |
|
|
253
|
+
| **Memory** | always on | RAM used / free / cached / total with gauge |
|
|
254
|
+
| **Disk** | `d` | Per-mount usage bars + read/write IO rates (MB/s) |
|
|
255
|
+
| **Network** | `n` | Per-interface RX/TX rates + 30-second throughput graph |
|
|
256
|
+
| **Runtime** | `R` | Heap, RSS, event-loop lag, GC stats for managed processes |
|
|
257
|
+
| **Processes** | `p` | Scrollable process table — All or Watched mode |
|
|
258
|
+
| **Logs** | `l` (focus) | Buffered stdout/stderr of managed processes |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Keyboard Shortcuts
|
|
263
|
+
|
|
264
|
+
### Global
|
|
265
|
+
|
|
266
|
+
| Key | Action |
|
|
267
|
+
|---|---|
|
|
268
|
+
| `q` / `Ctrl+C` | Quit MetWatch |
|
|
269
|
+
| `?` | Toggle keybindings help overlay |
|
|
270
|
+
|
|
271
|
+
### Panel toggles
|
|
272
|
+
|
|
273
|
+
| Key | Action |
|
|
274
|
+
|---|---|
|
|
275
|
+
| `d` | Toggle Disk panel |
|
|
276
|
+
| `n` | Toggle Network panel |
|
|
277
|
+
| `R` | Toggle Runtime panel |
|
|
278
|
+
| `p` | Toggle Process panel |
|
|
279
|
+
| `l` | Focus / toggle Logs panel |
|
|
280
|
+
|
|
281
|
+
### Process table
|
|
282
|
+
|
|
283
|
+
| Key | Action |
|
|
284
|
+
|---|---|
|
|
285
|
+
| `a` | All processes mode |
|
|
286
|
+
| `f` | Watched processes mode (from config) |
|
|
287
|
+
| `c` | Sort by CPU usage |
|
|
288
|
+
| `m` | Sort by memory usage |
|
|
289
|
+
| `↑` / `k` | Move selection up |
|
|
290
|
+
| `↓` / `j` | Move selection down |
|
|
291
|
+
| `K` (Shift+k) | Kill selected process (confirm dialog) |
|
|
292
|
+
| `r` | Restart selected managed process |
|
|
293
|
+
| `s` | Stop selected managed process |
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Contributing
|
|
298
|
+
|
|
299
|
+
Contributions are welcome! Here is how to get started:
|
|
300
|
+
|
|
301
|
+
1. **Fork** the repository on GitHub
|
|
302
|
+
2. **Clone** your fork: `git clone https://github.com/<your-username>/metwatch.git`
|
|
303
|
+
3. **Create a branch**: `git checkout -b feat/my-feature`
|
|
304
|
+
4. **Install dependencies**: `bun install`
|
|
305
|
+
5. **Make your changes** following the conventions below
|
|
306
|
+
6. **Typecheck**: `bun run typecheck` — must exit with 0 errors before opening a PR
|
|
307
|
+
7. **Open a Pull Request** against `main` on [victorhhh/metwatch](https://github.com/victorhhh/metwatch/pulls)
|
|
308
|
+
|
|
309
|
+
> **Note:** All pull requests are reviewed and merged exclusively by the maintainer ([@elhuguito](https://github.com/victorhhh)). Submitting a PR does not guarantee acceptance, but all contributions are genuinely appreciated and reviewed.
|
|
310
|
+
|
|
311
|
+
### Code conventions (summary)
|
|
312
|
+
|
|
313
|
+
- **TypeScript strict mode** — no `any`, no `@ts-ignore` without a comment
|
|
314
|
+
- **Factory functions** over classes — use `create*()` returning plain objects
|
|
315
|
+
- **Event bus** — every `bus.on()` must store and call its unsubscribe in `destroy()`
|
|
316
|
+
- **Widgets are stateless renderers** — no domain data, only UI state
|
|
317
|
+
- **Layer rules** — widgets never import from `services/` directly; all data via bus + state
|
|
318
|
+
- **No `.then()` chains** — use `async/await` everywhere
|
|
319
|
+
|
|
320
|
+
For a full architecture guide, see [AGENTS.md](./AGENTS.md).
|
|
321
|
+
|
|
322
|
+
### Reporting issues
|
|
323
|
+
|
|
324
|
+
Please open an issue at [github.com/victorhhh/metwatch/issues](https://github.com/victorhhh/metwatch/issues) with:
|
|
325
|
+
- OS and terminal emulator
|
|
326
|
+
- Bun version (`bun --version`)
|
|
327
|
+
- Steps to reproduce
|
|
328
|
+
- Screenshot or error output if applicable
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## License
|
|
333
|
+
|
|
334
|
+
MIT © 2026 [elhuguito](https://github.com/victorhhh)
|
|
335
|
+
|
|
336
|
+
See [LICENSE](./LICENSE) for the full text.
|
package/bin/mw.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// bin/mw.ts — MetWatch CLI entry point
|
|
4
|
+
//
|
|
5
|
+
// This file is the binary registered in package.json under "bin.mw".
|
|
6
|
+
// It simply delegates to the args router which handles all subcommands.
|
|
7
|
+
//
|
|
8
|
+
// Install globally:
|
|
9
|
+
// bun add -g metwatch (from npm)
|
|
10
|
+
// bun link (from local checkout, for development)
|
|
11
|
+
//
|
|
12
|
+
// Then use:
|
|
13
|
+
// mw Open the TUI
|
|
14
|
+
// mw start server.ts Run + watch a script
|
|
15
|
+
// mw list Print managed process states
|
|
16
|
+
// mw logs api --follow Tail logs
|
|
17
|
+
// mw stop all Stop everything
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
import '../src/cli/args.ts';
|
package/index.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Bootstrap — MetWatch entry point
|
|
3
|
+
//
|
|
4
|
+
// Initialization order:
|
|
5
|
+
// 1. Load config
|
|
6
|
+
// 2. Create blessed screen
|
|
7
|
+
// 3. Create launcher + log-manager + runtime-manager (if managed procs)
|
|
8
|
+
// 4. Wire launcher → runtime-manager (register inspector on start)
|
|
9
|
+
// 5. Build layout (widgets register bus subscriptions)
|
|
10
|
+
// 6. Start metrics + process managers
|
|
11
|
+
// 7. Launcher starts all managed processes
|
|
12
|
+
// 8. Register global keybindings + signal handlers
|
|
13
|
+
// 9. First screen render
|
|
14
|
+
//
|
|
15
|
+
// Teardown (quit):
|
|
16
|
+
// 1. Stop polling managers + runtime-manager
|
|
17
|
+
// 2. Stop all managed processes
|
|
18
|
+
// 3. Destroy layout (widgets unsubscribe)
|
|
19
|
+
// 4. Destroy screen (restore terminal)
|
|
20
|
+
// 5. process.exit(0)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
import { readFileSync, existsSync } from 'fs';
|
|
24
|
+
import { resolve } from 'path';
|
|
25
|
+
|
|
26
|
+
import { createScreen, destroyScreen } from './src/ui/screen.ts';
|
|
27
|
+
import { buildLayout } from './src/ui/layout.ts';
|
|
28
|
+
import { createMetricsManager } from './src/core/metrics-manager.ts';
|
|
29
|
+
import { createProcessManager } from './src/core/process-manager.ts';
|
|
30
|
+
import { createLauncher } from './src/core/launcher.ts';
|
|
31
|
+
import { createLogManager } from './src/core/log-manager.ts';
|
|
32
|
+
import { createRuntimeManager } from './src/core/runtime-manager.ts';
|
|
33
|
+
import { bus } from './src/core/event-bus.ts';
|
|
34
|
+
import {
|
|
35
|
+
DEFAULT_CONFIG,
|
|
36
|
+
type MetWatchConfig,
|
|
37
|
+
type ResolvedConfig,
|
|
38
|
+
} from './src/types/config.types.ts';
|
|
39
|
+
import type { ManagedProcessDef } from './src/types/managed-process.types.ts';
|
|
40
|
+
|
|
41
|
+
// ── Config loading ──────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
function loadConfig(): ResolvedConfig {
|
|
44
|
+
const configPath = resolve(process.cwd(), 'metwatch.config.json');
|
|
45
|
+
|
|
46
|
+
if (!existsSync(configPath)) return { ...DEFAULT_CONFIG };
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
50
|
+
const parsed = JSON.parse(raw) as Partial<MetWatchConfig>;
|
|
51
|
+
return {
|
|
52
|
+
watchedProcesses: parsed.watchedProcesses ?? DEFAULT_CONFIG.watchedProcesses,
|
|
53
|
+
managedProcesses: parsed.managedProcesses ?? DEFAULT_CONFIG.managedProcesses,
|
|
54
|
+
refreshInterval: Math.max(250, parsed.refreshInterval ?? DEFAULT_CONFIG.refreshInterval),
|
|
55
|
+
maxProcesses: parsed.maxProcesses ?? DEFAULT_CONFIG.maxProcesses,
|
|
56
|
+
logScrollback: parsed.logScrollback ?? DEFAULT_CONFIG.logScrollback,
|
|
57
|
+
panels: {
|
|
58
|
+
...DEFAULT_CONFIG.panels,
|
|
59
|
+
...(parsed.panels ?? {}),
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error(`[MetWatch] Failed to parse metwatch.config.json: ${String(err)}`);
|
|
64
|
+
return { ...DEFAULT_CONFIG };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Main ────────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
export async function main(extraDefs: ManagedProcessDef[] = []): Promise<void> {
|
|
71
|
+
const config = loadConfig();
|
|
72
|
+
|
|
73
|
+
// Merge managed process defs: config-defined + CLI-supplied
|
|
74
|
+
const configDefs: ManagedProcessDef[] = config.managedProcesses ?? [];
|
|
75
|
+
const cliNames = new Set(extraDefs.map(d => d.name));
|
|
76
|
+
const mergedDefs = [
|
|
77
|
+
...configDefs.filter(d => !cliNames.has(d.name)),
|
|
78
|
+
...extraDefs,
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const hasManaged = mergedDefs.length > 0;
|
|
82
|
+
|
|
83
|
+
const launcher = hasManaged ? createLauncher(mergedDefs) : null;
|
|
84
|
+
const logManager = hasManaged ? createLogManager(config.logScrollback ?? 500) : null;
|
|
85
|
+
const runtimeManager = hasManaged ? createRuntimeManager(config.refreshInterval) : null;
|
|
86
|
+
|
|
87
|
+
// Wire launcher events to runtime-manager so we get inspector connections
|
|
88
|
+
// automatically whenever a managed process starts or stops.
|
|
89
|
+
const unsubStarted = hasManaged ? bus.on('managed:started', ({ id, pid }) => {
|
|
90
|
+
// The launcher appends --inspect=0; the WS URL is reported on stderr.
|
|
91
|
+
// We receive it via the log:line event below.
|
|
92
|
+
void id; void pid;
|
|
93
|
+
}) : () => undefined;
|
|
94
|
+
|
|
95
|
+
// Parse inspector URL from log lines (Node/Bun print it to stderr on start)
|
|
96
|
+
const inspectorUrls = new Map<string, string>();
|
|
97
|
+
const unsubLogLine = hasManaged ? bus.on('log:line', ({ id, stream, line }) => {
|
|
98
|
+
if (stream !== 'stderr') return;
|
|
99
|
+
// e.g.: "Debugger listening on ws://127.0.0.1:9229/uuid"
|
|
100
|
+
const m = line.match(/Debugger listening on (ws:\/\/[^\s]+)/);
|
|
101
|
+
if (m && m[1] && !inspectorUrls.has(id)) {
|
|
102
|
+
inspectorUrls.set(id, m[1]);
|
|
103
|
+
// Find the pid from the latest managed:started event via launcher
|
|
104
|
+
const proc = launcher?.get(id);
|
|
105
|
+
if (proc?.pid) {
|
|
106
|
+
runtimeManager?.register(id, proc.pid, m[1]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}) : () => undefined;
|
|
110
|
+
|
|
111
|
+
const unsubStopped = hasManaged ? bus.on('managed:stopped', ({ id }) => {
|
|
112
|
+
runtimeManager?.unregister(id);
|
|
113
|
+
inspectorUrls.delete(id);
|
|
114
|
+
}) : () => undefined;
|
|
115
|
+
|
|
116
|
+
const unsubCrashed = hasManaged ? bus.on('managed:crashed', ({ id }) => {
|
|
117
|
+
runtimeManager?.unregister(id);
|
|
118
|
+
inspectorUrls.delete(id);
|
|
119
|
+
}) : () => undefined;
|
|
120
|
+
|
|
121
|
+
const screen = createScreen();
|
|
122
|
+
const layout = buildLayout({ screen, config, launcher, logManager });
|
|
123
|
+
|
|
124
|
+
const metricsManager = createMetricsManager({ intervalMs: config.refreshInterval });
|
|
125
|
+
const processManager = createProcessManager(config);
|
|
126
|
+
|
|
127
|
+
metricsManager.start();
|
|
128
|
+
processManager.start();
|
|
129
|
+
|
|
130
|
+
// Start managed processes after widgets are ready
|
|
131
|
+
launcher?.startAll();
|
|
132
|
+
|
|
133
|
+
// ── Global keybindings ────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
function quit(): void {
|
|
136
|
+
bus.emit('ui:quit', undefined);
|
|
137
|
+
metricsManager.stop();
|
|
138
|
+
processManager.stop();
|
|
139
|
+
runtimeManager?.stop();
|
|
140
|
+
launcher?.stopAll();
|
|
141
|
+
logManager?.destroy();
|
|
142
|
+
unsubStarted();
|
|
143
|
+
unsubLogLine();
|
|
144
|
+
unsubStopped();
|
|
145
|
+
unsubCrashed();
|
|
146
|
+
layout.destroy();
|
|
147
|
+
destroyScreen();
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
screen.key(['q', 'C-c'], quit);
|
|
152
|
+
|
|
153
|
+
// ── Error handling ────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
bus.on('app:error', ({ source, error }) => {
|
|
156
|
+
void source;
|
|
157
|
+
void error;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ── First render ──────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
screen.render();
|
|
163
|
+
|
|
164
|
+
// ── Signal handling ───────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
process.on('SIGTERM', quit);
|
|
167
|
+
process.on('uncaughtException', (err) => {
|
|
168
|
+
destroyScreen();
|
|
169
|
+
console.error('[MetWatch] Uncaught exception:', err);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await main();
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "metwatch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Terminal process monitoring & management tool — like htop + PM2 in your terminal",
|
|
5
|
+
"module": "index.ts",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mw": "./bin/mw.ts"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/",
|
|
13
|
+
"src/",
|
|
14
|
+
"index.ts",
|
|
15
|
+
"metwatch.config.json"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"process",
|
|
19
|
+
"monitor",
|
|
20
|
+
"tui",
|
|
21
|
+
"cli",
|
|
22
|
+
"htop",
|
|
23
|
+
"pm2",
|
|
24
|
+
"bun",
|
|
25
|
+
"terminal",
|
|
26
|
+
"dashboard"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"bun": ">=1.0.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"start": "bun run index.ts",
|
|
33
|
+
"dev": "bun run --watch index.ts",
|
|
34
|
+
"typecheck": "tsc --noEmit"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/bun": "latest",
|
|
38
|
+
"@types/node": "^25.8.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"typescript": "^5"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"blessed": "^0.1.81",
|
|
45
|
+
"blessed-contrib": "^4.11.0",
|
|
46
|
+
"chalk": "^5.6.2",
|
|
47
|
+
"pidusage": "^4.0.1",
|
|
48
|
+
"systeminformation": "^5.31.6"
|
|
49
|
+
}
|
|
50
|
+
}
|