StreamShell 2026.5.31

dotnet add package StreamShell --version 2026.5.31
                    
NuGet\Install-Package StreamShell -Version 2026.5.31
                    
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="StreamShell" Version="2026.5.31" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="StreamShell" Version="2026.5.31" />
                    
Directory.Packages.props
<PackageReference Include="StreamShell" />
                    
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 StreamShell --version 2026.5.31
                    
#r "nuget: StreamShell, 2026.5.31"
                    
#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 StreamShell@2026.5.31
                    
#: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=StreamShell&version=2026.5.31
                    
Install as a Cake Addin
#tool nuget:?package=StreamShell&version=2026.5.31
                    
Install as a Cake Tool

StreamShell

NuGet Downloads License: MIT .NET

An interactive, markup-capable console host for .NET — type, render, and command without the plumbing.


Hero Demo

<img width="800" alt="Hero Demo" src="https://github.com/user-attachments/assets/73611c40-19c9-42f2-b8d2-cc2abce623bd" />


What Is This?

StreamShell is a .NET library you embed into your console app to get a fully interactive terminal session — structured prompt loop, Spectre.Console markup rendering, and command dispatch — without writing the plumbing yourself.

It's for you if:

  • You're building a .NET CLI tool that needs a live, stateful REPL-style session
  • You want styled terminal output (colors, tables, progress) without a full TUI framework
  • You need inline text editing with cursor navigation, selection, clipboard, and undo
  • You want to display interactive selection panels for picking options at runtime
  • You need a prompt host, not a full-screen app

It's NOT:

  • A full TUI framework (no panels, layouts, or mouse support — see Terminal.Gui for that)
  • A command-line argument parser (see System.CommandLine)
  • A replacement for dotnet run or shell scripting

Features

✨ Markup Rendering

Display colored, styled terminal output using Spectre.Console markup syntax. Queued messages are rendered in-line as they arrive, with the input block automatically shifting down.

host.AddMessage("[green]Success![/]");
host.AddMessage("[bold][red]Error:[/] something went wrong[/]");

<img width="800" alt="demo-ezgif com-cut(1)" src="https://github.com/user-attachments/assets/1244e8b3-1238-4c80-9be7-930f4086ee5f" />

✨ Command Dispatch with Autocomplete

Register commands with /name. Type / and the command palette opens automatically with hints. Tab to autocomplete, or continue typing to filter.

host.AddCommand(new Command("hello", "Say hello", (args, named) =>
{
    host.AddMessage("[green]Hello, world![/]");
    return Task.CompletedTask;
}));

Commands can also register argument-level autocomplete suggestions:

host.AddCommand("config", "Set a config value",
    (args, named) => { /* handler */ },
    ["LargePasteThreshold", "CursorMarkup", "SelectionMarkup"]);

<img width="800" alt="Command autocomplete demo" src="https://github.com/user-attachments/assets/9c5c4f86-6fb4-40ec-ad66-0bafbf0de3eb" />

✨ Inline Text Editing

Full editing experience while typing — cursor movement, text selection with Shift+arrows, clipboard integration (Ctrl+C / Ctrl+V / Ctrl+X), undo (Ctrl+Z), and multi-line input wrapping.

Paste a large block of text and it's automatically detected and attached as a separate attachment object rather than flooding the input buffer.

<img width="800" alt="Text Editing" src="https://github.com/user-attachments/assets/cd70306d-a72e-44c5-91e6-0ea34a2a5a4d" />

✨ Interactive Selection Panels

Prompt users with a navigable selection panel at the bottom of the console. Arrow keys to navigate, Enter to select, Escape to cancel. Supports both single-select and multi-select modes with min/max constraints.

var selected = await host.PromptSelection("Pick an OS", osVariants);

<img width="800" alt="Interactive Selection Panels" src="https://github.com/user-attachments/assets/40038e51-06ff-46b7-8dfc-96287509fe4b" />

✨ Customizable Separators

Add styled separators between the message feed, input block, and hint panel. Each separator supports left/right text labels and configurable fill characters.

host.SetTopSeparator("Messages", "StreamShell", '-', "white");
host.SetBottomSeparator("Input", null, '\u2500');

✨ Extensible Bottom Panels

The hint area below the input is fully customizable via the IBottomPanel interface. Create panels that show character counters, status info, dynamic suggestions, or any custom content.

public class StatusPanel : IBottomPanel
{
    public int LineCount => 2;
    public IReadOnlyList<string> GetLines(string input)
    {
        return ["", $"[dim]Characters: {input.Length}[/]"];
    }
}

host.SetDefaultPanel(new StatusPanel());

<img width="1073" height="694" alt="Extensible Bottom Panels + Customizable Separators" src="https://github.com/user-attachments/assets/55425cc8-a92a-4480-8e68-4e8932cd0893" />

✨ Input Field Save/Load

Save and restore input field states by ID. Useful for preserving partially-typed input during operations that need to clear the buffer temporarily.

string id = host.InputHandler.SaveInputField();     // save current text
host.InputHandler.LoadInputField(id);                // restore it later
host.InputHandler.RemoveSavedInputField(id);         // dispose

Installation

dotnet add package StreamShell

Or via Package Manager Console:

Install-Package StreamShell

Requires: .NET 10.0 or later

NuGet page: https://www.nuget.org/packages/StreamShell/


Quick Start

using StreamShell;

var host = new ConsoleAppHost();

host.AddCommand(new Command("hello", "Say hello", (args, named) =>
{
    host.AddMessage("[green]Hello, world![/]");
    return Task.CompletedTask;
}));

host.AddCommand(new Command("quit", "Exit the app", (_, _) =>
{
    host.Stop();
    return Task.CompletedTask;
}));

host.UserInputSubmitted += input =>
{
    host.AddMessage($"[grey]You said:[/] {input.RawOutput}");
};

host.AddMessage("[yellow]StreamShell ready. Type /hello or /quit[/]");
await host.Run();

That's it. Run the app, type /hello, press Enter — you'll see Hello, world! rendered in green. Type anything else to see your input echoed back.


Usage

Registering Commands

Commands are triggered by typing /name. Register them with either the Command class:

host.AddCommand(new Command("echo", "Echo your message", (args, named) =>
{
    string message = string.Join(" ", args);
    host.AddMessage($"[cyan]{Markup.Escape(message)}[/]");
    return Task.CompletedTask;
}));

Or the fluent overload with argument suggestions:

host.AddCommand("greet", "Greet someone", async (args, named) =>
{
    string name = args.Length > 0 ? args[0] : "world";
    host.AddMessage($"[green]Hello, {Markup.Escape(name)}![/]");
}, ["friend", "colleague", "boss"]);

Handling Submitted Input

The UserInputSubmitted event fires for both commands and plain text. Check InputType to differentiate:

host.UserInputSubmitted += args =>
{
    if (args.InputType == InputType.PlainText)
        host.AddMessage($"[grey]Text:[/] {Markup.Escape(args.RawOutput)}");

    foreach (var att in args.Attachments)
        host.AddMessage($"[grey][[{att.Type}: {att.Content.Length} chars]][/]");
};

Selection Panels

Prompt the user to pick from a list of options. PromptSelection returns an array of IVariant[] or null if cancelled:

var options = new IVariant[]
{
    new VariantItem("[bold]Option A[/]"),
    new VariantItem("[green]Option B[/]"),
};

var result = await host.PromptSelection("Choose one", options);
if (result is not null)
    host.AddMessage($"[green]Selected: {Markup.Escape(result[0].Name)}[/]");

For multi-select, pass a SelectionInfo:

// Min 1, Max 3 selections
var result = await host.PromptSelection("Pick tools", toolVariants,
    new SelectionInfo { Min = 1, Max = 3 });

Custom Bottom Panels

Implement IBottomPanel to replace the hint area below the input:

public class StatusPanel : IBottomPanel
{
    public int LineCount => 2;
    public IReadOnlyList<string> GetLines(string currentInput)
    {
        return ["", $"[dim]Length: {currentInput.Length} chars[/]"];
    }
}

// Set as the default panel (shown when no command is active)
host.SetDefaultPanel(new StatusPanel());

// Or set as the active panel immediately
host.SetBottomPanel(new StatusPanel());

// Restore the built-in empty panel
host.ResetBottomPanel();

Async Commands

Command handlers support async operations natively:

host.AddCommand(new Command("fetch", "Fetch data", async (_, _) =>
{
    host.AddMessage("[yellow]Fetching...[/]");
    await Task.Delay(1000);
    host.AddMessage("[green]Data received![/]"));
}));

Cancellation and Shutdown

  • Ctrl+C is reserved for clipboard Copy (not cancellation)
  • Ctrl+D exits the session gracefully
  • Call host.Stop() from any command to terminate
  • Pass a CancellationToken to Run() for external cancellation

Configuration

Option Type Default Description
LargePasteThreshold int 300 Max chars before input is treated as a large paste (attachment)
LargePasteLineThreshold int 4 Max lines before input is treated as a large paste
CursorMarkup string "bold black on cyan" Spectre markup for cursor highlight
SelectionMarkup string "bold cyan on Grey27" Spectre markup for selected text
CommandSlashMarkup string "Red1" Spectre markup for the command slash (/)
InputPrefix string "[bold SkyBlue1]> [/]" Spectre markup for the input prompt prefix
ContinuationPrefix string " " Plain text prefix for wrapped continuation lines
WrappingRightMargin int 4 Right-edge buffer for text wrapping

Configure via the host's Settings property before running:

var host = new ConsoleAppHost();
host.Settings.LargePasteThreshold = 500;
host.Settings.CursorMarkup = "bold white on blue";
host.Settings.InputPrefix = "[bold green]$ [/]";

Markup Reference

StreamShell uses Spectre.Console markup for all styled output. Common tags:

Tag Effect Example
[bold]...[/] Bold text [bold]Important[/]
[red]...[/] Red foreground [red]Error[/]
[green]...[/] Green foreground [green]Success[/]
[cyan]...[/] Cyan foreground [cyan]Info[/]
[yellow]...[/] Yellow foreground [yellow]Warning[/]
[grey]...[/] Grey foreground [grey]debug[/]
[dim]...[/] Dimmed text [dim]optional[/]
[bg:blue]...[/] Blue background [bg:blue]Highlighted[/]

Markup can be nested: [bold][red]Bold red text[/][/]

Important: Always wrap untrusted content (user input, dynamic values) with Markup.Escape() to prevent broken markup:

host.AddMessage($"[green]User: {Markup.Escape(userInput)}[/]");

Recipes

Build a simple REPL

var host = new ConsoleAppHost();
host.AddCommand(new Command("eval", "Evaluate an expression", (args, _) =>
{
    string expr = string.Join(" ", args);
    host.AddMessage($"[cyan]Evaluated: {expr}[/]");
    return Task.CompletedTask;
}));
await host.Run();

Add dynamic tab completion for a specific command

var completions = new[] { "apple", "banana", "cherry" };
host.AddCommand("fruit", "Pick a fruit",
    (args, _) =>
    {
        string picked = args.Length > 0 ? args[0] : "none";
        host.AddMessage($"[green]You picked: {picked}[/]");
        return Task.CompletedTask;
    },
    completions);

Log all commands to a file

host.UserInputSubmitted += args =>
{
    if (args.InputType == InputType.Command)
        File.AppendAllText("commands.log",
            $"{DateTime.Now:O} {args.RawOutput}{Environment.NewLine}");
};

Intercept and handle background events

_ = Task.Run(async () =>
{
    while (true)
    {
        await Task.Delay(5000);
        host.AddMessage("[grey][[heartbeat: OK]][/]");
    }
});

Exit with a non-zero code on error

host.AddCommand(new Command("fail", "Exit with error", (_, _) =>
{
    host.AddMessage("[red]Fatal error![/]");
    Environment.ExitCode = 1;
    host.Stop();
    return Task.CompletedTask;
}));

How It Compares

StreamShell Raw Console.ReadLine() Spectre.Console Terminal.Gui
Drop-in prompt loop
Markup/styling ✅ (Spectre)
Command routing
Inline text editing ✅ (basic)
Selection panels
Full TUI (panels, mouse) ⚠️ partial
Bundle size Tiny Zero Medium Large
Learning curve Low None Low High

Samples

Six focused sample projects are included, one per feature:

Sample Feature Command
01-HelloStreamShell Full lifecycle, basic commands dotnet run -p samples/01-HelloStreamShell
02-MarkupRendering Styled messages, background events dotnet run -p samples/02-MarkupRendering
03-CommandAutocomplete Tab completion, arg suggestions dotnet run -p samples/03-CommandAutocomplete
04-TextEditing Cursor, selection, clipboard, paste dotnet run -p samples/04-TextEditing
05-SelectionPanels Single & multi-select panels dotnet run -p samples/05-SelectionPanels
06-SeparatorsPanels Separators, custom bottom panels dotnet run -p samples/06-SeparatorsPanels

The original comprehensive demo is also available:

cd samples/MySpectreApp
dotnet run

Contributing

Running Locally

git clone https://github.com/Venando/StreamShell
cd StreamShell
dotnet build
dotnet test

Running the Sample App

cd samples/MySpectreApp
dotnet run

PRs welcome. Please open an issue first for large changes.


License

MIT — see LICENSE for details.

Product Compatible and additional computed target framework versions.
.NET 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. 
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
2026.5.31 57 5/31/2026
2026.5.30 50 5/30/2026
2026.5.29 57 5/29/2026
2026.5.25 100 5/25/2026
2026.5.24 92 5/24/2026
2026.5.23 95 5/23/2026
2026.5.22 96 5/22/2026
2026.5.21 93 5/21/2026
2026.5.18 91 5/18/2026
2026.5.17 96 5/17/2026
2026.5.16 109 5/16/2026
2026.5.15 97 5/15/2026
2026.5.14 95 5/14/2026
2026.5.13 94 5/13/2026
2026.5.12 98 5/12/2026
2026.5.11 97 5/11/2026
2026.5.10 99 5/10/2026
2026.5.2 104 5/2/2026
2026.5.1 92 5/1/2026
Loading failed