Build your first command-line application in 5 minutes.
dotnet new console -n MyCliApp
cd MyCliApp
dotnet add package TimeWarp.NuruTimeWarp.Nuru provides two first-class patterns for defining commands. Choose based on your preference - both are fully supported.
Define routes inline with a fluent builder pattern:
using TimeWarp.Nuru;
NuruApp app = NuruApp.CreateBuilder(args)
.Map("add {x:double} {y:double}")
.WithHandler((double x, double y) => Console.WriteLine($"{x} + {y} = {x + y}"))
.AsCommand()
.Done()
.Map("greet {name}")
.WithHandler((string name) => Console.WriteLine($"Hello, {name}!"))
.AsQuery()
.Done()
.Build();
return await app.RunAsync(args);Define commands as classes with attributes - auto-discovered at build time:
Program.cs:
using TimeWarp.Nuru;
NuruApp app = NuruApp.CreateBuilder(args)
.DiscoverEndpoints()
.Build();
return await app.RunAsync(args);AddCommand.cs:
using TimeWarp.Nuru;
[NuruRoute("add", Description = "Add two numbers")]
public sealed class AddCommand : ICommand<Unit>
{
[Parameter(Description = "First number")]
public double X { get; set; }
[Parameter(Description = "Second number")]
public double Y { get; set; }
public sealed class Handler : ICommandHandler<AddCommand, Unit>
{
public ValueTask<Unit> Handle(AddCommand command, CancellationToken ct)
{
Console.WriteLine($"{command.X} + {command.Y} = {command.X + command.Y}");
return default;
}
}
}dotnet run -- add 15 25
# Output: 15 + 25 = 40
dotnet run -- greet Alice
# Output: Hello, Alice!| Aspect | Fluent DSL | Endpoints |
|---|---|---|
| Best for | Simple apps, scripts, quick prototypes | Larger apps, separation of concerns |
| Organization | Single file possible | Commands in separate files |
| Testability | Inline handlers | Handlers injected via DI |
| Discovery | Explicit .Map() calls |
Auto-discovered via source generator |
Both approaches:
- Use the same route pattern syntax
- Support async handlers
- Work with pipeline behaviors
- Are fully AOT compatible
"greet {name}" - "greet" is literal, {name} is a parameter
"add {x} {y}" - Multiple parameters
"{count:int}" - Integer
"{amount:double}" - Floating point
"{enabled:bool}" - Boolean (true/false)
"{when:datetime}" - DateTime
"{id:guid}" - GUID
"{name?}" - Optional (nullable in handler)
"{count:int?}" - Optional with type
"deploy --force" - Boolean flag
"deploy --env {env}" - Option with value
"deploy -f --env {e}" - Short and long forms
"echo {*words}" - Captures remaining args as string[]
- Command (
.AsCommand()): Performs an action, may have side effects - Query (
.AsQuery()): Returns information, no side effects - IdempotentCommand (
.AsIdempotentCommand()): Safe to retry
Add cross-cutting concerns like logging, telemetry, or authorization:
NuruApp app = NuruApp.CreateBuilder(args)
.AddBehavior(typeof(LoggingBehavior))
.AddBehavior(typeof(PerformanceBehavior))
.Map("deploy {env}")
.WithHandler((string env) => Deploy(env))
.AsCommand()
.Done()
.Build();
public sealed class LoggingBehavior : INuruBehavior
{
public async ValueTask HandleAsync(BehaviorContext context, Func<ValueTask> proceed)
{
Console.WriteLine($"[LOG] Handling {context.CommandName}");
await proceed();
Console.WriteLine($"[LOG] Completed {context.CommandName}");
}
}Options are automatically bound from configuration sections:
NuruApp app = NuruApp.CreateBuilder(args)
.Map("config show")
.WithHandler((IOptions<DatabaseOptions> dbOptions) =>
{
Console.WriteLine($"Host: {dbOptions.Value.Host}");
Console.WriteLine($"Port: {dbOptions.Value.Port}");
})
.AsQuery()
.Done()
.Build();
public class DatabaseOptions
{
public string Host { get; set; } = "localhost";
public int Port { get; set; } = 5432;
}Convention: DatabaseOptions binds to the "Database" config section (strips "Options" suffix).
NuruApp app = NuruApp.CreateBuilder(args)
.Map("greet {name}")
.WithHandler((string name) => Console.WriteLine($"Hello, {name}!"))
.AsQuery()
.Done()
.AddRepl(options =>
{
options.Prompt = "myapp> ";
options.WelcomeMessage = "Welcome! Type '--help' for commands.";
})
.Build();Run with -i or --interactive to enter REPL mode.
- Route Patterns - Complete syntax reference
- Pipeline Behaviors - Middleware for commands
- Endpoints - Deep dive on class-based commands
- Configuration - Settings and dependency injection
- REPL Mode - Interactive shell
- Samples - Working examples
TimeWarp.Nuru uses compile-time source generation for routing. Benefits:
- Zero runtime overhead
- Native AOT compatible
- Compile-time validation via Roslyn analyzer
- No reflection required
Yes! Use fluent DSL for simple commands and endpoints for complex ones in the same app:
NuruApp app = NuruApp.CreateBuilder(args)
.Map("version")
.WithHandler(() => Console.WriteLine("1.0.0"))
.AsQuery()
.Done()
.DiscoverEndpoints() // Also discovers endpoints
.Build();Help is automatic. Add descriptions with .WithDescription() or the Description property on [NuruRoute]:
.Map("deploy {env|Target environment}")
.WithDescription("Deploy the application to an environment")
.WithHandler((string env) => Deploy(env))
.AsCommand()
.Done()dotnet run -- --help
dotnet run -- deploy --help