CliWrap
Library for interacting with command-line interfaces
Install / Use
/learn @Tyrrrz/CliWrapREADME
CliWrap
<table> <tr> <td width="99999" align="center">Development of this project is entirely funded by the community. <b><a href="https://tyrrrz.me/donate">Consider donating to support!</a></b></td> </tr> </table> <p align="center"> <img src="favicon.png" alt="Icon" /> </p>CliWrap is a library for interacting with command-line interfaces. It provides a convenient model for launching processes, redirecting input and output streams, awaiting completion, handling cancellation, and more.
Terms of use<sup>[?]</sup>
By using this project or its source code, for any purpose and in any shape or form, you grant your implicit agreement to all the following statements:
- You condemn Russia and its military aggression against Ukraine
- You recognize that Russia is an occupant that unlawfully invaded a sovereign state
- You support Ukraine's territorial integrity, including its claims over temporarily occupied territories of Crimea and Donbas
- You reject false narratives perpetuated by Russian state propaganda
To learn more about the war and how you can help, click here. Glory to Ukraine! 🇺🇦
Install
- 📦 NuGet:
dotnet add package CliWrap
Features
- Airtight abstraction over
System.Diagnostics.Process - Fluent configuration interface
- Flexible support for piping
- Fully asynchronous and cancellation-aware API
- Graceful cancellation using interrupt signals
- Designed with strict immutability in mind
- Provides safety against typical deadlock scenarios
- Tested on Windows, Linux, and macOS
- Targets .NET Standard 2.0+, .NET Core 3.0+, .NET Framework 4.6.2+
- No external dependencies
Usage
Video guides
You can watch one of these videos to learn how to use the library:
Quick overview
Similarly to a shell, CliWrap's base unit of work is a command — an object that encapsulates instructions for running a process.
To build a command, start by calling Cli.Wrap(...) with the executable path, and then use the provided fluent interface to configure arguments, working directory, or other options.
Once the command is configured, you can run it by calling ExecuteAsync():
using CliWrap;
var result = await Cli.Wrap("path/to/exe")
.WithArguments(["--foo", "bar"])
.WithWorkingDirectory("work/dir/path")
.ExecuteAsync();
// Result contains:
// -- result.ExitCode (int)
// -- result.IsSuccess (bool)
// -- result.StartTime (DateTimeOffset)
// -- result.ExitTime (DateTimeOffset)
// -- result.RunTime (TimeSpan)
The code above spawns a child process with the configured command-line arguments and working directory, and then asynchronously waits for it to exit.
After the task has completed, it resolves to a CommandResult object that contains the process exit code and other relevant information.
[!WARNING] CliWrap will throw an exception if the underlying process returns a non-zero exit code, as it usually indicates an error. You can override this behavior by disabling result validation using
WithValidation(CommandResultValidation.None).
By default, the process's standard input, output and error streams are routed to CliWrap's equivalent of a null device, which represents an empty source and a target that discards all data.
You can change this by calling WithStandardInputPipe(...), WithStandardOutputPipe(...), or WithStandardErrorPipe(...) to configure pipes for the corresponding streams:
using CliWrap;
var stdOutBuffer = new StringBuilder();
var stdErrBuffer = new StringBuilder();
var result = await Cli.Wrap("path/to/exe")
.WithArguments(["--foo", "bar"])
.WithWorkingDirectory("work/dir/path")
// This can be simplified with `ExecuteBufferedAsync()`
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer))
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer))
.ExecuteAsync();
// Access stdout & stderr buffered in-memory as strings
var stdOut = stdOutBuffer.ToString();
var stdErr = stdErrBuffer.ToString();
This example command is configured to decode the data written to the standard output and error streams as text, and append it to the corresponding StringBuilder buffers.
Once the execution is complete, these buffers can be inspected to see what the process has printed to the console.
Handling command output is a very common use case, so CliWrap offers a few high-level execution models to make these scenarios simpler.
In particular, the same thing shown above can also be achieved more succinctly with the ExecuteBufferedAsync() extension method:
using CliWrap;
using CliWrap.Buffered;
// Calling `ExecuteBufferedAsync()` instead of `ExecuteAsync()`
// implicitly configures pipes that write to in-memory buffers.
var result = await Cli.Wrap("path/to/exe")
.WithArguments(["--foo", "bar"])
.WithWorkingDirectory("work/dir/path")
.ExecuteBufferedAsync();
// Result contains:
// -- result.StandardOutput (string) *
// -- result.StandardError (string) *
// -- result.ExitCode (int)
// -- result.IsSuccess (bool)
// -- result.StartTime (DateTimeOffset)
// -- result.ExitTime (DateTimeOffset)
// -- result.RunTime (TimeSpan)
[!WARNING] Be mindful when using
ExecuteBufferedAsync(). Programs can write arbitrary data (including binary) to the output and error streams, and storing it in-memory may be impractical. For more advanced scenarios, CliWrap also provides other piping options, which are covered in the piping section.
Command configuration
The fluent interface provided by the command object allows you to configure various aspects of its execution. This section covers all available configuration methods and their usage.
[!NOTE]
Commandis an immutable object — all configuration methods listed here create a new instance instead of modifying the existing one.
WithArguments(...)
Sets the command-line arguments passed to the child process.
Default: empty.
Examples:
- Set arguments using an array:
var cmd = Cli.Wrap("git")
// Each element is formatted as a separate argument.
// Equivalent to: `git commit -m "my commit"`
.WithArguments(["commit", "-m", "my commit"]);
- Set arguments using a builder:
var cmd = Cli.Wrap("git")
// Each Add(...) call takes care of formatting automatically.
// Equivalent to: `git clone https://github.com/Tyrrrz/CliWrap --depth 20`
.WithArguments(args => args
.Add("clone")
.Add("https://github.com/Tyrrrz/CliWrap")
.Add("--depth")
.Add(20)
);
var forcePush = true;
var cmd = Cli.Wrap("git")
// Arguments can also be constructed in an imperative fashion.
// Equivalent to: `git push --force`
.WithArguments(args =>
{
args.Add("push");
if (forcePush)
args.Add("--force");
});
[!TIP] The builder overload allows you to define custom extension methods for reusable argument patterns. Learn more.
- Set arguments directly:
var cmd = Cli.Wrap("git")
// Avoid using this overload unless you really have to.
// Equivalent to: `git commit -m "my commit"`
.WithArguments("commit -m \"my commit\"");
[!WARNING] Unless you absolutely have to, avoid setting command-line arguments directly from a string. This method expects all arguments to be correctly escaped and formatted ahead of time — which can be cumbersome to do yourself. Formatting errors may result in unexpected bugs and security vulnerabilities.
[!NOTE] There are some obscure scenarios, where you may need to assemble the command-line arguments yourself. In such cases, you can use the
ArgumentsBuilder.Escape(...)method to escape individual arguments manually.
WithWorkingDirectory(...)
Sets the working directory of the child process.
Default: current working directory, i.e. Directory.GetCurrentDirectory().
Example:
var cmd = Cli.Wrap("git")
.WithWorkingDirectory("c:/projects/my project/");
`WithEnvi
Related Skills
node-connect
337.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
337.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.2kCommit, push, and open a PR


