one window for all your local dev servers
mprocs spawns commands in panes. pulse does that plus http probes, port watch, live traffic tap,
and restart cascades on depends_on. all from a single pulse.toml.
$ pulse ╭── pulse · booting · 0 / 5 ready ───────────────────────────╮ │ ◐ postgres 00:00:01 starting :5432 waiting │ │ Ծ robot : "PROC_INIT: postgres" │ │ │ │ ○ redis 00:00:00 waiting -- │ │ -_- cat : "wake me when the db is up" │ │ │ │ ○ api 00:00:00 waiting -- │ │ ... goblin : "can i eat yet" │ │ │ │ ○ web 00:00:00 waiting -- │ │ zZz blob : "will stir when asked" │ ╰────────────────────────────────────────────────────────────╯ j/k nav r restart s stop t tap ? help
$ pulse ╭── pulse · booting · 2 / 5 ready ───────────────────────────╮ │ ● postgres 00:00:08 ready :5432 bound │ │ ᨀ robot : "PROC_ACTIVE: postgres" │ │ │ │ ● redis 00:00:06 ready :6379 bound │ │ =^.^= cat : "acceptable" │ │ │ │ ◐ api 00:00:02 probing... :4000 waiting │ │ @_@ goblin : "tickling port 4000" │ │ │ │ ○ web 00:00:00 waiting -- │ │ zZz blob : "it is not time" │ ╰────────────────────────────────────────────────────────────╯ j/k nav r restart s stop t tap ? help
$ pulse ╭── pulse · stack healthy · 4 / 5 ready ─────────────────────╮ │ ● postgres 00:01:14 ready :5432 bound │ │ ᨀ robot : "SYSTEMS NOMINAL" │ │ │ │ ● redis 00:01:12 ready :6379 bound │ │ =^.^= cat : "purring along" │ │ │ │ ● api 00:01:06 200 · 14ms · 100% :4000 bound │ │ ᨀ goblin : "fed. happy. mostly" │ │ │ │ ● web 00:01:03 200 · 38ms · 99% :3000 bound │ │ ( ᴗ ) blob : "great day for shapes" │ ╰────────────────────────────────────────────────────────────╯ j/k nav r restart s stop t tap ? help
$ pulse ╭── pulse · stack degraded · redis down ─────────────────────╮ │ ● postgres 00:01:23 ready :5432 bound │ │ ᨀ robot : "SYSTEMS NOMINAL" │ │ │ │ ● redis 00:01:21 exit 139 :6379 freed │ │ ✗_✗ cat : "knocked redis off the counter" │ │ │ │ ● api 00:01:15 502 · 1.8s · 64% :4000 bound │ │ @_@ goblin : "api: ECONNREFUSED redis:6379" │ │ │ │ ● web 00:01:12 500 · 2.1s · 61% :3000 bound │ │ (-_-) blob : "shapes trembling" │ ╰────────────────────────────────────────────────────────────╯ j/k nav r restart s stop t tap ? help
$ pulse ╭── pulse · restart cascade · depends_on ────────────────────╮ │ ● postgres 00:01:28 ready :5432 bound │ │ ᨀ robot : "awaiting cluster sync" │ │ │ │ ◐ redis 00:00:02 starting :6379 claimed │ │ =o.o= cat : "i did not do that" │ │ │ │ ◐ api 00:00:01 restarting :4000 waiting │ │ Ծ goblin : "cascade: redis → api" │ │ │ │ ◐ web 00:00:01 restarting :3000 waiting │ │ Ծ.Ծ blob : "re-assembling shape" │ ╰────────────────────────────────────────────────────────────╯ j/k nav r restart s stop t tap ? help
$ pulse ╭── pulse · stack healthy · recovered ───────────────────────╮ │ ● postgres 00:01:42 ready :5432 bound │ │ ᨀ robot : "CLUSTER SYNCED" │ │ │ │ ● redis 00:00:14 ready :6379 bound │ │ =^.^= cat : "back on the counter" │ │ │ │ ● api 00:00:12 200 · 12ms · 100% :4000 bound │ │ ᨀ goblin : "third restart. charmed" │ │ │ │ ● web 00:00:11 200 · 31ms · 98% :3000 bound │ │ ( ᴗ ) blob : "shape stable" │ ╰────────────────────────────────────────────────────────────╯ j/k nav r restart s stop t tap ? help
drop a pulse.toml in your repo root, run pulse, get a terminal ui where every
service has its own log pane, its own health status, and its own tiny ascii sentinel complaining when
things die. edit the config while it's running, the watcher diffs it and respawns what changed.
http probes
per-service poll with latency + rolling 60-sample success rate in the sidebar. status code badge flips red the moment the endpoint stops behaving.
port watch
[service.port] expect = 3000 polls a tcp connect every couple seconds. badge reads
:3000 bound (cyan) or :3000 free (dim).
traffic tap
tiny reverse-proxy in front of your service records the last 500 requests. press t for the
list, T to split on a specific one and see headers + body preview.
dep restart cascade
depends_on = ["postgres"] and web will bounce on its own when postgres
recovers. 1s grace, configurable. no more "restart everything" scripts.
ascii sentinels
one of five species per service — goblin, cat, ghost, robot, blob. they change face on state transitions and drop short lines in the status bar when something interesting happens.
auto-discover
pulse init reads package.json scripts, docker-compose.yml services,
and Procfile entries. drafts a config you can run in ~10 seconds.
a word on the sentinels
three of five species. each has its own voice and its own panic behaviour.
ᨀ goblin "api back up. third time's the charm" ≋ blob "postgres is just vibing at 5432" ᓚ cat "probe latency is weird, i'm watching it"
robot and ghost round out the set. robot is pure ascii for fonts that don't like the other glyphs. i spent more time on the message pools than i should have.
pulse vs the usual suspects
feature pulse mprocs tmux pm2 ───────────────────────────── ───── ────── ──── ─── multiplex processes yes yes yes yes per-service log panes yes yes diy yes http health probe yes no no diy port-in-use detection yes no no no restart cascade on deps yes no no no live traffic tap + replay yes no no no ascii sentinel per service yes no no no single toml config yes yes no yes auto-discover from repo yes no no no
pm2 is production-shaped and it shows in local dev. tmux is a multiplexer, not a process manager. mprocs is still the thing to beat. pulse is the direction i wanted mprocs to go.
install
curl -fsSL https://pulse.frkhd.com/install.sh | sh
detects your os + arch, drops the binary on your PATH. set PULSE_INSTALL_DIR if you want a specific spot.
brew install f4rkh4d/tap/pulse
macos + linuxbrew. pinned to the latest release.
cargo install --git https://github.com/f4rkh4d/pulse
rust 1.75+. grabs the latest main. slowest of the three, but you pick the commit.
mac (apple silicon + intel) and linux (amd64 + arm64). windows is on the list but not here yet.