# pend β do now, wait later π
`pend` is a **tiny cross-platform job runner** that lets you fire-and-forget
processes *now* and deal with their output *later* β all while keeping logs
tidy, exit statuses intact, and your shell scripts blissfully simple.
```
# Run two jobs in parallel β¦
pend do backend ./scripts/build_backend.sh
pend do frontend ./scripts/build_frontend.sh
# β¦ wait for them whenever you want (combined coloured output!)
pend wait backend frontend
# Clean up once you are done
pend clean --all
```
Why wrestle with `&`, `wait`, and brittle `tee` pipelines when **one binary**
does all the heavy lifting for you?
---
## π¦ Installation
```bash
cargo install pend # Rust way β zero dependencies
# or grab a pre-built release asset from GitHub
```
The crate is 100 % Rust, no native libraries, so a static binary drops out on
all tier-1 platforms (Windows / macOS / Linux β x86-64 & aarch64).
---
## π§ Mental model
| `pend do <job> <cmd β¦>` | Launches `<cmd>` detached in the background. Captures its stdout, stderr, exit code, metadata, _and_ a combined `.log` stream. Optional flags `--timeout <secs>` and `--retries <n>` kill or re-run the command automatically. |
| `pend wait <job β¦>` | Blocks until the supplied job(s) finish. Streams their output in the original order and exits with the very same code the first failing job produced. |
| `pend clean [--all \| <job β¦>]` | Deletes artifacts to free disk space. Skips jobs that are still running. |
| `pend tui` | Opens a super-lightweight TUI that auto-refreshes and shows a live list of all jobs (press `q` to quit). |
Thatβs the entire user-facing surface β **four deliberately boring verbs**.
---
## β¨ What you get β out of the box
β’ **Structured artifacts** β every job yields predictable files: `.out`, `.err`, `.log`, `.exit`, `.json`, `.signal` (Unix) β all plain text or JSON.
β’ **Crash-safe exit codes** β the `.exit` marker is written _before_ log pipes are closed so `pend wait` never hangs on a half-dead worker.
β’ **Coloured multi-job output** β `pend wait a b c` interleaves logs with deterministic colours and clear β / β status lines.
β’ **Size-bounded log rotation** β `--max-log-size 10M` keeps CI artifacts small yet complete.
β’ **Wall-clock timeout** β `pend do <job> --timeout 30 <cmd β¦>` terminates runaway processes after 30 s and marks the job as failed.
β’ **Automatic retries** β `--retries 3` re-runs flaky commands up to three times until one attempt succeeds.
β’ **Strong validation & security** β path traversal is impossible, job names are capped at 100 characters, and an advisory `.lock` prevents concurrent duplicates.
β’ **Platform quirks handled** β symlink tmpdirs on macOS, `MAX_PATH` on Windows, signals on Unix β tested on all three major OSes in CI.
β’ **Single static binary** β integrates into any Bash / PowerShell / CMD script without pulling in a runtime.
---
## π Artifact layout
Jobs live in a single directory (defaults to `$TMPDIR/pend`, override via
`--dir` or `PEND_DIR`). Files follow `<job>.<ext>`:
| `foo.out` / `foo.err` | Raw stdout / stderr as produced. |
| `foo.log` (+ `.log.1` β¦) | Chronological merged log (rotated). |
| `foo.exit` | Numeric exit code written first. |
| `foo.json` | Pretty-printed metadata (command, PID, UTC timestamps). |
| `foo.signal` (Unix) | Raw signal number, if any. |
| `foo.lock` | Advisory lock file; safe to delete when the job is not running. |
Everything is human-readable β `cat`, `jq`, or even Notepad work fine.
---
## π Example: parallel build & package
```bash
# Kick off long-running tasks
pend do backend docker build .
pend do frontend npm run build
# Meanwhile run unit tests β¦
pytest -q
# Stream & fail fast once the first build fails
pend wait backend frontend
# Ensure the linter finishes within 60 seconds and automatically retries once
# if it flaps due to I/O hiccups.
pend do lint --timeout 60 --retries 1 npm run lint
pend wait lint
# Package artifacts only if both succeeded
pend do package ./scripts/package.sh
pend wait package
# Upload artifacts, then clean workspace
pend clean package
```
---
## π Under the hood
* **Worker process** β spawns child cmd, merges pipes via channel fan-in, writes JSON, exits.
* **File watcher** β `pend wait` uses the cross-platform `notify` crate for instant `.exit` detection; falls back to exponential back-off polling if necessary.
* **No async runtime** β plain threads & channels keep the binary small (< 1 MiB on Linux/musl).
---
## π Prior art
`GNU parallel`, `xargs -P`, `make -j`, `ninja`, `taskwarrior`, `just`, `cargo-make` β¦ all wonderful β yet none hit the sweet spot of *ad-hoc, nameable, parallel shell jobs with deterministic logs*.
**pend** does.
---
Made with β€οΈ & Rust β so you can **do now, wait later**.