using System.CommandLine; public static class ReplayCommand { public static Command Create() { var fileArg = new Argument("scenario-file"); var speedOpt = new Option("--speed", () => 60, "Speed multiplier (0=instant, 1=realtime)"); var baseUrlOpt = new Option("--base-url", () => "http://localhost:5270"); var pollOpt = new Option("--poll", () => false, "Poll alerts/scores after each cluster"); var pollIntervalOpt = new Option("--poll-interval", () => 5, "Seconds between polls"); var usernameOpt = new Option("--username", () => "physician.demo", "API login username"); var passwordOpt = new Option("--password", () => "DemoPhysician1!", "API login password"); var gatewayOpt = new Option("--gateway", () => false, "Target ward gateway API (default base URL http://localhost:5081)"); var encounterIdOpt = new Option("--encounter-id", "Use existing encounter (required for --gateway when replica already synced)"); var skipSetupOpt = new Option("--skip-setup", () => false, "Skip patient/encounter registration — use --encounter-id"); var gatewayTokenOpt = new Option("--gateway-token", "Bearer token for ward gateway (default: GATEWAY_JWT env var when --gateway)"); var command = new Command("replay", "Replay a scenario against the API") { fileArg, speedOpt, baseUrlOpt, pollOpt, pollIntervalOpt, usernameOpt, passwordOpt, gatewayOpt, encounterIdOpt, skipSetupOpt, gatewayTokenOpt }; command.SetHandler(async context => { var file = context.ParseResult.GetValueForArgument(fileArg); var speed = context.ParseResult.GetValueForOption(speedOpt); var baseUrl = context.ParseResult.GetValueForOption(baseUrlOpt)!; var poll = context.ParseResult.GetValueForOption(pollOpt); var pollInterval = context.ParseResult.GetValueForOption(pollIntervalOpt); var username = context.ParseResult.GetValueForOption(usernameOpt)!; var password = context.ParseResult.GetValueForOption(passwordOpt)!; var gateway = context.ParseResult.GetValueForOption(gatewayOpt); var encounterId = context.ParseResult.GetValueForOption(encounterIdOpt); var skipSetup = context.ParseResult.GetValueForOption(skipSetupOpt); var gatewayToken = context.ParseResult.GetValueForOption(gatewayTokenOpt); var scenario = ScenarioLoader.Load(file.FullName); var errors = ScenarioValidator.Validate(scenario); if (errors.Count > 0) { SimulatorConsole.Error($"Scenario validation failed with {errors.Count} error(s):"); foreach (var err in errors) SimulatorConsole.Error($" • {err}"); return; } if (skipSetup && !encounterId.HasValue) { SimulatorConsole.Error("--skip-setup requires --encounter-id"); return; } if (gateway && !encounterId.HasValue) { SimulatorConsole.Error("--gateway requires --encounter-id"); return; } var effectiveBaseUrl = gateway ? "http://localhost:5081" : baseUrl; using var http = new HttpClient { BaseAddress = new Uri(effectiveBaseUrl) }; var client = new VigilCareApiClient(http); if (gateway) { var token = gatewayToken ?? Environment.GetEnvironmentVariable("GATEWAY_JWT"); if (string.IsNullOrWhiteSpace(token)) { SimulatorConsole.Error( "Gateway mode requires --gateway-token or GATEWAY_JWT (ward gateway has no /auth/login endpoint)"); return; } client.SetBearerToken(token); SimulatorConsole.Info("Using gateway bearer token."); } else { SimulatorConsole.Info($"Authenticating as {username}..."); await client.LoginAsync(username, password); SimulatorConsole.Info("Authenticated."); } var poller = poll ? new ApiPoller(client) : null; var engine = new ReplayEngine(client, poller); var options = new ReplayOptions( speed, poll, pollInterval, DryRun: false, Target: gateway ? ReplayTarget.Gateway : ReplayTarget.Central, ExistingEncounterId: encounterId); await engine.RunAsync(scenario, options); }); return command; } }