Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e10164e
Add interactive selection for ambiguous package matches
AmelBawa-msft Sep 28, 2026
1510821
Remove incorrect truncation guarantee from release notes
AmelBawa-msft Sep 28, 2026
d9263df
Fix spelling
AmelBawa-msft Sep 28, 2026
0ef9010
Lock package selection tokens against localization
AmelBawa-msft Sep 29, 2026
9b928b9
Resolve Unavailable once when building package choices
AmelBawa-msft Sep 29, 2026
b76321c
Always show sources in interactive package selection
AmelBawa-msft Sep 29, 2026
5c4e8a6
Move package selection tests alongside prompt workflows
AmelBawa-msft Sep 29, 2026
ff053c7
Require selection argument before optional downgrade flag
AmelBawa-msft Sep 29, 2026
9a8cef7
Replace package selection booleans with an explicit enum
AmelBawa-msft Sep 29, 2026
6b35743
Restore considerPins argument comment in show command
AmelBawa-msft Sep 29, 2026
42044ef
Use one-indexed wording in the package selection spec
AmelBawa-msft Sep 29, 2026
a3146a5
Clarify that multi-package flows do not prompt for selection
AmelBawa-msft Sep 29, 2026
8aea5a2
Allow package selection prompts with silent installation
AmelBawa-msft Sep 29, 2026
2082618
Build each package selection source row independently
AmelBawa-msft Sep 29, 2026
4287666
Move selection prompting into a reusable PromptFlow task
AmelBawa-msft Sep 29, 2026
b85ba3b
Fix spelling check failures in selection spec and tests
AmelBawa-msft Sep 29, 2026
34614f6
Remove redundant guidance from invalid selection feedback
AmelBawa-msft Sep 30, 2026
b6042c5
Move numbered selection prompt ownership into PromptFlow
AmelBawa-msft Sep 30, 2026
643ef46
Derive the selection count from numbered table rows
AmelBawa-msft Sep 30, 2026
9da01fa
Add typed integer prompting for numbered selection
AmelBawa-msft Sep 30, 2026
743405e
Fix Ctrl+C teardown race in package selection prompts
AmelBawa-msft Oct 1, 2026
a124a7c
Add verbose logging when selection prompts are unavailable
AmelBawa-msft Oct 1, 2026
4b94621
Restore the original interactivity comment
AmelBawa-msft Oct 1, 2026
7f23ccc
Rename the prompt selection data key to SelectedIndex
AmelBawa-msft Oct 1, 2026
4e13a17
Move integer range validation into Reporter
AmelBawa-msft Oct 1, 2026
4fc9079
Always show package identity on selection source rows
AmelBawa-msft Oct 1, 2026
b4b914d
Represent continuation rows explicitly in table output
AmelBawa-msft Oct 1, 2026
8052913
Make interactive package selection experimentally opt-in
AmelBawa-msft Oct 1, 2026
64e144d
Avoid spelling false positives in prompt test inputs
AmelBawa-msft Oct 1, 2026
77cdf03
Fix x86 signedness warnings in selection tests
AmelBawa-msft Oct 1, 2026
24ca1a4
Use fixed feedback for numbered selection prompts
AmelBawa-msft Oct 1, 2026
591f7bd
Reuse ambiguity tables for numbered package selection
AmelBawa-msft Oct 1, 2026
d729b8b
Use a shared title for interactive package selection
AmelBawa-msft Oct 1, 2026
4ee3425
Make ambiguity table builders private to the workflow
AmelBawa-msft Oct 2, 2026
71c25d8
Remove redundant operation gate from package selection
AmelBawa-msft Oct 2, 2026
a6c4a2c
Show refinement hints regardless of selection support
AmelBawa-msft Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ aspirational
Authenticode
AUTOLISTEN
azureedge
Bawa
binlog
binver
bstr
Expand Down
6 changes: 6 additions & 0 deletions doc/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## New Features

### Interactive package selection (experimental)

Set `experimentalFeatures.interactivePackageSelection` to `true` in settings to enable numbered choices when multiple packages match a single-package `install`, `show`, or `download` command in an interactive terminal. Enter a package number to continue or `0` to cancel.

This feature is disabled by default. Redirected and noninteractive callers retain the existing ambiguity error. Use `--id <ID> --exact --source <SOURCE>` to select a package explicitly, or `--disable-interactivity` to prevent prompts.

### Source priority

Source priority is now available without enabling an experimental feature. Use `winget source add --priority <value>` or `winget source edit --name <source> --priority <value>` to configure it. Higher values take precedence; sources with equal priority still require disambiguation when multiple matches remain.
Expand Down
12 changes: 12 additions & 0 deletions doc/Settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,15 @@ This feature enables support for fonts via `winget settings`. The `winget font l
"fonts": true
},
```

### interactivePackageSelection

Enables numbered choices for ambiguous single-package `install`, `show`, and `download` commands, including `show --versions`. Disabled by default.

```json
"experimentalFeatures": {
"interactivePackageSelection": true
},
```

`--disable-interactivity` and redirected input or output still prevent prompting.
127 changes: 127 additions & 0 deletions doc/specs/#5345 - Interactive package selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
author: AmelBawa-msft, GitHub Copilot <Copilot>
created on: 2026-09-28
last updated: 2026-10-01
issue id: 5345
---

# Interactive package selection

For [#5345](https://github.com/microsoft/winget-cli/issues/5345)

## Abstract

Let users resolve ambiguous package matches without restarting their command. An experimental setting enables numbered choices for single-package `install`, `show`, and `download` when interactive input and output are available.

## Inspiration

The same query can match multiple packages, including packages from different sources. Users should be able to choose deliberately without copying an ID into another invocation.

## Solution Design

This feature is disabled by default. Enable it in settings:

```json
{
"experimentalFeatures": {
"interactivePackageSelection": true
}
}
```

Apply existing search matching and source-priority rules first. If multiple candidates remain, eligible CLI call sites opt into selection. Shared workflows remain noninteractive by default.

Display candidates in their existing order with stable, one-indexed numbers. A valid number selects the existing package object without searching again. Preserve command options and continue normal version selection, applicability checks, and agreement handling.

| Situation | Behavior |
| --- | --- |
| Experimental feature disabled (default) | No selection prompts; retain ambiguity errors with refinement guidance. |
| No match | Existing no-match error. |
| One match after existing policy | Continue without prompting. |
| Multiple matches for single-package `install`, `show`, or `download` | Prompt if eligible, including `show --versions`. |
| Truncated results | Retain ambiguity error and request refinement. |
| Invalid or empty input | Explain the valid range and prompt again; no default. |
| `0` | Cancel without acting on a package. |
| Ctrl+C | Cancel immediately, including while waiting for input. |
| EOF or input failure | Report the existing prompt input error. |
| `--disable-interactivity`, interactivity disabled in settings or context | Retain ambiguity error without reading input. |
| `--silent` | Controls installer UI, not selection prompts; normal interactivity rules apply. |
| Redirected input or output, or disabled informational output | Do not prompt. |
| `--no-vt` | Use the same text and numeric input without terminal escape sequences. |
| Multi-package operations, including individual package contexts | No disambiguation prompts, either per package or up front; retain existing ambiguity errors. |
| Upgrade, uninstall, repair, pin, search, list, or completion | No selection prompts. |
| COM API, PowerShell cmdlets, or configuration/DSC | No new prompts or API changes. |

Apart from the experimental setting, the prompt adds no command-line flags, group policies, manifest fields, or schema versions. Existing interactivity controls and the experimental-features group policy apply. Package validation pipelines and manifest authoring tools are unchanged; manifest examples and schema snippets are not applicable.

## UI/UX Design

Reuse the existing ambiguity table with a leading selection number. Show Name, Id, and Source, including Source when all candidates use the same source. For example:

```text
Multiple packages match. Choose one.
# Name Id Source
------------------------------------------
1 Contoso Editor Contoso.Editor winget
2 Contoso Editor Contoso.Editor.Pro winget
Enter a number (1-2), or 0 to cancel: 1
Selected: Contoso Editor [Contoso.Editor]
```

For distinct candidates from different sources:

```text
# Name Id Source
---------------------------------------
1 Contoso Editor Contoso.Editor winget
2 Contoso Editor Contoso.Editor private
```

Each candidate occupies one row, using the existing ambiguity report's package identity and source. Sources grouped within a candidate do not add rows. Use the same introductory text for every command. Do not add another confirmation after selection. Existing consent prompts still apply.

Ambiguity errors include the candidate list and refinement guidance, even when interactive selection is disabled:

```text
Specify a package with --id <ID> --exact --source <SOURCE>.
```

For `configure export`, use `--package-id <ID> --source <SOURCE>` instead.

The original version option remains authoritative.

## Capabilities

### Accessibility

Numeric, line-oriented input works without color, cursor navigation, or arrow keys. All identifying information is text and uses localized labels. Invalid input includes recovery instructions.

### Security

There is no default selection or inferred equivalence. Choosing a package does not accept agreements or bypass existing source, trust, or installer checks.

### Reliability

Selection uses the displayed candidate object rather than re-running a potentially different search. EOF fails explicitly; cancellation never starts installation or download.

### Compatibility

The feature is disabled by default. After opt-in, scripts can preserve ambiguity errors with `--disable-interactivity`, or avoid ambiguity with exact ID and source selectors.

### Performance, Power, and Efficiency

Rendering uses available package metadata, without downloading manifests for display. No terminal redraw loop is required.

## Potential Issues

Long candidate lists require scrolling. Narrow terminals truncate table cells using the existing formatter; users can widen the terminal or cancel and refine their query if candidates are indistinguishable. The selection message includes the full name and ID. Source-defined result truncation must not be presented as a complete selectable list. Matching names and IDs do not prove that packages from different sources are equivalent.

## Future Considerations

Cross-source equivalence heuristics, arrow-key navigation, and selection for installed-package operations are separate changes.
Comment thread
AmelBawa-msft marked this conversation as resolved.

## Resources

- [Package matching background](%23292%20-%20winget%20should%20install%20an%20app%20if%20there%20is%20an%20exact%20match.md)
- [Settings reference](../Settings.md)
5 changes: 5 additions & 0 deletions schemas/JSON/settings/settings.schema.0.2.json
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@
"type": "boolean",
"default": false
},
"interactivePackageSelection": {
"description": "Enable interactive selection for ambiguous package matches",
"type": "boolean",
"default": false
},
"resume": {
"description": "Enable support for some commands to resume",
"type": "boolean",
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@
<ClCompile Include="ExecutionContext.cpp" />
<ClCompile Include="ExecutionProgress.cpp" />
<ClCompile Include="ExecutionReporter.cpp" />
<ClCompile Include="TableOutput.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@
<ClCompile Include="ExecutionReporter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="TableOutput.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Commands\HashCommand.cpp">
<Filter>Commands</Filter>
</ClCompile>
Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/DownloadCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ namespace AppInstaller::CLI
Workflow::OpenSource() <<
Workflow::SearchSourceForSingle <<
Workflow::HandleSearchResultFailures <<
Workflow::EnsureOneMatchFromSearchResult(OperationType::Download) <<
Workflow::EnsureOneMatchFromSearchResult(OperationType::Download, PackageSelectionBehavior::Prompt) <<
Workflow::GetManifestFromPackage(false);
}

Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/DscPackageResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ namespace AppInstaller::CLI
}

*SubContext <<
Workflow::SelectSinglePackageVersionForInstallOrUpgrade(Workflow::OperationType::Install, allowDowngrade) <<
Workflow::SelectSinglePackageVersionForInstallOrUpgrade(Workflow::OperationType::Install, Workflow::PackageSelectionBehavior::Disabled, allowDowngrade) <<
Workflow::InstallSinglePackage;

if (SubContext->IsTerminated())
Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/InstallCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ namespace AppInstaller::CLI
{
context <<
Checkpoint("PreInstallCheckpoint", {}) << // TODO: Capture context data
InstallOrUpgradeSinglePackage(OperationType::Install);
InstallOrUpgradeSinglePackage(OperationType::Install, PackageSelectionBehavior::Prompt);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/AppInstallerCLICore/Commands/ShowCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,15 @@ namespace AppInstaller::CLI
Workflow::OpenSource() <<
Workflow::SearchSourceForSingle <<
Workflow::HandleSearchResultFailures <<
Workflow::EnsureOneMatchFromSearchResult(OperationType::Show) <<
Workflow::EnsureOneMatchFromSearchResult(OperationType::Show, PackageSelectionBehavior::Prompt) <<
Workflow::ReportPackageIdentity <<
Workflow::ShowAppVersions;
}
}
else
{
context <<
GetManifest( /* considerPins */ false) <<
GetManifest( /* considerPins */ false, PackageSelectionBehavior::Prompt) <<
Workflow::ReportManifestIdentity <<
Workflow::SelectInstaller <<
Workflow::ShowManifestInfo;
Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/ExecutionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ namespace AppInstaller::CLI::Execution

private:
DestructionToken m_disableSignalTerminationHandlerOnExit = false;
bool m_isTerminated = false;
std::atomic<bool> m_isTerminated = false;
HRESULT m_terminationHR = S_OK;
size_t m_CtrlSignalCount = 0;
ContextFlag m_flags = ContextFlag::None;
Expand Down
7 changes: 7 additions & 0 deletions src/AppInstallerCLICore/ExecutionContextData.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ namespace AppInstaller::CLI::Execution
RepairString,
MsixDigests,
InstallerDownloadAuthenticators,
SelectedIndex,
Max
};

Expand Down Expand Up @@ -100,6 +101,12 @@ namespace AppInstaller::CLI::Execution
using value_t = Repository::SearchResult;
};

template <>
struct DataMapping<Data::SelectedIndex>
{
using value_t = std::optional<size_t>;
};

template <>
struct DataMapping<Data::SourceList>
{
Expand Down
Loading
Loading