Specification
The original project specification document detailing goals, behavior, and implementation details.
Prun Specification
This document contains the original project specification for prun, including goals, configuration format, CLI behavior, and implementation details.
Short Description
prun is a small command-line tool that reads a prun.toml configuration file, starts multiple tasks (commands) in parallel, and streams their combined output to the console in real time. It's designed for local development workflows where several processes (app, server, databases, watchers) need to be run together.
Goals
- Simple, predictable CLI for running multiple processes in parallel.
- Clear, easy-to-edit configuration file (
prun.toml). - Real-time, prefixed output per-task so users can distinguish logs.
- Signal forwarding and graceful shutdown.
- Minimal dependencies and easy packaging for Linux/macOS/Windows.
Config Format
prun will look for prun.toml in the current working directory (or a location provided by a --config flag). The file is a TOML document with a tasks list and per-task tables.
Example
tasks = ["app", "redis_server", "server"]
[task.app]
cmd = "pnpm run dev"
[task.redis_server]
cmd = "redis-server"
[task.server]
cmd = "./server"
path = "/home/user/my-server"Semantics
tasksis an ordered list of task names. Order is primarily for user readability; the runner will start tasks in the order listed but run them concurrently.- Each
[task.<name>]table must includecmd(string). Optionally it may include:path(string) — working directory to run the command in.env(table) — map of environment variables (string -> string) to set for the task.restart(boolean|string) — whether/how to restart a task on exit:false(default),true(restart always), or a policy likeon-failure.
CLI Behavior
Invocation
prun [flags] [--] [task1 task2 ...]Flags
-c, --config <path>: path toprun.toml. Defaults to./prun.toml.-v, --verbose: enable verbose logs for prun internals.-l, --list: print tasks defined and exit.-h, --help: show usage.
Behavior
- On start,
prunsearches for the config file. If not found, it prints a short message: "prun: no prun.toml found — runprun helpto create one" and exits with status code 2. - If found,
prunparses the TOML. If parsing fails, it prints the parse error and exits with status code 3. - If the user passes specific task names as arguments, only those tasks (in the order provided) are started. If no tasks are passed, all tasks listed under
tasksare started, in that order.
Process Lifecycle and Output
prunstarts all selected tasks as child processes.- For each task,
pruncaptures stdout and stderr, prefixes each line with a short task label (e.g.,[app],[redis_server]) and writes to the main stdout/stderr stream. The prefix helps distinguish interleaved logs. - The prefixing should support configurable width or colorization when stdout is a TTY. When output is not a TTY (e.g., piped to a file),
prunshould omit colors and may shorten prefixes. - Output should be unbuffered and near-real-time. Use a line-based scanner or incremental flush to avoid large delays.
Exit Codes and Failure Policies
- If any task exits with a non-zero status,
prunshould by default terminate all other tasks and exit with a non-zero status reflecting failure. - The
restartpolicy per-task (if implemented) can override this behavior — e.g., tasks withrestart = truewill be restarted automatically and won't causeprunto exit unless explicitly configured. - On receiving termination signals (SIGINT, SIGTERM),
prunshould forward the signal to child processes and wait for them to exit gracefully (with a short timeout, e.g., 5s) before forcing termination.
Edge Cases and Considerations
- Commands producing binary or very long lines — prefixing should be done safely (don't assume UTF-8 or small sizes).
- Process groups and shells: commands in
cmdmay be shell forms. By default,prunshould run the command through a shell (like/bin/sh -c) to support complex commands, but provide ashell = falseoption to run exec directly if desired. - Environment inheritance: tasks should inherit the parent environment unless overridden in
env. - Port collisions and graceful restarts are out-of-scope for initial implementation.
Acceptance Criteria
Minimum Viable Product (MVP)
prunsearches for./prun.toml. If absent, prints: "no prun.toml found — runprun help" and exits code 2.- Parses a valid
prun.tomlwith at leasttasksandcmdentries. - Starts all defined tasks concurrently, streams output with task prefixes, and shows both stdout and stderr combined in real time.
- On a non-zero exit from any task,
prunterminates remaining tasks and exits non-zero. - Handles SIGINT (Ctrl-C) by forwarding and cleanly shutting down processes.
Optional / Nice-to-Have (Post-MVP)
- Colorized prefixes and adjustable width.
- Per-task restart policies.
--watchto restart tasks when files change.--parallelismto limit number of concurrently running tasks.- Built-in command to generate a sample
prun.toml(prun init). - Windows-specific behavior and proper signal handling on Windows.
- Interactive TUI mode (implemented).
- File watching with debouncing (implemented).
Testing
Unit Tests
- TOML parsing tests for valid and invalid configs.
- Task selection logic (all tasks vs. subset by args).
- Prefix formatting and TTY detection.
Integration Tests (Fast)
- Start two small processes (e.g.,
sh -c 'echo a; sleep 0.1; echo done') and assertprunoutput contains both prefixes. - Verify
prunexits non-zero when a task fails. - Signal forwarding test: send SIGINT to
prunand assert child processes receive term signal.
Security and Safety
- Avoid shell injection risks by documenting that
cmdis executed via a shell; if users supply untrusted config files, commands will run with the user's privileges. - The tool will not attempt to sandbox commands.
Implementation Plan (High Level)
- Parse CLI flags and find config path.
- Read and parse
prun.tomlinto a typed config object. - Resolve the list of tasks to run.
- For each task, spawn a child process with appropriate working dir and env.
- Start goroutines to read stdout/stderr line-by-line, prefix, and write to the main stdout/stderr with synchronization.
- Monitor child exits; on failure or signal, implement shutdown procedure.
- Add tests and sample config files.
Files Structure
cmd/prun/main.go— CLI entrypoint and wiring.internal/config/config.go— TOML parsing (types + parser).internal/runner/runner.go— process management, output streaming.tests/— unit and integration tests described above.PROJECT_SPEC.md— this spec.
Open Questions / Assumptions
- Assumed implementation language: Go (based on repo), using
github.com/pelletier/go-tomlorBurntSushi/tomlfor parsing. - Assume running commands through
/bin/sh -cby default for Unix-like systems. - Default timeout for graceful shutdown: 5s (configurable later).
Next Steps
- Implement the config parser and unit tests for it.
- Implement a basic runner that starts tasks and streams output.
- Add signal handling and graceful shutdown.
Completion
This file defines the project behaviour and acceptance criteria for prun. Implementers should follow the acceptance criteria for the MVP and add optional features later.