diff --git a/AnchorCli.csproj b/AnchorCli.csproj
index e3555cb..84b01f9 100644
--- a/AnchorCli.csproj
+++ b/AnchorCli.csproj
@@ -19,6 +19,10 @@
false
+
+
+
+
diff --git a/Assets/3d.flf b/Assets/3d.flf
new file mode 100644
index 0000000..8414f16
--- /dev/null
+++ b/Assets/3d.flf
@@ -0,0 +1,818 @@
+flf2a$ 8 8 20 -1 1
+3d font created by xero
+$$@
+$$@
+$$@
+$$@
+$$@
+$$@
+$$@
+$$@@
+ ██@
+░██@
+░██@
+░██@
+░██@
+░░ @
+ ██@
+░░ @@
+ █ █@
+░█ ░█@
+░ ░ @
+ @
+ @
+ @
+ @
+ @@
+ @
+ ██ ██ @
+ ████████████@
+░░░██░░░░██░ @
+ ░██ ░██ @
+ ████████████@
+░░░██░░░░██░ @
+ ░░ ░░ @@
+ █ @
+ █████@
+░█░█░ @
+░█████@
+░░░█░█@
+ █████@
+░░░█░ @
+ ░ @@
+ @
+ ██ ██ @
+░░ ██ @
+ ██ @
+ ██ @
+ ██ @
+ ██ ██ @
+░░ ░░ @@
+ ██ @
+ █░ █ @
+ ░ ██ @
+ █░ █ █@
+ █ ░ █ @
+░█ ░█ @
+░ ████ █@
+ ░░░░ ░ @@
+ ██@
+░░█@
+ ░ @
+ @
+ @
+ @
+ @
+ @@
+ ██@
+ ██ @
+ ██ @
+░██ @
+░██ @
+░░██ @
+ ░░██@
+ ░░ @@
+ ██ @
+░░██ @
+ ░░██@
+ ░██@
+ ░██@
+ ██ @
+ ██ @
+░░ @@
+ ██ @
+ ██ ░██ ██ @
+ ░░██ ░██ ██ @
+ ██████████████@
+░░░██░░██░░██░ @
+ ██ ░██ ░░██ @
+ ░░ ░██ ░░ @
+ ░░ @@
+ @
+ █ @
+ ░█ @
+ █████████@
+ ░░░░░█░░░ @
+ ░█ @
+ ░ @
+ @@
+ @
+ @
+ @
+ @
+ @
+ ██@
+░░█@
+ ░ @@
+ @
+ @
+ @
+ █████@
+░░░░░ @
+ @
+ @
+ @@
+ @
+ @
+ @
+ @
+ @
+ ██@
+░██@
+░░ @@
+ ██@
+ ██ @
+ ██ @
+ ██ @
+ ██ @
+ ██ @
+ ██ @
+░░ @@
+ ████ @
+ █░░░██@
+░█ █░█@
+░█ █ ░█@
+░██ ░█@
+░█ ░█@
+░ ████ @
+ ░░░░ @@
+ ██ @
+ ███ @
+░░██ @
+ ░██ @
+ ░██ @
+ ░██ @
+ ████@
+░░░░ @@
+ ████ @
+ █░░░ █@
+░ ░█@
+ ███ @
+ █░░ @
+ █ @
+░██████@
+░░░░░░ @@
+ ████ @
+ █░░░ █@
+░ ░█@
+ ███ @
+ ░░░ █@
+ █ ░█@
+░ ████ @
+ ░░░░ @@
+ ██ @
+ █░█ @
+ █ ░█ @
+ ██████@
+░░░░░█ @
+ ░█ @
+ ░█ @
+ ░ @@
+ ██████@
+░█░░░░ @
+░█████ @
+░░░░░ █@
+ ░█@
+ █ ░█@
+░ ████ @
+ ░░░░ @@
+ ████ @
+ █░░░ █@
+░█ ░ @
+░█████ @
+░█░░░ █@
+░█ ░█@
+░ ████ @
+ ░░░░ @@
+ ██████@
+░░░░░░█@
+ ░█@
+ █ @
+ █ @
+ █ @
+ █ @
+ ░ @@
+ ████ @
+ █░░░ █@
+░█ ░█@
+░ ████ @
+ █░░░ █@
+░█ ░█@
+░ ████ @
+ ░░░░ @@
+ ████ @
+ █░░░ █@
+░█ ░█@
+░ ████ @
+ ░░░█ @
+ █ @
+ █ @
+ ░ @@
+ @
+ @
+ @
+ @
+ ██@
+░░ @
+ ██@
+░░ @@
+ @
+ @
+ @
+ ██@
+░░ @
+ ██@
+░░█@
+ ░ @@
+ ██@
+ ██░ @
+ ██░ @
+ ██░ @
+░░ ██ @
+ ░░ ██ @
+ ░░ ██@
+ ░░ @@
+ @
+ @
+ ██████@
+░░░░░░ @
+ ██████@
+░░░░░░ @
+ @
+ @@
+ ██ @
+░░ ██ @
+ ░░ ██ @
+ ░░ ██@
+ ██░ @
+ ██░ @
+ ██░ @
+░░ @@
+ ████ @
+ ██░░██@
+░██ ░██@
+░░ ██ @
+ ██ @
+ ░░ @
+ ██ @
+ ░░ @@
+ ████ @
+ █░░░ █@
+░█ ██░█@
+░█░█ ░█@
+░█░ ██ @
+░█ ░░ @
+░ █████@
+ ░░░░░ @@
+ ██ @
+ ████ @
+ ██░░██ @
+ ██ ░░██ @
+ ██████████@
+░██░░░░░░██@
+░██ ░██@
+░░ ░░ @@
+ ██████ @
+░█░░░░██ @
+░█ ░██ @
+░██████ @
+░█░░░░ ██@
+░█ ░██@
+░███████ @
+░░░░░░░ @@
+ ██████ @
+ ██░░░░██@
+ ██ ░░ @
+░██ @
+░██ @
+░░██ ██@
+ ░░██████ @
+ ░░░░░░ @@
+ ███████ @
+░██░░░░██ @
+░██ ░██@
+░██ ░██@
+░██ ░██@
+░██ ██ @
+░███████ @
+░░░░░░░ @@
+ ████████@
+░██░░░░░ @
+░██ @
+░███████ @
+░██░░░░ @
+░██ @
+░████████@
+░░░░░░░░ @@
+ ████████@
+░██░░░░░ @
+░██ @
+░███████ @
+░██░░░░ @
+░██ @
+░██ @
+░░ @@
+ ████████ @
+ ██░░░░░░██@
+ ██ ░░ @
+░██ @
+░██ █████@
+░░██ ░░░░██@
+ ░░████████ @
+ ░░░░░░░░ @@
+ ██ ██@
+░██ ░██@
+░██ ░██@
+░██████████@
+░██░░░░░░██@
+░██ ░██@
+░██ ░██@
+░░ ░░ @@
+ ██@
+░██@
+░██@
+░██@
+░██@
+░██@
+░██@
+░░ @@
+ ██@
+ ░██@
+ ░██@
+ ░██@
+ ░██@
+ ██ ░██@
+░░█████ @
+ ░░░░░ @@
+ ██ ██@
+░██ ██ @
+░██ ██ @
+░████ @
+░██░██ @
+░██░░██ @
+░██ ░░██@
+░░ ░░ @@
+ ██ @
+░██ @
+░██ @
+░██ @
+░██ @
+░██ @
+░████████@
+░░░░░░░░ @@
+ ████ ████@
+░██░██ ██░██@
+░██░░██ ██ ░██@
+░██ ░░███ ░██@
+░██ ░░█ ░██@
+░██ ░ ░██@
+░██ ░██@
+░░ ░░ @@
+ ████ ██@
+░██░██ ░██@
+░██░░██ ░██@
+░██ ░░██ ░██@
+░██ ░░██░██@
+░██ ░░████@
+░██ ░░███@
+░░ ░░░ @@
+ ███████ @
+ ██░░░░░██ @
+ ██ ░░██@
+░██ ░██@
+░██ ░██@
+░░██ ██ @
+ ░░███████ @
+ ░░░░░░░ @@
+ ███████ @
+░██░░░░██@
+░██ ░██@
+░███████ @
+░██░░░░ @
+░██ @
+░██ @
+░░ @@
+ ███████ @
+ ██░░░░░██ @
+ ██ ░░██ @
+░██ ░██ @
+░██ ██░██ @
+░░██ ░░ ██ @
+ ░░███████ ██@
+ ░░░░░░░ ░░ @@
+ ███████ @
+░██░░░░██ @
+░██ ░██ @
+░███████ @
+░██░░░██ @
+░██ ░░██ @
+░██ ░░██@
+░░ ░░ @@
+ ████████@
+ ██░░░░░░ @
+░██ @
+░█████████@
+░░░░░░░░██@
+ ░██@
+ ████████ @
+░░░░░░░░ @@
+ ██████████@
+░░░░░██░░░ @
+ ░██ @
+ ░██ @
+ ░██ @
+ ░██ @
+ ░██ @
+ ░░ @@
+ ██ ██@
+░██ ░██@
+░██ ░██@
+░██ ░██@
+░██ ░██@
+░██ ░██@
+░░███████ @
+ ░░░░░░░ @@
+ ██ ██@
+░██ ░██@
+░██ ░██@
+░░██ ██ @
+ ░░██ ██ @
+ ░░████ @
+ ░░██ @
+ ░░ @@
+ ██ ██@
+░██ ░██@
+░██ █ ░██@
+░██ ███ ░██@
+░██ ██░██░██@
+░████ ░░████@
+░██░ ░░░██@
+░░ ░░ @@
+ ██ ██@
+░░██ ██ @
+ ░░██ ██ @
+ ░░███ @
+ ██░██ @
+ ██ ░░██ @
+ ██ ░░██@
+░░ ░░ @@
+ ██ ██@
+░░██ ██ @
+ ░░████ @
+ ░░██ @
+ ░██ @
+ ░██ @
+ ░██ @
+ ░░ @@
+ ████████@
+░░░░░░██ @
+ ██ @
+ ██ @
+ ██ @
+ ██ @
+ ████████@
+░░░░░░░░ @@
+ █████@
+░██░░ @
+░██ @
+░██ @
+░██ @
+░██ @
+░█████@
+░░░░░ @@
+ ██ @
+░░██ @
+ ░░██ @
+ ░░██ @
+ ░░██ @
+ ░░██ @
+ ░░██@
+ ░░ @@
+ █████@
+░░░░██@
+ ░██@
+ ░██@
+ ░██@
+ ░██@
+ █████@
+░░░░░ @@
+ ██ @
+ ██░ ██ @
+ ██ ░░ ██@
+░░ ░░ @
+ @
+ @
+ @
+ @@
+ @
+ @
+ @
+ @
+ @
+ @
+ █████@
+░░░░░ @@
+ ██@
+░█ @
+░ @
+ @
+ @
+ @
+ @
+ @@
+ @
+ @
+ ██████ @
+ ░░░░░░██ @
+ ███████ @
+ ██░░░░██ @
+░░████████@
+ ░░░░░░░░ @@
+ ██ @
+░██ @
+░██ @
+░██████ @
+░██░░░██@
+░██ ░██@
+░██████ @
+░░░░░ @@
+ @
+ @
+ █████ @
+ ██░░░██@
+░██ ░░ @
+░██ ██@
+░░█████ @
+ ░░░░░ @@
+ ██@
+ ░██@
+ ░██@
+ ██████@
+ ██░░░██@
+░██ ░██@
+░░██████@
+ ░░░░░░ @@
+ @
+ @
+ █████ @
+ ██░░░██@
+░███████@
+░██░░░░ @
+░░██████@
+ ░░░░░░ @@
+ ████@
+ ░██░ @
+ ██████@
+░░░██░ @
+ ░██ @
+ ░██ @
+ ░██ @
+ ░░ @@
+ @
+ █████ @
+ ██░░░██@
+░██ ░██@
+░░██████@
+ ░░░░░██@
+ █████ @
+ ░░░░░ @@
+ ██ @
+░██ @
+░██ @
+░██████ @
+░██░░░██@
+░██ ░██@
+░██ ░██@
+░░ ░░ @@
+ ██@
+░░ @
+ ██@
+░██@
+░██@
+░██@
+░██@
+░░ @@
+ ██@
+ ░░ @
+ ██@
+ ░██@
+ ░██@
+ ██░██@
+░░███ @
+ ░░░ @@
+ ██ @
+░██ @
+░██ ██@
+░██ ██ @
+░████ @
+░██░██ @
+░██░░██@
+░░ ░░ @@
+ ██@
+ ░██@
+ ░██@
+ ░██@
+ ░██@
+ ░██@
+ ███@
+░░░ @@
+ @
+ @
+ ██████████ @
+░░██░░██░░██@
+ ░██ ░██ ░██@
+ ░██ ░██ ░██@
+ ███ ░██ ░██@
+░░░ ░░ ░░ @@
+ @
+ @
+ ███████ @
+░░██░░░██@
+ ░██ ░██@
+ ░██ ░██@
+ ███ ░██@
+░░░ ░░ @@
+ @
+ @
+ ██████ @
+ ██░░░░██@
+░██ ░██@
+░██ ░██@
+░░██████ @
+ ░░░░░░ @@
+ @
+ ██████ @
+░██░░░██@
+░██ ░██@
+░██████ @
+░██░░░ @
+░██ @
+░░ @@
+ @
+ ████ @
+ ██░░██ @
+░██ ░██ @
+░░█████ @
+ ░░░░██ @
+ ░███@
+ ░░░ @@
+ @
+ @
+ ██████@
+░░██░░█@
+ ░██ ░ @
+ ░██ @
+░███ @
+░░░ @@
+ @
+ @
+ ██████@
+ ██░░░░ @
+░░█████ @
+ ░░░░░██@
+ ██████ @
+░░░░░░ @@
+ ██ @
+ ░██ @
+ ██████@
+░░░██░ @
+ ░██ @
+ ░██ @
+ ░░██ @
+ ░░ @@
+ @
+ @
+ ██ ██@
+░██ ░██@
+░██ ░██@
+░██ ░██@
+░░██████@
+ ░░░░░░ @@
+ @
+ @
+ ██ ██@
+░██ ░██@
+░░██ ░██ @
+ ░░████ @
+ ░░██ @
+ ░░ @@
+ @
+ @
+ ███ ██@
+░░██ █ ░██@
+ ░██ ███░██@
+ ░████░████@
+ ███░ ░░░██@
+░░░ ░░░ @@
+ @
+ @
+ ██ ██@
+░░██ ██ @
+ ░░███ @
+ ██░██ @
+ ██ ░░██@
+░░ ░░ @@
+ @
+ ██ ██@
+ ░░██ ██ @
+ ░░███ @
+ ░██ @
+ ██ @
+ ██ @
+ ░░ @@
+ @
+ @
+ ██████@
+░░░░██ @
+ ██ @
+ ██ @
+ ██████@
+░░░░░░ @@
+ ███@
+ ██░ @
+ ░██ @
+ ███ @
+░░░██ @
+ ░██ @
+ ░░███@
+ ░░░ @@
+ █@
+░█@
+░█@
+░ @
+ █@
+░█@
+░█@
+░ @@
+ ███ @
+░░░██ @
+ ░██ @
+ ░░███@
+ ██░ @
+ ░██ @
+ ███ @
+░░░ @@
+ ██ ███ @
+░░███░░██@
+ ░░░ ░░ @
+ @
+ @
+ @
+ @
+ @@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@@
diff --git a/InputProcessor.cs b/InputProcessor.cs
new file mode 100644
index 0000000..be3898c
--- /dev/null
+++ b/InputProcessor.cs
@@ -0,0 +1,125 @@
+using Spectre.Console;
+
+namespace AnchorCli
+{
+ internal class InputProcessor
+ {
+ private static void DisplayText(int left, string buffer, int index = -1, string placeholder = "", int viewportOffset = 0)
+ {
+ Console.CursorLeft = left;
+
+ if (buffer.Length == 0 && index == 0)
+ {
+ AnsiConsole.Markup($"[grey dim]{placeholder}{new string(' ', Console.WindowWidth - 1 - left - placeholder.Length)}[/]");
+ return;
+ }
+
+ var visibleWidth = Console.WindowWidth - left - 1;
+ var displayStart = Math.Min(viewportOffset, Math.Max(0, buffer.Length - 1));
+ var displayEnd = Math.Min(displayStart + visibleWidth, buffer.Length);
+ var displayBuffer = string.Concat(buffer.AsSpan(displayStart, displayEnd - displayStart), " ");
+
+ for (var i = 0; i < displayBuffer.Length; i++)
+ {
+ var actualIndex = displayStart + i;
+ if (index != -1 && actualIndex == index)
+ {
+ Console.ForegroundColor = ConsoleColor.Black;
+ Console.BackgroundColor = ConsoleColor.White;
+ }
+
+ Console.Write(displayBuffer[i]);
+
+ if (index != -1 && actualIndex == index)
+ Console.ResetColor();
+ }
+
+ // Fill remaining space with spaces
+ var remainingSpaces = visibleWidth - displayBuffer.Length;
+ if (remainingSpaces > 0)
+ Console.Write(new string(' ', remainingSpaces));
+ }
+
+ public static string ReadLine(string placeholder = "")
+ {
+ Console.CursorVisible = false;
+ var buffer = string.Empty;
+ var index = 0;
+ var viewportOffset = 0;
+ var left = Console.CursorLeft;
+
+ DisplayText(left, buffer, index, placeholder, viewportOffset);
+
+ while (true)
+ {
+ var inputKey = Console.ReadKey(intercept: true);
+ switch (inputKey)
+ {
+ case { Key: ConsoleKey.Enter } when buffer.Length > 0:
+ DisplayText(left, buffer);
+ Console.WriteLine();
+ return buffer;
+
+ case { Key: ConsoleKey.Backspace } when index > 0:
+ index--;
+ buffer = buffer.Remove(index, 1);
+ break;
+
+ case { Key: ConsoleKey.Delete } when index < buffer.Length:
+ buffer = buffer.Remove(index, 1);
+ break;
+
+ case { Key: ConsoleKey.LeftArrow, Modifiers: ConsoleModifiers.Control }:
+ while (index > 0 && buffer[index - 1] == ' ')
+ index--;
+ while (index > 0 && buffer[index - 1] != ' ')
+ index--;
+ break;
+
+ case { Key: ConsoleKey.RightArrow, Modifiers: ConsoleModifiers.Control }:
+ while (index < buffer.Length && buffer[index] == ' ')
+ index++;
+ while (index < buffer.Length && buffer[index] != ' ')
+ index++;
+ break;
+
+ case { Key: ConsoleKey.LeftArrow } when index > 0:
+ index--;
+ break;
+
+ case { Key: ConsoleKey.RightArrow } when index < buffer.Length:
+ index++;
+ break;
+
+ case { Key: ConsoleKey.W, Modifiers: ConsoleModifiers.Control } when index > 0:
+ var deleteStart = index;
+ while (deleteStart > 0 && buffer[deleteStart - 1] == ' ')
+ deleteStart--;
+ while (deleteStart > 0 && buffer[deleteStart - 1] != ' ')
+ deleteStart--;
+ var charsToDelete = index - deleteStart;
+ buffer = buffer.Remove(deleteStart, charsToDelete);
+ index = deleteStart;
+ break;
+
+ default:
+ var keyChar = inputKey.KeyChar;
+ if (!(char.IsLetterOrDigit(keyChar) || char.IsWhiteSpace(keyChar) || char.IsPunctuation(keyChar) || char.IsSymbol(keyChar)))
+ break;
+
+ buffer = buffer.Insert(index, inputKey.KeyChar.ToString());
+ index++;
+ break;
+ }
+ // Adjust viewport for scrolling
+ var visibleWidth = Console.WindowWidth - left - 1;
+ if (index < viewportOffset)
+ viewportOffset = index;
+ else if (index >= viewportOffset + visibleWidth)
+ viewportOffset = index - visibleWidth + 1;
+
+ DisplayText(left, buffer, index, placeholder, viewportOffset);
+ }
+ }
+ }
+}
diff --git a/Program.cs b/Program.cs
index e8fda46..32ab85b 100644
--- a/Program.cs
+++ b/Program.cs
@@ -56,9 +56,21 @@ if (ProviderFactory.IsOpenRouter(endpoint))
// ── Pretty header ───────────────────────────────────────────────────────
-AnsiConsole.Write(
- new FigletText("anchor")
- .Color(Color.CornflowerBlue));
+var fontStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("AnchorCli.Assets.3d.flf");
+if (fontStream != null)
+{
+ var font = FigletFont.Load(fontStream);
+ AnsiConsole.Write(
+ new FigletText(font, "anchor")
+ .Color(Color.CornflowerBlue));
+}
+else
+{
+ AnsiConsole.Write(
+ new FigletText("anchor")
+ .Color(Color.CornflowerBlue));
+}
+
var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown";
diff --git a/ReplLoop.cs b/ReplLoop.cs
index c93f1e4..f749ff7 100644
--- a/ReplLoop.cs
+++ b/ReplLoop.cs
@@ -22,7 +22,6 @@ internal sealed class ReplLoop
public async Task RunAsync()
{
- AnsiConsole.MarkupLine("[dim]Type your message, or use [bold]/help[/] to see commands.[/]");
AnsiConsole.MarkupLine("[dim]Press [bold]Ctrl+C[/] to cancel the current response.[/]");
AnsiConsole.WriteLine();
@@ -36,7 +35,8 @@ internal sealed class ReplLoop
while (true)
{
- string input = ReadLine.Read("❯ ");
+ AnsiConsole.Markup("[grey]❯ [/]");
+ string input = InputProcessor.ReadLine("Type your message, or use [bold]/help[/] to see commands.");
if (string.IsNullOrWhiteSpace(input)) continue;
@@ -188,7 +188,6 @@ internal sealed class ReplLoop
}
AnsiConsole.Write(new Rule().RuleStyle(Style.Parse("grey dim")));
- AnsiConsole.WriteLine();
_session.History.Add(new ChatMessage(ChatRole.Assistant, fullResponse));