using BlueLaminate.Cli.Commands; using BlueLaminate.Cli.Logging; using BlueLaminate.Core.DependencyInjection; using BlueLaminate.EFCore.Data; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OpenTelemetry; using OpenTelemetry.Resources; using System.CommandLine; // Generic Host = composition root. The exact same wiring a web frontend would use: // configuration → AddBlueLaminateCore → resolve services per command scope. Args are // deliberately NOT handed to the host (System.CommandLine owns parsing; the host's // command-line config provider would reject bare verbs like "sync-skins"). The // content root is the binary directory so appsettings.json is found regardless of CWD. var builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings { ContentRootPath = AppContext.BaseDirectory, }); // Reuse the connection string stored in the EFCore project's user secrets (dev). builder.Configuration.AddUserSecrets(optional: true); // OpenTelemetry logging through a compact console sink that prints one // "{utc timestamp} {message}" line per record. Swapping in an OTLP exporter later // is a change here. ClearProviders drops the default console logger so we don't // double-print. builder.Logging.ClearProviders(); // IHttpClientFactory logs each request at Information under these categories; mute // to Warning so the compact console stays one line per app message. builder.Logging.AddFilter("System.Net.Http.HttpClient", LogLevel.Warning); // EF Core logs every SQL command at Information; we only care about failures, so // raise its floor to Warning (failed commands still log at Error). builder.Logging.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Warning); builder.Logging.AddOpenTelemetry(otel => { otel.SetResourceBuilder( ResourceBuilder.CreateDefault().AddService("BlueLaminate.Cli")); otel.IncludeFormattedMessage = true; otel.AddProcessor(new SimpleLogRecordExportProcessor(new CompactConsoleLogExporter())); }); builder.Services.AddBlueLaminateCore(builder.Configuration); using var host = builder.Build(); // This CLI builds the host but doesn't run it, so ValidateOnStart won't fire on its // own — trigger it explicitly. Invalid configuration (e.g. CsFloat:MaxLimit out of // range) fails fast here with a clear message instead of being silently clamped. try { host.Services.GetRequiredService().Validate(); } catch (OptionsValidationException ex) { Console.Error.WriteLine("Invalid configuration:"); foreach (var failure in ex.Failures) { Console.Error.WriteLine($" - {failure}"); } return 1; } // System.CommandLine builds the command tree, parsing, and help. Each command lives // in its own file under Commands/ and resolves its service from a DI scope. var root = new RootCommand("BlueLaminate CLI — Counter-Strike skin tracker tools.") { SyncSkinsCommand.Build(host), FetchListingsCommand.Build(host), SweepListingsCommand.Build(host), SweepCatalogCommand.Build(host), ProbeProxyCommand.Build(host), CaptureCsMoneyCommand.Build(host), }; // Ctrl+C → cancel the action's token so long-running commands (e.g. sweep-catalog, // which loops until stopped) unwind gracefully instead of hard-killing the process // mid-write. using var cts = new CancellationTokenSource(); Console.CancelKeyPress += (_, e) => { e.Cancel = true; // prevent immediate termination; let the token cancel cleanly cts.Cancel(); }; return await root.Parse(args).InvokeAsync(cancellationToken: cts.Token);