
Company News
Socket Named to Rising in Cyber 2026 List of Top Cybersecurity Startups
Socket was named to the Rising in Cyber 2026 list, recognizing 30 private cybersecurity startups selected by CISOs and security executives.
tenekon.commandline.extensions.polytype
Advanced tools
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.
[!NOTE] 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.
dotnet add package Tenekon.CommandLine.Extensions.PolyType
[GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)] and [GenerateShapeFor(IncludeMethods = MethodShapeFlags.AllPublic)].partial.netstandard2.0 (the package also ships net10.0).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;
}
}
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);
[!TIP] GenerateShapeForAttribute supports glob pattern
[!NOTE] Function commands require a function instance. Register it via
runtime.FunctionRegistryor provide a customICommandFunctionResolver.
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);
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].
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:
OptionSpecAttribute also supports:
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;
}
Supported handler signatures:
void Run() and int Run()Task RunAsync() and Task<int> RunAsync()Optional parameters:
CommandRuntimeContext as the first parameterCancellationToken as the last parameterPublic instance methods annotated with [CommandSpec] become child commands.
[CommandSpec]
public partial class ToolCommand
{
[CommandSpec(Description = "Clean outputs")]
public int Clean([OptionSpec] bool force) => 0;
}
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;
}
Command, Option, and Argument are stripped.kebab-case.Override naming in [CommandSpec]:
[CommandSpec(
Name = "init",
Alias = "i",
NameAutoGenerate = NameAutoGenerate.None,
NameCasingConvention = NameCasingConvention.KebabCase)]
public partial class InitializeCommand { }
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; } = [];
}
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; } = "";
}
Use CommandRuntime.Factory.Object for class-based commands and CommandRuntime.Factory.Function for function commands.
var runtime = CommandRuntime.Factory.Object.Create<RootCommand>(
settings: null,
modelRegistry: null,
modelBuildOptions: null,
serviceResolver: null);
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);
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);
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);
var result = runtime.Parse(args);
if (result.ParseResult.Errors.Count > 0)
{
// handle errors
}
var instance = result.Bind<RootCommand>();
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);
}
var options = new CommandInvocationOptions
{
ServiceResolver = new MyServiceResolver(),
FunctionResolver = new MyFunctionResolver()
};
return runtime.Run(args, options);
CommandRuntimeContext provides:
ParseResultIsEmptyCommand()ShowHelp()ShowHierarchy()ShowValues()public int Run(CommandRuntimeContext context)
{
if (context.IsEmptyCommand())
{
context.ShowHelp();
return 0;
}
context.ShowValues();
return 0;
}
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));
Function instances are resolved separately from services. The resolution order is:
CommandInvocationOptions.FunctionResolverCommandRuntime.FunctionRegistry and CommandRuntimeSettings.FunctionResolversICommandServiceResolver if AllowFunctionResolutionFromServices is enabled[!IMPORTANT] Service resolution for functions only happens when
AllowFunctionResolutionFromServicesis 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;
}
}
Common settings:
EnableDefaultExceptionHandlerShowHelpOnEmptyCommandAllowFunctionResolutionFromServicesEnableDiagramDirectiveEnableSuggestDirectiveEnableEnvironmentVariablesDirectiveOutput, ErrorFileSystem (Advanced)FunctionResolversEnablePosixBundlingResponseFileTokenReplacervar settings = new CommandRuntimeSettings
{
ShowHelpOnEmptyCommand = true,
EnableSuggestDirective = true
};
using System.CommandLine.Parsing;
var settings = new CommandRuntimeSettings
{
EnablePosixBundling = true,
ResponseFileTokenReplacer = static (Token token, out string[]? replacements) =>
{
replacements = null;
return false;
}
};
Configure built-in directives with:
EnableDiagramDirectiveEnableSuggestDirectiveEnableEnvironmentVariablesDirectiveValidation 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() => [];
}
}
PolyType source generation allows runtime binding without reflection, making this library trimming-friendly and suitable for Native AOT scenarios.
FAQs
Unknown package
We found that tenekon.commandline.extensions.polytype demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Socket was named to the Rising in Cyber 2026 list, recognizing 30 private cybersecurity startups selected by CISOs and security executives.

Research
Socket detected 84 compromised TanStack npm package artifacts modified with suspected CI credential-stealing malware.

Security News
A dispute over fsnotify maintainer access set off supply chain alarms around one of Go’s most widely used filesystem libraries.