dotnet-console-apps
.NET console apps - the CLI and bot interface surface
A console binary is one of two things by its external interface: a one-shot CLI tool (parse arguments, do the work, return an exit code) or a long-running gateway app (a bot or consumer that stays connected and reacts to events). The generic host that runs the long-running kind - lifecycle, BackgroundService, graceful shutdown, and the 24/7 hardening: resilience, rate limiting, reconnect, deployment - is dotnet-hosted-services. This skill owns the interface layer on top: how a CLI parses its command surface, and how each bot platform's SDK plugs into that host. Floor is .NET 8 / C# 12.
CLI argument parsing
Three libraries; pick by how much command surface you have.
- System.CommandLine - the parser Microsoft's own
dotnetCLI is built on; reached stable 2.0.0 GA in November 2025. The default for a real command tree (subcommands, options, arguments, shell tab-completion). Migration warning: the API churned hard through the betas -2.0.0-beta5(June 2025) landed major breaking changes, and four sub-packages are now deprecated and excluded from all future releases (DragonFruit, Hosting, Rendering, NamingConventionBinder). Treat any tutorial older than beta5 as stale, and do not adopt the deprecatedHostingpackage to bridge to the generic host - wire the host yourself. - Spectre.Console.Cli - opinionated, type-safe command/settings model (
[CommandArgument]/[CommandOption]), DI support, and rich rendering (tables, prompts, progress bars). The best default for a polished, interactive CLI. - Cocona - minimal, attribute/convention-based, ASP.NET-Core-like ergonomics; fastest to stand up a small command surface.
A CLI tool that also needs config, DI, and logging builds the generic host and drives the parser from it - Host.CreateApplicationBuilder, resolve the command handler from DI, return its exit code. Do not reach for a parser's abandoned hosting shim to do it. The GA shape - SetAction on the command, values read from the ParseResult (the beta-era SetHandler surface is gone):
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IngestCommand>();
using var host = builder.Build();