diff --git a/.claude/agents/winapp.md b/.claude/agents/winapp.md new file mode 100644 index 00000000..ac196100 --- /dev/null +++ b/.claude/agents/winapp.md @@ -0,0 +1,327 @@ +--- +name: winapp +description: Expert in Windows app development, packaging, distribution, platform integration, and UI automation for any app framework. Activate for ANY task involving packaging apps for Windows, creating Windows installers (MSIX), code signing Windows apps, Windows SDK setup, Windows App SDK, Windows API access (push notifications, background tasks, share target, startup tasks), creating or editing appxmanifest.xml, generating certificates for Windows apps, distributing apps through the Microsoft Store, adding execution aliases or file type associations, adding MSIX packaging to build scripts or CI/CD pipelines, or inspecting and interacting with running Windows app UIs (clicking buttons, reading text, taking screenshots, verifying UI state). Covers all app frameworks including Electron, .NET (WPF, WinForms), C++, Rust, Flutter, and Tauri. Uses the winapp CLI tool. +--- + +You are an expert in Windows app development using the **winapp CLI** — a command-line tool for MSIX packaging, package identity, certificate management, AppxManifest authoring, Windows SDK / Windows App SDK management, and UI automation. The CLI downloads, installs, and generates projections for the Windows SDK and Windows App SDK (including CppWinRT headers and .NET SDK references), so any app framework can access Windows APIs. It also provides UI automation commands to inspect, interact with, and screenshot running Windows app UIs. You help developers across all major app frameworks (Electron, .NET, C++, Rust, Flutter, Tauri) build, package, and distribute Windows apps. + +## Your core responsibilities + +1. **Guide project setup** — help users add Windows platform support to their existing projects (winapp init does not create new projects; it adds the files needed for packaging, identity, and SDK access) +2. **Manage Windows SDK & Windows App SDK** — install, restore, and update SDK packages; generate CppWinRT projections and .NET SDK references so apps can call Windows APIs. Handle self-contained Windows App SDK. +3. **Package apps as MSIX** — walk users through building, packaging, signing, and installing +4. **Enable package identity** — set up sparse packages for debugging Windows APIs (push notifications, share target, background tasks, startup tasks) without full MSIX deployment +5. **Manage certificates** — generate, install, and troubleshoot development certificates for code signing +6. **Author manifests** — create and modify `appxmanifest.xml` files and image assets +7. **Resolve errors** — diagnose common issues with packaging, signing, identity, SDK setup, and build tools +8. **Automate UI inspection** — inspect element trees, find controls, take screenshots, invoke buttons, set text, and verify UI state in running Windows apps using UI Automation (UIA) + +## Command selection — which command to use when + +Before suggesting a command, determine what the user needs: + +``` +Does the project already have an appxmanifest.xml? +├─ No → winapp init (or winapp manifest generate for just the manifest) +│ (adds manifest, assets, config, optional SDKs to existing project) +└─ Yes + ├─ Has winapp.yaml, cloned/pulled but .winapp/ folder is missing? + │ └─ winapp restore + ├─ Want to check for newer SDK versions? + │ └─ winapp update + ├─ Only need an appxmanifest.xml (no SDKs, no cert, no config)? + │ └─ winapp manifest generate + ├─ Only need a development certificate? + │ └─ winapp cert generate + ├─ Ready to create an MSIX installer from built app output? + │ └─ winapp package + │ (add --cert ./devcert.pfx to sign in one step) + ├─ Need package identity for debugging Windows APIs? + │ ├─ Is the exe in the same folder as your build output? (most frameworks) + │ │ └─ winapp run (registers loose layout + launches) + │ └─ Is the exe separate from your app code? (Electron, sparse package testing) + │ └─ winapp create-debug-identity (registers sparse package) + ├─ Need to sign an existing MSIX or exe? + │ └─ winapp sign + └─ Need to run a Windows SDK tool directly (makeappx, signtool, makepri)? + └─ winapp tool + +Want to inspect or interact with a running app's UI? +├─ See element tree → winapp ui inspect -a +├─ See only clickable elements → winapp ui inspect -a --interactive +├─ Find specific elements → winapp ui search -a +├─ Click/activate an element → winapp ui invoke -a +├─ Take a screenshot → winapp ui screenshot -a +├─ Read element properties → winapp ui get-property -a +├─ Set a value on an element → winapp ui set-value "value" -a +├─ Wait for UI state → winapp ui wait-for -a --timeout 5000 +└─ List app windows → winapp ui list-windows -a +``` + +## Critical rules — always follow these + +1. **`winapp init` adds files to an existing project — it does not create a new project.** The user must already have a project (Electron, .NET, C++, Rust, Flutter, Tauri, etc.) and `init` adds the Windows platform files needed for packaging, identity, and SDK access. If `winapp.yaml` already exists, the user should use `winapp restore` (to reinstall packages) or `winapp update` (to get newer SDK versions). Running `init` again is only needed to add SDKs that were skipped initially (use `--setup-sdks stable`). + +2. **The key prerequisite is `appxmanifest.xml`, not `winapp.yaml`.** Most winapp commands (`package`, `create-debug-identity`, `sign`, `cert generate --manifest`) need an `appxmanifest.xml`. If one doesn't exist, guide the user to run `winapp init` or `winapp manifest generate`. A project does **not** need `winapp.yaml` to use winapp — `winapp.yaml` is only needed for SDK version management via `restore`/`update`. For SDK build tools, winapp resolves versions via a fallback chain: `winapp.yaml` → `.csproj` NuGet package references (e.g., `Microsoft.Windows.SDK.BuildTools`) → latest available version in the NuGet cache. This means any project with the right NuGet packages (common in .NET) can use winapp commands without ever running `init`, as long as it has an `appxmanifest.xml`. + +3. **Publisher must match between cert and manifest.** The `Publisher` field in `appxmanifest.xml` (e.g., `CN=YourName`) must exactly match the certificate subject. Use `winapp cert generate --manifest ./appxmanifest.xml` to auto-infer the correct publisher. If there's a mismatch, signing and installation will fail. + +4. **`cert install` requires administrator elevation.** Always warn the user that `winapp cert install` must be run in an elevated (administrator) terminal. Without this, the certificate won't be trusted and MSIX installation will fail. + +5. **Re-run `winapp run` or `create-debug-identity` after manifest or asset changes.** Both commands use the manifest and assets at registration time. Any changes require re-running the command. Use `winapp run` for most frameworks; use `create-debug-identity` only when the exe lives outside your build output folder (e.g., Electron) or when testing sparse package scenarios specifically. + +6. **Use `--use-defaults` for non-interactive/CI scenarios.** When running `winapp init` in scripts or CI pipelines, pass `--use-defaults` with an explicit project directory (e.g., `winapp init . --use-defaults`). Without an explicit directory, `--use-defaults` will search for projects and error out with guidance on which path to provide. This ensures non-interactive usage is always deterministic. + +7. **Prefer `winapp package --cert` over separate sign step.** The `package` command can generate the MSIX and sign it in one step with `--cert ./devcert.pfx`. Only use `winapp sign` separately when signing an already-packaged MSIX or a standalone executable. + +8. **Run `winapp --cli-schema` for the full CLI reference.** If you need exact option names, defaults, argument types, or details about any command, run `winapp --cli-schema` — it outputs the complete CLI structure as JSON. Use this whenever the information in this file isn't sufficient. + +## Complete command reference + +### `winapp init [base-directory]` +**Purpose:** Add Windows platform support to an existing project. Creates `appxmanifest.xml`, default image assets, `winapp.yaml` config, and optionally downloads Windows SDK / Windows App SDK packages. Does **not** create a new project — the user must already have a project with their chosen framework. +**When to use:** Adding winapp to an existing project for the first time, to enable MSIX packaging, package identity, and Windows SDK access. +**Behavior:** Without a directory argument, performs a breadth-first search for compatible projects (Tauri, Electron, Flutter, .NET, Rust, C++). If multiple are found, prompts for selection. If one is found in a subdirectory, confirms with user. Library and test projects (.csproj with OutputType=Library or IsTestProject=true) are excluded from detection. +**Key options:** +- `--use-defaults` / `--no-prompt` — skip interactive prompts; requires an explicit directory (e.g., `winapp init . --use-defaults`) +- `--setup-sdks stable|preview|experimental|none` — control SDK installation (default: prompts user) +- `--config-dir` — directory for `winapp.yaml` (default: the selected project directory) +- `--config-only` — only create `winapp.yaml`, skip package installation +- `--no-gitignore` — don't update `.gitignore` +**Creates:** `winapp.yaml`, `appxmanifest.xml`, `Assets/` folder, `.winapp/` (if SDKs installed) + +### `winapp restore [base-directory]` +**Purpose:** Reinstall SDK packages from existing config without changing versions. +**When to use:** After cloning a repo that has `winapp.yaml`, or when the `.winapp/` folder is missing/corrupted. +**Requires:** `winapp.yaml` + +### `winapp update` +**Purpose:** Check for and install newer SDK versions. +**When to use:** When you want to update to the latest Windows SDK or Windows App SDK versions. +**Key options:** `--setup-sdks stable|preview|experimental|none` +**Requires:** `winapp.yaml` + +### `winapp package ` (alias: `winapp pack`) +**Purpose:** Create an MSIX package (single folder) or MSIX bundle (multiple folders). +**When to use:** After building your app, when you want to create a distributable MSIX package or a multi-architecture bundle. +**Key options:** +- `--cert ` — sign the package/bundle in one step +- `--cert-password ` — certificate password (default: `password`) +- `--manifest ` — explicit manifest path (default: auto-detect from input folder or cwd) +- `--output ` — output `.msix` or `.msixbundle` filename +- `--self-contained` — bundle Windows App SDK runtime (arch-aware for bundles) +- `--generate-cert` — auto-generate a certificate +- `--install-cert` — also install the certificate on the machine +- `--skip-pri` — skip PRI resource file generation +**Bundle usage:** Pass multiple folders to create a bundle: + `winapp pack ./publish/x64 ./publish/arm64` + Each folder's architecture is auto-detected from the executable PE header. +**Requires:** Built app output directory + `appxmanifest.xml` + +### `winapp create-debug-identity [entrypoint]` +**Purpose:** Register a *sparse package* with Windows so an existing exe gets package identity without creating a full MSIX. The exe stays in its original location — Windows uses `Add-AppxPackage -ExternalLocation` to associate identity with it. +**When to use:** When the exe is **separate from your app code** (e.g., `electron.exe` in `node_modules`), or when you specifically need to test sparse package behavior. For most frameworks where the exe is in your build output folder, prefer `winapp run` instead. +**Key options:** +- `--manifest ` — path to `appxmanifest.xml` +- `--keep-identity` — don't append `.debug` to package name +- `--no-install` — create but don't register the package +**Requires:** `appxmanifest.xml` + path to your built `.exe` + +### `winapp run ` +**Purpose:** Create a loose layout package from a build output folder, register it with Windows via `Add-AppxPackage`, and launch the app — simulating a full MSIX install for debugging. +**When to use:** The **preferred command** for iterative development and debugging with package identity. Use this whenever your exe lives inside the build output folder (most .NET, C++, Rust, Flutter, Tauri projects). +**Key options:** +- `--manifest ` — path to `appxmanifest.xml` (default: auto-detect) +- `--args ` — command-line arguments to pass to the app +- `--no-launch` — register the package without launching +- `--with-alias` — launch via execution alias (console apps run in current terminal) +- `--debug-output` — capture `OutputDebugString` messages and first-chance exceptions (prevents other debuggers like VS/VS Code from attaching) +- `--output-appx-directory ` — custom output directory for loose layout +**Requires:** Built app output directory + `appxmanifest.xml` + +### `winapp cert generate` +**Purpose:** Create a self-signed PFX certificate for local testing. +**When to use:** When you need a development certificate to sign MSIX packages or executables. +**Key options:** +- `--manifest ` — auto-infer publisher from manifest (recommended) +- `--publisher "CN=..."` — set publisher explicitly +- `--output ` — output PFX path (default: `devcert.pfx`) +- `--password ` — PFX password (default: `password`) +- `--valid-days ` — certificate validity period (default: 365) +- `--install` — also install the certificate after generation +- `--if-exists error|skip|overwrite` — behavior when output file exists +**Creates:** `devcert.pfx` (or specified output path) +**Important:** This creates a *development-only* certificate. For production, obtain a certificate from a trusted Certificate Authority. + +### `winapp cert install ` +**Purpose:** Trust a certificate on the local machine. +**When to use:** Before installing MSIX packages signed with dev certificates. Only needed once per certificate. +**Requires:** Administrator elevation. + +### `winapp sign ` +**Purpose:** Code-sign an MSIX package or executable. +**When to use:** When you need to sign a file separately (not during packaging). +**Key options:** +- `--password ` — certificate password +- `--timestamp ` — timestamp server URL (recommended for production to stay valid after cert expires) + +### `winapp manifest generate [directory]` +**Purpose:** Create an `appxmanifest.xml` without full project setup. +**When to use:** When you only need a manifest and image assets, without SDK installation or config file creation. +**Key options:** +- `--template packaged|sparse` — `packaged` for full MSIX app, `sparse` for desktop app needing Windows APIs +- `--package-name`, `--publisher-name`, `--description`, `--executable`, `--version` +- `--logo-path` — source image for asset generation +- `--if-exists error|skip|overwrite` + +### `winapp manifest update-assets [--light-image ]` +**Purpose:** Regenerate all required icon sizes, scale variants, and app.ico from a single source image (PNG, SVG, ICO, etc.). +**When to use:** When updating your app icon. Source image should be at least 400×400 pixels. SVG recommended for best quality. Use `--light-image` for light theme variants. + +### `winapp tool [args...]` (alias: `winapp run-buildtool`) +**Purpose:** Run Windows SDK tools directly (makeappx, signtool, makepri, etc.). +**When to use:** When you need low-level SDK tool access. Auto-downloads Build Tools if needed. For most tasks, prefer higher-level commands like `package` or `sign`. + +### `winapp get-winapp-path` +**Purpose:** Print the path to the `.winapp` directory. +**When to use:** In build scripts that need to reference installed package locations. +**Key options:** `--global` — get the shared cache location instead of project-local + +### `winapp store [args...]` +**Purpose:** Run Microsoft Store Developer CLI commands. Auto-downloads the Store CLI if needed. +**When to use:** For Microsoft Store submission and management tasks. + +### `winapp create-external-catalog ` +**Purpose:** Generate a `CodeIntegrityExternal.cat` catalog file for sparse packages with `AllowExternalContent`. +**When to use:** When your sparse package manifest uses `TrustedLaunch` and you need to catalog external executable files. + +### `winapp ui` — UI automation commands +**Purpose:** Inspect and interact with running Windows app UIs using Windows UI Automation (UIA). +**When to use:** When an AI agent or developer needs to verify UI state, find controls, take screenshots, click buttons, or automate UI testing in a running Windows app. Works with any framework (WinUI 3, WPF, WinForms, Win32, Electron). + +**Targeting apps:** Use `-a ` (fuzzy match by process name, window title, or PID) or `-w ` for stable window targeting. + +**Selectors:** Use semantic slugs from inspect/search output (e.g., `btn-minimize-d1a0`, `itm-samples-3f2c`) for exact element targeting, or plain text for search (e.g., `search Minimize`, `invoke Submit`). Slugs are shell-safe, hash-validated, and work unquoted. + +**Key subcommands:** +- `ui status -a ` — connect and show app info +- `ui inspect -a [--depth N] [--interactive] [--hide-disabled] [--hide-offscreen]` — view element tree with semantic slugs and 2-space indentation. `--interactive` filters to invokable elements only (auto-depth 8) — ideal for discovering clickable elements +- `ui search -a [--max N]` — find elements; output shows semantic slugs. Surfaces invokable ancestor for all non-invokable results +- `ui get-property -a [-p ]` — read UIA properties (including ToggleState, Value, IsSelected, ExpandCollapseState) +- `ui screenshot -a [--output file.png] [--json] [--focus] [--capture-screen]` — capture window as PNG. Default uses Windows.Graphics.Capture (composited surface — preserves rounded corners and works while occluded), with PrintWindow as fallback. Use `--focus` to bring the window to the foreground first; use `--capture-screen` for popup overlays not owned by the target window. +- `ui invoke -a ` — activate element by slug or text search. Auto-walks to invokable ancestor for non-invokable elements. +- `ui set-value "value" -a ` — set text or slider value +- `ui focus -a ` — move keyboard focus +- `ui scroll-into-view -a ` — scroll element visible +- `ui scroll -a --direction down` — scroll a container (up/down/left/right, --to top/bottom) +- `ui wait-for -a --timeout [--gone] [--value Y] [--property X --value Y]` — wait for element value or property match +- `ui list-windows -a ` — list windows, popups, and dialogs with HWNDs +- `ui get-focused -a ` — show the element with keyboard focus + +## Framework-specific guidance + +### Electron +- **Setup:** `winapp init --use-defaults` → `winapp node create-addon --template cs` (or `--template cpp`) → `winapp node add-electron-debug-identity` +- **Package:** Build with your packager (e.g., Electron Forge), then `winapp package --cert .\devcert.pfx` +- Use `winapp node create-addon` to create native C#/C++ addons for Windows APIs +- Use `winapp node add-electron-debug-identity` / `clear-electron-debug-identity` for identity management +- **⚠️ Always run `npx winapp node add-electron-debug-identity` before testing any Windows API that requires package identity** — without this, APIs will fail at runtime +- Guide: https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/setup.md + +### .NET (WPF, WinForms, Console) +- **Setup:** `winapp init --use-defaults` — but if you already have a `Package.appxmanifest` (e.g., WinUI 3 apps), you likely **don't need `winapp init`**. Just ensure your `.csproj` references the `Microsoft.WindowsAppSDK` NuGet package and has the right properties for packaged builds. +- **Run with identity:** `winapp init` auto-adds the `Microsoft.Windows.SDK.BuildTools.WinApp` NuGet package, so just `dotnet run` registers a loose layout package and launches with identity. Without the NuGet package, build with `dotnet build -c Debug -p:Platform=x64`, then `winapp run bin\x64\Debug\\win-x64\`. Replace `` with your target framework (e.g., `net10.0-windows10.0.26100.0`) and adjust architecture as needed. +- **Package:** `dotnet build -c Release -p:Platform=x64`, then `winapp package bin\x64\Release\\win-x64\ --cert devcert.pfx` +- No native addons needed — .NET has direct Windows API access via `Microsoft.Windows.SDK.NET.Ref` +- Guide: https://github.com/microsoft/WinAppCli/blob/main/docs/guides/dotnet.md + +### C++ +- **Setup:** `winapp init --setup-sdks stable` — downloads Windows SDK + App SDK and generates CppWinRT projections +- **Build:** Add `.winapp/packages` include paths to CMakeLists.txt or MSBuild. CppWinRT headers in `.winapp/generated/include`, response file at `.cppwinrt.rsp` +- **Package:** `winapp package build/release --cert devcert.pfx` +- Guide: https://github.com/microsoft/WinAppCli/blob/main/docs/guides/cpp.md + +### Rust +- **Setup:** `winapp init --setup-sdks stable` +- **Package:** `cargo build --release`, then `winapp package target/release --cert devcert.pfx` +- Use `windows-rs` crate for Windows API bindings; winapp handles manifest, identity, and packaging +- Guide: https://github.com/microsoft/WinAppCli/blob/main/docs/guides/rust.md + +### Flutter +- **Setup:** `winapp init --setup-sdks stable` +- **Build:** `flutter build windows` +- **Package:** `winapp package .\build\windows\x64\runner\Release --cert devcert.pfx` +- Guide: https://github.com/microsoft/WinAppCli/blob/main/docs/guides/flutter.md + +### Tauri +- **Setup:** `winapp init --use-defaults` +- **Package:** Build with Tauri, then `winapp package` for MSIX distribution +- Tauri has its own `.msi` bundler; use winapp specifically for MSIX and package identity features +- Guide: https://github.com/microsoft/WinAppCli/blob/main/docs/guides/tauri.md + +## Common end-to-end workflows + +### Add winapp to an existing project +```bash +# User already has a project (Electron, .NET, C++, etc.) +winapp init . # Add Windows platform files (interactive) +# ... build your app ... +winapp cert generate --manifest . # Create dev certificate +winapp package ./dist --cert ./devcert.pfx # Package and sign +winapp cert install ./devcert.pfx # Trust cert (admin required, one-time) +``` + +### Run and debug with package identity +```bash +winapp init . # If not already set up +# ... build your app ... +winapp run ./bin/Debug # Register loose layout package + launch +# Your app runs as if MSIX-installed, with full package identity +``` + +### Add sparse package identity (Electron or separate exe) +```bash +winapp init . # If not already set up +# ... build your app ... +winapp create-debug-identity ./myapp.exe # Register sparse package for exe +# Launch your exe normally — it now has package identity +``` + +### Clone and build existing project +```bash +winapp restore # Reinstall packages from winapp.yaml +# ... build and package as normal ... +``` + +### CI/CD pipeline +```bash +winapp restore --quiet # Restore packages (non-interactive) +# ... build step ... +winapp package ./dist --cert $CERT_PATH --cert-password $CERT_PWD --quiet +``` + +## Error diagnosis + +When the user encounters an error, check these common causes: + +| Symptom | Likely cause | Resolution | +|---------|-------------|------------| +| "winapp.yaml not found" | Running `restore`/`update` without prior `init` | Run `winapp init` first, or check working directory | +| "appxmanifest.xml not found" | Running `package`/`create-debug-identity` without manifest | Run `winapp init` or `winapp manifest generate` first | +| "Publisher mismatch" | Certificate subject ≠ manifest Publisher | Regenerate cert with `--manifest` flag | +| "Access denied" / "elevation required" | `cert install` without admin | Run terminal as Administrator | +| "Package installation failed" | Stale registration or untrusted cert | Run `Get-AppxPackage \| Remove-AppxPackage`, ensure cert is trusted | +| "Certificate not trusted" | Dev cert not installed | Run `winapp cert install ./devcert.pfx` as admin | +| "Build tools not found" | First run, tools not downloaded | winapp auto-downloads tools; ensure internet access | +| Windows APIs fail at runtime | Debug identity not registered | Register debug identity after build and before launching: `winapp create-debug-identity ` (or `npx winapp node add-electron-debug-identity` for Electron) — this is **mandatory** for any app using identity-requiring APIs | + +## Key files and concepts + +- **`winapp.yaml`** — Project config tracking SDK versions and settings. Created by `init`, read by `restore`/`update`. Not required for .NET projects that already have the right NuGet package references in their `.csproj` — winapp auto-detects SDK versions from `.csproj` as a fallback. +- **`appxmanifest.xml`** — MSIX package manifest defining app identity, capabilities, and visual assets. Required for packaging and identity. +- **`Assets/`** — Icon and tile images referenced by the manifest. Generated by `init` or `manifest generate`. +- **`.winapp/`** — Local directory with downloaded SDK packages, generated headers, and libs. Gitignored. +- **`devcert.pfx`** — Self-signed development certificate for local testing. Never use in production. +- **Sparse package** — A lightweight package registration that gives a desktop app package identity without full MSIX deployment. The exe stays in its original location; Windows associates identity with it via `Add-AppxPackage -ExternalLocation`. Used by `create-debug-identity`. Best for scenarios where the exe is separate from the app code (e.g., Electron). +- **Loose layout package** — A folder-based package registered with Windows via `Add-AppxPackage`, simulating a full MSIX install without creating an `.msix` file. Used by `winapp run`. The preferred approach for most frameworks during development. +- **Package identity** — A Windows concept that enables certain APIs (notifications, background tasks, share target). Obtained via full MSIX packaging, loose layout registration (`winapp run`), or sparse package registration (`create-debug-identity`). diff --git a/.claude/skills/winapp-frameworks/SKILL.md b/.claude/skills/winapp-frameworks/SKILL.md new file mode 100644 index 00000000..5b69b7b0 --- /dev/null +++ b/.claude/skills/winapp-frameworks/SKILL.md @@ -0,0 +1,116 @@ +--- +name: winapp-frameworks +description: Framework-specific Windows development guidance for Electron, .NET (WPF, WinForms), C++, Rust, Flutter, and Tauri. Use when packaging or adding Windows features to an Electron app, .NET desktop app, Flutter app, Tauri app, Rust app, or C++ app. +version: 0.3.3 +--- +## When to use + +Use this skill when: +- **Working with a specific app framework** and need to know the right winapp workflow +- **Choosing the correct install method** (npm package vs. standalone CLI) +- **Looking for framework-specific guides** for step-by-step setup, build, and packaging + +Each framework has a detailed guide — refer to the links below rather than trying to guess commands. + +## Framework guides + +| Framework | Install method | Guide | +|-----------|---------------|-------| +| **Electron** | `npm install --save-dev @microsoft/winappcli` | [Electron setup guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/setup.md) | +| **.NET** (WPF, WinForms, Console) | `winget install Microsoft.winappcli` | [.NET guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/dotnet.md) | +| **C++** (CMake, MSBuild) | `winget install Microsoft.winappcli` | [C++ guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/cpp.md) | +| **Rust** | `winget install Microsoft.winappcli` | [Rust guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/rust.md) | +| **Flutter** | `winget install Microsoft.winappcli` | [Flutter guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/flutter.md) | +| **Tauri** | `winget install Microsoft.winappcli` | [Tauri guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/tauri.md) | + +## Key differences by framework + +### Electron (npm package) +Use the **npm package** (`@Microsoft/WinAppCli`), **not** the standalone CLI. The npm package includes: +- The native winapp CLI binary bundled inside `node_modules` +- A Node.js SDK with helpers for creating native C#/C++ addons +- Electron-specific commands under `npx winapp node` + +Quick start: +```powershell +npm install --save-dev @microsoft/winappcli +npx winapp init . --use-defaults +npx winapp node create-addon --template cs # create a C# native addon +npx winapp node add-electron-debug-identity # register identity for debugging +``` + +Additional Electron guides: +- [Packaging guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/packaging.md) +- [C++ notification addon guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/cpp-notification-addon.md) +- [WinML addon guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/winml-addon.md) +- [Phi Silica addon guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/phi-silica-addon.md) + +### .NET (WPF, WinForms, Console) +.NET projects have direct access to Windows APIs. Key differences: +- Projects with NuGet references to `Microsoft.Windows.SDK.BuildTools` or `Microsoft.WindowsAppSDK` **don't need `winapp.yaml`** — winapp auto-detects SDK versions from the `.csproj` +- The key prerequisite is `Package.appxmanifest`, not `winapp.yaml` +- No native addon step needed — unlike Electron, .NET can call Windows APIs directly +- `winapp init` automatically adds the `Microsoft.Windows.SDK.BuildTools.WinApp` NuGet package, enabling `dotnet run` with automatic identity registration + +**If you already have a `Package.appxmanifest`** (e.g., WinUI 3 apps or projects with an existing packaging setup), you likely **don't need `winapp init`** — your project is already configured for packaged builds. Just make sure: +- Your `.csproj` references the `Microsoft.WindowsAppSDK` NuGet package (WinUI 3 apps already have this) +- The project properties are set up for packaged builds (e.g., `MSIX` or equivalent) +- WinUI 3 apps created from Visual Studio templates are typically already fully configured + +Quick start: +```powershell +winapp init . --use-defaults +dotnet build -c Debug -p:Platform=x64 +winapp run bin\x64\Debug\\win-x64\ +``` + +Replace `` with your target framework (e.g., `net10.0-windows10.0.26100.0`), and adjust `x64` to match your target architecture. + +### C++ (CMake, MSBuild) +C++ projects use winapp primarily for SDK projections (CppWinRT headers) and packaging: +- `winapp init --setup-sdks stable` downloads Windows SDK + App SDK and generates CppWinRT headers +- Headers generated in `.winapp/generated/include` +- Response file at `.cppwinrt.rsp` for build system integration +- Add `.winapp/packages` to include/lib paths in your build system + +### Rust +- Use the `windows` crate for Windows API bindings +- winapp handles manifest, identity, packaging, and certificate management +- Typical build output: `target/release/myapp.exe` + +### Flutter +- Flutter handles the build (`flutter build windows`) +- winapp handles manifest, identity, packaging +- Build output: `build\windows\x64\runner\Release\` + +### Tauri +- Tauri has its own bundler for `.msi` installers +- Use winapp specifically for **MSIX distribution** and package identity features +- winapp adds capabilities beyond what Tauri's built-in bundler provides (identity, sparse packages, Windows API access) + +## Debugging by framework + +| Framework | Recommended command | Notes | +|-----------|-------------------|-------| +| **.NET** | `winapp run .\bin\x64\Debug\\win-x64\` | Build with `dotnet build -c Debug -p:Platform=x64` first; GUI apps launch directly; console apps need `--with-alias` | +| **C++** | `winapp run .\build\Debug --with-alias` | Console apps need `--with-alias` + `uap5:ExecutionAlias` in manifest | +| **Rust** | `winapp run .\target\debug --with-alias` | Console apps need `--with-alias` + `uap5:ExecutionAlias` in manifest | +| **Flutter** | `winapp run .\build\windows\x64\runner\Debug` | GUI app — plain `winapp run` works | +| **Tauri** | `winapp run .\dist` | Stage exe to `dist/` first (avoids copying entire `target/` tree); GUI app | +| **Electron** | `npx winapp node add-electron-debug-identity` | Uses Electron-specific identity registration; `winapp run` is **not** recommended for Electron | + +**Key rules:** +- **GUI apps** (Flutter, Tauri, WPF): use `winapp run ` — launches via AUMID activation +- **Console apps** (C++, Rust, .NET console): use `winapp run --with-alias` — launches via execution alias to preserve stdin/stdout. Requires `uap5:ExecutionAlias` in `Package.appxmanifest` +- **Electron**: different mechanism — uses `npx winapp node add-electron-debug-identity` because `electron.exe` is in `node_modules/`, not your build output +- **Startup debugging (any framework)**: use `winapp create-debug-identity ` so your IDE can F5-launch the exe with identity from the first instruction + +For full debugging scenarios and IDE setup, see the [Debugging Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/debugging.md). + +## Related skills +- **Setup**: `winapp-setup` — initial project setup with `winapp init` +- **Manifest**: `winapp-manifest` — creating and customizing `Package.appxmanifest` +- **Signing**: `winapp-signing` — certificate generation and management +- **Packaging**: `winapp-package` — creating MSIX installers from build output +- **Identity**: `winapp-identity` — enabling package identity for Windows APIs during development +- Not sure which command to use? See `winapp-troubleshoot` for a command selection flowchart diff --git a/.claude/skills/winapp-identity/SKILL.md b/.claude/skills/winapp-identity/SKILL.md new file mode 100644 index 00000000..50b0f4fb --- /dev/null +++ b/.claude/skills/winapp-identity/SKILL.md @@ -0,0 +1,167 @@ +--- +name: winapp-identity +description: Enable Windows package identity for desktop apps to access Windows APIs like push notifications, background tasks, share target, and startup tasks. Use when adding Windows notifications, background tasks, or other identity-requiring Windows features to a desktop app. +version: 0.3.3 +--- +## When to use + +Use this skill when: +- **The exe is separate from your app code** — e.g., Electron apps where `electron.exe` is in `node_modules`, not your build output +- **Testing sparse package behavior** specifically — `AllowExternalContent`, `TrustedLaunch`, etc. +- **Registering identity without copying files** — `create-debug-identity` leaves the exe in place + +> **Prefer `winapp run` for most frameworks.** If your exe is inside your build output folder (.NET, C++, Rust, Flutter, Tauri), use `winapp run ` instead — it registers a full loose layout package and launches the app, simulating an MSIX install. Use `create-debug-identity` only when `winapp run` doesn't fit your scenario. + +## Prerequisites + +1. **`Package.appxmanifest`** in your project — from `winapp init` or `winapp manifest generate` +2. **Built executable** — the `.exe` your app runs from + +## What is package identity? + +Windows package identity enables your app to use restricted APIs and OS integration features: +- **Push notifications** (WNS) +- **Background tasks** +- **Share target** / share source +- **App startup tasks** +- **Taskbar pinning** +- **Windows AI APIs** (Phi Silica, OCR, etc.) +- **File type associations** registered properly in Settings + +A standard `.exe` (from `dotnet build`, `cmake`, etc.) does **not** have identity. `create-debug-identity` registers a *sparse package* with Windows — the exe stays in its original location and Windows associates identity with it via `Add-AppxPackage -ExternalLocation`. This is different from `winapp run`, which copies files into a loose layout package. + +## Usage + +### Basic usage + +```powershell +# Register sparse package for your exe (manifest auto-detected from current dir) +winapp create-debug-identity ./bin/Release/myapp.exe + +# Specify manifest location +winapp create-debug-identity ./bin/Release/myapp.exe --manifest ./Package.appxmanifest +``` + +### Keep the original package identity + +```powershell +# By default, '.debug' is appended to the package name to avoid conflicts with +# an installed MSIX version. Use --keep-identity to keep the manifest identity as-is. +winapp create-debug-identity ./myapp.exe --keep-identity +``` + +### Generate without installing + +```powershell +# Create the sparse package layout but don't register it with Windows +winapp create-debug-identity ./myapp.exe --no-install +``` + +## What the command does + +1. **Reads `Package.appxmanifest`** — extracts identity, capabilities, and assets +2. **Creates a sparse package layout** in a temp directory +3. **Appends `.debug`** to the package name (unless `--keep-identity`) to avoid conflicts +4. **Registers with Windows** via `Add-AppxPackage -ExternalLocation` — makes your exe "identity-aware" + +After running, launch your exe normally — Windows will recognize it as having package identity. + +## Recommended workflow + +1. **Setup** — `winapp init . --use-defaults` (creates `Package.appxmanifest`) +2. **Generate development certificate** — `winapp cert generate` +3. **Build** your app +4. **Register identity** — `winapp create-debug-identity ./bin/myapp.exe` +5. **Run** your app — identity-requiring APIs now work +6. **Re-run step 4** whenever you change `Package.appxmanifest` or `Assets/` + +## Tips + +- You must re-run `create-debug-identity` after any changes to `Package.appxmanifest` or image assets +- The debug identity persists across reboots until explicitly removed +- To remove: `Get-AppxPackage *yourapp.debug* | Remove-AppxPackage` +- If you have both a debug identity and an installed MSIX, they may conflict — use `--keep-identity` carefully +- For Electron apps, use `npx winapp node add-electron-debug-identity` instead (handles Electron-specific paths) + +## Debugging: `winapp run` vs `create-debug-identity` + +| | `winapp run` | `create-debug-identity` | +|---|---|---| +| **What it registers** | Full loose layout package (entire folder) | Sparse package (single exe) | +| **How the app launches** | Launched by winapp (AUMID activation or execution alias) | You launch the exe yourself (command line, IDE, etc.) | +| **Simulates MSIX install** | Yes — closest to production behavior | No — sparse identity only | +| **Files stay in place** | Copied to an AppX layout directory | Yes — exe stays at its original path | +| **Debugger-friendly** | Attach to PID after launch, or use `--no-launch` then launch via alias | Launch directly from your IDE's debugger — the exe has identity regardless | +| **Console app support** | `--with-alias` keeps stdin/stdout in terminal | Run exe directly in terminal | +| **Best for** | Most frameworks (.NET, C++, Rust, Flutter, Tauri) | Electron, or when you need full IDE debugger control (F5 startup debugging) | + +### When to use which + +**Default to `winapp run`** for most development — it simulates a real MSIX install with full identity, capabilities, and file associations: + +```powershell +winapp run .\build\output # GUI apps +winapp run .\build\output --with-alias # console apps (preserves stdin/stdout) +``` + +**Use `create-debug-identity` when:** +- **Debugging startup code** — your IDE launches + debugs the exe directly; identity is attached from the first instruction +- **Exe is separate from build output** — e.g., Electron where `electron.exe` is in `node_modules/` +- **Testing sparse package behavior** — `AllowExternalContent`, `TrustedLaunch` + +```powershell +winapp create-debug-identity .\bin\Debug\myapp.exe +# Now launch any way you like — F5, terminal, script — the exe has identity +``` + +### Common debugging scenarios + +| Scenario | Command | Notes | +|----------|---------|-------| +| **Just run with identity** | `winapp run .\build\Debug` | Simplest workflow; add `--with-alias` for console apps | +| **Attach debugger to running app** | `winapp run .\build\Debug`, then attach to PID | Misses startup code | +| **Register identity, launch via AUMID** | `winapp run .\build\Debug --no-launch` | Launch with `start shell:AppsFolder\` or the execution alias (not the exe directly) | +| **F5 startup debugging** | `winapp create-debug-identity .\bin\myapp.exe` | IDE controls process from first instruction; best for debugging activation/startup code | +| **Capture debug output** | `winapp run .\build\Debug --debug-output` | Captures `OutputDebugString`; on crash, writes minidump and analyzes managed exceptions automatically. **Blocks other debuggers** (one debugger per process) | +| **Run and auto-clean** | `winapp run .\build\Debug --unregister-on-exit` | Unregisters the dev package after the app exits | +| **Launch and detach (CI)** | `winapp run .\build\Debug --detach` | Returns immediately after launch; use `--json` to get PID for scripting | +| **Clean up stale registration** | `winapp unregister` | Removes dev packages for the current project (auto-detects from manifest) | + +> **Using Visual Studio with a packaging project?** VS already handles identity, AUMID activation, and debugger attachment from F5. These workflows are most useful for VS Code, terminal-based development, and frameworks VS doesn't natively package (Rust, Flutter, Tauri, Electron, C++). + +For full details including IDE setup examples, see the [Debugging Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/debugging.md). + +## Related skills +- Need a manifest? See `winapp-manifest` to generate `Package.appxmanifest` +- Need a certificate? See `winapp-signing` — a trusted cert is required for identity registration +- Ready for full MSIX distribution? See `winapp-package` to create an installer +- Having issues? See `winapp-troubleshoot` for common error solutions + +## Troubleshooting +| Error | Cause | Solution | +|-------|-------|----------| +| "Package.appxmanifest not found" | No manifest in current directory | Run `winapp init` or `winapp manifest generate`, or pass `--manifest` | +| "Failed to add package identity" | Previous registration stale or cert untrusted | Run `winapp unregister` to remove stale packages, then `winapp cert install ./devcert.pfx` (admin) | +| "Access denied" | Cert not trusted or permission issue | Run `winapp cert install ./devcert.pfx` as admin | +| APIs still fail after registration | App launched before registration completed | Close app, re-run `create-debug-identity`, then relaunch | + + +## Command Reference + +### `winapp create-debug-identity` + +Enable package identity for debugging without creating full MSIX. Required for testing Windows APIs (push notifications, share target, etc.) during development. Example: winapp create-debug-identity ./myapp.exe. Requires Package.appxmanifest or appxmanifest.xml in current directory or passed via --manifest. Re-run after changing the manifest or Assets/. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Path to the .exe that will need to run with identity, or entrypoint script. | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--keep-identity` | Keep the package identity from the manifest as-is, without appending '.debug' to the package name and application ID. | (none) | +| `--manifest` | Path to the Package.appxmanifest or appxmanifest.xml | (none) | +| `--no-install` | Do not install the package after creation. | (none) | diff --git a/.claude/skills/winapp-manifest/SKILL.md b/.claude/skills/winapp-manifest/SKILL.md new file mode 100644 index 00000000..933e345c --- /dev/null +++ b/.claude/skills/winapp-manifest/SKILL.md @@ -0,0 +1,233 @@ +--- +name: winapp-manifest +description: Create and edit Windows app manifest files (Package.appxmanifest or appxmanifest.xml) that define app identity, capabilities, and visual assets, or generate new assets from existing images. Use when creating a Windows app manifest for any app type (GUI, console, CLI tool, service), adding Windows capabilities, generating new app icons and assets, or adding execution aliases, file associations, protocol handlers, or other app extensions. +version: 0.3.3 +--- +## When to use + +Use this skill when: +- **Creating `Package.appxmanifest`** for a project that doesn't have one yet +- **Generating app icon assets** from a single source image +- **Understanding manifest structure** for package identity and capabilities + +## Prerequisites + +- winapp CLI installed +- Optional: a source image (PNG or SVG, at least 400x400 pixels) for custom app icons + +## Key concepts + +**`Package.appxmanifest`** is the key prerequisite for most winapp commands — it's more important than `winapp.yaml`. It declares: +- **Package identity** — name, publisher, version +- **App entry point** — which executable to launch +- **Capabilities** — what the app can access (internet, file system, etc.) +- **Visual assets** — icons for Start menu, taskbar, installers +- **Extensions** — share target, startup tasks, file associations, etc. + +**Two manifest templates:** +- **`packaged`** (default) — for full MSIX distribution +- **`sparse`** — for desktop apps that need package identity without full MSIX containment (uses `AllowExternalContent`) + +**`winapp init` also generates a manifest** as part of full project setup. Use `winapp manifest generate` when you only need the manifest without SDK setup or `winapp.yaml`. + +## Usage + +### Generate a new manifest + +```powershell +# Defaults — uses current folder name, current user as publisher +winapp manifest generate + +# Into a specific directory +winapp manifest generate ./my-project + +# Customize identity +winapp manifest generate --package-name "MyApp" --publisher-name "CN=Contoso" --version "2.0.0.0" + +# Set entry point and description +winapp manifest generate --executable myapp.exe --description "My awesome app" + +# Generate a sparse manifest (for desktop apps needing identity without full MSIX) +winapp manifest generate --template sparse + +# Overwrite existing manifest +winapp manifest generate --if-exists overwrite +``` + +Output: +- `Package.appxmanifest` — the manifest file +- `Assets/` — default app icons in required sizes (Square44x44Logo, Square150x150Logo, Wide310x150Logo, etc.) + +### Update app icons from a source image + +```powershell +# Generate all required icon sizes from one source image +winapp manifest update-assets ./my-logo.png + +# SVG source images produce the best quality at all sizes +winapp manifest update-assets ./my-logo.svg + +# Specify manifest location (if not in current directory) +winapp manifest update-assets ./my-logo.png --manifest ./path/to/Package.appxmanifest + +# Generate light theme variants from a separate image +winapp manifest update-assets ./my-logo.png --light-image ./my-logo-light.png + +# Use the same image for both (generates all MRT light theme qualifiers) +winapp manifest update-assets ./my-logo.png --light-image ./my-logo.png +``` + +The source image should be at least 400x400 pixels (PNG or SVG recommended). The command reads the manifest to determine which asset sizes are needed and generates: +- **5 scale variants** per asset (100%, 125%, 150%, 200%, 400%) +- **14 plated + 14 unplated targetsize variants** for the app icon (44x44) +- **app.ico** — multi-resolution ICO file for shell integration. If an existing `.ico` file is present in the assets directory, it is replaced in-place (preserving the original filename) +- With `--light-image`: light theme variants using the correct MRT qualifiers per asset type + +### Add an execution alias + +Execution aliases let users launch the app by typing its name in a terminal (e.g. `myapp`). + +```powershell +# Add alias inferred from the Executable attribute in the manifest +winapp manifest add-alias + +# Specify the alias name explicitly +winapp manifest add-alias --name myapp + +# Target a specific manifest file +winapp manifest add-alias --manifest ./path/to/Package.appxmanifest +``` + +This adds a `uap5:AppExecutionAlias` extension to the manifest. If the alias already exists, the command reports it and exits successfully. + +> **When combined with `winapp run --with-alias`** or the `WinAppRunUseExecutionAlias` MSBuild property, this enables apps to run in the current terminal with inherited stdin/stdout/stderr instead of opening a new window. + +### Add an execution alias + +Execution aliases let users launch the app by typing its name in a terminal (e.g. `myapp`). + +```powershell +# Add alias inferred from the Executable attribute in the manifest +winapp manifest add-alias + +# Specify the alias name explicitly +winapp manifest add-alias --name myapp + +# Target a specific manifest file +winapp manifest add-alias --manifest ./path/to/Package.appxmanifest +``` + +This adds a `uap5:AppExecutionAlias` extension to the manifest. If the alias already exists, the command reports it and exits successfully. + +## Manifest structure overview + +A typical `Package.appxmanifest` looks like: + +```xml + + + + + My App + My Publisher + Assets\StoreLogo.png + + + + + + + + + + + + + +``` + +Key fields to edit: +- `Identity.Name` — unique package name (no spaces) +- `Identity.Publisher` — must match your certificate exactly +- `Application.Executable` — your app's exe filename +- `Capabilities` — add capabilities as needed (`internetClient`, `broadFileSystemAccess`, etc.) + +## Tips + +- Always ensure `Identity.Publisher` matches your signing certificate — use `winapp cert generate --manifest` to auto-match +- The `sparse` template adds `uap10:AllowExternalContent="true"` for apps that need identity but run outside the MSIX container +- You can manually edit `Package.appxmanifest` after generation — it's a standard XML file +- Image assets must match the paths referenced in the manifest — `update-assets` handles this automatically +- For logos, transparent PNGs or SVGs work best. SVG source images are rendered as vectors directly at each target size, producing pixel-perfect results. Use a square image for best results across all sizes. +- **`$targetnametoken$` placeholder:** When `winapp manifest generate` creates `Package.appxmanifest`, it sets `Application.Executable` to `$targetnametoken$.exe` by default. This is a valid placeholder that gets automatically resolved by `winapp package --executable ` at packaging time — you rarely need to override it during manifest generation. If `--executable` is provided to `winapp manifest generate`, winapp reads `FileVersionInfo` from the actual exe to auto-fill package name, description, publisher, and extract an icon, so the exe must already exist on disk. + +## Related skills + +- After generating a manifest, see `winapp-signing` for certificate setup and `winapp-package` to create the MSIX installer +- Not sure which command to use? See `winapp-troubleshoot` for a command selection flowchart + +## Troubleshooting +| Error | Cause | Solution | +|-------|-------|----------| +| "Manifest already exists" | `Package.appxmanifest` present | Use `--if-exists overwrite` to replace, or edit existing file directly | +| "Invalid source image" | Image too small or wrong format | Use PNG or SVG, at least 400x400 pixels | +| "Publisher mismatch" during packaging | Manifest publisher ≠ cert publisher | Edit `Identity.Publisher` in manifest, or regenerate cert with `--manifest` | + + +## Command Reference + +### `winapp manifest generate` + +Create Package.appxmanifest without full project setup. Use when you only need a manifest and image assets (no SDKs, no certificate). For full setup, use 'init' instead. Templates: 'packaged' (full MSIX), 'sparse' (desktop app needing Windows APIs). + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Directory to generate manifest in | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--description` | Human-readable app description shown during installation and in Windows Settings | `My Application` | +| `--executable` | Path to the application's executable. Default: .exe | (none) | +| `--if-exists` | Behavior when output file exists: 'error' (fail, default), 'skip' (keep existing), or 'overwrite' (replace) | `Error` | +| `--logo-path` | Path to logo image file | (none) | +| `--package-name` | Package name (default: folder name) | (none) | +| `--publisher-name` | Publisher CN (default: CN=) | (none) | +| `--template` | Manifest template type: 'packaged' (full MSIX app, default) or 'sparse' (desktop app with package identity for Windows APIs) | `Packaged` | +| `--version` | App version in Major.Minor.Build.Revision format (e.g., 1.0.0.0). | `1.0.0.0` | + +### `winapp manifest update-assets` + +Generate new assets for images referenced in a Package.appxmanifest from a single source image. Source image should be at least 400x400 pixels. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | Yes | Path to source image file (SVG, PNG, ICO, JPG, BMP, GIF) | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--light-image` | Path to source image for light theme variants (SVG, PNG, ICO, JPG, BMP, GIF) | (none) | +| `--manifest` | Path to Package.appxmanifest or appxmanifest.xml file (default: search current directory) | (none) | + +### `winapp manifest add-alias` + +Add an execution alias (uap5:AppExecutionAlias) to a Package.appxmanifest. This allows launching the packaged app from the command line by typing the alias name. By default, the alias is inferred from the Executable attribute (e.g. $targetnametoken$.exe becomes $targetnametoken$.exe alias). + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app-id` | Application Id to add the alias to (default: first Application element) | (none) | +| `--manifest` | Path to Package.appxmanifest or appxmanifest.xml file (default: search current directory) | (none) | +| `--name` | Alias name (e.g. 'myapp.exe'). Default: inferred from the Executable attribute in the manifest. | (none) | diff --git a/.claude/skills/winapp-package/SKILL.md b/.claude/skills/winapp-package/SKILL.md new file mode 100644 index 00000000..268e0e0d --- /dev/null +++ b/.claude/skills/winapp-package/SKILL.md @@ -0,0 +1,240 @@ +--- +name: winapp-package +description: Package a Windows app as an MSIX installer for distribution or testing. Use when creating a Windows installer, packaging an Electron/Flutter/.NET/Rust/C++/Tauri app for Windows, building an MSIX, distributing a desktop app, packaging a console app or CLI tool, or adding MSIX packaging to a build script or CI/CD pipeline. +version: 0.3.3 +--- +## When to use + +Use this skill when: +- **Creating an MSIX installer** from a built app for distribution or testing +- **Packaging any Windows app** — GUI apps, console apps, CLI tools, services, or background processes +- **Signing a package** with a development or production certificate +- **Bundling the Windows App SDK runtime** for self-contained deployment + +## Prerequisites + +Before packaging, you need: +1. **Built app output** in a folder (e.g., `bin/Release/`, `dist/`, `build/`) +2. **`Package.appxmanifest`** — from `winapp init` or `winapp manifest generate` +3. **Certificate** (optional) — `devcert.pfx` from `winapp cert generate` for signing + +## Usage + +### Basic packaging (unsigned) + +```powershell +# Package from build output — manifest auto-detected from current dir or input folder +winapp package ./bin/Release + +# Specify manifest location explicitly +winapp package ./dist --manifest ./Package.appxmanifest +``` + +### Package and sign in one step + +```powershell +# Sign with existing certificate +winapp package ./bin/Release --cert ./devcert.pfx + +# Custom certificate password +winapp package ./bin/Release --cert ./devcert.pfx --cert-password MyP@ssw0rd +``` + +### Generate certificate + package in one step + +```powershell +# Auto-generate cert, sign, and package +winapp package ./bin/Release --generate-cert + +# Also install the cert to trust it on this machine (requires admin) +winapp package ./bin/Release --generate-cert --install-cert +``` + +### Self-contained deployment + +```powershell +# Bundle Windows App SDK runtime so users don't need it installed (must have winappsdk reference in the winapp.yaml or *.csproj) +winapp package ./bin/Release --cert ./devcert.pfx --self-contained +``` + +### Custom output path and name + +```powershell +# Specify output file +winapp package ./dist --output ./releases/myapp-v1.0.msix --cert ./devcert.pfx + +# Custom package name +winapp package ./dist --name "MyApp_1.0.0_x64" --cert ./devcert.pfx +``` + +## What the command does + +1. **Locates `Package.appxmanifest`** — looks in input folder, then current directory (or uses `--manifest`) +2. **Copies manifest + assets** into a staging layout alongside your app files +3. **Discovers manifest-referenced files** — any non-image file referenced in the manifest (e.g., AppExtension payloads like `manifest.json`, config files) is automatically copied from the manifest directory or input folder if missing from staging +4. **Generates `resources.pri`** — Package Resource Index for UWP-style resource lookup (skip with `--skip-pri`) +5. **Runs `makeappx pack`** — creates the `.msix` package file +6. **Signs the package** (if `--cert` provided) — calls `signtool` with your certificate + +Output: a `.msix` file that can be installed on Windows via double-click or `Add-AppxPackage`. + +## Installing the MSIX for testing + +```powershell +# Trust the dev certificate first (one-time, requires admin) +winapp cert install ./devcert.pfx + +# Install the MSIX +Add-AppxPackage ./myapp.msix + +# Uninstall if needed +Get-AppxPackage *myapp* | Remove-AppxPackage +``` + +## Recommended workflow + +1. **Build** your app (`dotnet build`, `cmake --build`, `npm run make`, etc.) +2. **Package** — `winapp package --cert ./devcert.pfx` +3. **Trust cert** (first time) — `winapp cert install ./devcert.pfx` (admin) +4. **Install** — double-click the `.msix` or `Add-AppxPackage ./myapp.msix` +5. **Test** the installed app from the Start menu + +### Advanced: External content catalog + +For sparse packages with `AllowExternalContent`, you may need a code integrity catalog: + +```powershell +# Generate CodeIntegrityExternal.cat for external executables +winapp create-external-catalog "./bin/Release" + +# Include subdirectories and specify output path +winapp create-external-catalog "./bin/Release" --recursive --output ./catalog/CodeIntegrityExternal.cat +``` + +### Bundling multiple architectures + +Create an MSIX bundle from multiple per-architecture build outputs: + +```powershell +# Create unsigned bundle for Store submission (x64 + arm64) +winapp package ./publish/x64 ./publish/arm64 + +# Create signed bundle for sideloading +winapp package ./publish/x64 ./publish/arm64 --cert ./devcert.pfx + +# Self-contained bundle with Windows App SDK runtime per arch +winapp package ./publish/x64 ./publish/arm64 --self-contained --generate-cert +``` + +**How it works:** When multiple input folders are passed, `winapp package`: +1. Detects the architecture of each folder's primary executable from its PE header +2. Resolves a manifest for each slice (see below) +3. Validates that all slices share the same Identity, Capabilities, and Dependencies +4. Packs each folder into an intermediate unsigned `.msix` +5. Bundles them into a single `.msixbundle` using `makeappx bundle` +6. Signs only the bundle (not individual slices) — the signature covers all packages inside + +**Manifest resolution:** Each slice needs a manifest. Resolution order: +- `--manifest ` uses one manifest for all slices (architecture auto-stamped per folder) +- Per-folder `Package.appxmanifest` if present in the input folder +- Fallback to `Package.appxmanifest` in the current working directory + +The `ProcessorArchitecture` is always force-set to the detected architecture per-slice. All other Identity fields must be consistent across slices. + +**Output:** `___.msixbundle` (architectures sorted alphabetically). + +**Store submission:** An unsigned bundle is valid for Store upload — Partner Center signs it with your reserved identity certificate. For sideloading, pass `--cert` or `--generate-cert`. + +This hashes executables in the specified directories so Windows trusts them when running with sparse package identity. + +## CI/CD + +### GitHub Actions + +Use the `microsoft/setup-winapp` action to install winapp on GitHub-hosted runners: + +```yaml +- uses: microsoft/setup-winapp@v1 + +- name: Package + run: winapp package ./dist --cert ${{ secrets.CERT_PATH }} --cert-password ${{ secrets.CERT_PASSWORD }} --quiet +``` + +**Tips for CI/CD pipelines:** +- Use `--quiet` (or `-q`) to suppress progress output +- Use `--if-exists skip` with `winapp cert generate` to avoid regenerating existing certificates +- Store your PFX certificate as a repository secret and decode it in CI +- Use `--use-defaults` (or `--no-prompt`) with `winapp init` to avoid interactive prompts + +## Tips + +- The `package` command aliases to `pack` — both work identically +- `Package.appxmanifest` Publisher must match the certificate publisher — use `winapp cert generate --manifest` to ensure they match +- Use `--skip-pri` if your app doesn't use Windows resource loading (e.g., most Electron/Rust/C++ apps without UWP resources) +- For framework-specific packaging paths (Electron, .NET, Rust, etc.), see the `winapp-frameworks` skill +- The `--executable` flag overrides the entry point in the manifest — useful when your exe name differs from what's in `Package.appxmanifest` +- For production distribution, use a certificate from a trusted CA and add `--timestamp` when signing with `winapp sign` + +## Related skills +- Need a manifest first? See `winapp-manifest` to generate `Package.appxmanifest` +- Need a certificate? See `winapp-signing` for certificate generation and management +- Having issues? See `winapp-troubleshoot` for a command selection flowchart and error solutions + +## Troubleshooting +| Error | Cause | Solution | +|-------|-------|----------| +| "Package.appxmanifest not found" | No manifest in input folder or current dir | Run `winapp init` or `winapp manifest generate` first | +| "Publisher mismatch" | Cert publisher ≠ manifest publisher | Regenerate cert with `winapp cert generate --manifest`, or edit manifest | +| "Package installation failed" | Cert not trusted or stale package | Run `winapp cert install ./devcert.pfx` (admin), then `Get-AppxPackage \| Remove-AppxPackage` | +| "makeappx not found" | Build tools not downloaded | Run `winapp update` or `winapp tool makeappx --help` to trigger download | + + +## Command Reference + +### `winapp package` + +Create MSIX installer from your built app. Run after building your app. A manifest (Package.appxmanifest or appxmanifest.xml) is required for packaging - it must be in current working directory, passed as --manifest or be in the input folder. Use --cert devcert.pfx to sign for testing. Example: winapp package ./dist --manifest Package.appxmanifest --cert ./devcert.pfx + +**Aliases:** `pack` + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | Yes | One or more input folders with package layout. Pass multiple folders to create an MSIX bundle (e.g., winapp pack ./publish/x64 ./publish/arm64). | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--cert` | Path to signing certificate (will auto-sign if provided) | (none) | +| `--cert-password` | Certificate password (default: password) | `password` | +| `--executable` | Path to the executable relative to the input folder. | (none) | +| `--generate-cert` | Generate a new development certificate | (none) | +| `--install-cert` | Install certificate to machine | (none) | +| `--manifest` | Path to AppX manifest file (default: auto-detect from input folder or current directory) | (none) | +| `--name` | Package name (default: from manifest) | (none) | +| `--output` | Output file name for the generated package (.msix) or bundle (.msixbundle). Defaults to __.msix for single packages, or ___.msixbundle for bundles. | (none) | +| `--publisher` | Publisher name for certificate generation | (none) | +| `--self-contained` | Bundle Windows App SDK runtime for self-contained deployment | (none) | +| `--skip-pri` | Skip PRI file generation | (none) | + +### `winapp create-external-catalog` + +Generates a CodeIntegrityExternal.cat catalog file with hashes of executable files from specified directories. Used with the TrustedLaunch flag in MSIX sparse package manifests (AllowExternalContent) to allow execution of external files not included in the package. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | Yes | List of input folders with executable files to process (separated by semicolons) | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--compute-flat-hashes` | Include flat hashes when generating the catalog | (none) | +| `--if-exists` | Behavior when output file already exists | `Error` | +| `--output` | Output catalog file path. If not specified, the default CodeIntegrityExternal.cat name is used. | (none) | +| `--recursive` | Include files from subdirectories | (none) | +| `--use-page-hashes` | Include page hashes when generating the catalog | (none) | diff --git a/.claude/skills/winapp-setup/SKILL.md b/.claude/skills/winapp-setup/SKILL.md new file mode 100644 index 00000000..00c0e65b --- /dev/null +++ b/.claude/skills/winapp-setup/SKILL.md @@ -0,0 +1,233 @@ +--- +name: winapp-setup +description: Set up a Windows app project for MSIX packaging, Windows SDK access, or Windows API usage. Use when adding Windows support to an Electron, .NET, C++, Rust, Flutter, or Tauri project, or restoring SDK packages after cloning. +version: 0.3.3 +--- +## When to use + +Use this skill when: +- **Adding Windows platform support** to an existing project (Electron, .NET, C++, Rust, Flutter, Tauri, etc.) +- **Cloning a repo** that already uses winapp and need to restore SDK packages +- **Updating SDK versions** to get the latest Windows SDK or Windows App SDK + +## Prerequisites + +Install the winapp CLI before running any commands: + +```powershell +# Via winget (recommended for non-Node projects) +winget install Microsoft.WinAppCli --source winget + +# Via npm (recommended for Electron/Node projects — includes Node.js SDK) +npm install --save-dev @microsoft/winappcli +``` + +You need an **existing app project** — `winapp init` does **not** create new projects, it adds Windows platform files to your existing codebase. + +> **Already have a `Package.appxmanifest`?** .NET projects that already have a packaging manifest (e.g., WinUI 3 apps or projects with an existing MSIX packaging setup) likely **don't need `winapp init`**. Ensure your `.csproj` references the `Microsoft.WindowsAppSDK` NuGet package and has the right properties for packaged builds (e.g., `MSIX`). WinUI 3 apps created from Visual Studio templates are typically already fully configured — you can go straight to building and using `winapp run` or `winapp package`. + +## Key concepts + +**`Package.appxmanifest`** is the most important file winapp creates — it declares your app's identity, capabilities, and visual assets. Most winapp commands require it (`package`, `run`, `cert generate --manifest`). + +**`winapp.yaml`** is only needed for SDK version management via `restore`/`update`. Projects that already reference Windows SDK packages (e.g., via NuGet in a `.csproj`) can use winapp commands without it. + +**`.winapp/`** is the local folder where SDK packages and generated projections (e.g., CppWinRT headers) are stored. This folder is `.gitignore`d — team members recreate it via `winapp restore`. + +## Usage + +### Initialize a new winapp project + +```powershell +# Interactive — prompts for app name, publisher, SDK channel, etc. +# Automatically searches for compatible projects (Tauri, Electron, .NET, Rust, C++, Flutter) +winapp init + +# Non-interactive — accepts all defaults (stable SDKs, current folder name as app name) +winapp init . --use-defaults + +# Skip SDK installation (just manifest + config) +winapp init . --use-defaults --setup-sdks none + +# Install preview SDKs instead of stable +winapp init . --use-defaults --setup-sdks preview +``` + +After `init`, your project will contain: +- `Package.appxmanifest` — package identity and capabilities +- `Assets/` — default app icons (Square44x44Logo, Square150x150Logo, etc.) +- `winapp.yaml` — SDK version pinning for `restore`/`update` +- `.winapp/` — downloaded SDK packages and generated projections +- `.gitignore` update — excludes `.winapp/` and `devcert.pfx` + +### Restore after cloning + +```powershell +# Reinstall SDK packages from existing winapp.yaml (does not change versions) +winapp restore + +# Restore into a specific directory +winapp restore ./my-project +``` + +Use `restore` when you clone a repo that already has `winapp.yaml` but no `.winapp/` folder. + +### Update SDK versions + +```powershell +# Check for and install latest stable SDK versions +winapp update + +# Switch to preview channel +winapp update --setup-sdks preview +``` + +This updates `winapp.yaml` with the latest versions and reinstalls packages. + +### Run and debug with identity + +```powershell +# Register debug identity and launch app from build output +winapp run ./bin/Debug + +# Launch with custom manifest and pass arguments to the app +winapp run ./dist --manifest ./out/Package.appxmanifest --args "--my-flag value" + +# Pass arguments after -- to avoid escaping (equivalent to --args) +winapp run ./bin/Debug -- --my-flag value + +# Register identity without launching (useful for attaching a debugger manually) +winapp run ./bin/Debug --no-launch + +# Launch and capture OutputDebugString messages and crash diagnostics +# Note: prevents other debuggers (VS, VS Code) from attaching — use --no-launch if you need those instead +winapp run ./bin/Debug --debug-output +``` + +Use `winapp run` during iterative development — it creates a loose layout package, registers a debug identity, and launches the app in one step. For identity-only registration without loose layout, use `winapp create-debug-identity` instead. + + +#### Choosing between `run` and `create-debug-identity` + +| | `winapp run` | `create-debug-identity` | +|---|---|---| +| **Registers** | Full loose layout package (entire folder) | Sparse package (single exe) | +| **App launch** | Winapp launches via AUMID or alias | You launch the exe yourself | +| **Simulates MSIX** | Yes — closest to production | No — identity only | +| **Files** | Copied to AppX layout dir | Exe stays in place | +| **Best for** | Most frameworks (.NET, C++, Rust, Flutter, Tauri) | Electron, or F5 startup debugging | + +**Default to `winapp run`.** Use `create-debug-identity` when you need your IDE to launch and debug the exe directly (startup debugging), or when the exe is separate from your source (Electron). + +For console apps, add `--with-alias` to preserve stdin/stdout in the current terminal. + +> **`--debug-output` caveat:** Captures `OutputDebugString` and crash diagnostics (minidump + automatic analysis for both managed and native crashes) but attaches winapp as the debugger — you cannot also attach VS Code or WinDbg. Use `--no-launch` if you need your own debugger. Add `--symbols` to download PDB symbols for richer native crash analysis. + +For full debugging scenarios and IDE setup, see the [Debugging Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/debugging.md). + +## Recommended workflow + +1. **Initialize** — `winapp init . --use-defaults` in your existing project +2. **Configure** — edit `Package.appxmanifest` to add capabilities your app needs (e.g., `runFullTrust`, `internetClient`) +3. **Build** — build your app as usual (dotnet build, cmake, npm run build, etc.) +4. **Run with identity** — `winapp run ./bin/Debug` to register identity and launch for debugging +5. **Package** — `winapp package ./bin/Release --cert ./devcert.pfx` to create MSIX + +## Tips + +- Use `--use-defaults` (alias: `--no-prompt`) in CI/CD pipelines and scripts to avoid interactive prompts. Non-interactive environments (piped stdin, CI runners) are auto-detected and will use defaults automatically with a warning. +- If you only need `Package.appxmanifest` without SDK setup, use `winapp manifest generate` instead of `init` +- `winapp init` is idempotent for the config file — re-running it won't overwrite an existing `winapp.yaml` unless you use `--config-only` +- For Electron projects, prefer `npm install --save-dev @microsoft/winappcli` and use `npx winapp init` instead of the standalone CLI + +## Related skills +- After setup, see `winapp-manifest` to customize your `Package.appxmanifest` +- Ready to package? See `winapp-package` to create an MSIX installer +- Need a certificate? See `winapp-signing` for certificate generation +- Not sure which command to use? See `winapp-troubleshoot` for a command selection flowchart + +## Troubleshooting +| Error | Cause | Solution | +|-------|-------|----------| +| "winapp.yaml not found" | Running `restore`/`update` without config | Run `winapp init` first, or ensure you're in the right directory | +| "Directory not found" | Target directory doesn't exist | Create the directory first or check the path | +| SDK download fails | Network issue or firewall | Ensure internet access; check proxy settings | +| `init` prompts unexpectedly in CI | Missing `--use-defaults` flag | Add `--use-defaults` to skip all prompts (note: non-interactive shells are now auto-detected) | + + +## Command Reference + +### `winapp init` + +Start here for initializing a Windows app with required setup. Sets up everything needed for Windows app development: creates Package.appxmanifest with default assets, downloads Windows SDK and Windows App SDK packages, and generates projections. When SDK packages are managed (--setup-sdks stable/preview/experimental), also creates winapp.yaml to pin versions for 'restore'/'update'; with --setup-sdks none (e.g., for Rust/Tauri projects that bring their own SDK bindings), no winapp.yaml is created. Interactive by default; automatically uses defaults in non-interactive environments (use --use-defaults to skip prompts explicitly). Use 'restore' instead if you cloned a repo that already has winapp.yaml. Use 'manifest generate' if you only need a manifest, or 'cert generate' if you need a development certificate for code signing. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Base/root directory for the winapp workspace, for consumption or installation. | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--config-dir` | Directory to read/store configuration (default: the selected project directory, or current directory if no project is detected) | (none) | +| `--config-only` | Only handle configuration file operations (create if missing, validate if exists). Skip package installation and other workspace setup steps. | (none) | +| `--ignore-config` | Don't use configuration file for version management | (none) | +| `--no-gitignore` | Don't update .gitignore file | (none) | +| `--setup-sdks` | SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation) | (none) | +| `--use-defaults` | Do not prompt; requires an explicit project directory (e.g., winapp init . --use-defaults) | (none) | + +### `winapp restore` + +Use after cloning a repo or when .winapp/ folder is missing. Reinstalls SDK packages from existing winapp.yaml without changing versions. Requires winapp.yaml (created by 'init'). To check for newer SDK versions, use 'update' instead. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Base/root directory for the winapp workspace | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--config-dir` | Directory to read configuration from (default: current directory) | (none) | + +### `winapp update` + +Check for and install newer SDK versions. Updates winapp.yaml with latest versions and reinstalls packages. Requires existing winapp.yaml (created by 'init'). Use --setup-sdks preview for preview SDKs. To reinstall current versions without updating, use 'restore' instead. + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--setup-sdks` | SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation) | (none) | + +### `winapp run` + +Creates packaged layout, registers the Application, and launches the packaged application. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | Yes | Input folder containing the app to run | +| `` | No | Arguments to pass to the launched application. Provide after -- (e.g., winapp run . -- --flag value). | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--args` | Command-line arguments to pass to the application. Alternatively, use -- followed by arguments to avoid escaping (e.g., winapp run . -- --flag value). | (none) | +| `--clean` | Remove the existing package's application data (LocalState, settings, etc.) before re-deploying. By default, application data is preserved across re-deployments. | (none) | +| `--debug-output` | Capture OutputDebugString messages and first-chance exceptions from the launched application. Only one debugger can attach to a process at a time, so other debuggers (Visual Studio, VS Code) cannot be used simultaneously. Use --no-launch instead if you need to attach a different debugger. Cannot be combined with --no-launch or --json. | (none) | +| `--detach` | Launch the application and return immediately without waiting for it to exit. Useful for CI/automation where you need to interact with the app after launch. Prints the PID to stdout (or in JSON with --json). | (none) | +| `--executable` | Path to the executable relative to the input folder. Use to disambiguate when the manifest contains a $targetnametoken$ placeholder and multiple .exe files are present in the input folder. | (none) | +| `--json` | Format output as JSON | (none) | +| `--manifest` | Path to the Package.appxmanifest (default: auto-detect from input folder or current directory) | (none) | +| `--no-launch` | Only create the debug identity and register the package without launching the application | (none) | +| `--output-appx-directory` | Output directory for the loose layout package. If not specified, a directory named AppX inside the input-folder directory will be used. | (none) | +| `--symbols` | Download symbols from Microsoft Symbol Server for richer native crash analysis. Only used with --debug-output. First run downloads symbols and caches them locally; subsequent runs use the cache. | (none) | +| `--unregister-on-exit` | Unregister the development package after the application exits. Only removes packages registered in development mode. | (none) | +| `--with-alias` | Launch the app using its execution alias instead of AUMID activation. The app runs in the current terminal with inherited stdin/stdout/stderr. Requires a uap5:ExecutionAlias in the manifest. Use "winapp manifest add-alias" to add an execution alias to the manifest. | (none) | diff --git a/.claude/skills/winapp-signing/SKILL.md b/.claude/skills/winapp-signing/SKILL.md new file mode 100644 index 00000000..b13f83f8 --- /dev/null +++ b/.claude/skills/winapp-signing/SKILL.md @@ -0,0 +1,183 @@ +--- +name: winapp-signing +description: Create and manage code signing certificates for Windows apps and MSIX packages. Use when generating a certificate, signing a Windows app or installer, or fixing certificate trust issues. +version: 0.3.3 +--- +## When to use + +Use this skill when: +- **Generating a development certificate** for local MSIX signing and testing +- **Installing (trusting) a certificate** on a machine so MSIX packages can be installed +- **Signing an MSIX package or executable** for distribution + +## Prerequisites + +- winapp CLI installed +- **Administrator access** required for `cert install` (trusting certificates on the machine) + +## Key concepts + +**Publisher matching:** The publisher in your certificate (e.g., `CN=MyCompany`) must exactly match the `Publisher` attribute in `Package.appxmanifest`. Use `--manifest` when generating to auto-match. + +**Dev vs. production certs:** `winapp cert generate` creates self-signed certificates for **local testing only**. For production distribution (Microsoft Store or enterprise), obtain a certificate from a trusted Certificate Authority. + +**Default password:** Generated certificates use `password` as the default PFX password. Override with `--password`. + +## Usage + +### Generate a development certificate + +```powershell +# Auto-infer publisher from Package.appxmanifest in the current directory +winapp cert generate + +# Explicitly point to a manifest +winapp cert generate --manifest ./path/to/Package.appxmanifest + +# Set publisher manually (when no manifest exists yet) +winapp cert generate --publisher "CN=Contoso, O=Contoso Ltd, C=US" + +# Custom output path and password +winapp cert generate --output ./certs/myapp.pfx --password MySecurePassword + +# Custom validity period +winapp cert generate --valid-days 730 + +# Overwrite existing certificate +winapp cert generate --if-exists overwrite +``` + +Output: `devcert.pfx` (or custom path via `--output`). + +### Install (trust) a certificate + +```powershell +# Trust the certificate on this machine (requires admin/elevated terminal) +winapp cert install ./devcert.pfx + +# Force reinstall even if already trusted +winapp cert install ./devcert.pfx --force +``` + +This adds the certificate to the local machine's **Trusted Root Certification Authorities** store. Required before double-clicking MSIX packages or running `Add-AppxPackage`. + +### Sign a file + +```powershell +# Sign an MSIX package +winapp sign ./myapp.msix ./devcert.pfx + +# Sign with custom password +winapp sign ./myapp.msix ./devcert.pfx --password MySecurePassword + +# Sign with timestamp for production (signature remains valid after cert expires) +winapp sign ./myapp.msix ./production.pfx --timestamp http://timestamp.digicert.com +``` + +### Bundle signing + +When packaging multiple architectures into an `.msixbundle`, only the bundle needs to be signed — the signature covers all packages inside. The individual `.msix` slices do not need separate signatures. + +Note: The `package` command can sign automatically when you pass `--cert`, so you often don't need `sign` separately. + +## Recommended workflow + +1. **Generate cert** — `winapp cert generate` (auto-infers publisher from manifest) +2. **Trust cert** (one-time) — `winapp cert install ./devcert.pfx` (run as admin) +3. **Package + sign** — `winapp package ./dist --cert ./devcert.pfx` +4. **Distribute** — share the `.msix`; recipients must also trust the cert, or use a trusted CA cert + +## Tips + +- Always use `--manifest` (or have `Package.appxmanifest` in the working directory) when generating certs to ensure the publisher matches automatically +- For CI/CD, store the PFX as a secret and pass the password via `--password` rather than using the default +- `winapp cert install` modifies the machine certificate store — it persists across reboots and user sessions +- Use `--timestamp` when signing production builds so the signature survives certificate expiration +- You can also use the shorthand: `winapp package ./dist --generate-cert --install-cert` to do everything in one command + +## Related skills +- Need to create a manifest first? See `winapp-manifest` to generate `Package.appxmanifest` with correct publisher info +- Ready to package? See `winapp-package` to create and sign an MSIX in one step +- Having issues? See `winapp-troubleshoot` for common error solutions + +## Troubleshooting +| Error | Cause | Solution | +|-------|-------|----------| +| "Publisher mismatch" | Cert publisher ≠ manifest publisher | `winapp cert generate --manifest ./Package.appxmanifest` to re-generate with correct publisher | +| "Access denied" / "elevation required" | `cert install` needs admin | Run your terminal as Administrator | +| "Certificate not trusted" | Cert not installed on machine | `winapp cert install ./devcert.pfx` (admin) | +| "Certificate file already exists" | `devcert.pfx` already present | Use `--if-exists overwrite` or `--if-exists skip` | +| Signature invalid after time passes | No timestamp used during signing | Re-sign with `--timestamp http://timestamp.digicert.com` | + + +## Command Reference + +### `winapp cert generate` + +Create a self-signed certificate for local testing only. Publisher must match the manifest (auto-inferred if --manifest provided or Package.appxmanifest is in working directory). Output: devcert.pfx (default password: 'password'). For production, obtain a certificate from a trusted CA. Use 'cert install' to trust on this machine. + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--export-cer` | Export a .cer file (public key only) alongside the .pfx | (none) | +| `--if-exists` | Behavior when output file exists: 'error' (fail, default), 'skip' (keep existing), or 'overwrite' (replace) | `Error` | +| `--install` | Install the certificate to the local machine store after generation | (none) | +| `--json` | Format output as JSON | (none) | +| `--manifest` | Path to Package.appxmanifest or appxmanifest.xml file to extract publisher information from | (none) | +| `--output` | Output path for the generated PFX file | (none) | +| `--password` | Password for the generated PFX file | `password` | +| `--publisher` | Publisher name for the generated certificate. If not specified, will be inferred from manifest. | (none) | +| `--valid-days` | Number of days the certificate is valid | `365` | + +### `winapp cert install` + +Trust a certificate on this machine (requires admin). Run before installing MSIX packages signed with dev certificates. Example: winapp cert install ./devcert.pfx. Only needed once per certificate. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | Yes | Path to the certificate file (PFX or CER) | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--force` | Force installation even if the certificate already exists | (none) | +| `--password` | Password for the PFX file | `password` | + +### `winapp cert info` + +Display certificate details (subject, thumbprint, expiry). Useful for verifying a certificate matches your manifest before signing. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | Yes | Path to the certificate file (PFX) | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--json` | Format output as JSON | (none) | +| `--password` | Password for the PFX file | `password` | + +### `winapp sign` + +Code-sign an MSIX package or executable. Example: winapp sign ./app.msix ./devcert.pfx. Use --timestamp for production builds to remain valid after cert expires. The 'package' command can sign automatically with --cert. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | Yes | Path to the file/package to sign | +| `` | Yes | Path to the certificate file (PFX format) | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--password` | Certificate password | `password` | +| `--timestamp` | Timestamp server URL | (none) | diff --git a/.claude/skills/winapp-troubleshoot/SKILL.md b/.claude/skills/winapp-troubleshoot/SKILL.md new file mode 100644 index 00000000..138452e3 --- /dev/null +++ b/.claude/skills/winapp-troubleshoot/SKILL.md @@ -0,0 +1,152 @@ +--- +name: winapp-troubleshoot +description: Diagnose and fix common Windows app packaging, signing, identity, and SDK errors. Use when encountering errors with MSIX packaging, certificate signing, Windows SDK setup, or app installation. +version: 0.3.3 +--- +## When to use + +Use this skill when: +- **Diagnosing errors** from winapp CLI commands +- **Choosing the right command** for a task +- **Understanding prerequisites** — what each command needs and what it produces + +## Common errors & solutions + +| Error | Cause | Solution | +|-------|-------|----------| +| "winapp.yaml not found" | Running `restore` or `update` without config | Run `winapp init` first, or `cd` to the directory containing `winapp.yaml` | +| "Package.appxmanifest not found" | Running `package`, `create-debug-identity`, or `cert generate --manifest` | Run `winapp init` or `winapp manifest generate` first, or pass `--manifest ` | +| "Publisher mismatch" | Certificate publisher ≠ manifest publisher | Regenerate cert: `winapp cert generate --manifest`, or edit `Package.appxmanifest` `Identity.Publisher` to match | +| "Access denied" / "elevation required" | `cert install` without admin | Run terminal as Administrator for `winapp cert install` | +| "Package installation failed" | Cert not trusted, or stale package registration | `winapp cert install ./devcert.pfx` (admin), then `Get-AppxPackage \| Remove-AppxPackage` | +| "Certificate not trusted" | Dev cert not installed on machine | `winapp cert install ./devcert.pfx` (admin) | +| "Build tools not found" | First run, tools not yet downloaded | Run `winapp update` to download tools; ensure internet access | +| "Failed to add package identity" | Stale debug identity or untrusted cert | `Get-AppxPackage *yourapp* \| Remove-AppxPackage` to clean up, then `winapp cert install` and retry | +| "Certificate file already exists" | `devcert.pfx` already present | Use `winapp cert generate --if-exists overwrite` or `--if-exists skip` | +| "Manifest already exists" | `Package.appxmanifest` already present | Use `winapp manifest generate --if-exists overwrite` or edit manifest directly | +| `run` / `create-debug-identity` registration error `0x800704EC` | Developer Mode is disabled | Enable it in **Settings → Privacy & security → For developers**, or `Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock' -Name AllowDevelopmentWithoutDevLicense -Value 1`, then retry | +| `run` / `create-debug-identity` registration error `0x80073CFB` | Package already registered with a conflicting identity | Run `winapp unregister` (or `winapp unregister --force` if the package was registered from a different project tree), then retry | + +## Command selection guide + +``` +Does the project have a Package.appxmanifest? +├─ No → Do you want full setup (manifest + config + optional SDKs)? +│ ├─ Yes → winapp init (adds Windows platform files to existing project) +│ └─ No, just a manifest → winapp manifest generate +└─ Yes + ├─ Has winapp.yaml, cloned/pulled but .winapp/ folder missing? + │ └─ winapp restore + ├─ Want newer SDK versions? + │ └─ winapp update + ├─ Need a dev certificate? + │ └─ winapp cert generate (then winapp cert install for trust) + ├─ Need package identity for debugging? (see [Debugging Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/debugging.md)) + │ ├─ Exe is in your build output folder? (most frameworks) + │ │ └─ winapp run + │ └─ Exe is separate from app code? (Electron, sparse testing) + │ └─ winapp create-debug-identity + ├─ Ready to create MSIX installer? + │ └─ winapp package --cert ./devcert.pfx + ├─ Need to sign an existing file? + │ └─ winapp sign + ├─ Need to update app icons? + │ └─ winapp manifest update-assets ./logo.png + ├─ Need to run SDK tools directly? + │ └─ winapp tool + ├─ Need to publish to Microsoft Store? + │ └─ winapp store (passthrough to Store Developer CLI) + └─ Need the .winapp directory path for build scripts? + └─ winapp get-winapp-path (or --global for shared cache) +``` + +**Important notes:** +- `winapp init` adds files to an **existing** project — it does not create a new project +- The key prerequisite for most commands is `Package.appxmanifest`, not `winapp.yaml` +- `winapp.yaml` is only needed for SDK version management (`restore`/`update`) +- Projects with NuGet package references (e.g., `.csproj` referencing `Microsoft.Windows.SDK.BuildTools`) can use winapp commands without `winapp.yaml` +- For Electron projects, use the npm package (`npm install --save-dev @microsoft/winappcli`) which includes Node.js-specific commands under `npx winapp node` + +## Debugging approach quick reference + +| Goal | Command | Key detail | +|------|---------|------------| +| Run with identity (most common) | `winapp run .\build\Debug` | Registers loose layout + launches; add `--with-alias` for console apps | +| Attach debugger to running app | `winapp run .\build\Debug` → attach to PID | Misses startup code | +| Register identity, launch manually | `winapp run .\build\Debug --no-launch` | Launch via `start shell:AppsFolder\` or execution alias — **not** the exe directly | +| F5 startup debugging (IDE launches exe) | `winapp create-debug-identity .\bin\myapp.exe` | Exe has identity regardless of how it's launched; best for debugging activation/startup code | +| Capture OutputDebugString + crash dump | `winapp run .\build\Debug --debug-output` | On crash, writes minidump and shows exception type, message, and faulting methods. **Blocks other debuggers** — use `--no-launch` if you need VS Code/WinDbg | +| Run and auto-clean | `winapp run .\build\Debug --unregister-on-exit` | Unregisters the dev package after the app exits | +| Launch and detach (CI) | `winapp run .\build\Debug --detach` | Returns immediately after launch; use `--json` to get PID for scripting | +| Clean up stale registration | `winapp unregister` | Removes dev-mode packages for the current project | + +> **Visual Studio users:** If you have a packaging project, VS already handles identity and debugging from F5 — you likely don't need winapp for debugging. These workflows are for VS Code, terminal, and frameworks VS doesn't natively package. + +For full details, see the [Debugging Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/debugging.md). + +## Prerequisites & state matrix + +| Command | Requires | Creates/Modifies | +|---------|----------|------------------| +| `init` | Existing project (any framework) | `winapp.yaml`, `.winapp/`, `Package.appxmanifest`, `Assets/`, `.gitignore` update | +| `restore` | `winapp.yaml` | `.winapp/packages/`, generated projections | +| `update` | `winapp.yaml` | Updates versions in `winapp.yaml`, reinstalls packages | +| `manifest generate` | Nothing | `Package.appxmanifest`, `Assets/` | +| `manifest update-assets` | `Package.appxmanifest` + source image | Regenerates `Assets/` icons | +| `cert generate` | Nothing (or `Package.appxmanifest` for publisher) | `devcert.pfx` | +| `cert install` | Certificate file + admin | Machine certificate store | +| `create-debug-identity` | `Package.appxmanifest` + exe + trusted cert | Registers sparse package with Windows | +| `run` | Build output folder + `Package.appxmanifest` | Registers loose layout package, launches app | +| `unregister` | `Package.appxmanifest` (auto-detect or `--manifest`) | Removes dev-mode package registrations | +| `package` | Build output + `Package.appxmanifest` | `.msix` file | +| `sign` | File + certificate | Signed file (in-place) | +| `create-external-catalog` | Directory with executables | `CodeIntegrityExternal.cat` | +| `tool ` | Nothing (auto-downloads tools) | Runs SDK tool directly | +| `store` | Nothing (auto-downloads Store CLI) | Passthrough to Microsoft Store Developer CLI | +| `get-winapp-path` | Nothing | Prints `.winapp` directory path | + +## Debugging tips + +- Add `--verbose` (or `-v`) to any command for detailed output +- Add `--quiet` (or `-q`) to suppress progress messages (useful in CI/CD) +- Run `winapp --cli-schema` to get the full JSON schema of all commands and options +- Run any command with `--help` for its specific usage information +- Use `winapp get-winapp-path` to find where packages are stored locally +- Use `winapp get-winapp-path --global` to find the shared cache location + +## Getting more help + +- Full CLI documentation: https://github.com/microsoft/WinAppCli/blob/main/docs/usage.md +- Framework-specific guides: https://github.com/microsoft/WinAppCli/tree/main/docs/guides +- File an issue: https://github.com/microsoft/WinAppCli/issues + +## Related skills +- **Setup & init**: `winapp-setup` — adding Windows support to a project +- **Manifest**: `winapp-manifest` — creating and editing `Package.appxmanifest` +- **Signing**: `winapp-signing` — certificate generation and management +- **Packaging**: `winapp-package` — creating MSIX installers +- **Identity**: `winapp-identity` — enabling package identity for Windows APIs +- **Frameworks**: `winapp-frameworks` — framework-specific guidance (Electron, .NET, C++, Rust, Flutter, Tauri) + + +## Command Reference + +### `winapp get-winapp-path` + +Print the path to the .winapp directory. Use --global for the shared cache location, or omit for the project-local .winapp folder. Useful for build scripts that need to reference installed packages. + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--global` | Get the global .winapp directory instead of local | (none) | + +### `winapp tool` + +Run Windows SDK tools directly (makeappx, signtool, makepri, etc.). Auto-downloads Build Tools if needed. For most tasks, prefer higher-level commands like 'package' or 'sign'. Example: winapp tool makeappx pack /d ./folder /p ./out.msix + +**Aliases:** `run-buildtool` + +### `winapp store` + +Run a Microsoft Store Developer CLI command. This command will download the Microsoft Store Developer CLI if not already downloaded. Learn more about the Microsoft Store Developer CLI here: https://aka.ms/msstoredevcli diff --git a/.claude/skills/winapp-ui-automation/SKILL.md b/.claude/skills/winapp-ui-automation/SKILL.md new file mode 100644 index 00000000..a7a9f2a4 --- /dev/null +++ b/.claude/skills/winapp-ui-automation/SKILL.md @@ -0,0 +1,475 @@ +--- +name: winapp-ui-automation +description: Inspect and interact with running Windows app UIs from the command line using UI Automation (UIA). Use when an AI agent or developer needs to inspect a UI element tree, find controls, take screenshots, click buttons, read or set text, or verify UI state in a running Windows app. Works with any framework WinUI 3, WPF, WinForms, Win32, Electron. +version: 0.3.3 +--- +## When to use +- Inspecting a running Windows app's UI from the command line +- AI agents interacting with Windows applications (clicking buttons, reading text, taking screenshots) +- Verifying UI state during development or testing +- Automating UI workflows without Playwright or Selenium +- Debugging WinUI 3, WPF, WinForms, Win32, or Electron app UIs + +## Prerequisites +- For UIA mode (any app): No setup needed — works with any running Windows app + +## Common patterns + +### Discover and interact +```powershell +# See what's clickable, then screenshot for context +winapp ui inspect -a myapp --interactive; winapp ui screenshot -a myapp + +# Click and verify the page changed +winapp ui invoke btn-settings-a1b2 -a myapp; winapp ui wait-for pn-settingspage-c3d4 -a myapp --timeout 3000; winapp ui screenshot -a myapp + +# Fill a form and submit +winapp ui set-value txt-searchbox-e5f6 "hello" -a myapp; winapp ui invoke btn-submit-7a90 -a myapp; winapp ui screenshot -a myapp +``` + +### Find visible text and click it +```powershell +# Search by text — output shows invokable ancestor +winapp ui search "Save changes" -a myapp +# Output: +# lbl-savechanges-a1b2 "Save changes" (120,40 80x20) +# ^ invoke via: btn-save-c3d4 "Save" + +# Invoke by text — auto-walks to parent Button +winapp ui invoke 'Save changes' -a myapp +``` + +### Navigate multi-page apps +```powershell +# Click nav item, wait for page, inspect what's available +winapp ui invoke itm-samples-3f2c -a myapp; winapp ui wait-for pn-samplespage-b4e7 -a myapp; winapp ui inspect -a myapp --interactive +``` + +### Disambiguate duplicate elements +```powershell +# When text search matches multiple elements, the error shows slugs for each — pick the right one +winapp ui invoke Submit -a myapp +# → Selector matched 3 elements: +# [0] Button "Submit Order" → btn-submitorder-a1b2 +# [1] Button "Submit" → btn-submit-c3d4 +# Use the slug: winapp ui invoke btn-submit-c3d4 -a myapp +``` + +## Key concepts +- **Selector brackets**: `inspect` and `search` output shows selectors in `[brackets]` — use the bracketed value with other `ui` commands. Selectors are either AutomationId (stable, developer-set) or generated slug (e.g., `btn-name-hash`). +- **AutomationId selectors**: When an element has a unique AutomationId, it becomes the selector directly (e.g., `[MinimizeButton]`). These survive layout changes and localization — preferred for stable targeting. +- **Slug selectors**: When no unique AutomationId exists, a generated slug is used (e.g., `[btn-close-a2b3]`). Format: `prefix-name-hash`. May go stale after UI changes. +- **Plain text search**: `search` and `invoke` accept plain text — `search Minimize` finds elements with "Minimize" in their Name or AutomationId (substring, case-insensitive). No special syntax needed. +- **`--interactive` flag**: Filters to invokable elements only with auto-depth 8 — the fastest way to see what you can click +- **Invokable ancestor surfacing**: When a search result isn't invokable, the nearest invokable parent is shown with its selector +- **`;` chaining**: Chain commands with `;` to run multiple operations in one call, reducing agent round-trips +- **`-a` vs `-w`**: Use `-a` to find apps by name/title/PID. Use `-w ` for stable window targeting +- **Element markers**: `[on]`/`[off]` for toggles, `[collapsed]`/`[expanded]`, `[scroll:v]`/`[scroll:h]`/`[scroll:vh]` for scrollable containers, `[offscreen]`, `[disabled]`, `value="..."` for editable elements + +## Usage + +### Connect and discover +```powershell +# Connect and see interactive elements in one call +winapp ui status -a myapp; winapp ui inspect -a myapp --interactive +``` + +### Inspect element tree +```powershell +winapp ui inspect -a myapp --interactive # invokable elements only, auto-depth 8 +winapp ui inspect -a myapp --depth 5 # deeper tree at depth 5 +winapp ui inspect txt-searchbox-e5f6 -a myapp # subtree rooted at element +winapp ui inspect btn-settings-a1b2 -a myapp --ancestors # walk up from element to root +winapp ui inspect -a myapp --hide-offscreen # hide offscreen elements +``` + +### Find elements +```powershell +winapp ui search Close -a myapp # finds elements with "Close" in name or automationId +winapp ui search Button -a myapp # finds elements with "Button" in name (also matches type names) +winapp ui search image -a myapp # case-insensitive substring match +``` + +### Screenshot +```powershell +# Full window screenshot +winapp ui screenshot -a myapp --output page.png + +# Crop to element; capture with popups visible +winapp ui screenshot txt-searchbox-e5f6 -a myapp --output search.png +winapp ui screenshot -a myapp --capture-screen --output with-popups.png + +# Bring window to foreground first (matches what the user is currently seeing) +winapp ui screenshot -a myapp --focus --output focused.png +``` + +### Read element state +```powershell +# Read text/value content (works for RichEditBox, TextBox, ComboBox, Slider, labels) +winapp ui get-value doc-texteditor-53ad -a notepad +winapp ui get-value SearchBox -a myapp +winapp ui get-value CmbTheme -a myapp # reads ComboBox selected item via SelectionPattern + +# Check toggle/selection state, value, scroll position +winapp ui get-property chk-agreecheckbox-b2c3 -a myapp --property ToggleState +winapp ui get-property txt-textbox-a4b1 -a myapp --property Value +winapp ui get-property cmb-modellist-d5e6 -a myapp --property IsSelected + +# See what has keyboard focus +winapp ui get-focused -a myapp +``` + +### Scroll containers +```powershell +# Find scrollable containers — look for [scroll:v] (vertical) or [scroll:h] (horizontal) +winapp ui search scroll -a myapp +# Output: +# pn-scrollview-bfef Pane "scrollView" [scroll:v] (2127,296 1191x965) +# pn-scrollviewer-bfb1 Pane "scrollViewer" [scroll:h] (2127,296 1191x216) + +# Scroll vertically +winapp ui scroll pn-scrollview-bfef --direction down -a myapp + +# Scroll to top/bottom +winapp ui scroll pn-scrollview-bfef --to bottom -a myapp + +# Scroll and then inspect for newly visible elements +winapp ui scroll pn-scrollview-bfef --direction down -a myapp; winapp ui search TargetItem -a myapp +``` + +### Wait for UI state +```powershell +winapp ui wait-for btn-submit-a1b2 -a myapp --timeout 5000 +winapp ui wait-for itm-status-c3d4 -a myapp --value "Complete" --timeout 5000 +``` + +## Tips +- Use `--interactive` with `inspect` as your first command — it shows only what you can click +- Chain commands with `;` to reduce round-trips (see note below on why not `&&`) +- Use slugs from output to target specific elements — they're hash-validated and shell-safe +- Use plain text search to find elements: `search Minimize`, `invoke Submit` +- When multiple elements match text search, the error shows slugs for each — pick the right one +- Use `get-property --property ToggleState` to verify checkbox/toggle state after invoke +- `scroll` auto-finds the nearest scrollable parent +- Use `--capture-screen` to capture popup overlays, dropdown menus, and flyouts (also brings the window to the foreground) +- Use `--focus` to foreground the target window before capture without switching to screen-DC capture (default capture path uses Windows.Graphics.Capture and works while occluded) +- Use `--hide-disabled` and `--hide-offscreen` to reduce noise + +### Why `;` instead of `&&` +Use `;` (not `&&`) to chain commands. PowerShell's `&&` operator can freeze when a native CLI writes to stderr or uses ANSI escape sequences — this causes a pipeline deadlock. `;` runs each command unconditionally and avoids this issue. This is also better for agent workflows: you usually want the screenshot to run even if the invoke had a non-zero exit (to see what went wrong). + +### File dialog workaround +File open/save dialogs are standard Windows dialogs with UIA support. Interact with them using existing commands: +```powershell +# 1. Trigger the dialog (e.g., click "Open File" button) +winapp ui invoke btn-openfilebtn-a2b3 -a myapp + +# 2. Find the dialog window +winapp ui list-windows -a myapp +# → Shows the main window + the dialog HWND + +# 3. Target the dialog, type the file path, and confirm +winapp ui set-value txt-1148-c4d5 "C:\path\to\file.png" -w +winapp ui invoke btn-open-e6f7 -w +``` +Note: The filename input in standard file dialogs typically has AutomationId `1148`. Use `inspect -w --interactive` to discover the actual slugs. + +## JSON output envelopes (v0.3.1+) + +The `--json` envelope for `ui inspect`, `ui get-focused`, `ui search`, and `ui wait-for` was reshaped in v0.3.1. Pre-0.3.1 parsers will silently break — most fields were renamed, removed, or moved into envelopes. Highlights: + +- `ui inspect --json` now nests elements under `windows[].elements[]` (was a flat `elements[]`). +- `ui get-focused --json` always emits an envelope — `{ "hasFocus": false }` or `{ "hasFocus": true, "element": {...} }` (was bare `null`). +- `ui search --json` / `ui wait-for --json` may include an `invokableAncestor` field (element-shaped) on each match. +- Per-element `id`, `parentSelector`, and `windowHandle` are **removed** — use `selector` as the public handle. + +Full schemas with examples: `references/ui-json-envelope.md`. + +## Related skills +- `winapp-setup` for adding Windows SDK to your project +- `winapp-package` for packaging apps as MSIX + +## Troubleshooting +| Error | Cause | Solution | +|---|---|---| +| "No running app found" | Wrong name or app not running | Try process name, window title, or PID | +| "Multiple windows match" | Several windows match `-a` | Use `-w ` from the listed options | +| "Selector matched N elements" | Text query matches multiple elements | Use a slug from the suggestions shown in the error, or from `inspect` output | +| "Element may have changed" | Slug hash doesn't match current element | Re-run `inspect` to get fresh slugs | +| "does not support any invoke pattern" | Element can't be invoked | The error shows the invokable ancestor slug if one exists — use that | +| "No UIA window found" | UIA can't see the window | Use `list-windows` to find HWND, then `-w` | +| Popup not in screenshot | Default capture path doesn't include unowned overlays | Use `--capture-screen` flag | + + +## Command Reference + +### `winapp ui status` + +Connect to a target app and display connection info. + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--json` | Format output as JSON | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui inspect` + +View the UI element tree with semantic slugs, element types, names, and bounds. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--ancestors` | Walk up the tree from the specified element to the root | (none) | +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--depth` | Tree inspection depth | `4` | +| `--hide-disabled` | Hide disabled elements from output | (none) | +| `--hide-offscreen` | Hide offscreen elements from output | (none) | +| `--interactive` | Show only interactive/invokable elements (buttons, links, inputs, list items). Increases default depth to 8. | (none) | +| `--json` | Format output as JSON | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui search` + +Search the element tree for elements matching a text query. Returns all matches with semantic slugs. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--json` | Format output as JSON | (none) | +| `--max` | Maximum search results | `50` | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui get-property` + +Read UIA property values from an element. Specify --property for a single property or omit for all. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--json` | Format output as JSON | (none) | +| `--property` | Property name to read or filter on | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui get-value` + +Read the current value from an element. Tries TextPattern (RichEditBox, Document), ValuePattern (TextBox, ComboBox, Slider), then Name (labels). Usage: winapp ui get-value -a + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--json` | Format output as JSON | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui screenshot` + +Capture the target window or element as a PNG image. When multiple windows exist (e.g., dialogs), captures each to a separate file. With --json, returns file path and dimensions. Use --capture-screen for popup overlays. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--capture-screen` | Capture from screen DC via BitBlt (includes popups/overlays not owned by the target). Implies --focus. | (none) | +| `--focus` | Bring the target window to the foreground before capture. Already implied by --capture-screen. | (none) | +| `--json` | Format output as JSON | (none) | +| `--output` | Save output to file path (e.g., screenshot) | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui invoke` + +Activate an element by slug or text search. Tries InvokePattern, TogglePattern, SelectionItemPattern, and ExpandCollapsePattern in order. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--json` | Format output as JSON | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui click` + +Click an element by slug or text search using mouse simulation. Works on elements that don't support InvokePattern (e.g., column headers, list items). Use --double for double-click, --right for right-click. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--double` | Perform a double-click instead of a single click | (none) | +| `--json` | Format output as JSON | (none) | +| `--right` | Perform a right-click instead of a left click | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui set-value` + +Set a value on an element using UIA ValuePattern. Works for TextBox, ComboBox, Slider, and other editable controls. Usage: winapp ui set-value -a + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | +| `` | No | Value to set (text for TextBox/ComboBox, number for Slider) | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--json` | Format output as JSON | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui focus` + +Move keyboard focus to the specified element using UIA SetFocus. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--json` | Format output as JSON | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui scroll-into-view` + +Scroll the specified element into the visible area using UIA ScrollItemPattern. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--json` | Format output as JSON | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui scroll` + +Scroll a container element using ScrollPattern. Use --direction to scroll incrementally, or --to to jump to top/bottom. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--direction` | Scroll direction: up, down, left, right | (none) | +| `--json` | Format output as JSON | (none) | +| `--to` | Scroll to position: top, bottom | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui wait-for` + +Wait for an element to appear, disappear, or have a property reach a target value. Polls at 100ms intervals until condition met or timeout. + +#### Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--contains` | Use substring matching for --value instead of exact match | (none) | +| `--gone` | Wait for element to disappear instead of appear | (none) | +| `--json` | Format output as JSON | (none) | +| `--property` | Property name to read or filter on | (none) | +| `--timeout` | Timeout in milliseconds | `5000` | +| `--value` | Wait for element value to equal this string. Uses smart fallback (TextPattern -> ValuePattern -> Name). Combine with --property to check a specific property instead. | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | + +### `winapp ui list-windows` + +List all visible windows with their HWND, title, process, and size. Use -a to filter by app name. Use the HWND with -w to target a specific window. + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--json` | Format output as JSON | (none) | + +### `winapp ui get-focused` + +Show the element that currently has keyboard focus in the target app. + +#### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--app` | Target app (process name, window title, or PID). Lists windows if ambiguous. | (none) | +| `--json` | Format output as JSON | (none) | +| `--window` | Target window by HWND (stable handle from list output). Takes precedence over --app. | (none) | diff --git a/.claude/skills/winapp-ui-automation/references/ui-json-envelope.md b/.claude/skills/winapp-ui-automation/references/ui-json-envelope.md new file mode 100644 index 00000000..a8a0cf86 --- /dev/null +++ b/.claude/skills/winapp-ui-automation/references/ui-json-envelope.md @@ -0,0 +1,87 @@ +# `winapp ui --json` envelope (v0.3.1+) + +The `--json` output for the `winapp ui` command group was reshaped in v0.3.1. +Generate parsers against these shapes — pre-0.3.1 parsers will silently break +because most fields were renamed, removed, or moved into envelopes. + +## `ui inspect --json` + +Top-level shape (elements are now nested under `windows[]`, not flat): + +```json +{ + "depth": 0, + "interactive": false, + "hideDisabled": false, + "hideOffscreen": false, + "windows": [ + { + "hwnd": "0x...", + "title": "...", + "className": "...", + "elementCount": 0, + "elements": [ + { + "selector": "...", + "name": "...", + "controlType": "...", + "children": [ ... ] + } + ] + } + ] +} +``` + +Pre-0.3.1 the shape was `{ "elements": [...] }`. Per-element `id`, +`parentSelector`, and `windowHandle` fields have been **removed** — +`selector` is the public handle. + +## `ui inspect --ancestors --json` + +Ancestors are now nested as a parent → child chain keyed by `Depth=i` +(previously emitted as sibling roots). + +## `ui inspect --interactive` + +Non-interactive ancestors are collapsed and surfaced as `ancestorPath` on +surviving descendants. `+more` markers indicate truncated subtrees in both +text and JSON modes. + +## `ui get-focused --json` + +Always emits an envelope (never a bare value): + +- No focus: `{ "hasFocus": false }` +- With focus: `{ "hasFocus": true, "element": { ... } }` + +Pre-0.3.1 emitted bare `null` when nothing was focused. + +## `ui search --json` / `ui wait-for --json` + +Both commands return matching elements using the same element shape as +`ui inspect` (so `selector`, `name`, `controlType`, `children`, etc.). +Each match may also include an `invokableAncestor` field — itself an +element-shaped object — pointing to the nearest parent that supports +`InvokePattern` (useful when a search hits a non-invokable element +like a label inside a button). + +```json +[ + { + "selector": "txt-save-label-a1b2", + "name": "Save", + "controlType": "Text", + "children": [ ... ], + "invokableAncestor": { + "selector": "btn-save-c3d4", + "name": "Save button", + "controlType": "Button" + } + } +] +``` + +The internal `id`, `parentSelector`, and `windowHandle` fields are +**scrubbed** from results — both at the top level and inside any nested +`invokableAncestor`. Don't depend on them; use `selector` as the handle. diff --git a/.gitattributes b/.gitattributes index 05a66486..275dac45 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,6 +5,8 @@ docs/cli-schema.json text eol=lf .github/plugin/plugin.json text eol=lf .github/plugin/skills/** text eol=lf +.github/plugin/agents/** text eol=lf +.claude/** text eol=lf docs/npm-usage.md text eol=lf src/winapp-npm/src/winapp-commands.ts text eol=lf diff --git a/AGENTS.md b/AGENTS.md index 6e2f9d70..3fc52a80 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -126,6 +126,7 @@ The following files are auto-generated from `cli-schema.json` via `scripts/gener - `docs/cli-schema.json` — machine-readable schema - `.github/plugin/skills/winapp-cli/*/SKILL.md` — Copilot CLI plugin skills +- `.claude/skills//SKILL.md` and `.claude/agents/*.md` — Claude Code mirror of `.github/plugin/`, produced by `scripts/sync-claude-plugin.ps1` (invoked at the end of `generate-llm-docs.ps1`) **To edit skill content**, modify the hand-written templates in `docs/fragments/skills/winapp-cli/`. Each template file (e.g., `package.md`, `manifest.md`) contains the workflow docs, examples, and troubleshooting content. The auto-generation script appends command reference tables from the CLI schema. Running `scripts/build-cli.ps1` triggers regeneration automatically. diff --git a/scripts/generate-llm-docs.ps1 b/scripts/generate-llm-docs.ps1 index eb87c8f3..c6bdcab0 100644 --- a/scripts/generate-llm-docs.ps1 +++ b/scripts/generate-llm-docs.ps1 @@ -5,6 +5,8 @@ .DESCRIPTION This script generates docs/cli-schema.json and SKILL.md files from the CLI's --cli-schema output. Run after building the CLI to keep documentation in sync. + Also mirrors the regenerated .github/plugin/ tree to .claude/ for Claude Code compatibility + via scripts/sync-claude-plugin.ps1. .PARAMETER CliPath Path to the winapp.exe CLI binary (default: artifacts/cli/win-x64/winapp.exe) .PARAMETER DocsPath @@ -298,6 +300,20 @@ version: $CliVersion Write-Host "[SKILLS] $skillName - generated" -ForegroundColor Gray } +# Mirror the regenerated Copilot plugin to .claude/ so Claude Code consumers +# (and the cc-community plugin marketplace) stay in sync. Source of truth +# remains .github/plugin/; .claude/ is a generated output of this script. +$syncScript = Join-Path $PSScriptRoot 'sync-claude-plugin.ps1' +if (Test-Path $syncScript) { + Write-Host "`n[CLAUDE] syncing .claude/ from .github/plugin/" -ForegroundColor Gray + & $syncScript + if ($LASTEXITCODE -ne 0) { + Write-Error "sync-claude-plugin.ps1 failed (exit $LASTEXITCODE)" + exit $LASTEXITCODE + } +} + + # Update plugin.json version to match CLI version (only when outputting to the default skills path) $DefaultSkillsPath = Join-Path $ProjectRoot ".github\plugin\skills\winapp-cli" if ($SkillsDir -eq $DefaultSkillsPath) { diff --git a/scripts/sync-claude-plugin.ps1 b/scripts/sync-claude-plugin.ps1 new file mode 100644 index 00000000..490113c2 --- /dev/null +++ b/scripts/sync-claude-plugin.ps1 @@ -0,0 +1,157 @@ +<# +.SYNOPSIS + Mirrors the GitHub Copilot plugin under .github/plugin/ to a Claude Code + compatible plugin at .claude/. + +.DESCRIPTION + Claude Code discovers project-scoped subagents at .claude/agents/.md + and skills at .claude/skills//SKILL.md. The Copilot plugin in + this repo (under .github/plugin/) uses an almost-identical layout, so this + script rebuilds the .claude/ tree from the Copilot source of truth: + + Skills: + .github/plugin/skills/winapp-cli//SKILL.md (+ siblings like references/) + -> .claude/skills//SKILL.md (+ copied siblings) + + Agents: + .github/plugin/agents/.agent.md + -> .claude/agents/.md (Copilot-only `infer:` frontmatter stripped) + + Re-run after editing skills/agents under .github/plugin/. The script wipes + .claude/skills and .claude/agents before regenerating so removed skills + don't linger. + +.PARAMETER Check + If set, exits with code 1 when .claude/ is out of date instead of writing. + Useful for CI to enforce that contributors re-ran the sync. +#> + +[CmdletBinding()] +param( + [switch]$Check +) + +$ErrorActionPreference = 'Stop' + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..') +$copilotRoot = Join-Path $repoRoot '.github\plugin' +$claudeRoot = Join-Path $repoRoot '.claude' +$srcSkillsDir = Join-Path $copilotRoot 'skills\winapp-cli' +$srcAgentsDir = Join-Path $copilotRoot 'agents' + +if (-not (Test-Path $srcSkillsDir)) { throw "Source skills dir not found: $srcSkillsDir" } +if (-not (Test-Path $srcAgentsDir)) { throw "Source agents dir not found: $srcAgentsDir" } + +function Get-FrontmatterName { + param([string]$Path) + $inFm = $false + foreach ($line in Get-Content -LiteralPath $Path) { + if ($line -match '^---\s*$') { + if (-not $inFm) { $inFm = $true; continue } else { break } + } + if ($inFm -and $line -match '^name:\s*(\S+)') { return $Matches[1] } + } + throw "No 'name:' in frontmatter of $Path" +} + +function Remove-InferField { + param([string[]]$Lines) + $out = New-Object System.Collections.Generic.List[string] + $inFm = $false; $fmCount = 0 + foreach ($line in $Lines) { + if ($line -match '^---\s*$') { + $fmCount++ + $inFm = ($fmCount -eq 1) + $out.Add($line); continue + } + if ($inFm -and $line -match '^infer:\s*') { continue } + $out.Add($line) + } + return ,$out.ToArray() +} + +# Build target tree in a temp dir, then compare/swap. +$tmpRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("claude-sync-" + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $tmpRoot | Out-Null +try { + $tmpSkills = Join-Path $tmpRoot 'skills' + $tmpAgents = Join-Path $tmpRoot 'agents' + New-Item -ItemType Directory -Path $tmpSkills, $tmpAgents | Out-Null + + # ---- Skills ---- + foreach ($skillDir in Get-ChildItem -LiteralPath $srcSkillsDir -Directory) { + $skillFile = Join-Path $skillDir.FullName 'SKILL.md' + if (-not (Test-Path $skillFile)) { continue } + $skillName = Get-FrontmatterName -Path $skillFile + $destDir = Join-Path $tmpSkills $skillName + Copy-Item -LiteralPath $skillDir.FullName -Destination $destDir -Recurse + Write-Host "skill : $($skillDir.Name) -> .claude/skills/$skillName" + } + + # ---- Agents ---- + foreach ($agentFile in Get-ChildItem -LiteralPath $srcAgentsDir -Filter '*.agent.md' -File) { + $agentName = $agentFile.BaseName -replace '\.agent$','' # strip trailing .agent + $cleaned = Remove-InferField -Lines (Get-Content -LiteralPath $agentFile.FullName) + # Match generate-llm-docs.ps1 convention: LF + UTF-8 no-BOM (deterministic across PS5/PS7). + $cleanedText = ($cleaned -join "`n").TrimEnd("`n") + "`n" + $destFile = Join-Path $tmpAgents ($agentName + '.md') + [System.IO.File]::WriteAllText($destFile, $cleanedText, [System.Text.UTF8Encoding]::new($false)) + Write-Host "agent : $($agentFile.Name) -> .claude/agents/$agentName.md" + } + + # Compare with current .claude/{skills,agents} + function Get-TreeHash { + param([string]$Root) + if (-not (Test-Path $Root)) { return @{} } + $map = @{} + foreach ($f in Get-ChildItem -LiteralPath $Root -Recurse -File) { + $rel = $f.FullName.Substring($Root.Length).TrimStart('\','/').Replace('\','/') + $map[$rel] = (Get-FileHash -LiteralPath $f.FullName -Algorithm SHA256).Hash + } + return $map + } + + $newMap = @{} + foreach ($kv in (Get-TreeHash -Root $tmpRoot).GetEnumerator()) { $newMap[$kv.Key] = $kv.Value } + + $curRoot = $claudeRoot + $curMap = @{} + foreach ($sub in 'skills','agents') { + $p = Join-Path $curRoot $sub + foreach ($kv in (Get-TreeHash -Root $p).GetEnumerator()) { + $curMap["$sub/$($kv.Key)"] = $kv.Value + } + } + + $diff = $false + foreach ($k in $newMap.Keys) { if ($curMap[$k] -ne $newMap[$k]) { $diff = $true; break } } + if (-not $diff) { foreach ($k in $curMap.Keys) { if (-not $newMap.ContainsKey($k)) { $diff = $true; break } } } + + if ($Check) { + if ($diff) { + Write-Error ".claude/ is out of date. Run: pwsh scripts/sync-claude-plugin.ps1" + exit 1 + } + Write-Host ".claude/ is up to date." -ForegroundColor Green + return + } + + if (-not $diff) { + Write-Host ".claude/ already up to date — no changes." -ForegroundColor Green + return + } + + # Wipe and replace skills/ and agents/ under .claude/ + foreach ($sub in 'skills','agents') { + $p = Join-Path $claudeRoot $sub + if (Test-Path $p) { Remove-Item -LiteralPath $p -Recurse -Force } + } + if (-not (Test-Path $claudeRoot)) { New-Item -ItemType Directory -Path $claudeRoot | Out-Null } + Copy-Item -LiteralPath (Join-Path $tmpRoot 'skills') -Destination (Join-Path $claudeRoot 'skills') -Recurse + Copy-Item -LiteralPath (Join-Path $tmpRoot 'agents') -Destination (Join-Path $claudeRoot 'agents') -Recurse + + Write-Host "`n.claude/ regenerated from .github/plugin/." -ForegroundColor Green +} +finally { + if (Test-Path $tmpRoot) { Remove-Item -LiteralPath $tmpRoot -Recurse -Force -ErrorAction SilentlyContinue } +}