CliRunner
The one place an app spawns a child command-line tool. Every call is time-boxed, kills the process tree when the box expires, drains stdout and stderr concurrently, and reports failure in-band rather than throwing, so a caller degrades gracefully when a tool is absent, refuses, or hangs.
var result = await CliRunner.RunAsync(
"git", workingDirectory, ["status", "--porcelain"], timeout: TimeSpan.FromSeconds(10), ct);
if (!result.Ok)
{
toasts.ShowWarning(result.Message("git is not available"));
return;
}
Parse(result.Stdout);Arguments are a list, never a command string, so there is no shell and no quoting to get wrong. The default timeout is thirty seconds. ExistsAsync(exe) probes whether a tool is reachable at all.
CliResult carries Ok, Stdout, Stderr, the ExitCode when it got that far, and a CliFailure that separates a non-zero exit from TimedOut, Canceled and a failure to start. The distinction that matters most is cancelled versus timed out: work the user abandoned must not be reported as a hung tool, and a genuinely hung tool must not be written off as a change of mind.
Core's own use is LoginShellPath, which runs the user's login shell at startup. That is exactly the call that used to hang an app forever when an rc file filled the stderr pipe, and it is why every spawn goes through here.
It lives in the Avalloy.Cli namespace rather than Avalloy.Process because a namespace of that name shadows System.Diagnostics.Process for every file inside it.