# 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 ```bash dotnet tool install -g Microsoft.Maui.Cli --prerelease ``` ### 2. Check your environment ```bash # Run diagnostics maui doctor # List connected devices maui device list ``` ### 3. Bootstrap AI-powered development ```bash # 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: ```bash 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 ` | Install one skill through its owner (bundled DevFlow or repository). No implicit MCP or agent installation. | | `maui ai add agent ` | Install one Copilot agent definition. | | `maui ai add mcp ` | 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 --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: ```bash 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](../../docs/Cli/ai-automation.md) documents the [JSON Schema](../../docs/Cli/ai-result.schema.json), 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 `.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 ` and `--branch `; 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`. **Breaking changes from earlier versions of this unmerged PR:** use `add skill ` instead of `add `; `-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 `..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 ```bash # 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 ```bash # 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" # Manage JDK installations maui android jdk install # Create and manage emulators maui android emulator create --name MyEmulator maui android emulator start --name MyEmulator ``` ### 6. Set up Apple development (macOS only) ```bash # 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: ```bash maui devflow broker start ``` Then open `http://localhost:19223/inspector/`. See the [MAUI DevFlow Inspector guide](https://github.com/dotnet/maui-labs/blob/main/docs/DevFlow/inspector.md) 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 and emulators | | `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 ` | Install exactly one skill, without implicit MCP setup | | `maui ai add agent ` | Install one Copilot agent | | `maui ai add mcp ` | 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 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 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 | | `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) | | **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 | Run `maui --help` for detailed options on any command. For the shared Inspector UI and its host integrations, see the [MAUI DevFlow Inspector guide](https://github.com/dotnet/maui-labs/blob/main/docs/DevFlow/inspector.md). DevFlow file commands can use local files directly: ```bash # 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 ``` ## 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 ```bash # 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. ### 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. ```json { "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](https://github.com/dotnet/maui-labs/issues/197)) to expose this catalogue at runtime. The table above is the authoritative list until then. ### Consuming the error envelope **Bash / jq:** ```bash 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:** ```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 ` emits `E2106` with an `autofixable` remediation when the Android emulator binary is not installed: ```bash 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 > [`plugins/dotnet-maui/skills/maui-devflow-debug/references/troubleshooting.md`](../../plugins/dotnet-maui/skills/maui-devflow-debug/references/troubleshooting.md). ## 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 ```bash # 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 ```