Command-Line Interfaces (CLI) are invaluable tools for a developer. We use them daily to interact with AWS, Docker, GitHub, dotnet etc. We can develop scripts based on CLI commands to carry out complex tasks. In this post, we are going to develop a CLI for ourselves. Let’s get started!
Getting Started
We are going to use two things:
- dotnet tool command
- a very handy NuGet package called CliFx
dotnet tool
The simplest way to describe a dotnet tool is a console application distributed as a NuGet package.
Usually, when you go to a NuGet source site such as Nuget.org, you deal with class libraries. You download the class library and consume it in your application.
Similarly, you can publish your console application as a dotnet tool in NuGet package format. This allows installing applications by simply using dotnet CLI, such as:
dotnet tool install --global --add-source {PACKAGE PATH} {PACKAGE NAME}
To achieve that, all we have to do is create a new console application and modify the csproj file by adding the following lines:
<PackAsTool>true</PackAsTool> <ToolCommandName>{ COMMAND NAME }</ToolCommandName> <PackageOutputPath>./nupkg</PackageOutputPath>
Now let’s have a walkthrough and see it in action:
- Create a console application using dotnet CLI:
dotnet new console
- Edit the csproj file. In this example, I’m going to use JetBrains Rider IDE to edit, but you can use any IDE/text editor you want:

- Add the following lines inside the PropertyGroup element so that it looks something like this:

- Run the following command to create the NuGet package:
dotnet pack

- Install it globally on your computer by running the following command:
dotnet tool install --global --add-source ./nupkg develop-a-cli-with-csharp
- Now you can test the tool simply by running the name of the tool in the terminal:
mycli
and the output should look like this:

Great! We have our tool installed nicely on the computer. We can run it anywhere in the terminal (regardless of the path we are in). But there is more to a CLI than simply executing a console application. The most important of a CLI is to have commands and subcommands. For example, when we use the dotnet CLI, we enter the following command:
dotnet tool install --global --add-source ./nupkg develop-a-cli-with-csharp
In this example,
- dotnet is the name of the CLI
- tool is the command
- install is the subcommand
- The rest are arguments passed to the subcommand
We don’t have any mechanism to understand commands, subcommands and arguments. This is where CliFx comes in.
CliFx
CliFx is a simple to use NuGet package that adds the full capabilities of a CLI to our console application.
- Let’s start with installing the package:
dotnet add package CliFx
You should be able to see the package after running the command above:

- Replace the Main method with the following code:
using CliFx; public static class Program { public static async Task<int> Main() => await new CliApplicationBuilder() .AddCommandsFromThisAssembly() .SetExecutableName("mycli") .SetTitle("My CLI") .SetDescription("A useful CLI tool to demo") .Build() .RunAsync(); }
- Now, let’s create our commands by creating two new classes: HelloCommand and WorldCommand. They should look like the below:
using CliFx; public static class Program { public static async Task<int> Main() => await new CliApplicationBuilder() .AddCommandsFromThisAssembly() .SetExecutableName("mycli") .SetTitle("My CLI") .SetDescription("A useful CLI tool to demo") .Build() .RunAsync(); }
[Command("hello world")] public class WorldCommand : ICommand { public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine("Hello, World!"); return default; } }
- Now run the application in the terminal without any parameters. You should get a nice help output:

- Test the command and subcommand by running the following commands:
dotnet run -- hello dotnet run -- hello world
The output should look like this:

Notice that by running the “hello” command, we are executing the ExecuteAsync method in HelloCommand class. WorldCommand is a subcommand of the hello command, so we can execute a different method by running “hello world”.
At this point, our installed tool is not affected by these changes. So we have to pack and update our tool now by running the following commands:
dotnet pack dotnet tool update --global --add-source ./nupkg develop-a-cli-with-csharp
You can confirm the tool is updated by looking for output like this:

- Finally, open another terminal window and type the CLI name
mycli
and you should see the new help output listing the available commands in the CLI:

Conclusion
CLIs are handy tools for developers. In this post, we looked into creating a CLI capable of creating commands and subcommands. It can also be installed as a dotnet tool and distributed as a NuGet package.