diff --git a/CliWrap.Immersive.Tests/CliWrap.Immersive.Tests.csproj b/CliWrap.Immersive.Tests/CliWrap.Immersive.Tests.csproj
new file mode 100644
index 00000000..36fc8773
--- /dev/null
+++ b/CliWrap.Immersive.Tests/CliWrap.Immersive.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net8.0
+ $(TargetFrameworks);net48
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CliWrap.Immersive.Tests/ExecutionSpecs.cs b/CliWrap.Immersive.Tests/ExecutionSpecs.cs
new file mode 100644
index 00000000..02050f34
--- /dev/null
+++ b/CliWrap.Immersive.Tests/ExecutionSpecs.cs
@@ -0,0 +1,67 @@
+using System.Threading.Tasks;
+using FluentAssertions;
+using Xunit;
+using static CliWrap.Immersive.Spells;
+using Dummy = CliWrap.Tests.Dummy;
+
+namespace CliWrap.Immersive.Tests;
+
+public class ExecutionSpecs
+{
+ [Fact(Timeout = 15000)]
+ public async Task I_can_execute_a_command_with_magic_and_get_the_exit_code()
+ {
+ // Arrange
+ var cmd = Command(Dummy.Program.FilePath);
+
+ // Act
+ int result = await cmd;
+
+ // Assert
+ result.Should().Be(0);
+ }
+
+ [Fact(Timeout = 15000)]
+ public async Task I_can_execute_a_command_with_magic_and_verify_that_it_succeeded()
+ {
+ // Arrange
+ var cmd = Command(Dummy.Program.FilePath);
+
+ // Act
+ bool result = await cmd;
+
+ // Assert
+ result.Should().BeTrue();
+ }
+
+ [Fact(Timeout = 15000)]
+ public async Task I_can_execute_a_command_with_magic_and_get_the_stdout()
+ {
+ // Arrange
+ var cmd = Command(Dummy.Program.FilePath, [ "echo", "Hello stdout" ]);
+
+ // Act
+ string result = await cmd;
+
+ // Assert
+ result.Trim().Should().Be("Hello stdout");
+ }
+
+ [Fact(Timeout = 15000)]
+ public async Task I_can_execute_a_command_with_magic_and_get_the_stdout_and_stderr()
+ {
+ // Arrange
+ var cmd = Command(
+ Dummy.Program.FilePath,
+ [ "echo", "Hello stdout and stderr", "--target", "all" ]
+ );
+
+ // Act
+ var (exitCode, stdOut, stdErr) = await cmd;
+
+ // Assert
+ exitCode.Should().Be(0);
+ stdOut.Trim().Should().Be("Hello stdout and stderr");
+ stdErr.Trim().Should().Be("Hello stdout and stderr");
+ }
+}
diff --git a/CliWrap.Immersive.Tests/xunit.runner.json b/CliWrap.Immersive.Tests/xunit.runner.json
new file mode 100644
index 00000000..f41def88
--- /dev/null
+++ b/CliWrap.Immersive.Tests/xunit.runner.json
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
+ "appDomain": "denied",
+ "methodDisplayOptions": "all",
+ "methodDisplay": "method"
+}
\ No newline at end of file
diff --git a/CliWrap.Immersive/CliWrap.Immersive.csproj b/CliWrap.Immersive/CliWrap.Immersive.csproj
new file mode 100644
index 00000000..8b4a5847
--- /dev/null
+++ b/CliWrap.Immersive/CliWrap.Immersive.csproj
@@ -0,0 +1,32 @@
+
+
+
+ netstandard2.0;netstandard2.1;netcoreapp3.0;net462
+ true
+
+
+ false
+
+
+
+ Extension for CliWrap that provides a shell-like experience for executing commands
+ https://github.com/Tyrrrz/CliWrap/tree/master/CliWrap.Immersive
+ favicon.png
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CliWrap.Immersive/ImmersiveCommandResult.cs b/CliWrap.Immersive/ImmersiveCommandResult.cs
new file mode 100644
index 00000000..51d3fa9f
--- /dev/null
+++ b/CliWrap.Immersive/ImmersiveCommandResult.cs
@@ -0,0 +1,45 @@
+using System;
+using CliWrap.Buffered;
+
+namespace CliWrap.Immersive;
+
+///
+/// Result of a command execution, with buffered text data from standard output and standard error streams.
+/// Includes additional operator overloads for convenience in immersive mode.
+///
+public partial class ImmersiveCommandResult(
+ int exitCode,
+ DateTimeOffset startTime,
+ DateTimeOffset exitTime,
+ string standardOutput,
+ string standardError
+) : BufferedCommandResult(exitCode, startTime, exitTime, standardOutput, standardError)
+{
+ ///
+ /// Deconstructs the result into its components.
+ ///
+ public void Deconstruct(out int exitCode, out string standardOutput, out string standardError)
+ {
+ exitCode = ExitCode;
+ standardOutput = StandardOutput;
+ standardError = StandardError;
+ }
+}
+
+public partial class ImmersiveCommandResult
+{
+ ///
+ /// Converts the result to an integer value that corresponds to the property.
+ ///
+ public static implicit operator int(ImmersiveCommandResult result) => result.ExitCode;
+
+ ///
+ /// Converts the result to a boolean value that corresponds to the property.
+ ///
+ public static implicit operator bool(ImmersiveCommandResult result) => result.IsSuccess;
+
+ ///
+ /// Converts the result to a string value that corresponds to the property.
+ ///
+ public static implicit operator string(ImmersiveCommandResult result) => result.StandardOutput;
+}
diff --git a/CliWrap.Immersive/MagicExtensions.cs b/CliWrap.Immersive/MagicExtensions.cs
new file mode 100644
index 00000000..a4639bdd
--- /dev/null
+++ b/CliWrap.Immersive/MagicExtensions.cs
@@ -0,0 +1,33 @@
+using System.Runtime.CompilerServices;
+using CliWrap.Buffered;
+
+namespace CliWrap.Immersive;
+
+///
+/// Extensions for types.
+///
+public static class MagicExtensions
+{
+ ///
+ /// Executes the command with magic.
+ ///
+ public static CommandTask ExecuteMagicalAsync(this Command command) =>
+ command
+ .ExecuteBufferedAsync()
+ .Select(
+ r =>
+ new ImmersiveCommandResult(
+ r.ExitCode,
+ r.StartTime,
+ r.ExitTime,
+ r.StandardOutput,
+ r.StandardError
+ )
+ );
+
+ ///
+ /// Executes the command with buffering and returns the awaiter for the result.
+ ///
+ public static TaskAwaiter GetAwaiter(this Command command) =>
+ command.ExecuteMagicalAsync().GetAwaiter();
+}
diff --git a/CliWrap.Immersive/Readme.md b/CliWrap.Immersive/Readme.md
new file mode 100644
index 00000000..6ec5c575
--- /dev/null
+++ b/CliWrap.Immersive/Readme.md
@@ -0,0 +1,95 @@
+# CliWrap.Immersive
+
+[![Version](https://img.shields.io/nuget/v/CliWrap.Immersive.svg)](https://nuget.org/packages/CliWrap.Immersive)
+[![Downloads](https://img.shields.io/nuget/dt/CliWrap.Immersive.svg)](https://nuget.org/packages/CliWrap.Immersive)
+
+**CliWrap.Immersive** is an extension package for **CliWrap** that provides a shell-like experience for executing commands.
+
+## Install
+
+- 📦 [NuGet](https://nuget.org/packages/CliWrap.Immersive): `dotnet add package CliWrap.Immersive`
+
+## Usage
+
+### Quick overview
+
+Add `using static CliWrap.Immersive.Spells;` to your file and start writing scripts like this:
+
+```csharp
+using static CliWrap.Immersive.Spells;
+
+// Create commands using the _() method, execute them simply by awaiting.
+// Check for exit code directly in if statements.
+if (!await _("git"))
+{
+ WriteErrorLine("Git is not installed");
+ Exit(1);
+ return;
+}
+
+// Executing a command returns an object which has implicit conversions to:
+// - int (exit code)
+// - bool (exit code == 0)
+// - string (standard output)
+string version = await _("git", "--version"); // git version 2.43.0.windows.1
+WriteLine($"Git version: {version}");
+
+// Just like with regular CliWrap, arguments are automatically
+// escaped to form a well-formed command line string.
+// Non-string arguments of many different types can also be passed directly.
+await _("git", "clone", "https://github.com/Tyrrrz/CliWrap", "--depth", 0);
+
+// Resolve environment variables easily with the Environment() method.
+var commit = Environment("HEAD_SHA");
+
+// Prompt the user for additional input with the ReadLine() method.
+// Check for truthy values using the IsTruthy() method.
+if (!IsTruthy(commit))
+ commit = ReadLine("Enter commit hash");
+
+// Just like with regular CliWrap, arguments are automatically
+// escaped to form a well-formed command line string.
+await _("git", "checkout", commit);
+
+// Set environment variables using the Environment() method.
+// This returns an object that you can dispose to restore the original value.
+using (Environment("HEAD_SHA", "deadbeef"))
+{
+ await _("/bin/sh", "-c", "echo $HEAD_SHA"); // deadbeef
+
+ // You can also run commands in the default system shell directly
+ // using the Shell() method.
+ await Shell("echo $HEAD_SHA"); // deadbeef
+}
+
+// Same with the WorkingDirectory() method.
+using (WorkingDirectory("/tmp/my-script/"))
+{
+ // Get the current working directory using the same method.
+ var cwd = WorkingDirectory();
+}
+
+// Magic also supports CliWrap's piping syntax.
+var commits = new List(); // this will contain commit hashes
+await (
+ _("git", "log", "--pretty=format:%H") | commits.Add
+);
+```
+
+### Executing commands
+
+In order to run a command with **CliWrap.Immersive**, use the `_` method with the target file path:
+
+```csharp
+using CliWrap.Immersive;
+using static CliWrap.Immersive.Shell;
+
+await _("dotnet");
+var version = await _("dotnet", "--version");
+```
+
+Piping works the same way as it does in regular **CliWrap**:
+
+```csharp
+await ("standard input" | _("dotnet", "run"));
+```
\ No newline at end of file
diff --git a/CliWrap.Immersive/Spells.cs b/CliWrap.Immersive/Spells.cs
new file mode 100644
index 00000000..c6dd39eb
--- /dev/null
+++ b/CliWrap.Immersive/Spells.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using CliWrap.Immersive.Utils;
+
+namespace CliWrap.Immersive;
+
+///
+/// Utility methods for working with the shell environment.
+///
+public static class Spells
+{
+ ///
+ /// Default standard input pipe used for commands created by methods in this class.
+ ///
+ public static PipeSource DefaultStandardInputPipe { get; set; } =
+ PipeSource.FromStream(Console.OpenStandardInput());
+
+ ///
+ /// Default standard output pipe used for commands created by methods in this class.
+ ///
+ public static PipeTarget DefaultStandardOutputPipe { get; set; } =
+ PipeTarget.ToStream(Console.OpenStandardOutput());
+
+ ///
+ /// Default standard error pipe used for commands created by methods in this class.
+ ///
+ public static PipeTarget DefaultStandardErrorPipe { get; set; } =
+ PipeTarget.ToStream(Console.OpenStandardError());
+
+ ///
+ /// Creates a new command with the specified target file path.
+ ///
+ public static Command Command(string targetFilePath) =>
+ Cli.Wrap(targetFilePath)
+ .WithStandardInputPipe(DefaultStandardInputPipe)
+ .WithStandardOutputPipe(DefaultStandardOutputPipe)
+ .WithStandardErrorPipe(DefaultStandardErrorPipe);
+
+ ///
+ /// Creates a new command with the specified target file path and command-line arguments.
+ ///
+ public static Command Command(string targetFilePath, IEnumerable arguments) =>
+ Command(targetFilePath).WithArguments(arguments);
+
+ private static Command Shell(Command command) =>
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
+ ? Cli.Wrap("cmd.exe")
+ .WithArguments(new[] { "/c", command.TargetFilePath, command.Arguments })
+ : Cli.Wrap("/bin/sh")
+ .WithArguments(new[] { "-c", command.TargetFilePath, command.Arguments });
+
+ ///
+ /// Creates a new command with the specified target file path and command-line arguments,
+ /// wrapped in the default system shell.
+ ///
+ ///
+ /// The default system shell is determined based on the current operating system:
+ /// cmd.exe on Windows, /bin/sh on Linux and macOS.
+ ///
+ public static Command Shell(string targetFilePath, IEnumerable arguments) =>
+ Shell(Command(targetFilePath).WithArguments(arguments));
+
+ ///
+ /// Gets the current working directory.
+ ///
+ public static string WorkingDirectory() => Directory.GetCurrentDirectory();
+
+ ///
+ /// Changes the current working directory to the specified path.
+ ///
+ ///
+ /// You can dispose the returned object to reset the path back to its previous value.
+ ///
+ public static IDisposable WorkingDirectory(string path)
+ {
+ var lastPath = WorkingDirectory();
+ Directory.SetCurrentDirectory(path);
+
+ return Disposable.Create(() => Directory.SetCurrentDirectory(lastPath));
+ }
+
+ ///
+ /// Gets the value of the specified environment variable.
+ ///
+ public static string? Environment(string name) =>
+ System.Environment.GetEnvironmentVariable(name);
+
+ ///
+ /// Sets the value of the specified environment variable.
+ ///
+ ///
+ /// You can dispose the returned object to reset the environment variable back to its previous value.
+ ///
+ public static IDisposable Environment(string name, string? value)
+ {
+ var lastValue = System.Environment.GetEnvironmentVariable(name);
+ System.Environment.SetEnvironmentVariable(name, value);
+
+ return Disposable.Create(() => System.Environment.SetEnvironmentVariable(name, lastValue));
+ }
+
+ ///
+ /// Terminates the current process with the specified exit code.
+ ///
+ public static void Exit(int exitCode = 0) => System.Environment.Exit(exitCode);
+
+ ///
+ /// Prompt the user for input, with an optional message.
+ ///
+ public static string? ReadLine(string? message = null)
+ {
+ if (!string.IsNullOrWhiteSpace(message))
+ Console.Write(message);
+
+ return Console.ReadLine();
+ }
+
+ ///
+ /// Writes the specified text to the standard output stream.
+ ///
+ public static void Write(string text) => Console.Write(text);
+
+ ///
+ /// Writes the specified text to the standard output stream, followed by a line terminator.
+ ///
+ public static void WriteLine(string text) => Write(text + System.Environment.NewLine);
+
+ ///
+ /// Writes the specified text to the standard error stream.
+ ///
+ public static void WriteError(string text) => Console.Error.Write(text);
+
+ ///
+ /// Writes the specified text to the standard error stream, followed by a line terminator.
+ ///
+ public static void WriteErrorLine(string text) => WriteError(text + System.Environment.NewLine);
+
+ ///
+ /// Checks if the specified value is truthy.
+ ///
+ public static bool IsTruthy(bool value) => value;
+
+ ///
+ /// Checks if the specified value is truthy.
+ ///
+ public static bool IsTruthy(string? value) => !string.IsNullOrEmpty(value);
+
+ ///
+ /// Checks if the specified value is truthy.
+ ///
+ public static bool IsTruthy(int? value) => value.HasValue && value.Value != default;
+
+ ///
+ /// Checks if the specified value is truthy.
+ ///
+ public static bool IsTruthy(long? value) => value.HasValue && value.Value != default;
+
+ ///
+ /// Checks if the specified value is truthy.
+ ///
+ public static bool IsTruthy(IEnumerable values) => values.Any();
+
+ ///
+ /// Checks if the specified value is truthy.
+ ///
+ public static bool IsTruthy(object? value) =>
+ value switch
+ {
+ null => false,
+ bool x => IsTruthy(x),
+ string x => IsTruthy(x),
+ int x => IsTruthy(x),
+ long x => IsTruthy(x),
+ IEnumerable