Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .agents/skills/pretty-console-expert/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
name: pretty-console-expert
description: Expert workflow for using PrettyConsole correctly and efficiently in C# console apps. Use when tasks involve console styling, colored output, regular prints, prompts, typed input parsing, confirmation prompts, menu/table rendering, overwrite-based rendering, progress bars, spinners, OutputPipe routing, or migration from Spectre.Console/manual ANSI/older PrettyConsole APIs.
---

# PrettyConsole Expert

## Core Workflow

1. Verify the installed PrettyConsole version before coding.
- Read `Directory.Packages.props`, `*.csproj`, and/or run `dotnet list package`.
- Keep implementation compatible with the installed version; do not "fix" compilation by downgrading unless the user explicitly requests downgrading.

2. Bring extension APIs into scope:

```csharp
using PrettyConsole;
using static System.Console; // optional
```

3. Choose APIs by intent.
- Styled output: `Console.WriteInterpolated`, `Console.WriteLineInterpolated`.
- Inputs/prompts: `Console.TryReadLine`, `Console.ReadLine`, `Console.Confirm`, `Console.RequestAnyInput`.
- Dynamic rendering: `Console.Overwrite`, `Console.ClearNextLines`, `Console.SkipLines`.
- Progress UI: `ProgressBar.Update`, `ProgressBar.Render`, `Spinner.RunAsync`.
- Menus/tables: `Console.Selection`, `Console.MultiSelection`, `Console.TreeMenu`, `Console.Table`.
- Low-level override only: use `Console.Write(...)` / `Console.WriteLine(...)` span+`ISpanFormattable` overloads only when you intentionally bypass the handler for a custom formatting pipeline.

## Handler Special Formats

- Use `:duration` with `TimeSpan` to render compact elapsed time text from the handler:
`Console.WriteInterpolated($"Elapsed {elapsed:duration}")` -> `Elapsed 12h 5m 33s`
- Use `:bytes` with `double` to render human-readable file sizes from the handler:
`Console.WriteInterpolated($"Transferred {bytes:bytes}")` -> `Transferred 12.3 MB`
- Interpolation holes accept `ReadOnlySpan<char>` directly and prefer `ISpanFormattable`, so slices and span-format-capable values stay on the high-performance handler path without dropping to low-level `Write(ReadOnlySpan<char>)` APIs.
- Prefer these formats in status/progress output instead of manual formatting logic.

## Performance Rules

- Prefer interpolated-handler APIs over manually concatenated strings.
- Avoid span/formattable `Write`/`WriteLine` overloads in normal app code; reserve them for rare advanced/manual formatting scenarios.
- Keep ANSI/decorations inside interpolation holes (for example, `$"{Markup.Bold}..."`) instead of literal escape codes inside string literals.
- Route transient UI (spinner/progress/overwrite loops) to `OutputPipe.Error` to keep stdout pipe-friendly.
- For very frequent concurrent status updates, prefer a single-reader bounded `Channel<T>` that owns `Console.Overwrite(...)`; let workers `TryWrite` snapshots into a capacity-1 channel with `FullMode = DropWrite` so producers stay non-blocking and intermediate frames can be skipped.
- After the last overwrite/progress frame, clear the UI region once with `Console.ClearNextLines(totalLines, pipe)` or intentionally keep it with `Console.SkipLines`.

## Practical Patterns

- For wizard-like flows, wrap `Console.Selection(...)` / `Console.MultiSelection(...)` in retrying `Console.Overwrite(...)` loops so each step reuses one screen region instead of scrolling.
- Prefer `Console.Overwrite(state, static ...)` for fixed-height live regions such as `status + progress`; it avoids closure captures and keeps the rendered surface explicit through `lines`.
- For dynamic spinner headers tied to concurrent work, keep the mutable step/progress state outside the spinner and read it with `Volatile.Read` / `Interlocked` inside the handler factory.

## API Guardrails (Current Surface)

- Use `Spinner`, not `IndeterminateProgressBar`.
- Use `Pattern`, not `AnimationSequence`.
- Use `ProgressBar.Render(...)`, not `ProgressBar.WriteProgressBar(...)`.
- Use `ConsoleContext`, not `PrettyConsoleExtensions`.
- Use `ConsoleColor` helpers/tuples (for example `ConsoleColor.Red / ConsoleColor.White`), not removed `ColoredOutput`/`Color` types.
- Use `Confirm(ReadOnlySpan<string> trueValues, ref PrettyConsoleInterpolatedStringHandler handler, bool emptyIsTrue = true)` (boolean parameter is last).
- Use handler factory overloads for dynamic spinner/progress headers:
`(builder, out handler) => handler = builder.Build(OutputPipe.Error, $"...")`.

## Fast Templates

```csharp
// Colored/status output
Console.WriteLineInterpolated($"{ConsoleColor.Green / ConsoleColor.DefaultBackground}OK{ConsoleColor.Default}");

// Typed input
if (!Console.TryReadLine(out int port, $"Port ({ConsoleColor.Cyan}5000{ConsoleColor.Default}): "))
port = 5000;

// Confirm with custom truthy tokens
bool deploy = Console.Confirm(["y", "yes", "deploy"], $"Deploy now? ", emptyIsTrue: false);

// Spinner
var spinner = new Spinner();
await spinner.RunAsync(workTask, (builder, out handler) =>
handler = builder.Build(OutputPipe.Error, $"Syncing..."));

// Progress rendering
var bar = new ProgressBar { ProgressColor = ConsoleColor.Green };
bar.Update(65, "Downloading", sameLine: true);
ProgressBar.Render(OutputPipe.Error, 65, ConsoleColor.Green);
```

## Reference File

Read [references/v5-api-map.md](references/v5-api-map.md) when you need exact usage snippets, migration mapping from old APIs, or a compile-fix checklist.

If public API usage changes in the edited project, ask whether to update `README.md` and changelog/release-notes files.
4 changes: 4 additions & 0 deletions .agents/skills/pretty-console-expert/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "PrettyConsole Expert"
short_description: "Use PrettyConsole APIs correctly for fast console UIs"
default_prompt: "Implement PrettyConsole features with current APIs, migration-safe names, and allocation-conscious patterns."
209 changes: 209 additions & 0 deletions .agents/skills/pretty-console-expert/references/v5-api-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# PrettyConsole v5 API Map

Use this file when implementing or reviewing PrettyConsole usage so code compiles against modern APIs and keeps allocation-conscious patterns.

## 1. Version First

Read installed version before coding:

```bash
dotnet list package
rg -n "PrettyConsole" --glob "*.csproj" .
# optionally also check central package management if present:
# rg -n "PrettyConsole" Directory.Packages.props
```

If version and request conflict, keep the installed version and adapt code accordingly.

## 2. Namespace and Setup

```csharp
using PrettyConsole;
using static System.Console; // optional
```

PrettyConsole methods are extension members on `System.Console`.

## 3. Correct Modern APIs

- Styled writes:
- `Console.WriteInterpolated(...)`
- `Console.WriteLineInterpolated(...)`
- Inputs:
- `Console.TryReadLine(...)`
- `Console.ReadLine(...)`
- `Console.Confirm(...)`
- `Console.RequestAnyInput(...)`
- Rendering:
- `Console.Overwrite(...)`
- `Console.ClearNextLines(...)`
- `Console.SkipLines(...)`
- Progress:
- `ProgressBar.Update(...)`
- `ProgressBar.Render(...)`
- `Spinner.RunAsync(...)`
- Menus/tables:
- `Console.Selection(...)`
- `Console.MultiSelection(...)`
- `Console.TreeMenu(...)`
- `Console.Table(...)`

### Interpolated-handler special formats

- `TimeSpan` with `:duration`:
- `Console.WriteInterpolated($"Elapsed {elapsed:duration}")`
- Example output: `Elapsed 5h 32m 12s`
- `double` with `:bytes`:
- `Console.WriteInterpolated($"Downloaded {size:bytes}")`
- Example output: `Downloaded 12.3 MB`
- `ReadOnlySpan<char>` and `ISpanFormattable` values work directly in interpolation holes:

```csharp
ReadOnlySpan<char> prefix = "artifact:".AsSpan()[..8];
int count = 42;
Console.WriteInterpolated($"{prefix} {count:D4}");
```

Prefer this over dropping to low-level span `Write(...)` APIs when you still want normal interpolated output composition.

### Low-level escape hatch (rare)

Use these only when intentionally bypassing the interpolated handler for a custom formatting pipeline:

- `Console.Write<T>(...) where T : ISpanFormattable`
- `Console.Write(ReadOnlySpan<char> ...)`
- `Console.WriteLine<T>(...)`

## 4. Old -> New Migration Table

- `IndeterminateProgressBar` -> `Spinner`
- `AnimationSequence` -> `Pattern`
- `ProgressBar.WriteProgressBar` -> `ProgressBar.Render`
- `PrettyConsoleExtensions` -> `ConsoleContext`
- Legacy `ColoredOutput`/`Color` types -> `ConsoleColor` helpers and tuples

## 5. Compile-Safe Patterns

### Styled output

```csharp
Console.WriteInterpolated($"[{ConsoleColor.Cyan}info{ConsoleColor.Default}] {message}");
Console.WriteLineInterpolated(OutputPipe.Error, $"{ConsoleColor.Yellow}warn{ConsoleColor.Default}");
```

### Typed input

```csharp
if (!Console.TryReadLine(out int port, $"Port ({ConsoleColor.Green}5000{ConsoleColor.Default}): "))
port = 5000;
```

### Confirmation

```csharp
bool yes = Console.Confirm(["y", "yes"], $"Continue? ", emptyIsTrue: false);
```

### Wizard-style menus

```csharp
static string PromptSelection(string title, string[] options) {
string selection = string.Empty;

while (selection.Length == 0) {
Console.Overwrite(() => {
selection = Console.Selection(options, $"{ConsoleColor.Cyan}{title}{ConsoleColor.DefaultForeground}:");
if (selection.Length == 0)
Console.WriteLineInterpolated(OutputPipe.Error, $"{ConsoleColor.Red}Invalid choice.");
}, lines: options.Length + 3, pipe: OutputPipe.Out);
}

return selection;
}
```

Use this when you want multi-step prompts to behave like page transitions instead of adding scrollback on each retry.

### Spinner with shared progress state

```csharp
string[] steps = ["Restore", "Compile", "Pack"];
var step = 0;

var workTask = Task.Run(async () => {
for (; step < steps.Length; Interlocked.Increment(ref step))
await Task.Delay(500);
});

var spinner = new Spinner();
await spinner.RunAsync(workTask, (builder, out handler) => {
var current = Math.Min(Volatile.Read(ref step), steps.Length - 1);
handler = builder.Build(OutputPipe.Error, $"Current step: {ConsoleColor.Green}{steps[current]}");
});
```

Use this when the spinner header should reflect concurrently changing state without locking around the render path.

### Stateful overwrite rendering

```csharp
Console.Overwrite(percent, static current => {
ProgressBar.Render(OutputPipe.Error, current, ConsoleColor.Cyan, maxLineWidth: 40);
Console.NewLine(OutputPipe.Error);
Console.WriteInterpolated(OutputPipe.Error, $"Downloading assets... {ConsoleColor.Cyan}{current}");
}, lines: 2, pipe: OutputPipe.Error);
```

Prefer this shape for live `status + progress` regions. It keeps the state explicit, avoids closure allocations, and makes the rendered height obvious.

### Overwrite loop cleanup

```csharp
Console.Overwrite(() => {
Console.WriteLineInterpolated(OutputPipe.Error, $"Running...");
ProgressBar.Render(OutputPipe.Error, percent, ConsoleColor.Cyan);
}, lines: 2, pipe: OutputPipe.Error);

Console.ClearNextLines(2, OutputPipe.Error);
```

### High-frequency concurrent status updates

Use one reader task to own all `Console.Overwrite(...)` calls and let concurrent workers publish snapshots through a bounded channel:

```csharp
using System.Threading.Channels;

var channel = Channel.CreateBounded<Stats>(new BoundedChannelOptions(1) {
SingleWriter = false,
SingleReader = true,
FullMode = BoundedChannelFullMode.DropWrite
});

_ = Task.Run(async () => {
await foreach (var stats in channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) {
Console.Overwrite(stats, static current => {
PrintMetrics(current);
}, lines: 2, pipe: OutputPipe.Error);
}

Console.ClearNextLines(2, OutputPipe.Error);
}, cancellationToken);

// Workers stay non-blocking and may skip intermediate frames when the UI is busy.
channel.Writer.TryWrite(latestStats);
```

Why this works:

- only one reader ever renders, so `Overwrite` calls do not race each other
- capacity `1` + `DropWrite` avoids backpressure on workers during high-frequency updates
- this pattern is best when dropped intermediate states are acceptable and only recent snapshots matter

## 6. Performance Checklist

- Prefer interpolated handlers over string concatenation.
- Treat span/formattable `Write`/`WriteLine` overloads as advanced escape hatches, not default app-level APIs.
- Keep ANSI/decorations in interpolation holes, not raw literal spans.
- Use `OutputPipe.Error` for transient rendering.
- Avoid introducing wrapper abstractions when direct PrettyConsole APIs already solve the task.
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Summary
- PrettyConsole.Tests/ — interactive/demo runner (manually selects visual feature demos)
- PrettyConsole.Tests.Unit/ — xUnit v3 unit tests using Microsoft Testing Platform
- Examples/ — standalone `.cs` sample apps plus `assets/` previews; documented in `Examples/README.md` and excluded from automated builds/tests
- v5.3.1 (current) renames `IndeterminateProgressBar` to `Spinner` (and `AnimationSequence` to `Pattern`), triggers the line reset at the start of each spinner frame, gives all `RunAsync` overloads default cancellation tokens, and renames `ProgressBar.WriteProgressBar` to `Render` while adding handler-factory overloads and switching header parameters to `string`. It still passes handlers by `ref`, adds `AppendInline`, and introduces the ctor that takes only `OutputPipe` + optional `IFormatProvider`; `SkipLines` advances the cursor while keeping overwritten UIs; `Confirm(trueValues, ref handler, bool emptyIsTrue = true)` has the boolean last; spinner header factories use `PrettyConsoleInterpolatedStringHandlerFactory` with the singleton builder; `AnsiColors` is public. v5.2.0 rewrote the handler to buffer before writing and added `WhiteSpace`; v5.1.0 renamed `PrettyConsoleExtensions` to `ConsoleContext`, added `Console.WriteWhiteSpaces(length, pipe)`, and made `Out`/`Error`/`In` settable; v5.0.0 removed the legacy `ColoredOutput`/`Color` types in favor of `ConsoleColor` helpers and tuples.
- v5.4.0 (current) renames `IndeterminateProgressBar` to `Spinner` (and `AnimationSequence` to `Pattern`), triggers the line reset at the start of each spinner frame, gives all `RunAsync` overloads default cancellation tokens, and renames `ProgressBar.WriteProgressBar` to `Render` while adding handler-factory overloads and switching header parameters to `string`. It still passes handlers by `ref`, adds `AppendInline`, and introduces the ctor that takes only `OutputPipe` + optional `IFormatProvider`; `SkipLines` advances the cursor while keeping overwritten UIs; `Confirm(trueValues, ref handler, bool emptyIsTrue = true)` has the boolean last; spinner header factories use `PrettyConsoleInterpolatedStringHandlerFactory` with the singleton builder; `AnsiColors` is public. v5.2.0 rewrote the handler to buffer before writing and added `WhiteSpace`; v5.1.0 renamed `PrettyConsoleExtensions` to `ConsoleContext`, added `Console.WriteWhiteSpaces(length, pipe)`, and made `Out`/`Error`/`In` settable; v5.0.0 removed the legacy `ColoredOutput`/`Color` types in favor of `ConsoleColor` helpers and tuples.

Commands you’ll use often

Expand Down Expand Up @@ -56,9 +56,9 @@ High-level architecture and key concepts
- Markup decorations
- The `Markup` static class exposes ANSI sequences for underline, bold, italic, and strikethrough. Fields expand to escape codes only when output/error aren’t redirected; otherwise they collapse to empty strings so callers can safely interpolate them without extra checks.
- Write APIs
- `WriteInterpolated`/`WriteLineInterpolated` host the interpolated-string handler; `Write`/`WriteLine` overloads target `ISpanFormattable` values (including `ref struct`s) and raw `ReadOnlySpan<char>` spans with optional foreground/background overrides. Implementations rent buffers from `ArrayPool<char>.Shared` to avoid allocation spikes and always reset colors.
- `WriteInterpolated`/`WriteLineInterpolated` are the default output APIs and host the interpolated-string handler; this path already covers high-performance formatting and coloring. Keep `Write`/`WriteLine` overloads (`ISpanFormattable`/`ReadOnlySpan<char>`) for rare low-level scenarios where callers intentionally bypass the handler with custom formatting pipelines. Those overloads still rent buffers from `ArrayPool<char>.Shared` and reset colors.
- TextWriter helpers
- `ConsoleContext` surfaces the live `Out`/`Error` writers (now with public setters for test doubles) and keeps helpers like `GetWidthOrDefault`. Use `Console.WriteWhiteSpaces(int length, OutputPipe pipe = OutputPipe.Out)` for direct padding from call sites; `TextWriter.WriteWhiteSpaces(int)` remains available on the writers if you already have them on hand.
- `ConsoleContext` surfaces the live `Out`/`Error` writers (now with public setters for test doubles) and keeps helpers like `GetWidthOrDefault`. Use `Console.WriteWhiteSpaces(int length)` for the default output path and specify `OutputPipe.Error` only when needed; `TextWriter.WriteWhiteSpaces(int)` remains available on the writers if you already have them on hand.
- Inputs
- `ReadLine`/`TryReadLine` support `IParsable<T>` types, optional defaults, enum parsing with `ignoreCase`, and interpolated prompts. `Confirm` exposes `DefaultConfirmValues`, overloads for custom truthy tokens, and interpolated prompts; `RequestAnyInput` blocks on `ReadKey` with colored prompts if desired.
- Rendering controls
Expand All @@ -83,7 +83,7 @@ Testing structure and workflows

Notes and gotchas

- The library aims to minimize allocations; prefer span-based overloads (`ReadOnlySpan<char>`, `ISpanFormattable`) plus the inline `ConsoleColor` tuples instead of recreating strings or structs.
- The library aims to minimize allocations; for normal app-level output prefer interpolated-handler APIs (`WriteInterpolated`/`WriteLineInterpolated`) plus inline `ConsoleColor` tuples. Use span-based `Write`/`WriteLine` overloads only for rare low-level formatting bypass scenarios.
- When authoring new features, pick the appropriate OutputPipe to keep CLI piping behavior intact.
- On macOS terminals, ANSI is supported; Windows legacy terminals are handled via ANSI-compatible rendering in the library.
- `ProgressBar.Update` re-renders on every call (even when the percentage is unchanged) and accepts `sameLine` to place the status above the bar; the static `ProgressBar.Render` renders one-off bars without writing a trailing newline, so rely on `Console.Overwrite`/`lines` to stack multiple bars cleanly.
Expand Down
14 changes: 11 additions & 3 deletions PrettyConsole/PrettyConsole.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<IncludeSymbols>true</IncludeSymbols>
<IncludeSource>true</IncludeSource>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRespositoryUrl>true</PublishRespositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>

<Authors>David Shnayder</Authors>
Expand All @@ -26,15 +26,15 @@
<PackageProjectUrl>https://github.com/dusrdev/PrettyConsole/</PackageProjectUrl>
<RepositoryUrl>https://github.com/dusrdev/PrettyConsole/</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>5.4.0</Version>
<Version>5.4.1</Version>
<Nullable>enable</Nullable>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<Using Include="System.Runtime.CompilerServices"/>
<Using Include="System.Runtime.CompilerServices" />
</ItemGroup>

<ItemGroup>
Expand All @@ -55,6 +55,14 @@

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="Payload" Version="1.0.0" />
</ItemGroup>

<ItemGroup>
<PayloadContent Include="../.agents/skills/pretty-console-expert">
<Tag>PrettyConsoleExpert</Tag>
<TargetPath>.agents/skills/pretty-console-expert</TargetPath>
</PayloadContent>
</ItemGroup>

<PropertyGroup>
Expand Down
Loading