Working with Multiple Command Hierarchies

How to create hierarchical (nested) commands using branching

When building a CLI with grouped commands like git remote add and git remote remove, use AddBranch to create command hierarchies. The branch groups related subcommands together and can share common options through settings inheritance.

What We're Building

A git-style CLI with nested remote subcommands—add, remove, and list—all sharing a --verbose flag inherited from the branch:

Command hierarchies demonstration

Create a Command Branch

Use AddBranch to define a parent command with nested subcommands. Use the generic overload when you want shared settings/options for the branch:

var app = new CommandApp();
  
app.Configure(config =>
{
    config.SetApplicationName("myapp");
  
    // Create a "remote" branch with nested commands
    config.AddBranch<RemoteSettings>("remote", remote =>
    {
        remote.SetDescription("Manage remote repositories");
  
        remote.AddCommand<RemoteAddCommand>("add")
            .WithDescription("Add a new remote");
  
        remote.AddCommand<RemoteRemoveCommand>("remove")
            .WithDescription("Remove a remote")
            .WithAlias("rm");
  
        remote.AddCommand<RemoteListCommand>("list")
            .WithDescription("List all remotes")
            .WithAlias("ls");
    });
});
  
return await app.RunAsync(args);

This creates commands like myapp remote add, myapp remote remove, and myapp remote list. Running myapp remote --help shows all subcommands.

Share Options with Settings Inheritance

Define a base settings class for the branch, then inherit from it in subcommand settings:

public class RemoteSettings : CommandSettings
{
    [CommandOption("-v|--verbose")]
    [Description("Show detailed output")]
    public bool Verbose { get; init; }
}
  
public class RemoteAddSettings : RemoteSettings
{
    [CommandArgument(0, "<name>")]
    [Description("Name for the remote")]
    public string Name { get; init; } = string.Empty;
  
    [CommandArgument(1, "<url>")]
    [Description("URL of the remote repository")]
    public string Url { get; init; } = string.Empty;
}
  
public class RemoteListSettings : RemoteSettings
{
}

The --verbose flag is now available on all remote subcommands, and can be specified either before or after the subcommand name: myapp remote --verbose add origin https://... or myapp remote add origin https://... --verbose.

When using AddBranch<TSettings>, each subcommand's settings class must inherit from TSettings. Even if a subcommand doesn't add any extra arguments or options (like list above), create a dedicated settings type that inherits from the branch settings—this ensures the framework correctly binds shared options to all subcommands.

Nest Multiple Levels

For complex CLIs, branches can contain other branches (use the non-generic overload when you just want grouping):

config.AddBranch("cloud", cloud =>
{
    cloud.AddBranch("storage", storage =>
    {
        storage.AddCommand<UploadCommand>("upload");
        storage.AddCommand<DownloadCommand>("download");
    });
});

This creates deeply nested commands like myapp cloud storage upload.

See Also