CliWrap 3.6.0-alpha1
See the version list below for details.
dotnet add package CliWrap --version 3.6.0-alpha1
NuGet\Install-Package CliWrap -Version 3.6.0-alpha1
<PackageReference Include="CliWrap" Version="3.6.0-alpha1" />
paket add CliWrap --version 3.6.0-alpha1
#r "nuget: CliWrap, 3.6.0-alpha1"
// Install CliWrap as a Cake Addin #addin nuget:?package=CliWrap&version=3.6.0-alpha1&prerelease // Install CliWrap as a Cake Tool #tool nuget:?package=CliWrap&version=3.6.0-alpha1&prerelease
CliWrap
π’ Project status: active<sup>[?]</sup>
CliWrap is a library for interacting with external 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
- 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.1+
- No external dependencies
Usage
Video guides
You can watch one of these videos to get a quick and detailed overview of the library:
Quick overview
Similarly to a shell, CliWrap's base unit of work is a command β an object that encodes 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.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 related 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:
var stdOutBuffer = new StringBuilder();
var stdErrBuffer = new StringBuilder();
// β This particular example can also be simplified with ExecuteBufferedAsync().
// Continue reading below!
var result = await Cli.Wrap("path/to/exe")
.WithArguments("--foo bar")
.WithWorkingDirectory("work/dir/path")
.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 standard output and error streams as text, and append it to the corresponding StringBuilder
buffers.
After 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.StartTime (DateTimeOffset)
// -- result.ExitTime (DateTimeOffset)
// -- result.RunTime (TimeSpan)
Warning: Be mindful when using
ExecuteBufferedAsync()
. Programs can write arbitrary data (including binary) to output and error streams, which may be impractical to buffer in-memory. 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 options related to its execution. Below list covers all available configuration methods and their usage.
Note:
Command
is 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 from a string:
var cmd = Cli.Wrap("git")
.WithArguments("commit -m \"my commit\"");
Warning: Unless you absolutely have to, avoid setting command arguments from a string. This method expects that all of the arguments are already escaped and formatted correctly β which can be really hard to get right.
- Set arguments from an array β each element is treated as a separate argument and spaces are escaped automatically:
var cmd = Cli.Wrap("git")
.WithArguments(new[] {"commit", "-m", "my commit"});
- Set arguments using a builder β same as above, but also works with non-string arguments and can be enhanced with your own extension methods:
var cmd = Cli.Wrap("git")
.WithArguments(args => args
.Add("clone")
.Add("https://github.com/Tyrrrz/CliWrap")
.Add("--depth")
.Add(20)
);
Note: You can also manually instantiate
ArgumentsBuilder
to help with the formatting and escaping of arguments. This may be useful if you need to generate an argument string outside of theWithArguments(...)
method.
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/");
WithEnvironmentVariables(...)
Sets additional environment variables exposed to the child process.
Default: empty.
Examples:
- Set environment variables from a dictionary:
var cmd = Cli.Wrap("git")
.WithEnvironmentVariables(new Dictionary<string, string?>
{
["GIT_AUTHOR_NAME"] = "John",
["GIT_AUTHOR_EMAIL"] = "john@email.com"
});
- Set environment variables using a builder:
var cmd = Cli.Wrap("git")
.WithEnvironmentVariables(env => env
.Set("GIT_AUTHOR_NAME", "John")
.Set("GIT_AUTHOR_EMAIL", "john@email.com")
);
Note: Environment variables configured using
WithEnvironmentVariables(...)
are applied on top of those inherited from the parent process. If you need to remove an inherited variable, set the corresponding value tonull
.
WithCredentials(...)
Sets domain, name and password of the user, under whom the child process is started.
Default: no credentials.
Examples:
- Set credentials directly:
var cmd = Cli.Wrap("git")
.WithCredentials(new Credentials(
domain: "some_workspace",
userName: "johndoe",
password: "securepassword123",
loadUserProfile: true
));
- Set credentials using a builder:
var cmd = Cli.Wrap("git")
.WithCredentials(creds => creds
.SetDomain("some_workspace")
.SetUserName("johndoe")
.SetPassword("securepassword123")
.LoadUserProfile()
);
Warning: Running a process under a different username is supported across all platforms, but other options are only available on Windows.
WithValidation(...)
Sets the strategy for validating the result of an execution.
Accepted values:
CommandResultValidation.None
β no validationCommandResultValidation.ZeroExitCode
β ensures zero exit code when the process exits
Default: CommandResultValidation.ZeroExitCode
.
Examples:
- Enable validation β will throw an exception if the process exits with a non-zero exit code:
var cmd = Cli.Wrap("git")
.WithValidation(CommandResultValidation.ZeroExitCode);
- Disable validation:
var cmd = Cli.Wrap("git")
.WithValidation(CommandResultValidation.None);
WithStandardInputPipe(...)
Sets the pipe source that will be used for the standard input stream of the process.
Default: PipeSource.Null
.
Read more about this method in the piping section.
WithStandardOutputPipe(...)
Sets the pipe target that will be used for the standard output stream of the process.
Default: PipeTarget.Null
.
Read more about this method in the piping section.
WithStandardErrorPipe(...)
Sets the pipe target that will be used for the standard error stream of the process.
Default: PipeTarget.Null
.
Read more about this method in the piping section.
Piping
CliWrap provides a very powerful and flexible piping model that allows you to redirect process's streams, transform input and output data, and even chain multiple commands together with minimal effort.
At its core, it's based on two abstractions: PipeSource
which provides data for the standard input stream, and PipeTarget
which reads data coming from the standard output stream or the standard error stream.
By default, command's input pipe is set to PipeSource.Null
and the output and error pipes are set to PipeTarget.Null
.
These objects effectively represent no-op stubs that provide empty input and discard all output respectively.
You can specify your own PipeSource
and PipeTarget
instances by calling the corresponding configuration methods on the command:
await using var input = File.OpenRead("input.txt");
await using var output = File.Create("output.txt");
await Cli.Wrap("foo")
.WithStandardInputPipe(PipeSource.FromStream(input))
.WithStandardOutputPipe(PipeTarget.ToStream(output))
.ExecuteAsync();
Alternatively, pipes can also be configured in a slightly terser way using pipe operators:
await using var input = File.OpenRead("input.txt");
await using var output = File.Create("output.txt");
await (input | Cli.Wrap("foo") | output).ExecuteAsync();
Both PipeSource
and PipeTarget
have many factory methods that let you create pipe implementations for different scenarios:
PipeSource
:PipeSource.Null
β represents an empty pipe sourcePipeSource.FromStream(...)
β pipes data from any readable streamPipeSource.FromFile(...)
β pipes data from a filePipeSource.FromBytes(...)
β pipes data from a byte arrayPipeSource.FromString(...)
β pipes from a text stringPipeSource.FromCommand(...)
β pipes data from the standard output of another command
PipeTarget
:PipeTarget.Null
β represents a pipe target that discards all dataPipeTarget.ToStream(...)
β pipes data into any writable streamPipeTarget.ToFile(...)
β pipes data into a filePipeTarget.ToStringBuilder(...)
β pipes data as text into aStringBuilder
PipeTarget.ToDelegate(...)
β pipes data as text, line-by-line, into anAction<string>
or aFunc<string, Task>
PipeTarget.Merge(...)
β merges multiple outbound pipes by replicating the same data across all of them
Warning: Using
PipeTarget.Null
results in the corresponding stream (stdout or stderr) not being opened for the underlying process at all. In the vast majority of cases, this behavior should be functionally equivalent to piping to a null stream, but without the performance overhead of consuming and discarding unneeded data. This may be undesirable in certain situations β in which case it's recommended to pipe to a null stream explicitly usingPipeTarget.ToStream(Stream.Null)
.
Below you can see some examples of what you can achieve with the help of CliWrap's piping feature:
- Pipe a string into stdin:
var cmd = "Hello world" | Cli.Wrap("foo");
await cmd.ExecuteAsync();
- Pipe stdout as text into a
StringBuilder
:
var stdOutBuffer = new StringBuilder();
var cmd = Cli.Wrap("foo") | stdOutBuffer;
await cmd.ExecuteAsync();
- Pipe a binary HTTP stream into stdin:
using var httpClient = new HttpClient();
await using var input = await httpClient.GetStreamAsync("https://example.com/image.png");
var cmd = input | Cli.Wrap("foo");
await cmd.ExecuteAsync();
- Pipe stdout of one command into stdin of another:
var cmd = Cli.Wrap("foo") | Cli.Wrap("bar") | Cli.Wrap("baz");
await cmd.ExecuteAsync();
- Pipe stdout and stderr into stdout and stderr of the parent process:
await using var stdOut = Console.OpenStandardOutput();
await using var stdErr = Console.OpenStandardError();
var cmd = Cli.Wrap("foo") | (stdOut, stdErr);
await cmd.ExecuteAsync();
- Pipe stdout into a delegate:
var cmd = Cli.Wrap("foo") | Debug.WriteLine;
await cmd.ExecuteAsync();
- Pipe stdout into a file and stderr into a
StringBuilder
:
var buffer = new StringBuilder();
var cmd = Cli.Wrap("foo") |
(PipeTarget.ToFile("output.txt"), PipeTarget.ToStringBuilder(buffer));
await cmd.ExecuteAsync();
- Pipe stdout into multiple files simultaneously:
var target = PipeTarget.Merge(
PipeTarget.ToFile("file1.txt"),
PipeTarget.ToFile("file2.txt"),
PipeTarget.ToFile("file3.txt")
);
var cmd = Cli.Wrap("foo") | target;
await cmd.ExecuteAsync();
- Pipe a string into stdin of one command, stdout of that command into stdin of another command, and then stdout and stderr of the last command into stdout and stderr of the parent process:
var cmd =
"Hello world" |
Cli.Wrap("foo").WithArguments("aaa") |
Cli.Wrap("bar").WithArguments("bbb") |
(Console.WriteLine, Console.Error.WriteLine);
await cmd.ExecuteAsync();
Execution models
CliWrap provides a few high-level execution models that offer alternative ways to reason about commands. These are essentially just extension methods that work by leveraging the piping feature shown earlier.
Buffered execution
This execution model lets you run a process while buffering its standard output and error streams in-memory. The buffered data can then be accessed after the command finishes executing.
In order to execute a command with buffering, call the ExecuteBufferedAsync()
extension method:
using CliWrap;
using CliWrap.Buffered;
var result = await Cli.Wrap("path/to/exe")
.WithArguments("--foo bar")
.ExecuteBufferedAsync();
var exitCode = result.ExitCode;
var stdOut = result.StandardOutput;
var stdErr = result.StandardError;
By default, ExecuteBufferedAsync()
assumes that the underlying process uses the default encoding (Console.OutputEncoding
) for writing text to the console.
To override this, specify the encoding explicitly by using one of the available overloads:
// Treat both stdout and stderr as UTF8-encoded text streams
var result = await Cli.Wrap("path/to/exe")
.WithArguments("--foo bar")
.ExecuteBufferedAsync(Encoding.UTF8);
// Treat stdout as ASCII-encoded and stderr as UTF8-encoded
var result = await Cli.Wrap("path/to/exe")
.WithArguments("--foo bar")
.ExecuteBufferedAsync(Encoding.ASCII, Encoding.UTF8);
Pull-based event stream
Besides executing a command as a task, CliWrap also supports an alternative model, in which the execution is represented as an event stream. This lets you start a command and react to the events it produces in real-time.
Those events are:
StartedCommandEvent
β received just once, when the command starts executing (contains process ID)StandardOutputCommandEvent
β received every time the underlying process writes a new line to the output stream (contains the text as a string)StandardErrorCommandEvent
β received every time the underlying process writes a new line to the error stream (contains the text as a string)ExitedCommandEvent
β received just once, when the command finishes executing (contains exit code)
To execute a command as a pull-based event stream, use the ListenAsync()
extension method:
using CliWrap;
using CliWrap.EventStream;
var cmd = Cli.Wrap("foo").WithArguments("bar");
await foreach (var cmdEvent in cmd.ListenAsync())
{
switch (cmdEvent)
{
case StartedCommandEvent started:
_output.WriteLine($"Process started; ID: {started.ProcessId}");
break;
case StandardOutputCommandEvent stdOut:
_output.WriteLine($"Out> {stdOut.Text}");
break;
case StandardErrorCommandEvent stdErr:
_output.WriteLine($"Err> {stdErr.Text}");
break;
case ExitedCommandEvent exited:
_output.WriteLine($"Process exited; Code: {exited.ExitCode}");
break;
}
}
The ListenAsync()
method starts the command and returns an object of type IAsyncEnumerable<CommandEvent>
, which you can iterate using the await foreach
construct introduced in C# 8.
When using this execution model, back pressure is facilitated by locking the pipes between each iteration of the loop, preventing unnecessary buffering of data in-memory.
Note: Just like with
ExecuteBufferedAsync()
, you can specify custom encoding forListenAsync()
using one of its overloads.
Push-based event stream
Similarly to the pull-based stream, you can also execute a command as a push-based event stream instead:
using CliWrap;
using CliWrap.EventStream;
using System.Reactive;
await cmd.Observe().ForEachAsync(cmdEvent =>
{
switch (cmdEvent)
{
case StartedCommandEvent started:
_output.WriteLine($"Process started; ID: {started.ProcessId}");
break;
case StandardOutputCommandEvent stdOut:
_output.WriteLine($"Out> {stdOut.Text}");
break;
case StandardErrorCommandEvent stdErr:
_output.WriteLine($"Err> {stdErr.Text}");
break;
case ExitedCommandEvent exited:
_output.WriteLine($"Process exited; Code: {exited.ExitCode}");
break;
}
});
In this case, Observe()
returns a cold IObservable<CommandEvent>
that represents an observable stream of command events.
You can use the set of extensions provided by Rx.NET to transform, filter, throttle, or otherwise manipulate this stream.
Unlike the pull-based event stream, this execution model does not involve any back pressure, meaning that the data is pushed to the observer at the rate it becomes available.
Note: Similarly to
ExecuteBufferedAsync()
, you can specify custom encoding forObserve()
using one of its overloads.
Combining execution models with custom pipes
The different execution models shown above are based on the piping model, but those two concepts are not mutually exclusive.
When running a command using one of the built-in execution models, existing pipe configurations are preserved and extended using PipeTarget.Merge(...)
.
This means that you can, for example, pipe a command to a file and simultaneously execute it as an event stream:
var cmd =
PipeSource.FromFile("input.txt") |
Cli.Wrap("foo") |
PipeTarget.ToFile("output.txt");
// Iterate as an event stream and pipe to a file at the same time
// (execution models preserve configured pipes)
await foreach (var cmdEvent in cmd.ListenAsync())
{
// ...
}
Timeout and cancellation
Command execution is asynchronous in nature as it involves a completely separate process. In many cases, it may be useful to implement an abortion mechanism to stop the execution before it finishes, either through a manual trigger or a timeout.
To do that, issue the corresponding CancellationToken
and pass it when calling ExecuteAsync()
:
using var cts = new CancellationTokenSource();
// Cancel after a timeout of 10 seconds
cts.CancelAfter(TimeSpan.FromSeconds(10));
var result = await Cli.Wrap("path/to/exe").ExecuteAsync(cts.Token);
In the event of a cancellation request, the underlying process will be killed and ExecuteAsync()
will throw an exception of type OperationCanceledException
(or its derivative, TaskCanceledException
).
You will need to catch this exception in your code to recover from cancellation:
try
{
await Cli.Wrap("path/to/exe").ExecuteAsync(cts.Token);
}
catch (OperationCanceledException)
{
// Command was canceled
}
Besides outright killing the process, you can also request cancellation in a more graceful way by sending an interrupt signal.
To do that, use the overload of ExecuteAsync()
that accepts two cancellation tokens:
using var forcefulCts = new CancellationTokenSource();
using var gracefulCts = new CancellationTokenSource();
// Cancel gracefully after a timeout of 10 seconds
gracefulCts.CancelAfter(TimeSpan.FromSeconds(10));
// Cancel forcefully after a timeout of 15 seconds
// (i.e. 5 seconds after graceful cancellation)
forcefulCts.CancelAfter(TimeSpan.FromSeconds(15));
var result = await Cli.Wrap("path/to/exe").ExecuteAsync(forcefulCts.Token, gracefulCts.Token);
Requesting graceful cancellation in CliWrap is functionally equivalent to pressing Ctrl+C
in the console window.
The underlying process may handle this signal to perform last-minute critical work before finally exiting on its own terms.
Warning: Because graceful cancellation is inherently cooperative, it's possible that the underlying process may choose to ignore the request or take too long to fulfill it. Consider always employing forceful cancellation as a backup to prevent the command from hanging.
Note: Similarly to
ExecuteAsync()
, cancellation is also supported byExecuteBufferedAsync()
,ListenAsync()
, andObserve()
.
Note: You can read more about
CancellationTokenSource
andCancellationToken
to learn how they work in .NET here.
Retrieving process ID
The task returned by ExecuteAsync()
and ExecuteBufferedAsync()
is in fact not a regular Task<T>
, but an instance of CommandTask<T>
.
This is a specialized awaitable object that contains additional information about the execution of the associated command.
You can inspect the task while it's running to get the ID of the underlying process:
var task = Cli.Wrap("path/to/exe")
.WithArguments("--foo bar")
.ExecuteAsync();
// Get the process ID (for example, for logging purposes)
var processId = task.ProcessId;
// Wait for the task to complete
await task;
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 is compatible. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
.NET Framework | net461 was computed. net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETCoreApp 3.0
- No dependencies.
-
.NETFramework 4.6.2
- Microsoft.Bcl.AsyncInterfaces (>= 7.0.0)
- System.Buffers (>= 4.5.1)
- System.Memory (>= 4.5.5)
- System.Runtime.InteropServices.RuntimeInformation (>= 4.3.0)
- System.Threading.Tasks.Extensions (>= 4.5.4)
- System.ValueTuple (>= 4.5.0)
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 7.0.0)
- System.Buffers (>= 4.5.1)
- System.Memory (>= 4.5.5)
- System.Threading.Tasks.Extensions (>= 4.5.4)
-
.NETStandard 2.1
- No dependencies.
NuGet packages (129)
Showing the top 5 NuGet packages that depend on CliWrap:
Package | Downloads |
---|---|
YoutubeExplode.Converter
Extension for YoutubeExplode that provides an interface to download and convert videos using FFmpeg |
|
Pulumi.Automation
Pulumi Automation API, the programmatic interface for driving Pulumi programs without the CLI. |
|
Weasyprint.Wrapped
A .netstandaard wrapper for the weasyprint PDF printer. This package is standalone and does not required you to install any software on your server. It runs on windows and linux. |
|
Devlooped.JQ
A .NET-friendly distribution of the official JQ implementation |
|
ModularPipelines
Write your pipelines in C#! |
GitHub repositories (58)
Showing the top 5 popular GitHub repositories that depend on CliWrap:
Repository | Stars |
---|---|
icsharpcode/ILSpy
.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
|
|
d2phap/ImageGlass
🏞 A lightweight, versatile image viewer
|
|
BililiveRecorder/BililiveRecorder
ε½ζ姬 | mikufans ηζΎιε½εΆ
|
|
TheJoeFin/Text-Grab
Use OCR in Windows quickly and easily with Text Grab. With optional background process and notifications.
|
|
Tyrrrz/YoutubeExplode
Abstraction layer over YouTube's internal API
|
Version | Downloads | Last updated |
---|---|---|
3.6.7 | 7,812 | 10/25/2024 |
3.6.6 | 1,308,140 | 1/16/2024 |
3.6.5 | 2,291 | 1/15/2024 |
3.6.4 | 1,014,289 | 6/23/2023 |
3.6.3 | 167,245 | 5/18/2023 |
3.6.2 | 1,265 | 5/18/2023 |
3.6.1 | 74,485 | 4/27/2023 |
3.4.1 | 130,405 | 1/30/2022 |
3.4.0 | 59,542 | 1/7/2022 |
3.3.3 | 1,193,428 | 8/31/2021 |
3.3.2 | 1,187,773 | 4/1/2021 |
3.3.1 | 47,209 | 2/21/2021 |
3.3.0 | 32,388 | 12/29/2020 |
3.2.4 | 25,499 | 12/5/2020 |
3.2.3 | 110,866 | 11/4/2020 |
3.2.2 | 69,804 | 10/14/2020 |
3.2.1 | 3,897 | 10/13/2020 |
3.2.0 | 86,407 | 9/17/2020 |
3.1.1 | 102,510 | 9/6/2020 |
3.1.0 | 52,472 | 6/27/2020 |
3.0.3 | 3,252 | 6/22/2020 |
3.0.2 | 388,912 | 4/28/2020 |
3.0.1 | 1,681 | 4/26/2020 |
3.0.0 | 74,437 | 2/27/2020 |
3.0.0-alpha3 | 1,431 | 2/23/2020 |
3.0.0-alpha2 | 1,281 | 2/22/2020 |
3.0.0-alpha | 1,344 | 2/21/2020 |
2.5.0 | 33,314 | 10/30/2019 |
2.4.0 | 48,725 | 10/3/2019 |
2.3.1 | 31,683 | 7/10/2019 |
2.3.0 | 6,950 | 5/28/2019 |
2.2.2 | 6,632 | 5/10/2019 |
2.2.1 | 5,482 | 4/1/2019 |
2.2.0 | 42,451 | 12/20/2018 |
2.1.0 | 6,640 | 10/14/2018 |
2.0.1 | 2,396 | 9/17/2018 |
2.0.0 | 2,332 | 9/12/2018 |
1.8.5 | 9,453 | 6/9/2018 |
1.8.4 | 4,928 | 3/9/2018 |
1.8.3 | 2,354 | 3/3/2018 |
1.8.2 | 2,597 | 2/2/2018 |
1.8.1 | 2,394 | 1/25/2018 |
1.8.0 | 2,338 | 1/25/2018 |
1.7.6-alpha | 2,175 | 1/24/2018 |
1.7.5 | 2,673 | 1/21/2018 |
1.7.4 | 2,477 | 1/10/2018 |
1.7.3 | 2,414 | 12/19/2017 |
1.7.2 | 2,341 | 12/19/2017 |
1.7.1 | 2,220 | 12/5/2017 |
1.7.0 | 2,190 | 12/4/2017 |
1.6.1 | 2,572 | 11/25/2017 |
1.6.0 | 2,522 | 10/18/2017 |
1.5.3 | 2,519 | 10/7/2017 |
1.5.2 | 2,485 | 10/2/2017 |
1.5.1 | 2,523 | 9/8/2017 |
1.5.0 | 2,326 | 7/25/2017 |
1.4.0 | 2,258 | 7/25/2017 |
1.3.0 | 2,360 | 6/30/2017 |
1.2.3 | 2,289 | 6/1/2017 |
1.2.2 | 2,250 | 5/4/2017 |
1.2.1 | 2,242 | 4/27/2017 |
1.2.0 | 2,274 | 4/27/2017 |
1.1.0 | 2,224 | 4/26/2017 |
1.0.0 | 5,825 | 4/11/2017 |