Skip to content

Add AvdManagerRunner for avdmanager CLI operations#282

Open
rmarinho wants to merge 39 commits intomainfrom
feature/avdmanager-runner
Open

Add AvdManagerRunner for avdmanager CLI operations#282
rmarinho wants to merge 39 commits intomainfrom
feature/avdmanager-runner

Conversation

@rmarinho
Copy link
Member

@rmarinho rmarinho commented Feb 23, 2026

Summary

Wraps avdmanager CLI operations for programmatic AVD management. Addresses #277.

API Surface

public class AvdManagerRunner
{
    // Constructor takes a resolved avdmanager path + optional environment variables
    public AvdManagerRunner(string avdManagerPath, IDictionary<string, string>? environmentVariables = null);

    // List all configured AVDs
    public async Task<IReadOnlyList<AvdInfo>> ListAvdsAsync(CancellationToken ct = default);

    // Create an AVD with the given name and system image
    public async Task CreateAvdAsync(string name, string systemImage, string? deviceProfile = null,
        bool force = false, CancellationToken ct = default);

    // Delete an AVD by name
    public async Task DeleteAvdAsync(string name, CancellationToken ct = default);

    // Get the AVD root directory
    public string GetAvdRootDirectory();

    // Static parser for avdmanager list avd output
    public static List<AvdInfo> ParseAvdListOutput(string output);
}

public record AvdInfo
{
    public required string Name { get; init; }
    public string? DeviceProfile { get; init; }
    public string? Path { get; init; }
}

Key Design Decisions

  • Resolved path constructor: Takes string avdManagerPath (full path to avdmanager binary) instead of Func<string?>. Callers resolve the path before constructing the runner. Follows the same pattern as AdbRunner (Add AdbRunner for adb CLI operations #283).
  • Environment variables via dictionary: IDictionary<string, string>? environmentVariables passed to ProcessUtils.StartProcess for ANDROID_HOME/JAVA_HOME/PATH configuration. Avoids internal PSI manipulation.
  • ThrowIfFailed with StringWriter overload: Uses ProcessUtils.ThrowIfFailed(exitCode, command, stderr) directly with StringWriter parameters — no .ToString() at call sites.
  • Pattern matching for null checks: Uses is { Length: > 0 } instead of !string.IsNullOrEmpty() — consistent with codebase conventions and avoids netstandard2.0 nullable narrowing issues.
  • ProcessUtils.FindCmdlineTool: Static helper that searches cmdline-tools/{version}/bin/ (highest version first), then latest, then legacy tools/bin/. Consumers call this to resolve the avdmanager path before constructing the runner.
  • ProcessUtils.ValidateNotNullOrEmpty: Shared validation helper for constructor/method parameter checks.

Tests

  • 17 tests: parsing (2), validation (7), directory search (4), environment variable handling (4)
  • FindCmdlineTool_FindsVersionedDir, _PrefersHigherVersion, _FallsBackToLatest, _MissingSdk_ReturnsNull — test path resolution
  • Constructor_NullPath_ThrowsArgumentException, Constructor_EmptyPath_ThrowsArgumentException — constructor validation
  • CreateAvdAsync_*, DeleteAvdAsync_* — argument validation

Commits

SHA Description
271b5c1 Use shared helpers in AvdManagerRunner
37c3a79 Address PR review feedback: exit codes, type splitting, structured args
d96296a Refactor constructor to resolved path, pattern matching, ThrowIfFailed overload

Copilot AI review requested due to automatic review settings February 23, 2026 17:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “runner” layer to Xamarin.Android.Tools.AndroidSdk to execute Android SDK command-line tools programmatically, with an initial AvdManagerRunner implementation for creating/listing/deleting AVDs.

Changes:

  • Introduces AndroidToolRunner for running SDK tools with timeout/stdout/stderr capture (sync + async) and a background-start helper.
  • Adds AvdManagerRunner to locate and invoke avdmanager, parse list avd output, and enrich results from config.ini.
  • Adds supporting models (ToolRunnerResult, AvdInfo) and environment setup utilities (AndroidEnvironmentHelper).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs Adds an avdmanager wrapper with list/create/delete and output parsing/enrichment.
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidToolRunner.cs Adds process execution utilities (sync/async + background start).
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs Provides JAVA_HOME / ANDROID_HOME setup plus ABI/API/tag mapping helpers.
src/Xamarin.Android.Tools.AndroidSdk/Models/ToolRunnerResult.cs Introduces a result type for tool execution (typed + untyped).
src/Xamarin.Android.Tools.AndroidSdk/Models/AvdInfo.cs Adds an AVD info model used by AvdManagerRunner.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@rmarinho rmarinho added the copilot `copilot-cli` or other AIs were used to author this label Feb 23, 2026
@jonathanpeppers
Copy link
Member

I'd like to get the System.Diagnostics.Process code unified like mentioned here:

rmarinho added a commit that referenced this pull request Feb 24, 2026
Addresses PR #282 feedback to use existing ProcessUtils instead of
the removed AndroidToolRunner. Simplifies API:

- Methods now throw InvalidOperationException on failure instead of
  returning result types with error states
- Uses ProcessUtils.RunToolAsync() for all tool invocations
- Simplified AvdInfo model
- Removed complex ToolRunnerResult<T> wrapper types

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho force-pushed the feature/avdmanager-runner branch 6 times, most recently from b286a7a to 6e09e61 Compare March 3, 2026 13:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

Comment on lines +108 to +113
// Detect orphaned AVD directory (folder exists without .ini registration).
var avdDir = Path.Combine (
Environment.GetFolderPath (Environment.SpecialFolder.UserProfile),
".android", "avd", $"{name}.avd");
if (Directory.Exists (avdDir))
force = true;
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateAvdAsync hard-codes the AVD directory as $HOME/.android/avd/<name>.avd for orphan detection and for the returned AvdInfo.Path. avdmanager can place AVDs elsewhere when ANDROID_USER_HOME/ANDROID_AVD_HOME are set, so this can incorrectly force --force and/or return the wrong path. Consider deriving the AVD location from the tool output (or re-list after creation and return the matching entry) and basing orphan detection on the same resolved location/env vars.

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +158
public async Task DeleteAvdAsync (string name, CancellationToken cancellationToken = default)
{
var avdManagerPath = RequireAvdManagerPath ();

using var stderr = new StringWriter ();
var psi = ProcessUtils.CreateProcessStartInfo (avdManagerPath, "delete", "avd", "--name", name);
ConfigureEnvironment (psi);
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeleteAvdAsync doesn't validate name (null/empty/whitespace). Passing an empty name will invoke avdmanager with an invalid --name value and can lead to confusing errors; please add the same argument validation used in CreateAvdAsync.

Copilot uses AI. Check for mistakes.
rmarinho added a commit that referenced this pull request Mar 3, 2026
- Fix argument validation: CreateAvdAsync/DeleteAvdAsync now throw
  ArgumentException for empty strings, ArgumentNullException for null,
  and validate before RequireAvdManagerPath()
- Make AndroidEnvironmentHelper internal, remove unused GetToolEnvironment
- Add ANDROID_USER_HOME to ConfigureEnvironment for consistent AVD
  location across tools (matches SdkManager behavior)
- Add AndroidUserHome constant to EnvironmentVariableNames
- Fix hard-coded AVD directory: use GetAvdRootDirectory() that respects
  ANDROID_AVD_HOME and ANDROID_USER_HOME env vars
- Re-list after CreateAvdAsync to get actual path from avdmanager
- Add 5 new validation tests for Create/DeleteAvdAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rmarinho added a commit that referenced this pull request Mar 3, 2026
- Fix argument validation: CreateAvdAsync/DeleteAvdAsync now throw
  ArgumentException for empty strings, ArgumentNullException for null,
  and validate before RequireAvdManagerPath()
- Make AndroidEnvironmentHelper internal, remove unused GetToolEnvironment
- Add ANDROID_USER_HOME to ConfigureEnvironment for consistent AVD
  location across tools (matches SdkManager behavior)
- Add AndroidUserHome constant to EnvironmentVariableNames
- Fix hard-coded AVD directory: use GetAvdRootDirectory() that respects
  ANDROID_AVD_HOME and ANDROID_USER_HOME env vars
- Re-list after CreateAvdAsync to get actual path from avdmanager
- Add 5 new validation tests for Create/DeleteAvdAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho force-pushed the feature/avdmanager-runner branch from 4c8ce13 to 3a788bb Compare March 3, 2026 18:23
Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review Summary

Found 5 issues: 1 security concern, 1 error handling gap, 1 consistency gap, 1 missing constant, 1 convention violation.

  • Security / API design: additionalArgs as single string risks injection and breaks multi-arg callers (EmulatorRunner.cs:57)
  • Error handling: Bare catch block silently swallows exceptions (AdbRunner.cs:106)
  • Consistency: Exit code not checked in ListDevicesAsync / ListAvdNamesAsync while other methods do (AdbRunner.cs:71)
  • Naming: Raw "ANDROID_AVD_HOME" string instead of EnvironmentVariableNames constant (AvdManagerRunner.cs:212)
  • Code organization: Three types in one file (AdbDeviceInfo.cs)

👍 Excellent test coverage with thorough parsing tests. Good CancellationToken propagation throughout, correct OperationCanceledException rethrow. AndroidEnvironmentHelper is a clean factoring of shared env setup. Orphaned AVD detection in CreateAvdAsync is a smart touch.


Review generated by android-tools-reviewer from review guidelines by @jonathanpeppers.

rmarinho added a commit that referenced this pull request Mar 5, 2026
- Fix argument validation: CreateAvdAsync/DeleteAvdAsync now throw
  ArgumentException for empty strings, ArgumentNullException for null,
  and validate before RequireAvdManagerPath()
- Make AndroidEnvironmentHelper internal, remove unused GetToolEnvironment
- Add ANDROID_USER_HOME to ConfigureEnvironment for consistent AVD
  location across tools (matches SdkManager behavior)
- Add AndroidUserHome constant to EnvironmentVariableNames
- Fix hard-coded AVD directory: use GetAvdRootDirectory() that respects
  ANDROID_AVD_HOME and ANDROID_USER_HOME env vars
- Re-list after CreateAvdAsync to get actual path from avdmanager
- Add 5 new validation tests for Create/DeleteAvdAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rmarinho added a commit that referenced this pull request Mar 5, 2026
- Fix argument validation: CreateAvdAsync/DeleteAvdAsync now throw
  ArgumentException for empty strings, ArgumentNullException for null,
  and validate before RequireAvdManagerPath()
- Make AndroidEnvironmentHelper internal, remove unused GetToolEnvironment
- Add ANDROID_USER_HOME to ConfigureEnvironment for consistent AVD
  location across tools (matches SdkManager behavior)
- Add AndroidUserHome constant to EnvironmentVariableNames
- Fix hard-coded AVD directory: use GetAvdRootDirectory() that respects
  ANDROID_AVD_HOME and ANDROID_USER_HOME env vars
- Re-list after CreateAvdAsync to get actual path from avdmanager
- Add 5 new validation tests for Create/DeleteAvdAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho force-pushed the feature/avdmanager-runner branch from 7a84734 to 37c4ffe Compare March 5, 2026 12:53
rmarinho added a commit that referenced this pull request Mar 5, 2026
…ed args

- AdbRunner: check exit codes consistently in ListDevicesAsync, WaitForDeviceAsync, StopEmulatorAsync
- AdbRunner: bare catch {} → catch (Exception) in GetEmulatorAvdNameAsync
- AdbDeviceInfo.cs: split 3 public types into separate files (AdbDeviceType.cs, AdbDeviceStatus.cs)
- EnvironmentVariableNames: add AndroidAvdHome constant, use in AvdManagerRunner
- EmulatorRunner.StartAvd: additionalArgs string? → IEnumerable<string>? for proper arg separation
- EmulatorRunner.StartAvd: set RedirectStandardOutput/Error=false to prevent pipe deadlock

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rmarinho and others added 2 commits March 5, 2026 15:37
- GetToolEnvironment(): Set ANDROID_HOME, JAVA_HOME, and PATH

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rmarinho and others added 25 commits March 5, 2026 15:37
Port the adb device parsing, display name formatting, status mapping, and
emulator merging logic from dotnet/android's GetAvailableAndroidDevices
MSBuild task into the shared android-tools library.

Changes:
- AdbDeviceInfo: Add Type, Status, Description, AvdName, Product, TransportId
  fields using AdbDeviceType/AdbDeviceStatus enums
- AdbRunner.ParseAdbDevicesOutput: Regex-based parser matching dotnet/android
  (handles device/offline/unauthorized/no permissions states)
- AdbRunner.BuildDeviceDescription: Priority order AVD name > model > product >
  device > serial, with underscore-to-space cleanup
- AdbRunner.FormatDisplayName: Title case + API capitalization for AVD names
- AdbRunner.MapAdbStateToStatus: Maps adb states to AdbDeviceStatus enum
- AdbRunner.MergeDevicesAndEmulators: Merges running + non-running emulators,
  deduplicates by AVD name (case-insensitive), sorts online first
- AdbRunner.GetEmulatorAvdNameAsync: Queries AVD name via adb emu avd name
- AdbRunnerTests: 33 tests ported from dotnet/android test cases

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The bare catch block was swallowing cancellation tokens, causing
cancelled operations to silently continue spawning adb processes
instead of propagating the cancellation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- StartAvd(): Start an emulator for an AVD
- ListAvdNamesAsync(): List installed AVD names

Minimal implementation without external dependencies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add XML documentation to all public members
- Use 'is not null' instead of '!= null'
- Improve code formatting
- Remove unused variable assignment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add optional Func<string?> getJdkPath constructor parameter
- ConfigureEnvironment() sets JAVA_HOME and ANDROID_HOME on all ProcessStartInfo
- Applied to StartAvd and ListAvdNamesAsync
- Backward compatible: existing 1-arg constructor still works

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…riter

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… add tests

- Replace manual ProcessStartInfo with ProcessUtils.CreateProcessStartInfo
- Use AndroidEnvironmentHelper.ConfigureEnvironment instead of duplicated method
- Extract ParseListAvdsOutput as internal static for testability
- Return IReadOnlyList<string> from ListAvdNamesAsync
- Add RequireEmulatorPath helper
- Add EmulatorRunnerTests (7 tests): parsing, path discovery, null/missing sdk

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ListAvdsAsync(): List configured AVDs
- DeleteAvdAsync(): Delete an AVD

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add XML documentation to all public members
- Split AvdInfo into its own file (one type per file)
- Use 'is not null' instead of '!= null'
- Improve code formatting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add optional Func<string?> getJdkPath constructor parameter
- ConfigureEnvironment() sets JAVA_HOME and ANDROID_HOME on all ProcessStartInfo
- Add CreateAvdAsync with duplicate detection, orphaned AVD directory handling, stdin 'no' for hardware profile prompt
- Extract ParseAvdListOutput as internal static for testability
- Backward compatible: existing 1-arg constructor still works

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add 'using' to StringWriter instances in ListAvdsAsync and CreateAvdAsync
  to ensure proper disposal
- Add AvdManagerRunnerTests with 5 focused tests for ParseAvdListOutput:
  multiple AVDs, Windows newlines, empty output, no AVDs, missing device

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Convert AvdManagerRunner.cs and AvdInfo.cs to file-scoped namespaces
  per repo conventions for new files
- Remove accidentally tracked nupkg (already in .gitignore)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ironmentVariableNames, exit code checks

- AvdManagerPath: enumerate versioned cmdline-tools dirs descending
  (mirrors SdkManager.FindSdkManagerPath pattern), then latest, then
  legacy tools/bin
- Use ProcessUtils.CreateProcessStartInfo with separate args instead
  of building Arguments string manually (all 3 methods)
- Use EnvironmentVariableNames.AndroidHome/.JavaHome constants instead
  of hard-coded strings
- Check exit codes in ListAvdsAsync and DeleteAvdAsync, throw on failure
- Return IReadOnlyList<AvdInfo> from ListAvdsAsync
- Set Path on AvdInfo returned from CreateAvdAsync for consistency
- Extract RequireAvdManagerPath helper

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Delegate to AndroidEnvironmentHelper.ConfigureEnvironment instead of inline setup
- Add 5 path discovery tests: versioned dir, higher version priority, latest fallback, null/missing sdk

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix argument validation: CreateAvdAsync/DeleteAvdAsync now throw
  ArgumentException for empty strings, ArgumentNullException for null,
  and validate before RequireAvdManagerPath()
- Make AndroidEnvironmentHelper internal, remove unused GetToolEnvironment
- Add ANDROID_USER_HOME to ConfigureEnvironment for consistent AVD
  location across tools (matches SdkManager behavior)
- Add AndroidUserHome constant to EnvironmentVariableNames
- Fix hard-coded AVD directory: use GetAvdRootDirectory() that respects
  ANDROID_AVD_HOME and ANDROID_USER_HOME env vars
- Re-list after CreateAvdAsync to get actual path from avdmanager
- Add 5 new validation tests for Create/DeleteAvdAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eTool

- ProcessUtils.ThrowIfFailed: consistent exit code checking with stderr/stdout context
- ProcessUtils.ValidateNotNullOrEmpty: reusable null/empty argument validation
- ProcessUtils.FindCmdlineTool: version-aware cmdline-tools directory search with legacy fallback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace cmdline-tools path lookup with ProcessUtils.FindCmdlineTool (~20 lines removed)
- Replace duplicated null/empty validation with ProcessUtils.ValidateNotNullOrEmpty
- Replace inline exit code checks with ProcessUtils.ThrowIfFailed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rmarinho added a commit that referenced this pull request Mar 5, 2026
- Fix argument validation: CreateAvdAsync/DeleteAvdAsync now throw
  ArgumentException for empty strings, ArgumentNullException for null,
  and validate before RequireAvdManagerPath()
- Make AndroidEnvironmentHelper internal, remove unused GetToolEnvironment
- Add ANDROID_USER_HOME to ConfigureEnvironment for consistent AVD
  location across tools (matches SdkManager behavior)
- Add AndroidUserHome constant to EnvironmentVariableNames
- Fix hard-coded AVD directory: use GetAvdRootDirectory() that respects
  ANDROID_AVD_HOME and ANDROID_USER_HOME env vars
- Re-list after CreateAvdAsync to get actual path from avdmanager
- Add 5 new validation tests for Create/DeleteAvdAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho force-pushed the feature/avdmanager-runner branch from 797964e to 271b5c1 Compare March 5, 2026 15:38
rmarinho and others added 2 commits March 5, 2026 18:26
…ed args

- AdbRunner: check exit codes consistently in ListDevicesAsync, WaitForDeviceAsync, StopEmulatorAsync
- AdbRunner: bare catch {} → catch (Exception) in GetEmulatorAvdNameAsync
- AdbDeviceInfo.cs: split 3 public types into separate files (AdbDeviceType.cs, AdbDeviceStatus.cs)
- EnvironmentVariableNames: add AndroidAvdHome constant, use in AvdManagerRunner
- EmulatorRunner.StartAvd: additionalArgs string? → IEnumerable<string>? for proper arg separation
- EmulatorRunner.StartAvd: set RedirectStandardOutput/Error=false to prevent pipe deadlock

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…g, ThrowIfFailed overload

Apply review patterns from PR #283 (AdbRunner):
- Constructor takes resolved 'string avdManagerPath' instead of Func<string?> delegates
- Add IDictionary<string, string>? environmentVariables for ANDROID_HOME/JAVA_HOME
- Remove AvdManagerPath property, IsAvailable, RequireAvdManagerPath(), ConfigureEnvironment()
- Use 'is { Length: > 0 }' pattern matching for null/empty checks
- Add ThrowIfFailed(StringWriter) overload to ProcessUtils
- Change ThrowIfFailed/ValidateNotNullOrEmpty/FindCmdlineTool to internal visibility
- Update tests: FindCmdlineTool tests replace AvdManagerPath tests, add constructor validation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rmarinho added a commit that referenced this pull request Mar 5, 2026
Resolves merge conflicts: keeps canonical AdbRunner from #283, adds shell
methods and virtual ListDevicesAsync for EmulatorRunner BootAndWait, keeps
updated AvdManagerRunner with resolved-path constructor from #282.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot `copilot-cli` or other AIs were used to author this

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants