cli-stream
A small, generic streaming subprocess engine for Rust: spawn a child
process, stream its stdout/stderr line-by-line through a callback, and cancel
it cleanly (SIGTERM → SIGKILL). Plus an optional PATH helper so a packaged
GUI app (a macOS .app launched from Finder, with a minimal environment) can
still find CLIs installed via nvm / Homebrew / asdf / volta.
No domain knowledge — just process streaming. Useful to anything that drives a child CLI: a task runner, a build/test wrapper, a TUI, a desktop app shelling out to tools.
= "0.4"
use ;
let handle = spawn_streaming?;
// handle.cancel()?; // SIGTERM, then SIGKILL after a grace period
What you get
spawn_streaming(program, args, env, cwd, run_id, callback)→ aProcessHandle, streamingProcessEvents (Started / Stdout / Stderr / Error / Exited) from reader threads. Errors are a typedStreamError—Spawncarries the underlyingio::Error, so "not on PATH" is distinguishable from "permission denied".ProcessHandle::cancel()— SIGTERM, then SIGKILL after a grace period. Pollstry_wait(no blockingwaitunder a lock), so it terminates a running child promptly, not just on its next line of output.- Cancelling ends the tree. A child that starts its own children and exits
would otherwise leave them holding the stdout they inherited: the pipe never
closes, so no
Exitedarrives and a caller waiting on the stream waits forever. The child leads its own process group on unix and is placed in a Job Object on Windows, so the signal or the terminate reaches everything it started. InstallEvent— a sibling event shape for streamed install/setup output, for hosts that run their own setup steps.
The environment is the caller's: this spawns what it is told to spawn, with the
PATH it is given. Finding a CLI a user installed — resolving a bare name,
locating the runtime it was installed under — is a different question, and
agent-harness answers it.
License
MIT OR Apache-2.0.