Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
CommandLineAPI
https://github.com/dotnet/command-line-api
Admin
System.CommandLine is a library for creating powerful command-line applications with parsing, model
...
Tokens:
20,775
Snippets:
200
Trust Score:
8.3
Update:
2 weeks ago
Context
Skills
Chat
Benchmark
90.7
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# System.CommandLine System.CommandLine is a .NET library for building command-line applications with robust argument parsing, model binding, command invocation, and shell completions. It provides a declarative API for defining commands, options, and arguments while handling parsing complexities like POSIX-style bundling, response files, and automatic help/version generation. The library supports both synchronous and asynchronous command handlers, custom argument validation, type conversion from strings to strongly-typed values, and automatic tab completion across bash, zsh, and PowerShell. It follows both POSIX and Windows command-line conventions, making it suitable for cross-platform CLI tools. The architecture separates command definition from parsing and invocation, enabling testability and flexible configuration. ## RootCommand - Entry Point for CLI Applications The `RootCommand` class represents the main entry point for command-line applications. It automatically includes built-in help (`-h`, `--help`) and version (`--version`) options. For simple single-action applications, use RootCommand directly; for complex applications with multiple actions, add subcommands. ```csharp using System.CommandLine; // Create a root command with a description var rootCommand = new RootCommand("A sample application that greets users"); // Add an argument for the name var nameArg = new Argument<string>("name", "The name to greet"); rootCommand.Add(nameArg); // Add an optional greeting option var greetingOption = new Option<string>("--greeting", "-g") { Description = "The greeting to use" }; greetingOption.DefaultValueFactory = _ => "Hello"; rootCommand.Add(greetingOption); // Set the action handler rootCommand.SetAction((parseResult) => { var name = parseResult.GetValue(nameArg); var greeting = parseResult.GetValue(greetingOption); Console.WriteLine($"{greeting}, {name}!"); return 0; }); // Parse and invoke return rootCommand.Parse(args).Invoke(); // Example usage: // > myapp Alice → "Hello, Alice!" // > myapp Bob --greeting Hi → "Hi, Bob!" // > myapp --help → Shows auto-generated help // > myapp --version → Shows version information ``` ## Command - Subcommands and Nested Commands The `Command` class represents a specific action identified by a name. Commands can be nested to create hierarchical CLI structures like `git commit` or `dotnet build`. Each command can have its own options, arguments, and handler. ```csharp using System.CommandLine; // Create root command var rootCommand = new RootCommand("File management tool"); // Create 'list' subcommand var listCommand = new Command("list", "List files in a directory"); var pathArg = new Argument<string>("path", () => ".", "Directory path to list"); listCommand.Add(pathArg); var recursiveOption = new Option<bool>("--recursive", "-r") { Description = "List recursively" }; listCommand.Add(recursiveOption); listCommand.SetAction((parseResult) => { var path = parseResult.GetValue(pathArg); var recursive = parseResult.GetValue(recursiveOption); Console.WriteLine($"Listing {path} (recursive: {recursive})"); }); // Create 'delete' subcommand var deleteCommand = new Command("delete", "Delete a file"); var fileArg = new Argument<FileInfo>("file", "File to delete"); var forceOption = new Option<bool>("--force", "-f") { Description = "Force deletion" }; deleteCommand.Add(fileArg); deleteCommand.Add(forceOption); deleteCommand.SetAction((parseResult) => { var file = parseResult.GetValue(fileArg); var force = parseResult.GetValue(forceOption); Console.WriteLine($"Deleting {file?.FullName} (force: {force})"); }); // Add subcommands to root rootCommand.Add(listCommand); rootCommand.Add(deleteCommand); // Add command aliases deleteCommand.Aliases.Add("rm"); // 'rm' is now an alias for 'delete' return rootCommand.Parse(args).Invoke(); // Example usage: // > filetool list /home --recursive // > filetool delete myfile.txt --force // > filetool rm myfile.txt -f (using alias) ``` ## Option<T> - Named Parameters with Values Options are named parameters that can be specified using prefixes like `--` or `-`. The generic `Option<T>` class provides type-safe parsing with automatic conversion from command-line strings to the target type. ```csharp using System.CommandLine; var rootCommand = new RootCommand("Build configuration tool"); // String option with alias var configOption = new Option<string>("--config", "-c") { Description = "Configuration file path", Required = true // Option must be provided }; rootCommand.Add(configOption); // Integer option with default value var verbosityOption = new Option<int>("--verbosity", "-v") { Description = "Verbosity level (0-3)" }; verbosityOption.DefaultValueFactory = _ => 1; rootCommand.Add(verbosityOption); // Boolean flag option (no value needed) var dryRunOption = new Option<bool>("--dry-run") { Description = "Perform a dry run without changes" }; rootCommand.Add(dryRunOption); // Array option (accepts multiple values) var tagsOption = new Option<string[]>("--tags", "-t") { Description = "Tags to apply", AllowMultipleArgumentsPerToken = true // Allows: --tags a b c }; rootCommand.Add(tagsOption); // Enum option with constrained values var formatOption = new Option<OutputFormat>("--format", "-f") { Description = "Output format" }; rootCommand.Add(formatOption); rootCommand.SetAction((parseResult) => { var config = parseResult.GetValue(configOption); var verbosity = parseResult.GetValue(verbosityOption); var dryRun = parseResult.GetValue(dryRunOption); var tags = parseResult.GetValue(tagsOption) ?? Array.Empty<string>(); var format = parseResult.GetValue(formatOption); Console.WriteLine($"Config: {config}, Verbosity: {verbosity}, DryRun: {dryRun}"); Console.WriteLine($"Tags: {string.Join(", ", tags)}, Format: {format}"); }); enum OutputFormat { Json, Xml, Text } // Example usage: // > buildtool --config app.json -v 2 --dry-run // > buildtool -c app.json --tags dev test prod --format Json // > buildtool --config=app.json --verbosity:3 ``` ## Argument<T> - Positional Parameters Arguments are positional parameters that don't require a name prefix. They are parsed based on their position in the command line. Use arguments for required inputs that don't need names. ```csharp using System.CommandLine; var rootCommand = new RootCommand("File copy utility"); // Required source argument var sourceArg = new Argument<FileInfo>("source", "Source file to copy"); rootCommand.Add(sourceArg); // Required destination argument var destArg = new Argument<string>("destination", "Destination path"); rootCommand.Add(destArg); // Optional count argument with default var countArg = new Argument<int>("count", () => 1, "Number of copies"); countArg.Arity = ArgumentArity.ZeroOrOne; // Makes it optional rootCommand.Add(countArg); // Multiple values argument var multiCommand = new Command("multi", "Copy multiple files"); var filesArg = new Argument<FileInfo[]>("files", "Files to process"); filesArg.Arity = ArgumentArity.OneOrMore; // At least one required multiCommand.Add(filesArg); rootCommand.Add(multiCommand); rootCommand.SetAction((parseResult) => { var source = parseResult.GetRequiredValue(sourceArg); // Throws if missing var dest = parseResult.GetRequiredValue(destArg); var count = parseResult.GetValue(countArg); // Returns default if missing Console.WriteLine($"Copying {source.Name} to {dest}, {count} time(s)"); }); multiCommand.SetAction((parseResult) => { var files = parseResult.GetValue(filesArg); Console.WriteLine($"Processing {files?.Length ?? 0} files"); }); // Example usage: // > copytool input.txt /output/ (count defaults to 1) // > copytool input.txt /output/ 3 (3 copies) // > copytool multi file1.txt file2.txt file3.txt ``` ## ArgumentArity - Controlling Value Count ArgumentArity defines how many values an argument or option can accept. Use predefined arities or create custom ones for specific requirements. ```csharp using System.CommandLine; var rootCommand = new RootCommand("Arity demonstration"); // Zero values allowed (flag-style) var flagOption = new Option<bool>("--flag"); flagOption.Arity = ArgumentArity.Zero; // No value expected rootCommand.Add(flagOption); // Exactly one value required var singleOption = new Option<string>("--single"); singleOption.Arity = ArgumentArity.ExactlyOne; rootCommand.Add(singleOption); // Zero or one value (optional) var optionalOption = new Option<string>("--optional"); optionalOption.Arity = ArgumentArity.ZeroOrOne; rootCommand.Add(optionalOption); // Zero or more values var anyOption = new Option<string[]>("--any"); anyOption.Arity = ArgumentArity.ZeroOrMore; rootCommand.Add(anyOption); // One or more values (at least one required) var manyOption = new Option<string[]>("--many"); manyOption.Arity = ArgumentArity.OneOrMore; rootCommand.Add(manyOption); // Custom arity: exactly 2 to 4 values var customOption = new Option<string[]>("--custom"); customOption.Arity = new ArgumentArity(2, 4); rootCommand.Add(customOption); rootCommand.SetAction((parseResult) => { // Parse result contains validated values based on arity var single = parseResult.GetValue(singleOption); var custom = parseResult.GetValue(customOption); Console.WriteLine($"Single: {single}, Custom count: {custom?.Length}"); }); // Example usage: // > arityapp --single value --custom a b c (valid: 3 values in 2-4 range) // > arityapp --custom a (error: minimum 2 required) // > arityapp --custom a b c d e (error: maximum 4 exceeded) ``` ## Validation - AcceptOnlyFromAmong and Custom Validators System.CommandLine provides built-in validation methods and supports custom validators for arguments and options. Validation errors are collected and reported to users with helpful messages. ```csharp using System.CommandLine; using System.CommandLine.Parsing; using System.IO; var rootCommand = new RootCommand("Validated command"); // Accept only specific string values var envOption = new Option<string>("--env", "-e") { Description = "Deployment environment" }.AcceptOnlyFromAmong("dev", "staging", "prod"); rootCommand.Add(envOption); // Accept only existing files var inputArg = new Argument<FileInfo>("input", "Input file"); inputArg.AcceptExistingOnly(); // File must exist rootCommand.Add(inputArg); // Accept only existing directories var outputDirOption = new Option<DirectoryInfo>("--output-dir", "-o"); outputDirOption.AcceptExistingOnly(); // Directory must exist rootCommand.Add(outputDirOption); // Accept only legal file names (no path separators) var nameOption = new Option<string>("--name"); nameOption.AcceptLegalFileNamesOnly(); rootCommand.Add(nameOption); // Accept only legal file paths var pathOption = new Option<string>("--path"); pathOption.AcceptLegalFilePathsOnly(); rootCommand.Add(pathOption); // Custom option validator var portOption = new Option<int>("--port", "-p") { Description = "Server port (1024-65535)" }; portOption.Validators.Add(result => { var value = result.GetValue<int>(); if (value < 1024 || value > 65535) { result.AddError($"Port must be between 1024 and 65535, got {value}"); } }); rootCommand.Add(portOption); // Custom argument validator var emailArg = new Argument<string>("email", "User email"); emailArg.Validators.Add(result => { var value = result.Tokens.FirstOrDefault()?.Value; if (value != null && !value.Contains("@")) { result.AddError($"'{value}' is not a valid email address"); } }); rootCommand.Add(emailArg); rootCommand.SetAction((parseResult) => { if (parseResult.Errors.Count > 0) { foreach (var error in parseResult.Errors) Console.WriteLine($"Error: {error.Message}"); return 1; } Console.WriteLine("All validations passed!"); return 0; }); // Example usage: // > app --env production input.txt (error: must be dev/staging/prod) // > app --env dev missing.txt (error: file does not exist) // > app --port 80 --env dev file.txt (error: port out of range) ``` ## Custom Parsing - CustomParser and DefaultValueFactory For complex parsing scenarios, use `CustomParser` to transform tokens into values and `DefaultValueFactory` to provide computed defaults. ```csharp using System.CommandLine; using System.CommandLine.Parsing; using System.Net; var rootCommand = new RootCommand("Custom parsing demo"); // Parse IP address from string var ipOption = new Option<IPAddress>("--ip") { Description = "Server IP address" }; ipOption.CustomParser = result => { var token = result.Tokens.SingleOrDefault()?.Value; if (token == null) return null; if (IPAddress.TryParse(token, out var ip)) return ip; result.AddError($"'{token}' is not a valid IP address"); return null; }; rootCommand.Add(ipOption); // Parse comma-separated integers var numbersArg = new Argument<int[]>("numbers", "Comma-separated numbers"); numbersArg.CustomParser = result => { var token = result.Tokens.SingleOrDefault()?.Value; if (string.IsNullOrEmpty(token)) return Array.Empty<int>(); try { return token.Split(',').Select(int.Parse).ToArray(); } catch { result.AddError($"Cannot parse '{token}' as comma-separated integers"); return Array.Empty<int>(); } }; rootCommand.Add(numbersArg); // Dynamic default value based on environment var configOption = new Option<string>("--config") { Description = "Configuration file" }; configOption.DefaultValueFactory = result => { var env = Environment.GetEnvironmentVariable("APP_ENV") ?? "dev"; return $"config.{env}.json"; }; rootCommand.Add(configOption); // Parse key=value pairs into dictionary var propsOption = new Option<Dictionary<string, string>>("--props", "-p") { Description = "Properties as key=value pairs" }; propsOption.Arity = ArgumentArity.ZeroOrMore; propsOption.CustomParser = result => { var dict = new Dictionary<string, string>(); foreach (var token in result.Tokens) { var parts = token.Value.Split('=', 2); if (parts.Length == 2) dict[parts[0]] = parts[1]; else result.AddError($"Invalid property format: '{token.Value}'. Expected key=value"); } return dict; }; rootCommand.Add(propsOption); rootCommand.SetAction((parseResult) => { var ip = parseResult.GetValue(ipOption); var numbers = parseResult.GetValue(numbersArg); var config = parseResult.GetValue(configOption); var props = parseResult.GetValue(propsOption); Console.WriteLine($"IP: {ip}, Numbers: [{string.Join(",", numbers ?? Array.Empty<int>())}]"); Console.WriteLine($"Config: {config}"); Console.WriteLine($"Props: {string.Join(", ", props?.Select(p => $"{p.Key}={p.Value}") ?? Array.Empty<string>())}"); }); // Example usage: // > app --ip 192.168.1.1 1,2,3,4,5 // > app --props name=value debug=true 1,2,3 // > app 10,20,30 --config custom.json ``` ## Async Command Handlers with Cancellation For long-running operations, use async handlers with `CancellationToken` support. System.CommandLine handles process termination signals (Ctrl+C, SIGTERM) gracefully. ```csharp using System.CommandLine; using System.Threading; using System.Threading.Tasks; var rootCommand = new RootCommand("Async operation demo"); var durationOption = new Option<int>("--duration", "-d") { Description = "Operation duration in seconds" }; durationOption.DefaultValueFactory = _ => 10; rootCommand.Add(durationOption); // Async handler with cancellation support rootCommand.SetAction(async (parseResult, cancellationToken) => { var duration = parseResult.GetValue(durationOption); Console.WriteLine($"Starting {duration}s operation. Press Ctrl+C to cancel."); try { for (int i = 0; i < duration; i++) { cancellationToken.ThrowIfCancellationRequested(); Console.WriteLine($"Progress: {i + 1}/{duration}"); await Task.Delay(1000, cancellationToken); } Console.WriteLine("Operation completed successfully!"); return 0; } catch (OperationCanceledException) { Console.WriteLine("Operation was cancelled."); return 1; } }); // Configure termination timeout var config = new InvocationConfiguration { ProcessTerminationTimeout = TimeSpan.FromSeconds(5), // Allow 5s for graceful shutdown EnableDefaultExceptionHandler = true // Catch unhandled exceptions }; // Parse and invoke async return await rootCommand.Parse(args).InvokeAsync(config, CancellationToken.None); // Example usage: // > asyncapp --duration 30 (runs for 30 seconds) // > asyncapp -d 5 (runs for 5 seconds) // Press Ctrl+C during execution to trigger cancellation ``` ## ParseResult - Accessing Parsed Values The `ParseResult` object contains all information about the parsed command line, including values, errors, and matched tokens. It provides type-safe access to argument and option values. ```csharp using System.CommandLine; using System.CommandLine.Parsing; var rootCommand = new RootCommand("ParseResult demo"); var verboseOption = new Option<bool>("--verbose", "-v"); var countOption = new Option<int>("--count", "-c"); countOption.DefaultValueFactory = _ => 1; var nameArg = new Argument<string>("name"); rootCommand.Add(verboseOption); rootCommand.Add(countOption); rootCommand.Add(nameArg); // Parse without invoking ParseResult result = rootCommand.Parse("--verbose -c 5 Alice"); // Check for parse errors if (result.Errors.Count > 0) { foreach (var error in result.Errors) Console.WriteLine($"Parse error: {error.Message}"); } // Get typed values bool verbose = result.GetValue(verboseOption); // true int count = result.GetValue(countOption); // 5 string? name = result.GetValue(nameArg); // "Alice" // Get required values (throws if missing) string requiredName = result.GetRequiredValue(nameArg); // "Alice" or throws // Get values by name (less type-safe) string? nameByString = result.GetValue<string>("name"); // Check if option/argument was explicitly provided OptionResult? verboseResult = result.GetResult(verboseOption); bool wasVerboseProvided = verboseResult != null && !verboseResult.Implicit; // Access command result CommandResult cmdResult = result.CommandResult; Console.WriteLine($"Command: {cmdResult.Command.Name}"); // Access all tokens foreach (var token in result.Tokens) Console.WriteLine($"Token: {token.Value} ({token.Type})"); // Access unmatched tokens (when TreatUnmatchedTokensAsErrors = false) foreach (var unmatched in result.UnmatchedTokens) Console.WriteLine($"Unmatched: {unmatched}"); // Invoke the action int exitCode = result.Invoke(); // Or await result.InvokeAsync(); ``` ## ParserConfiguration - Customizing Parser Behavior Configure parser behavior including POSIX bundling, response files, and other parsing options. ```csharp using System.CommandLine; using System.CommandLine.Parsing; var rootCommand = new RootCommand("Config demo"); rootCommand.Add(new Option<bool>("-a")); rootCommand.Add(new Option<bool>("-b")); rootCommand.Add(new Option<bool>("-c")); rootCommand.Add(new Option<string>("--file")); // Default configuration: POSIX bundling enabled, response files enabled var defaultResult = rootCommand.Parse("-abc"); // Unbundles to -a -b -c // Disable POSIX bundling var noBundlingConfig = new ParserConfiguration { EnablePosixBundling = false }; var noBundleResult = rootCommand.Parse("-abc", noBundlingConfig); // Treats -abc as unknown // Disable response files var noResponseConfig = new ParserConfiguration { ResponseFileTokenReplacer = null // Disables @file expansion }; // Custom response file handling var customResponseConfig = new ParserConfiguration { ResponseFileTokenReplacer = (token, out IReadOnlyList<string>? replacements, out string? error) => { if (token.StartsWith("@")) { var filePath = token.Substring(1); if (File.Exists(filePath)) { replacements = File.ReadAllLines(filePath) .Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#")) .ToArray(); error = null; return true; } replacements = null; error = $"Response file not found: {filePath}"; return true; // Token was a response file reference (even if error) } replacements = null; error = null; return false; // Not a response file token } }; // Parse with configuration var result = rootCommand.Parse("@myargs.txt --file output.txt", customResponseConfig); // Example response file (myargs.txt): // -a // -b // --file input.txt ``` ## InvocationConfiguration - Controlling Invocation Behavior Configure runtime behavior during command invocation, including output streams, exception handling, and process termination. ```csharp using System.CommandLine; using System.IO; var rootCommand = new RootCommand("Invocation config demo"); var messageOption = new Option<string>("--message", "-m"); messageOption.DefaultValueFactory = _ => "Hello"; rootCommand.Add(messageOption); rootCommand.SetAction((parseResult) => { var message = parseResult.GetValue(messageOption); // Write to configured output (not Console directly for testability) parseResult.InvocationConfiguration.Output.WriteLine($"Output: {message}"); parseResult.InvocationConfiguration.Error.WriteLine("Error stream message"); return 0; }); // Default configuration (writes to Console.Out/Error) rootCommand.Parse("-m test").Invoke(); // Custom output for testing var outputWriter = new StringWriter(); var errorWriter = new StringWriter(); var testConfig = new InvocationConfiguration { Output = outputWriter, Error = errorWriter, EnableDefaultExceptionHandler = false, // Don't catch exceptions (for testing) ProcessTerminationTimeout = null // Disable Ctrl+C handling }; rootCommand.Parse("-m test").Invoke(testConfig); Console.WriteLine($"Captured output: {outputWriter}"); Console.WriteLine($"Captured error: {errorWriter}"); // Disable all output var silentConfig = new InvocationConfiguration { Output = TextWriter.Null, Error = TextWriter.Null }; rootCommand.Parse("-m test").Invoke(silentConfig); ``` ## Directives - Cross-Cutting Functionality Directives provide special functionality that applies across commands. They use `[name]` syntax and are processed before regular parsing. ```csharp using System.CommandLine; using System.CommandLine.Invocation; var rootCommand = new RootCommand("Directive demo"); // Built-in suggest directive for shell completions // Already included in RootCommand: [suggest] // Add environment variables directive rootCommand.Add(new EnvironmentVariablesDirective()); // Create custom directive var debugDirective = new Directive("debug") { Action = new DebugDirectiveAction() }; rootCommand.Add(debugDirective); rootCommand.SetAction((parseResult) => { Console.WriteLine("Main command executed"); return 0; }); class DebugDirectiveAction : SynchronousCommandLineAction { public override int Invoke(ParseResult parseResult) { Console.WriteLine("Debug mode enabled"); Console.WriteLine($"Command line: {string.Join(" ", parseResult.Tokens.Select(t => t.Value))}"); return 0; // Continue to command } public override bool Terminating => false; // Don't stop processing } // Example usage: // > app [debug] --option value (triggers debug directive, then command) // > app [env:VAR=value] command (sets environment variable) // > app [suggest] --opt (returns completions for shell) ``` ## Recursive Options - Global Options Across Subcommands Options marked as `Recursive` are available to a command and all its subcommands, useful for global settings like verbosity or configuration. ```csharp using System.CommandLine; var rootCommand = new RootCommand("Multi-level CLI"); // Global verbose option available to all subcommands var verboseOption = new Option<bool>("--verbose", "-v") { Description = "Enable verbose output", Recursive = true // Available in all subcommands }; rootCommand.Add(verboseOption); // Global config option var configOption = new Option<string>("--config", "-c") { Description = "Configuration file", Recursive = true }; rootCommand.Add(configOption); // Subcommand 'build' var buildCommand = new Command("build", "Build the project"); var releaseOption = new Option<bool>("--release", "-r"); buildCommand.Add(releaseOption); buildCommand.SetAction((parseResult) => { var verbose = parseResult.GetValue(verboseOption); // Access recursive option var config = parseResult.GetValue(configOption); var release = parseResult.GetValue(releaseOption); if (verbose) Console.WriteLine($"Using config: {config}"); Console.WriteLine($"Building in {(release ? "release" : "debug")} mode"); }); rootCommand.Add(buildCommand); // Nested subcommand 'build test' var testCommand = new Command("test", "Run tests"); var filterOption = new Option<string>("--filter"); testCommand.Add(filterOption); testCommand.SetAction((parseResult) => { var verbose = parseResult.GetValue(verboseOption); // Still available var filter = parseResult.GetValue(filterOption); if (verbose) Console.WriteLine("Verbose test output enabled"); Console.WriteLine($"Running tests with filter: {filter ?? "all"}"); }); buildCommand.Add(testCommand); // Example usage: // > app --verbose build --release (verbose available in build) // > app build test --filter "Unit*" --verbose (verbose available in nested test) // > app -v -c prod.json build test (both recursive options work) ``` ## Shell Completions - Tab Completion Support System.CommandLine provides built-in shell completion support. Options and arguments can provide custom completion suggestions. ```csharp using System.CommandLine; using System.CommandLine.Completions; var rootCommand = new RootCommand("Completion demo"); // Enum automatically provides completions var formatOption = new Option<OutputFormat>("--format", "-f"); rootCommand.Add(formatOption); // Static completions via AcceptOnlyFromAmong var envOption = new Option<string>("--env") .AcceptOnlyFromAmong("development", "staging", "production"); rootCommand.Add(envOption); // Dynamic completions var fileOption = new Option<string>("--file"); fileOption.CompletionSources.Add(context => { // Return files in current directory matching input var pattern = context.WordToComplete + "*"; return Directory.GetFiles(".", pattern) .Select(f => new CompletionItem(Path.GetFileName(f))); }); rootCommand.Add(fileOption); // Argument with custom completions var projectArg = new Argument<string>("project"); projectArg.CompletionSources.Add(context => { // Could query database, file system, or API return new[] { new CompletionItem("project-alpha", CompletionItem.KindKeyword, "Main project"), new CompletionItem("project-beta", CompletionItem.KindKeyword, "Beta release"), new CompletionItem("project-gamma", CompletionItem.KindKeyword, "Experimental") }; }); rootCommand.Add(projectArg); enum OutputFormat { Json, Xml, Csv, Text } // To enable shell completions, users install dotnet-suggest: // > dotnet tool install -g dotnet-suggest // > dotnet-suggest script bash >> ~/.bashrc (for bash) // > dotnet-suggest script zsh >> ~/.zshrc (for zsh) // > dotnet-suggest script pwsh (for PowerShell) ``` ## Summary System.CommandLine excels at building robust command-line applications with complex argument structures. Its primary use cases include: building CLI tools with subcommands (like `git` or `dotnet`), creating utilities with rich option parsing and validation, developing cross-platform tools that work consistently across shells, and building testable CLI applications with injectable I/O streams. The library handles the complexity of argument parsing, type conversion, help generation, and shell completions, letting developers focus on application logic. For integration, the typical pattern involves: defining a command structure with `RootCommand` and `Command`, adding strongly-typed `Option<T>` and `Argument<T>` parameters, setting action handlers via `SetAction()`, and invoking via `Parse().Invoke()` or `Parse().InvokeAsync()`. The library supports dependency injection patterns through `ParseResult`, enables comprehensive testing via `InvocationConfiguration` output redirection, and integrates with .NET's async/await and cancellation token patterns. Response files (`@file.txt`) provide a way to handle complex command lines, while directives enable cross-cutting concerns without polluting command syntax.