1
0

Compare commits

...

2 Commits

5 changed files with 974 additions and 13 deletions

View File

@@ -19,6 +19,10 @@
<PublishAot>false</PublishAot>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\3d.flf" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI" Version="10.3.0" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.3.0" />

818
Assets/3d.flf Normal file
View File

@@ -0,0 +1,818 @@
flf2a$ 8 8 20 -1 1
3d font created by xero <x@xero.nu>
$$@
$$@
$$@
$$@
$$@
$$@
$$@
$$@@
██@
░██@
░██@
░██@
░██@
░░ @
██@
░░ @@
█ █@
░█ ░█@
░ ░ @
@
@
@
@
@@
@
██ ██ @
████████████@
░░░██░░░░██░ @
░██ ░██ @
████████████@
░░░██░░░░██░ @
░░ ░░ @@
█ @
█████@
░█░█░ @
░█████@
░░░█░█@
█████@
░░░█░ @
░ @@
@
██ ██ @
░░ ██ @
██ @
██ @
██ @
██ ██ @
░░ ░░ @@
██ @
█░ █ @
░ ██ @
█░ █ █@
█ ░ █ @
░█ ░█ @
░ ████ █@
░░░░ ░ @@
██@
░░█@
░ @
@
@
@
@
@@
██@
██ @
██ @
░██ @
░██ @
░░██ @
░░██@
░░ @@
██ @
░░██ @
░░██@
░██@
░██@
██ @
██ @
░░ @@
██ @
██ ░██ ██ @
░░██ ░██ ██ @
██████████████@
░░░██░░██░░██░ @
██ ░██ ░░██ @
░░ ░██ ░░ @
░░ @@
@
█ @
░█ @
█████████@
░░░░░█░░░ @
░█ @
░ @
@@
@
@
@
@
@
██@
░░█@
░ @@
@
@
@
█████@
░░░░░ @
@
@
@@
@
@
@
@
@
██@
░██@
░░ @@
██@
██ @
██ @
██ @
██ @
██ @
██ @
░░ @@
████ @
█░░░██@
░█ █░█@
░█ █ ░█@
░██ ░█@
░█ ░█@
░ ████ @
░░░░ @@
██ @
███ @
░░██ @
░██ @
░██ @
░██ @
████@
░░░░ @@
████ @
█░░░ █@
░ ░█@
███ @
█░░ @
█ @
░██████@
░░░░░░ @@
████ @
█░░░ █@
░ ░█@
███ @
░░░ █@
█ ░█@
░ ████ @
░░░░ @@
██ @
█░█ @
█ ░█ @
██████@
░░░░░█ @
░█ @
░█ @
░ @@
██████@
░█░░░░ @
░█████ @
░░░░░ █@
░█@
█ ░█@
░ ████ @
░░░░ @@
████ @
█░░░ █@
░█ ░ @
░█████ @
░█░░░ █@
░█ ░█@
░ ████ @
░░░░ @@
██████@
░░░░░░█@
░█@
█ @
█ @
█ @
█ @
░ @@
████ @
█░░░ █@
░█ ░█@
░ ████ @
█░░░ █@
░█ ░█@
░ ████ @
░░░░ @@
████ @
█░░░ █@
░█ ░█@
░ ████ @
░░░█ @
█ @
█ @
░ @@
@
@
@
@
██@
░░ @
██@
░░ @@
@
@
@
██@
░░ @
██@
░░█@
░ @@
██@
██░ @
██░ @
██░ @
░░ ██ @
░░ ██ @
░░ ██@
░░ @@
@
@
██████@
░░░░░░ @
██████@
░░░░░░ @
@
@@
██ @
░░ ██ @
░░ ██ @
░░ ██@
██░ @
██░ @
██░ @
░░ @@
████ @
██░░██@
░██ ░██@
░░ ██ @
██ @
░░ @
██ @
░░ @@
████ @
█░░░ █@
░█ ██░█@
░█░█ ░█@
░█░ ██ @
░█ ░░ @
░ █████@
░░░░░ @@
██ @
████ @
██░░██ @
██ ░░██ @
██████████@
░██░░░░░░██@
░██ ░██@
░░ ░░ @@
██████ @
░█░░░░██ @
░█ ░██ @
░██████ @
░█░░░░ ██@
░█ ░██@
░███████ @
░░░░░░░ @@
██████ @
██░░░░██@
██ ░░ @
░██ @
░██ @
░░██ ██@
░░██████ @
░░░░░░ @@
███████ @
░██░░░░██ @
░██ ░██@
░██ ░██@
░██ ░██@
░██ ██ @
░███████ @
░░░░░░░ @@
████████@
░██░░░░░ @
░██ @
░███████ @
░██░░░░ @
░██ @
░████████@
░░░░░░░░ @@
████████@
░██░░░░░ @
░██ @
░███████ @
░██░░░░ @
░██ @
░██ @
░░ @@
████████ @
██░░░░░░██@
██ ░░ @
░██ @
░██ █████@
░░██ ░░░░██@
░░████████ @
░░░░░░░░ @@
██ ██@
░██ ░██@
░██ ░██@
░██████████@
░██░░░░░░██@
░██ ░██@
░██ ░██@
░░ ░░ @@
██@
░██@
░██@
░██@
░██@
░██@
░██@
░░ @@
██@
░██@
░██@
░██@
░██@
██ ░██@
░░█████ @
░░░░░ @@
██ ██@
░██ ██ @
░██ ██ @
░████ @
░██░██ @
░██░░██ @
░██ ░░██@
░░ ░░ @@
██ @
░██ @
░██ @
░██ @
░██ @
░██ @
░████████@
░░░░░░░░ @@
████ ████@
░██░██ ██░██@
░██░░██ ██ ░██@
░██ ░░███ ░██@
░██ ░░█ ░██@
░██ ░ ░██@
░██ ░██@
░░ ░░ @@
████ ██@
░██░██ ░██@
░██░░██ ░██@
░██ ░░██ ░██@
░██ ░░██░██@
░██ ░░████@
░██ ░░███@
░░ ░░░ @@
███████ @
██░░░░░██ @
██ ░░██@
░██ ░██@
░██ ░██@
░░██ ██ @
░░███████ @
░░░░░░░ @@
███████ @
░██░░░░██@
░██ ░██@
░███████ @
░██░░░░ @
░██ @
░██ @
░░ @@
███████ @
██░░░░░██ @
██ ░░██ @
░██ ░██ @
░██ ██░██ @
░░██ ░░ ██ @
░░███████ ██@
░░░░░░░ ░░ @@
███████ @
░██░░░░██ @
░██ ░██ @
░███████ @
░██░░░██ @
░██ ░░██ @
░██ ░░██@
░░ ░░ @@
████████@
██░░░░░░ @
░██ @
░█████████@
░░░░░░░░██@
░██@
████████ @
░░░░░░░░ @@
██████████@
░░░░░██░░░ @
░██ @
░██ @
░██ @
░██ @
░██ @
░░ @@
██ ██@
░██ ░██@
░██ ░██@
░██ ░██@
░██ ░██@
░██ ░██@
░░███████ @
░░░░░░░ @@
██ ██@
░██ ░██@
░██ ░██@
░░██ ██ @
░░██ ██ @
░░████ @
░░██ @
░░ @@
██ ██@
░██ ░██@
░██ █ ░██@
░██ ███ ░██@
░██ ██░██░██@
░████ ░░████@
░██░ ░░░██@
░░ ░░ @@
██ ██@
░░██ ██ @
░░██ ██ @
░░███ @
██░██ @
██ ░░██ @
██ ░░██@
░░ ░░ @@
██ ██@
░░██ ██ @
░░████ @
░░██ @
░██ @
░██ @
░██ @
░░ @@
████████@
░░░░░░██ @
██ @
██ @
██ @
██ @
████████@
░░░░░░░░ @@
█████@
░██░░ @
░██ @
░██ @
░██ @
░██ @
░█████@
░░░░░ @@
██ @
░░██ @
░░██ @
░░██ @
░░██ @
░░██ @
░░██@
░░ @@
█████@
░░░░██@
░██@
░██@
░██@
░██@
█████@
░░░░░ @@
██ @
██░ ██ @
██ ░░ ██@
░░ ░░ @
@
@
@
@@
@
@
@
@
@
@
█████@
░░░░░ @@
██@
░█ @
░ @
@
@
@
@
@@
@
@
██████ @
░░░░░░██ @
███████ @
██░░░░██ @
░░████████@
░░░░░░░░ @@
██ @
░██ @
░██ @
░██████ @
░██░░░██@
░██ ░██@
░██████ @
░░░░░ @@
@
@
█████ @
██░░░██@
░██ ░░ @
░██ ██@
░░█████ @
░░░░░ @@
██@
░██@
░██@
██████@
██░░░██@
░██ ░██@
░░██████@
░░░░░░ @@
@
@
█████ @
██░░░██@
░███████@
░██░░░░ @
░░██████@
░░░░░░ @@
████@
░██░ @
██████@
░░░██░ @
░██ @
░██ @
░██ @
░░ @@
@
█████ @
██░░░██@
░██ ░██@
░░██████@
░░░░░██@
█████ @
░░░░░ @@
██ @
░██ @
░██ @
░██████ @
░██░░░██@
░██ ░██@
░██ ░██@
░░ ░░ @@
██@
░░ @
██@
░██@
░██@
░██@
░██@
░░ @@
██@
░░ @
██@
░██@
░██@
██░██@
░░███ @
░░░ @@
██ @
░██ @
░██ ██@
░██ ██ @
░████ @
░██░██ @
░██░░██@
░░ ░░ @@
██@
░██@
░██@
░██@
░██@
░██@
███@
░░░ @@
@
@
██████████ @
░░██░░██░░██@
░██ ░██ ░██@
░██ ░██ ░██@
███ ░██ ░██@
░░░ ░░ ░░ @@
@
@
███████ @
░░██░░░██@
░██ ░██@
░██ ░██@
███ ░██@
░░░ ░░ @@
@
@
██████ @
██░░░░██@
░██ ░██@
░██ ░██@
░░██████ @
░░░░░░ @@
@
██████ @
░██░░░██@
░██ ░██@
░██████ @
░██░░░ @
░██ @
░░ @@
@
████ @
██░░██ @
░██ ░██ @
░░█████ @
░░░░██ @
░███@
░░░ @@
@
@
██████@
░░██░░█@
░██ ░ @
░██ @
░███ @
░░░ @@
@
@
██████@
██░░░░ @
░░█████ @
░░░░░██@
██████ @
░░░░░░ @@
██ @
░██ @
██████@
░░░██░ @
░██ @
░██ @
░░██ @
░░ @@
@
@
██ ██@
░██ ░██@
░██ ░██@
░██ ░██@
░░██████@
░░░░░░ @@
@
@
██ ██@
░██ ░██@
░░██ ░██ @
░░████ @
░░██ @
░░ @@
@
@
███ ██@
░░██ █ ░██@
░██ ███░██@
░████░████@
███░ ░░░██@
░░░ ░░░ @@
@
@
██ ██@
░░██ ██ @
░░███ @
██░██ @
██ ░░██@
░░ ░░ @@
@
██ ██@
░░██ ██ @
░░███ @
░██ @
██ @
██ @
░░ @@
@
@
██████@
░░░░██ @
██ @
██ @
██████@
░░░░░░ @@
███@
██░ @
░██ @
███ @
░░░██ @
░██ @
░░███@
░░░ @@
█@
░█@
░█@
░ @
█@
░█@
░█@
░ @@
███ @
░░░██ @
░██ @
░░███@
██░ @
░██ @
███ @
░░░ @@
██ ███ @
░░███░░██@
░░░ ░░ @
@
@
@
@
@@
@
@
@
@
@
@
@
@@
@
@
@
@
@
@
@
@@
@
@
@
@
@
@
@
@@
@
@
@
@
@
@
@
@@
@
@
@
@
@
@
@
@@
@
@
@
@
@
@
@
@@
@
@
@
@
@
@
@
@@

125
InputProcessor.cs Normal file
View File

@@ -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);
}
}
}
}

View File

@@ -9,6 +9,9 @@ using AnchorCli.Commands;
using AnchorCli.OpenRouter;
using Spectre.Console;
Console.InputEncoding = System.Text.Encoding.UTF8;
Console.OutputEncoding = System.Text.Encoding.UTF8;
// ── Setup subcommand ─────────────────────────────────────────────────────
if (args.Length > 0 && args[0].Equals("setup", StringComparison.OrdinalIgnoreCase))
{
@@ -53,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";
@@ -81,12 +96,12 @@ infoTable.AddRow("[grey]Version[/]", $"[magenta]{version}[/]");
if (modelInfo?.Pricing != null && tokenTracker != null)
{
var inM = tokenTracker.InputPrice * 1_000_000m;
var outM = tokenTracker.OutputPrice * 1_000_000m;
infoTable.AddRow("[grey]Pricing[/]",
$"[yellow]${inM:F2}[/][dim]/M in[/] [yellow]${outM:F2}[/][dim]/M out[/]");
}
{
var inM = tokenTracker.InputPrice * 1_000_000m;
var outM = tokenTracker.OutputPrice * 1_000_000m;
infoTable.AddRow("[grey]Pricing[/]",
$"[yellow]${inM:F2}[/][dim]/M in[/] [yellow]${outM:F2}[/][dim]/M out[/]");
}
if (modelInfo != null)
{
infoTable.AddRow("[grey]Context[/]",
@@ -116,7 +131,7 @@ void ToolLog(string message)
lock (consoleLock)
{
Console.Write("\r" + new string(' ', 40) + "\r");
AnsiConsole.MarkupLine($"[dim grey]{Markup.Escape(message)}[/]");
AnsiConsole.MarkupLine($"[dim grey]{Markup.Escape(message)}[/]");
}
}

View File

@@ -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));