File: Program.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.Tool.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.CommandLine;
using System.CommandLine.Parsing;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using Aspire.Cli.Agents;
using Aspire.Cli.Agents.ClaudeCode;
using Aspire.Cli.Agents.CopilotCli;
using Aspire.Cli.Agents.OpenCode;
using Aspire.Cli.Agents.VsCode;
using Aspire.Cli.Backchannel;
using Aspire.Cli.Caching;
using Aspire.Cli.Certificates;
using Aspire.Cli.Commands;
using Aspire.Cli.Commands.Sdk;
using Aspire.Cli.Configuration;
using Aspire.Cli.DotNet;
using Aspire.Cli.Git;
using Aspire.Cli.Interaction;
using Aspire.Cli.NuGet;
using Aspire.Cli.Packaging;
using Aspire.Cli.Projects;
using Aspire.Cli.Resources;
using Aspire.Cli.Scaffolding;
using Aspire.Cli.Telemetry;
using Aspire.Cli.Templating;
using Aspire.Cli.Utils;
using Aspire.Cli.Utils.EnvironmentChecker;
using Aspire.Cli.Mcp.Docs;
using Aspire.Hosting;
using Aspire.Shared;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Spectre.Console;
using RootCommand = Aspire.Cli.Commands.RootCommand;
 
namespace Aspire.Cli;
 
public class Program
{
    private static string GetUsersAspirePath()
    {
        var homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
        var aspirePath = Path.Combine(homeDirectory, ".aspire");
        return aspirePath;
    }
 
    private static string GetGlobalSettingsPath()
    {
        var usersAspirePath = GetUsersAspirePath();
        var globalSettingsPath = Path.Combine(usersAspirePath, "globalsettings.json");
        return globalSettingsPath;
    }
 
    internal static async Task<IHost> BuildApplicationAsync(string[] args, Dictionary<string, string?>? configurationValues = null)
    {
        // Check for --non-interactive flag early
        var nonInteractive = args?.Any(a => a == "--non-interactive") ?? false;
 
        // Check if running MCP start command - all logs should go to stderr to keep stdout clean for MCP protocol
        // Support both old 'mcp start' and new 'agent mcp' commands
        var isMcpStartCommand = args?.Length >= 2 &&
            ((args[0] == "mcp" && args[1] == "start") || (args[0] == "agent" && args[1] == "mcp"));
 
        var settings = new HostApplicationBuilderSettings
        {
            Configuration = new ConfigurationManager()
        };
        settings.Configuration.AddEnvironmentVariables();
 
        if (configurationValues is not null)
        {
            settings.Configuration.AddInMemoryCollection(configurationValues);
        }
 
        var builder = Host.CreateEmptyApplicationBuilder(settings);
 
        // Set up settings with appropriate paths.
        var globalSettingsFilePath = GetGlobalSettingsPath();
        var globalSettingsFile = new FileInfo(globalSettingsFilePath);
        var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);
        ConfigurationHelper.RegisterSettingsFiles(builder.Configuration, workingDirectory, globalSettingsFile);
 
        await TrySetLocaleOverrideAsync(LocaleHelpers.GetLocaleOverride(builder.Configuration));
 
#if !DEBUG
        // In release builds, limit shutdown wait time for telemetry flush to 200ms
        // to ensure the CLI exits quickly even if waiting on shutdown tasks.
        builder.Services.Configure<HostOptions>(options =>
        {
            options.ShutdownTimeout = TimeSpan.FromMilliseconds(200);
        });
#endif
 
        // Always configure OpenTelemetry.
        builder.Logging.AddOpenTelemetry(logging =>
        {
            logging.IncludeFormattedMessage = true;
            logging.IncludeScopes = true;
        });
 
        // Configure OpenTelemetry tracing. TelemetryManager reads configuration and creates
        // separate TracerProvider instances:
        // - Azure Monitor provider with filtering (only exports activities with EXTERNAL_TELEMETRY=true)
        // - Diagnostic provider for OTLP/console exporters (exports all activities, DEBUG only)
        builder.Services.AddSingleton(new TelemetryManager(builder.Configuration));
 
        var debugMode = args?.Any(a => a == "--debug" || a == "-d") ?? false;
        var extensionEndpoint = builder.Configuration[KnownConfigNames.ExtensionEndpoint];
 
        if (debugMode && !isMcpStartCommand && extensionEndpoint is null)
        {
            builder.Logging.AddFilter("Aspire.Cli", LogLevel.Debug);
            builder.Logging.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.Warning); // Reduce noise from hosting lifecycle
            // Use custom Spectre Console logger for clean debug output to stderr
            builder.Services.AddSingleton<ILoggerProvider>(sp =>
                new SpectreConsoleLoggerProvider(sp.GetRequiredService<ConsoleEnvironment>().Error.Profile.Out.Writer));
        }
 
        // For MCP start command, configure console logger to route all logs to stderr
        // This keeps stdout clean for MCP protocol JSON-RPC messages
        if (isMcpStartCommand)
        {
            if (debugMode)
            {
                builder.Logging.AddFilter("Aspire.Cli", LogLevel.Debug);
                builder.Logging.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.Warning); // Reduce noise from hosting lifecycle
            }
 
            builder.Logging.AddConsole(consoleLogOptions =>
            {
                // Configure all logs to go to stderr
                consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
            });
        }
 
        // Shared services.
        builder.Services.AddSingleton(_ => BuildCliExecutionContext(debugMode));
        builder.Services.AddSingleton(s => new ConsoleEnvironment(
            BuildAnsiConsole(s, Console.Out),
            BuildAnsiConsole(s, Console.Error)));
        builder.Services.AddSingleton(s => s.GetRequiredService<ConsoleEnvironment>().Out);
        builder.Services.AddSingleton<ICliHostEnvironment>(provider =>
        {
            var configuration = provider.GetRequiredService<IConfiguration>();
            return new CliHostEnvironment(configuration, nonInteractive);
        });
        builder.Services.AddSingleton(TimeProvider.System);
        AddInteractionServices(builder);
        builder.Services.AddSingleton<IProjectLocator, ProjectLocator>();
        builder.Services.AddSingleton<ISolutionLocator, SolutionLocator>();
        builder.Services.AddSingleton<ILanguageService, LanguageService>();
        builder.Services.AddSingleton<IScaffoldingService, ScaffoldingService>();
        builder.Services.AddSingleton<FallbackProjectParser>();
        builder.Services.AddSingleton<IProjectUpdater, ProjectUpdater>();
        builder.Services.AddSingleton<INewCommandPrompter, NewCommandPrompter>();
        builder.Services.AddSingleton<IAddCommandPrompter, AddCommandPrompter>();
        builder.Services.AddSingleton<IPublishCommandPrompter, PublishCommandPrompter>();
        builder.Services.AddSingleton<ICertificateService, CertificateService>();
        builder.Services.AddSingleton(BuildConfigurationService);
        builder.Services.AddSingleton<IFeatures, Features>();
        builder.Services.AddTelemetryServices();
        builder.Services.AddTransient<IDotNetCliExecutionFactory, DotNetCliExecutionFactory>();
        builder.Services.AddTransient<IDotNetCliRunner, DotNetCliRunner>();
        builder.Services.AddSingleton<IDiskCache, DiskCache>();
        builder.Services.AddSingleton<IDotNetSdkInstaller, DotNetSdkInstaller>();
        builder.Services.AddTransient<IAppHostCliBackchannel, AppHostCliBackchannel>();
        builder.Services.AddSingleton<INuGetPackageCache, NuGetPackageCache>();
        builder.Services.AddSingleton<NuGetPackagePrefetcher>();
        builder.Services.AddHostedService(sp => sp.GetRequiredService<NuGetPackagePrefetcher>());
        builder.Services.AddSingleton<AuxiliaryBackchannelMonitor>();
        builder.Services.AddSingleton<IAuxiliaryBackchannelMonitor>(sp => sp.GetRequiredService<AuxiliaryBackchannelMonitor>());
        builder.Services.AddHostedService(sp => sp.GetRequiredService<AuxiliaryBackchannelMonitor>());
        builder.Services.AddSingleton<ICliUpdateNotifier, CliUpdateNotifier>();
        builder.Services.AddSingleton<IPackagingService, PackagingService>();
        builder.Services.AddSingleton<IAppHostServerProjectFactory, AppHostServerProjectFactory>();
        builder.Services.AddSingleton<ICliDownloader, CliDownloader>();
        builder.Services.AddSingleton<IFirstTimeUseNoticeSentinel>(_ => new FirstTimeUseNoticeSentinel(GetUsersAspirePath()));
        builder.Services.AddMemoryCache();
 
        // MCP server: aspire.dev docs services.
        builder.Services.AddSingleton<IDocsCache, DocsCache>();
        builder.Services.AddHttpClient<IDocsFetcher, DocsFetcher>();
        builder.Services.AddSingleton<IDocsIndexService, DocsIndexService>();
        builder.Services.AddSingleton<IDocsSearchService, DocsSearchService>();
 
        // Git repository operations.
        builder.Services.AddSingleton<IGitRepository, GitRepository>();
 
        // OpenCode CLI operations.
        builder.Services.AddSingleton<IOpenCodeCliRunner, OpenCodeCliRunner>();
 
        // Claude Code CLI operations.
        builder.Services.AddSingleton<IClaudeCodeCliRunner, ClaudeCodeCliRunner>();
 
        // VS Code CLI operations.
        builder.Services.AddSingleton<IVsCodeCliRunner, VsCodeCliRunner>();
        builder.Services.AddSingleton<ICopilotCliRunner, CopilotCliRunner>();
 
        // Agent environment detection.
        builder.Services.AddSingleton<IAgentEnvironmentDetector, AgentEnvironmentDetector>();
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IAgentEnvironmentScanner, VsCodeAgentEnvironmentScanner>());
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IAgentEnvironmentScanner, CopilotCliAgentEnvironmentScanner>());
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IAgentEnvironmentScanner, OpenCodeAgentEnvironmentScanner>());
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IAgentEnvironmentScanner, ClaudeCodeAgentEnvironmentScanner>());
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IAgentEnvironmentScanner, DeprecatedMcpCommandScanner>());
 
        // Template factories.
        builder.Services.AddSingleton<ITemplateProvider, TemplateProvider>();
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ITemplateFactory, DotNetTemplateFactory>());
 
        // Language discovery for polyglot support.
        builder.Services.AddSingleton<ILanguageDiscovery, DefaultLanguageDiscovery>();
 
        // AppHost server session factory for RPC communication.
        builder.Services.AddSingleton<IAppHostServerSessionFactory, AppHostServerSessionFactory>();
 
        // AppHost project handlers.
        builder.Services.AddSingleton<DotNetAppHostProject>();
        builder.Services.AddSingleton<Func<LanguageInfo, GuestAppHostProject>>(sp =>
        {
            return language => ActivatorUtilities.CreateInstance<GuestAppHostProject>(sp, language);
        });
        builder.Services.AddSingleton<IAppHostProjectFactory, AppHostProjectFactory>();
 
        // Environment checking services.
        builder.Services.AddSingleton<IEnvironmentCheck, WslEnvironmentCheck>();
        builder.Services.AddSingleton<IEnvironmentCheck, DotNetSdkCheck>();
        builder.Services.AddSingleton<IEnvironmentCheck, DeprecatedWorkloadCheck>();
        builder.Services.AddSingleton<IEnvironmentCheck, DevCertsCheck>();
        builder.Services.AddSingleton<IEnvironmentCheck, ContainerRuntimeCheck>();
        builder.Services.AddSingleton<IEnvironmentCheck, DeprecatedAgentConfigCheck>();
        builder.Services.AddSingleton<IEnvironmentChecker, EnvironmentChecker>();
 
        // Commands.
        builder.Services.AddTransient<NewCommand>();
        builder.Services.AddTransient<InitCommand>();
        builder.Services.AddTransient<RunCommand>();
        builder.Services.AddTransient<StopCommand>();
        builder.Services.AddTransient<PsCommand>();
        builder.Services.AddTransient<ResourcesCommand>();
        builder.Services.AddTransient<LogsCommand>();
        builder.Services.AddTransient<AddCommand>();
        builder.Services.AddTransient<PublishCommand>();
        builder.Services.AddTransient<ConfigCommand>();
        builder.Services.AddTransient<CacheCommand>();
        builder.Services.AddTransient<DoctorCommand>();
        builder.Services.AddTransient<UpdateCommand>();
        builder.Services.AddTransient<DeployCommand>();
        builder.Services.AddTransient<DoCommand>();
        builder.Services.AddTransient<ExecCommand>();
        builder.Services.AddTransient<McpCommand>();
        builder.Services.AddTransient<AgentCommand>();
        builder.Services.AddTransient<AgentMcpCommand>();
        builder.Services.AddTransient<AgentInitCommand>();
        builder.Services.AddTransient<SdkCommand>();
        builder.Services.AddTransient<SdkGenerateCommand>();
        builder.Services.AddTransient<SdkDumpCommand>();
        builder.Services.AddTransient<RootCommand>();
        builder.Services.AddTransient<ExtensionInternalCommand>();
 
        var app = builder.Build();
        return app;
    }
 
    private static DirectoryInfo GetHivesDirectory()
    {
        var homeDirectory = GetUsersAspirePath();
        var hivesDirectory = Path.Combine(homeDirectory, "hives");
        return new DirectoryInfo(hivesDirectory);
    }
 
    private static DirectoryInfo GetSdksDirectory()
    {
        var homeDirectory = GetUsersAspirePath();
        var sdksPath = Path.Combine(homeDirectory, "sdks");
        return new DirectoryInfo(sdksPath);
    }
 
    private static CliExecutionContext BuildCliExecutionContext(bool debugMode)
    {
        var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);
        var hivesDirectory = GetHivesDirectory();
        var cacheDirectory = GetCacheDirectory();
        var sdksDirectory = GetSdksDirectory();
        return new CliExecutionContext(workingDirectory, hivesDirectory, cacheDirectory, sdksDirectory, debugMode);
    }
 
    private static DirectoryInfo GetCacheDirectory()
    {
        var homeDirectory = GetUsersAspirePath();
        var cacheDirectoryPath = Path.Combine(homeDirectory, "cache");
        return new DirectoryInfo(cacheDirectoryPath);
    }
 
    private static async Task TrySetLocaleOverrideAsync(string? localeOverride)
    {
        if (localeOverride is not null)
        {
            var result = LocaleHelpers.TrySetLocaleOverride(localeOverride);
 
            string errorMessage;
            switch (result)
            {
                case SetLocaleResult.Success:
                    return;
                case SetLocaleResult.InvalidLocale:
                    errorMessage = string.Format(CultureInfo.CurrentCulture, ErrorStrings.UnsupportedLocaleProvided, localeOverride, string.Join(", ", LocaleHelpers.SupportedLocales));
                    break;
                case SetLocaleResult.UnsupportedLocale:
                    errorMessage = string.Format(CultureInfo.CurrentCulture, ErrorStrings.InvalidLocaleProvided, localeOverride);
                    break;
                default:
                    throw new InvalidOperationException($"Unexpected result: {result}");
            }
 
            // This writes directly to Console.Error because services aren't available yet.
            await Console.Error.WriteLineAsync(errorMessage);
        }
    }
 
    private static IConfigurationService BuildConfigurationService(IServiceProvider serviceProvider)
    {
        var configuration = serviceProvider.GetRequiredService<IConfiguration>();
        var executionContext = serviceProvider.GetRequiredService<CliExecutionContext>();
        var globalSettingsFile = new FileInfo(GetGlobalSettingsPath());
        return new ConfigurationService(configuration, executionContext, globalSettingsFile);
    }
 
    internal static void DisplayFirstTimeUseNoticeIfNeeded(IServiceProvider serviceProvider, bool noLogo)
    {
        var sentinel = serviceProvider.GetRequiredService<IFirstTimeUseNoticeSentinel>();
 
        if (sentinel.Exists())
        {
            return;
        }
 
        if (!noLogo)
        {
            // Write to stderr to avoid interfering with tools that parse stdout
            var consoleEnvironment = serviceProvider.GetRequiredService<ConsoleEnvironment>();
 
            // Display welcome. Matches ConsoleInteractionService.DisplayMessage to display a message with emoji consistently.
            consoleEnvironment.Error.Markup(":waving_hand:");
            consoleEnvironment.Error.Write("\u001b[4G");
            consoleEnvironment.Error.MarkupLine(RootCommandStrings.FirstTimeUseWelcome);
 
            consoleEnvironment.Error.WriteLine();
            consoleEnvironment.Error.WriteLine(RootCommandStrings.FirstTimeUseTelemetryNotice);
            consoleEnvironment.Error.WriteLine();
        }
 
        sentinel.CreateIfNotExists();
    }
 
    private static IAnsiConsole BuildAnsiConsole(IServiceProvider serviceProvider, TextWriter writer)
    {
        var configuration = serviceProvider.GetRequiredService<IConfiguration>();
        var hostEnvironment = serviceProvider.GetRequiredService<ICliHostEnvironment>();
        var isPlayground = CliHostEnvironment.IsPlaygroundMode(configuration);
 
        // Create custom output that handles width detection better in CI environments
        // and encapsulates ASPIRE_CONSOLE_WIDTH environment variable handling
        var output = new AspireAnsiConsoleOutput(writer, configuration);
 
        var settings = new AnsiConsoleSettings()
        {
            Ansi = isPlayground ? AnsiSupport.Yes : AnsiSupport.Detect,
            Interactive = isPlayground ? InteractionSupport.Yes : InteractionSupport.Detect,
            ColorSystem = isPlayground ? ColorSystemSupport.Standard : ColorSystemSupport.Detect,
            Out = output,
        };
 
        // Use SupportsAnsi from hostEnvironment which already checks ASPIRE_ANSI_PASS_THRU
        if (hostEnvironment.SupportsAnsi)
        {
            settings.Ansi = AnsiSupport.Yes;
            settings.ColorSystem = ColorSystemSupport.Standard;
        }
 
        if (isPlayground)
        {
            // Enrichers interfere with interactive playground experience so
            // this suppresses the default enrichers so that the CLI experience
            // is more like what we would get in an interactive experience.
            settings.Enrichment.UseDefaultEnrichers = false;
            settings.Enrichment.Enrichers = new()
            {
                new AspirePlaygroundEnricher()
            };
        }
 
        var ansiConsole = AnsiConsole.Create(settings);
        return ansiConsole;
    }
 
    public static async Task<int> Main(string[] args)
    {
        // Setup handling of CTRL-C as early as possible so that if
        // we get a CTRL-C anywhere that is not handled by Spectre Console
        // already that we know to trigger cancellation.
        using var cts = new CancellationTokenSource();
        Console.CancelKeyPress += (sender, eventArgs) =>
        {
            cts.Cancel();
            eventArgs.Cancel = true;
        };
 
        Console.OutputEncoding = Encoding.UTF8;
 
        using var app = await BuildApplicationAsync(args);
 
        await app.StartAsync().ConfigureAwait(false);
 
        // Display first run experience if this is the first time the CLI is run on this machine
        var configuration = app.Services.GetRequiredService<IConfiguration>();
        var noLogo = args.Any(a => a == "--nologo") || configuration.GetBool(CliConfigNames.NoLogo, defaultValue: false);
        DisplayFirstTimeUseNoticeIfNeeded(app.Services, noLogo);
 
        var rootCommand = app.Services.GetRequiredService<RootCommand>();
        var invokeConfig = new InvocationConfiguration()
        {
            // Disable default exception handler so we can log exceptions to telemetry.
            EnableDefaultExceptionHandler = false
        };
 
        var telemetry = app.Services.GetRequiredService<AspireCliTelemetry>();
        var telemetryManager = app.Services.GetRequiredService<TelemetryManager>();
        var logger = app.Services.GetRequiredService<ILogger<Program>>();
 
        using var mainActivity = telemetry.StartReportedActivity(name: TelemetryConstants.Activities.Main, kind: ActivityKind.Internal);
 
        if (mainActivity != null)
        {
            var currentProcess = Process.GetCurrentProcess();
            mainActivity.SetStartTime(currentProcess.StartTime);
            mainActivity.AddTag(TelemetryConstants.Tags.ProcessPid, currentProcess.Id);
            mainActivity.AddTag(TelemetryConstants.Tags.ProcessExecutableName, "aspire");
        }
 
        try
        {
            logger.LogDebug("Parsing arguments: {Args}", string.Join(" ", args));
            var parseResult = rootCommand.Parse(args);
 
            var commandName = GetCommandName(parseResult);
            logger.LogDebug("Executing command: {CommandName}", commandName);
 
            mainActivity?.SetTag(TelemetryConstants.Tags.CommandName, commandName);
 
            var exitCode = await parseResult.InvokeAsync(invokeConfig, cts.Token);
 
            mainActivity?.SetTag(TelemetryConstants.Tags.ProcessExitCode, exitCode);
            mainActivity?.Stop();
 
            return exitCode;
        }
        catch (Exception ex)
        {
            const int unknownErrorExitCode = 1;
            // Catch block is used instead of System.Commandline's default handler behavior.
            // Allows logging of exceptions to telemetry.
 
            // Don't log or display cancellation exceptions.
            if (!(ex is OperationCanceledException && cts.IsCancellationRequested))
            {
                logger.LogError(ex, "An unexpected error occurred.");
 
                telemetry.RecordError("An unexpected error occurred.", ex);
 
                var interactionService = app.Services.GetRequiredService<IInteractionService>();
                interactionService.DisplayError(string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.UnexpectedErrorOccurred, ex.Message));
            }
 
            mainActivity?.SetTag(TelemetryConstants.Tags.ProcessExitCode, unknownErrorExitCode);
            mainActivity?.Stop();
 
            return unknownErrorExitCode;
        }
        finally
        {
            // Shutting down telemetry manager to flush any remaining telemetry and will take time.
            // Start shutdown of telemetry manager immediately and run concurrently with app shutdown.
            var shutdownTelemetryTask = telemetryManager.ShutdownAsync();
 
            await app.StopAsync().ConfigureAwait(false);
            await shutdownTelemetryTask;
        }
    }
 
    private static string GetCommandName(ParseResult r)
    {
        // Walk the parent command tree to find the top-level command name and get the full command name for this parseresult.
        var parentNames = new List<string> { r.CommandResult.Command.Name };
        var current = r.CommandResult.Parent;
        while (current is CommandResult parentCommandResult)
        {
            parentNames.Add(parentCommandResult.Command.Name);
            current = parentCommandResult.Parent;
        }
        parentNames.Reverse();
        return string.Join(' ', parentNames);
    }
 
    private static void AddInteractionServices(HostApplicationBuilder builder)
    {
        var extensionEndpoint = builder.Configuration[KnownConfigNames.ExtensionEndpoint];
 
        if (extensionEndpoint is not null)
        {
            builder.Services.AddSingleton<IExtensionRpcTarget, ExtensionRpcTarget>();
            builder.Services.AddSingleton<IExtensionBackchannel, ExtensionBackchannel>();
 
            var extensionPromptEnabled = builder.Configuration[KnownConfigNames.ExtensionPromptEnabled] is "true";
            builder.Services.AddSingleton<IInteractionService>(provider =>
            {
                var consoleEnvironment = provider.GetRequiredService<ConsoleEnvironment>();
                consoleEnvironment.Out.Profile.Width = 256; // VS code terminal will handle wrapping so set a large width here.
                var executionContext = provider.GetRequiredService<CliExecutionContext>();
                var hostEnvironment = provider.GetRequiredService<ICliHostEnvironment>();
                var consoleInteractionService = new ConsoleInteractionService(consoleEnvironment, executionContext, hostEnvironment);
                return new ExtensionInteractionService(consoleInteractionService,
                    provider.GetRequiredService<IExtensionBackchannel>(),
                    extensionPromptEnabled);
            });
        }
        else
        {
            builder.Services.AddSingleton<IInteractionService>(provider =>
            {
                var consoleEnvironment = provider.GetRequiredService<ConsoleEnvironment>();
                var executionContext = provider.GetRequiredService<CliExecutionContext>();
                var hostEnvironment = provider.GetRequiredService<ICliHostEnvironment>();
                return new ConsoleInteractionService(consoleEnvironment, executionContext, hostEnvironment);
            });
        }
    }
}
 
internal class AspirePlaygroundEnricher : IProfileEnricher
{
    public string Name => "Aspire Playground";
 
    public bool Enabled(IDictionary<string, string> environmentVariables)
    {
        if (!environmentVariables.TryGetValue("ASPIRE_PLAYGROUND", out var value))
        {
            return false;
        }
 
        if (!bool.TryParse(value, out var isEnabled))
        {
            return false;
        }
 
        return isEnabled;
    }
 
    public void Enrich(Profile profile)
    {
        profile.Capabilities.Interactive = true;
    }
}