TimeWarp.Amuru 1.0.0-beta.8

Prefix Reserved
This is a prerelease version of TimeWarp.Amuru.
dotnet add package TimeWarp.Amuru --version 1.0.0-beta.8
                    
NuGet\Install-Package TimeWarp.Amuru -Version 1.0.0-beta.8
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TimeWarp.Amuru" Version="1.0.0-beta.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TimeWarp.Amuru" Version="1.0.0-beta.8" />
                    
Directory.Packages.props
<PackageReference Include="TimeWarp.Amuru" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TimeWarp.Amuru --version 1.0.0-beta.8
                    
#r "nuget: TimeWarp.Amuru, 1.0.0-beta.8"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TimeWarp.Amuru@1.0.0-beta.8
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TimeWarp.Amuru&version=1.0.0-beta.8&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=TimeWarp.Amuru&version=1.0.0-beta.8&prerelease
                    
Install as a Cake Tool

Stars workflow Forks License Issues Open OpenSSF Scorecard

nuget nuget

Twitter Dotnet

Discord Twitter Twitter

<img src="https://raw.githubusercontent.com/TimeWarpEngineering/timewarpengineering.github.io/refs/heads/master/images/LogoNoMarginNoShadow.svg" alt="logo" height="120" style="float: right" />

TimeWarp.Amuru

Amuru means "command" in Swahili

TimeWarp.Amuru is a powerful fluent API library for elegant command-line execution in C#. It transforms shell scripting into a type-safe, IntelliSense-friendly experience with a simple static Builder() method, async operations, and proper error handling.

Designed for modern C# developers, TimeWarp.Amuru brings the power of shell scripting directly into your C# code. Whether you're building automation tools, DevOps scripts, or integrating command-line tools into your applications, TimeWarp.Amuru provides the elegant, type-safe API you need.

Why TimeWarp.Amuru?

  • Zero Learning Curve: If you know C#, you already know how to use TimeWarp.Amuru
  • IntelliSense Everything: Full IDE support with autocomplete, parameter hints, and documentation
  • Type Safety: Catch errors at compile-time, not runtime
  • No String Escaping Hell: Use C# arrays and parameters naturally
  • Built for .NET 10: Modern C# features and performance optimizations
  • Script or Library: Use it in quick scripts or production applications

Give a Star! ⭐

If you find this project useful, please give it a star. Thanks!

Quick Start

#!/usr/bin/dotnet run
#:package TimeWarp.Amuru

using TimeWarp.Amuru;

// Default behavior - stream to console (like bash/PowerShell)
await Shell.Builder("npm", "install").RunAsync();

// Capture output when needed
var result = await Shell.Builder("git", "status").CaptureAsync();
if (result.Success)
{
    Console.WriteLine($"Git says: {result.Stdout}");
}

// Stream large files without memory issues
await foreach (var line in Shell.Builder("tail", "-f", "/var/log/app.log").StreamStdoutAsync())
{
    Console.WriteLine($"Log: {line}");
}

// Chain commands with pipelines
var result = await Shell.Builder("find", ".", "-name", "*.cs")
    .Pipe("grep", "async")
    .CaptureAsync();
Console.WriteLine($"Found {result.Lines.Length} async files");

// Work with CommandOutput
var output = await Shell.Builder("docker", "ps").CaptureAsync();
Console.WriteLine($"Exit code: {output.ExitCode}");
Console.WriteLine($"Success: {output.Success}");
Console.WriteLine($"Stdout: {output.Stdout}");
Console.WriteLine($"Stderr: {output.Stderr}");
Console.WriteLine($"Combined: {output.Combined}");

// Use the fluent builder API for complex commands
var result = await Shell.Builder("git")
    .WithArguments("log", "--oneline", "-n", "10")
    .WithWorkingDirectory("/my/repo")
    .WithCancellationToken(cancellationToken)
    .CaptureAsync();

// Provide standard input to commands
var grepResult = await Shell.Builder("grep")
    .WithArguments("pattern")
    .WithStandardInput("line1\nline2 with pattern\nline3")
    .CaptureAsync();

// Interactive selection with Fzf
var selectedFile = await Fzf.Builder()
    .FromInput("file1.txt", "file2.txt", "file3.txt")
    .WithPreview("cat {}")
    .SelectAsync();

// Interactive pipeline - find and select files
var chosenFile = await Shell.Builder("find")
    .WithArguments(".", "-name", "*.cs")
    .Pipe("fzf", "--preview", "head -20 {}")
    .SelectAsync();

// Full interactive mode for editors, REPLs, etc.
await Shell.Builder("vim")
    .WithArguments("myfile.txt")
    .PassthroughAsync();

Installation

dotnet add package TimeWarp.Amuru --prerelease

Or reference in your C# script:

#:package TimeWarp.Amuru@1.0.0-beta.3

Check out the latest NuGet package: TimeWarp.Amuru nuget

DotNet Commands

// Global dotnet options
var sdks = await DotNet.WithListSdks().CaptureAsync();
var runtimes = await DotNet.WithListRuntimes().CaptureAsync();
var version = await DotNet.WithVersion().CaptureAsync();

// Base builder for custom arguments
var result = await DotNet.Builder()
    .WithArguments("--list-sdks")
    .CaptureAsync();

// Build and test with streaming output
await DotNet.Build()
    .WithConfiguration("Release")
    .RunAsync();

await DotNet.Test()
    .WithFilter("Category=Unit")
    .RunAsync();

Key Features

  • Shell-Like Default: RunAsync() streams to console just like bash/PowerShell
  • Explicit Capture: CaptureAsync() for when you need to process output
  • Memory-Efficient Streaming: IAsyncEnumerable for large data without buffering
  • Complete Output Access: CommandOutput with Stdout, Stderr, Combined, and ExitCode
  • Fluent Interface: Chain operations naturally with .Pipe() and builder methods
  • Async-First Design: All operations support modern async/await patterns
  • Smart Error Handling: Commands throw on errors by default, with opt-in graceful degradation
  • Pipeline Support: Chain commands with Unix-like pipe semantics
  • Standard Input Support: Provide stdin to commands with .WithStandardInput()
  • NO CACHING Philosophy: Like shells, commands run fresh every time
  • Configuration Options: Working directory, environment variables, and more
  • Cancellation Support: Full CancellationToken support for timeouts and manual cancellation
  • Cross-Platform: Works on Windows, Linux, and macOS
  • Command Builders: Fluent builders for complex commands (DotNet, Fzf, Ghq, Gwq)
  • Interactive Commands: PassthroughAsync() for editors, SelectAsync() for selection tools
  • .NET 10 Script Support: AppContext extensions and ScriptContext for file-based apps

Output Handling

Core API Methods

TimeWarp.Amuru provides clear, purpose-built methods for different scenarios:

// RunAsync() - Default shell behavior, streams to console
await Shell.Builder("npm", "install").RunAsync();
// Returns: exit code (int)
// Console output: real-time streaming

// CaptureAsync() - Silent execution with full output capture
var result = await Shell.Builder("git", "status").CaptureAsync();
// Returns: CommandOutput with all streams
// Console output: none (silent)

// PassthroughAsync() - Full terminal control for interactive tools
await Shell.Builder("vim", "file.txt").PassthroughAsync();
// Returns: void
// Console output: direct terminal passthrough

// SelectAsync() - Selection tools (shows UI, captures selection)
var selected = await Fzf.Builder()
    .FromInput("option1", "option2")
    .SelectAsync();
// Returns: selected string
// Console output: UI on stderr, selection captured from stdout

The CommandOutput Type

var output = await Shell.Builder("docker", "ps").CaptureAsync();

// Access individual streams
Console.WriteLine($"Stdout: {output.Stdout}");
Console.WriteLine($"Stderr: {output.Stderr}");
Console.WriteLine($"Combined: {output.Combined}"); // Both in chronological order

// Check status
Console.WriteLine($"Exit code: {output.ExitCode}");
Console.WriteLine($"Success: {output.Success}"); // ExitCode == 0

// Convenience properties for line processing
foreach (var line in output.Lines) // Combined.Split('\n')
{
    ProcessLine(line);
}

Streaming Large Data

For commands that produce large amounts of data:

// Stream lines as they arrive (no buffering)
await foreach (var line in Shell.Builder("tail", "-f", "/var/log/app.log")
    .StreamStdoutAsync(cancellationToken))
{
    Console.WriteLine($"Log: {line}");
}

// Stream with LINQ-style processing
var errorLines = Shell.Builder("cat", "huge.log")
    .StreamStdoutAsync()
    .Where(line => line.Contains("ERROR"))
    .Take(100);

await foreach (var error in errorLines)
{
    LogError(error);
}

Method Comparison

Method Console Output Captures Returns Primary Use Case
RunAsync() ✅ Real-time Exit code Default scripting (80%)
CaptureAsync() ❌ Silent ✅ All streams CommandOutput Process output (15%)
PassthroughAsync() ✅ Direct void Interactive tools (4%)
SelectAsync() ✅ UI only ✅ Selection string Selection tools (1%)
StreamStdoutAsync() ✅ As stream IAsyncEnumerable Large data

Design Philosophy: NO CACHING

TimeWarp.Amuru intentionally does NOT cache command results:

// Shells don't cache - neither do we
await Shell.Builder("date").RunAsync();  // Shows current time
await Shell.Builder("date").RunAsync();  // Shows NEW current time

// If you need caching, it's trivial in C#:
private static CommandOutput? cachedResult;
var result = cachedResult ??= await Shell.Builder("expensive-command").CaptureAsync();

Why no caching?

  • Commands can have side effects
  • Results change over time
  • Shells don't cache
  • Users can trivially cache in C# if needed

Error Handling

TimeWarp.Amuru provides intelligent error handling that distinguishes between different failure types:

Default Behavior (Throws Exceptions)

// Throws CommandExecutionException on non-zero exit code
await Shell.Builder("ls", "/nonexistent").RunAsync();

// CaptureAsync also throws on failure by default
var result = await Shell.Builder("git", "invalid-command").CaptureAsync();

Graceful Degradation (Opt-in)

// Disable validation for graceful degradation
var result = await Shell.Builder("ls", "/nonexistent")
    .WithValidation(CommandResultValidation.None)
    .CaptureAsync();

if (!result.Success)
{
    Console.WriteLine($"Command failed with exit code: {result.ExitCode}");
    Console.WriteLine($"Error: {result.Stderr}");
}

Cancellation and Timeouts

// With explicit cancellation token
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await Shell.Builder("long-running-command")
    .RunAsync(cts.Token);

// With timeout via builder
await Shell.Builder("slow-command")
    .WithTimeout(TimeSpan.FromSeconds(10))
    .RunAsync();

// Timeout and external token are combined
await Shell.Builder("another-command")
    .WithTimeout(TimeSpan.FromSeconds(5))
    .RunAsync(userCancellationToken);

Testing and Mocking

TimeWarp.Amuru provides built-in support for mocking commands during testing through the CliConfiguration class:

Basic Mocking

// Set up mock commands for testing
CliConfiguration.SetCommandPath("fzf", "/path/to/mock/fzf");
CliConfiguration.SetCommandPath("git", "/path/to/mock/git");

// Your code using these commands will now use the mocks
var selected = await Fzf.Builder()
    .FromInput("option1", "option2", "option3")
    .SelectAsync(); // Uses mock fzf

var status = await Shell.Builder("git", "status")
    .CaptureAsync(); // Uses mock git

// Clean up after tests
CliConfiguration.Reset();

Creating Mock Executables

// Create a simple mock script
File.WriteAllText("/tmp/mock-fzf", "#!/bin/bash\necho 'mock-selection'");
await Shell.Builder("chmod", "+x", "/tmp/mock-fzf").RunAsync();

// Configure TimeWarp.Amuru to use it
CliConfiguration.SetCommandPath("fzf", "/tmp/mock-fzf");

// Now SelectAsync will use the mock
var selected = await Fzf.Builder()
    .FromInput("a", "b", "c")
    .SelectAsync(); // Returns "mock-selection"

Testing Interactive Commands

For commands like fzf that are normally interactive, you can either:

  1. Use mock executables as shown above
  2. Use non-interactive modes (e.g., fzf --filter)

API Reference

  • CliConfiguration.SetCommandPath(command, path) - Set custom executable path
  • CliConfiguration.ClearCommandPath(command) - Remove custom path for a command
  • CliConfiguration.Reset() - Clear all custom paths
  • CliConfiguration.HasCustomPath(command) - Check if command has custom path
  • CliConfiguration.AllCommandPaths - Get all configured paths

.NET 10 File-Based App Support

TimeWarp.Amuru provides specialized support for .NET 10's new file-based apps (single-file C# scripts) with AppContext extensions and ScriptContext for directory management.

  • AppContext Extensions - Clean access to script metadata without magic strings
  • ScriptContext - Automatic working directory management with cleanup guarantees
  • ProcessExit Handling - Cleanup runs even with Environment.Exit()

📖 See the documentation for detailed usage guides and examples.

Architecture

TimeWarp.Amuru is built on several key architectural principles:

  • Static Entry Point: Minimal ceremony with global Builder() method
  • Immutable Design: Thread-safe, readonly objects throughout
  • Integration Testing: Real command validation over mocking
  • Predictable Error Handling: Clear distinction between failure types
  • Opt-in Complexity: Advanced features available when needed

See our Architectural Decision Records for detailed design rationale.

Documentation

Example Scripts

See Spikes/CsScripts/ for example scripts demonstrating TimeWarp.Amuru usage patterns.

Unlicense

License
This project is licensed under the Unlicense.

Contributing

Your contributions are welcome! Before starting any work, please open a discussion.

See our Kanban board for current development tasks and priorities.

Contact

If you have an issue and don't receive a timely response, feel free to reach out on our Discord server.

Discord

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-beta.8 168 8/28/2025
1.0.0-beta.7 157 8/28/2025
1.0.0-beta.6 158 8/28/2025
1.0.0-beta.5 167 8/27/2025
1.0.0-beta.4 161 8/26/2025
1.0.0-beta.3 45 8/23/2025
1.0.0-beta.2 68 8/22/2025
1.0.0-beta.1 86 8/3/2025