Skip to main content

cliwrap

Use when executing external command-line processes from .NET with CliWrap. Covers fluent command building, output capturing, piping, streaming, cancellation, and environment variable configuration. USE FOR: executing CLI tools from .NET code, capturing stdout/stderr output, piping between processes, streaming large command output, running git/docker/npm from C# applications DO NOT USE FOR: building CLI argument parsers (use commandline-cheatsheet or spectre-console), rendering terminal UI (use spectre-console), running in-process code (use Task.Run), simple Process.Start without advanced piping

Quellinformationen

Repository
Tyler-R-Kendrick/agent-skills
Letzte Quellaktivität
11. Februar 2026 um 05:14
Erkannte Sprache von SKILL.md
Englisch
Sterne
11
Forks
4

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
22 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
cliwrap
description
Use when executing external command-line processes from .NET with CliWrap. Covers fluent command building, output capturing, piping, streaming, cancellation, and environment variable configuration. USE FOR: executing CLI tools from .NET code, capturing stdout/stderr output, piping between processes, streaming large command output, running git/docker/npm from C# applications DO NOT USE FOR: building CLI argument parsers (use commandline-cheatsheet or spectre-console), rendering terminal UI (use spectre-console), running in-process code (use Task.Run), simple Process.Start without advanced piping
license
MIT
metadata
{"displayName":"CliWrap","author":"Tyler-R-Kendrick","version":"1.0.0"}
compatibility
claude, copilot, cursor
references
[{"title":"CliWrap GitHub Repository","url":"https://github.com/Tyrrrz/CliWrap"},{"title":"CliWrap NuGet Package","url":"https://www.nuget.org/packages/CliWrap"}]
# CliWrap ## Overview CliWrap is a library for interacting with external command-line processes in .NET. It provides a fluent API for building commands, capturing output, piping between processes, streaming stdout/stderr in real time, and handling cancellation and timeouts. CliWrap replaces direct use of `System.Diagnostics.Process` with a safer, more composable, and async-first API. ## NuGet Packages ```bash dotnet add package CliWrap ``` ## Basic Command Execution ```csharp using CliWrap; // Simple execution (fire and forget with exit code check) var result = await Cli.Wrap("dotnet") .WithArguments("--version") .ExecuteAsync(); Console.WriteLine($"Exit code: {result.ExitCode}"); Console.WriteLine($"Started: {result.StartTime}"); Console.WriteLine($"Exited: {result.ExitTime}"); Console.WriteLine($"Duration: {result.RunTime}"); ``` ## Buffered Output (Capturing stdout/stderr) ```csharp using CliWrap; using CliWrap.Buffered; var result = await Cli.Wrap("dotnet") .WithArguments(["build", "--configuration", "Release"]) .WithWorkingDirectory("/src/MyProject") .ExecuteBufferedAsync(); Console.WriteLine($"STDOUT:\n{result.StandardOutput}"); Console.WriteLine($"STDERR:\n{result.StandardError}"); Console.WriteLine($"Exit code: {result.ExitCode}"); ``` ## Building Arguments ```csharp using CliWrap; // Array-based arguments (automatically handles escaping) var result = await Cli.Wrap("git") .WithArguments(["commit", "-m", "Fix: handle spaces in file names"]) .ExecuteBufferedAsync(); // Builder-based arguments for complex commands var result2 = await Cli.Wrap("docker") .WithArguments(args => args .Add("run") .Add("--rm") .Add("-v").Add("/host/path:/container/path") .Add("-e").Add("CONNECTION_STRING=Server=localhost;Database=mydb") .Add("--name").Add("my-container") .Add("mcr.microsoft.com/dotnet/sdk:8.0")) .ExecuteBufferedAsync(); ``` ## Streaming Output in Real Time ```csharp using CliWrap; using CliWrap.EventStream; var cmd = Cli.Wrap("dotnet") .WithArguments(["test", "--verbosity", "normal"]) .WithWorkingDirectory("/src/MyProject"); await foreach (var cmdEvent in cmd.ListenAsync()) { switch (cmdEvent) { case StartedCommandEvent started: Console.WriteLine($"Process started; PID: {started.ProcessId}"); break; case StandardOutputCommandEvent stdOut: Console.WriteLine($"[OUT] {stdOut.Text}"); break; case StandardErrorCommandEvent stdErr: Console.WriteLine($"[ERR] {stdErr.Text}"); break; case ExitedCommandEvent exited: Console.WriteLine($"Process exited; Code: {exited.ExitCode}"); break; } } ``` ## Piping stdout/stderr ```csharp using CliWrap; // Pipe to delegates await Cli.Wrap("dotnet") .WithArguments(["build"]) .WithStandardOutputPipe(PipeTarget.ToDelegate(line => Console.WriteLine($"[BUILD] {line}"))) .WithStandardErrorPipe(PipeTarget.ToDelegate(line => Console.Error.WriteLine($"[ERROR] {line}"))) .ExecuteAsync(); // Pipe to file await Cli.Wrap("dotnet") .WithArguments(["test", "--logger", "trx"]) .WithStandardOutputPipe(PipeTarget.ToFile("test-output.log")) .ExecuteAsync(); // Pipe to StringBuilder var stdOutBuffer = new StringBuilder(); var stdErrBuffer = new StringBuilder(); await Cli.Wrap("git") .WithArguments(["log", "--oneline", "-20"]) .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) .ExecuteAsync(); Console.WriteLine(stdOutBuffer.ToString()); // Pipe to Stream using var memoryStream = new MemoryStream(); await Cli.Wrap("curl") .WithArguments(["-s", "https://api.example.com/data"]) .WithStandardOutputPipe(PipeTarget.ToStream(memoryStream)) .ExecuteAsync(); // Pipe to multiple targets simultaneously await Cli.Wrap("dotnet") .WithArguments(["build"]) .WithStandardOutputPipe(PipeTarget.Merge( PipeTarget.ToDelegate(Console.WriteLine), PipeTarget.ToFile("build.log"))) .ExecuteAsync(); ``` ## Piping Between Processes ```csharp using CliWrap; // Pipe stdout of one command into stdin of another (like shell pipes) var result = await ( Cli.Wrap("dotnet") .WithArguments(["tool", "list", "--global"]) | Cli.Wrap("findstr") .WithArguments(["dotnet-ef"]) ).ExecuteBufferedAsync(); Console.WriteLine(result.StandardOutput); ``` ## Providing stdin Input ```csharp using CliWrap; // Pipe string input var result = await Cli.Wrap("dotnet") .WithArguments(["script", "eval"]) .WithStandardInputPipe(PipeSource.FromString("Console.WriteLine(1 + 2);")) .ExecuteBufferedAsync(); // Pipe from file await Cli.Wrap("python") .WithArguments(["script.py"]) .WithStandardInputPipe(PipeSource.FromFile("input.txt")) .ExecuteAsync(); // Pipe from stream using var inputStream = new MemoryStream("Hello, World!"u8.ToArray()); await Cli.Wrap("cat") .WithStandardInputPipe(PipeSource.FromStream(inputStream)) .ExecuteAsync(); ``` ## Cancellation and Timeouts ```csharp using CliWrap; // Cancellation token using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); try { await Cli.Wrap("dotnet") .WithArguments(["test"]) .ExecuteAsync(cts.Token); } catch (OperationCanceledException) { Console.WriteLine("Command was cancelled or timed out"); } // Graceful shutdown: sends SIGINT first, then SIGKILL after timeout using var cts2 = new CancellationTokenSource(); cts2.CancelAfter(TimeSpan.FromMinutes(5)); try { await Cli.Wrap("long-running-process") .ExecuteAsync( forcefulCancellationToken: cts2.Token, gracefulCancellationToken: cts2.Token); } catch (OperationCanceledException) { Console.WriteLine("Process was terminated"); } ``` ## Environment Variables and Configuration ```csharp using CliWrap; var result = await Cli.Wrap("dotnet") .WithArguments(["run"]) .WithWorkingDirectory("/src/MyApp") .WithEnvironmentVariables(env => env .Set("ASPNETCORE_ENVIRONMENT", "Staging") .Set("ConnectionStrings__Default", "Server=localhost;Database=testdb") .Set("DOTNET_CLI_TELEMETRY_OPTOUT", "1")) .WithValidation(CommandResultValidation.ZeroExitCode) .ExecuteAsync(); ``` ## Error Handling ```csharp using CliWrap; using CliWrap.Exceptions; try { await Cli.Wrap("dotnet") .WithArguments(["build"]) .WithValidation(CommandResultValidation.ZeroExitCode) // Default behavior .ExecuteBufferedAsync(); } catch (CommandExecutionException ex) { Console.Error.WriteLine($"Command failed with exit code: {ex.ExitCode}"); Console.Error.WriteLine($"Command: {ex.Command}"); } // Disable validation to handle non-zero exit codes manually var result = await Cli.Wrap("git") .WithArguments(["diff", "--exit-code"]) .WithValidation(CommandResultValidation.None) .ExecuteBufferedAsync(); if (result.ExitCode != 0) { Console.WriteLine("Working directory has changes:"); Console.WriteLine(result.StandardOutput); } ``` ## Wrapping Common CLI Tools ```csharp public class GitClient { private readonly string _workingDirectory; public GitClient(string workingDirectory) => _workingDirectory = workingDirectory; private Command BaseCommand => Cli.Wrap("git") .WithWorkingDirectory(_workingDirectory) .WithValidation(CommandResultValidation.ZeroExitCode); public async Task<string> GetCurrentBranchAsync(CancellationToken ct = default) { var result = await BaseCommand .WithArguments(["branch", "--show-current"]) .ExecuteBufferedAsync(ct); return result.StandardOutput.Trim(); } public async Task<string> GetStatusAsync(CancellationToken ct = default) { var result = await BaseCommand .WithArguments(["status", "--porcelain"]) .ExecuteBufferedAsync(ct); return result.StandardOutput; } public async Task CommitAsync(string message, CancellationToken ct = default) { await BaseCommand .WithArguments(["add", "-A"]) .ExecuteAsync(ct); await BaseCommand .WithArguments(["commit", "-m", message]) .ExecuteAsync(ct); } public async Task<IReadOnlyList<string>> GetLogAsync( int count = 10, CancellationToken ct = default) { var result = await BaseCommand .WithArguments(["log", $"--oneline", $"-{count}"]) .ExecuteBufferedAsync(ct); return result.StandardOutput .Split('\n', StringSplitOptions.RemoveEmptyEntries); } } ``` ## Best Practices - Use `WithArguments(string[])` (array overload) instead of `WithArguments(string)` (raw string) to get automatic escaping of spaces, quotes, and special characters in argument values. - Use `ExecuteBufferedAsync` only for commands with bounded output; for commands that may produce megabytes of output (e.g., `dotnet test --verbosity detailed`), use `ListenAsync` or pipe to a `Stream` to avoid memory pressure. - Set `WithValidation(CommandResultValidation.ZeroExitCode)` explicitly (or rely on the default) to get `CommandExecutionException` on failure rather than silently ignoring non-zero exit codes. - Always pass `CancellationToken` to `ExecuteAsync` and `ExecuteBufferedAsync`; use `CancellationTokenSource.CancelAfter` to enforce timeouts on long-running commands. - Use `WithWorkingDirectory` to set the process working directory explicitly rather than relying on the host application's current directory, which may differ between development and deployment. - Create typed wrapper classes (e.g., `GitClient`, `DockerClient`) that expose domain-specific methods using a shared `BaseCommand` property to centralize working directory, validation, and environment configuration. - Use `PipeTarget.Merge` to send output to both a log file and the console simultaneously during build/deploy scripts where you need real-time feedback and a persistent record. - Handle `CommandExecutionException` specifically (not just `Exception`) to extract the exit code and command details for structured error reporting and logging. - Use the `|` pipe operator between `Command` instances (`cmdA | cmdB`) for shell-like piping instead of capturing intermediate output in memory and feeding it to the next process. - Set environment variables with `WithEnvironmentVariables` rather than modifying `Environment.SetEnvironmentVariable` globally, which affects all threads and is not isolated per command.
Auf GitHub ansehen