Loading

Nullean.Argh

Build full-featured .NET CLIs without writing a parser.

A Roslyn source generator turns plain C# methods into a complete CLI at build time. No reflection, no runtime overhead, trimming- and AOT-safe by default.

This single file produces a CLI with namespaces, typed flags, validation, shell completions, JSON schema, and rich --help output:

using Nullean.Argh;
using System.ComponentModel.DataAnnotations;

var app = new ArghApp();

app.UseGlobalOptions<GlobalOptions>();
app.Map<DeployCommands>();
app.MapNamespace<StorageCommands>("storage", ns =>
{
    ns.MapNamespace<BlobCommands>("blob");
});

return await app.RunAsync(args);

public record GlobalOptions(bool Verbose = false);

public static class DeployCommands
{
    /// <summary>Deploy the app to a target environment.</summary>
    /// <param name="environment">Target environment.</param>
    /// <param name="dryRun">Validate only, make no changes.</param>
    /// <param name="port">-p, Service port.</param>
    public static async Task<int> Deploy(
        [Argument] string environment,
        [Range(1, 65535)] int port = 8080,
        bool dryRun = false,
        CancellationToken ct = default)
    {
        Console.WriteLine($"Deploying to {environment}:{port}");
        return 0;
    }
}

/// <summary>Manage cloud storage resources.</summary>
public sealed class StorageCommands
{
    /// <summary>List objects in the bucket.</summary>
    public void List() => Console.WriteLine("listing...");
}

/// <summary>Blob sub-commands.</summary>
public sealed class BlobCommands
{
    /// <summary>Upload a file to storage.</summary>
    /// <param name="path">-p, --path, Local file path.</param>
    public void Upload([Existing] FileInfo path) =>
        Console.WriteLine($"uploading {path.Name}");
}
		

What you get from this:

$ myapp deploy --help
Usage: myapp deploy <environment> [options]

  Deploy the app to a target environment.

Arguments:
  <environment>       Target environment.

Options:
  -p, --port <int>    Service port. [default: 8080] [range: 1-65535]
  --dry-run           Validate only, make no changes.

Global options:
  -h, --help          Show help.
  --verbose

$ myapp storage blob upload --path ~/missing.txt
Error: --path: file does not exist.

$ myapp storag list
Error: unknown command 'storag'. Did you mean 'storage'?

$ myapp __schema > cli-schema.json
$ eval "$(myapp __completion bash)"
		
  1. full JSON schema for agents/docs/CI
  2. tab completions installed

Every feature below is generated at compile time. Nothing runs via reflection.

<PackageReference Include="Nullean.Argh" />
		
<PackageReference Include="Nullean.Argh.Hosting" />
		
Package Description
Nullean.Argh Dependency-free console apps
Nullean.Argh.Hosting Microsoft.Extensions.Hosting integration
Nullean.Argh.Core Shared runtime + embedded generator (transitive)
Nullean.Argh.Interfaces Contracts and attributes for shared libraries
Tip

Most apps only need Nullean.Argh or Nullean.Argh.Hosting. Everything else is pulled in transitively.