From a2d5b95b4f5facf474f7b7d87b6c578b4f58f9d8 Mon Sep 17 00:00:00 2001 From: "aspire-repo-bot[bot]" <268009190+aspire-repo-bot[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 05:34:36 +0000 Subject: [PATCH 01/32] [docs] Document timestamp search qualifier for telemetry filtering (#1181) * docs: document timestamp search qualifier for telemetry filtering Documents the new 'timestamp' search qualifier added in microsoft/aspire#17816. Users can now filter traces and structured logs by date/time using ISO 8601 strings with comparison operators (>, >=, <, <=) in the dashboard filter bar. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Move timestamp qualifier docs to shared CLI references Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> * Fix severity casing in timestamp search example Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: James Newton-King * Apply suggestion from @JamesNK * Apply suggestion from @JamesNK * docs: clarify date-only timestamp search values Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> * Apply suggestion from @JamesNK * Apply suggestion from @JamesNK * Update timestamp example dates to use 2026 --------- Co-authored-by: aspire-repo-bot[bot] <268009190+aspire-repo-bot[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> Co-authored-by: James Newton-King --- .../cli/commands/aspire-otel-logs.mdx | 9 +++++- .../cli/commands/aspire-otel-spans.mdx | 10 +++++-- .../cli/commands/aspire-otel-traces.mdx | 10 +++++-- .../docs/reference/cli/search-filter.mdx | 29 ++++++++++++++++--- 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-logs.mdx b/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-logs.mdx index 2971234ed..a3b0ba7df 100644 --- a/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-logs.mdx +++ b/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-logs.mdx @@ -73,9 +73,10 @@ The following options are available: | `"quoted phrase"` | Single fragment containing spaces | | `field:value` | Field qualifier — value must match the named field | | `-field:value` | Negated qualifier — excludes matches | + | `field:>value` / `field:>=value` / `field: @@ -133,6 +134,12 @@ The following options are available: aspire otel logs --search "scope:Microsoft.EntityFrameworkCore severity:Warning" ``` +- Filter logs by timestamp: + + ```bash title="Aspire CLI" + aspire otel logs --search "timestamp:>=2026-06-01T12:00:00 severity:error" + ``` + - View logs from a standalone dashboard using the login URL (token is automatically exchanged for an API key): ```bash title="Aspire CLI" diff --git a/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-spans.mdx b/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-spans.mdx index 595fa1c69..628a9c0ca 100644 --- a/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-spans.mdx +++ b/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-spans.mdx @@ -73,10 +73,10 @@ The following options are available: | `"quoted phrase"` | Single fragment containing spaces | | `field:value` | Field qualifier — value must match the named field | | `-field:value` | Negated qualifier — excludes matches | - | `field:>N` / `field:>=N` / `field:value` / `field:>=value` / `field: @@ -134,6 +134,12 @@ The following options are available: aspire otel spans --search "@db.system:postgresql" ``` +- Filter spans by timestamp: + + ```bash title="Aspire CLI" + aspire otel spans --search "timestamp:>=2026-03-01T08:30:00 kind:server" + ``` + - View spans from a standalone dashboard using the login URL (token is automatically exchanged for an API key): ```bash title="Aspire CLI" diff --git a/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-traces.mdx b/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-traces.mdx index 2e174409e..27c494276 100644 --- a/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-traces.mdx +++ b/src/frontend/src/content/docs/reference/cli/commands/aspire-otel-traces.mdx @@ -71,10 +71,10 @@ The following options are available: | `"quoted phrase"` | Single fragment containing spaces | | `field:value` | Field qualifier — value must match the named field | | `-field:value` | Negated qualifier — excludes matches | - | `field:>N` / `field:>=N` / `field:value` / `field:>=value` / `field: @@ -132,6 +132,12 @@ The following options are available: aspire otel traces --search "@http.status_code:500" ``` +- Filter traces by timestamp: + + ```bash title="Aspire CLI" + aspire otel traces --search "timestamp:>2026-01-15T09:30:00 status:error" + ``` + - View traces from a standalone dashboard using the login URL (token is automatically exchanged for an API key): ```bash title="Aspire CLI" diff --git a/src/frontend/src/content/docs/reference/cli/search-filter.mdx b/src/frontend/src/content/docs/reference/cli/search-filter.mdx index a9f4f514a..3d667bb0d 100644 --- a/src/frontend/src/content/docs/reference/cli/search-filter.mdx +++ b/src/frontend/src/content/docs/reference/cli/search-filter.mdx @@ -28,10 +28,28 @@ Search queries support free-text fragments, field qualifiers, attribute qualifie | `field:"value with spaces"` | Quoted field qualifier value | `message:"connection failed"` | | `@attr:value` | Attribute qualifier — matches custom span/log attributes | `@http.method:GET` | | `-field:value` / `-@attr:value` | Negated qualifier — excludes matches | `-severity:debug` | -| `field:>N` / `field:>=N` / `field:100` | +| `field:>value` / `field:>=value` / `field:100` | All terms are AND'd: every fragment and qualifier must independently match. +## Timestamp qualifier + +Use the `timestamp` field qualifier to narrow structured logs, traces, and spans to a specific date/time range. The qualifier accepts ISO 8601 date or date/time strings together with a comparison operator: + +```bash title="Aspire CLI" +aspire otel logs --search "timestamp:>2026-01-15T09:30:00" +aspire otel traces --search "timestamp:<=2026-06-01T12:00:00Z" +aspire otel spans --search "timestamp:>=2026-01-01 timestamp:<2027-01-01" +``` + +The supported comparison operators are `>`, `>=`, `<`, and `<=`. + +Timestamp values are internally stored with UTC. The `timestamp` value is converted to UTC using these timezone rules: + +- A value with no timezone suffix (for example `2026-01-15` or `2026-01-15T09:30:00`) is treated as local time on the dashboard server and adjusted to UTC before comparison. +- A value with a UTC offset (for example `2026-06-01T12:00:00+05:00`) is adjusted to UTC before comparison. +- A value with the `Z` suffix is treated as UTC directly. + ## Console logs Matches against log line text content and resource name. Only free-text is supported (no structured qualifiers). @@ -45,36 +63,39 @@ aspire logs --follow --search "\"connection error\"" Supports free-text and structured qualifiers. Free-text matches against message, resource name, scope, trace/span IDs, severity, and all attribute keys/values. -Available field qualifiers: `severity`, `resource`, `scope`, `message`, `trace-id`, `span-id`, `event`. +Available field qualifiers: `severity`, `resource`, `scope`, `message`, `trace-id`, `span-id`, `event`, `timestamp`. ```bash title="Aspire CLI" aspire otel logs --search "severity:error \"connection failed\"" aspire otel logs --search "resource:api -severity:debug" aspire otel logs --search "scope:Microsoft.EntityFrameworkCore" +aspire otel logs --search "timestamp:>=2026-06-01T12:00:00 severity:error" ``` ## Traces Supports free-text and structured qualifiers. Free-text matches against trace name, resource name, and all span fields within the trace. -Available field qualifiers: `name`, `resource`, `trace-id`, `status`, `duration`. +Available field qualifiers: `name`, `resource`, `trace-id`, `status`, `duration`, `timestamp`. ```bash title="Aspire CLI" aspire otel traces --search "checkout" aspire otel traces --search "status:error duration:>500" aspire otel traces --search "@http.status_code:500" +aspire otel traces --search "timestamp:>2026-01-15T09:30:00 status:error" ``` ## Spans Supports free-text and structured qualifiers. Free-text matches against span name, resource name, scope, trace/span IDs, status, kind, and all attribute keys/values. -Available field qualifiers: `name`, `resource`, `scope`, `status`, `kind`, `trace-id`, `span-id`, `duration`. +Available field qualifiers: `name`, `resource`, `scope`, `status`, `kind`, `trace-id`, `span-id`, `duration`, `timestamp`. ```bash title="Aspire CLI" aspire otel spans --search "@http.method:GET duration:>100 status:error" aspire otel spans --search "@db.system:postgresql" aspire otel spans --search "-kind:internal" +aspire otel spans --search "timestamp:>=2026-03-01T08:30:00 kind:server" ``` ## See also From 05a46fd4656722a0cd11265a3175040782568bff Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 8 Jun 2026 07:32:14 -0500 Subject: [PATCH 02/32] [docs] Fix persistent container endpoint proxy default docs (#1227) * Fix persistent container endpoint proxy default docs Persistent containers use proxied endpoints by default (same as session containers), while persistent executables and projects default to proxyless endpoints. Also document that proxyless container endpoints with only a targetPort immediately allocate the targetPort as the host port. Corrects docs that previously stated all persistent resources default to proxyless endpoints. Documents changes from microsoft/aspire#17960. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: aspire-repo-bot[bot] <268009190+aspire-repo-bot[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: David Negstad <50252651+danegsta@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/content/docs/app-host/resource-lifetimes.mdx | 4 +++- .../content/docs/fundamentals/networking-overview.mdx | 8 +++++--- .../src/content/docs/whats-new/aspire-13-4.mdx | 11 ++++++----- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx index 3335e6aa7..183e8dabc 100644 --- a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx +++ b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx @@ -38,7 +38,9 @@ Use persistent lifetime for resources that are expensive to initialize, need sta Persistent resources are automatically recreated when the AppHost detects meaningful configuration changes. If the configuration differs, the resource is recreated with the new settings. ::: -Persistent resources default to proxyless endpoints so a stable local endpoint can remain reachable after the AppHost stops. You can still configure endpoint proxy behavior explicitly. For example, set `isProxied: true` or `IsProxied = true` when you need Aspire's proxy for a specific endpoint, or disable endpoint proxy support when you intentionally want every endpoint on a resource to be proxyless. Persistent executable endpoints must have a concrete `port` or `targetPort`; automatically persisted random executable ports aren't supported. +Persistent containers use proxied endpoints by default, just like session containers. The proxy runs only while the AppHost is running, so the proxy address isn't reachable after the AppHost stops. Persistent executables and projects default to proxyless endpoints so their direct addresses stay stable and reachable even after the AppHost stops. + +You can still configure endpoint proxy behavior explicitly on any persistent resource. Set `isProxied: false` on an individual endpoint, or call `WithEndpointProxySupport(false)` to make every endpoint on a resource proxyless. Persistent executable endpoints must have a concrete `port` or `targetPort`; automatically persisted random executable ports aren't supported. For proxyless container endpoints with only a `targetPort`, Aspire immediately uses the target port as the allocated host port. :::danger[Persistent container ≠ persistent data] Persistent container lifetime doesn't guarantee data durability. For details, see [Container lifetime vs. data durability](#container-lifetime-vs-data-durability). diff --git a/src/frontend/src/content/docs/fundamentals/networking-overview.mdx b/src/frontend/src/content/docs/fundamentals/networking-overview.mdx index b2fcaaf7f..5e7d3e6e3 100644 --- a/src/frontend/src/content/docs/fundamentals/networking-overview.mdx +++ b/src/frontend/src/content/docs/fundamentals/networking-overview.mdx @@ -55,7 +55,7 @@ The inner loop is the process of developing and testing your app locally before - **Endpoints/Endpoint configurations**: Endpoints are the connections between your app and the services it depends on, such as databases, message queues, or APIs. Endpoints provide information such as the service name, host port, scheme, and environment variable. Aspire can create endpoints automatically from resource configuration, and you can also add them explicitly by calling `WithEndpoint`. - **Proxies**: Aspire automatically launches a proxy for each proxied service binding you add to your app, and assigns a port for the proxy to listen on. The proxy then forwards the requests to the port that your app listens on, which might be different from the proxy port. This way, you can avoid port conflicts and access your app and services using consistent and predictable URLs. -- **Proxyless endpoints**: Endpoints with `IsProxied` set to `false` connect directly to the resource instead of routing through an Aspire-managed proxy. Starting in Aspire 13.4, endpoints on persistent resources are proxyless by default. Proxyless endpoints must specify a target port so Aspire knows which port the resource listens on. +- **Proxyless endpoints**: Endpoints with `IsProxied` set to `false` connect directly to the resource instead of routing through an Aspire-managed proxy. Starting in Aspire 13.4, endpoints on persistent executables and projects are proxyless by default. Persistent containers use proxied endpoints by default, the same as session containers. Proxyless endpoints must specify a target port so Aspire knows which port the resource listens on. - **Container networks**: Aspire creates and manages dedicated networks for container resources so containers can discover and communicate with each other during local development. ## How endpoints work @@ -230,7 +230,9 @@ The underlying service still listens on its own port, and Aspire makes that allo Most endpoints are proxied by default. A proxyless endpoint skips the Aspire-managed proxy and exposes the resource's listener directly. Use a proxyless endpoint when a proxy would interfere with the resource's lifetime or networking behavior. -Starting in Aspire 13.4, endpoints on persistent resources are proxyless by default. This lets the persistent resource keep using its direct endpoint across AppHost runs instead of depending on a new proxy instance each time. This also ensures that the ports your service is reachable on stay stable whether the AppHost is running or not. +Starting in Aspire 13.4, endpoints on persistent executables and projects are proxyless by default. This lets the persistent resource keep using its direct endpoint across AppHost runs instead of depending on a new proxy instance each time. This also ensures that the ports your service is reachable on stay stable whether the AppHost is running or not. Persistent containers use proxied endpoints by default, the same as session containers, so integrations that depend on endpoint allocation before startup continue to work. + +For proxyless container endpoints, Aspire resolves the host port immediately when only a `targetPort` is specified — there is no deferred allocation. For example, `.WithEndpoint(targetPort: 6379, isProxied: false)` allocates host port `6379` right away. Because there's no proxy to listen on one port and forward to another, every proxyless endpoint must include a target port. The target port tells Aspire which port the resource listens on. You can also set `port` when you need a specific host port, but `targetPort` is still required for proxyless endpoints. @@ -405,7 +407,7 @@ The `AllocatedEndpoint` property allows you to get or set the endpoint for a ser on. -The `Name` property identifies the service, whereas the `Port` and `TargetPort` properties specify the desired and listening ports, respectively. `TargetPort` is required for proxyless endpoints, including endpoints with `IsProxied` set to `false` and, starting in Aspire 13.4, endpoints on persistent resources by default. +The `Name` property identifies the service, whereas the `Port` and `TargetPort` properties specify the desired and listening ports, respectively. `TargetPort` is required for proxyless endpoints, including endpoints with `IsProxied` set to `false` and, starting in Aspire 13.4, endpoints on persistent executables and projects by default. For network communication, the `Protocol` property supports **TCP** and **UDP**, with potential for more in the future, and the `Transport` property indicates the transport protocol (**HTTP**, **HTTP2**, **HTTP3**). Lastly, if the service is URI-addressable, the `UriScheme` property provides the URI scheme for constructing the service URI. diff --git a/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx b/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx index e74557761..da10fb7af 100644 --- a/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx +++ b/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx @@ -473,10 +473,11 @@ Persistent lifetimes now extend beyond containers to executables and projects. C @@ -779,7 +780,7 @@ Re-run `aspire run` (or your code-generation step) to regenerate references, and #### Persistent executable and project lifetimes -Support for persistent lifetimes on executables and projects updates the underlying DCP executable model (new `persistent`, `start`, and `stop` fields). Persistent resources default to proxyless endpoints, require concrete ports for executables, don't support replicas, and aren't compatible with Aspire IDE debugging sessions. +Support for persistent lifetimes on executables and projects updates the underlying DCP executable model (new `persistent`, `start`, and `stop` fields). Persistent executables and projects default to proxyless endpoints, require concrete ports for executables, don't support replicas, and aren't compatible with Aspire IDE debugging sessions. Persistent containers use proxied endpoints by default, matching session container behavior, so integrations that depend on endpoint allocation before startup work correctly without changes. If you adopt `WithPersistentLifetime()`, review endpoint and port configuration. Tooling pinned to the previous DCP executable schema may need to be updated. See [Configure resource lifetimes](/app-host/resource-lifetimes/). From 935ad6428b695ac2ae0f1e0c28ca8ad7e44dbda6 Mon Sep 17 00:00:00 2001 From: David Pine <7679720+IEvangelist@users.noreply.github.com> Date: Mon, 8 Jun 2026 07:39:42 -0500 Subject: [PATCH 03/32] Add Aspire 13.5 release scaffold Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/frontend/config/sidebar/docs.topics.ts | 4 + .../content/docs/whats-new/aspire-13-5.mdx | 83 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/frontend/src/content/docs/whats-new/aspire-13-5.mdx diff --git a/src/frontend/config/sidebar/docs.topics.ts b/src/frontend/config/sidebar/docs.topics.ts index 252aad6fe..2b87a829c 100644 --- a/src/frontend/config/sidebar/docs.topics.ts +++ b/src/frontend/config/sidebar/docs.topics.ts @@ -90,6 +90,10 @@ export const docsTopics: StarlightSidebarTopicsUserConfig = { label: "What's new", collapsed: true, items: [ + { + label: 'Aspire 13.5', + slug: 'whats-new/aspire-13-5', + }, { label: 'Aspire 13.4', slug: 'whats-new/aspire-13-4', diff --git a/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx b/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx new file mode 100644 index 000000000..a5d17d0cf --- /dev/null +++ b/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx @@ -0,0 +1,83 @@ +--- +title: "What's new in Aspire 13.5" +description: 'Prepare for Aspire 13.5 with placeholder release notes, upgrade guidance, and links for final features, fixes, and breaking changes as they are finalized.' +sidebar: + label: Aspire 13.5 + order: 0 +tableOfContents: + minHeadingLevel: 2 + maxHeadingLevel: 2 +--- + +import { Steps, Aside } from '@astrojs/starlight/components'; +import LearnMore from '@components/LearnMore.astro'; +import OsAwareTabs from '@components/OsAwareTabs.astro'; + +Aspire 13.5 release notes are being prepared. This page is available early so release branch navigation, redirects, and documentation validation can target the Aspire 13.5 release while the final content is completed. + + + +## Upgrade to Aspire 13.5 + + +
+ +For general upgrade guidance, see [Upgrade Aspire](/whats-new/upgrade-aspire/). + +The standard upgrade flow is to update the Aspire CLI first, then update your projects from the root of your repository: + + + +1. Update the Aspire CLI itself: + + ```bash title="Aspire CLI - Update the CLI" + aspire update --self + ``` + +1. Update your projects: + + ```bash title="Aspire CLI - Update all Aspire packages" + aspire update + ``` + + + +Or install the CLI from scratch: + + + + + ```bash title="Aspire CLI - Install Aspire CLI" + curl -sSL https://aspire.dev/install.sh | bash + ``` + + + + + ```powershell title="Aspire CLI - Install Aspire CLI" + irm https://aspire.dev/install.ps1 | iex + ``` + + + + + + For more details on installing the Aspire CLI, see [Install the + CLI](/get-started/install-cli/). + + +## Release highlights + +Final Aspire 13.5 highlights will be added here as features, fixes, and documentation updates are finalized for the release. + +## Breaking changes + +No Aspire 13.5 breaking changes are documented yet. This section will be updated as release notes are finalized. + +## Known issues + +No Aspire 13.5 known issues are documented yet. This section will be updated if release validation identifies issues that users should know about. From 956a98e0221cb685c5dff8ca50acf81dd3507b72 Mon Sep 17 00:00:00 2001 From: "aspire-repo-bot[bot]" <268009190+aspire-repo-bot[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 21:26:38 -0500 Subject: [PATCH 04/32] [docs] Update Foundry Local docs to reflect CLI-based lifecycle (aspire#17889) (#1210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: update Foundry Local section to reflect CLI-based lifecycle The Foundry Local integration now uses the installed 'foundry' CLI (foundry service start/stop/list and foundry model download/load) instead of the FoundryLocalManager in-proc APIs. Aspire manages the entire service lifecycle automatically. Update docs to: - Clarify users do not need to pre-start Foundry Local - State that the 'foundry' CLI must be on PATH - Describe the automatic start/stop lifecycle management Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: aspire-repo-bot[bot] <268009190+aspire-repo-bot[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Sébastien Ros Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx b/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx index e2bcff7eb..923e74059 100644 --- a/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx +++ b/src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx @@ -663,9 +663,11 @@ await builder.build().run(); -When the AppHost starts up, the local foundry service is also started. This requires the local machine to have [Foundry Local](https://learn.microsoft.com/azure/ai-foundry/foundry-local/get-started) installed and running. +When the AppHost starts, Aspire automatically starts the Foundry Local service using the `foundry` CLI (`foundry service start`). It then discovers the service endpoint, downloads and loads the specified models via CLI commands, and stops the service (`foundry service stop`) when the AppHost shuts down or is disposed. You do not need to pre-start Foundry Local manually. -The `RunAsFoundryLocal` method configures the resource to run as an emulator. It downloads and loads the specified models locally. The method provides health checks for the local service and automatically manages the Foundry Local lifecycle. +This requires the `foundry` CLI to be installed and available on `PATH`. For installation instructions, see [Foundry Local get started](https://learn.microsoft.com/azure/ai-foundry/foundry-local/get-started). + +The `RunAsFoundryLocal` method configures the resource to use the local service. It downloads and loads the specified models locally, provides health checks for the local service, and fully manages the Foundry Local lifecycle — including start and stop — through the `foundry` CLI. +## Persistent volumes + +Model durable storage as a first-class `KubernetesPersistentVolumeResource` with `AddPersistentVolume`, configure its storage class, capacity, and access modes, then bind it to workloads. At publish time the volume renders as a `v1.PersistentVolumeClaim`, and any workload bound to it is promoted to a `StatefulSet`. + + + + +```csharp title="AppHost.cs" +using Aspire.Hosting.Kubernetes; + +var k8s = builder.AddKubernetesEnvironment("k8s"); + +var pgData = k8s.AddPersistentVolume("pg-data") + .WithStorageClass("managed-csi") + .WithCapacity("20Gi"); + +builder.AddPostgres("pg") + .WithDataVolume("pg-data") + .WithPersistentVolume(pgData); +``` + + + + +```typescript title="apphost.mts" +const k8s = await builder.addKubernetesEnvironment('k8s'); + +const pgData = await k8s.addPersistentVolume('pg-data'); +await pgData.withStorageClass('managed-csi'); +await pgData.withCapacity('20Gi'); + +const pg = await builder.addPostgres('pg'); +await pg.withDataVolume({ name: 'pg-data' }); +await pg.withKubernetesPersistentVolume(pgData); +``` + + + + + + For the full configuration surface, binding overloads, access modes, and generated output, see [Persistent volumes on Kubernetes](/deployment/kubernetes/persistent-volumes/). + + ## Customize individual resources Use `PublishAsKubernetesService` to modify the generated Kubernetes resources for individual services: @@ -356,6 +399,7 @@ For complete walkthroughs, see [Deploy to Kubernetes clusters](/deployment/kuber ## See also - [Deploy to Kubernetes](/deployment/kubernetes/) +- [Persistent volumes on Kubernetes](/deployment/kubernetes/persistent-volumes/) - [Expose services with Ingress and Gateway API](/deployment/kubernetes-ingress/) - [Configure Ingress on AKS](/deployment/kubernetes-ingress-aks/) - [Configure Gateway API on AKS](/deployment/kubernetes-gateway-aks/) From ec461abe262f9e13dc2bddd2f4d46b796e48da7d Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Mon, 6 Jul 2026 15:10:05 +1000 Subject: [PATCH 22/32] Add WithTerminal docs and aspire terminal CLI reference (#1329) * Add WithTerminal docs and aspire terminal CLI reference Document the experimental WithTerminal() AppHost API for exposing interactive terminal sessions on resources, plus the aspire terminal, aspire terminal attach, and aspire terminal ps CLI commands. Wires the new pages into the docs and reference sidebars and repoints the 13.5 what's-new link to the new slug. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Frame no-auto-debugger as a temporary NOTE callout Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Give C# and TypeScript equal billing in with-terminal docs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Consolidate WithTerminal docs onto with-terminal.mdx Remove the duplicate withterminal.mdx (from #1244) in favor of the kebab-case with-terminal.mdx, matching the app-host directory naming convention. Fold in the dashboard section and the TypeScript config tracking issue reference. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address PR review: self-contained snippets and ASPIRETERMINAL001 guidance Add the #pragma warning disable ASPIRETERMINAL001 suppression to the C# examples, make the Experimental aside actionable, and make the configure and replicas snippets self-contained (full builder bootstrap + run) for both C# and TypeScript. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Mitch Denny Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/frontend/config/sidebar/docs.topics.ts | 2 +- .../config/sidebar/reference.topics.ts | 18 ++ .../content/docs/app-host/with-terminal.mdx | 184 ++++++++++++++++++ .../content/docs/app-host/withterminal.mdx | 171 ---------------- .../cli/commands/aspire-terminal-attach.mdx | 98 ++++++++++ .../cli/commands/aspire-terminal-ps.mdx | 97 +++++++++ .../cli/commands/aspire-terminal.mdx | 62 ++++++ .../content/docs/whats-new/aspire-13-5.mdx | 2 +- 8 files changed, 461 insertions(+), 173 deletions(-) create mode 100644 src/frontend/src/content/docs/app-host/with-terminal.mdx delete mode 100644 src/frontend/src/content/docs/app-host/withterminal.mdx create mode 100644 src/frontend/src/content/docs/reference/cli/commands/aspire-terminal-attach.mdx create mode 100644 src/frontend/src/content/docs/reference/cli/commands/aspire-terminal-ps.mdx create mode 100644 src/frontend/src/content/docs/reference/cli/commands/aspire-terminal.mdx diff --git a/src/frontend/config/sidebar/docs.topics.ts b/src/frontend/config/sidebar/docs.topics.ts index 93a125caf..cf23dff1c 100644 --- a/src/frontend/config/sidebar/docs.topics.ts +++ b/src/frontend/config/sidebar/docs.topics.ts @@ -942,7 +942,7 @@ export const docsTopics: StarlightSidebarTopicsUserConfig = { }, { label: 'Interactive terminals', - slug: 'app-host/withterminal', + slug: 'app-host/with-terminal', }, ], }, diff --git a/src/frontend/config/sidebar/reference.topics.ts b/src/frontend/config/sidebar/reference.topics.ts index 501411893..42380fa31 100644 --- a/src/frontend/config/sidebar/reference.topics.ts +++ b/src/frontend/config/sidebar/reference.topics.ts @@ -538,6 +538,24 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = { slug: 'reference/cli/commands/aspire-start', }, { label: 'aspire stop', slug: 'reference/cli/commands/aspire-stop' }, + { + label: 'aspire terminal', + collapsed: true, + items: [ + { + label: 'aspire terminal', + slug: 'reference/cli/commands/aspire-terminal', + }, + { + label: 'aspire terminal attach', + slug: 'reference/cli/commands/aspire-terminal-attach', + }, + { + label: 'aspire terminal ps', + slug: 'reference/cli/commands/aspire-terminal-ps', + }, + ], + }, { label: 'aspire update', slug: 'reference/cli/commands/aspire-update', diff --git a/src/frontend/src/content/docs/app-host/with-terminal.mdx b/src/frontend/src/content/docs/app-host/with-terminal.mdx new file mode 100644 index 000000000..a9fd4a317 --- /dev/null +++ b/src/frontend/src/content/docs/app-host/with-terminal.mdx @@ -0,0 +1,184 @@ +--- +title: Test TUI and shell apps with WithTerminal +seoTitle: Test terminal apps in your Aspire AppHost with WithTerminal +description: Use WithTerminal to expose an interactive terminal for a resource in your Aspire app model, then attach to it from the dashboard or the aspire terminal CLI to drive TUI and shell-based experiences. +--- + +import { Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +If you have a terminal user interface (TUI) application or a shell-based experience that you want to exercise while it runs under Aspire, add `WithTerminal(...)` to the resource. Aspire then exposes an interactive terminal session that you can attach to from the [Aspire dashboard](/dashboard/overview/) or from the [`aspire terminal`](/reference/cli/commands/aspire-terminal/) CLI command. + + + + +```csharp title="AppHost.cs" +#pragma warning disable ASPIRETERMINAL001 +var builder = DistributedApplication.CreateBuilder(args); + +var agent = builder.AddExecutable("agent", "my-agent", ".") + .WithTerminal(); + +builder.Build().Run(); +``` + + + + +```typescript title="apphost.ts" +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const agent = await builder.addExecutable("agent", "my-agent", ".") + .withTerminal(); + +await builder.build().run(); +``` + + + + +Once the app is running, open the resource's terminal page in the dashboard—or run `aspire terminal attach agent`—to interact with the process just as you would in a local shell. + + + +## When to use WithTerminal + +Reach for `WithTerminal` when a resource is interactive rather than a plain background service: + +- A **TUI application**—for example, an agent, a diagnostics console, or a curses-style tool—that draws a full-screen interface you want to see and drive. +- A **shell-based experience** where you want an interactive prompt inside a container or executable while it runs as part of your app model. +- Any resource you want to **poke at live** during development without leaving the Aspire dashboard or CLI. + +## The debugger is not attached automatically + +When you apply `WithTerminal`, Aspire runs the resource as a plain process and **does not automatically attach the debugger**. If you need to debug the resource, attach the debugger manually to the running process from your IDE. + + + +## Attach from multiple places at once + +Terminal sessions support multiple simultaneous viewers. You can open **two browser tabs pointing at the same terminal**—or a browser tab and the CLI together—and both stay responsive: input and output are mirrored to every attached peer. + +One peer holds the **primary** role and drives the terminal's dimensions, while the others attach as **viewers**. From the CLI you can join as a passive viewer with `aspire terminal attach --viewer`, and take control later with the `Ctrl+B T` hotkey. + +## Configure the terminal + +The terminal session is described by a set of options with sensible defaults: + +| Option | Default | Description | +| ------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Columns` | `120` | The initial number of columns for the terminal grid. | +| `Rows` | `30` | The initial number of rows for the terminal grid. | +| `Shell` | `null` | The shell to launch for the session. When `null`, the default is used: for containers this is typically `/bin/sh`; for executables the process itself is the terminal program. | +| `ShowTerminalHost` | `false` | Whether the hidden per-replica terminal host resources appear in the dashboard and CLI resource lists. Set to `true` to diagnose terminal-host startup or connectivity issues. | + +In C#, pass a callback to override any of these options: + + + + +```csharp title="AppHost.cs" +#pragma warning disable ASPIRETERMINAL001 +var builder = DistributedApplication.CreateBuilder(args); + +var agent = builder.AddExecutable("agent", "my-agent", ".") + .WithTerminal(options => + { + options.Columns = 200; + options.Rows = 50; + }); + +builder.Build().Run(); +``` + + + + +```typescript title="apphost.ts" +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const agent = await builder.addExecutable("agent", "my-agent", ".") + .withTerminal(); + +await builder.build().run(); +``` + + + + + + +## Terminals and replicas + +Each replica of a resource gets its own independent terminal session. Aspire creates one terminal host per parent replica, so requesting three replicas yields three separate terminals. The order of `WithReplicas` and `WithTerminal` does not matter—the final replica count is always honored: + + + + +```csharp title="AppHost.cs" +#pragma warning disable ASPIRETERMINAL001 +var builder = DistributedApplication.CreateBuilder(args); + +// Three replicas, each with its own interactive terminal. +var agent = builder.AddExecutable("agent", "my-agent", ".") + .WithReplicas(3) + .WithTerminal(); + +builder.Build().Run(); +``` + + + + +```typescript title="apphost.ts" +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const agent = await builder.addExecutable("agent", "my-agent", ".") + .withReplicas(3) + .withTerminal(); + +await builder.build().run(); +``` + + + + +When a resource has more than one replica, choose which one to attach to with `aspire terminal attach --replica ` (indices are 0-based), or pick interactively when prompted. + + + +## View terminals in the dashboard + +When a resource has `WithTerminal` applied, its **Console Logs** tab in the [Aspire dashboard](/dashboard/overview/) is replaced by a live terminal session. You can drive the running process directly in the browser—type commands, scroll the scrollback buffer, and switch between replicas—without leaving the dashboard. Each replica appears as its own entry (for example, `agent-r0`, `agent-r1`, `agent-r2`) with an independent session. + +## Work with terminals from the CLI + +The `aspire terminal` command group lets you list and attach to terminal sessions from your shell. Because `WithTerminal` is experimental, these commands are hidden behind a feature flag. Enable them with: + +```bash title="Enable the aspire terminal commands" +aspire config set features.terminalCommandsEnabled true +``` + +Then: + +- [`aspire terminal ps`](/reference/cli/commands/aspire-terminal-ps/) lists every terminal-enabled resource in the running AppHost, with grid size, attached-peer count, and per-replica health. +- [`aspire terminal attach`](/reference/cli/commands/aspire-terminal-attach/) attaches your local terminal to a resource's interactive PTY session. + +## See also + +- [aspire terminal command](/reference/cli/commands/aspire-terminal/) +- [Executable resources](/app-host/executable-resources/) +- [Aspire dashboard overview](/dashboard/overview/) diff --git a/src/frontend/src/content/docs/app-host/withterminal.mdx b/src/frontend/src/content/docs/app-host/withterminal.mdx deleted file mode 100644 index 4bd76e3a6..000000000 --- a/src/frontend/src/content/docs/app-host/withterminal.mdx +++ /dev/null @@ -1,171 +0,0 @@ ---- -title: Interactive terminal sessions -description: Attach an interactive terminal to any Aspire resource using WithTerminal — access live PTY sessions from the dashboard or the `aspire terminal` CLI commands. ---- - -import { Tabs, TabItem } from '@astrojs/starlight/components'; - -:::danger[Experimental feature] -`WithTerminal()` is an experimental API. Reference the `ASPIRETERMINAL001` diagnostic ID to opt in: - -```csharp -#pragma warning disable ASPIRETERMINAL001 -builder.AddExecutable("repl", "my-repl", ".") - .WithTerminal(); -``` -::: - -The `WithTerminal()` extension method attaches an interactive pseudo-terminal (PTY) to any Aspire resource. When a resource is configured with `WithTerminal()`, the Aspire Dashboard replaces the standard console log view with a live terminal session, and the `aspire terminal` CLI command lets you connect directly from your local shell. - -## Enable interactive terminals - -Call `WithTerminal()` on any executable or project resource in your AppHost: - - - -```csharp title="AppHost.cs" -#pragma warning disable ASPIRETERMINAL001 -var builder = DistributedApplication.CreateBuilder(args); - -var repl = builder.AddExecutable("repl", "my-repl", ".") - .WithTerminal(); - -builder.Build().Run(); -``` - - -```typescript title="apphost.mts" -import { createBuilder } from './.aspire/modules/aspire.mjs'; - -const builder = await createBuilder(); - -const repl = await builder - .addNodeApp("guessing-game", "./guessing-game", "game.mjs") - .withTerminal(); - -await builder.build().run(); -``` - - - -## Configure terminal dimensions - -Use the optional `configure` callback to set the initial column and row count for the terminal window: - - - -```csharp title="AppHost.cs" -#pragma warning disable ASPIRETERMINAL001 -var builder = DistributedApplication.CreateBuilder(args); - -var repl = builder.AddExecutable("repl", "my-repl", ".") - .WithTerminal(options => - { - options.Columns = 100; - options.Rows = 28; - }); -builder.Build().Run(); -``` - - - -```typescript title="apphost.mts" -import { createBuilder } from './.aspire/modules/aspire.mjs'; - -const builder = await createBuilder(); - -const repl = await builder - .addNodeApp("guessing-game", "./guessing-game", "game.mjs") - .withTerminal(); - -await builder.build().run(); -``` - - - - -TypeScript AppHost currently supports only the parameterless `withTerminal()` call. Support for configuring dimensions in TypeScript is tracked by [microsoft/aspire#18105](https://github.com/microsoft/aspire/issues/18105). - -The default dimensions are **120 columns × 30 rows**. The dashboard terminal panel auto-fits to the browser window, so these values primarily affect the PTY allocation on the host side. - -## Use with replicated resources - -Use `WithTerminal()` with `WithReplicas()` to give each replica its own interactive terminal session: - - - -```csharp title="AppHost.cs" -#pragma warning disable ASPIRETERMINAL001 -var builder = DistributedApplication.CreateBuilder(args); - -// Three replicas, each with its own interactive terminal. -var agent = builder.AddExecutable("agent", "my-agent", ".") - .WithReplicas(3) - .WithTerminal(); - -builder.Build().Run(); -``` - - -```typescript title="apphost.mts" -import { createBuilder } from './.aspire/modules/aspire.mjs'; - -const builder = await createBuilder(); - -const agent = await builder - .addNodeApp("agent", "./agent", "index.mjs"); -await agent.withReplicas(3); -await agent.withTerminal(); - -await builder.build().run(); -``` - - - -Each replica gets its own independent terminal session and appears as a separate entry in the dashboard resource list (for example, `agent-r0`, `agent-r1`, `agent-r2`). - -## Access terminals from the dashboard - -When a resource has `WithTerminal()` applied, the **Console Logs** tab for that resource in the Aspire Dashboard is replaced by a live terminal session. You can interact with the running process directly in the browser — type commands, scroll the scrollback buffer, and switch between replicas using the replica selector. - -## Access terminals from the CLI - -The `aspire terminal` command lets you attach to or inspect terminal sessions from your local shell. - -### List terminal-enabled resources - -```bash -aspire terminal ps -``` - -This displays a table of every `WithTerminal()`-enabled resource in the connected AppHost, including the current terminal dimensions, the number of attached viewers, and per-replica health status. - -To emit structured JSON output for scripting: - -```bash -aspire terminal ps --format json -``` - -### Attach to a terminal session - -```bash -aspire terminal attach -``` - -If the resource has a single replica, the CLI attaches immediately. For multi-replica resources, pass `--replica` with the 0-based index: - -```bash -aspire terminal attach agent --replica 1 -``` - -To connect as a secondary viewer — receiving output without driving the session dimensions — add `--viewer`: - -```bash -aspire terminal attach agent --viewer -``` - -## See also - -- [Executable resources](/app-host/executable-resources/) -- [Resource lifetimes](/app-host/resource-lifetimes/) -- [Aspire CLI reference](/reference/cli/overview/) diff --git a/src/frontend/src/content/docs/reference/cli/commands/aspire-terminal-attach.mdx b/src/frontend/src/content/docs/reference/cli/commands/aspire-terminal-attach.mdx new file mode 100644 index 000000000..65d5b0ef5 --- /dev/null +++ b/src/frontend/src/content/docs/reference/cli/commands/aspire-terminal-attach.mdx @@ -0,0 +1,98 @@ +--- +title: aspire terminal attach command +seoTitle: aspire terminal attach command reference for the Aspire CLI +description: Learn about the aspire terminal attach command, which connects your local terminal to a resource's interactive PTY session exposed with WithTerminal. +sidebar: + badge: Preview +--- + +import Include from '@components/Include.astro'; + +## Name + +`aspire terminal attach` - Attach the local terminal to an interactive PTY session for a resource. + +## Synopsis + +```bash title="Aspire CLI" +aspire terminal attach [options] +``` + +## Description + +The `aspire terminal attach` command connects your local terminal to the interactive pseudo-terminal (PTY) session of a resource that was registered with [`WithTerminal()`](/app-host/with-terminal/). Once attached, you interact with the resource's terminal—drive its TUI, type into its shell—as if it were running directly in your console. + +The command discovers the running AppHost, verifies it supports the `terminals.v1` capability, looks up the resource, and connects to one of its terminal replicas. When the resource has a single replica, that replica is selected automatically. When it has more than one, you're prompted to choose one—or you can pass `--replica`. + +Multiple peers can attach to the same session at once. By default `attach` takes the **primary** role and drives the terminal's dimensions. Pass `--viewer` to join as a passive **viewer** instead—useful when another peer, such as the dashboard, is currently driving the session. + +While attached, the following hotkeys are available: + +| Hotkey | Action | +| ---------- | -------------------------------------------- | +| `Ctrl+B D` | Detach from the session and exit cleanly. | +| `Ctrl+B T` | Take control (request the primary role). | + +If the selected replica has already exited, the command attaches to the historical output buffer; no live input is sent. + +Because `WithTerminal` is experimental, this command is hidden behind a feature flag. Enable it with `aspire config set features.terminalCommandsEnabled true`. + +## Arguments + +The following arguments are available: + +- **``** + + The name of the resource to attach a terminal to. The resource must have been registered with `.WithTerminal()`. + +## Options + +The following options are available: + +- + +- **`-r, --replica `** + + The 0-based replica index to attach to. Required when the resource has more than one replica and the CLI is not running interactively. + +- **`--viewer`** + + Connect as a viewer (secondary) instead of taking primary control. Viewers see the terminal output but do not drive its dimensions. Useful when another peer (for example, the dashboard) is currently driving the session. Take control later with `Ctrl+B T`. + +- + +- + +- + +- + +- + +- + +## Examples + +- Attach to a resource's terminal: + + ```bash title="Aspire CLI" + aspire terminal attach agent + ``` + +- Attach to a specific replica of a multi-replica resource: + + ```bash title="Aspire CLI" + aspire terminal attach agent --replica 1 + ``` + +- Join an existing session as a passive viewer: + + ```bash title="Aspire CLI" + aspire terminal attach agent --viewer + ``` + +## See also + +- [Test TUI and shell apps with WithTerminal](/app-host/with-terminal/) +- [aspire terminal command](../aspire-terminal/) +- [aspire terminal ps command](../aspire-terminal-ps/) diff --git a/src/frontend/src/content/docs/reference/cli/commands/aspire-terminal-ps.mdx b/src/frontend/src/content/docs/reference/cli/commands/aspire-terminal-ps.mdx new file mode 100644 index 000000000..da3d28c7b --- /dev/null +++ b/src/frontend/src/content/docs/reference/cli/commands/aspire-terminal-ps.mdx @@ -0,0 +1,97 @@ +--- +title: aspire terminal ps command +seoTitle: aspire terminal ps command reference for the Aspire CLI +description: Learn about the aspire terminal ps command, which lists every WithTerminal-enabled resource in the connected AppHost with grid size, attached peers, and per-replica health. +sidebar: + badge: Preview +--- + +import Include from '@components/Include.astro'; + +## Name + +`aspire terminal ps` - List interactive terminal sessions in the connected AppHost. + +## Synopsis + +```bash title="Aspire CLI" +aspire terminal ps [options] +``` + +## Description + +The `aspire terminal ps` command lists every resource in the connected AppHost that was registered with [`WithTerminal()`](/app-host/with-terminal/). For each terminal it reports the current grid size, the number of attached peers, and per-replica health so you can see what's available before running [`aspire terminal attach`](/reference/cli/commands/aspire-terminal-attach/). + +Resources whose terminal host isn't reachable are still listed with a status that indicates they're unavailable, rather than being silently dropped. + +Because `WithTerminal` is experimental, this command is hidden behind a feature flag. Enable it with `aspire config set features.terminalCommandsEnabled true`. The connected AppHost must advertise the `terminals.v1` capability (Aspire.Hosting 13.4 or later). + +The default output is a human-readable table with the following columns: + +| Column | Description | +| ---------- | --------------------------------------------------------------------------- | +| `Resource` | The display name of the terminal-enabled resource. | +| `Replica` | The 0-based replica index (one row per replica). | +| `Status` | Whether the replica is `alive`, `exited ()`, or `host unreachable`. | +| `Size` | The current terminal grid size (`columns`x`rows`). | +| `Peers` | The number of peers currently attached to the replica. | +| `Restarts` | The number of times the replica's terminal host has restarted. | + +## Options + +The following options are available: + +- + +- **`--format `** + + Output format. `text` (default) renders a table; `json` emits structured output for scripting. The JSON output is an array of terminal entries with `resourceName`, `displayName`, `configuredColumns`, `configuredRows`, `isHostReachable`, and a `replicas` array. Each replica includes `replicaIndex`, `isAlive`, `exitCode`, `producerConnected`, `restartCount`, `currentColumns`, `currentRows`, and `attachedPeerCount`. When `--verbose` is set, each replica also includes a `peers` array of `{ peerId, displayName }`. + +- **`-v, --verbose`** + + Include per-peer details for every attached viewer (peer id and display name). + +- + +- + +- + +- + +- + +- + +## Examples + +- List terminal sessions in the running AppHost: + + ```bash title="Aspire CLI" + aspire terminal ps + ``` + + Example output: + + ```text title="Output" + Resource Replica Status Size Peers Restarts + agent 0 alive 120x30 1 0 + ``` + +- Include per-peer details: + + ```bash title="Aspire CLI" + aspire terminal ps --verbose + ``` + +- Emit JSON for scripting and pipe to `jq`: + + ```bash title="Aspire CLI" + aspire terminal ps --format json | jq -c '.[] | { resourceName, isHostReachable }' + ``` + +## See also + +- [Test TUI and shell apps with WithTerminal](/app-host/with-terminal/) +- [aspire terminal command](/reference/cli/commands/aspire-terminal/) +- [aspire terminal attach command](/reference/cli/commands/aspire-terminal-attach/) diff --git a/src/frontend/src/content/docs/reference/cli/commands/aspire-terminal.mdx b/src/frontend/src/content/docs/reference/cli/commands/aspire-terminal.mdx new file mode 100644 index 000000000..170c21aa0 --- /dev/null +++ b/src/frontend/src/content/docs/reference/cli/commands/aspire-terminal.mdx @@ -0,0 +1,62 @@ +--- +title: aspire terminal command +seoTitle: aspire terminal command reference for the Aspire CLI +description: Learn about the aspire terminal command, which lists and attaches to interactive terminal sessions for resources registered with WithTerminal. +sidebar: + badge: Preview +--- + +import Include from '@components/Include.astro'; + +## Name + +`aspire terminal` - Manage interactive terminal sessions for resources. + +## Synopsis + +```bash title="Aspire CLI" +aspire terminal [command] [options] +``` + +## Description + +The `aspire terminal` command provides subcommands for working with interactive terminal sessions exposed by resources that were registered with [`WithTerminal()`](/app-host/with-terminal/) in the AppHost. You can list which resources have a terminal and attach your local terminal to a running session. + +Because `WithTerminal` is experimental, the `aspire terminal` command group is hidden behind a feature flag. Enable it before use: + +```bash title="Enable the aspire terminal commands" +aspire config set features.terminalCommandsEnabled true +``` + +The connected AppHost must advertise the `terminals.v1` capability (Aspire.Hosting 13.4 or later). Against an older AppHost the subcommands report that terminals are not supported. + +## Options + +The following options are available: + +- + +- + +- + +- + +- + +- + +## Commands + +The following commands are available: + +| Command | Function | +| ---------------------------------------------------------- | -------------------------------------------------------------- | +| [`aspire terminal attach`](../aspire-terminal-attach/) | Attach the local terminal to a resource's interactive session. | +| [`aspire terminal ps`](../aspire-terminal-ps/) | List interactive terminal sessions in the connected AppHost. | + +## See also + +- [Test TUI and shell apps with WithTerminal](/app-host/with-terminal/) +- [aspire terminal attach command](../aspire-terminal-attach/) +- [aspire terminal ps command](../aspire-terminal-ps/) diff --git a/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx b/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx index 39593ea6b..1c30c8e1e 100644 --- a/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx +++ b/src/frontend/src/content/docs/whats-new/aspire-13-5.mdx @@ -107,7 +107,7 @@ Or install the CLI from scratch: AppHost authors can now call `WithTerminal()` on a resource to enable interactive terminal sessions. The Dashboard and CLI can attach to and detach from the session at will, enabling live use of REPLs, shells, and other terminal programs running as Aspire resources. This is particularly powerful for resource orchestration workflows where you need to interact with services in real-time. - For details, see [WithTerminal() interactive terminal sessions](/app-host/withterminal/). + For details, see [WithTerminal() interactive terminal sessions](/app-host/with-terminal/). ## 🌐 IInteractionService available across polyglot AppHosts From 55c420e7a028b07730efcc8e368d648bc9a1301a Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 6 Jul 2026 10:53:36 -0500 Subject: [PATCH 23/32] Document AI agent skill-usage telemetry (#1229) * Document AI agent skill usage telemetry in CLI telemetry reference Extend the Microsoft-collected CLI telemetry page to cover the agent telemetry hooks installed by 'aspire agent init': the three event types (skill_invocation, tool_invocation, reference_file_read), the exact low-cardinality fields recorded, the privacy guarantees (only Aspire-owned skill/tool names and skill-relative reference paths; never absolute paths, repo or user names, file contents, or tool arguments), and the ASPIRE_CLI_AGENT_TELEMETRY_OPTOUT / --no-telemetry-hooks opt-out paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Simplify CLI telemetry docs to a single opt-out Remove the AI-only opt-out section (ASPIRE_CLI_AGENT_TELEMETRY_OPTOUT) and the 'aspire agent init --no-telemetry-hooks' note. The single ASPIRE_CLI_TELEMETRY_OPTOUT switch disables all CLI telemetry including AI agent skill usage. Soften the over-claimed opt-out re-check wording. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Alistair Matthews --------- Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Alistair Matthews --- .../cli/microsoft-collected-cli-telemetry.mdx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/frontend/src/content/docs/reference/cli/microsoft-collected-cli-telemetry.mdx b/src/frontend/src/content/docs/reference/cli/microsoft-collected-cli-telemetry.mdx index 00ee2525e..ef3c17590 100644 --- a/src/frontend/src/content/docs/reference/cli/microsoft-collected-cli-telemetry.mdx +++ b/src/frontend/src/content/docs/reference/cli/microsoft-collected-cli-telemetry.mdx @@ -11,6 +11,12 @@ The Aspire CLI collects usage telemetry to help Microsoft improve the product. T Aspire CLI telemetry is collected when using the CLI to run commands. Telemetry is gathered during normal CLI usage, unless you have [opted out](#how-to-opt-out) of telemetry collection. +### AI agent skill usage + +When you run `aspire agent init`, Aspire installs Aspire's AI skills and a `PostToolUse` hook into each detected agent client's user-level configuration (for example, GitHub Copilot CLI and Claude Code). The hook lets Aspire understand which of its AI skills and tools are actually used so the team can invest in the most valuable ones. + +After each agent tool use, the hook checks if an **Aspire** skill, Aspire MCP tool, or Aspire skill reference file was involved. If so, the hook inspects the event and forwards a low-cardinality usage event to the hidden `aspire agent telemetry` command. The command records the event through the same CLI telemetry pipeline. Tool uses that don't involve Aspire skills or tools are ignored. See [AI agent skill usage data](#ai-agent-skill-usage-data) for exactly what's recorded. + ## How to opt-out To opt-out of telemetry collection, set the `ASPIRE_CLI_TELEMETRY_OPTOUT` environment variable to `true`. This will disable telemetry collection for all CLI commands: @@ -50,6 +56,29 @@ Aspire CLI collects the following data: | 13.2 | Ensure .NET SDK install. | Includes .NET SDK version installed. | | 13.2 | CLI-related unhandled exceptions. | — | | 13.5 | Coding agent detection. | When the CLI is invoked from a known coding agent environment (such as GitHub Copilot, Claude, Cursor, or others), the detected agent name is recorded. Only the known agent name is sent — no environment variable values or other identifying data are included. This data point is omitted entirely when no known coding agent is detected. | +| 13.5 | AI agent skill usage. | Recorded only for Aspire's own skills and tools. See [AI agent skill usage data](#ai-agent-skill-usage-data). | + +### AI agent skill usage data + +The agent telemetry hook installed by `aspire agent init` reports a single event each time one of Aspire's own AI skills or tools is used. Three event types are reported: + +- `skill_invocation` — an Aspire skill was invoked, or its `SKILL.md` was read. +- `tool_invocation` — an Aspire MCP tool was called. +- `reference_file_read` — a reference file bundled alongside an Aspire skill was read. + +Each event records only a subset of the following low-cardinality, Aspire-owned values (depending on the event type): + +| Field | Description | +|-------|-------------| +| Event type | One of `skill_invocation`, `tool_invocation`, or `reference_file_read`. | +| Client name | The agent client that produced the event, for example `copilot-cli`, `claude-code`, or `vscode`. | +| Session ID | The agent session identifier (an opaque GUID), used to group events from one session. | +| Skill name | The Aspire skill name, matched against the fixed list of skills Aspire ships (for example `aspire-deployment`). | +| Tool name | The Aspire MCP tool name (for example `aspire-list_resources`). | +| File reference | The skill-relative path of a reference file, for example `aspire/references/deploy.md`. | +| Event timestamp | The UTC time the event occurred. | + +To protect your privacy, the hook only forwards an event when the skill or tool name matches the fixed set Aspire ships, and a file reference is reported only as the path **after** the skill folder. Absolute paths, repository names, user names, file contents, and tool arguments are never recorded. Before sending, the `aspire agent telemetry` command validates each value, discards anything it doesn't recognize, and honors the same `ASPIRE_CLI_TELEMETRY_OPTOUT` opt-out. ## See also From 2a666e113ed58e6c05bd10766249c2ac1eab2aae Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 10 Jul 2026 19:26:47 +0800 Subject: [PATCH 24/32] Document --skills and --skill-locations flags for init and new commands (#1348) Add documentation for the --skills and --skill-locations options on aspire init and aspire new commands. These flags were added in microsoft/aspire#18191 and microsoft/aspire#18192 to support non-interactive mode for controlling which agent skills are installed during initialization. --- .../reference/cli/commands/aspire-init.mdx | 20 +++++++++++++++++++ .../reference/cli/commands/aspire-new.mdx | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/frontend/src/content/docs/reference/cli/commands/aspire-init.mdx b/src/frontend/src/content/docs/reference/cli/commands/aspire-init.mdx index 7981425e4..995e6a287 100644 --- a/src/frontend/src/content/docs/reference/cli/commands/aspire-init.mdx +++ b/src/frontend/src/content/docs/reference/cli/commands/aspire-init.mdx @@ -102,6 +102,14 @@ The following options are available: The AppHost language to scaffold (`csharp`, `typescript`). +- **`--skill-locations `** + + Comma-separated list of skill locations to configure. Use `all` to configure all supported locations or `none` to skip skill location configuration. When not specified, the command prompts interactively. Useful for non-interactive mode in scripts and CI environments. For the list of available locations, see the [`aspire agent init` command reference](../aspire-agent-init/#options). + +- **`--skills `** + + Comma-separated list of skills to install. Use `all` to install all available skills or `none` to skip skill installation. When not specified, the command prompts interactively. Useful for non-interactive mode in scripts and CI environments. For the list of available skills, see the [`aspire agent init` command reference](../aspire-agent-init/#options). + - - @@ -133,3 +141,15 @@ The following options are available: ```bash title="Aspire CLI" aspire init --channel daily ``` + +- Initialize Aspire support non-interactively without installing any agent skills: + + ```bash title="Aspire CLI" + aspire init --non-interactive --skills none + ``` + +- Initialize Aspire support non-interactively, installing all skills in the standard location: + + ```bash title="Aspire CLI" + aspire init --non-interactive --language csharp --skill-locations standard --skills all + ``` diff --git a/src/frontend/src/content/docs/reference/cli/commands/aspire-new.mdx b/src/frontend/src/content/docs/reference/cli/commands/aspire-new.mdx index b8cd93487..3e5d69bcd 100644 --- a/src/frontend/src/content/docs/reference/cli/commands/aspire-new.mdx +++ b/src/frontend/src/content/docs/reference/cli/commands/aspire-new.mdx @@ -122,6 +122,14 @@ The following options are available: Suppress the prompt to initialize MCP server configuration for agent environments after project creation. +- **`--skill-locations `** + + Comma-separated list of skill locations to configure. Use `all` to configure all supported locations or `none` to skip skill location configuration. When not specified, the command prompts interactively. Useful for non-interactive mode in scripts and CI environments. For the list of available locations, see the [`aspire agent init` command reference](../aspire-agent-init/#options). + +- **`--skills `** + + Comma-separated list of skills to install. Use `all` to install all available skills or `none` to skip skill installation. When not specified, the command prompts interactively. Useful for non-interactive mode in scripts and CI environments. For the list of available skills, see the [`aspire agent init` command reference](../aspire-agent-init/#options). + - - @@ -179,6 +187,18 @@ The `aspire-py-starter` template accepts the following additional options: aspire new aspire-starter --channel daily ``` +- Create a new project non-interactively without installing any agent skills: + + ```bash title="Aspire CLI" + aspire new aspire-starter --name myapp --skills none --non-interactive + ``` + +- Create a new project non-interactively and install all skills in all supported locations: + + ```bash title="Aspire CLI" + aspire new aspire-starter --name myapp --skill-locations all --skills all --non-interactive + ``` + ## Troubleshooting ### Language support not found From e3f3ed0d2388879cd1b6dcf9f75c07d8b63bcc54 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 10 Jul 2026 19:48:09 +0800 Subject: [PATCH 25/32] Document PromptProgressAsync API and add TypeScript examples to interaction service (#1347) - Add PromptProgressAsync to the interaction service docs with C# and TypeScript examples - Add CommandProgressOptions section to custom resource commands page - Integrate TypeScript tabs throughout interaction service documentation - Update method table and context notes to include PromptProgressAsync Documents changes from microsoft/aspire#18493 --- .../extensibility/interaction-service.mdx | 590 ++++++++++++++++-- .../fundamentals/custom-resource-commands.mdx | 91 +++ 2 files changed, 612 insertions(+), 69 deletions(-) diff --git a/src/frontend/src/content/docs/extensibility/interaction-service.mdx b/src/frontend/src/content/docs/extensibility/interaction-service.mdx index 58b971a11..66b230947 100644 --- a/src/frontend/src/content/docs/extensibility/interaction-service.mdx +++ b/src/frontend/src/content/docs/extensibility/interaction-service.mdx @@ -1,10 +1,10 @@ --- -title: Interaction service (Preview) -seoTitle: Aspire interaction service (Preview) for AppHost authors +title: Interaction service +seoTitle: Aspire interaction service for AppHost authors description: Use the Aspire interaction service to prompt users for input, request confirmation, and display messages from AppHost extensions, integrations, and custom resources. --- -import { Aside, Steps } from '@astrojs/starlight/components'; +import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; import { Image } from 'astro:assets'; import messageDialog from '@assets/extensibility/interaction-service-message-dialog.png'; import messageBar from '@assets/extensibility/interaction-service-message-bar.png'; @@ -13,7 +13,7 @@ import multipleInput from '@assets/extensibility/interaction-service-multiple-in import multipleInputFilled from '@assets/extensibility/interaction-service-multiple-input-filled.png'; import multipleInputLogs from '@assets/extensibility/interaction-service-multiple-input-logs.png'; -The interaction service (`Aspire.Hosting.IInteractionService`) allows you to prompt users for input, request confirmation, and display messages. The interaction service works in two different contexts: +The interaction service (`Aspire.Hosting.IInteractionService`) allows you to prompt users for input, request confirmation, and display messages. It works in two different contexts: - **Aspire dashboard**: When running `aspire run` or launching the AppHost directly, interactions appear as dialogs and notifications in the dashboard UI. - **Aspire CLI**: When running `aspire publish` or `aspire deploy`, interactions are prompted through the command-line interface. @@ -22,19 +22,18 @@ This is useful for scenarios where you need to gather information from the user - ## The `IInteractionService` API -The `IInteractionService` interface is retrieved from the `DistributedApplication` dependency injection container. `IInteractionService` can be injected into types created from DI or resolved from `IServiceProvider`, which is usually available on a context argument passed to events. +The `IInteractionService` interface provides methods for prompting users. In C#, it's retrieved from the dependency injection container. In TypeScript, it's resolved from the command context's service provider. Always check if the service is available before use — if you attempt to call a method when it's not available, an exception is thrown. For example, the interaction service isn't available when a command is triggered from the CLI. -When you request `IInteractionService`, be sure to check if it's available for usage. If you attempt to use the interaction service when it's not available (`IInteractionService.IsAvailable` returns `false`), an exception is thrown. For example, the interaction service isn't available when a command is triggered from the CLI. + + -```csharp +```csharp title="AppHost.cs" var interactionService = serviceProvider.GetRequiredService(); if (interactionService.IsAvailable) { @@ -49,6 +48,28 @@ if (interactionService.IsAvailable) } ``` + + + +```typescript title="apphost.mts" +const interactionService = + await context.services().getInteractionService(); + +if (await interactionService.isAvailable()) { + const result = await interactionService.promptConfirmation( + "Delete confirmation", + "Are you sure you want to delete the data?", + ); + + if (!result.canceled) { + // Run your resource/command logic. + } +} +``` + + + + The interaction service has several methods that you use to interact with users or display messages. The behavior of these methods depends on the execution context: - **Dashboard context** (`aspire run` or direct AppHost launch): Interactions appear as modal dialogs, notifications, and form inputs in the [Aspire dashboard web interface](/dashboard/overview/). @@ -63,9 +84,10 @@ The following sections describe how to use these APIs effectively in both contex | `PromptConfirmationAsync` | Displays a confirmation dialog with options for the user to confirm or cancel an action. | Dashboard only | | `PromptInputAsync` | Prompts the user for a single input value, such as text or secret. | Dashboard, CLI | | `PromptInputsAsync` | Prompts the user for multiple input values in a single dialog (dashboard) or sequentially (CLI). | Dashboard, CLI | +| `PromptProgressAsync` | Displays a progress dialog with an indeterminate progress indicator during long-running operations. | Dashboard only | ## Where to use the interaction service @@ -134,7 +156,10 @@ Message display methods (`PromptMessageBoxAsync` and `PromptNotificationAsync`) Dialog messages provide important information that requires user attention. -The `IInteractionService.PromptMessageBoxAsync` method displays a message with customizable response options. +The `PromptMessageBoxAsync` method displays a message with customizable response options. + + + ```csharp title="AppHost.cs" var interactionService = services.GetRequiredService(); @@ -168,6 +193,33 @@ else } ``` + + + +```typescript title="apphost.mts" +const interactionService = + await context.services().getInteractionService(); + +const result = await interactionService.promptMessageBox( + "Apply migrations", + "The database schema is out of date.\nWould you like to apply pending migrations now?", + { + enableMessageMarkdown: true, + primaryButtonText: "Apply", + secondaryButtonText: "Skip", + showSecondaryButton: true, + }, +); + +if (!result.canceled) { + // User clicked "Apply" + // Run migrations. +} +``` + + + + **Dashboard view:** Aspire dashboard interface showing a message dialog with a title, message, and buttons. @@ -186,60 +238,65 @@ In the dashboard, notification messages appear stacked at the top, so you can sh The `PromptNotificationAsync` method displays informational messages with optional action links in the dashboard context. You don't have to await the result of a notification message if you don't need to. This is especially useful for notifications, since you might want to display a notification and continue without waiting for user to dismiss it. + + + ```csharp title="AppHost.cs" var interactionService = services.GetRequiredService(); -// Demonstrating various notification types with different intents -var tasks = new List -{ - interactionService.PromptNotificationAsync( - title: "Confirmation", - message: "Are you sure you want to proceed?", - options: new NotificationInteractionOptions - { - Intent = MessageIntent.Confirmation - }), - interactionService.PromptNotificationAsync( - title: "Success", - message: "Your operation completed successfully.", - options: new NotificationInteractionOptions - { - Intent = MessageIntent.Success, - LinkText = "View Details", - LinkUrl = "/" - }), - interactionService.PromptNotificationAsync( - title: "Warning", - message: "Your SSL certificate will expire soon.", - options: new NotificationInteractionOptions - { - Intent = MessageIntent.Warning, - LinkText = "Renew Certificate", - LinkUrl = "https://portal.azure.com/certificates" - }), - interactionService.PromptNotificationAsync( - title: "Information", - message: "There is an update available for your application.", - options: new NotificationInteractionOptions - { - Intent = MessageIntent.Information, - LinkText = "Update Now", - LinkUrl = "/" - }), - interactionService.PromptNotificationAsync( - title: "Error", - message: "An error occurred while processing your request.", - options: new NotificationInteractionOptions - { - Intent = MessageIntent.Error, - LinkText = "Troubleshoot", - LinkUrl = "/get-started/troubleshooting/" - }) -}; -await Task.WhenAll(tasks); +await interactionService.PromptNotificationAsync( + title: "Success", + message: "Your operation completed successfully.", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Success, + LinkText = "View Details", + LinkUrl = "/" + }); + +await interactionService.PromptNotificationAsync( + title: "Warning", + message: "Your SSL certificate will expire soon.", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Warning, + LinkText = "Renew Certificate", + LinkUrl = "https://portal.azure.com/certificates" + }); +``` + + + + +```typescript title="apphost.mts" +const interactionService = + await context.services().getInteractionService(); + +await interactionService.promptNotification( + "Success", + "Your operation completed successfully.", + { + intent: MessageIntent.Success, + linkText: "View Details", + linkUrl: "/", + }, +); + +await interactionService.promptNotification( + "Warning", + "Your SSL certificate will expire soon.", + { + intent: MessageIntent.Warning, + linkText: "Renew Certificate", + linkUrl: "https://portal.azure.com/certificates", + }, +); ``` -The previous example demonstrates several ways to use the notification API. Each approach displays different types of notifications, all of which are invoked in parallel and displayed in the dashboard around the same time. + + + +The previous example demonstrates how to use the notification API with different intents and optional action links. **Dashboard view:** @@ -257,6 +314,9 @@ Use the interaction service when you need the user to confirm an action before p For operations that can't be undone, such as deleting resources, always prompt for confirmation: + + + ```csharp title="AppHost.cs" var interactionService = services.GetRequiredService(); // Prompt for confirmation before resetting database @@ -278,6 +338,33 @@ if (resetConfirmation.Data) } ``` + + + +```typescript title="apphost.mts" +const interactionService = + await context.services().getInteractionService(); + +const resetConfirmation = await interactionService.promptConfirmation( + "Confirm Reset", + "Are you sure you want to reset the database? This action cannot be undone.", + { + intent: MessageIntent.Confirmation, + primaryButtonText: "Reset", + secondaryButtonText: "Cancel", + showSecondaryButton: true, + enableMessageMarkdown: true, + }, +); + +if (!resetConfirmation.canceled) { + // Perform the reset operation... +} +``` + + + + **Dashboard view:** Aspire dashboard interface showing a confirmation dialog with a title, message, and buttons for confirming or canceling the operation. @@ -288,7 +375,7 @@ The `PromptConfirmationAsync` method isn't available in CLI contexts. If you cal ## Prompt for user input -The interaction service API allows you to prompt users for input in various ways. You can collect single values or multiple values, with support for different input types including text, secrets, choices, booleans, and numbers. The presentation adapts automatically to the execution context. +The interaction service API allows you to prompt users for input in various ways. You can collect single values or multiple values, with support for different input types including text, secrets, choices, booleans, numbers, and files. The presentation adapts automatically to the execution context. | Type | Dashboard | CLI prompt | |--------------|---------------------------|-----------------------| @@ -297,6 +384,7 @@ The interaction service API allows you to prompt users for input in various ways | `Choice` | Dropdown box of options | Choice prompt | | `Boolean` | Checkbox | Boolean choice prompt | | `Number` | Number textbox | Text prompt | +| `File` | File picker dialog | File path prompt | Each `InteractionInput` has a `Name` and a `Label` property: @@ -313,6 +401,9 @@ It's possible to create wizard-like flows using the interaction service. By chai Consider the following example, which prompts the user for multiple input values: + + + ```csharp title="AppHost.cs" var interactionService = services.GetRequiredService(); var loggerService = services.GetRequiredService(); @@ -383,6 +474,64 @@ if (!appConfigurationInput.Canceled) } ``` + + + +```typescript title="apphost.mts" +const interactionService = + await context.services().getInteractionService(); + +const appName = await interactionService.createTextInput("AppName", { + label: "Application Name", + required: true, + placeholder: "my-app", +}); + +const environment = await interactionService.createChoiceInput( + "Environment", + { + choices: [ + { value: "dev", label: "Development" }, + { value: "staging", label: "Staging" }, + { value: "test", label: "Testing" }, + ], + options: { required: true }, + }, +); + +const instanceCount = await interactionService.createNumberInput( + "InstanceCount", + { + label: "Instance Count", + required: true, + placeholder: "1", + }, +); + +const enableMonitoring = await interactionService.createBooleanInput( + "EnableMonitoring", + { label: "Enable Monitoring" }, +); + +const result = await interactionService.promptInputs( + "Application Configuration", + "Configure your application deployment settings:", + [appName, environment, instanceCount, enableMonitoring], +); + +if (!(await result.canceled())) { + const name = await result.inputs().value("AppName"); + const env = await result.inputs().value("Environment"); + const count = await result.inputs().value("InstanceCount"); + const monitoring = await result.inputs().value("EnableMonitoring"); + + // Use the collected values as needed +} +``` + + + + **Dashboard view:** This renders on the dashboard as shown in the following image: @@ -438,13 +587,16 @@ Environment: The interaction service supports dynamic inputs so you can populate options based on earlier responses. This enables cascading dropdowns and other dependent prompts in both the dashboard and CLI experiences. + + + To configure a dynamic input, set the `InteractionInput.DynamicLoading` property to an instance of `InputLoadOptions`. `InputLoadOptions` has a range of properties: - **LoadCallback**: A callback function that populates or refreshes the input options. This function is invoked when the input needs to be loaded or refreshed. You can access the current values of other inputs through the `context.AllInputs` dictionary. - **DependsOnInputs**: A list of input names that the dynamic input depends on. When any of these inputs change, the `LoadCallback` is triggered to refresh the options for the dynamic input. If no dependencies are specified then the callback is triggered when the interaction starts. - **AlwaysLoadOnStart**: If set to `true`, the `LoadCallback` is always called when the interaction starts, even if the dynamic input has dependencies. -```csharp +```csharp title="AppHost.cs" var inputs = new List { new() @@ -491,15 +643,66 @@ if (!result.Canceled) } ``` -In the preceding example, the `DatabaseVersion` input is dynamically populated based on the selected `DatabaseType`. + + + +Use `withDynamicLoading` to populate options based on other inputs. The callback runs server-side, receives a `loadContext`, and updates the loading input through `loadContext.input()`: + +```typescript title="apphost.mts" +const interactionService = + await context.services().getInteractionService(); + +const dbType = await interactionService.createChoiceInput("DatabaseType", { + label: "Database Type", + choices: [ + { value: "postgres", label: "PostgreSQL" }, + { value: "mysql", label: "MySQL" }, + { value: "sqlserver", label: "SQL Server" }, + ], + options: { required: true }, +}); + +const dbVersion = await ( + await interactionService.createChoiceInput("DatabaseVersion", { + label: "Database Version", + options: { required: true }, + }) +).withDynamicLoading( + async (loadContext) => { + const selectedType = await loadContext.inputs().value("DatabaseType"); + const versions = await getAvailableVersions(selectedType); + await loadContext.input().setChoiceOptions(versions); + }, + { dependsOnInputs: ["DatabaseType"] }, +); + +const result = await interactionService.promptInputs( + "Database configuration", + "Select a database type and version", + [dbType, dbVersion], +); + +if (!(await result.canceled())) { + const version = await result.inputs().value("DatabaseVersion"); + // Use version-specific configuration... +} +``` + + + + +In the preceding example, the database version input is dynamically populated based on the selected database type. #### Input validation -Basic input validation is available by configuring `InteractionInput`. It provides options for requiring a value, or the maximum text length of `Text` or `SecretText` fields. +Basic input validation is available by configuring inputs with `Required` and `MaxLength` properties. For complex scenarios, you can provide a custom validation callback that runs server-side when the user submits the dialog. + + + -For complex scenarios, you can provide custom validation logic using the `InputsDialogInteractionOptions.ValidationCallback` property: +Use the `InputsDialogInteractionOptions.ValidationCallback` property: -```csharp +```csharp title="AppHost.cs" // Multiple inputs with custom validation var databaseInputs = new List { @@ -572,8 +775,156 @@ if (!dbResult.Canceled && dbResult.Data != null) } ``` + + + +Supply a `validationCallback` on the options object. The callback reads submitted values through `validationContext.inputs().value(name)` and registers per-field errors through `validationContext.addValidationError(field, message)`: + +```typescript title="apphost.mts" +const interactionService = + await context.services().getInteractionService(); + +const password = await interactionService.createSecretInput("password", { + label: "Password", + required: true, + placeholder: "Enter a strong password", +}); + +const confirmPassword = await interactionService.createSecretInput( + "confirmPassword", + { + label: "Confirm password", + required: true, + placeholder: "Confirm your password", + }, +); + +const result = await interactionService.promptInputs( + "Database configuration", + "Configure your PostgreSQL database connection:", + [password, confirmPassword], + { + validationCallback: async (validationContext) => { + const pw = await validationContext.inputs().value("password"); + const cpw = await validationContext + .inputs() + .value("confirmPassword"); + + if (pw && pw.length < 8) { + await validationContext.addValidationError( + "password", + "Password must be at least 8 characters long", + ); + } + if (pw !== cpw) { + await validationContext.addValidationError( + "confirmPassword", + "Passwords do not match", + ); + } + }, + }, +); + +if (!(await result.canceled())) { + // Use the validated configuration +} +``` + + + + Prompting the user for a password and confirming they match is referred to as "dual independent verification" input. This approach is common in scenarios where you want to ensure the user enters the same password twice to avoid typos or mismatches. +### Prompt for file upload + +The `File` input type enables users to select and upload files through the native OS/browser file picker in the dashboard, or by providing a file path in the CLI. Files are uploaded to the AppHost and stored on disk, with metadata available through the `InteractionFile` class. + + + + +```csharp title="AppHost.cs" +var interactionService = services.GetRequiredService(); + +var fileInput = new InteractionInput +{ + Name = "ConfigFile", + InputType = InputType.File, + Label = "Configuration file", + Placeholder = "Select a file to import", + Required = true, + FileFilter = ".json,.yaml,.yml", + MaxFileSize = 10 * 1024 * 1024 // 10 MB +}; + +var result = await interactionService.PromptInputAsync( + "Import configuration", + "Select a configuration file to import.", + fileInput, + cancellationToken: cancellationToken); + +if (result.Canceled) +{ + return CommandResults.Failure("Canceled"); +} + +var file = result.Data.Files?[0]; +if (file is null) +{ + return CommandResults.Failure("No file uploaded"); +} + +// Read all file content as bytes +var content = await file.ReadAllBytesAsync(cancellationToken); + +// Or open as a stream for large files +await using var stream = file.OpenRead(); +``` + + + + +```typescript title="apphost.mts" +const interactionService = + await context.services().getInteractionService(); + +const fileInput = await interactionService.createFileInput("ConfigFile", { + label: "Configuration file", + description: "Select a file to import", + required: true, + maxFileSize: 10 * 1024 * 1024, // 10 MB +}); + +const result = await interactionService.promptInput( + "Import configuration", + "Select a configuration file to import.", + fileInput, + { primaryButtonText: "Upload" }, +); + +if (result.canceled) { + return { success: false, message: "Canceled" }; +} + +const files = result.input?.files ?? []; +if (files.length === 0) { + return { success: false, message: "No file uploaded" }; +} + +const file = files[0]; +// file.name - original filename +// file.filePath - path to uploaded file on disk +``` + + + + +The `InteractionInput` for file uploads supports these additional properties: + +- **`FileFilter`**: Restricts selectable files using the same format as the HTML `accept` attribute (for example, `".pem,.pfx,.crt"` or `"image/*"`). +- **`MaxFileSize`**: Maximum file size in bytes. Files exceeding this limit are rejected with a validation error. +- **`AllowMultipleFiles`**: When `true`, the user can select more than one file. + ### Best practices for user input When prompting for user input, consider these best practices: @@ -589,6 +940,106 @@ When prompting for user input, consider these best practices: +## Display progress + +The interaction service can display a progress dialog during long-running operations. The progress dialog shows an indeterminate progress indicator with an optional title, message, and cancel button. This is useful for scenarios such as resource deployment, data processing, or Azure provisioning where the user should wait for an operation to complete. + +:::caution[Experimental API] +`PromptProgressAsync` and its associated types (`ProgressInteractionOptions`, `ProgressContext`) are experimental and require suppressing the [`ASPIREINTERACTION001`](/diagnostics/aspireinteraction001/) diagnostic. The API shape may change before it stabilizes. +::: + +:::note +The `PromptProgressAsync` method is only available in dashboard contexts. It throws an exception if called during `aspire publish` or `aspire deploy` operations. +::: + +### Display progress with a work callback + +The simplest approach is to provide a `Work` callback via `ProgressInteractionOptions`. The progress dialog opens when `PromptProgressAsync` is called and closes automatically when the callback completes: + + + + +```csharp title="AppHost.cs" +#pragma warning disable ASPIREINTERACTION001 + +var interactionService = services.GetRequiredService(); +var result = await interactionService.PromptProgressAsync( + "Please wait while resources are being downloaded...", + "Downloading resources", + new ProgressInteractionOptions + { + PrimaryButtonText = "Cancel", + Work = async progress => + { + await DownloadResourcesAsync(progress.CancellationToken); + } + }, + cancellationToken); + +if (result.Canceled) +{ + // User clicked the cancel button. +} +``` + + + + +```typescript title="apphost.mts" +const interactionService = + await context.services().getInteractionService(); + +const result = await interactionService.promptProgress( + "Please wait while resources are being downloaded...", + { + title: "Downloading resources", + options: { + primaryButtonText: "Cancel", + work: async () => { + await downloadResources(); + }, + }, + }, +); + +if (result.canceled) { + // User clicked the cancel button. +} +``` + + + + +When a `Work` callback is provided: + +- The dialog stays open while the callback runs. +- If a cancel button is shown (via `PrimaryButtonText`), clicking it triggers `ProgressContext.CancellationToken`, which you can observe inside the callback. +- The method returns an `InteractionResult` with `Canceled = true` if the user clicked cancel. + +### Display progress without a work callback + +If you don't provide a `Work` callback, the progress dialog stays open until the `cancellationToken` passed to `PromptProgressAsync` is canceled, or the user clicks the cancel button (if `PrimaryButtonText` is set). This pattern is useful when you manage the work yourself and want to close the dialog at a specific point: + +```csharp title="AppHost.cs" +#pragma warning disable ASPIREINTERACTION001 + +var interactionService = services.GetRequiredService(); +using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + +var progressTask = interactionService.PromptProgressAsync( + "Processing data...", + "Processing", + cancellationToken: cts.Token); + +// Do work, then close the dialog by canceling the token. +await DoWorkAsync(cancellationToken); +cts.Cancel(); + +await progressTask; +``` + +Because no `PrimaryButtonText` is set, the dialog has no cancel button and the user cannot dismiss it. + ## Interaction contexts The interaction service behaves differently depending on how your Aspire solution is launched: @@ -599,6 +1050,7 @@ When you run your application using `aspire run` or by directly launching the Ap - **Modal dialogs**: Message boxes and input prompts appear as overlay dialogs that require user interaction. - **Notification messages**: Informational messages appear as dismissible banners at the top of the dashboard. +- **Progress dialogs**: Long-running operations display a non-dismissable progress indicator with an optional cancel button. - **Rich UI**: Full support for interactive form elements, validation, and visual feedback. - **All methods available**: All interaction service methods are supported in dashboard contexts. @@ -608,10 +1060,10 @@ When you run `aspire publish` or `aspire deploy`, interactions are prompted thro - **Text prompts**: Only input prompts (`PromptInputAsync` and `PromptInputsAsync`) are available and appear as text-based prompts in the terminal. - **Sequential input**: Multiple inputs are requested one at a time rather than in a single dialog. -- **Limited functionality**: Message boxes, notifications, and confirmation dialogs aren't available and throw exceptions if called. +- **Limited functionality**: Message boxes, notifications, confirmation dialogs, and progress dialogs aren't available and throw exceptions if called. ## See also diff --git a/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx b/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx index 9f0e592a6..fc2a935d8 100644 --- a/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx +++ b/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx @@ -1269,6 +1269,97 @@ await builder.build().run(); Use `ResourceCommandVisibility.Api` for automation-only commands (diagnostics, scripting, CI integration) that you don't want to clutter the dashboard UI. Use `ResourceCommandVisibility.UI` for interactive commands that only make sense when a developer is watching the results. +## Command progress + +Commands can display a progress dialog in the dashboard while they execute. The progress dialog shows an indeterminate progress indicator with an optional title, message, and cancel button. Set `CommandOptions.Progress` to a `CommandProgressOptions` value with a `Message` to enable this behavior — the dialog only appears when `Message` is set. + + + + +```csharp title="AppHost.cs" +builder.AddProject("api") + .WithCommand( + name: "seed-database", + displayName: "Seed Database", + executeCommand: async context => + { + await SeedDatabaseAsync(context.CancellationToken); + return CommandResults.Success("Database seeded."); + }, + commandOptions: new CommandOptions + { + IconName = "DatabaseArrowDown", + Progress = new CommandProgressOptions + { + Title = "Seeding", + Message = "Populating database with sample data..." + } + }); +``` + + + + +```typescript title="apphost.mts" +await api.withCommand( + "seed-database", + "Seed Database", + async () => { + await seedDatabase(); + return { success: true, message: "Database seeded." }; + }, + { + commandOptions: { + iconName: "DatabaseArrowDown", + progress: { + title: "Seeding", + message: "Populating database with sample data...", + }, + }, + }, +); +``` + + + + +When `CommandProgressOptions.Message` is set, the dashboard automatically shows the progress dialog when the command starts and closes it when the command completes. By default, a **Cancel** button is shown. Clicking it cancels the command via `ExecuteCommandContext.CancellationToken`. + +To hide the cancel button, set `HideCancelButton` to `true`: + + + + +```csharp title="AppHost.cs" +var commandOptions = new CommandOptions +{ + Progress = new CommandProgressOptions + { + Message = "Finalizing...", + HideCancelButton = true + } +}; +``` + + + + +```typescript title="apphost.mts" +{ + commandOptions: { + progress: { + message: "Finalizing...", + hideCancelButton: true, + }, + }, +} +``` + + + + +For more control over progress behavior, including custom work callbacks and programmatic dismissal, use `IInteractionService.PromptProgressAsync` directly. For more information, see [Display progress](/extensibility/interaction-service/#display-progress). + ## Process-backed resource commands :::caution[Experimental API] From 554797d9a72d731c1e4d27345d7baa610f2f5fe3 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 13 Jul 2026 07:38:09 -0500 Subject: [PATCH 26/32] [docs] CLI: mention 'Stopping Aspire...' feedback message on Ctrl+C (#1353) * docs: mention 'Stopping Aspire...' feedback message on Ctrl+C * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: David Pine Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/content/docs/reference/cli/commands/aspire-run.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/content/docs/reference/cli/commands/aspire-run.mdx b/src/frontend/src/content/docs/reference/cli/commands/aspire-run.mdx index 9e4c82ac6..0ea0a480c 100644 --- a/src/frontend/src/content/docs/reference/cli/commands/aspire-run.mdx +++ b/src/frontend/src/content/docs/reference/cli/commands/aspire-run.mdx @@ -35,7 +35,7 @@ The command performs the following steps to run an Aspire AppHost: ## Stopping the AppHost -To stop the running AppHost and exit, press (or send `SIGTERM` on Linux/macOS). The CLI requests a graceful shutdown of the AppHost and its resources. +To stop the running AppHost and exit, press (or send `SIGTERM` on Linux/macOS). The CLI requests a graceful shutdown of the AppHost and its resources. If shutdown takes longer than a moment, a `🛑 Stopping Aspire.` message is displayed shortly after you press to confirm that shutdown is in progress. If graceful shutdown is taking too long and you need to exit immediately, press a second time to terminate the process immediately. From 9aa8cdd8477b0e51b94db28dfd46141fff06134a Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 13 Jul 2026 07:47:34 -0500 Subject: [PATCH 27/32] docs: note graceful backchannel stream cancellation during aspire run shutdown (#1356) Co-authored-by: David Pine --- src/frontend/src/content/docs/whats-new/aspire-13-4.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx b/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx index de4c919ab..d2e2c9d23 100644 --- a/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx +++ b/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx @@ -398,6 +398,7 @@ Aspire 13.4 includes a set of smaller CLI improvements and fixes: - `aspire update` now requires `--yes` in non-interactive mode, matching `aspire destroy`. - `aspire stop --all` output includes the AppHost name and, when needed, the PID for clearer multi-instance shutdown output. - `aspire new`, `aspire init`, `aspire run`, and `aspire update` include fixes for NuGet feed errors, output paths, generated files, disabled dashboards, detached shutdown, and AppHost package-reference cleanup. +- `aspire run` now handles AppHost shutdown more gracefully: backchannel log, resource-state, and publishing-activity streams complete immediately when a shutdown is already in progress, and a warning is shown if log capture is interrupted rather than surfacing an unhandled error. ## 🧩 App model and AppHost From b9b32d33a63b78d5a681cda219431b3789f2c8a2 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Jul 2026 07:50:03 -0500 Subject: [PATCH 28/32] docs: document command return values for custom resource commands (#1161) Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> Address review feedback: add TypeScript Markdown example, fix heading style, imp Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- .../src/content/docs/dashboard/explore.mdx | 6 +- .../fundamentals/custom-resource-commands.mdx | 105 +++++++++++++++++- .../docs/fundamentals/http-commands.mdx | 2 +- .../content/docs/whats-new/aspire-13-3.mdx | 2 +- 4 files changed, 110 insertions(+), 5 deletions(-) diff --git a/src/frontend/src/content/docs/dashboard/explore.mdx b/src/frontend/src/content/docs/dashboard/explore.mdx index 53c5cb0f9..d1726d0e0 100644 --- a/src/frontend/src/content/docs/dashboard/explore.mdx +++ b/src/frontend/src/content/docs/dashboard/explore.mdx @@ -579,13 +579,17 @@ Selecting **View response** opens the text visualizer with the command's result Aspire dashboard text visualizer showing a command response opened from the notification center. + + The unread count resets when you open the notification center. To clear all notifications, select the **Dismiss all** button in the dialog. -For information on returning messages from custom commands, see [Custom resource commands](/fundamentals/custom-resource-commands/). +For information on returning command result payloads from custom commands, see [Custom resource commands](/fundamentals/custom-resource-commands/). ## Settings dialog diff --git a/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx b/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx index fc2a935d8..4ce592584 100644 --- a/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx +++ b/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx @@ -629,7 +629,7 @@ The `ExecuteCommandResult` type carries the outcome of a command. It exposes the - **`Success` / `success`** _(boolean)_ — Whether the command completed successfully. - **`Canceled` / `canceled`** _(boolean)_ — Whether the command was canceled by the user. - **`Message` / `message`** _(string?)_ — A human-readable message that surfaces in the dashboard [notification center](/dashboard/explore/#notification-center) and CLI output. Used for both success messages and error messages. -- **`Data` / `data`** _(`CommandResultData?`)_ — Optional structured output (plain text, JSON, or Markdown). See [Return structured output from a command](#return-structured-output-from-a-command). +- **`Data` / `data`** _(`CommandResultData?`)_ — Optional structured output (plain text, JSON, or Markdown). See [Return a result from a command](#return-a-result-from-a-command).