Skip to content

TimeWarpEngineering/timewarp-cli

Repository files navigation

Stars workflow Forks License Issues Open OpenSSF Scorecard

nuget nuget

Twitter Dotnet

Discord Twitter Twitter

logo

TimeWarp.Cli

TimeWarp.Cli 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 Run() method, async operations, and proper error handling.

Designed for modern C# developers, TimeWarp.Cli 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.Cli provides the elegant, type-safe API you need.

Why TimeWarp.Cli?

  • Zero Learning Curve: If you know C#, you already know how to use TimeWarp.Cli
  • 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.Cli

using TimeWarp.Cli;

// Get command output as string
var date = await Run("date").GetStringAsync();
Console.WriteLine($"Current date: {date}");

// Process output line by line
var files = await Run("find", ".", "-name", "*.cs").GetLinesAsync();
foreach (var file in files)
{
    Console.WriteLine($"Found: {file}");
}

// Execute without capturing output
await Run("echo", "Hello World").ExecuteAsync();

// Chain commands with pipelines
var filteredFiles = await Run("find", ".", "-name", "*.cs")
    .Pipe("grep", "async")
    .GetLinesAsync();

// Use caching for expensive operations
var files = Run("find", "/large/dir", "-name", "*.log").Cached();
var errors = await files.Pipe("grep", "ERROR").GetLinesAsync();
var warnings = await files.Pipe("grep", "WARN").GetLinesAsync();
// Only one expensive find operation executed!

// C# scripts with arguments work seamlessly
await Run("./myscript.cs", "--verbose", "-o", "output.txt").ExecuteAsync();

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

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

// Use fluent command builders for .NET commands
var packages = await DotNet.ListPackages()
    .WithOutdated()
    .AsJson()
    .ToListAsync();

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

Installation

dotnet add package TimeWarp.Cli

Or reference in your C# script:

#:package TimeWarp.Cli

Check out the latest NuGet package: TimeWarp.Cli nuget

Key Features

  • Simple Static API: Global Run() method for immediate access
  • Fluent Interface: Chain operations naturally with .Pipe(), .Cached(), etc.
  • 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()
  • Opt-in Caching: Cache expensive command results with .Cached() method
  • 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
  • C# Script Support: Seamless execution of C# scripts with proper argument handling
  • Command Builders: Fluent builders for complex commands (DotNet, Fzf, Ghq, Gwq)

Error Handling

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

Default Behavior (Throws Exceptions)

// Throws CommandExecutionException on non-zero exit code
await Run("ls", "/nonexistent").GetStringAsync();

// Throws exception if command not found
await Run("nonexistentcommand").GetStringAsync();

Graceful Degradation (Opt-in)

// Returns empty string/array on command failure
var options = new CommandOptions().WithValidation(CommandResultValidation.None);
var result = await Run("ls", "/nonexistent", options).GetStringAsync(); // ""

// Note: Process start failures (command not found) always throw
await Run("nonexistentcommand", options).GetStringAsync(); // Still throws!

Special Cases

  • Empty/whitespace commands return empty results (no exception)
  • Null command options return empty results (defensive programming)
  • Pipeline failures propagate based on validation settings

Testing and Mocking

TimeWarp.Cli 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.Run()
    .FromInput("option1", "option2", "option3")
    .GetStringAsync(); // Uses mock fzf

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

Creating Mock Executables

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

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

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

Architecture

TimeWarp.Cli is built on several key architectural principles:

  • Static Entry Point: Minimal ceremony with global Run() 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.Cli 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

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •