A lightweight Zig TUI command runner for starting, watching, stopping, and restarting multiple development processes from one terminal.
Key Features | Usage | Install | Configure | FAQ
runmux keeps a local development stack visible and controllable without opening one terminal per service. Define processes in JSON or TOML, pick a profile, and run them through an interactive TUI or a plain log stream for CI.
Run runmux run for a split-pane TUI with process state on the left and live logs on the right. Start, stop, restart, filter, search, and inspect everything from one screen.
Use depends_on to express process order. A dependency is ready when it is running with no health check, has passed its health check, or has exited successfully.
| Process | Rule | Result |
|---|---|---|
db |
no dependencies | starts immediately |
api |
depends_on: ["db"] |
waits for db readiness |
web |
depends_on: ["api"] |
waits for api readiness |
Attach health checks to processes and tune restart behavior per process or through defaults.
{
"name": "api",
"cmd": "zig build run -- serve",
"depends_on": ["db"],
"restart": {
"policy": "on_failure",
"max_restarts": 3,
"delay_ms": 1000
},
"health": {
"argv": ["/bin/sh", "-c", "curl -fsS http://localhost:8080/health"],
"interval_ms": 1000,
"timeout_ms": 5000,
"retries": 30
}
}Use --plain when a TUI is not appropriate. Logs are timestamped and prefixed with process name and stream.
runmux run --plain --config runmux.json12:00:01 [api:stdout] listening on :8080
12:00:02 [web:stderr] warning: rebuilt bundle
Keep bounded in-memory logs, strip unsafe escape sequences by default, filter by process or stream, search by substring, and optionally write one log file per process.
runmux run --log-dir logsThe default file is runmux.json, but files ending in .toml are accepted with the same schema.
runmux check --config runmux.toml
runmux list --config runmux.toml --profile devPOSIX child processes run in a dedicated process group so stop and kill target the process tree. On Windows, runmux uses Job Objects when available, with direct-process termination as a fallback.
zig build
zig build run -- init
zig build run -- check
zig build run -- runAfter installing the binary:
runmux init
runmux check
runmux runrunmux run [--config runmux.json] [--profile dev] [--plain] [--log-dir logs] [--exit-on-critical-failure] [--theme dark|light|mono]
runmux check [--config runmux.json] [--profile dev]
runmux init [--config runmux.json] [--force]
runmux list [--config runmux.json] [--profile dev]
runmux --help
runmux --version| Command | Purpose |
|---|---|
run |
start autostart processes and open the TUI |
run --plain |
run without the TUI and print prefixed logs |
check |
validate the selected JSON or TOML config |
init |
write a sample config, refusing to overwrite unless --force is set |
list |
print resolved processes, dependencies, restart policy, logs, and health checks |
| Option | Description |
|---|---|
--config PATH |
read a config file other than runmux.json |
--profile NAME |
select a profile from the config |
--plain |
use the non-interactive log runner |
--log-dir DIR |
write one log file per process |
--exit-on-critical-failure |
stop all processes when a critical process fails |
| `--theme dark | light |
| Key | Action |
|---|---|
Up / Down, j / k |
select process |
Enter |
start or stop selected process |
r |
restart selected process |
a / x |
start all or stop all |
Tab |
switch between selected-process logs and all logs |
/ |
search logs by substring |
i |
send one line to selected process stdin |
s |
cycle stream filter |
f |
filter logs to selected process |
u |
clear log filters |
p |
pause or resume log follow |
? |
toggle help overlay |
q, Ctrl+C |
stop children and quit |
runmux targets Zig 0.16.0.
git clone https://github.com/ydah/runmux.git
cd runmux
zig build -Doptimize=ReleaseFast
sudo install -m 0755 zig-out/bin/runmux /usr/local/bin/runmuxzig build
zig build test
zig build run -- --helpDefault path: runmux.json.
{
"version": 1,
"default_profile": "dev",
"defaults": {
"cwd": ".",
"shell": true,
"autostart": true,
"restart": {
"policy": "never",
"max_restarts": 0,
"delay_ms": 1000
},
"log": {
"max_lines": 1000,
"strip_ansi": true
}
},
"profiles": [
{
"name": "dev",
"processes": [
{
"name": "clock",
"cmd": "while true; do date; sleep 1; done"
},
{
"name": "stderr-demo",
"cmd": "while true; do echo warn >&2; sleep 2; done"
},
{
"name": "manual",
"cmd": "echo manual process; sleep 5",
"depends_on": ["clock"],
"dependency_failure": "ignore",
"health": {
"argv": ["/bin/sh", "-c", "exit 0"],
"interval_ms": 1000,
"timeout_ms": 5000,
"retries": 3
},
"autostart": false
}
]
}
]
}version = 1
default_profile = "dev"
[defaults]
cwd = "."
shell = true
autostart = true
[defaults.restart]
policy = "never"
max_restarts = 0
delay_ms = 1000
[defaults.log]
max_lines = 1000
strip_ansi = true
[[profiles]]
name = "dev"
[[profiles.processes]]
name = "clock"
cmd = "while true; do date; sleep 1; done"
[[profiles.processes]]
name = "manual"
cmd = "echo manual process; sleep 5"
depends_on = ["clock"]
dependency_failure = "ignore"
autostart = false
[profiles.processes.health]
argv = ["/bin/sh", "-c", "exit 0"]
interval_ms = 1000
timeout_ms = 5000
retries = 3| Field | Description |
|---|---|
name |
unique process name in the profile |
cmd / argv |
exactly one command form is required |
cwd |
working directory, defaulting to . |
env |
object of environment variables |
shell |
run cmd through the shell when true |
autostart |
start automatically when run begins |
depends_on |
process names that must become ready first |
dependency_failure |
ignore, stop, or restart |
critical |
marks a process for --exit-on-critical-failure |
restart |
never, on_failure, or always with limits and delay |
log |
max_lines and strip_ansi |
health |
cmd or argv, plus interval, timeout, and retries |
runmux run --profile dev
runmux run --profile test --plain{
"name": "api",
"cmd": "zig build run -- serve",
"critical": true
}runmux run --exit-on-critical-failure{
"name": "server",
"argv": ["zig", "build", "run", "--", "serve"],
"shell": false
}When shell is false, cmd must be a single executable path with no arguments. Use argv for direct execution with arguments.
The default file is runmux.json in the current directory.
runmux run --config path/to/runmux.jsonRun check first, then inspect dependency readiness.
runmux check
runmux listProcesses with health checks are not considered ready until the health check passes.
Try the mono theme or use plain mode in terminals with limited color support.
runmux run --theme mono
runmux run --plainrunmux pipes child stdout and stderr into the TUI. It is not a pseudo-terminal multiplexer, so programs that require a real TTY may behave differently.
ANSI escape sequences are stripped by default. Set log.strip_ansi to false to render SGR styles, 16-color, 256-color, and truecolor codes safely; unsupported escape sequences are still dropped.
- POSIX is the primary target. Windows support includes shell lookup through
COMSPECand Job Object cleanup when available. - Interactive child stdin is limited to sending one line to the selected process.
- TOML support covers the
runmuxschema, but it is not a general-purpose TOML parser. - Unicode width handling is delegated to libvaxis, but complex logs can still render imperfectly in some terminals.
- Keep local process orchestration fast, visible, and dependency-aware.
- Provide a useful TUI without requiring tmux, panes, or a Procfile runtime.
- Keep CI and non-interactive use covered through
--plain. - Stay configuration-first and easy to inspect with
checkandlist.
zig build
zig build test
zig build run -- check --config runmux.example.json
zig build run -- list --config runmux.example.json
zig build run -- run --config runmux.example.json
zig build run -- run --plain --config testdata/plain.runmux.json
zig build run -- run --plain --config testdata/plain.runmux.json --log-dir logs- libvaxis for terminal rendering
- Zig's standard library for the build, process, JSON, and IO foundations
MIT. See LICENSE.