From d6191a83933bf8d61169834d732f470a56d3e9fc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 16 May 2026 07:38:52 +0000
Subject: [PATCH 1/2] Initial plan
From 03c4a18bd37c222bddfda3934ade012ebd4a4052 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 16 May 2026 07:44:41 +0000
Subject: [PATCH 2/2] feat: add Winget and Chocolatey support to cratis update
command
- Add Winget and Chocolatey values to CliUpdateStrategy enum
- Detect winget install path (\WinGet\Packages\) and chocolatey paths (\chocolatey\bin\, \chocolatey\lib\)
- Run `winget upgrade --id Cratis.Cli [--version x]` for winget installs
- Run `choco upgrade cratis --yes [--version x]` for chocolatey installs
- Update SelfUpdateCommand LlmDescription and process-start failure hints
- Add specs covering detection and update process start info for both package managers
- Update getting-started docs with Windows Winget and Chocolatey install instructions
Agent-Logs-Url: https://github.com/Cratis/cli/sessions/f35097ca-c130-4964-86ce-89ff10c7010e
Co-authored-by: einari <134365+einari@users.noreply.github.com>
---
Documentation/getting-started/index.md | 24 ++++
.../for_CliUpdate/when_detecting_strategy.cs | 104 ++++++++++++++++++
Source/Cli/CliUpdate.cs | 67 +++++++++++
.../Cli/Commands/Version/SelfUpdateCommand.cs | 4 +-
4 files changed, 198 insertions(+), 1 deletion(-)
diff --git a/Documentation/getting-started/index.md b/Documentation/getting-started/index.md
index 3b0898a..cd9c3ea 100644
--- a/Documentation/getting-started/index.md
+++ b/Documentation/getting-started/index.md
@@ -15,6 +15,30 @@ To upgrade:
brew upgrade cratis
```
+### Windows (Winget)
+
+```powershell
+winget install Cratis.Cli
+```
+
+To upgrade:
+
+```powershell
+winget upgrade Cratis.Cli
+```
+
+### Windows (Chocolatey)
+
+```powershell
+choco install cratis
+```
+
+To upgrade:
+
+```powershell
+choco upgrade cratis
+```
+
### Linux
Download and install the pre-built native binary from the [latest release](https://github.com/Cratis/cli/releases/latest):
diff --git a/Source/Cli.Specs/for_CliUpdate/when_detecting_strategy.cs b/Source/Cli.Specs/for_CliUpdate/when_detecting_strategy.cs
index a25967b..6d7a29e 100644
--- a/Source/Cli.Specs/for_CliUpdate/when_detecting_strategy.cs
+++ b/Source/Cli.Specs/for_CliUpdate/when_detecting_strategy.cs
@@ -44,6 +44,58 @@ void should_detect_manual_linux_when_native_on_linux()
strategy.ShouldEqual(CliUpdateStrategy.ManualLinux);
}
+ [Fact]
+ void should_detect_winget_from_path()
+ {
+ var strategy = CliUpdate.DetectStrategy(
+ @"C:\Users\user\AppData\Local\Microsoft\WinGet\Packages\Cratis.Cli_abc123\cratis.exe",
+ @"C:\Users\user\AppData\Local\Microsoft\WinGet\Packages\Cratis.Cli_abc123\",
+ isNativeBuild: true,
+ isMacOS: false,
+ isLinux: false);
+
+ strategy.ShouldEqual(CliUpdateStrategy.Winget);
+ }
+
+ [Fact]
+ void should_detect_winget_from_machine_scoped_path()
+ {
+ var strategy = CliUpdate.DetectStrategy(
+ @"C:\Program Files\WinGet\Packages\Cratis.Cli_abc123\cratis.exe",
+ @"C:\Program Files\WinGet\Packages\Cratis.Cli_abc123\",
+ isNativeBuild: true,
+ isMacOS: false,
+ isLinux: false);
+
+ strategy.ShouldEqual(CliUpdateStrategy.Winget);
+ }
+
+ [Fact]
+ void should_detect_chocolatey_from_bin_path()
+ {
+ var strategy = CliUpdate.DetectStrategy(
+ @"C:\ProgramData\chocolatey\bin\cratis.exe",
+ @"C:\ProgramData\chocolatey\bin\",
+ isNativeBuild: true,
+ isMacOS: false,
+ isLinux: false);
+
+ strategy.ShouldEqual(CliUpdateStrategy.Chocolatey);
+ }
+
+ [Fact]
+ void should_detect_chocolatey_from_lib_path()
+ {
+ var strategy = CliUpdate.DetectStrategy(
+ @"C:\ProgramData\chocolatey\lib\cratis\tools\cratis.exe",
+ @"C:\ProgramData\chocolatey\lib\cratis\tools\",
+ isNativeBuild: true,
+ isMacOS: false,
+ isLinux: false);
+
+ strategy.ShouldEqual(CliUpdateStrategy.Chocolatey);
+ }
+
[Fact]
void should_use_cratis_update_hint_for_auto_update_strategies()
{
@@ -52,6 +104,22 @@ void should_use_cratis_update_hint_for_auto_update_strategies()
hint.ShouldContain("1.0.0 -> 1.1.0");
}
+ [Fact]
+ void should_use_cratis_update_hint_for_winget()
+ {
+ var hint = CliUpdate.GetUpdateHint(CliUpdateStrategy.Winget, "1.0.0", "1.1.0");
+ hint.ShouldContain("run 'cratis update'");
+ hint.ShouldContain("1.0.0 -> 1.1.0");
+ }
+
+ [Fact]
+ void should_use_cratis_update_hint_for_chocolatey()
+ {
+ var hint = CliUpdate.GetUpdateHint(CliUpdateStrategy.Chocolatey, "1.0.0", "1.1.0");
+ hint.ShouldContain("run 'cratis update'");
+ hint.ShouldContain("1.0.0 -> 1.1.0");
+ }
+
[Fact]
void should_prepare_brew_update_before_upgrade_for_homebrew()
{
@@ -67,4 +135,40 @@ void should_not_prepare_brew_update_when_target_version_is_set()
var startInfo = CliUpdate.CreatePreUpdateProcessStartInfo(CliUpdateStrategy.Homebrew, "1.2.3");
startInfo.ShouldBeNull();
}
+
+ [Fact]
+ void should_create_winget_upgrade_process_start_info()
+ {
+ var startInfo = CliUpdate.CreateUpdateProcessStartInfo(CliUpdateStrategy.Winget);
+ startInfo.ShouldNotBeNull();
+ startInfo!.FileName.ShouldEqual("winget");
+ startInfo.Arguments.ShouldEqual("upgrade --id Cratis.Cli");
+ }
+
+ [Fact]
+ void should_create_winget_upgrade_process_start_info_with_version()
+ {
+ var startInfo = CliUpdate.CreateUpdateProcessStartInfo(CliUpdateStrategy.Winget, "1.2.3");
+ startInfo.ShouldNotBeNull();
+ startInfo!.FileName.ShouldEqual("winget");
+ startInfo.Arguments.ShouldEqual("upgrade --id Cratis.Cli --version 1.2.3");
+ }
+
+ [Fact]
+ void should_create_chocolatey_upgrade_process_start_info()
+ {
+ var startInfo = CliUpdate.CreateUpdateProcessStartInfo(CliUpdateStrategy.Chocolatey);
+ startInfo.ShouldNotBeNull();
+ startInfo!.FileName.ShouldEqual("choco");
+ startInfo.Arguments.ShouldEqual("upgrade cratis --yes");
+ }
+
+ [Fact]
+ void should_create_chocolatey_upgrade_process_start_info_with_version()
+ {
+ var startInfo = CliUpdate.CreateUpdateProcessStartInfo(CliUpdateStrategy.Chocolatey, "1.2.3");
+ startInfo.ShouldNotBeNull();
+ startInfo!.FileName.ShouldEqual("choco");
+ startInfo.Arguments.ShouldEqual("upgrade cratis --yes --version 1.2.3");
+ }
}
diff --git a/Source/Cli/CliUpdate.cs b/Source/Cli/CliUpdate.cs
index faf37c4..f4f4a11 100644
--- a/Source/Cli/CliUpdate.cs
+++ b/Source/Cli/CliUpdate.cs
@@ -20,6 +20,16 @@ public enum CliUpdateStrategy
///
Homebrew,
+ ///
+ /// Update by running winget upgrade --id Cratis.Cli.
+ ///
+ Winget,
+
+ ///
+ /// Update by running choco upgrade cratis.
+ ///
+ Chocolatey,
+
///
/// Update manually by downloading and replacing the Linux native binary.
///
@@ -129,6 +139,44 @@ public static CliUpdateStrategy DetectStrategy()
};
}
+ if (strategy == CliUpdateStrategy.Winget)
+ {
+ var arguments = "upgrade --id Cratis.Cli";
+ if (!string.IsNullOrWhiteSpace(targetVersion))
+ {
+ arguments += $" --version {targetVersion}";
+ }
+
+ return new ProcessStartInfo
+ {
+ FileName = "winget",
+ Arguments = arguments,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+ }
+
+ if (strategy == CliUpdateStrategy.Chocolatey)
+ {
+ var arguments = "upgrade cratis --yes";
+ if (!string.IsNullOrWhiteSpace(targetVersion))
+ {
+ arguments += $" --version {targetVersion}";
+ }
+
+ return new ProcessStartInfo
+ {
+ FileName = "choco",
+ Arguments = arguments,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+ }
+
return null;
}
@@ -190,6 +238,16 @@ internal static CliUpdateStrategy DetectStrategy(
return CliUpdateStrategy.Homebrew;
}
+ if (IsWingetPath(processPath))
+ {
+ return CliUpdateStrategy.Winget;
+ }
+
+ if (IsChocolateyPath(processPath))
+ {
+ return CliUpdateStrategy.Chocolatey;
+ }
+
if (isLinux)
{
return CliUpdateStrategy.ManualLinux;
@@ -208,6 +266,15 @@ static bool IsHomebrewPath(string? path) =>
(path.Contains("/Cellar/cratis/", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Homebrew/Cellar/cratis/", StringComparison.OrdinalIgnoreCase));
+ static bool IsWingetPath(string? path) =>
+ !string.IsNullOrWhiteSpace(path) &&
+ path.Contains(@"\WinGet\Packages\", StringComparison.OrdinalIgnoreCase);
+
+ static bool IsChocolateyPath(string? path) =>
+ !string.IsNullOrWhiteSpace(path) &&
+ (path.Contains(@"\chocolatey\bin\", StringComparison.OrdinalIgnoreCase) ||
+ path.Contains(@"\chocolatey\lib\", StringComparison.OrdinalIgnoreCase));
+
static string? GetEffectiveProcessPath()
{
var path = Environment.ProcessPath;
diff --git a/Source/Cli/Commands/Version/SelfUpdateCommand.cs b/Source/Cli/Commands/Version/SelfUpdateCommand.cs
index c6543df..59e4b1c 100644
--- a/Source/Cli/Commands/Version/SelfUpdateCommand.cs
+++ b/Source/Cli/Commands/Version/SelfUpdateCommand.cs
@@ -8,7 +8,7 @@ namespace Cratis.Cli.Commands.Version;
///
/// Updates the Cratis CLI to the latest version using the detected installation method.
///
-[LlmDescription("Updates the cratis CLI to the latest version using the appropriate installation method (dotnet tool or Homebrew).")]
+[LlmDescription("Updates the cratis CLI to the latest version using the appropriate installation method (dotnet tool, Homebrew, Winget, or Chocolatey).")]
[CliCommand("update", "Update the Cratis CLI to the latest version")]
[CliExample("update")]
[CliExample("update", "--version", "1.2.3")]
@@ -103,6 +103,8 @@ protected override async Task ExecuteAsync(CommandContext context, SelfUpda
{
CliUpdateStrategy.DotNetTool => "Ensure the .NET SDK is installed and 'dotnet' is on your PATH",
CliUpdateStrategy.Homebrew => "Ensure Homebrew is installed and 'brew' is on your PATH",
+ CliUpdateStrategy.Winget => "Ensure winget is installed and 'winget' is on your PATH",
+ CliUpdateStrategy.Chocolatey => "Ensure Chocolatey is installed and 'choco' is on your PATH",
_ => null
};
OutputFormatter.WriteError(format, "Failed to start update process", hint, ExitCodes.ServerErrorCode);