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/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/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/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
index 470da09..6b331df 100644
--- a/tests/Terminal.Gui.Cli.Tests/MetadataHelpProviderTests.cs
+++ b/tests/Terminal.Gui.Cli.Tests/MetadataHelpProviderTests.cs
@@ -6,54 +6,59 @@ namespace Terminal.Gui.Cli.Tests;
public sealed class MetadataHelpProviderTests
{
[Fact]
- public void GetRootHelp_GeneratesMarkdownWithHeadings ()
+ public void GetRootHelp_ProducesMarkdown ()
{
- CommandRegistry registry = new ();
- registry.Register (new StubCommand ("pick", "Pick something."));
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 ("## Framework options", result);
- Assert.Contains ("- `pick`", result);
- Assert.Contains ("`--help`", result);
+ Assert.Contains ("| `demo` | A demo command. |", result);
+ Assert.Contains ("## Framework Options", result);
+ Assert.Contains ("| `--help`, `-h` | Show help |", result);
}
[Fact]
- public void GetRootHelp_ListsAllRegisteredCommands ()
+ public void GetCommandHelp_ProducesMarkdown ()
{
- CommandRegistry registry = new ();
- registry.Register (new StubCommand ("alpha", "Alpha command."));
- registry.Register (new StubCommand ("beta", "Beta command."));
MetadataHelpProvider provider = new ();
+ StubCommand command = new ("test", "Test command.");
- var result = provider.GetRootHelp (registry);
+ var result = provider.GetCommandHelp (command);
Assert.NotNull (result);
- Assert.Contains ("- `alpha` — Alpha command.", result);
- Assert.Contains ("- `beta` — Beta command.", result);
+ Assert.Contains ("# test", result);
+ Assert.Contains ("Test command.", result);
}
- private sealed class StubCommand (string alias, string description) : ICliCommand
+ private sealed class StubCommand : ICliCommand
{
- public string PrimaryAlias { get; } = alias;
+ public StubCommand (string alias, string description)
+ {
+ PrimaryAlias = alias;
+ Aliases = [alias];
+ Description = description;
+ }
+
+ public string PrimaryAlias { get; }
- public IReadOnlyList Aliases => [PrimaryAlias];
+ public IReadOnlyList Aliases { get; }
- public string Description => description;
+ public string Description { get; }
public CommandKind Kind => CommandKind.Input;
- public Type ResultType => typeof (string);
+ public Type ResultType => typeof (void);
public IReadOnlyList Options { get; } = [];
- public Task RunAsync (IApplication app, string? initial, CommandRunOptions options,
- CancellationToken cancellationToken)
+ public Task RunAsync (IApplication app, string? initial,
+ CommandRunOptions options, CancellationToken cancellationToken)
{
- return Task.FromResult (new CommandResult (CommandStatus.Ok, "ok", null, null));
+ throw new NotImplementedException ();
}
}
}