Skip to content

Latest commit

 

History

History
584 lines (455 loc) · 35 KB

README.md

File metadata and controls

584 lines (455 loc) · 35 KB

Microsoft.Maui.Cli

A command-line tool for .NET MAUI development environment setup and device management.

⚠️ Experimental — APIs may change between releases. Not covered by the Microsoft Support Policy.

Package

Package Description
Microsoft.Maui.Cli Global CLI tool (maui) for environment setup, device management, and diagnostics.

Quick Start

1. Install the CLI tool

dotnet tool install -g Microsoft.Maui.Cli --prerelease

Updating the CLI

maui update --self
maui update --self --prerelease
maui update --self --dry-run

The --self flag explicitly targets the CLI. Bare maui update displays guidance without updating anything; project updates are not implemented by this command.

Self-update supports the default global .NET tool installation only, not local tool manifests, custom --tool-path installs, or source builds. Preview installations automatically include prereleases; stable installations require --prerelease to include them. The SDK chooses the latest eligible version; the command does not request downgrades.

Updates preserve the current directory's normal SDK selection (global.json) and NuGet configuration. An installed .NET SDK is required; missing SDKs, unreachable feeds, and permissions failures must be resolved before retrying. No feeds or configuration files are changed.

Only run self-update from a trusted directory: repository-local NuGet.config files can select feeds and influence the CLI package installed by the SDK. To avoid a repository's configuration, run the command from a trusted directory outside that repository. Public update notices do not validate or override the feeds used for installation.

On Windows, the running tool cannot uninstall its own loaded package. maui update --self starts a one-shot PowerShell handoff that waits for this process to exit before running the SDK updater. In an interactive terminal, the updater prints progress and a final completion or failure message in the same terminal. The shell prompt may return before these messages appear. Started is not completed: wait for the completion message before running the CLI again. The printed log path also records SDK output and SDK_EXIT=0 on success if you miss the terminal message. Keep the terminal open until completion; closing it may interrupt the updater. A nonzero SDK_EXIT indicates failure. The updater waits at most one minute for the original process to exit. On macOS/Linux the command waits for the SDK and reports its result directly.

Existing --json, --ci, --verbose, and --dry-run flags apply. JSON reports started with a log path for the Windows handoff, not a successful completed installation; its exit code confirms handoff only. Dry-run invokes no updater and performs no network checks or state writes. JSON, CI, and redirected invocations receive no delayed terminal messages; use the log for completion.

Ordinary interactive commands can show an update notice after successful completion. The first eligible invocation each UTC day checks listed versions on NuGet.org through its version autocomplete service in-process, with a two-second timeout covering service discovery and version retrieval; later invocations use a small user cache. Failed attempts are cached too. There is no daemon or notification subprocess. Checks are suppressed in CI, JSON, redirected output, help, dry-run, and protocol/server commands. Set MAUI_CLI_NO_UPDATE_CHECK=1 to disable checks and notices. Notifications use public package metadata; actual updates still use your configured feeds.

2. Check your environment

# Run diagnostics
maui doctor

# List connected devices
maui device list

3. Bootstrap AI-powered development

# Preview recommended setup for VS Code, including destinations and scope
maui ai init --env VsCode --dry-run

# Install the recommended skills, agents, and MCP registration
maui ai init --env VsCode --yes

# Discover all supported asset kinds, then inspect this project's inventory
maui ai list
maui ai status

# Refresh only existing managed assets; missing recommendations are NOT added
maui ai update

AI assets are explicit: skills, agents, and MCP registrations. Adding a skill does not install an agent or configure MCP. Use init for recommended combined setup, or choose individual assets:

maui ai add skill maui-devflow-debug --env Claude --yes
maui ai list agent
maui ai add agent "Comet Squad" --env VsCode --yes
maui ai add mcp maui-devflow --env VsCode --yes

# Only these named assets; selecting a bundled skill does not install siblings
maui ai init --skill maui-devflow-debug --mcp maui-devflow --env Claude --yes

# Restrict inventory and updates to one kind and environment
maui ai status skill --env Claude
maui ai update skill --skill maui-devflow-debug --env Claude --yes

Recommendations are explicit, not every catalog entry: the three bundled DevFlow skills, DevFlow MCP registration, the expert-reviewer agent, and the available maui-current-apis, maui-project-structure, maui-app-architecture, maui-ui-patterns, maui-unit-testing, and maui-accessibility skills. Only client-compatible recommendations are selected. Other catalog assets, including Comet-specific guidance, remain available through explicit selectors/add commands; adding an entry to the catalog does not automatically add it to init.

Human catalog output labels recommendations explicitly. Each command's --help includes examples; [type] means an optional positional kind, not a required option. Global --json, --ci, and --dry-run control CLI behavior, not installation scope.

AI command scope and options

Run from the project you want to configure. In Git repositories, the Git root is the project boundary; outside Git, the current directory is used. Detected nested skill/configuration locations are honored. Copilot agents live in project .github/agents and support VS Code and Copilot CLI, not Claude Code or OpenCode.

Command Behavior and important options
maui ai init Recommended combined setup. Repeatable --skill, --agent, and --mcp selectors instead install only their explicit union. Each bundled skill selector identifies one skill.
maui ai list [type] Available assets, default all kinds; optional singular skill, agent, or mcp. Catalog discovery may use the network.
maui ai status [type] Read-only installed/configured inventory, including unmanaged and tracked-but-missing assets. Not an MCP connectivity check.
maui ai update [type] Refresh existing managed assets only, using recorded origins. Missing or unmanaged assets are not installed/adopted, even with force. Supports exact type-specific selectors.
maui ai add skill <name> Install one skill through its owner (bundled DevFlow or repository). No implicit MCP or agent installation.
maui ai add agent <name> Install one Copilot agent definition.
maui ai add mcp <name> Merge a known server registration; currently maui-devflow. Does not install an executable, approve a client connection, or probe connectivity.

All commands support repeatable --env Claude|VsCode|CopilotCli|OpenCode. An explicit kind/name plus explicit environments must be compatible in every requested pairing: add agent <name> --env Claude VsCode fails before writing instead of silently targeting only VS Code. Without explicit assets, combined setup/catalog/inventory includes applicable kinds for the chosen environments. Shared physical destinations are deduplicated while retaining client associations.

init and add --env can target a canonical environment before its marker directory exists. Without --env, the default is all detected environments, not Copilot. Setup explains the configuration markers or recorded installations that selected each client, together with destinations and scope. A marker is not proof that the client executable is installed or running. In particular, a user's ~/.copilot/ can select Copilot CLI even without a project Copilot marker.

Interactive init offers a client selector when none are detected. It never invents a default client. With --yes, --ci, --json, --dry-run, or redirected input, missing targets instead produce actionable --env guidance without prompting. Read-only commands and update never create environment directories. Preview setup explicitly when configuring a project for the first time:

maui ai init --env Claude --dry-run
maui ai init --env Claude --yes

--yes (alias -y) accepts confirmation prompts only. --force authorizes replacement/adoption, not prompt acceptance; combine it with --yes for unattended replacement. For bundled DevFlow skills, force can also replace a newer CLI's content with the running CLI's bundle. Global --json and --ci suppress prompts but never authorize overwriting conflicts. CI stops later mutations after a failure. Customized or conflicting unmanaged assets require force and otherwise produce a blocked result/nonzero exit. Harmless already-current actions can skip successfully.

Global --dry-run never prompts or writes, including ownership/freshness state. It uses the same selection and conflict rules as execution and reports create/replace/adopt/skip actions, reasons, destinations, and project/user scope. Network access may still be required.

JSON command results use a versioned envelope with schemaVersion, command, dryRun, aggregate status, and results. Each result identifies the asset kind/name, associated environments, destination and scope, ownership, observed state, planned action, outcome, and reason. A successful inventory command is not a claim that every row is current or managed. Inspect row states and reasons; blocked actions and application failures produce nonzero exits.

The automation contract documents the JSON Schema, reason codes, exit semantics, compatibility rules, and the parser/cancellation cases outside the envelope.

MCP configuration uses .mcp.json for Claude Code, .vscode/mcp.json for VS Code, opencode.json (or existing .jsonc) for OpenCode, and user-wide ~/.copilot/mcp-config.json for Copilot CLI. Only the known definition's launch fields are managed; other servers, credentials, environment settings, and user options are preserved even under force. Malformed shared configuration remains an error, not permission to replace the whole file. An exact unmanaged registration is not silently adopted; use add mcp ... --force to opt into management. MCP update refreshes the registration definition, not the server executable version.

JSONC comments cannot be retained when a configuration is rewritten; the original is backed up to <config>.bak. This is a latest-only recovery file, not backup history: another comment-bearing rewrite replaces it. Copy it elsewhere before another rewrite if you need to retain that recovery point. Clients may require restart, project trust, or server approval before loading a registration. Inventory reports configuration and ownership, not connection health.

The default catalog is dotnet/maui-labs on main. Source overrides use --repo <owner/repo> and --branch <ref>; managed assets retain their recorded origin for later updates unless explicitly overridden. Missing required provenance is reported rather than guessed. Bundled versions refer to the running CLI's content, not remote downloads.

Each remote repository/ref is resolved once per plan to an immutable commit, so catalog discovery and downloaded content cannot mix revisions when a branch moves. Results report origin.resolvedCommit when a source is resolved, while origin.branch retains the tracking ref. To reproduce remote selection across separate preview/apply runs, pass the same full 40-character commit to --branch and use the same CLI version. A commit-pinned installation stays pinned on update unless its source is explicitly overridden; ordinary branches continue tracking new commits. See the automation contract for pipeline examples and limitations.

Skills retain their existing owner metadata (.skill-version or DevFlow state). Agent/project MCP ownership uses .maui/ai-assets.json under the project root; user MCP ownership is separate under the user's .maui. Registries record identity, origin, and managed-content hashes, not raw credentials/configuration. These are mutable local installation records; consider excluding them from version control. Commands do not silently change .gitignore.

New DevFlow skill workspace IDs and custom-path target keys use 32 lowercase hex characters from SHA-256. Existing shorter state directories, target keys, and lock files stay in use so installed skills keep their ownership records. If both old and new state locations exist, the CLI stops instead of merging or replacing records. Resolve that conflict before continuing. Do not use an older CLI to manage a target first created with the longer state keys.

Breaking changes from earlier versions of this unmerged PR: use add skill <name> instead of add <name>; -y now means yes, not force; adding skills no longer configures MCP; selectors no longer expand bundled groups or target another kind; list defaults to all asset kinds; update never installs missing recommendations; JSON output uses the typed result envelope instead of the earlier command-specific arrays/records. No hook/canvas commands or executable stubs are provided.

Skill replacement stages downloads and restores the previous directory on ordinary write failures. It is not crash-atomic: interruption between directory renames can leave <skill>.<id>.bak alongside the skills directory. If the skill directory is missing, inspect that backup and rename it back before retrying. Concurrent installations into the same destination are not supported. Path/symlink checks are not handle-based isolation against concurrent filesystem changes; use trusted, locally controlled destinations. Repository-hosted Copilot agents are currently single-file assets; writes across multiple assets are not one transaction.

4. Manage a project's .NET MAUI version

# Show the effective MAUI version for the current project
maui project version
maui project version --project ./src/MyApp/MyApp.csproj

# List available versions
maui project version list
maui project version list --channel nightly --take 20

# Pin a specific version and restore
maui project version set 10.0.60

# Use the latest stable or nightly package version
maui project version set --latest
maui project version set --latest-nightly --nuget-config

# Switch back to the installed workload version
maui project version use-workload

5. Set up Android development

# Full interactive Android setup (JDK + SDK + emulator)
maui android install

# Manage Android SDK packages
maui android sdk list
maui android sdk install "platforms;android-35"

# Ensure the latest Android command-line tools are installed
maui android sdk update-tools
maui android sdk update-tools --json

# Manage JDK installations
maui android jdk install

# Create and manage emulators
maui android emulator create --name MyEmulator
maui android emulator start --name MyEmulator

SDK package listing supports both the legacy sdkmanager table and the Android CLI table produced by Command-line Tools 23. JSON retains semicolon-separated package IDs, including dotted API levels such as platforms;android-37.0. Installed Command-line Tools omitted by the inventory are detected from their executable and source.properties revision. Listed revisions are preserved when source.properties or its revision is missing; unlisted tools without revision metadata keep an unknown version. Inventory execution or parsing failures return an error rather than an empty successful package list.

sdk update-tools checks Google's catalog and bootstraps or updates cmdline-tools;latest as needed. It uses the configured SDK path (or the default SDK location) and requires a working JDK. --dry-run only previews the target path. JSON success reports status: "ready" whether the tools were already current or were installed; it does not claim an update occurred. maui doctor reports the selected tool's path and revision when available. The resolver prefers cmdline-tools/latest, then the highest installed command-line tools revision. Legacy tools/bin installations are no longer supported; run maui android sdk update-tools to install modern tools.

6. Set up Apple development (macOS only)

# List installed Xcode versions
maui apple xcode list

# List simulator runtimes
maui apple runtime list
maui apple runtime list --platform iOS

# Manage simulators
maui apple simulator list
maui apple simulator start "iPhone 16 Pro"
maui apple simulator stop "iPhone 16 Pro"
maui apple simulator delete "iPhone 16 Pro"

6. Open the MAUI DevFlow Inspector

After adding the DevFlow agent to a running app:

maui devflow broker start

Then open http://localhost:19223/inspector/. See the MAUI DevFlow Inspector guide for app registration and browser, VS Code, GitHub Copilot desktop, and Copilot CLI setup.

Commands

Command Description
maui doctor Run environment diagnostics and auto-fix issues
maui device list List connected devices, emulators, and simulators (including physical iPhones and iPads on macOS)
maui project version Show the effective .NET MAUI version for a project
maui project version list List available .NET MAUI package versions
maui project version set Pin a project to a specific, latest stable, nightly, or custom-source MAUI version
maui project version use-workload Use the installed MAUI workload version instead of a pinned project version
maui version Display version information
AI
maui ai init Bootstrap MAUI/Copilot skills, bundled DevFlow skills, Copilot agents, and MCP configuration
maui ai list [type] List available skills, agents, and MCP registrations
maui ai status [type] Read-only installed/configured asset inventory
maui ai update [type] Refresh existing managed assets only
maui ai add skill <name> Install exactly one skill, without implicit MCP setup
maui ai add agent <name> Install one Copilot agent
maui ai add mcp <name> Configure one known MCP server registration
Android
maui android install Full interactive Android environment setup
maui android sdk list List available and installed Android SDK packages
maui android sdk install Install Android SDK packages
maui android sdk check Check Android SDK installation status
maui android sdk uninstall Uninstall Android SDK packages
maui android sdk accept-licenses Accept Android SDK licenses interactively
maui android sdk update-tools Ensure the latest Android SDK command-line tools are installed
maui android jdk install Install and manage JDK versions
maui android jdk check Check JDK installation status
maui android jdk list List available JDK versions
maui android emulator create Create an Android emulator
maui android emulator start Start an Android emulator
maui android emulator stop Stop a running emulator
maui android emulator delete Delete an emulator
maui android emulator list List available emulators
Apple (macOS only)
maui apple xcode list List installed Xcode versions
maui apple runtime list List installed simulator runtimes
maui apple simulator list List simulator devices
maui apple simulator start Boot a simulator
maui apple simulator stop Shut down a simulator
maui apple simulator delete Delete a simulator
DevFlow
maui devflow init Install project-scoped DevFlow onboarding/debugging skills
maui devflow skills Manage bundled DevFlow skill installs and updates
maui devflow ui Visual tree inspection, interaction, and screenshots
maui devflow device fold Foldable emulator/simulator modes, hinge angles, and verified transitions (iOS requires experimental opt-in)
maui devflow ui diagnostics Detect clipping, overflow, text truncation, overlap, and occlusion
maui devflow recording Manage UI recording sessions (start, stop, status)
maui devflow webview Blazor WebView automation via Chrome DevTools Protocol
maui devflow logs Fetch and stream application logs
maui devflow network Monitor HTTP network requests
maui devflow storage Access app preferences, secure storage, discover file storage roots, and manage sandboxed app files
maui devflow agent Discover and inspect connected DevFlow agents
maui devflow broker Manage the DevFlow agent broker (start, stop, status, log)
maui devflow batch Execute commands from stdin for scripting; stop on failure by default. --continue-on-error runs later commands but still exits nonzero if any command fails.
maui devflow commands List all available commands (schema discovery)
maui devflow diagnose Check DevFlow agent health
maui devflow wait Wait for an agent to connect
maui devflow mcp Start the MCP server for AI agent integration
Profiling
maui profile startup Collect a startup trace for a .NET MAUI app (.nettrace, speedscope, or MIBC output)
maui profile manual Launch a MAUI app normally, then attach and stop tracing on demand
Go
maui go create Create a new MAUI Go single-file project
maui go serve Start the dev server with hot reload
maui go upgrade Graduate a Go project to a full MAUI project

maui go upgrade keeps the existing ASCII name rules for generated application IDs. If the directory name has no ASCII letters or digits, a new ID uses com.cometgo.app followed by the first 32 lowercase hex characters of SHA-256 of the UTF-8 directory name. An explicit #:property ApplicationId=... remains unchanged. For these directory names, upgrade refuses to overwrite an existing .csproj, even with --force. Keep the existing project and its ApplicationId to preserve the app identity, including IDs generated with the previous eight-character fallback.

Run maui <command> --help for detailed options on any command.

For screen recording, select the platform and target when starting:

maui devflow recording start --platform android --device <serial> --output capture.mp4
# Or: --platform ios --device <simulator-UDID>
maui devflow recording stop

An iOS simulator recording requires an explicit --device. Stopping uses the saved recording platform and Android serial; it does not require repeating --platform or --device, and those options do not override the saved target. The iOS driver stops its saved recording process. Without a saved recording, stop reports an error without creating a platform driver.

Profiling commands wait up to two minutes for dotnet-trace to finish rundown and flush after a manual, timed, or event-driven stop. Use --trace-stop-timeout to adjust that finalization window independently of --duration.

MIBC conversion uses the selected target framework and configuration's isolated build outputs, filtered to the app's runtime identifier (ABI). An explicit RuntimeIdentifier is honored; for multi-ABI builds, the selected device's runtime identifier chooses the references (iOS simulators without architecture metadata use the host architecture). RID-specific output paths are evaluated separately so SDK artifact layouts are supported. The evaluated build settings determine the assembly-processing stage: shrinking uses only shrunk, linking without shrinking uses only linked, and untrimmed builds use neither. Reference-only assemblies are excluded. Missing stage outputs or missing/ambiguous ABI information are reported as errors rather than falling back to another stage or ABI.

For the shared Inspector UI and its host integrations, see the MAUI DevFlow Inspector guide.

DevFlow file commands can use local files directly:

# Upload local bytes into the selected app storage root
maui devflow storage files upload logs/app.log --file ./app.log

# Download to a directory, preserving the device file name
maui devflow storage files download logs/app.log --output ./downloads/

# Download to an explicit local file name
maui devflow storage files download logs/app.log --output ./downloads/app-copy.log

DevFlow WebView DOM and input helpers accept --webview (or -w) to select a WebView by index, AutomationId, or element ID; omitting it selects the first WebView. The cdp alias accepts the same options. The target is sent with the request, not inserted into the generated JavaScript:

maui devflow webview webviews
maui devflow webview --webview Right DOM querySelector "#name"
maui devflow webview -w Right snapshot
maui devflow webview -w Right Input fill "#name" "Hello"
maui devflow webview -w Right Input dispatchClickEvent "#submit"
maui devflow webview -w Right Input insertText "More text"

fill sets the matching field's value and dispatches input/change events; insertText inserts at the cursor in the selected WebView. These DOM helpers are separate from native ui query --selector and MCP maui_query_css.

Global Options

Option Description
--json Output in JSON format (for scripting and CI)
-v, --verbose Enable verbose output
--dry-run Show what would be done without making changes
--ci CI mode — non-interactive, fail fast on errors

Output Formats

The CLI supports two output modes:

  • Interactive (default) — Rich Spectre.Console output with colors, tables, and progress bars
  • JSON (--json) — Machine-readable JSON for scripting and CI pipelines
# Human-friendly output
maui doctor

# JSON output for scripting
maui doctor --json | jq '.checks[] | select(.status == "failed")'

--json flag

Most commands accept --json for structured output. Some commands may emit multiple JSON objects to stdout (JSONL / newline-delimited JSON) — for example, progress or status records before the final result. The final JSON object is the command result, whose shape is command-specific (see per-command --help and examples below). When a non-DevFlow command fails and the exception is handled by HandleCommandException, it emits the canonical error envelope described in the next section. This applies to both recognized MauiToolException errors (which produce specific error codes) and unexpected exceptions (which become E1001/InternalError via ErrorResult.FromException). Note: unrecognized parse errors (e.g. invalid flags) do not use this envelope, OperationCanceledException produces a status message with exit code 130 rather than the error envelope, and maui devflow ... uses a different JSON contract and writes structured errors to stderr rather than stdout.

Use --ci together with --json for non-interactive, fail-fast runs in automation contexts. Parse the output as a stream of JSON objects rather than assuming a single top-level document.

Successful maui profile startup --json and maui profile start --json runs emit a final result with snake_case project, framework, platform, device, diagnostic, and timestamp fields. format identifies the output format and output_path points to the primary artifact; raw_trace_path is included for speedscope and MIBC output and omitted for nettrace output.

Error envelope

For non-DevFlow maui commands, when a command throws an exception that is handled by HandleCommandException and --json is active, it writes a structured error object to stdout. The fields appear at the top level — there is no enclosing "error" wrapper. Property names are snake_case. Note: some commands may return non-zero exit codes without throwing (e.g. validation paths), and OperationCanceledException is treated as a cancellation (exit code 130) rather than an error envelope. This section does not apply to maui devflow ..., which uses a different JSON error shape and writes structured errors to stderr.

{
  "code": "E2106",          // stable error code — see the error code table below
  "category": "platform",   // tool | platform | user | network | permission
  "severity": "error",      // always "error" today (info | warning reserved for future use)
  "message": "Android emulator not installed",

  // OPTIONAL — omitted entirely when null (never serialized as JSON null)

  "native_error": "...",    // raw error text from the underlying tool, when available
  "context": { ... },       // command-specific diagnostics bag
  "remediation": {
    "type": "autofixable",              // autofixable | useraction | terminal | unknown
    "command": "maui android sdk install emulator",  // present when type == autofixable
    "manual_steps": ["...", "..."]      // present when type == useraction
  },
  "docs_url": "https://...",          // reserved — not yet populated by any command
  "correlation_id": "..."
}

Contract guarantees (verified line-by-line against Models/ErrorResult.cs):

  • Property names are snake_case — enforced by [JsonPropertyName] attributes on ErrorResult.
  • remediation.type values are lowercase strings — serialized via .ToString().ToLowerInvariant().
  • Optional fields (native_error, context, remediation, docs_url, correlation_id) are omitted entirely when null — enforced by [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)].
  • There is no outer "error" wrapper — all fields are top-level.
  • code, category, severity, and message are always present.
  • The CLI intends to keep this envelope shape consistent; future changes are expected to be additive (e.g. new optional fields) rather than changing existing field names or types.

Error code categories

Prefix category value Examples
E1xxx tool E1001 InternalError, E1004 InvalidArgument, E1006 DeviceNotFound, E1007 PlatformNotSupported
E20xx platform E2001 JdkNotFound, E2002 JdkVersionUnsupported, E2003 JdkInstallFailed
E21xx platform E2101 AndroidSdkNotFound, E2102 AndroidSdkManagerNotFound, E2103 AndroidLicensesNotAccepted, E2105 AndroidPackageInstallFailed, E2106 AndroidEmulatorNotFound, E2108 AndroidAvdCreateFailed, E2110 AndroidAdbNotFound, E2111 AndroidDeviceNotFound, E2112 AndroidAvdDeleteFailed
E22xx platform E2201 AppleXcodeNotFound, E2202 AppleCltNotFound, E2203 AppleSimctlFailed, E2204 AppleSimulatorNotFound, E2205 AppleXcodeLicenseNotAccepted, E2206 AppleSetupFailed, E2207 AppleSimulatorCreateFailed, E2208 AppleSimulatorEraseFailed
E23xx platform E2301 WindowsSdkNotFound
E24xx platform E2401 DotNetNotFound, E2402 MauiWorkloadMissing, E2403 DiagnosticsToolNotFound
E3xxx user User action required (wrong arguments, missing inputs)
E4xxx network Download / connectivity failures
E5xxx permission Privacy / OS permission issues

A maui errors list command is planned (issue #197) to expose this catalogue at runtime. The table above is the authoritative list until then.

Consuming the error envelope

Bash / jq:

if ! out=$(maui android sdk install emulator --json 2>&1); then
  # Output may contain multiple JSONL lines; extract the error envelope (has a "code" field)
  err=$(echo "$out" | jq -s '[.[] | select(.code)] | last')
  rem_type=$(echo "$err" | jq -r '.remediation.type // "unknown"')
  rem_cmd=$(echo "$err" | jq -r '.remediation.command // empty')
  if [[ "$rem_type" == "autofixable" && -n "$rem_cmd" ]]; then
    # Run the remediation command directly (never pass untrusted input to eval)
    $rem_cmd
  fi
fi

PowerShell:

$lines = maui android sdk install emulator --json
if ($LASTEXITCODE -ne 0) {
    # Output may contain multiple JSONL lines; pick the error envelope (has a "code" property)
    $err = $lines | ForEach-Object { $_ | ConvertFrom-Json } |
           Where-Object { $_.code } | Select-Object -Last 1
    if ($err.remediation.type -eq 'autofixable' -and $err.remediation.command) {
        # Split and invoke directly (avoid Invoke-Expression with untrusted input)
        $parts = $err.remediation.command -split ' '
        & $parts[0] $parts[1..($parts.Length-1)]
    }
}

Worked example: E2106 AndroidEmulatorNotFound

maui android emulator start <name> emits E2106 with an autofixable remediation when the Android emulator binary is not installed:

maui android emulator start Pixel8 --json
# → { "code": "E2106",
#     "category": "platform",
#     "severity": "error",
#     "message": "Android emulator not installed",
#     "remediation": { "type": "autofixable",
#                      "command": "maui android sdk install emulator" } }

# Auto-fix path:
maui android sdk install emulator --json
maui android emulator start Pixel8 --json   # retry original

Other E2106 throw sites (e.g., "no AVD with that name") emit the same code without a remediation block — surface message and stop retrying.

For agent-facing usage examples and remediation patterns used by AI coding agents, see maui-devflow-debug troubleshooting.

For Inspector reconnect behavior, control handoff, and XAML workspace recovery, see Inspector recovery and source editing.

Platform Support

Platform Status Notes
macOS ✅ Full support including Apple commands (Xcode, simulators, runtimes)
Windows ✅ Android SDK, JDK, and emulator commands
Linux ✅ Android commands

Development

DevFlow command registration, argument validation, presentation, exit codes, and batch parser re-entry live in DevFlowCommands. Internal instance operations under the DevFlow/Operations/ directory own native UI, WebView/CDP, storage/device, streaming, theme, extensions, capture/recording, host alerts/permissions, and broker/discovery execution. Command-side helpers prepare and own agent connections; operations borrow clients and do not dispose them. Stream cancellation subscriptions and rendering stay at the CLI boundary.

# Open just the CLI in your IDE
open src/Cli/Cli.slnf

# Build
dotnet build src/Cli/Cli.slnf

# Run tests
dotnet test src/Cli/Microsoft.Maui.Cli.UnitTests/Microsoft.Maui.Cli.UnitTests.csproj

# Run locally without installing
dotnet run --project src/Cli/Microsoft.Maui.Cli/ -- doctor