metwatch
v0.5.0
Published
Process monitor & manager with a terminal and a local web dashboard — like htop + PM2, works with your existing PM2 apps
Downloads
451
Maintainers
Readme
MetWatch
MetWatch is a process monitor and manager with two dashboards: one in your terminal and one in your browser. Think htop + PM2 in one tool: realtime CPU, memory, disk and network, a full process table, and managed processes with restarts, logs, history and alerts. Already using PM2? Your PM2 apps show up automatically.
bun add -g metwatch
mw web # web dashboard in your browser
mw # terminal dashboard✨ Web dashboard — mw web
A free, modern dashboard for your processes and PM2 apps, running on your own machine. One command, no account, no cloud, no agent to install.

- Overview — live CPU, memory, disk and network with trends, your processes at a glance, recent events and system load
- Processes — MetWatch and PM2 apps together (cluster apps as
×N), search and filters, restart / reload / stop with confirmation - Process detail — CPU and memory charts (live, 1h, 6h, 24h), heap and event-loop lag, restarts, uptime, live logs and events
- Logs — every process in one live stream, with search, per-process and stdout/stderr filters, pause and resume
- Events & alerts — starts, crashes, restarts, crash loops and memory alerts; edit alert rules and Slack / Discord / Telegram / webhook targets right in the dashboard, with a test button
- Start processes — launch the apps from your config or saved with
mw save; new commands too with--allow-exec - System — per-core load, disks, network interfaces, top processes
- Always on —
mw daemon start --webkeeps the dashboard up with the daemon; history and events survive restarts - Light and dark — follows your system theme; works on mobile screens too
- Private by default — listens on
127.0.0.1only, with a private link; reach it on a server through an SSH tunnel
| | PM2 on its own | PM2 + MetWatch |
|---|---|---|
| Web dashboard | PM2 Plus: hosted service, account and pm2 link | mw web: local, free, no account |
| Terminal dashboard | pm2 monit | Full system + processes dashboard with charts and a details view |
| CPU / memory history | PM2 Plus | 24h per process, in both dashboards and mw history |
| Crash & memory alerts | PM2 Plus | Slack, Discord, Telegram or JSON webhooks from your machine |
| Logs | pm2 logs | Time and regex filters, web log viewer with search |
| Start on boot | pm2 startup + pm2 save | mw startup + mw save for MetWatch processes, no admin rights |
| Wait until ready | — | mw wait pm2:web --port 3000 |
| AI agents | — | --json everywhere + MCP server |
PM2 keeps running your apps exactly as before: MetWatch only reads them and can restart, reload or stop them. It never creates, deletes or kills PM2 apps.
🖥️ Terminal dashboard — mw
For servers over SSH or when you live in the terminal: the whole machine and your processes in one screen, sized exactly to your terminal.

Press Enter on any process for its details: CPU and memory charts, heap and event-loop lag, latest logs, and restart / stop / reload.

Features
- Web dashboard —
mw web: overview, processes with actions, per-process charts, logs, events and system pages, light/dark, local only - Live terminal dashboard — CPU (per-core grid, history, busiest processes), memory (history, swap, top consumers), disks (usage, read/write rates), network (mirrored RX/TX chart, TCP connections)
- Process table — sortable, filterable (
/), All / Watched modes, managed processes highlighted with status - Process details — press
Enteron any process for CPU/memory charts, status, logs and restart / stop / reload in one view - Managed processes — like PM2: run scripts with auto-restart and exponential back-off, in the dashboard or in a background daemon that survives closing the terminal
- Managed panel — CPU, memory, uptime and restarts per process; heap and event-loop lag for Node/Bun with
--inspect - Logs — buffered stdout/stderr, live in the dashboards or from the CLI with time and regex filters
- Works with PM2 — apps of a running PM2 daemon show up automatically as
pm2:<app>: both dashboards, logs with filters,wait, zero-downtimereload, alerts and MCP tools, without replacing PM2 - Alerts & history — Slack, Discord, Telegram or JSON webhooks on crashes, crash loops and memory limits, editable from the web dashboard; per-process CPU/memory history for 24h that survives restarts
- Runs on its own —
mw save+mw startupbring your processes back when you sign in or the server boots (Windows, Linux systemd, macOS launchd) - Agent friendly —
--jsonoutput, stable exit codes,mw waitfor readiness, and a built-in MCP server for AI agents and editors - Lightweight — non-overlapping polling, one persistent PowerShell session on Windows instead of a process per poll; the web dashboard only collects data while a browser is open
Requirements
| Requirement | Version | |---|---| | Bun | ≥ 1.3 | | Terminal | Interactive terminal for the dashboard (CLI commands and MCP work anywhere) | | OS | macOS, Linux, Windows |
Install Bun if you haven't already:
curl -fsSL https://bun.sh/install | bashInstallation
Global install (recommended)
bun add -g metwatchThen run from anywhere:
mwRun without installing
bunx metwatchFrom source
git clone https://github.com/victorhhh/metwatch.git
cd metwatch
bun install
bun run startQuick Start
# Open the web dashboard (starts the background daemon if needed)
mw web
# Or the terminal dashboard
mw
# Run a server and watch it in the dashboard (stops when you quit)
mw start server.ts
# Run it in the background instead, then wait until it listens
mw start server.ts --name api --detach
mw wait api --port 3000
# Check on it from anywhere
mw list
mw logs api --since 5m --grep error
# One-shot system report (add --json for scripts and agents)
mw snapshotKeep it running on a server
mw start server.ts --name api --detach # your apps, in the background daemon
mw save # remember them
mw startup --web # start the daemon (and web dashboard) at boot/sign-in
mw web # get the dashboard link any time
# From your computer, open the dashboard of a remote server privately:
ssh -N -L 9615:127.0.0.1:9615 you@server # then open the link printed by `mw web` on the serverUsage
Run mw help <command> for every flag. Commands that talk to managed processes (list, logs, stop, restart, wait) connect to the running MetWatch instance — the dashboard or the daemon — over a local named pipe (Windows) or unix socket (macOS/Linux). Only one instance per user owns managed processes.
Output conventions: most commands accept --json (one JSON document on stdout, errors as {"error": "..."}; logs --json streams one object per line). Human output has no colors. Exit codes: 0 success, 1 failure, 2 usage error.
mw / mw monitor
Open the dashboard. If a daemon is running, the dashboard attaches to it: you see and control its processes, and quitting leaves them running. Without an interactive terminal (pipes, CI, agents) it prints a snapshot instead of failing.
mw web — web dashboard
Opens the web dashboard in your browser: overview with live CPU, memory, disk and network; processes (MetWatch and PM2) with search, filters, restart / reload / stop; a detail page per process with CPU and memory charts (live, or 1h / 6h / 24h from the history), logs and events; a log viewer with search; the event log; and a system page with cores, disks, interfaces and top processes. It follows your system's light/dark theme, with a toggle.
It also lets you start processes (the ones in metwatch.config.json or saved with mw save) and edit alert settings: rules, Slack / Discord / Telegram / JSON targets and a Send test alert button. Changes apply immediately and are saved to the instance's metwatch.config.json.
It starts the daemon if needed (the dashboard reads everything from it), listens on 127.0.0.1:9615 (--port <n>; if that port is taken it picks a free one) and opens the browser (--no-open just prints the link). Ctrl+C stops the web server; the daemon and its processes keep running. If the daemon already serves the dashboard (mw daemon start --web), mw web just prints and opens that link.
| Flag | Description | Default |
|---|---|---|
| --port <n> | Port on 127.0.0.1 | 9615 |
| --no-open | Print the link without opening a browser | off |
| --allow-exec | Allow starting new commands from the dashboard (configured and saved processes can always be started) | off |
It is private by default: it binds to 127.0.0.1, and the printed link carries a session token that is swapped for an HttpOnly cookie. Requests from other sites or host names are rejected. Don't share the link.
On a remote server, keep it that way and use an SSH tunnel (any local port works):
# on the server
mw daemon start --web # or: mw web --no-open
mw web --no-open # prints the link
# on your computer
ssh -N -L 9615:127.0.0.1:9615 you@server
# open the printed link in your browser (use your local port if you picked another)mw start <file>
Launch a script as a managed process. The runtime is inferred from the extension: .ts/.tsx → bun, .js/.mjs/.cjs → node, .py → python, anything else is executed directly.
| Flag | Description | Default |
|---|---|---|
| --name <label> | Process name | basename of <file> |
| --runtime <cmd> | Override the inferred runtime | auto-detected |
| --no-restart | Disable auto-restart on crash | auto-restart enabled |
| --cwd <dir> | Working directory for the child process | current directory |
| --env KEY=VALUE | Environment variable (repeatable) | — |
| --inspect | Node/Bun: heap and event-loop metrics in the Managed panel. Opens a debugger port on localhost. | off |
| --detach, -d | Run in the background daemon (started automatically) and return | off |
| --json | With --detach: print the started process as JSON | off |
mw start server.ts # dashboard hosts it
mw start worker.js --cwd ./workers --env PORT=4000 --detach
mw start main.ts --runtime tsx --inspectmw daemon start | stop | status
The daemon keeps managed processes running without a dashboard. It loads metwatch.config.json from --cwd (default: current directory), starts its managedProcesses, and polls nothing on its own. Errors are written to metwatch-<user>-daemon.log in the temp directory.
| Flag | Description |
|---|---|
| --web | Also serve the web dashboard while the daemon runs (config: "web": { "enabled": true }) — get the link with mw web |
| --port <n> | Web dashboard port (default 9615) |
| --resurrect | Also start the processes saved with mw save |
mw daemon start
mw daemon status --json
mw daemon stop # stops the daemon and its processesmw save / mw startup
mw save remembers the MetWatch processes running now (command, arguments, directory and environment) in MetWatch's private data directory. mw daemon start --resurrect starts them again.
mw startup makes that automatic, without administrator rights:
| OS | What it installs | When it starts |
|---|---|---|
| Windows | MetWatch.cmd in your Startup folder | when you sign in |
| Linux | systemd user service metwatch.service (enabled and started) | with your session; run loginctl enable-linger $USER once to start at boot without signing in |
| macOS | LaunchAgent dev.metwatch.daemon.plist | when you sign in |
mw save
mw startup --web --cwd /srv/shop # the daemon loads /srv/shop/metwatch.config.json
mw startup status
mw startup removePM2 apps are started by PM2 itself (pm2 startup + pm2 save); MetWatch picks them up automatically.
mw list
Managed processes of the running instance. With nothing running, lists the definitions from metwatch.config.json.
NAME STATUS PID RESTARTS UPTIME COMMAND
api running 12345 0 2m 34s bun run api.ts
jobs crashed - 3 - node worker.jsmw logs <name|all>
| Flag | Description | Default |
|---|---|---|
| --follow, -f | Keep streaming new lines | off |
| --lines, -n <n> | Buffered lines to show | 50 |
| --since <duration> | Only newer lines: 30s, 5m, 2h | — |
| --grep <regex> | Only matching lines (case-insensitive) | — |
| --json | NDJSON: {id, stream, line, timestamp} | off |
mw stop <name|all> / mw restart <name|all> / mw reload <name>
Stop (no auto-restart), restart or reload managed processes. reload is a zero-downtime rolling reload for PM2 cluster apps and a restart for everything else. all targets MetWatch's own processes; pm2:all targets every PM2 app.
mw history <name> / mw events [name]
Recorded by the running dashboard or daemon: history shows CPU and memory once per minute for the last 24 hours (--last 6h), events lists starts, stops, crashes, restarts and alerts (--since 1h, --limit). Both accept --json. They are saved in MetWatch's data directory, so restarting the daemon (or the machine) keeps them.
mw wait <name>
Block until a process is ready; exit 0 when ready, 1 if it crashes, stops or times out.
| Flag | Description | Default |
|---|---|---|
| --log <regex> | A log line of the current run matches | — |
| --port <n> | localhost:<n> accepts TCP connections | — |
| --timeout <duration> | Give up after | 60s |
| --json | {ready, name, elapsedMs, reason?, matchedLine?, process} | off |
mw start server.ts --name api --detach && mw wait api --log "listening on" --port 3000mw snapshot
CPU, memory, disks, network, top processes by CPU and memory, and managed processes — once. Uses the running instance if there is one, otherwise samples locally (about a second, since rates need two readings). --top <n> sets processes per ranking (default 10), --json prints everything.
PM2 integration
Already deploying with PM2? Keep it. When a PM2 daemon is running, its apps appear in MetWatch automatically as pm2:<app> — no configuration, and nothing to install in PM2.
pm2 start ecosystem.config.js # deploy as usual
mw # dashboard: pm2 apps in Managed, Processes and Logs
mw list # SOURCE column: metwatch | pm2 (cluster apps as "web ×4")
mw logs pm2:web --since 10m --grep error
mw wait pm2:web --port 3000
mw reload pm2:web # zero-downtime rolling reload (cluster mode)
mw daemon start # history, events and alerts for PM2 apps too- Works without a MetWatch instance:
list,logs,wait,stop,restartandreloadtalk to PM2 directly. History, events and alerts need the dashboard or daemon running. - Metrics: CPU, memory, heap and event-loop latency come from PM2's own probes — no
--inspectneeded. - Light: MetWatch listens to PM2's event bus for logs and state changes and asks for the full process list only occasionally.
- Safe by design: MetWatch never creates, deletes or kills PM2 apps (only restart / stop / reload),
allnever touches PM2 apps, and PM2's process environments — which often hold secrets — are never read into MetWatch's output. - Opt out with
"integrations": { "pm2": false }inmetwatch.config.json. - MetWatch talks to PM2 as a separate program over its local sockets and bundles no PM2 code. PM2 is a trademark of its respective owners.
AI agents & MCP
MetWatch is built to be driven by coding agents as well as people: an agent can start a dev server in the background, wait until it is ready, read its logs when something fails, and check system load — without holding a terminal open.
From a shell (Claude Code, Codex, Cursor agents…): use the CLI with --json and the exit codes above.
Over MCP: mw mcp runs a Model Context Protocol server over stdio.
| Tool | What it does |
|---|---|
| get_system_metrics | CPU, memory, disks, network, top processes, managed processes |
| list_processes | OS processes filtered by name/command/PID, sorted and limited |
| list_managed | Managed processes (or configured ones when nothing runs) |
| get_logs | Recent lines with lines, since and grep |
| wait_until_ready | Wait for a log pattern and/or port; fails fast on crash |
| start_process | Start a process in the daemon (started if needed) |
| stop_process / restart_process / reload_process | Lifecycle control (reload = zero-downtime for PM2 cluster apps) |
| get_process_history | CPU/memory over time with averages and maxima |
| get_events | Crashes, restarts and alerts |
| daemon_status | Whether a dashboard or daemon is running |
Every tool accepts PM2 apps as pm2:<app>.
Add it to your client:
# Claude Code
claude mcp add metwatch -- mw mcp{
"mcpServers": {
"metwatch": { "command": "mw", "args": ["mcp"] }
}
}If mw isn't on the client's PATH (or on Windows with an npm install), use "command": "bunx", "args": ["metwatch", "mcp"].
Safety: by default start_process only starts processes defined in metwatch.config.json, by name. Run mw mcp --allow-exec to let agents start arbitrary commands — they run as your user. Tools can only stop or restart processes MetWatch manages; there is no tool to kill arbitrary PIDs. Responses are size-limited (lines, line length, process counts) to keep agent context small.
Configuration
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 or invalid fields fall back to defaults and print a warning, without crashing.
{
"watchedProcesses": [
{ "name": "node", "label": "Node Apps" },
{ "name": "bun", "label": "Bun Apps" },
{ "name": "python", "label": "Python" }
],
"refreshInterval": 1000,
"maxProcesses": 50
}Fields:
| Field | Type | Default | Description |
|---|---|---|---|
| watchedProcesses | array | [] | Processes to highlight in Watched mode |
| watchedProcesses[].name | string | — | Substring matched against process name (case-insensitive) |
| watchedProcesses[].label | string | same as name | Display label in the Watched view |
| refreshInterval | number (ms) | 2000 | CPU/memory poll interval — minimum 500 ms. Network polls at least every 2 s, disk every 3 s, the process list at 2× this interval. |
| maxProcesses | number | 50 | Top processes kept by CPU and by memory (so both sort orders are meaningful) |
| managedProcesses | array | [] | Processes started by the dashboard or daemon, and startable by name from MCP: { "name", "command", "args"?, "autoRestart"?, "cwd"?, "env"?, "inspect"? } |
| logScrollback | number | 500 | Log lines kept per managed process |
| integrations.pm2 | boolean | true | Show and control PM2 apps as pm2:<app> |
| alerts | object | see below | Webhook alerts for crashes, crash loops and memory limits (also editable from the web dashboard) |
| web.enabled | boolean | false | Serve the web dashboard from the daemon (like mw daemon start --web) |
| web.port | number | 9615 | Port of the daemon's web dashboard on 127.0.0.1 |
| web.allowExec | boolean | false | Allow starting new commands from the daemon's web dashboard |
| panels | object | all true | Initial panel visibility, e.g. { "disk": false } |
Data directory — history, events and saved processes live in %LOCALAPPDATA%\metwatch (Windows), ~/Library/Application Support/metwatch (macOS) or ~/.local/state/metwatch (Linux). Set METWATCH_HOME to use another folder.
Alerts
Alerts are evaluated by the running dashboard or daemon, for MetWatch processes and PM2 apps alike, and listed by mw events.
{
"alerts": {
"webhooks": [
{ "url": "https://hooks.slack.com/services/…", "format": "slack" },
{ "url": "https://discord.com/api/webhooks/…", "format": "discord" },
{ "url": "https://api.telegram.org/bot<token>/sendMessage", "format": "telegram", "chatId": "-1001234567890" },
{ "url": "https://example.com/metwatch", "format": "json" }
],
"crash": true,
"restartLoop": { "restarts": 5, "withinSec": 300 },
"memoryMB": 1024,
"cooldownSec": 300
}
}| Field | Default | Description |
|---|---|---|
| webhooks | [] | POST targets; format is json (full alert), slack ({text}), discord ({content}) or telegram (Bot API sendMessage, needs chatId) |
| crash | true | Alert when a process exits with an error |
| restartLoop | { "restarts": 5, "withinSec": 300 } | Alert when a process crashes this often in the window; null disables |
| memoryMB | null | Alert when a process uses more memory (checked once per minute) |
| cooldownSec | 300 | Minimum time between identical alerts for the same process |
Telegram: create a bot with @BotFather and copy its token. Send the bot a message (or add it to your group), then open https://api.telegram.org/bot<token>/getUpdates and copy chat.id — groups and channels start with -100; public channels can use @channelname (the bot must be an admin there). In the web dashboard, pick Telegram and paste the token and chat ID; the token is stored in the config like any webhook URL, so keep that file private.
Dashboard Panels
The layout adapts to the terminal size: every panel is sized to fit exactly, the Network/Managed row is hidden on short terminals so the process table stays usable, and Managed and Logs only appear when there are managed processes.
| Panel | Toggle key | What it shows |
|---|---|---|
| Header | — | Host, OS, uptime, thread count, RAM, process count, live CPU/memory %, clock |
| CPU | always on | Total load meter + history; per-core grid (meters, compact cells or blocks depending on space); busiest processes; load average on macOS/Linux |
| Memory | always on | RAM and swap/page-file meters, used/available, top memory consumers, usage history chart |
| Disk | d | Per-mount usage meter, used/total, free space and filesystem; read/write rates in the title |
| Network | n | Total ↓RX / ↑TX, TCP connections (Windows/Linux), active interfaces, mirrored history chart (download up, upload down, one scale) |
| Managed | R | One row per managed process and PM2 app (tagged pm2, clusters as ×N): status, PID, CPU, memory, uptime, restarts; heap and event-loop lag from --inspect or PM2's probes |
| Processes | p | Sortable, filterable table; managed processes show their name and status badge |
| Logs | l / Tab (focus) | Interleaved stdout/stderr of managed processes, colored per process, with scroll-back |
| Footer | — | Key hints for the focused panel, confirmations and status messages |
Colors: green / yellow / red always mean load level and are shown next to the number; RX/TX and per-process colors use cyan/magenta/blue so they never read as warnings.
Keyboard Shortcuts
Press ? inside MetWatch for the same list.
Global
| Key | Action |
|---|---|
| q / Ctrl+C | Quit MetWatch |
| ? | Toggle help overlay |
| Tab | Switch focus between Processes and Logs |
| l / Esc | Focus Logs / back to Processes (Esc also clears the filter) |
| d n R p | Toggle Disk / Network / Managed / Processes panel |
Process table
| Key | Action |
|---|---|
| ↑ ↓ / k j | Move selection (it follows the process when the list re-sorts) |
| PgUp PgDn / g G | Page / jump to top or bottom |
| / | Filter by name, command, managed name or PID prefix (Enter keeps, Esc clears) |
| c / m / o | Sort by CPU / memory / cycle CPU → MEM → NAME → PID |
| a / f | All processes / watched processes (config + managed) |
| K (Shift+k) | Kill selected process (confirm with y) |
| Enter | Open process details |
| r / s / u | Restart / stop / reload the selected managed process or PM2 app |
Process details
Charts of CPU and memory (managed apps are sampled in the background, so their charts already have history when opened; other processes start sampling when the view opens), status, PIDs, uptime, restarts, heap and event-loop lag, and the latest logs.
| Key | Action |
|---|---|
| Esc / ← / Backspace | Back to the dashboard |
| r / s / u | Restart / stop / reload (managed processes and PM2 apps) |
| K | Kill (confirm with y) |
Logs (when focused)
| Key | Action |
|---|---|
| ↑ ↓ / PgUp PgDn | Scroll back (pauses following) |
| G / End | Resume following new output |
| ← → | Show all processes or one at a time |
Contributing
Contributions are welcome! Here is how to get started:
- Fork the repository on GitHub
- Clone your fork:
git clone https://github.com/<your-username>/metwatch.git - Create a branch:
git checkout -b feat/my-feature - Install dependencies:
bun install - Make your changes following the conventions below
- Typecheck & test:
bun run typecheckmust exit with 0 errors andbun testmust pass before opening a PR - Open a Pull Request against
mainon victorhhh/metwatch
Note: All pull requests are reviewed and merged exclusively by the maintainer (@elhuguito). Submitting a PR does not guarantee acceptance, but all contributions are genuinely appreciated and reviewed.
Code conventions (summary)
- TypeScript strict mode — no
any, no@ts-ignorewithout a comment - Factory functions over classes — managers use
create*()returning plain objects - React components — widgets are
.tsxfiles usinguseState/useEffecthooks;useInputfor keyboard handling - Event bus — every
bus.on()must store and call its unsubscribe in theuseEffectcleanup - Layer rules — widgets never import from
services/directly; all data via bus + state - No
.then()chains — useasync/awaiteverywhere
For a full architecture guide, see AGENTS.md.
Reporting issues
Please open an issue at github.com/victorhhh/metwatch/issues with:
- OS and terminal emulator
- Bun version (
bun --version) - Steps to reproduce
- Screenshot or error output if applicable
License
MIT © 2026 elhuguito
See LICENSE for the full text.
