Build full-featured .NET CLIs without writing a parser: methods become commands, XML docs become help text.
Methods become commands, XML docs become help text, records become option sets. A Roslyn source generator emits parsing, routing, dispatch and help into your assembly at build time: no reflection, no runtime overhead, trimming- and AOT-safe by default.
Write vanilla C# and get a fully functional CLI in return: rich --help output, shell tab-completions for bash, zsh and
fish, and a machine-readable JSON schema ready for agentic use cases, all without writing a line of plumbing code.
Heavily inspired by ConsoleAppFramework (Cysharp): rewritten from scratch with a different feature set, but ConsoleAppFramework laid out the path for source-generated CLIs in .NET.
dotnet add package Nullean.ArghFor an app built on Microsoft.Extensions.Hosting and DI:
dotnet add package Nullean.Argh.Hosting
dotnet add package Microsoft.Extensions.Hostingusing Nullean.Argh;
var app = new ArghApp();
app.Map("hello", Handlers.Hello);
return await app.RunAsync(args);
static class Handlers
{
/// <summary>Greets someone by name.</summary>
/// <param name="name">-n,--name, Name to greet.</param>
public static void Hello(string name) => Console.WriteLine($"Hello, {name}!");
}dotnet run -- hello --name Argh prints Hello, Argh!, and dotnet run -- hello --help prints the XML docs as help.
using Microsoft.Extensions.Hosting;
using Nullean.Argh.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddArgh(args, b =>
{
b.Map("hello", Handlers.Hello);
});
await builder.Build().RunAsync();- XML docs are your help text: summaries, param descriptions, remarks and
<example>blocks appear in--helpautomatically. - Everything is generated C#: typed dispatch tree, option parsers and help printers emitted directly into your assembly.
MapGroup-style namespaces: nested command groups with their own help pages and scoped option types.- DTO binding with
[AsParameters]: records and classes expand into flags without a custom bind loop. - Shell completions built in: bash, zsh, fish; one install command per shell.
- Agent-ready schema:
myapp __schemaemits full JSON, conforming to cli-schema v1. - Fuzzy matching: typos produce actionable errors with suggestions.
- DataAnnotations validation:
[Range],[StringLength],[AllowedValues],[Existing]and more; constraints show in--help, failures exit 2. - CancellationToken injection: add it to a handler and it tracks Ctrl+C (or host shutdown with Hosting).
- Zero-dep or ME.* native:
Nullean.Arghhas noMicrosoft.Extensions.*dependency;Nullean.Argh.Hostingplugs intoIHostand DI.
// Method group: direct typed dispatch
app.Map("deploy", DeployHandlers.Run);
// Lambda: one-liners
app.Map("greet", (string name) => Console.WriteLine($"Hello, {name}!"));
// Class: every public method becomes a command
app.Map<StorageHandlers>();
// Namespaces: nested command groups
app.MapNamespace<StorageCommands>("storage", ns =>
{
ns.MapNamespace<BlobCommands>("blob");
});using System.ComponentModel.DataAnnotations;
using Nullean.Argh;
public record DeployOptions(string Environment, bool DryRun = false);
public static class Commands
{
// Positional arguments
public static void Deploy([Argument] string environment) => Console.WriteLine(environment);
// Named flags, kebab-cased: --output-dir ./bin --release
public static void Build(string outputDir, bool release = false) => Console.WriteLine($"{outputDir} {release}");
// DTO binding: --environment prod --dry-run
public static void Ship([AsParameters] DeployOptions opts) => Console.WriteLine(opts);
// Validation
public static void Run(
[Range(1, 65535)] int port,
[AllowedValues("dev", "staging", "prod")] string env) => Console.WriteLine($"{env}:{port}");
}eval "$(myapp __completion bash)" # bash
source <(myapp __completion zsh) # zsh
myapp __completion fish > ~/.config/fish/completions/myapp.fish # fishmyapp __schema > cli-schema.jsonEmits a full JSON description conforming to cli-schema v1: feed it to an LLM, generate docs, or diff it in CI.
| Package | What it is | |
|---|---|---|
Nullean.Argh |
metapackage for console apps (Core + Interfaces) | |
Nullean.Argh.Hosting |
Microsoft.Extensions.Hosting and DI integration |
|
Nullean.Argh.Core |
shared runtime and embedded source generator (transitive) | |
Nullean.Argh.Interfaces |
contracts and attributes (transitive, or for shared libraries) |
Which package do I need? Nullean.Argh for dependency-free console apps, Nullean.Argh.Hosting for apps using
Microsoft.Extensions.Hosting and DI. Everything else is pulled in transitively.
- Documentation: https://argh.nullean.net
examples/, complete working projects:Basic(minimal console app),Hosted(Microsoft.Extensions.Hosting),HostedRoot(hosted with a root command),XmlDocShowcase(full XML doc tag inventory),ArghAotSmoketest(Native AOT validation)- Build targets on top of argh: runna
- Releases
- Contributing
MIT licensed. Copyright Nullean and contributors.
