Tenekon.CommandLine.Extensions.PolyType 1.0.0-rc.4

Prefix Reserved
This is a prerelease version of Tenekon.CommandLine.Extensions.PolyType.
dotnet add package Tenekon.CommandLine.Extensions.PolyType --version 1.0.0-rc.4
                    
NuGet\Install-Package Tenekon.CommandLine.Extensions.PolyType -Version 1.0.0-rc.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Tenekon.CommandLine.Extensions.PolyType" Version="1.0.0-rc.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Tenekon.CommandLine.Extensions.PolyType" Version="1.0.0-rc.4" />
                    
Directory.Packages.props
<PackageReference Include="Tenekon.CommandLine.Extensions.PolyType" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Tenekon.CommandLine.Extensions.PolyType --version 1.0.0-rc.4
                    
#r "nuget: Tenekon.CommandLine.Extensions.PolyType, 1.0.0-rc.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Tenekon.CommandLine.Extensions.PolyType@1.0.0-rc.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Tenekon.CommandLine.Extensions.PolyType&version=1.0.0-rc.4&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Tenekon.CommandLine.Extensions.PolyType&version=1.0.0-rc.4&prerelease
                    
Install as a Cake Tool

Tenekon.CommandLine.Extensions.PolyType

Build NuGet Codecov License

Tenekon.CommandLine.Extensions.PolyType adds an attribute-driven layer on top of System.CommandLine, powered by PolyType shape generation. You define commands, options, and arguments with attributes, and get fast, strongly-typed binding without runtime reflection. It supports class commands and function commands and is trimming/AOT friendly.

This project was crafted in a very short time. The public API is intended to be stable, but changes may occur as the library matures.

Install

dotnet add package Tenekon.CommandLine.Extensions.PolyType

Table of Content

Prerequisites

  • Use PolyType source generation via [GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)] and [GenerateShapeFor(IncludeMethods = MethodShapeFlags.AllPublic)].
  • Command types must be partial.
  • Target any project that can reference netstandard2.0 (the package also ships net10.0).

Quick Start (Class-Based)

Program.cs:

using Tenekon.CommandLine.Extensions.PolyType.Runtime;

return CommandRuntime.Factory.Object
    .Create<RootCommand>(
        settings: null,
        modelRegistry: null,
        modelBuildOptions: null,
        serviceResolver: null)
    .Run(args);

Command type:

using PolyType;
using PolyType.SourceGenModel;
using Tenekon.CommandLine.Extensions.PolyType.Spec;
using Tenekon.CommandLine.Extensions.PolyType.Runtime;

[GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
[CommandSpec(Description = "Root command")]
public partial class RootCommand
{
    [OptionSpec(Description = "Greeting target")]
    public string Name { get; set; } = "world";

    [ArgumentSpec(Description = "Input file")]
    public string? File { get; set; }

    public int Run(CommandRuntimeContext context)
    {
        if (context.IsEmptyCommand())
        {
            context.ShowHelp();
            return 0;
        }

        Console.WriteLine($"Hello {Name}");
        Console.WriteLine($"File = {File}");
        return 0;
    }
}

Quick Start (Function-Based)

Program.cs: Preparation:

using PolyType;
using Tenekon.CommandLine.Extensions.PolyType.Spec;

[GenerateShapeFor(typeof(GreetCommand))]
public partial class CliShapes;

[CommandSpec(Description = "Greets from a function")]
public delegate int GreetCommand([OptionSpec] string name);

GenerateShapeForAttribute supports glob pattern

Function commands require a function instance. Register it via runtime.FunctionRegistry or provide a custom ICommandFunctionResolver.

using Tenekon.CommandLine.Extensions.PolyType.Runtime;

var runtime = CommandRuntime.Factory.Function.Create<GreetCommand, CliShapes>(
    settings: null,
    modelRegistry: null,
    modelBuildOptions: null,
    serviceResolver: null);

runtime.FunctionRegistry.Set<GreetCommand>(name =>
{
    Console.WriteLine($"Hello {name}");
    return 0;
});

return runtime.Run(args);

Explicit:

using PolyType;
using PolyType.Abstractions;
using Tenekon.CommandLine.Extensions.PolyType.Runtime;

var shape = (IFunctionTypeShape)TypeShapeResolver.Resolve<GreetCommand>();
var provider = shape.Provider;

var runtime = CommandRuntime.Factory.Function.Create(
    commandType: typeof(GreetCommand),
    commandTypeShapeProvider: provider,
    settings: null,
    modelRegistry: null,
    modelBuildOptions: null,
    serviceResolver: null);

runtime.FunctionRegistry.Set<GreetCommand>(name =>
{
    Console.WriteLine($"Hello {name}");
    return 0;
});

return runtime.Run(args);

Concepts at a Glance

  • Spec attributes describe commands, options, arguments, and directives.
  • Model (Advanced) represents the metadata graph derived from shapes.
  • Runtime builds System.CommandLine commands and binders from the model.
  • Binding maps parsed tokens to strongly-typed objects.
  • Invocation runs handlers with services and functions resolved.

Defining Commands

Use [CommandSpec] on classes to define commands.

Nested child commands:

[CommandSpec(Name = "git", Description = "Root command")]
public partial class GitCommand
{
    [CommandSpec(Name = "status")]
    public partial class StatusCommand
    {
        public int Run() => 0;
    }
}

Explicit parent/child linkage:

[CommandSpec(Description = "Root", Children = new[] { typeof(StatusCommand) })]
public partial class RootCommand { }

[CommandSpec(Parent = typeof(RootCommand), Description = "Child")]
public partial class StatusCommand { }

You can also control unmatched tokens with TreatUnmatchedTokensAsErrors on [CommandSpec].

Defining Options and Arguments

Property-based definitions:

[CommandSpec]
public partial class BuildCommand
{
    [OptionSpec(Description = "Configuration")]
    public string? Configuration { get; set; }

    [ArgumentSpec(Description = "Project path")]
    public string Project { get; set; } = "";

    public int Run() => 0;
}

Parameter-based definitions:

[CommandSpec]
public partial class CleanCommand
{
    public int Run([OptionSpec] bool force, [ArgumentSpec] string path) => 0;
}

OptionSpecAttribute and ArgumentSpecAttribute support:

  • Name, Description, Hidden, Order
  • Arity, Required
  • AllowedValues
  • ValidationRules, ValidationPattern, ValidationMessage

OptionSpecAttribute also supports:

  • Alias, Aliases, HelpName
  • Recursive
  • AllowMultipleArgumentsPerToken

Defining Directives

Define directives with [DirectiveSpec] on properties or parameters. Supported types are bool, string, and string[].

[CommandSpec]
public partial class AnalyzeCommand
{
    [DirectiveSpec(Description = "Enable verbose diagnostics")]
    public bool Verbose { get; set; }

    public int Run() => 0;
}

Handler Methods and Signatures

Supported handler signatures:

  • void Run() and int Run()
  • Task RunAsync() and Task<int> RunAsync()

Optional parameters:

  • CommandRuntimeContext as the first parameter
  • CancellationToken as the last parameter
  • Any other parameters resolve as services or functions

Method Commands (Instance Methods)

Public instance methods annotated with [CommandSpec] become child commands.

[CommandSpec]
public partial class ToolCommand
{
    [CommandSpec(Description = "Clean outputs")]
    public int Clean([OptionSpec] bool force) => 0;
}

Interface-Based Specs

Attributes can live on interfaces when the shape is generated with [GenerateShapeFor].

public interface IHasVerbosity
{
    [OptionSpec(Description = "Verbose output")]
    bool Verbose { get; set; }
}

[GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
[GenerateShapeFor(typeof(IHasVerbosity))]
[CommandSpec]
public partial class InfoCommand : IHasVerbosity
{
    public bool Verbose { get; set; }
    public int Run() => 0;
}

Naming Rules and Aliases

  • Names are auto-generated from type/member names.
  • Common suffixes like Command, Option, and Argument are stripped.
  • Names default to kebab-case.
  • Options get long and short forms by default.

Override naming in [CommandSpec]:

[CommandSpec(
    Name = "init",
    Alias = "i",
    NameAutoGenerate = NameAutoGenerate.None,
    NameCasingConvention = NameCasingConvention.KebabCase)]
public partial class InitializeCommand { }

Requiredness and Arity

Requiredness and arity can be explicit or inferred from nullability and defaults.

[CommandSpec]
public partial class DeployCommand
{
    [OptionSpec(Required = true)]
    public string Environment { get; set; } = "";

    [ArgumentSpec(Arity = ArgumentArity.OneOrMore)]
    public string[] Targets { get; set; } = [];
}

Validation and Allowed Values

ValidationRules supports common file, directory, path, and URL rules. You can also provide a regex with ValidationPattern and a custom ValidationMessage.

[CommandSpec]
public partial class ScanCommand
{
    [ArgumentSpec(
        ValidationRules = ValidationRules.ExistingFile | ValidationRules.LegalPath,
        ValidationMessage = "Input must be an existing file")]
    public string Input { get; set; } = "";
}

Runtime Creation

Use CommandRuntime.Factory.Object for class-based commands and CommandRuntime.Factory.Function for function commands.

Runtime Creation: Class-Based

var runtime = CommandRuntime.Factory.Object.Create<RootCommand>(
    settings: null,
    modelRegistry: null,
    modelBuildOptions: null,
    serviceResolver: null);

Runtime Creation: Custom ITypeShapeProvider

using PolyType.Abstractions;

var shape = (IObjectTypeShape)TypeShapeResolver.Resolve<RootCommand>();
var provider = shape.Provider;

var runtime = CommandRuntime.Factory.Object.Create(
    commandType: typeof(RootCommand),
    commandTypeShapeProvider: provider,
    settings: null,
    modelRegistry: null,
    modelBuildOptions: null,
    serviceResolver: null);

Runtime Creation: Model-First (Advanced)

using PolyType.Abstractions;
using Tenekon.CommandLine.Extensions.PolyType.Model;

var shape = (IObjectTypeShape)TypeShapeResolver.Resolve<RootCommand>();
var provider = shape.Provider;

var registry = new CommandModelRegistry();
var model = registry.Object.GetOrAdd(
    typeof(RootCommand),
    provider,
    new CommandModelBuildOptions { RootParentHandling = RootParentHandling.Ignore });

var runtime = CommandRuntime.Factory.Object.CreateFromModel(
    model,
    settings: null,
    serviceResolver: null);

Runtime Creation: Function-Based

using PolyType.Abstractions;

var shape = (IFunctionTypeShape)TypeShapeResolver.Resolve<GreetCommand>();
var provider = shape.Provider;

var runtime = CommandRuntime.Factory.Function.Create(
    commandType: typeof(GreetCommand),
    commandTypeShapeProvider: provider,
    settings: null,
    modelRegistry: null,
    modelBuildOptions: null,
    serviceResolver: null);

Parsing Without Invocation

var result = runtime.Parse(args);
if (result.ParseResult.Errors.Count > 0)
{
    // handle errors
}

var instance = result.Bind<RootCommand>();

Binding Results and Helpers

var called = result.BindCalled();
var all = result.BindAll();
var isCalled = result.IsCalled<RootCommand>();
var hasRoot = result.Contains<RootCommand>();

if (result.TryGetBinder(typeof(RootCommand), typeof(RootCommand), out var binder))
{
    binder(instance, result.ParseResult);
}

Invocation Options (Per Call)

var options = new CommandInvocationOptions
{
    ServiceResolver = new MyServiceResolver(),
    FunctionResolver = new MyFunctionResolver()
};

return runtime.Run(args, options);

CommandRuntimeContext

CommandRuntimeContext provides:

  • ParseResult
  • IsEmptyCommand()
  • ShowHelp()
  • ShowHierarchy()
  • ShowValues()
public int Run(CommandRuntimeContext context)
{
    if (context.IsEmptyCommand())
    {
        context.ShowHelp();
        return 0;
    }

    context.ShowValues();
    return 0;
}

Services and Service Resolution

ICommandServiceResolver provides constructor and handler dependencies that are not bound as options, arguments, or directives.

public sealed class ServiceProviderResolver(IServiceProvider provider) : ICommandServiceResolver
{
    public bool TryResolve<TService>(out TService? value)
    {
        value = (TService?)provider.GetService(typeof(TService));
        return value is not null;
    }
}

var runtime = CommandRuntime.Factory.Object.Create<RootCommand>(
    settings: null,
    modelRegistry: null,
    modelBuildOptions: null,
    serviceResolver: new ServiceProviderResolver(serviceProvider));

Functions and Function Resolution

Function instances are resolved separately from services. The resolution order is:

  • CommandInvocationOptions.FunctionResolver
  • CommandRuntime.FunctionRegistry and CommandRuntimeSettings.FunctionResolvers
  • ICommandServiceResolver if AllowFunctionResolutionFromServices is enabled

Service resolution for functions only happens when AllowFunctionResolutionFromServices is enabled.

Register a function instance:

runtime.FunctionRegistry.Set<GreetCommand>(name =>
{
    Console.WriteLine($"Hello {name}");
    return 0;
});

Custom function resolver:

public sealed class MyFunctionResolver : ICommandFunctionResolver
{
    public bool TryResolve<TFunction>(out TFunction value)
    {
        if (typeof(TFunction) == typeof(GreetCommand))
        {
            value = (TFunction)(object)(GreetCommand)(name => 0);
            return true;
        }

        value = default!;
        return false;
    }
}

Settings (CommandRuntimeSettings)

Common settings:

  • EnableDefaultExceptionHandler
  • ShowHelpOnEmptyCommand
  • AllowFunctionResolutionFromServices
  • EnableDiagramDirective
  • EnableSuggestDirective
  • EnableEnvironmentVariablesDirective
  • Output, Error
  • FileSystem (Advanced)
  • FunctionResolvers
  • EnablePosixBundling
  • ResponseFileTokenReplacer
var settings = new CommandRuntimeSettings
{
    ShowHelpOnEmptyCommand = true,
    EnableSuggestDirective = true
};

Response Files and POSIX Bundling

using System.CommandLine.Parsing;

var settings = new CommandRuntimeSettings
{
    EnablePosixBundling = true,
    ResponseFileTokenReplacer = static (Token token, out string[]? replacements) =>
    {
        replacements = null;
        return false;
    }
};

Built-In Directives

Configure built-in directives with:

  • EnableDiagramDirective
  • EnableSuggestDirective
  • EnableEnvironmentVariablesDirective

File System Abstraction (Advanced)

Validation rules use IFileSystem. Provide your own implementation if needed.

public sealed class InMemoryFileSystem : IFileSystem
{
    public IFileSystemFile File { get; } = new InMemoryFile();
    public IFileSystemDirectory Directory { get; } = new InMemoryDirectory();
    public IFileSystemPath Path { get; } = new InMemoryPath();

    private sealed class InMemoryFile : IFileSystemFile
    {
        public bool FileExists(string path) => false;
    }

    private sealed class InMemoryDirectory : IFileSystemDirectory
    {
        public bool DirectoryExists(string path) => false;
    }

    private sealed class InMemoryPath : IFileSystemPath
    {
        public char[] GetInvalidPathChars() => [];
        public char[] GetInvalidFileNameChars() => [];
    }
}

Trimming and AOT

PolyType source generation allows runtime binding without reflection, making this library trimming-friendly and suitable for Native AOT scenarios.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-rc.4 62 2/23/2026
1.0.0-rc.3 37 2/23/2026
1.0.0-rc.2 43 2/18/2026
1.0.0-rc.1 42 2/15/2026
0.0.1-alpha.1 50 2/12/2026

Initial public release. Attribute-based command definitions for System.CommandLine powered by PolyType (no reflection), with strongly-typed binding, DI support, and command composition.