Skip to content

Repository files navigation

procs

A personal multi-project dev juggler — run all your services from a single terminal TUI. procs keeps each project's logs in its own pane, lets you start/stop/restart individual processes or named groups with a single keystroke, and gives you a full attach-mode bridge so you can type directly into any child process.

Features

  • Single static binary, zero runtime dependencies
  • Detach mode (macOS/Linux): a background daemon supervises your processes; the TUI is a client you can detach from and reattach to (tmux-style). Close the terminal or drop your SSH session — your services keep running. See Detach Mode
  • Terminal UI built with Bubble Tea
  • Per-project log panes with scroll, filter, and rotation
  • Named groups — boot the whole stack with one key
  • Auto-restart on failure with configurable exponential backoff
  • Attach mode: raw PTY bridge with Esc Esc to detach; OS-native paste (Cmd-V / Ctrl-Shift-V / Shift-Insert) is forwarded to the child as bracketed paste when the child enabled mode 2004. Mouse drag inside the attach pane selects → release auto-copies to the system clipboard; when the attached child enables its own mouse tracking (vim, htop, lazygit, less -R) the mouse is instead forwarded to it (tmux-style)
  • Mouse drag-select on the log pane → release auto-copies to system clipboard. Wheel still scrolls. Hold Option (macOS Terminal/iTerm) or Shift (most Linux terminals) to fall back to the terminal's native selection across borders/scrollbar
  • Git branch display per project + in-TUI branch picker with live filter, plus :fetch / :pull --ff-only
  • Active port/socket display (processes that LISTEN show their port)
  • Per-process CPU% and memory (RSS) in the status bar (whole-process-tree sum; updates every 2 s). CPU can exceed 100% on multi-core workloads — matches htop convention.
  • Hotkey Ctrl-E opens the active config in $VISUAL/$EDITOR; on save, procs auto-reloads (added projects appear, removed projects stop, changed projects toast a "restart to apply" hint). :reload re-reads from disk without opening an editor.
  • Goroutine-safe; race-detector clean

Install

Go install (requires Go 1.22+)

go install github.com/lpwanw/procs/cmd/procs@latest

Binary lands in $(go env GOBIN) — falls back to $(go env GOPATH)/bin. Make sure that directory is on your PATH.

From source

git clone https://github.com/lpwanw/procs
cd procs
make install                  # default PREFIX=$HOME/.local → ~/.local/bin/procs (no sudo)
# system-wide install (requires sudo, shared with other users):
sudo make install PREFIX=/usr/local

Direct binary (macOS/Linux)

Download the archive for your OS/arch from Releases, extract, and place procs on your PATH.

Homebrew: tap coming in a future release.

Update

Installed via Command
go install go install github.com/lpwanw/procs/cmd/procs@latest
source checkout make update (runs git pull --ff-only && make install)
direct binary re-download the latest release and overwrite the binary

Uninstall

Installed via Command
go install rm "$(command -v procs)" (typically $GOPATH/bin/procs)
source checkout make uninstall (matches the PREFIX you installed with)
direct binary delete the binary you placed on PATH

Remove your config and cached logs if you don't want them around:

rm -rf ~/.config/procs ~/.cache/procs

Quick Start

  1. Create ~/.config/procs/config.yml:
projects:
  api:
    path: ~/code/myapi
    cmd: go run ./cmd/api
    restart: on-failure
  web:
    path: ~/code/myweb
    cmd: ./scripts/dev
    restart: on-failure

groups:
  fullstack: [api, web]
  1. Run procs. On macOS/Linux the first run starts a background daemon and attaches a TUI to it; all projects are listed in the sidebar.

Detach Mode (daemon)

On macOS/Linux, procs splits into a headless daemon that owns your processes and a TUI client that connects to it over a per-config unix socket. This means your services survive the terminal closing, an SSH disconnect, or logout — exactly like leaving them in tmux, but with per-project panes, named-group control, and reattach built in.

  • Attach / reattach: just run procs. If a daemon is already running for that config it reattaches (restoring live state + recent logs); otherwise it spawns one and attaches.
  • Detach: :detach (or :q / Ctrl-C) leaves the TUI and keeps the daemon and all processes running. Reattach later with procs.
  • Stop everything: :shutdown from inside the TUI, or procs kill from a shell, stops the daemon and all its children.

Subcommands

Command Action
procs Attach to the daemon, spawning it if needed
procs status Print the daemon's project states (or "no daemon running")
procs kill Stop the daemon and all its children
procs kill --orphans Also stop processes left behind by a crashed daemon

Files

Per-config state lives under your cache dir (~/.cache/procs on Linux, ~/Library/Caches/procs on macOS), keyed by a hash of the config path, all owner-only (0600):

File Purpose
<hash>.sock daemon control socket
<hash>.pid daemon pidfile + lock
<hash>.daemon.log daemon stdout/stderr
<hash>.children live child PIDs (for orphan recovery)

The socket is restricted to your user (mode 0600 inside a 0700 directory, with a peer-uid check), so other local users can't drive your processes.

Windows: there is no daemon — procs runs the TUI in-process (today's behavior) and :detach is unavailable.

Why not just use tmux/screen? You can, but the daemon gives you procs' per-project log panes, named-group start/stop, auto-restart, and one-key reattach without wrapping the whole UI in a multiplexer.

Config

Full reference in examples/config.yml.

Key Type Default Description
projects.<id>.path string required Working directory for the process
projects.<id>.cmd string required Shell command to run
projects.<id>.restart never / on-failure never Restart policy
projects.<id>.env map[string]string {} Inline env vars injected into the child. Quote numeric values in YAML (PORT: "8080")
projects.<id>.env_file string Path to a KEY=VALUE env file (supports # comments, export prefix, quoted values). Merged under inline env: — inline wins on conflict
groups.<name> [id, ...] Named group for batch start
settings.log_buffer_lines int 1000 Per-project in-memory ring size
settings.log_dir string ~/.cache/procs/logs Log file directory
settings.log_rotate_size_mb int 10 Rotate log file after N MB
settings.log_rotate_keep int 5 Rotated log files to keep
settings.shutdown_grace_ms int 5000 Grace period before SIGKILL
settings.group_start_delay_ms int 300 Delay between group member starts
settings.restart_backoff_ms [int, ...] [1000,2000,4000,8000,16000] Backoff schedule (ms)
settings.restart_max_attempts int 5 Max restart attempts
settings.pty_cols / pty_rows int 120 / 40 PTY dimensions

Live reload

Press Ctrl-E in the TUI to open the active config in $VISUAL (preferred), $EDITOR, or vi (fallback). Saving and quitting the editor triggers an automatic reload:

  • Added projects appear in the sidebar immediately.
  • Removed projects are stopped (if running) and dropped from the sidebar.
  • Changed projects (path/cmd/env/restart) keep running on the old definition; a toast hints "restart to apply" — press r on the project to pick up the new command.
  • Parse errors keep the previous config intact; the TUI shows a config error: … toast.

:reload re-reads the file from disk without opening an editor (useful when you edit it from another terminal). For GUI editors, set EDITOR so it blocks until exit — e.g. export EDITOR='code -w', export EDITOR='subl -w'.

Keybindings

Key Action
k / Select previous process
j / Select next process
s Start selected process
r Restart selected process
x Stop selected process
X Stop all processes
a Attach to selected process (raw PTY)
S Open group picker → start group
c / b Open branch picker (type to filter; arrows to navigate; Enter checks out; Esc clears filter then closes)
f git fetch --prune for the selected project (async; toast on result)
p git pull --ff-only for the selected project — never merges
/ Search logs (vim-style; matches highlighted inline)
n / N Jump to next / previous search match
19 Quick-jump to project 1–9 in the sidebar
PgUp / Ctrl-B Scroll log up
PgDn / Ctrl-F Scroll log down
g Scroll to top
G Scroll to bottom
Tab Enter log focus — hand the keyboard to the log pane for vim-style nav + yank (double-Esc to return)
:set nu / :set nonu Toggle line-number gutter in the log panel
:set sev / :set nosev Toggle severity colouring (ERROR/WARN/INFO/DEBUG → fg colour). On by default
:set json / :set nojson Toggle pretty-print for single-line JSON log lines
:set sql / :set nosql Toggle keyword-aware SQL formatting (handles Rails/Sequel/GORM prefixes)
:set wrap / :set nowrap Toggle hard-wrap of long lines at viewport width. On by default
:clear / :c / Ctrl-L Empty the in-memory log buffer for the selected project (files on disk untouched)
:w {path} Dump the currently visible log buffer to {path} (supports ~ / $VAR)
Ctrl-E / :edit Open active config in $VISUAL / $EDITOR / vi; auto-reload on save
:reload Re-read config from disk without opening an editor
:fetch git fetch --prune the selected project (async; result toasted)
:pull git pull --ff-only the selected project — never merges; refuses diverged branches
:detach Detach the TUI; the daemon + processes keep running (macOS/Linux)
:shutdown Stop the daemon and all its children
? Toggle help overlay
: Open command bar (:q to quit)
Ctrl-C Detach (daemon mode) / quit — press twice within 2 s to confirm
Esc Cancel / close overlay

Log focus (vim motions + copy)

Press Tab to hand the keyboard to the log pane. The sidebar dims, a cursor appears, line numbers show, and all process-control keys become inert — only vim motions and yank commands are active. Focus persists across multiple yanks; to return to process-switching press Esc twice within 2 seconds (mirrors the double-Ctrl-C quit pattern).

Counts work in front of any motion or operator: 3w, 2yy, y3e, 3f.. The command buffer shows up in the status bar while you type (3yi …) so multi-key commands are discoverable.

Motions:

Key Moves to
h j k l / arrows char / line
w W next word / WORD
b B prev word / WORD
e E end of (WORD-)word forward
ge gE end of (WORD-)word backward
0 ^ $ line start / first-non-blank / end
+ - next / prev line first-non-blank
gg G buffer top / bottom
Space pause / resume sticky auto-scroll
Ctrl-u / d half-page up / down
Ctrl-b / f full-page up / down
H M L viewport top / mid / bottom
f{c} F{c} find char forward / backward (current line)
t{c} T{c} till char (stop one before)
; , repeat last find (same / reverse direction)
/ n N filter bar; cursor-jump to next / prev match

Visual + yank:

Key Action
v V char / line visual
y (visual) yank selection → clipboard
yy / Y yank current line (count: 3yy)
y{motion} yank to motion target (yw, y3e, yf., y$)

Text objects (current-line scoped):

Object Meaning
iw aw / iW aW inner / around word (WORD)
i" a" / i' a' / i` a` inner / around quote pair
i( a( i) a) inner / around parens
i[ a[ i] a] inner / around brackets
i{ a{ i} a} inner / around braces
i< a< i> a> inner / around angle brackets

Exit: first Esc cancels any pending operator, count, or selection; second Esc within 2 s returns to sidebar / process switching. The status label flips NORMALLOGCOPY as focus and selection change. The line-number gutter is always shown while log focus is active; outside focus, toggle it via :set nu / :set nonu from the command bar.

Attach Mode

Note: Attach currently requires in-process mode. Since macOS/Linux default to daemon mode, run procs --no-daemon to use attach (this runs the TUI without a daemon, so processes stop when you quit). Remote attach over the daemon socket is planned for a future release.

Press a to attach to a running process. The terminal enters raw mode and all input goes directly to the child PTY. To return to procs:

Press Esc twice (within 400ms)

This matches the double-Esc exit gesture used in log-focus mode, so the same key works across the TUI. The detach sequence is 0x1b 0x1b (Esc Esc). A lone Esc is forwarded to the child after the 400 ms window expires, which introduces a small latency for vim normal-mode and other Esc-driven UIs.

Paste: use your terminal's native paste (Cmd-V on macOS, Ctrl-Shift-V or Shift-Insert on Linux). The bracketed-paste sequence is forwarded to the child PTY; if the child enabled DEC mode 2004 (?2004h), it sees the content wrapped in \x1b[200~\x1b[201~ so shells treat it as data instead of typed commands. Newlines are normalised to CR (the universal "Enter").

Troubleshooting

No config file found:

procs: config file not found at "/Users/you/.config/procs/config.yml"

Create the file at that path (see Quick Start), or pass -c /path/to/config.yml.

Port not shown in sidebar: The port column only shows after the process has opened a listening socket. Wait a moment for the process to bind, then the display updates automatically.

Git branch not shown: The project's path must be inside a Git repository. If git is not on $PATH or the directory is not a repo, the branch column is left blank.

Process does not restart: Check that restart: on-failure is set in your config. restart: never (the default) means the process stays stopped after it exits.

Daemon won't start / procs hangs on launch: Check the daemon log at ~/.cache/procs/<hash>.daemon.log (macOS: ~/Library/Caches/procs/...). If a previous daemon crashed, a stale socket is cleaned up automatically on the next launch.

"N processes from a previously crashed daemon are still running": A daemon died without stopping its children. Run procs kill --orphans to stop them before they conflict with newly started processes.

Changes to a running project's command aren't applied after reload: :reload / Ctrl-E reconcile added/removed projects immediately, but a changed command keeps running on the old definition — press r on the project to pick up the new command.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages