TeCLI is a source-generated CLI parsing library for .NET that simplifies command-line interface development. Using Roslyn source generators and custom attributes, TeCLI automatically generates type-safe parsing and dispatching logic at compile time.
- Source Generation - Zero-runtime reflection, all code generated at compile time
- Attribute-Based API - Simple, declarative command and option definitions
- Type-Safe Parsing - Automatic parsing of all primitive types, enums, and collections
- Help Generation - Automatic
--helpand--versiontext generation - Roslyn Analyzers - 32 analyzers providing real-time feedback and error detection
- Dependency Injection - Multiple DI container integrations (Microsoft DI, Autofac, SimpleInjector, Jab, PureDI)
- Async Support - First-class support for async actions with
TaskandValueTask - Short/Long Options - Support for both
-eand--environmentstyle flags - Container Parameters - Group related options into complex types
- Nested Commands - Git-style hierarchical command structures with unlimited nesting
- Command Aliases - Multiple names for commands and actions
- Validation - Built-in validation attributes (
[Range],[RegularExpression],[FileExists], etc.) - Environment Variables - Automatic fallback to environment variables for options
- Global Options - Shared options across all commands
- Shell Completion - Generate completion scripts for Bash, Zsh, PowerShell, and Fish
- Exit Codes - Structured exit code support with exception mapping
- Middleware/Hooks - Pre/post execution hooks for authentication, logging, etc.
- Interactive Mode - Prompt for missing arguments with optional secure input
- Pipeline/Stream Support - Unix-style stdin/stdout piping with
Stream,TextReader,TextWriter - Testing Utilities -
TeCLI.Extensions.Testingpackage for easy CLI testing - Configuration Files - Load options from JSON, YAML, TOML, or INI files with profile support
- Localization (i18n) -
TeCLI.Extensions.Localizationfor internationalized help text and messages - Structured Output -
TeCLI.Extensions.Outputfor JSON, XML, YAML, and table output formatting
dotnet add package TeCLIFor dependency injection support:
dotnet add package TeCLI.Extensions.DependencyInjectionFor configuration file support:
dotnet add package TeCLI.Extensions.ConfigurationFor structured output formatting:
dotnet add package TeCLI.Extensions.Outputusing TeCLI;
[Command("greet", Description = "Greets the user")]
public class GreetCommand
{
[Primary(Description = "Say hello")]
public void Hello([Argument(Description = "Name to greet")] string name)
{
Console.WriteLine($"Hello, {name}!");
}
[Action("goodbye", Description = "Say goodbye")]
public void Goodbye([Argument] string name)
{
Console.WriteLine($"Goodbye, {name}!");
}
}public class Program
{
public static async Task Main(string[] args)
{
await CommandDispatcher.DispatchAsync(args);
}
}# Run primary action
myapp greet John
# Output: Hello, John!
# Run named action
myapp greet goodbye John
# Output: Goodbye, John!
# Get help
myapp --help
myapp greet --helpMarks a class as a CLI command.
- Name (required): Command name as it appears on the command line
- Description (optional): Description shown in help text
- Aliases (optional): Alternative names for the command
[Command("deploy", Description = "Deploy the application", Aliases = new[] { "dpl" })]
public class DeployCommand { }Marks a method as a named action (subcommand).
- Name (required): Action name
- Description (optional): Description shown in help text
- Aliases (optional): Alternative names for the action
[Action("start", Description = "Start the service", Aliases = new[] { "run", "begin" })]
public void Start() { }Marks a method as the default action when no action name is specified.
- Only one
[Primary]action allowed per command - Can be combined with
[Action]for both default and named invocation
[Primary(Description = "Run the default action")]
public void Execute() { }Marks a parameter or property as a named option.
- Name (required): Long option name (used with
--) - ShortName (optional): Single-character short name (used with
-) - Description (optional): Description for help text
- Required (optional): Mark as required (
Required = true) - EnvVar (optional): Environment variable fallback name
- Prompt (optional): Interactive prompt message if not provided
- SecurePrompt (optional): Mask input for sensitive data
- MutuallyExclusiveSet (optional): Group mutually exclusive options
[Option("environment", ShortName = 'e', Description = "Target environment")]
string environment
[Option("force", ShortName = 'f')] // Boolean switch
bool force
[Option("api-key", Required = true, EnvVar = "API_KEY")]
string apiKeyMarks a parameter or property as a positional argument.
- Description (optional): Description for help text
- Prompt (optional): Interactive prompt message if not provided
- SecurePrompt (optional): Mask input for sensitive data
- Arguments are positional and required by default
- Use default values to make arguments optional
[Argument(Description = "Input file path")]
string inputFile
[Argument(Description = "Output file path")]
string outputFile = "output.txt" // Optional with default
[Argument(Prompt = "Enter password", SecurePrompt = true)]
string passwordAll primitive .NET types and more are supported for options and arguments:
- Boolean:
bool(switches when used as options) - Characters:
char - Integers:
sbyte,byte,short,ushort,int,uint,long,ulong - Floating-point:
float,double,decimal - Strings:
string - Enums: Any enum type with case-insensitive parsing, including
[Flags]enums - Collections:
T[],List<T>,IEnumerable<T>,ICollection<T>,IList<T>,IReadOnlyCollection<T>,IReadOnlyList<T> - Common Types:
Uri,DateTime,DateTimeOffset,TimeSpan,Guid,FileInfo,DirectoryInfo - Streams:
Stream,TextReader,TextWriter,StreamReader,StreamWriter(for pipeline support) - Custom Types: Via
ITypeConverter<T>interface and[TypeConverter]attribute
TeCLI automatically generates comprehensive help text for your CLI.
The --help and -h switches are reserved and cannot be used as user-defined option names. They are available at both application and command levels.
myapp --helpShows all available commands with descriptions.
myapp deploy --helpShows:
- Command description
- Usage patterns for all actions
- Available actions with descriptions
- Options (including
--help)
TeCLI supports multiple DI containers through extension packages:
TeCLI.Extensions.DependencyInjection- Microsoft.Extensions.DependencyInjectionTeCLI.Extensions.Autofac- Autofac containerTeCLI.Extensions.SimpleInjector- SimpleInjector containerTeCLI.Extensions.Jab- Jab source-generated containerTeCLI.Extensions.PureDI- PureDI container
using Microsoft.Extensions.DependencyInjection;
using TeCLI.Extensions.DependencyInjection;
IServiceCollection services = new ServiceCollection();
// Register your services
services.AddSingleton<IMyService, MyService>();
// Add command dispatcher
services.AddCommandDispatcher();
// Build and dispatch
var serviceProvider = services.BuildServiceProvider();
var dispatcher = serviceProvider.GetRequiredService<CommandDispatcher>();
await dispatcher.DispatchAsync(args);[Command("process")]
public class ProcessCommand
{
private readonly IMyService _service;
public ProcessCommand(IMyService service)
{
_service = service;
}
[Primary]
public void Execute()
{
_service.DoWork();
}
}TeCLI fully supports asynchronous actions using Task and ValueTask:
[Command("fetch")]
public class FetchCommand
{
[Primary]
public async Task FetchData(string url)
{
using var client = new HttpClient();
var data = await client.GetStringAsync(url);
Console.WriteLine(data);
}
[Action("multiple")]
public async ValueTask FetchMultiple(string[] urls)
{
// Async implementation
}
}Group related options into complex types:
public class DeploymentOptions
{
[Option("environment", ShortName = 'e')]
public string Environment { get; set; }
[Option("region", ShortName = 'r')]
public string Region { get; set; }
[Option("verbose", ShortName = 'v')]
public bool Verbose { get; set; }
}
[Command("deploy")]
public class DeployCommand
{
[Primary]
public void Execute(DeploymentOptions options)
{
Console.WriteLine($"Deploying to {options.Environment} in {options.Region}");
}
}Usage:
myapp deploy -e production -r us-west --verboseTeCLI includes 32 Roslyn analyzers that provide real-time feedback during development:
- CLI001: Options/arguments must use supported types
- CLI002: Option properties must have accessible setters
- CLI003: Only one
[Primary]action allowed per command - CLI006: Command/action/option names cannot be empty
- CLI007: Action names must be unique within a command
- CLI008: Option names must be unique within an action
- CLI009: Argument positions cannot conflict
- CLI010: Option short names must be unique within an action
- CLI016: Invalid validation attribute combinations
- CLI018: Duplicate command names across classes
- CLI021: Collection argument must be in last position
- CLI022: Option/Argument property without setter
- CLI030: Option uses empty enum type
- CLI004: Command names should contain only letters, numbers, and hyphens
- CLI005: Option names should contain only letters, numbers, and hyphens
- CLI011: Async methods must return
TaskorValueTask - CLI012: Avoid async void in action methods
- CLI013: Optional argument before required argument
- CLI015: Action method in non-command class or inaccessible
- CLI017: Option name conflicts with reserved switch (
--help,-h) - CLI020: Boolean option marked as required
- CLI024: Command class without action methods
- CLI028: Hidden option marked as required
- CLI031: Multiple GlobalOptions classes
- CLI014: Consider using container parameter for 4+ options
- CLI019: Missing description on Command/Option/Argument
- CLI023: Async action without CancellationToken
- CLI025: Inconsistent naming convention
- CLI026: Single-character option name should use ShortName
- CLI027: Redundant name specification
- CLI029: Nullable option without explicit default
- CLI032: Sensitive option detected (security info)
- CLI900: Suppresses CS8618 nullable warnings for properties with
[Option]/[Argument]attributes (generator initializes them)
TeCLI provides helpful error messages for common issues:
- Missing required parameters
- Invalid option values
- Unknown commands or actions
- Type conversion failures
All error messages include a suggestion to use --help for guidance.
[Command("build", Description = "Build the project")]
public class BuildCommand
{
[Primary(Description = "Build the project")]
public void Build(
[Option("configuration", ShortName = 'c', Description = "Build configuration")]
string configuration = "Debug",
[Option("output", ShortName = 'o', Description = "Output directory")]
string output = "./bin",
[Option("verbose", ShortName = 'v', Description = "Verbose output")]
bool verbose = false)
{
Console.WriteLine($"Building in {configuration} mode...");
if (verbose)
{
Console.WriteLine($"Output: {output}");
}
}
}Usage:
myapp build -c Release -o ./dist --verbose
myapp build --configuration Release --output ./dist -v[Command("git")]
public class GitCommand
{
[Action("commit", Description = "Commit changes")]
public void Commit(
[Option("message", ShortName = 'm')] string message,
[Option("all", ShortName = 'a')] bool all = false)
{
Console.WriteLine($"Committing: {message}");
}
[Action("push", Description = "Push to remote")]
public async Task Push(
[Option("force", ShortName = 'f')] bool force = false)
{
await Task.Run(() => Console.WriteLine("Pushing..."));
}
}Usage:
myapp git commit -m "Initial commit" --all
myapp git push --forceTeCLI supports loading default option values from configuration files using the TeCLI.Extensions.Configuration package.
- JSON (
.json) - Standard JSON with comments and trailing commas - YAML (
.yaml,.yml) - Human-readable configuration - TOML (
.toml) - Minimal, unambiguous configuration - INI (
.ini,.cfg,.conf) - Simple key-value configuration
Configuration files are discovered automatically in the following order (lowest to highest precedence):
- Global configuration (
/etc/,%ProgramData%) - User configuration (
~/.config/,%AppData%) - Working directory tree (walking up from current directory)
- Explicit config file path
{
"environment": "development",
"region": "us-east",
"verbose": false,
"deploy": {
"timeout": 600
},
"profiles": {
"dev": {
"environment": "development",
"verbose": true
},
"prod": {
"environment": "production",
"region": "us-west",
"verbose": false
}
}
}using TeCLI.Configuration;
// Load and merge configuration with CLI arguments
var mergedArgs = args.WithConfiguration(appName: "myapp");
await CommandDispatcher.DispatchAsync(mergedArgs);
// Or use a specific profile
var prodArgs = args.WithProfile("prod", appName: "myapp");
await CommandDispatcher.DispatchAsync(prodArgs);Values are merged in this order (later sources override earlier ones):
- Configuration file defaults
- Environment variables (with optional prefix)
- CLI arguments (highest precedence)
This means users can set defaults in config files but always override them via CLI.
TeCLI provides internationalization support through the TeCLI.Extensions.Localization package.
dotnet add package TeCLI.Extensions.Localizationusing TeCLI.Localization;
// Initialize with automatic culture detection (checks --lang arg and LANG env var)
Localizer.Initialize(
new ResourceLocalizationProvider(typeof(Strings)),
args
);
// Or configure manually
Localizer.Configure(new ResourceLocalizationProvider(typeof(Strings)));
Localizer.CurrentCulture = new CultureInfo("fr");using TeCLI.Localization;
[Command("greet")]
[LocalizedDescription("GreetCommand_Description")]
public class GreetCommand
{
[Primary]
[LocalizedDescription("GreetCommand_Hello_Description")]
public void Hello(
[Argument]
[LocalizedDescription("GreetCommand_Hello_Name_Description")]
string name)
{
// Use localized output messages
Console.WriteLine(Localizer.GetString("Greeting_Hello", name));
}
}// Show localized help for a command
Localizer.ShowHelp<GreetCommand>();
// Or use the help renderer directly
var renderer = Localizer.CreateHelpRenderer();
renderer.RenderCommandHelp(typeof(GreetCommand));The extension automatically detects culture from:
- Command line arguments:
--lang=fr,--locale=de-DE,-l es - Environment variables:
LANG,LC_ALL,LC_MESSAGES - System culture (fallback)
| Provider | Use Case |
|---|---|
ResourceLocalizationProvider |
.resx resource files |
DictionaryLocalizationProvider |
In-memory translations (testing, simple apps) |
CompositeLocalizationProvider |
Chain multiple providers together |
Localizer.Configure(p =>
{
p.AddTranslation("en", "greeting", "Hello, {0}!");
p.AddTranslation("fr", "greeting", "Bonjour, {0}!");
p.AddDefault("farewell", "Goodbye!");
});
Console.WriteLine(Localizer.GetString("greeting", "World"));// Returns singular or plural based on count
var message = Localizer.GetPluralString("file_singular", "file_plural", count);
// "1 file" vs "5 files"See examples/TeCLI.Example.Localization for a complete working example with English, French, German, and Spanish translations.
TeCLI provides structured output formatting through the TeCLI.Extensions.Output package.
dotnet add package TeCLI.Extensions.OutputApply [OutputFormat] to action methods to enable output format selection:
using TeCLI.Output;
[Command("list")]
public class ListCommand
{
[Action("users")]
[OutputFormat] // Enables --output json|xml|table|yaml
public IEnumerable<User> ListUsers()
{
return _userService.GetAll();
}
}
// Usage:
// myapp list users --output json
// myapp list users --output table
// myapp list users -o yamlFor more control, use OutputContext:
using TeCLI.Output;
// Fluent API
OutputContext.Create()
.WithFormat(OutputFormat.Json)
.WriteTo(Console.Out)
.Write(users);
// Or construct directly
var context = new OutputContext(OutputFormat.Table, Console.Out);
context.Write(users);| Format | Description |
|---|---|
json |
JSON output using System.Text.Json |
xml |
XML output with configurable element names |
yaml |
YAML output (lightweight, no external dependencies) |
table |
Rich table output using Spectre.Console |
Implement IOutputFormatter<T> for custom formatting:
public class UserCsvFormatter : IOutputFormatter<User>
{
public OutputFormat Format => OutputFormat.Csv;
public string FileExtension => ".csv";
public string MimeType => "text/csv";
public void Format(User value, TextWriter output)
{
output.WriteLine($"{value.Id},{value.Name},{value.Email}");
}
public void FormatCollection(IEnumerable<User> values, TextWriter output)
{
output.WriteLine("Id,Name,Email");
foreach (var user in values)
Format(user, output);
}
}See examples/TeCLI.Example.Output for a complete working example.
TeCLI supports .NET 6.0, 7.0, 8.0, 9.0, and 10.0.
Core Library: Targets netstandard2.0 for maximum compatibility with source generators.
Test & Example Projects: Support .NET 8.0, 9.0, and 10.0.
Contributions are welcome! Please read our Contributing Guidelines before submitting a pull request.
- See CONTRIBUTING.md for development setup and guidelines
- See CODE_OF_CONDUCT.md for community guidelines
- See AGENTS.md for AI coding agent instructions
- Code Coverage: See COVERAGE.md for testing and coverage guidelines
- Benchmarks: See TeCLI.Benchmarks/README.md for performance benchmarks
- Integration Tests: See TeCLI.Tests/README.md for test examples