Quick start
Write idiomatic C#, get a CLI
Ajna maps C# onto the command line as directly as it can, then gets out of the way. There is no command registry to populate, no builder chain to learn and no attributes to apply. A method is a command, a parameter is a flag, an XML summary is help text.
The program at the top of this page is complete: one using, one call to CLI.From<Toolbox>( ).Run( ), one plain class. The session beside it is what it produces — including the help output, which nobody wrote by hand.
Every invocation below reaches the same method:
add numbers 20 22 // positional
add numbers --left 20 --right 22 // long flags, any order
add numbers -l 20 -r 22 // short flags
add numbers 20 --right 22 // mixed
You can run and edit this exact program in the playground — the library compiled to WebAssembly, bound to a DOM terminal, with Roslyn recompiling your edits in the browser. For binding to an instance or a static class, see Command sources.
Conventions
How names become syntax
Names are rewritten mechanically. Each uppercase letter starts a new word; commands join words with spaces, flags join them with hyphens. Underscores are ignored and can be used for visual aid.
Up to 25 parameters are supported — one per lowercase ASCII letter, with -h reserved for help.
API design
How it stays out of the way
The conventions above are the visible half. The other half is what Ajna does not ask of your code.
Your class stays a plain class
No base type, no interface, no attributes. A command source is any type; its public methods are the commands and can be called, tested and reused like any other methods. The framework appears in your code only where you opt in — [CLINoCommand] to hide a public method, a CancellationToken member to receive Ctrl+C.
Parameters use the types you have
Primitives, enums, arrays and nullables work as-is. Any type with a static Parse(string) method or a (string) constructor is a valid parameter type without registration; CLI.AddParseFor<T> is there when you would rather be explicit.
There is no output API
Return whatever is natural. Scalars and objects print through ToString; an IEnumerable<T> prints as it is enumerated, so a yield return loop is a live progress readout without a single call into the library.
Errors are exceptions
Throw CLICustomCommandException or CLICustomParseException and the message is shown under the command's name. Anything else propagates untouched — no result wrappers, no error codes.
Help writes itself
The /// comments you would write anyway become the -h text. Nothing is written twice, and the help cannot drift from the signature it documents.
I/O is a seam, not a dependency
System.Console is the default, not a requirement. With<> swaps in your own ITerminalSurface and IKeyInputSource — the playground on this site is the unchanged library running against a DOM.
Under the hood
What you never have to think about
None of this shows in the API; it is why the API can afford to be this small.
MethodInfo.Invoke, no object[], no reflection on the hot path.Next
Read on
Command sources and discovery, the full flag model — booleans, optionals, arrays, enums — argument parsing and custom types, command results and streaming, cancellation, error handling, help generation and I/O redirection.
/// comments become the help text.
Source
Repository
Implementation, tests and design notes.