Achievement Quantifier .NET CLI Tool from Zero to First Deployment using Spectre.Console.Cli, ADO.NET, and SQLite

This post showcases Achievement Quantifier - a .NET CLI tool for tracking and managing achievements.
Here is how it works.
Define achievement classes:
> aq class add -n "Do programming" -u "Git commits"
info: AQ.Console.Commands.AddAchievementClass[0]
Added 'AchievementClass { Id: 1, Name: Do programming, Unit: Git commits }'.
Log achievements with custom quantities and arbitrary dates:
> aq achievement add -n "Do programming" -q 4 -d "31/12/2024"
info: AQ.Console.Commands.AddAchievement[0]
Added achievement: Achievement { Id: 1, Class: Do programming, Quantity: 4 Unit: Git commits, CompletedDate: 31/12/2024 }.
> aq achievement add -n "Do programming" -d "01/01/2025"
info: AQ.Console.Commands.AddAchievement[0]
Added achievement: Achievement { Id: 2, Class: Do programming, Quantity: 1 Unit: Git commits, CompletedDate: 1/1/2025 }.
> aq achievement add -n "Do programming" -q 2 -d "02/01/2025"
info: AQ.Console.Commands.AddAchievement[0]
Added achievement: Achievement { Id: 3, Class: Do programming, Quantity: 2 Unit: Git commits, CompletedDate: 2/1/2025 }.
View the progress:
> aq achievement list
info: AQ.Console.Commands.ListAchievements[0]
Found 3 achievements.
Achievement { Id: 1, Class: Do programming, Quantity: 4 Unit: Git commits, CompletedDate: 31/12/2024 }
Achievement { Id: 2, Class: Do programming, Quantity: 1 Unit: Git commits, CompletedDate: 1/1/2025 }
Achievement { Id: 3, Class: Do programming, Quantity: 2 Unit: Git commits, CompletedDate: 2/1/2025 }
The project is available on GitHub here.
The changes described in this post are part of the pull request here.
The rest of the post describes implementation commit by commit.
The First Command - Status
The first commit sets up dependency injection and defines the first command - status - which simply prints out the runtime environment:
> dotnet run -- --help
USAGE:
aq [OPTIONS] <COMMAND>
OPTIONS:
-h, --help Prints help information
COMMANDS:
status Shows status
> dotnet run -- status
The runtime environment is 'Production'
>
According to the documentation, the three standard environments are Development
, Staging
, and Production
; with Production
being the default.
The environment can be changed to Development
with the following command:
> export DOTNET_ENVIRONMENT=Development
> dotnet run -- status
The runtime environment is 'Development'
>
Care needs to be taken with the scope of the environment variables.
In the example above, the DOTNET_ENVIRONMENT
variable is set in the terminal, and therefore is only used when the app is run via the dotnet run
command. The instruction export
ensures that the variable is passed to any subprocess - dotnet
in this case.
If the app is launched from elsewhere, environment variables need to be set elsewhere too. For example, Rider IDE requires setting program arguments and environment variables in the configuration like this:


Achievement Classes
The second commit adds commands to manipulate achievement classes. The commands are set up exactly as per Spectre.Console.Cli's documentation. There is no persistence yet, just a logger that reports successful execution.
The Spectre.Console.Cli
library supports reusing setting properties via inheritance. This does not work in this case because the properties cannot be arranged into an inheritance structure:
Id | Name | Unit | |
---|---|---|---|
Add | X | X | |
Delete | X | ||
List | X? | X? | |
Update | X | X | X |
More precisely, the inheritance structure requires that child classes have all properties of their single parent plus optional extras. But here, the Add
and Delete
commands have mutually exclusive sets of properties, while the Update
command combines both of them, making single-parent inheritance impossible. It's moments like this when one wishes for multiple inheritance support in C#.
The third commit adds the repository, which is borrowed from this Getting started with ADO.NET tutorial and slightly adapted.
Here are the results. From now on the achievement classes are persisted in a SQLite database:
> dotnet run -- class add --name "Drink water" --unit "Glasses"
info: AQ.Console.Commands.AddAchievementClass[0]
Added 'AchievementClass { Id: 1, Name: Drink water, Unit: Glasses }'.
> dotnet run -- class add -n "Drink coffee" -u "Shots"
info: AQ.Console.Commands.AddAchievementClass[0]
Added 'AchievementClass { Id: 2, Name: Drink coffee, Unit: Shots }'.
> dotnet run -- class list
info: AQ.Console.Commands.ListAchievementClasses[0]
Found 2 achievement classes.
AchievementClass { Id: 1, Name: Drink water, Unit: Glasses }
AchievementClass { Id: 2, Name: Drink coffee, Unit: Shots }
>
Achievements
The fourth commit adds achievements, which are set up identically to the achievement classes. First is the model represented by the Achievement
class. Second is persistence implemented through the methods of the Repository
class. Third are the Spectre.Console.Cli
commands. And fourth is the commands configuration in the Program.cs
file.
Here is a table summarising which commands have which properties:
Id | Name | Date | Quantity | |
---|---|---|---|---|
Add | X | X? | X? | |
Delete | X | |||
List | X? | X? | ||
Update | X | X | X | X |
At first glance, the commands work correctly:
> dotnet run -- achievement add -n "Drink water"
info: AQ.Console.Commands.AddAchievement[0]
Added achievement: Achievement { Id: 1, Class: Drink water, Quantity: 1 Unit: Glasses, CompletedDate: 31/12/2024 }.
> dotnet run -- achievement add -n "Drink water" -d "01/01/2025"
info: AQ.Console.Commands.AddAchievement[0]
Added achievement: Achievement { Id: 2, Class: Drink water, Quantity: 1 Unit: Glasses, CompletedDate: 1/1/2025 }.
>
Yet, the date conversion fails because by default it expects an out of order "MM/dd/yyyy" format instead of a typical "dd/MM/yyyy" format:
> dotnet run -- achievement add -n "Drink water" -d "31/01/2024"
Error: Failed to convert '31/01/2024' to DateOnly.
>
The fifth commit fixes this problem by adding a custom type converter. Now the date input is in "dd/MM/yyyy" format for both Add
and Update
commands:
> dotnet run -- achievement add -n "Drink water" -d "31/01/2024"
info: AQ.Console.Commands.AddAchievement[0]
Added achievement: Achievement { Id: 3, Class: Drink water, Quantity: 1 Unit: Glasses, CompletedDate: 31/1/2024 }.
> dotnet run -- achievement update --id 3 -n "Drink water" -q 1 -d "31/12/2024"
info: AQ.Console.Commands.UpdateAchievement[0]
Updated achievement: Achievement { Id: 3, Class: Drink water, Quantity: 1 Unit: Glasses, CompletedDate: 31/12/2024 }.
> dotnet run -- achievement list
info: AQ.Console.Commands.ListAchievements[0]
Found 3 achievements.
Achievement { Id: 1, Class: Drink water, Quantity: 1 Unit: Glasses, CompletedDate: 31/12/2024 }
Achievement { Id: 2, Class: Drink water, Quantity: 1 Unit: Glasses, CompletedDate: 1/1/2025 }
Achievement { Id: 3, Class: Drink water, Quantity: 1 Unit: Glasses, CompletedDate: 31/12/2024 }
>
Deployment
The sixth commit prepares the app for deployment as a global .NET CLI tool.
From the ./AQ.Console
directory, it can be packed and installed as:
> dotnet pack
> dotnet tool install AQ --global --add-source ./packages
Once installed, it can be found in the list of global .NET tools:
> dotnet tool list -g "aq"
Package Id Version Commands
-------------------------------------
aq 0.1.0 aq
And it can be invoked from the terminal in any directory:
> aq --help
USAGE:
aq [OPTIONS] <COMMAND>
OPTIONS:
-h, --help Prints help information
COMMANDS:
achievement Manipulate achievements (add/delete/list/update)
class Manipulate achievement classes (add/delete/list/update)
status Shows status
>
Conclusion
The result is a working app and an initial prototype, demonstrating the Spectre.Console.Cli
library and basic data operations for a one-to-many relationship between achievements and their classes.
Future extensions could include parameterisation of achievement classes with more properties, introduction of time-tracking features like a stopwatch, and usage reports.