diff --git a/CLAUDE.md b/CLAUDE.md index 63c2c87..0df42db 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ If guidance conflicts, follow `specs/constitution.md`. - `tests/Terminal.Gui.Cli.Tests` — unit tests - `tests/Terminal.Gui.Cli.IntegrationTests` — integration tests - `tests/Terminal.Gui.Cli.SmokeTests` — smoke tests -- `examples/Terminal.Gui.Cli.ExampleApp` — sample console app +- `examples/greet` — sample console app ## Build and test diff --git a/Directory.Build.props b/Directory.Build.props index f0681d4..6b50b18 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,8 +19,18 @@ LICENSE 2.4.1-develop.11 + + + true + true + true + snupkg + + + + diff --git a/README.md b/README.md index c54e767..3fec970 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,35 @@ # Terminal.Gui.Cli -![Terminal.Gui.Cli Example App](docs/images/hero.gif) +[![NuGet](https://img.shields.io/nuget/vpre/Terminal.Gui.Cli)](https://www.nuget.org/packages/Terminal.Gui.Cli) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) -A .NET library that lets [Terminal.Gui](https://github.com/gui-cs/Terminal.Gui) applications expose Views as scriptable CLI commands with typed JSON output, POSIX exit codes, and AI-agent discoverability. +> A .NET library that turns [Terminal.Gui](https://github.com/gui-cs/Terminal.Gui) apps into scriptable CLI tools — with typed JSON output, POSIX exit codes, and built-in AI-agent discoverability. -Ships as a single NuGet package: **[`Terminal.Gui.Cli`](https://www.nuget.org/packages/Terminal.Gui.Cli)**. +![Terminal.Gui.Cli in action](docs/images/hero.gif) -## What it does +## Why -`Terminal.Gui.Cli` provides a hosting layer (`CliHost`) that wires up: +Terminal.Gui gives you rich TUI applications. **Terminal.Gui.Cli** lets those same apps participate in scripts, pipelines, and agentic workflows — no separate CLI layer needed. -- **CLI parsing** — positional command dispatch, typed options, `--initial` pre-fill for input commands. -- **Structured output** — `--json` emits a versioned `JsonEnvelope`; `--cat` renders viewer content headlessly. -- **AI-agent discoverability** — `--opencli` emits machine-readable metadata; `--agent-guide` serves embedded Markdown guidance. -- **Built-in help** — `--help` renders command/option metadata via pluggable `IHelpProvider`. -- **Exit codes** — deterministic POSIX exit codes from `CommandResult` status. +One NuGet package. One `CliHost`. All your views become commands. -## Command model +## Features -| Kind | Interface | Description | -|------|-----------|-------------| -| **Input** | `ICliCommand` | Launches a Terminal.Gui UI, returns a typed result. | -| **Viewer** | `IViewerCommand` | Displays content; supports `--cat` for headless rendering. | - -Commands register explicitly (no reflection scanning) and resolve by case-insensitive alias. +| Capability | How | +|---|---| +| **CLI parsing** | Positional command dispatch, typed options, `--initial` pre-fill | +| **Structured output** | `--json` emits a versioned `JsonEnvelope` | +| **Headless rendering** | `--cat` renders viewer content without a TUI | +| **AI discoverability** | `--opencli` metadata + `agent-guide` embedded Markdown | +| **Built-in help** | `--help` via pluggable `IHelpProvider` | +| **Exit codes** | Deterministic POSIX codes from `CommandResult` | ## Quickstart +```sh +dotnet add package Terminal.Gui.Cli +``` + ```csharp using Terminal.Gui.Cli; @@ -36,29 +39,33 @@ CliHost host = new (options => options.Version = "1.0.0"; }); -host.Registry.Register (new MyCommand ()); +host.Registry.Register (new GreetCommand ()); return await host.RunAsync (args); ``` +Then run it: + ```sh -# Interactive (launches Terminal.Gui) -my-app greet --initial "World" +my-app greet --initial "World" # interactive TUI +my-app greet --initial "World" --json # → {"schemaVersion":1,"status":"ok","value":"Hello, World!"} +my-app info --cat # headless viewer output +my-app --opencli # machine-readable command metadata +my-app agent-guide # embedded agent guidance (Markdown) +``` -# JSON envelope -my-app greet --initial "World" --json +## Command model -# Agent discovery -my-app --opencli -my-app agent-guide +| Kind | Interface | Description | +|------|-----------|-------------| +| **Input** | `ICliCommand` | Launches a Terminal.Gui UI, returns a typed result | +| **Viewer** | `IViewerCommand` | Displays content; supports `--cat` for headless rendering | -# Headless viewer -my-app info --cat -``` +Commands register explicitly (no reflection scanning) and resolve by case-insensitive alias. -## Framework options +## Global options -All commands inherit these options from the host: +Every command inherits these from the host: | Option | Description | |--------|-------------| @@ -66,7 +73,7 @@ All commands inherit these options from the host: | `--version` | Show version | | `--opencli` | Emit OpenCLI metadata JSON | | `--json` | Wrap output in JSON envelope | -| `--initial ` | Pre-fill input value | +| `--initial ` | Pre-fill input value (non-interactive mode) | | `--timeout ` | Cancel after duration (e.g., `30s`, `5m`) | | `--output ` / `-o` | Write output to file | | `--cat` | Headless render (viewer commands only) | @@ -74,35 +81,39 @@ All commands inherit these options from the host: ## Repository layout ``` -specs/ Constitution and library spec src/ Terminal.Gui.Cli library tests/ Unit, integration, and smoke tests -examples/ Example console app +examples/ Example console app (see hero GIF above) +specs/ Constitution and library specification scripts/ Tooling and recording scripts docs/ Images and documentation assets ``` -## Build +## Building from source -Requires .NET 10 SDK. Solution file: `Terminal.Gui.Cli.slnx`. +Requires **.NET 10 SDK**. Solution: `Terminal.Gui.Cli.slnx`. ```sh dotnet restore Terminal.Gui.Cli.slnx dotnet build Terminal.Gui.Cli.slnx -# Tests +# Run all test tiers dotnet run --project tests/Terminal.Gui.Cli.Tests dotnet run --project tests/Terminal.Gui.Cli.IntegrationTests dotnet run --project tests/Terminal.Gui.Cli.SmokeTests -# Example app +# Try the example app dotnet run --project examples/Terminal.Gui.Cli.ExampleApp -- greet --initial "World" --json ``` ## Status -**Alpha** — `0.1.0-develop` pre-release stream on the `develop` branch. +**Alpha** — `0.1.0-develop` pre-release. API surface is stabilizing; breaking changes possible. + +## Contributing + +See [`specs/constitution.md`](specs/constitution.md) for architectural rules and PR requirements. ## License -MIT; see [`LICENSE`](LICENSE). +MIT — see [`LICENSE`](LICENSE). diff --git a/Terminal.Gui.Cli.slnx b/Terminal.Gui.Cli.slnx index 2a828b3..f78cb7c 100644 --- a/Terminal.Gui.Cli.slnx +++ b/Terminal.Gui.Cli.slnx @@ -16,7 +16,7 @@ - + diff --git a/docs/images/hero.gif b/docs/images/hero.gif index da61867..a3670fa 100644 Binary files a/docs/images/hero.gif and b/docs/images/hero.gif differ diff --git a/examples/Terminal.Gui.Cli.ExampleApp/InfoCommand.cs b/examples/Terminal.Gui.Cli.ExampleApp/InfoCommand.cs deleted file mode 100644 index b9331d2..0000000 --- a/examples/Terminal.Gui.Cli.ExampleApp/InfoCommand.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Terminal.Gui.App; - -namespace Terminal.Gui.Cli.ExampleApp; - -/// A viewer command that displays application information. -public sealed class InfoCommand : IViewerCommand -{ - private const string InfoText = """ - Example App v1.0.0 - A demonstration of the Terminal.Gui.Cli library. - - This app shows how to: - - Register input and viewer commands - - Use --help, --json, --opencli, and agent-guide - - Embed an agent-guide.md resource - - Support --cat for headless content rendering - """; - - /// - public string PrimaryAlias => "info"; - - /// - public IReadOnlyList Aliases { get; } = ["info"]; - - /// - public string Description => "Display application information."; - - /// - public CommandKind Kind => CommandKind.Viewer; - - /// - public Type ResultType => typeof (string); - - /// - public IReadOnlyList Options { get; } = []; - - /// - public Task RunAsync ( - IApplication app, - string? initial, - CommandRunOptions options, - CancellationToken cancellationToken) - { - return Task.FromResult (new CommandResult (CommandStatus.Ok, InfoText, null, null)); - } - - /// - public Task RenderCatAsync ( - CommandRunOptions options, - TextWriter stdout, - CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull (stdout); - stdout.Write (InfoText); - return Task.FromResult (new CommandResult (CommandStatus.Ok, null, null, null)); - } -} diff --git a/examples/Terminal.Gui.Cli.ExampleApp/Program.cs b/examples/Terminal.Gui.Cli.ExampleApp/Program.cs deleted file mode 100644 index e241dff..0000000 --- a/examples/Terminal.Gui.Cli.ExampleApp/Program.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Reflection; -using Terminal.Gui.Cli; -using Terminal.Gui.Cli.ExampleApp; - -CliHost host = new (options => -{ - options.ApplicationName = "example-app"; - options.Version = "1.0.0"; - options.AgentGuide = "Terminal.Gui.Cli.ExampleApp.agent-guide.md"; - options.AgentGuideIsResource = true; - options.ResourceAssembly = Assembly.GetExecutingAssembly (); -}); - -host.Registry.Register (new GreetCommand ()); -host.Registry.Register (new InfoCommand ()); - -return await host.RunAsync (args); diff --git a/examples/Terminal.Gui.Cli.ExampleApp/Terminal.Gui.Cli.ExampleApp.csproj b/examples/Terminal.Gui.Cli.ExampleApp/Terminal.Gui.Cli.ExampleApp.csproj deleted file mode 100644 index 3737551..0000000 --- a/examples/Terminal.Gui.Cli.ExampleApp/Terminal.Gui.Cli.ExampleApp.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - Exe - Terminal.Gui.Cli.ExampleApp - Terminal.Gui.Cli.ExampleApp - false - - - - - - - - - - - diff --git a/examples/greet/FarewellCommand.cs b/examples/greet/FarewellCommand.cs new file mode 100644 index 0000000..5e269fc --- /dev/null +++ b/examples/greet/FarewellCommand.cs @@ -0,0 +1,52 @@ +using Terminal.Gui.App; + +namespace Terminal.Gui.Cli.Greet; + +/// An input command that says goodbye to someone. +public sealed class FarewellCommand : ICliCommand +{ + /// + public string PrimaryAlias => "farewell"; + + /// + public IReadOnlyList Aliases { get; } = ["farewell", "bye"]; + + /// + public string Description => "Say goodbye to someone."; + + /// + public CommandKind Kind => CommandKind.Input; + + /// + public Type ResultType => typeof (string); + + /// + public IReadOnlyList Options { get; } = + [ + new ("until", "u", typeof (string), "When you expect to meet again.", false, null) + ]; + + /// + public bool AcceptsPositionalArgs => true; + + /// + public Task> RunAsync ( + IApplication app, + string? initial, + CommandRunOptions options, + CancellationToken cancellationToken) + { + var name = options.Arguments.Count > 0 + ? string.Join (" ", options.Arguments) + : initial ?? "World"; + var until = options.CommandOptions.TryGetValue ("until", out var untilValue) + ? untilValue + : null; + + var farewell = until is not null + ? $"Goodbye, {name}! See you {until}." + : $"Goodbye, {name}!"; + + return Task.FromResult (new CommandResult (CommandStatus.Ok, farewell, null, null)); + } +} diff --git a/examples/Terminal.Gui.Cli.ExampleApp/GreetCommand.cs b/examples/greet/GreetCommand.cs similarity index 85% rename from examples/Terminal.Gui.Cli.ExampleApp/GreetCommand.cs rename to examples/greet/GreetCommand.cs index f0308ca..766d164 100644 --- a/examples/Terminal.Gui.Cli.ExampleApp/GreetCommand.cs +++ b/examples/greet/GreetCommand.cs @@ -1,6 +1,6 @@ using Terminal.Gui.App; -namespace Terminal.Gui.Cli.ExampleApp; +namespace Terminal.Gui.Cli.Greet; /// An input command that prompts for a name and returns a greeting. public sealed class GreetCommand : ICliCommand @@ -26,6 +26,9 @@ public sealed class GreetCommand : ICliCommand new ("formal", "f", typeof (bool), "Use a formal greeting style.", false, null) ]; + /// + public bool AcceptsPositionalArgs => true; + /// public Task> RunAsync ( IApplication app, @@ -33,7 +36,10 @@ public Task> RunAsync ( CommandRunOptions options, CancellationToken cancellationToken) { - var name = initial ?? "World"; + var name = options.Arguments.Count > 0 + ? string.Join (" ", options.Arguments) + : initial ?? "World"; + var formal = options.CommandOptions.TryGetValue ("formal", out var formalValue) && formalValue.Equals ("true", StringComparison.OrdinalIgnoreCase); diff --git a/examples/greet/InfoCommand.cs b/examples/greet/InfoCommand.cs new file mode 100644 index 0000000..82ce38c --- /dev/null +++ b/examples/greet/InfoCommand.cs @@ -0,0 +1,102 @@ +using Terminal.Gui.App; +using Terminal.Gui.Input; +using Terminal.Gui.ViewBase; +using Terminal.Gui.Views; + +namespace Terminal.Gui.Cli.Greet; + +/// A viewer command that displays application information in a TUI markdown view. +public sealed class InfoCommand : IViewerCommand +{ + private const string InfoMarkdown = """ + # greet — Info + + **Version:** 1.0.0 + + A demonstration of the `Terminal.Gui.Cli` library. + + ## Features + + This app shows how to: + + - Register input and viewer commands + - Use `--help`, `--json`, `--opencli`, and `agent-guide` + - Embed an `agent-guide.md` resource + - Support `--cat` for headless content rendering + - Launch a TUI markdown viewer for `help` and `info` + + ## Usage + + ``` + greet [name] Greet someone (default: World) + greet --formal [name] Use a formal greeting style + greet help Browse help topics + greet help greet Help for the greet command + greet info Show this info page + greet --help Render help as ANSI to stdout + ``` + """; + + /// + public string PrimaryAlias => "info"; + + /// + public IReadOnlyList Aliases { get; } = ["info"]; + + /// + public string Description => "Display application information."; + + /// + public CommandKind Kind => CommandKind.Viewer; + + /// + public Type ResultType => typeof (void); + + /// + public IReadOnlyList Options { get; } = []; + + /// + public async Task RunAsync ( + IApplication app, + string? initial, + CommandRunOptions options, + CancellationToken cancellationToken) + { + Runnable window = new () + { + Title = "Info", + Width = Dim.Fill (), + Height = Dim.Fill () + }; + + Markdown markdownView = new () + { + Width = Dim.Fill (), + Height = Dim.Fill (1) + }; + + StatusBar statusBar = new ( + [ + new Shortcut (Application.GetDefaultKey (Command.Quit), "Quit", window.RequestStop) + ]); + + window.Add (markdownView, statusBar); + + window.Initialized += (_, _) => { markdownView.Text = InfoMarkdown; }; + + await app.RunAsync (window, cancellationToken); + + return new CommandResult (CommandStatus.Ok, null, null, null); + } + + /// + public Task RenderCatAsync ( + CommandRunOptions options, + TextWriter stdout, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull (stdout); + MarkdownRenderer.RenderToAnsi (InfoMarkdown, stdout); + return Task.FromResult (new CommandResult (CommandStatus.Ok, null, null, null)); + } +} diff --git a/examples/greet/Program.cs b/examples/greet/Program.cs new file mode 100644 index 0000000..2c8480d --- /dev/null +++ b/examples/greet/Program.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using Terminal.Gui.Cli; +using Terminal.Gui.Cli.Greet; + +Assembly assembly = Assembly.GetExecutingAssembly (); + +CliHost host = new (options => +{ + options.ApplicationName = "greet"; + options.Version = "1.0.0"; + options.DefaultCommand = "greet"; + options.AgentGuide = "Terminal.Gui.Cli.Greet.agent-guide.md"; + options.AgentGuideIsResource = true; + options.ResourceAssembly = assembly; + options.HelpProvider = new EmbeddedMarkdownHelpProvider (assembly); +}); + +host.Registry.Register (new GreetCommand ()); +host.Registry.Register (new FarewellCommand ()); +host.Registry.Register (new InfoCommand ()); + +return await host.RunAsync (args); diff --git a/examples/greet/README.md b/examples/greet/README.md new file mode 100644 index 0000000..11ee92f --- /dev/null +++ b/examples/greet/README.md @@ -0,0 +1,51 @@ +# greet + +A sample CLI app built with `Terminal.Gui.Cli` that generates greetings. + +## Usage + +```bash +# Launch the TUI greeting prompt +greet greet + +# Provide a name directly (headless) +greet greet --initial "Alice" + +# Formal greeting style +greet greet --initial "Bob" --formal + +# JSON envelope output +greet greet --initial "World" --json + +# Show help in the TUI markdown viewer +greet help + +# Render help as ANSI markdown to stdout +greet help --cat + +# Show root help (ANSI markdown to stdout) +greet --help + +# Show version +greet --version +``` + +## Commands + +| Command | Description | +|---------|-------------| +| `greet` | Prompt for a name and return a greeting. | +| `info` | Display application information. | +| `help` | Show command help in a TUI markdown viewer. | + +## Building + +```bash +dotnet build examples/Terminal.Gui.Cli.Greet/Terminal.Gui.Cli.Greet.csproj +``` + +## Running + +```bash +dotnet run --project examples/Terminal.Gui.Cli.Greet -- greet --initial "World" +``` diff --git a/examples/greet/Resources/Help/farewell.md b/examples/greet/Resources/Help/farewell.md new file mode 100644 index 0000000..20e2ae7 --- /dev/null +++ b/examples/greet/Resources/Help/farewell.md @@ -0,0 +1,36 @@ +# farewell + +Say goodbye to someone. + +[Back to main help](help:help) + +## Usage + +``` +farewell [name] +farewell --until + + + <_Parameter1>GoldensSourcePath + <_Parameter2>$(MSBuildProjectDirectory)\Goldens + + + + + + + + + + + diff --git a/tests/Terminal.Gui.Cli.Tests/AssemblyAttributes.cs b/tests/Terminal.Gui.Cli.Tests/AssemblyAttributes.cs new file mode 100644 index 0000000..6cdafbb --- /dev/null +++ b/tests/Terminal.Gui.Cli.Tests/AssemblyAttributes.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior (DisableTestParallelization = true)] diff --git a/tests/Terminal.Gui.Cli.Tests/CliHostTests.cs b/tests/Terminal.Gui.Cli.Tests/CliHostTests.cs index ae35150..dfb1791 100644 --- a/tests/Terminal.Gui.Cli.Tests/CliHostTests.cs +++ b/tests/Terminal.Gui.Cli.Tests/CliHostTests.cs @@ -43,6 +43,52 @@ public async Task RunAsync_AgentGuideCat_WritesLiteralWithoutStartingTui () Assert.Equal (string.Empty, stderr.ToString ()); } + [Fact] + public async Task RunAsync_HelpFlag_RendersMarkdownAsAnsi () + { + CliHost host = new (options => + { + options.ApplicationName = "sample"; + options.Version = "1.0.0"; + }); + using StringWriter stdout = new (); + using StringWriter stderr = new (); + + var exitCode = await host.RunAsync (["--help"], TestContext.Current.CancellationToken, stdout, stderr); + + Assert.Equal (ExitCodes.Ok, exitCode); + Assert.Equal (string.Empty, stderr.ToString ()); + + var output = stdout.ToString (); + + // ANSI escape sequences should be present (rendered markdown) + Assert.Contains ("\x1b[", output); + + // Should contain the command name from the registry + Assert.Contains ("help", output); + } + + [Fact] + public async Task RunAsync_HelpCat_RendersMarkdownAsAnsi () + { + CliHost host = new (); + using StringWriter stdout = new (); + using StringWriter stderr = new (); + + var exitCode = await host.RunAsync (["help", "--cat"], TestContext.Current.CancellationToken, stdout, stderr); + + Assert.Equal (ExitCodes.Ok, exitCode); + Assert.Equal (string.Empty, stderr.ToString ()); + + var output = stdout.ToString (); + + // ANSI escape sequences should be present (rendered markdown) + Assert.Contains ("\x1b[", output); + + // Should contain the command name from the registry + Assert.Contains ("help", output); + } + [Fact] public async Task RunAsync_CommandCancellation_ReturnsCancelledExitCode () { diff --git a/tests/Terminal.Gui.Cli.Tests/MarkdownRendererTests.cs b/tests/Terminal.Gui.Cli.Tests/MarkdownRendererTests.cs new file mode 100644 index 0000000..c7973ae --- /dev/null +++ b/tests/Terminal.Gui.Cli.Tests/MarkdownRendererTests.cs @@ -0,0 +1,52 @@ +using Xunit; + +namespace Terminal.Gui.Cli.Tests; + +[Collection ("MarkdownRenderer")] +public sealed class MarkdownRendererTests +{ + [Fact] + public void RenderToAnsi_RestoresDisableRealDriverIO_WhenUnset () + { + // Arrange: clear the env var + Environment.SetEnvironmentVariable ("DisableRealDriverIO", null); + + using StringWriter output = new (); + + // Act + MarkdownRenderer.RenderToAnsi ("# Hello", output); + + // Assert: should be restored to null (unset) + var after = Environment.GetEnvironmentVariable ("DisableRealDriverIO"); + Assert.Null (after); + } + + [Fact] + public void RenderToAnsi_RestoresDisableRealDriverIO_WhenPreviouslySet () + { + // Arrange: set the env var to a known value + Environment.SetEnvironmentVariable ("DisableRealDriverIO", "0"); + + using StringWriter output = new (); + + // Act + MarkdownRenderer.RenderToAnsi ("# Hello", output); + + // Assert: should restore to the previous value, not leave it as "1" + var after = Environment.GetEnvironmentVariable ("DisableRealDriverIO"); + Assert.Equal ("0", after); + + // Cleanup + Environment.SetEnvironmentVariable ("DisableRealDriverIO", null); + } + + [Fact] + public void RenderToAnsi_ProducesOutput () + { + using StringWriter output = new (); + + MarkdownRenderer.RenderToAnsi ("**bold**", output); + + Assert.NotEmpty (output.ToString ()); + } +} diff --git a/tests/Terminal.Gui.Cli.Tests/MetadataHelpProviderTests.cs b/tests/Terminal.Gui.Cli.Tests/MetadataHelpProviderTests.cs new file mode 100644 index 0000000..6b331df --- /dev/null +++ b/tests/Terminal.Gui.Cli.Tests/MetadataHelpProviderTests.cs @@ -0,0 +1,64 @@ +using Terminal.Gui.App; +using Xunit; + +namespace Terminal.Gui.Cli.Tests; + +public sealed class MetadataHelpProviderTests +{ + [Fact] + public void GetRootHelp_ProducesMarkdown () + { + MetadataHelpProvider provider = new (); + CommandRegistry registry = new (); + registry.Register (new StubCommand ("demo", "A demo command.")); + + var result = provider.GetRootHelp (registry); + + Assert.NotNull (result); + Assert.Contains ("## Commands", result); + Assert.Contains ("| `demo` | A demo command. |", result); + Assert.Contains ("## Framework Options", result); + Assert.Contains ("| `--help`, `-h` | Show help |", result); + } + + [Fact] + public void GetCommandHelp_ProducesMarkdown () + { + MetadataHelpProvider provider = new (); + StubCommand command = new ("test", "Test command."); + + var result = provider.GetCommandHelp (command); + + Assert.NotNull (result); + Assert.Contains ("# test", result); + Assert.Contains ("Test command.", result); + } + + private sealed class StubCommand : ICliCommand + { + public StubCommand (string alias, string description) + { + PrimaryAlias = alias; + Aliases = [alias]; + Description = description; + } + + public string PrimaryAlias { get; } + + public IReadOnlyList Aliases { get; } + + public string Description { get; } + + public CommandKind Kind => CommandKind.Input; + + public Type ResultType => typeof (void); + + public IReadOnlyList Options { get; } = []; + + public Task RunAsync (IApplication app, string? initial, + CommandRunOptions options, CancellationToken cancellationToken) + { + throw new NotImplementedException (); + } + } +}