using System.ClientModel; using Microsoft.Extensions.AI; using OpenAI; using AnchorCli; using AnchorCli.Tools; using AnchorCli.Commands; using AnchorCli.OpenRouter; using Spectre.Console; // ── Setup subcommand ───────────────────────────────────────────────────── if (args.Length > 0 && args[0].Equals("setup", StringComparison.OrdinalIgnoreCase)) { SetupTui.Run(); return; } // ── Config ────────────────────────────────────────────────────────────── const string endpoint = "https://openrouter.ai/api/v1"; var cfg = AnchorConfig.Load(); string apiKey = cfg.ApiKey; string model = cfg.Model; if (string.IsNullOrWhiteSpace(apiKey)) { AnsiConsole.MarkupLine("[red]No API key configured. Run [bold]anchor setup[/] first.[/]"); return; } // ── Fetch model pricing from OpenRouter ───────────────────────────────── var pricingProvider = new PricingProvider(); var tokenTracker = new TokenTracker(); ModelInfo? modelInfo = null; await AnsiConsole.Status() .Spinner(Spinner.Known.BouncingBar) .SpinnerStyle(Style.Parse("cornflowerblue")) .StartAsync("Fetching model pricing...", async ctx => { try { modelInfo = await pricingProvider.GetModelInfoAsync(model); if (modelInfo?.Pricing != null) { tokenTracker.InputPrice = PricingProvider.ParsePrice(modelInfo.Pricing.Prompt); tokenTracker.OutputPrice = PricingProvider.ParsePrice(modelInfo.Pricing.Completion); tokenTracker.RequestPrice = PricingProvider.ParsePrice(modelInfo.Pricing.Request); } } catch { /* pricing is best-effort */ } }); // ── Pretty header ─────────────────────────────────────────────────────── AnsiConsole.Write( new FigletText("anchor") .Color(Color.CornflowerBlue)); AnsiConsole.Write( new Rule("[dim]AI-powered coding assistant[/]") .RuleStyle(Style.Parse("cornflowerblue dim")) .LeftJustified()); AnsiConsole.WriteLine(); var infoTable = new Table() .Border(TableBorder.Rounded) .BorderColor(Color.Grey) .AddColumn(new TableColumn("[dim]Setting[/]").NoWrap()) .AddColumn(new TableColumn("[dim]Value[/]")); infoTable.AddRow("[grey]Model[/]", $"[cyan]{Markup.Escape(modelInfo?.Name ?? model)}[/]"); infoTable.AddRow("[grey]Endpoint[/]", $"[blue]OpenRouter[/]"); infoTable.AddRow("[grey]CWD[/]", $"[green]{Markup.Escape(Environment.CurrentDirectory)}[/]"); if (modelInfo?.Pricing != 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[/]"); } if (modelInfo != null) { infoTable.AddRow("[grey]Context[/]", $"[dim]{modelInfo.ContextLength:N0} tokens[/]"); } AnsiConsole.Write(infoTable); AnsiConsole.WriteLine(); // ── Build the chat client with tool-calling support ───────────────────── var httpClient = new HttpClient(); OpenRouterHeaders.ApplyTo(httpClient); var openAiClient = new OpenAIClient(new ApiKeyCredential(apiKey), new OpenAIClientOptions { Endpoint = new Uri(endpoint), Transport = new System.ClientModel.Primitives.HttpClientPipelineTransport(httpClient) }); IChatClient innerClient = openAiClient.GetChatClient(model).AsIChatClient(); // ── Tool call logging via Spectre ─────────────────────────────────────── object consoleLock = new object(); void ToolLog(string message) { lock (consoleLock) { Console.Write("\r" + new string(' ', 40) + "\r"); AnsiConsole.MarkupLine($"[dim grey] ● {Markup.Escape(message)}[/]"); } } CommandTool.Log = DirTools.Log = FileTools.Log = EditTools.Log = ToolLog; // ── Instantiate Core Components ────────────────────────────────────────── var session = new ChatSession(innerClient); if (modelInfo != null) { tokenTracker.ContextLength = modelInfo.ContextLength; } var commandRegistry = new CommandRegistry(); commandRegistry.Register(new ExitCommand()); commandRegistry.Register(new HelpCommand(commandRegistry)); commandRegistry.Register(new ClearCommand()); commandRegistry.Register(new StatusCommand(model, endpoint)); commandRegistry.Register(new CompactCommand(session.Compactor, session.History)); commandRegistry.Register(new SetupCommand()); commandRegistry.Register(new ResetCommand(session, tokenTracker)); var commandDispatcher = new CommandDispatcher(commandRegistry); // ── Run Repl ──────────────────────────────────────────────────────────── var repl = new ReplLoop(session, tokenTracker, commandDispatcher); await repl.RunAsync();