From 791a95a5998647cca43654de8eb7f10ea32545eb Mon Sep 17 00:00:00 2001 From: Nikola Metulev <711864+nmetulev@users.noreply.github.com> Date: Mon, 5 Oct 2026 20:23:22 -0700 Subject: [PATCH 1/2] Stop tracking generated npm command wrappers Generate wrappers before standalone npm compile, watch, test, and docs commands. Preserve explicit schema generation in integrated builds and keep the published JavaScript and declarations unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 9 + scripts/build-cli.ps1 | 7 +- scripts/package-npm.ps1 | 2 +- scripts/tests/artifact-workflow.Tests.ps1 | 1 + scripts/tests/build-cli.Tests.ps1 | 7 +- scripts/tests/npm-codegen.Tests.ps1 | 89 + src/winapp-npm/.gitignore | 3 + src/winapp-npm/package.json | 7 +- src/winapp-npm/scripts/generate-commands.mjs | 3 +- src/winapp-npm/src/winapp-commands.ts | 2151 ------------------ 10 files changed, 120 insertions(+), 2159 deletions(-) create mode 100644 scripts/tests/npm-codegen.Tests.ps1 delete mode 100644 src/winapp-npm/src/winapp-commands.ts diff --git a/AGENTS.md b/AGENTS.md index 569bcc91f..11bd7c8e3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -99,6 +99,15 @@ node cli.js help .\scripts\build-cli.ps1 -OnlyTests -UseExistingArtifacts ``` +`src\winapp-npm\src\winapp-commands.ts` is ignored generated output. Change the +CLI commands or `src\winapp-npm\scripts\generate-commands.mjs`, not that file. +Standalone npm compile, watch, test, and documentation commands regenerate it +automatically, using an available CLI binary or the tracked `docs\cli-schema.json` +on a fresh checkout. The repository build extracts its current CLI schema +explicitly and skips these npm pre-hooks to preserve that schema. Keep the +generated schema and npm API documentation tracked; do not add the generated +TypeScript wrappers to a commit. + ### Running tests on a Microsoft corporate machine `api.nuget.org` is **not reachable** from corp machines. Any test that downloads a package diff --git a/scripts/build-cli.ps1 b/scripts/build-cli.ps1 index 7e0957aa7..7d277c53e 100644 --- a/scripts/build-cli.ps1 +++ b/scripts/build-cli.ps1 @@ -567,7 +567,8 @@ try Write-Error "Node CLI command generation failed" exit 1 } - npm run compile + # Preserve the explicit live schema; standalone pre-hooks may find stale npm binaries. + npm run compile --ignore-scripts if ($LASTEXITCODE -ne 0) { Write-Error "Node CLI compile failed" exit 1 @@ -576,7 +577,7 @@ try if ($RunAuxiliaryTests) { Write-Host "[TEST] Running npm unit tests..." -ForegroundColor Blue - npm test + npm test --ignore-scripts if ($LASTEXITCODE -ne 0) { Write-Warning "npm unit tests failed with exit code $LASTEXITCODE" if ($FailOnTestFailure) { @@ -760,7 +761,7 @@ try Write-Host "[NPM] Generating npm API documentation..." -ForegroundColor Blue Push-Location (Join-Path $ProjectRoot "src\winapp-npm") try { - npm run generate-docs + npm run generate-docs --ignore-scripts if ($LASTEXITCODE -ne 0) { Write-Warning "npm API documentation generation failed, but continuing..." } else { diff --git a/scripts/package-npm.ps1 b/scripts/package-npm.ps1 index 87c541e4c..5e8135487 100644 --- a/scripts/package-npm.ps1 +++ b/scripts/package-npm.ps1 @@ -170,7 +170,7 @@ try exit 1 } - npm run compile + npm run compile --ignore-scripts if ($LASTEXITCODE -ne 0) { Write-Error "TypeScript compilation failed" Pop-Location diff --git a/scripts/tests/artifact-workflow.Tests.ps1 b/scripts/tests/artifact-workflow.Tests.ps1 index 5f520cad9..3ad51efd8 100644 --- a/scripts/tests/artifact-workflow.Tests.ps1 +++ b/scripts/tests/artifact-workflow.Tests.ps1 @@ -90,6 +90,7 @@ Describe 'Artifact-first workflow dependencies' { $codegen | Should -BeLessThan $format $format | Should -BeLessThan $lint $lint | Should -BeLessThan $compile + $npmPackaging | Should -Match 'npm run compile --ignore-scripts' } It 'starts validation, docs, UI E2E and samples from early artifacts, not the final gate' { diff --git a/scripts/tests/build-cli.Tests.ps1 b/scripts/tests/build-cli.Tests.ps1 index f4575463a..648ae9414 100644 --- a/scripts/tests/build-cli.Tests.ps1 +++ b/scripts/tests/build-cli.Tests.ps1 @@ -163,6 +163,9 @@ function npm { throw 'Codegen used a stale schema' } } + if ($step -in @('compile', 'test', 'generate-docs') -and $arguments -notcontains '--ignore-scripts') { + throw 'Standalone generation hooks would replace the explicitly generated commands' + } $global:LASTEXITCODE = if ($fixture.Fail -eq "npm-$step") { 13 } else { 0 } } function Get-Module { @@ -259,8 +262,8 @@ Describe 'build-cli.ps1 control flow' { $result.Trace | Should -Match 'WinApp.Cli.csproj -c Debug --no-build --cli-schema' $result.Trace | Should -Match 'npm ci --ignore-scripts' $result.Trace | Should -Match 'npm run generate-commands --schema .*artifacts\\TestResults\\cli-schema-All.json' - $result.Trace | Should -Match 'npm run compile' - $result.Trace | Should -Match 'npm test' + $result.Trace | Should -Match 'npm run compile --ignore-scripts' + $result.Trace | Should -Match 'npm test --ignore-scripts' $result.Trace | Should -Match 'WinApp.Cli.Tests.csproj -c Debug --no-build' $result.Trace | Should -Match 'WinApp.UIAutomation.Tests.csproj -c Debug --no-build' $result.Trace | Should -Match 'dotnet test .*Microsoft.WindowsAppSDK.Analyzers.Tests.csproj -c Debug' diff --git a/scripts/tests/npm-codegen.Tests.ps1 b/scripts/tests/npm-codegen.Tests.ps1 new file mode 100644 index 000000000..dab865f64 --- /dev/null +++ b/scripts/tests/npm-codegen.Tests.ps1 @@ -0,0 +1,89 @@ +#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } + +BeforeAll { + $script:repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent + $script:npm = (Get-Command npm.cmd -ErrorAction Stop).Source + + function New-NpmFixture { + $root = Join-Path $TestDrive ([guid]::NewGuid().ToString('N')) + $npmRoot = Join-Path $root 'src\winapp-npm' + New-Item -ItemType Directory -Path "$npmRoot\src", "$npmRoot\scripts", "$root\docs" -Force | Out-Null + Copy-Item "$repoRoot\src\winapp-npm\scripts\generate-commands.mjs" "$npmRoot\scripts" + Copy-Item "$repoRoot\docs\cli-schema.json" "$root\docs" + + $package = Get-Content "$repoRoot\src\winapp-npm\package.json" -Raw | ConvertFrom-Json + foreach ($entryPoint in @('compile', 'compile:watch', 'test', 'generate-docs', 'generate-docs:check')) { + $package.scripts.$entryPoint = 'node verify-generated.mjs' + } + $package | ConvertTo-Json -Depth 10 | Set-Content "$npmRoot\package.json" + Set-Content "$npmRoot\verify-generated.mjs" @' +import assert from 'node:assert/strict'; +import { readFileSync, writeFileSync } from 'node:fs'; +const source = readFileSync('src/winapp-commands.ts', 'utf8'); +assert.ok(source.includes('AUTO-GENERATED')); +assert.ok(source.includes('export interface CommonOptions')); +writeFileSync('action-ran.txt', source); +'@ + return $npmRoot + } + + function Invoke-NpmFixture { + param([string]$Root, [string]$EntryPoint) + Push-Location $Root + try { + $output = & $npm run $EntryPoint --ignore-scripts=false 2>&1 + return @{ + ExitCode = $LASTEXITCODE + Output = $output -join "`n" + } + } finally { + Pop-Location + } + } +} + +Describe 'Standalone npm entry points' { + BeforeEach { + $root = New-NpmFixture + } + + It 'generates commands without import.meta.dirname on older supported Node versions' { + $generator = Join-Path $root 'scripts\generate-commands.mjs' + $source = [System.IO.File]::ReadAllText($generator) + [System.IO.File]::WriteAllText($generator, $source.Replace('import.meta.dirname', 'undefined')) + $result = Invoke-NpmFixture $root 'compile' + $result.ExitCode | Should -Be 0 -Because $result.Output + Join-Path $root 'action-ran.txt' | Should -Exist + } + + It ' generates commands before consuming them on a fresh checkout' -ForEach @( + @{ EntryPoint = 'compile' } + @{ EntryPoint = 'compile:watch' } + @{ EntryPoint = 'test' } + @{ EntryPoint = 'generate-docs' } + @{ EntryPoint = 'generate-docs:check' } + @{ EntryPoint = 'prepublishOnly' } + ) { + Join-Path $root 'src\winapp-commands.ts' | Should -Not -Exist + $result = Invoke-NpmFixture $root $EntryPoint + $result.ExitCode | Should -Be 0 -Because $result.Output + Join-Path $root 'src\winapp-commands.ts' | Should -Exist + Join-Path $root 'action-ran.txt' | Should -Exist + } + + It ' stops before consuming commands when generation fails' -ForEach @( + @{ EntryPoint = 'compile' } + @{ EntryPoint = 'compile:watch' } + @{ EntryPoint = 'test' } + @{ EntryPoint = 'generate-docs' } + @{ EntryPoint = 'generate-docs:check' } + @{ EntryPoint = 'prepublishOnly' } + ) { + Set-Content (Join-Path $root '..\..\docs\cli-schema.json') '{invalid' + $result = Invoke-NpmFixture $root $EntryPoint + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'SyntaxError' + Join-Path $root 'src\winapp-commands.ts' | Should -Not -Exist + Join-Path $root 'action-ran.txt' | Should -Not -Exist + } +} diff --git a/src/winapp-npm/.gitignore b/src/winapp-npm/.gitignore index bfdb9ea1f..6bb3c1145 100644 --- a/src/winapp-npm/.gitignore +++ b/src/winapp-npm/.gitignore @@ -12,5 +12,8 @@ bin/ # Ignore TypeScript compiled output dist/ +# Command wrappers are regenerated from the CLI schema before compilation, tests, and docs. +/src/winapp-commands.ts + # Node modules node_modules/ diff --git a/src/winapp-npm/package.json b/src/winapp-npm/package.json index 222d8bc89..b0c20bb60 100644 --- a/src/winapp-npm/package.json +++ b/src/winapp-npm/package.json @@ -10,16 +10,21 @@ "scripts": { "generate-commands": "node scripts/generate-commands.mjs", "generate-commands:check": "node scripts/generate-commands.mjs --check", + "pregenerate-docs": "npm run generate-commands", "generate-docs": "node scripts/generate-docs.mjs", + "pregenerate-docs:check": "npm run generate-commands", "generate-docs:check": "node scripts/generate-docs.mjs --check", + "precompile": "npm run generate-commands", "compile": "tsc", + "precompile:watch": "npm run generate-commands", "compile:watch": "tsc --watch", + "pretest": "npm run generate-commands", "test": "tsc -p tsconfig.test.json && node --test \"dist-test/test/**/*.test.js\"", "lint": "eslint src/", "lint:fix": "eslint src/ --fix", "format": "prettier --write src/", "format:check": "prettier --check src/", - "build": "npm run generate-commands && npm run format:check && npm run lint && npm run compile && npm run build-cli", + "build": "npm run generate-commands && npm run format:check && npm run lint && npm run compile --ignore-scripts && npm run build-cli", "build-cli": "npm run build-x64 && npm run build-arm64", "build-x64": "dotnet publish ../winapp-CLI/WinApp.Cli/WinApp.Cli.csproj -c Release -r win-x64 --self-contained -o bin/win-x64", "build-arm64": "dotnet publish ../winapp-CLI/WinApp.Cli/WinApp.Cli.csproj -c Release -r win-arm64 --self-contained -o bin/win-arm64", diff --git a/src/winapp-npm/scripts/generate-commands.mjs b/src/winapp-npm/scripts/generate-commands.mjs index d162d95b2..687082b25 100644 --- a/src/winapp-npm/scripts/generate-commands.mjs +++ b/src/winapp-npm/scripts/generate-commands.mjs @@ -12,6 +12,7 @@ import { execSync } from 'node:child_process'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { resolve, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; // --------------------------------------------------------------------------- // CLI arg parsing @@ -21,7 +22,7 @@ const checkOnly = args.includes('--check'); const schemaIdx = args.indexOf('--schema'); const schemaOverride = schemaIdx !== -1 ? args[schemaIdx + 1] : null; -const SCRIPT_DIR = import.meta.dirname; +const SCRIPT_DIR = fileURLToPath(new URL('.', import.meta.url)); const NPM_ROOT = resolve(SCRIPT_DIR, '..'); const OUTPUT = resolve(NPM_ROOT, 'src/winapp-commands.ts'); diff --git a/src/winapp-npm/src/winapp-commands.ts b/src/winapp-npm/src/winapp-commands.ts deleted file mode 100644 index 069a8b790..000000000 --- a/src/winapp-npm/src/winapp-commands.ts +++ /dev/null @@ -1,2151 +0,0 @@ -/** - * AUTO-GENERATED — DO NOT EDIT - * - * Regenerate with: npm run generate-commands - * Source schema version: 0.7.2 - * - * Programmatic wrappers for all winapp CLI commands. - * Each function builds the CLI arguments, invokes the native CLI, - * and returns a typed result with captured stdout/stderr. - */ -import { - callWinappCliCapture, - CallWinappCliCaptureOptions, - CallWinappCliCaptureResult, -} from './winapp-cli-utils'; - -// --------------------------------------------------------------------------- -// Shared / common types -// --------------------------------------------------------------------------- - -/** IfExists values. */ -export type IfExists = 'error' | 'overwrite' | 'skip'; - -/** SdkInstallMode values. */ -export type SdkInstallMode = 'stable' | 'preview' | 'experimental' | 'none'; - -/** ManifestTemplates values. */ -export type ManifestTemplates = 'packaged' | 'sparse'; - -/** Base options shared by most commands. */ -export interface CommonOptions { - /** Suppress progress messages. */ - quiet?: boolean; - /** Enable verbose output. */ - verbose?: boolean; - /** Working directory for the CLI process (defaults to process.cwd()). */ - cwd?: string; - /** - * Cancels the whole native invocation, not just a wait for the shared desktop. - * - * `winapp ui` commands take cooperative turns on the desktop, so a command may wait for another - * workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may - * not run, but Windows releases its coordination handles and deletes its participant lease, and - * other processes reclaim the queue entry. If the abort lands after the command acquired the - * desktop, UI side effects may already have happened, and aborting an active recording can leave - * partial output. Rejects with an `AbortError`. - */ - signal?: AbortSignal; - /** - * Groups this call with other `winapp ui` calls passing the same value into one logical workflow. - * - * Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn - * whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop - * reserved between invocations for a short idle grace, may overlap with each other (a recording and - * the clicks it is recording), and are never interleaved with another workflow's input. Without it, - * each call is a self-contained one-shot that releases the desktop as soon as it finishes. - * - * Applied to the spawned child process only; `process.env` is never modified. - */ - workflowId?: string; -} - -/** Result returned by every command wrapper. */ -export interface WinappResult { - /** Process exit code (always 0 on success – non-zero throws). */ - exitCode: number; - /** Captured standard output. */ - stdout: string; - /** Captured standard error. */ - stderr: string; -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -function pushCommon(args: string[], opts: CommonOptions): void { - // Insert global flags before any "--" passthrough separator so winapp consumes them rather - // than forwarding them to the launched app/tool (e.g. run ... -- appArgs). - const flags: string[] = []; - if (opts.quiet) flags.push('--quiet'); - if (opts.verbose) flags.push('--verbose'); - if (flags.length === 0) return; - const sep = args.indexOf('--'); - if (sep === -1) args.push(...flags); - else args.splice(sep, 0, ...flags); -} - -function captureOpts(opts: CommonOptions): CallWinappCliCaptureOptions { - const result: CallWinappCliCaptureOptions = {}; - if (opts.cwd) result.cwd = opts.cwd; - if (opts.signal) result.signal = opts.signal; - if (opts.workflowId !== undefined) result.workflowId = opts.workflowId; - return result; -} - -async function execCommand(args: string[], opts: CommonOptions): Promise { - pushCommon(args, opts); - const result: CallWinappCliCaptureResult = await callWinappCliCapture(args, captureOpts(opts)); - return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr }; -} - -// --------------------------------------------------------------------------- -// az-sign -// --------------------------------------------------------------------------- - -export interface AzSignOptions extends CommonOptions { - /** Path to the file to sign (exe, msix, or msixbundle) */ - filePath: string; - /** Signing account name. Must be used with --resource-group */ - account?: string; - /** Path to an existing metadata.json file. Skips resource discovery and account/profile selection prompts and signs using this file directly. A non-interactive Azure credential should already be available; the CLI can otherwise fall back to an interactive tenant prompt or 'az login', but the npm programmatic API is always non-interactive and fails instead of prompting. */ - metadataFile?: string; - /** Certificate profile name. Must be used with --account */ - profile?: string; - /** Resource group to narrow down signing accounts */ - resourceGroup?: string; - /** Azure subscription ID to use. If not provided and multiple subscriptions exist, you will be prompted. */ - subscription?: string; -} - -/** - * Code-sign a file using Azure Trusted Signing. Signs executables, MSIX packages, or MSIX bundles using a cloud-managed signing identity. Example: winapp az-sign ./app.msix - */ -export async function azSign(options: AzSignOptions): Promise { - const args: string[] = ['az-sign']; - const positionals: string[] = []; - positionals.push(options.filePath); - if (options.account !== undefined) args.push('--account', options.account); - if (options.metadataFile !== undefined) args.push('--metadata-file', options.metadataFile); - if (options.profile !== undefined) args.push('--profile', options.profile); - if (options.resourceGroup !== undefined) args.push('--resource-group', options.resourceGroup); - if (options.subscription !== undefined) args.push('--subscription', options.subscription); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// cert generate -// --------------------------------------------------------------------------- - -export interface CertGenerateOptions extends CommonOptions { - /** Export a .cer file (public key only) alongside the .pfx */ - exportCer?: boolean; - /** Behavior when output file exists: 'error' (fail, default), 'skip' (keep existing), or 'overwrite' (replace) */ - ifExists?: IfExists; - /** Install the certificate to the local machine store after generation */ - install?: boolean; - /** Format output as JSON */ - json?: boolean; - /** Path to Package.appxmanifest or appxmanifest.xml file to extract publisher information from */ - manifest?: string; - /** Output path for the generated PFX file */ - output?: string; - /** Password for the generated PFX file. Defaults to 'password', which is publicly known — a certificate left with that password is development-only, because anyone who obtains the .pfx can sign as you. */ - password?: string; - /** Publisher distinguished name (DN) for the generated certificate (e.g., CN=MyCompany or OU=Team, O=Corp, C=US). Components must be single-valued and comma-separated; multi-valued '+' RDNs, ';' separators, and backslashes are not supported. If not specified, will be inferred from manifest. Bare names are auto-wrapped as CN=. */ - publisher?: string; - /** Number of days the certificate is valid */ - validDays?: number; -} - -/** - * 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. - */ -export async function certGenerate(options: CertGenerateOptions = {}): Promise { - const args: string[] = ['cert', 'generate']; - if (options.exportCer) args.push('--export-cer'); - if (options.ifExists !== undefined) args.push('--if-exists', options.ifExists); - if (options.install) args.push('--install'); - if (options.json) args.push('--json'); - if (options.manifest !== undefined) args.push('--manifest', options.manifest); - if (options.output !== undefined) args.push('--output', options.output); - if (options.password !== undefined) args.push('--password', options.password); - if (options.publisher !== undefined) args.push('--publisher', options.publisher); - if (options.validDays !== undefined) args.push('--valid-days', options.validDays.toString()); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// cert info -// --------------------------------------------------------------------------- - -export interface CertInfoOptions extends CommonOptions { - /** Path to the certificate file (PFX or CER) */ - certPath: string; - /** Format output as JSON */ - json?: boolean; - /** Password for the PFX file (ignored for a public CER) */ - password?: string; -} - -/** - * Display certificate details (subject, thumbprint, expiry). Useful for verifying a certificate matches your manifest before signing. - */ -export async function certInfo(options: CertInfoOptions): Promise { - const args: string[] = ['cert', 'info']; - const positionals: string[] = []; - positionals.push(options.certPath); - if (options.json) args.push('--json'); - if (options.password !== undefined) args.push('--password', options.password); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// cert install -// --------------------------------------------------------------------------- - -export interface CertInstallOptions extends CommonOptions { - /** Path to the certificate file (PFX or CER) */ - certPath: string; - /** Force installation even if the certificate already exists */ - force?: boolean; - /** Password for the PFX file */ - password?: string; -} - -/** - * 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. - */ -export async function certInstall(options: CertInstallOptions): Promise { - const args: string[] = ['cert', 'install']; - const positionals: string[] = []; - positionals.push(options.certPath); - if (options.force) args.push('--force'); - if (options.password !== undefined) args.push('--password', options.password); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// create-debug-identity -// --------------------------------------------------------------------------- - -export interface CreateDebugIdentityOptions extends CommonOptions { - /** Path to the .exe that will need to run with identity, or entrypoint script. */ - entrypoint?: string; - /** Keep the package identity from the manifest as-is, without appending '.debug' to the package name and application ID. */ - keepIdentity?: boolean; - /** Path to the Package.appxmanifest or appxmanifest.xml */ - manifest?: string; - /** Do not install the package after creation. */ - noInstall?: boolean; -} - -/** - * 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/. - */ -export async function createDebugIdentity(options: CreateDebugIdentityOptions = {}): Promise { - const args: string[] = ['create-debug-identity']; - const positionals: string[] = []; - if (options.entrypoint) positionals.push(options.entrypoint); - if (options.keepIdentity) args.push('--keep-identity'); - if (options.manifest !== undefined) args.push('--manifest', options.manifest); - if (options.noInstall) args.push('--no-install'); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// create-external-catalog -// --------------------------------------------------------------------------- - -export interface CreateExternalCatalogOptions extends CommonOptions { - /** List of input folders with executable files to process (separated by semicolons) */ - inputFolder: string; - /** Include flat hashes when generating the catalog */ - computeFlatHashes?: boolean; - /** Behavior when output file already exists */ - ifExists?: IfExists; - /** Output catalog file path. If not specified, the default CodeIntegrityExternal.cat name is used. */ - output?: string; - /** Include files from subdirectories */ - recursive?: boolean; - /** Include page hashes when generating the catalog */ - usePageHashes?: boolean; -} - -/** - * 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. - */ -export async function createExternalCatalog(options: CreateExternalCatalogOptions): Promise { - const args: string[] = ['create-external-catalog']; - const positionals: string[] = []; - positionals.push(options.inputFolder); - if (options.computeFlatHashes) args.push('--compute-flat-hashes'); - if (options.ifExists !== undefined) args.push('--if-exists', options.ifExists); - if (options.output !== undefined) args.push('--output', options.output); - if (options.recursive) args.push('--recursive'); - if (options.usePageHashes) args.push('--use-page-hashes'); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// embed-identity -// --------------------------------------------------------------------------- - -export interface EmbedIdentityOptions extends CommonOptions { - /** Path to the .exe (embeds identity into its side-by-side manifest via mt.exe) or an .xml/.manifest side-by-side manifest file (inserts/replaces the element; created if it doesn't exist). */ - target: string; - /** Path to the sparse appxmanifest.xml to read identity from. When omitted, searched in a 'sparse/' folder (where 'winapp init --exe --sparse' writes it by default) beside the target first, then in the current directory, then beside the target and in the current directory. */ - manifest?: string; -} - -/** - * Connect a desktop exe to its sparse identity package by embedding the element. Reads identity (packageName, publisher, applicationId) from a sparse appxmanifest.xml and writes it into the target's side-by-side (fusion) manifest. EXE targets are updated with mt.exe; .xml/.manifest targets are edited directly. Example: winapp embed-identity ./bin/MyApp.exe. This is step 3 of the sparse packaging workflow (after 'winapp init --exe --sparse' and 'winapp pack'). - */ -export async function embedIdentity(options: EmbedIdentityOptions): Promise { - const args: string[] = ['embed-identity']; - const positionals: string[] = []; - positionals.push(options.target); - if (options.manifest !== undefined) args.push('--manifest', options.manifest); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// find-api -// --------------------------------------------------------------------------- - -export interface FindApiOptions extends CommonOptions { - /** What to search for, e.g. "acrylic brush" or "NavigationView". Matched lexically against type and member names across the project's indexed API metadata. Pass several quoted queries to run them in a single call. */ - query?: string | string[]; - /** Format output as JSON */ - json?: boolean; - /** Maximum number of namespace-grouped results to return. */ - max?: number; - /** Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. */ - project?: string; - /** Project directory to query (defaults to the current directory). Used to locate the indexed project. */ - projectDir?: string; -} - -/** - * Agent-first: built primarily for AI coding agents to ground code generation in the API surface a project actually references instead of guessing (pair it with --json); it works just as well typed by hand. Search and inspect the Windows/WinRT API surface (types, members, enums) available to a project, resolved from its referenced .winmd/.dll metadata. The bare form searches; sub-verbs drill into a specific type or the index itself. Search, members, enums, and check-property each accept several subjects in one call — batch your lookups rather than issuing one call per question. The index is built from the project's restored NuGet/SDK packages and refreshed automatically when the project is restored. - */ -export async function findApi(options: FindApiOptions = {}): Promise { - const args: string[] = ['find-api']; - const positionals: string[] = []; - if (options.query) { - const queryArr = Array.isArray(options.query) ? options.query : [options.query]; - positionals.push(...queryArr); - } - if (options.json) args.push('--json'); - if (options.max !== undefined) args.push('--max', options.max.toString()); - if (options.project !== undefined) args.push('--project', options.project); - if (options.projectDir !== undefined) args.push('--project-dir', options.projectDir); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// find-api check-property -// --------------------------------------------------------------------------- - -export interface FindApiCheckPropertyOptions extends CommonOptions { - /** The type to check. */ - type?: string; - /** One or more property names to validate on the type. Pass several to check them all in a single call. */ - property?: string | string[]; - /** Format output as JSON */ - json?: boolean; - /** Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. */ - project?: string; - /** Project directory to query (defaults to the current directory). Used to locate the indexed project. */ - projectDir?: string; -} - -/** - * Validate that one or more properties exist on a type before you write XAML/code against it. Pass several property names to check them in one call. On a miss, suggests similar properties on the type, attached-property forms, and other types that declare the property. Exits non-zero when any property does not exist. - */ -export async function findApiCheckProperty(options: FindApiCheckPropertyOptions = {}): Promise { - const args: string[] = ['find-api', 'check-property']; - const positionals: string[] = []; - if (options.type) positionals.push(options.type); - if (options.property) { - const propertyArr = Array.isArray(options.property) ? options.property : [options.property]; - positionals.push(...propertyArr); - } - if (options.json) args.push('--json'); - if (options.project !== undefined) args.push('--project', options.project); - if (options.projectDir !== undefined) args.push('--project-dir', options.projectDir); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// find-api enums -// --------------------------------------------------------------------------- - -export interface FindApiEnumsOptions extends CommonOptions { - /** One or more enum types to list, e.g. Symbol or Microsoft.UI.Xaml.Controls.Symbol. Pass several to list them in a single call. */ - type?: string | string[]; - /** Only list values whose name contains this text (case-insensitive), e.g. --filter folder. The unfiltered total is still reported. Prefer listing the whole enum once over repeated filtered calls — most enums are small enough that the full list is cheaper than several narrowed lookups. */ - filter?: string; - /** Format output as JSON */ - json?: boolean; - /** Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. */ - project?: string; - /** Project directory to query (defaults to the current directory). Used to locate the indexed project. */ - projectDir?: string; -} - -/** - * List the values of one or more enum types. Pass several type names to list them in one call. Exits non-zero when a type exists but is not an enum. - */ -export async function findApiEnums(options: FindApiEnumsOptions = {}): Promise { - const args: string[] = ['find-api', 'enums']; - const positionals: string[] = []; - if (options.type) { - const typeArr = Array.isArray(options.type) ? options.type : [options.type]; - positionals.push(...typeArr); - } - if (options.filter !== undefined) args.push('--filter', options.filter); - if (options.json) args.push('--json'); - if (options.project !== undefined) args.push('--project', options.project); - if (options.projectDir !== undefined) args.push('--project-dir', options.projectDir); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// find-api members -// --------------------------------------------------------------------------- - -export interface FindApiMembersOptions extends CommonOptions { - /** One or more types to inspect. Accepts short names (NavigationView) or fully-qualified names (Microsoft.UI.Xaml.Controls.NavigationView). Pass several to resolve them in a single call. */ - type?: string | string[]; - /** List the complete member surface: include dependency-property identifier statics (BackgroundProperty) and per-member descriptions, both of which an unfiltered listing omits to save context. Implied by --verbose, and usable together with --json (--verbose is not). */ - all?: boolean; - /** Only list members whose name contains this text (case-insensitive), e.g. --filter background. Totals for the unfiltered type are still reported. Applies to every type in the call. */ - filter?: string; - /** Format output as JSON */ - json?: boolean; - /** Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. */ - project?: string; - /** Project directory to query (defaults to the current directory). Used to locate the indexed project. */ - projectDir?: string; -} - -/** - * List the properties, events, and methods of one or more types (with XML-doc descriptions and inherited members), resolved from the project's indexed API metadata. Pass several type names to inspect them all in one call. - */ -export async function findApiMembers(options: FindApiMembersOptions = {}): Promise { - const args: string[] = ['find-api', 'members']; - const positionals: string[] = []; - if (options.type) { - const typeArr = Array.isArray(options.type) ? options.type : [options.type]; - positionals.push(...typeArr); - } - if (options.all) args.push('--all'); - if (options.filter !== undefined) args.push('--filter', options.filter); - if (options.json) args.push('--json'); - if (options.project !== undefined) args.push('--project', options.project); - if (options.projectDir !== undefined) args.push('--project-dir', options.projectDir); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// find-api packages -// --------------------------------------------------------------------------- - -export interface FindApiPackagesOptions extends CommonOptions { - /** Format output as JSON */ - json?: boolean; - /** Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. */ - project?: string; - /** Project directory to query (defaults to the current directory). Used to locate the indexed project. */ - projectDir?: string; -} - -/** - * List the NuGet/SDK packages whose API metadata is indexed for a project, with per-package type and member counts. - */ -export async function findApiPackages(options: FindApiPackagesOptions = {}): Promise { - const args: string[] = ['find-api', 'packages']; - if (options.json) args.push('--json'); - if (options.project !== undefined) args.push('--project', options.project); - if (options.projectDir !== undefined) args.push('--project-dir', options.projectDir); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// find-api refresh -// --------------------------------------------------------------------------- - -export interface FindApiRefreshOptions extends CommonOptions { - /** Format output as JSON */ - json?: boolean; - /** Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. */ - project?: string; - /** Project directory to query (defaults to the current directory). Used to locate the indexed project. */ - projectDir?: string; - /** Recursively discover and index every project under the directory instead of just the top-level project(s). */ - scan?: boolean; -} - -/** - * Rebuild the API metadata index for a project from its restored packages. Runs automatically when a project is restored; run it manually to force a re-index or to index a project for the first time. - */ -export async function findApiRefresh(options: FindApiRefreshOptions = {}): Promise { - const args: string[] = ['find-api', 'refresh']; - if (options.json) args.push('--json'); - if (options.project !== undefined) args.push('--project', options.project); - if (options.projectDir !== undefined) args.push('--project-dir', options.projectDir); - if (options.scan) args.push('--scan'); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// find-api stats -// --------------------------------------------------------------------------- - -export interface FindApiStatsOptions extends CommonOptions { - /** Format output as JSON */ - json?: boolean; - /** Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. */ - project?: string; - /** Project directory to query (defaults to the current directory). Used to locate the indexed project. */ - projectDir?: string; -} - -/** - * Show aggregate statistics for a project's API index: package, namespace, type, member, and .winmd file counts. - */ -export async function findApiStats(options: FindApiStatsOptions = {}): Promise { - const args: string[] = ['find-api', 'stats']; - if (options.json) args.push('--json'); - if (options.project !== undefined) args.push('--project', options.project); - if (options.projectDir !== undefined) args.push('--project-dir', options.projectDir); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// find-ui -// --------------------------------------------------------------------------- - -export interface FindUiOptions extends CommonOptions { - /** What you're looking for, e.g. "tabbed layout" or "color picker". Matched lexically against WinUI control names, sample headers, and tags. */ - query?: string; - /** Fetch the code (Gallery/Toolkit return XAML and/or C#; Reactor is C#-only) plus prerequisite notes for one or more scenario ids from a prior search (e.g. gallery-tabview-1). */ - id?: string | string[]; - /** Format output as JSON */ - json?: boolean; - /** List every discoverable control/sample id instead of searching. Covers Gallery, Toolkit, and core; the opt-in Reactor source is excluded (search it with --source reactor). */ - list?: boolean; - /** Maximum number of matched controls to return. Applies to search only; ignored with --list and --id. */ - max?: number; - /** Bypass the local cache and re-fetch the WinUI corpus from GitHub. */ - refresh?: boolean; - /** Restrict results to a single source: gallery (WinUI 3 Gallery), toolkit (Windows Community Toolkit), reactor (microsoft-ui-reactor, C#-only declarative WinUI), or core (curated patterns). Reactor is opt-in — it is excluded from a normal search, so pass --source reactor to search it (only do this for a Reactor/MVU project; its C#-only samples don't paste into a standard XAML app). */ - source?: string; -} - -/** - * Agent-first: built primarily for AI coding agents to pull a real WinUI sample into the editor instead of inventing markup (pair it with --json); it works just as well typed by hand. Search WinUI controls and samples for a working code example. WinUI-only: covers the WinUI 3 Gallery and the Windows Community Toolkit by default (plus the microsoft-ui-reactor ReactorGallery as an opt-in source via --source reactor); not WPF/WinForms. A corpus is baked into the CLI, so this works offline and behind proxies; when GitHub is reachable it refreshes to the latest samples and caches them per-user. - */ -export async function findUi(options: FindUiOptions = {}): Promise { - const args: string[] = ['find-ui']; - const positionals: string[] = []; - if (options.query) positionals.push(options.query); - if (options.id) { - const idArr = Array.isArray(options.id) ? options.id : [options.id]; - for (const v of idArr) args.push('--id', v); - } - if (options.json) args.push('--json'); - if (options.list) args.push('--list'); - if (options.max !== undefined) args.push('--max', options.max.toString()); - if (options.refresh) args.push('--refresh'); - if (options.source !== undefined) args.push('--source', options.source); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// get-winapp-path -// --------------------------------------------------------------------------- - -export interface GetWinappPathOptions extends CommonOptions { - /** Get the global .winapp directory instead of local */ - global?: boolean; -} - -/** - * 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. - */ -export async function getWinappPath(options: GetWinappPathOptions = {}): Promise { - const args: string[] = ['get-winapp-path']; - if (options.global) args.push('--global'); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// init -// --------------------------------------------------------------------------- - -export interface InitOptions extends CommonOptions { - /** Base/root directory for the winapp workspace, for consumption or installation. */ - baseDirectory?: string; - /** Directory to read/store configuration (default: the selected project directory, or current directory if no project is detected) */ - configDir?: string; - /** Only handle configuration file operations (create if missing, validate if exists). Skip package installation and other workspace setup steps. */ - configOnly?: boolean; - /** Path to the application executable. Requires --sparse. Generates an identity-only sparse manifest for the exe instead of a full package/SDK setup. */ - exe?: string; - /** Overwrite an existing appxmanifest.xml in the target directory (sparse only). Without this, init fails instead of replacing existing manifest/asset files. */ - force?: boolean; - /** Don't use configuration file for version management */ - ignoreConfig?: boolean; - /** Override the package name (sparse only; default: inferred from the exe) */ - name?: string; - /** Don't update .gitignore file */ - noGitignore?: boolean; - /** Directory to write the sparse manifest and Assets/ (sparse only; default: a 'sparse/' folder in the current directory) */ - outputDir?: string; - /** Override the publisher CN (sparse only; default: inferred from the exe's company name). Bare names are auto-wrapped as CN=. */ - publisher?: string; - /** SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation) */ - setupSdks?: SdkInstallMode; - /** Generate a sparse identity manifest (appxmanifest.xml) for an existing desktop exe instead of a full package manifest. Use with --exe. Skips SDK/package installation. */ - sparse?: boolean; - /** Skip interactive prompts and use default answers. Normal init targets the positional project directory if given, otherwise the current directory (e.g., winapp init . --use-defaults). Sparse init (--exe --sparse) ignores the positional directory and writes to --output-dir instead. */ - useDefaults?: boolean; -} - -/** - * 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. - */ -export async function init(options: InitOptions = {}): Promise { - const args: string[] = ['init']; - const positionals: string[] = []; - if (options.baseDirectory) positionals.push(options.baseDirectory); - if (options.configDir !== undefined) args.push('--config-dir', options.configDir); - if (options.configOnly) args.push('--config-only'); - if (options.exe !== undefined) args.push('--exe', options.exe); - if (options.force) args.push('--force'); - if (options.ignoreConfig) args.push('--ignore-config'); - if (options.name !== undefined) args.push('--name', options.name); - if (options.noGitignore) args.push('--no-gitignore'); - if (options.outputDir !== undefined) args.push('--output-dir', options.outputDir); - if (options.publisher !== undefined) args.push('--publisher', options.publisher); - if (options.setupSdks !== undefined) args.push('--setup-sdks', options.setupSdks); - if (options.sparse) args.push('--sparse'); - if (options.useDefaults) args.push('--use-defaults'); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// manifest add-alias -// --------------------------------------------------------------------------- - -export interface ManifestAddAliasOptions extends CommonOptions { - /** Application Id to add the alias to (default: first Application element) */ - appId?: string; - /** Path to Package.appxmanifest or appxmanifest.xml file (default: search current directory) */ - manifest?: string; - /** Alias name (e.g. 'myapp.exe'). Default: inferred from the Executable attribute in the manifest. */ - name?: string; -} - -/** - * 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). - */ -export async function manifestAddAlias(options: ManifestAddAliasOptions = {}): Promise { - const args: string[] = ['manifest', 'add-alias']; - if (options.appId !== undefined) args.push('--app-id', options.appId); - if (options.manifest !== undefined) args.push('--manifest', options.manifest); - if (options.name !== undefined) args.push('--name', options.name); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// manifest generate -// --------------------------------------------------------------------------- - -export interface ManifestGenerateOptions extends CommonOptions { - /** Directory to generate manifest in */ - directory?: string; - /** Human-readable app description shown during installation and in Windows Settings */ - description?: string; - /** Path to the application's executable. Default: .exe */ - executable?: string; - /** Behavior when output file exists: 'error' (fail, default), 'skip' (keep existing), or 'overwrite' (replace) */ - ifExists?: IfExists; - /** Path to logo image file */ - logoPath?: string; - /** Package name (default: folder name) */ - packageName?: string; - /** Publisher distinguished name (DN) (default: CN=). Accepts an X.500 DN with single-valued, comma-separated components (multi-valued '+' RDNs, ';' separators, and backslashes are not supported); bare names are auto-wrapped as CN=. */ - publisherName?: string; - /** Manifest template type: 'packaged' (full MSIX app, default) or 'sparse' (desktop app with package identity for Windows APIs) */ - template?: ManifestTemplates; - /** App version in Major.Minor.Build.Revision format (e.g., 1.0.0.0). */ - version?: string; -} - -/** - * 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). - */ -export async function manifestGenerate(options: ManifestGenerateOptions = {}): Promise { - const args: string[] = ['manifest', 'generate']; - const positionals: string[] = []; - if (options.directory) positionals.push(options.directory); - if (options.description !== undefined) args.push('--description', options.description); - if (options.executable !== undefined) args.push('--executable', options.executable); - if (options.ifExists !== undefined) args.push('--if-exists', options.ifExists); - if (options.logoPath !== undefined) args.push('--logo-path', options.logoPath); - if (options.packageName !== undefined) args.push('--package-name', options.packageName); - if (options.publisherName !== undefined) args.push('--publisher-name', options.publisherName); - if (options.template !== undefined) args.push('--template', options.template); - if (options.version !== undefined) args.push('--version', options.version); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// manifest update-assets -// --------------------------------------------------------------------------- - -export interface ManifestUpdateAssetsOptions extends CommonOptions { - /** Path to source image file (SVG, PNG, ICO, JPG, BMP, GIF) */ - imagePath: string; - /** Path to source image for light theme variants (SVG, PNG, ICO, JPG, BMP, GIF) */ - lightImage?: string; - /** Path to Package.appxmanifest or appxmanifest.xml file (default: search current directory) */ - manifest?: string; -} - -/** - * Generate new assets for images referenced in a Package.appxmanifest from a single source image. Source image should be at least 400x400 pixels. - */ -export async function manifestUpdateAssets(options: ManifestUpdateAssetsOptions): Promise { - const args: string[] = ['manifest', 'update-assets']; - const positionals: string[] = []; - positionals.push(options.imagePath); - if (options.lightImage !== undefined) args.push('--light-image', options.lightImage); - if (options.manifest !== undefined) args.push('--manifest', options.manifest); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// new -// --------------------------------------------------------------------------- - -export interface NewOptions extends CommonOptions { - /** Scaffold even if the output directory already contains files. */ - force?: boolean; - /** Format output as JSON */ - json?: boolean; - /** List the available WinUI templates and exit (installs the latest template pack if none is installed). */ - list?: boolean; - /** Name for the new app/project (default: derived from --output, else 'WinUIApp'). */ - name?: string; - /** Directory to create the app in (default: ./). Created if it doesn't exist. */ - output?: string; - /** Template short name. XAML templates: winui, winui-navview, winui-tabview, winui-mvvm, winui-lib, winui-unittest. Experimental Reactor (C#-only, MVU) templates: reactor, reactor-mvu, reactor-navview, reactor-tabview. Run 'winapp new --list' to see all. */ - template?: string; - /** WinUI template pack version: 'latest' (install newest), 'installed' (keep what's installed), or an explicit version. Default: install latest if none, else prompt to update a stale pack. */ - templateVersion?: string; - /** Do not prompt; use defaults (blank template, name from --output/--name, keep installed templates). */ - useDefaults?: boolean; -} - -/** - * Create a new WinUI app from an official Windows App SDK template. Templates cover both markup-based XAML apps (blank, NavigationView, TabView, MVVM) and the experimental Reactor apps (C#-only, MVU) — pick one interactively, then a name (the output directory defaults to ./). Automatically uses defaults in non-interactive environments (use --use-defaults to skip prompts explicitly). Requires the .NET SDK; installs the WinUI template pack on demand (grabbing the latest, or offering to update a stale one) and delegates scaffolding to 'dotnet new'. Use --list to see the available templates. Scaffolds against the installed SDK's target framework and prints a template-specific next step when done (e.g. 'dotnet run' for app templates). - */ -export async function newCommand(options: NewOptions = {}): Promise { - const args: string[] = ['new']; - if (options.force) args.push('--force'); - if (options.json) args.push('--json'); - if (options.list) args.push('--list'); - if (options.name !== undefined) args.push('--name', options.name); - if (options.output !== undefined) args.push('--output', options.output); - if (options.template !== undefined) args.push('--template', options.template); - if (options.templateVersion !== undefined) args.push('--template-version', options.templateVersion); - if (options.useDefaults) args.push('--use-defaults'); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// package -// --------------------------------------------------------------------------- - -export interface PackageOptions extends CommonOptions { - /** A single .csproj to build and package (project mode), one or more input folders with package layout, or a single sparse appxmanifest.xml file (an identity-only package with AllowExternalContent). Pass multiple folders to create an MSIX bundle (e.g., winapp pack ./publish/x64 ./publish/arm64). */ - inputFolder: string | string[]; - /** Project mode: target architecture (x64, arm64, or x86). Repeatable — pass two or more to publish each and produce one architecture .msixbundle. Requires a .csproj input; rejected for folder/bundle/manifest inputs. Default: the current process architecture. */ - arch?: string | string[]; - /** Path to signing certificate (will auto-sign if provided) */ - cert?: string; - /** Certificate password (default: password) */ - certPassword?: string; - /** Project mode: build configuration (e.g., Debug, Release). Requires a .csproj input; rejected for folder/bundle/manifest inputs. Default: Release. */ - configuration?: string; - /** Path to the executable relative to the input folder. */ - executable?: string; - /** Project mode: target framework moniker for multi-targeted projects (e.g. net10.0-windows10.0.26100.0). Requires a .csproj input; rejected for folder/bundle/manifest inputs. */ - framework?: string; - /** Generate a new development certificate */ - generateCert?: boolean; - /** Install certificate to machine */ - installCert?: boolean; - /** Path to AppX manifest file (default: auto-detect from input folder or current directory) */ - manifest?: string; - /** Package name (default: from manifest) */ - name?: string; - /** Project mode: skip building and package the existing build output (still evaluates output properties). Requires a .csproj input; rejected for folder/bundle/manifest inputs. */ - noBuild?: boolean; - /** Project mode: skip restoring the project before building. Requires a .csproj input; rejected for folder/bundle/manifest inputs. */ - noRestore?: boolean; - /** Deliver the package unsigned, overriding any project signing configuration (e.g. for Store submission or an external signing pipeline). Cannot be combined with --cert or --generate-cert. */ - noSign?: boolean; - /** Output file name for the generated package (.msix) or bundle (.msixbundle). Defaults to __.msix for single packages, or ___.msixbundle for bundles. */ - output?: string; - /** Project mode: MSBuild property as Name=Value, forwarded to both build and evaluation. Repeatable (e.g. -p WindowsPackageType=None). Use -c for configuration, -f for framework, and --arch for architecture; a -p Configuration/TargetFramework is dropped in favor of those flags, while a lone -p RuntimeIdentifier (no --arch) selects an exact RID. Requires a .csproj input; rejected for folder/bundle/manifest inputs. */ - property?: string | string[]; - /** Publisher distinguished name (DN) for certificate generation (e.g., CN=MyCompany). Bare names are auto-wrapped as CN=. */ - publisher?: string; - /** Bundle Windows App SDK runtime for self-contained deployment */ - selfContained?: boolean; - /** Skip PRI file generation */ - skipPri?: boolean; -} - -/** - * Create an MSIX installer from a built app folder or directly from a .csproj. Pass a package-layout folder (run after building your app; a manifest must be in the current directory, passed as --manifest, or in the input folder), or pass a .csproj to build and package it in one step (e.g. winapp package ./MyApp.csproj -c Release). Use --cert devcert.pfx to sign for testing. - */ -export async function packageApp(options: PackageOptions): Promise { - const args: string[] = ['package']; - const positionals: string[] = []; - const inputFolderArr = Array.isArray(options.inputFolder) ? options.inputFolder : [options.inputFolder]; - positionals.push(...inputFolderArr); - if (options.arch) { - const archArr = Array.isArray(options.arch) ? options.arch : [options.arch]; - for (const v of archArr) args.push('--arch', v); - } - if (options.cert !== undefined) args.push('--cert', options.cert); - if (options.certPassword !== undefined) args.push('--cert-password', options.certPassword); - if (options.configuration !== undefined) args.push('--configuration', options.configuration); - if (options.executable !== undefined) args.push('--executable', options.executable); - if (options.framework !== undefined) args.push('--framework', options.framework); - if (options.generateCert) args.push('--generate-cert'); - if (options.installCert) args.push('--install-cert'); - if (options.manifest !== undefined) args.push('--manifest', options.manifest); - if (options.name !== undefined) args.push('--name', options.name); - if (options.noBuild) args.push('--no-build'); - if (options.noRestore) args.push('--no-restore'); - if (options.noSign) args.push('--no-sign'); - if (options.output !== undefined) args.push('--output', options.output); - if (options.property) { - const propertyArr = Array.isArray(options.property) ? options.property : [options.property]; - for (const v of propertyArr) args.push('--property', v); - } - if (options.publisher !== undefined) args.push('--publisher', options.publisher); - if (options.selfContained) args.push('--self-contained'); - if (options.skipPri) args.push('--skip-pri'); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// restore -// --------------------------------------------------------------------------- - -export interface RestoreOptions extends CommonOptions { - /** Base/root directory for the winapp workspace */ - baseDirectory?: string; - /** Directory to read configuration from (default: base-directory) */ - configDir?: string; -} - -/** - * Use after cloning a repo or when .winapp/ folder is missing. Reinstalls SDK packages without changing versions, reading them from winapp.yaml or, for a .NET project initialized by 'init', from the .csproj via 'dotnet restore'. Requires a project already initialized by 'init'. To check for newer SDK versions, use 'update' instead. - */ -export async function restore(options: RestoreOptions = {}): Promise { - const args: string[] = ['restore']; - const positionals: string[] = []; - if (options.baseDirectory) positionals.push(options.baseDirectory); - if (options.configDir !== undefined) args.push('--config-dir', options.configDir); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// run -// --------------------------------------------------------------------------- - -export interface RunOptions extends CommonOptions { - /** Path to the app to run: a build-output folder, a .cs .NET file-based app, a .csproj project, a .sln/.slnx solution, or a directory containing one of those at its top level (default: current directory). */ - input?: string; - /** @deprecated Use `input` instead. Retained for backward compatibility. */ - inputFolder?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Project mode: run the project's configured .NET Native AOT publish. Requires effective PublishAot=true. */ - aot?: boolean; - /** Project mode: target architecture (x64, arm64, or x86). Sets the canonical Windows RID and selects a matching platform-dependent publish profile when required by the effective build. Ignored in folder mode. Honored for a .cs file-based app too; when omitted, winapp builds for the current process architecture. Default: the current process architecture. */ - arch?: string; - /** Command-line arguments to pass to the application. Alternatively, use -- followed by arguments to avoid escaping (e.g., winapp run . -- --flag value). */ - args?: string; - /** Remove the existing package's application data (LocalState, settings, etc.) before re-deploying. By default, application data is preserved across re-deployments. */ - clean?: boolean; - /** Project and single-file mode: build configuration (e.g., Debug, Release). Ignored in folder mode. Default: Debug. */ - configuration?: string; - /** 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. For WinUI apps, a crash also triggers a stowed-exception triage pass; the first run downloads debugger components (cached under the winapp global directory) and can be pointed at an existing debugger install via the WINAPP_DBGTOOLS_DIR environment variable. Cannot be combined with --no-launch or --json. */ - debugOutput?: boolean; - /** 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. Local runs print the PID; target runs print the scoped UI target. JSON includes the PID and target scope. */ - detach?: boolean; - /** 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. */ - executable?: string; - /** Project mode: target framework moniker for multi-targeted projects (e.g. net10.0-windows10.0.26100.0). Ignored in folder mode. Rejected for a .cs file-based app, which declares its own with '#:property TargetFramework=...'. */ - framework?: string; - /** Format output as JSON */ - json?: boolean; - /** Path to the Package.appxmanifest (default: auto-detect from input folder or current directory) */ - manifest?: string; - /** Project and single-file mode: skip building and run the existing build output (still evaluates output properties). Ignored in folder mode. */ - noBuild?: boolean; - /** Only create the debug identity and register the package without launching the application */ - noLaunch?: boolean; - /** Project and single-file mode: skip restoring before build or Native AOT publish. Ignored in folder mode. */ - noRestore?: boolean; - /** Output directory for the loose layout package. If not specified, a directory named AppX inside the input directory will be used. */ - outputAppxDirectory?: string; - /** Project mode: when the input is a solution (.sln/.slnx) or a directory with multiple runnable app projects, selects which project to launch (by name or path). Ignored in folder mode. Rejected for a .cs file-based app, which is itself the project. */ - project?: string; - /** Project and single-file mode: MSBuild property as Name=Value, forwarded to both build and evaluation. Repeatable. Ignored in folder mode. */ - property?: string | string[]; - /** Project mode: target .NET runtime identifier (RID), e.g. win-x64. Project mode uses only the RID's architecture, always builds the canonical win-, rejects non-Windows RIDs (e.g. linux-x64), and can select a required architecture-dependent publish profile; it overrides --arch. Ignored in folder mode. Honored for a .cs file-based app too. */ - runtime?: string; - /** Download symbols from Microsoft Symbol Server for richer native crash analysis, including the WinUI stowed-exception dispatch stack. Only used with --debug-output. First run downloads symbols and caches them locally; subsequent runs use the cache. */ - symbols?: boolean; - /** Unregister the development package after the application exits. Only removes packages registered in development mode. */ - unregisterOnExit?: boolean; - /** Launch the app using its execution alias instead of AUMID activation. The app runs in the current terminal with inherited stdin/stdout/stderr. Console apps (OutputType=Exe) already do this by default; pass this to force it for a windowed app. winapp adds a uap5:ExecutionAlias to the manifest it stages for you, so no manifest edit is needed. */ - withAlias?: boolean; - /** Launch via AUMID activation even for a console app, instead of the default execution alias. The app then runs without a console, so it prints nothing to this terminal. */ - withoutAlias?: boolean; - /** Arguments to pass to the launched application (forwarded after --). */ - appArgs?: string | string[]; -} - -/** - * Builds or Native AOT-publishes and runs a Windows app from a .cs file-based app, a .csproj/.sln, or a build-output folder. In project mode, invokes dotnet build — or the project's configured Native AOT publish with --aot — then launches the app (packaged or unpackaged); in single-file mode, builds the .cs and launches it, generating a manifest from its #:property directives when the app is packaged; in folder mode, creates a debug-signed layout, registers the package, and launches it. - */ -export async function run(options: RunOptions = {}): Promise { - const args: string[] = ['run']; - const inputValue = options.input ?? options.inputFolder; - if (inputValue) args.push(inputValue); - if (options.on !== undefined) args.push('--on', options.on); - if (options.aot) args.push('--aot'); - if (options.arch !== undefined) args.push('--arch', options.arch); - if (options.args !== undefined) args.push('--args', options.args); - if (options.clean) args.push('--clean'); - if (options.configuration !== undefined) args.push('--configuration', options.configuration); - if (options.debugOutput) args.push('--debug-output'); - if (options.detach) args.push('--detach'); - if (options.executable !== undefined) args.push('--executable', options.executable); - if (options.framework !== undefined) args.push('--framework', options.framework); - if (options.json) args.push('--json'); - if (options.manifest !== undefined) args.push('--manifest', options.manifest); - if (options.noBuild) args.push('--no-build'); - if (options.noLaunch) args.push('--no-launch'); - if (options.noRestore) args.push('--no-restore'); - if (options.outputAppxDirectory !== undefined) args.push('--output-appx-directory', options.outputAppxDirectory); - if (options.project !== undefined) args.push('--project', options.project); - if (options.property) { - const propertyArr = Array.isArray(options.property) ? options.property : [options.property]; - for (const v of propertyArr) args.push('--property', v); - } - if (options.runtime !== undefined) args.push('--runtime', options.runtime); - if (options.symbols) args.push('--symbols'); - if (options.unregisterOnExit) args.push('--unregister-on-exit'); - if (options.withAlias) args.push('--with-alias'); - if (options.withoutAlias) args.push('--without-alias'); - if (options.appArgs !== undefined) { - const appArgsArr = Array.isArray(options.appArgs) ? options.appArgs : [options.appArgs]; - if (appArgsArr.length > 0) { - args.push('--', ...appArgsArr); - } - } - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// sign -// --------------------------------------------------------------------------- - -export interface SignOptions extends CommonOptions { - /** Path to the file/package to sign */ - filePath: string; - /** Path to the certificate file (PFX format) */ - certPath: string; - /** Certificate password */ - password?: string; - /** Timestamp server URL */ - timestamp?: string; -} - -/** - * 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. - */ -export async function sign(options: SignOptions): Promise { - const args: string[] = ['sign']; - const positionals: string[] = []; - positionals.push(options.filePath); - positionals.push(options.certPath); - if (options.password !== undefined) args.push('--password', options.password); - if (options.timestamp !== undefined) args.push('--timestamp', options.timestamp); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// store -// --------------------------------------------------------------------------- - -export interface StoreOptions extends CommonOptions { - /** Arguments to pass through to the Microsoft Store Developer CLI. */ - storeArgs?: string | string[]; -} - -/** - * 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 - */ -export async function store(options: StoreOptions = {}): Promise { - const args: string[] = ['store']; - if (options.storeArgs !== undefined) { - const storeArgsArr = Array.isArray(options.storeArgs) ? options.storeArgs : [options.storeArgs]; - args.push(...storeArgsArr); - } - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// target exec -// --------------------------------------------------------------------------- - -export interface TargetExecOptions extends CommonOptions { - /** Execution target to act on. Currently: 'sandbox'. */ - target: string; - /** Working directory on the target. */ - targetCwd?: string; - /** Format output as JSON */ - json?: boolean; - /** Executable and arguments to run on the target, e.g. ['dotnet', '--info'] (forwarded after --). */ - command?: string | string[]; -} - -/** - * Run a command on an execution target, as that target's interactive user. Streams stdin, stdout, and stderr, and returns the command's own exit code. Does not provide a full terminal, so interactive console applications may see redirected pipes. - */ -export async function targetExec(options: TargetExecOptions): Promise { - const args: string[] = ['target', 'exec']; - args.push(options.target); - if (options.targetCwd !== undefined) args.push('--cwd', options.targetCwd); - if (options.json) args.push('--json'); - if (options.command !== undefined) { - const commandArr = Array.isArray(options.command) ? options.command : [options.command]; - if (commandArr.length > 0) { - args.push('--', ...commandArr); - } - } - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// target pull -// --------------------------------------------------------------------------- - -export interface TargetPullOptions extends CommonOptions { - /** Execution target to act on. Currently: 'sandbox'. */ - target: string; - /** File or directory on the target to copy, relative to its managed work area. */ - source: string; - /** Destination path on this machine. */ - destination: string; - /** Format output as JSON */ - json?: boolean; -} - -/** - * Copy files or directories from an execution target to this machine. Directory structure and useful timestamps are preserved, unchanged files are skipped, and changed files are replaced atomically. - */ -export async function targetPull(options: TargetPullOptions): Promise { - const args: string[] = ['target', 'pull']; - const positionals: string[] = []; - positionals.push(options.target); - positionals.push(options.source); - positionals.push(options.destination); - if (options.json) args.push('--json'); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// target push -// --------------------------------------------------------------------------- - -export interface TargetPushOptions extends CommonOptions { - /** Execution target to act on. Currently: 'sandbox'. */ - target: string; - /** File or directory on this machine to copy. */ - source: string; - /** Destination path on the target, relative to its managed work area. */ - destination: string; - /** Format output as JSON */ - json?: boolean; -} - -/** - * Copy files or directories from this machine to an execution target. Directory structure and useful timestamps are preserved, unchanged files are skipped, and changed files are replaced atomically. - */ -export async function targetPush(options: TargetPushOptions): Promise { - const args: string[] = ['target', 'push']; - const positionals: string[] = []; - positionals.push(options.target); - positionals.push(options.source); - positionals.push(options.destination); - if (options.json) args.push('--json'); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// target record -// --------------------------------------------------------------------------- - -export interface TargetRecordOptions extends CommonOptions { - /** Execution target to act on. Currently: 'sandbox'. */ - target: string; - /** Recording duration in seconds. 0 records until Ctrl+C or redirected-stdin newline/EOF. */ - durationSec?: number; - /** Frames per second to capture */ - fps?: number; - /** Write timestamped JPEGs, frames.ndjson, and manifest.json to .frames. Supports 1-30 fps and max-edge 64-4096 (default 1280), with a 1 GiB frame-data cap. */ - frames?: boolean; - /** Format output as JSON */ - json?: boolean; - /** Downscale so the longest edge is at most this many pixels (0 = no downscale) */ - maxEdge?: number; - /** Save output to this file path. */ - output?: string; - /** Replace an existing recording only after the new take finishes. Previous frame bundles are retained under a .previous- directory. */ - overwrite?: boolean; -} - -// _targetRecordGenerated: options interface exported above; function body omitted — use the -// public guarded wrapper (e.g. uiRecord from ui-record-guard.ts) instead. - -// --------------------------------------------------------------------------- -// target screenshot -// --------------------------------------------------------------------------- - -export interface TargetScreenshotOptions extends CommonOptions { - /** Execution target to act on. Currently: 'sandbox'. */ - target: string; - /** Format output as JSON */ - json?: boolean; - /** Save output to this file path. */ - output?: string; -} - -/** - * Capture an execution target's entire desktop at its native pixel size. Saves a PNG on this machine without activating a host or guest window. JSON includes the guest screen origin and pixel-coordinate mapping. - */ -export async function targetScreenshot(options: TargetScreenshotOptions): Promise { - const args: string[] = ['target', 'screenshot']; - const positionals: string[] = []; - positionals.push(options.target); - if (options.json) args.push('--json'); - if (options.output !== undefined) args.push('--output', options.output); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// target snapshot -// --------------------------------------------------------------------------- - -export interface TargetSnapshotOptions extends CommonOptions { - /** Execution target to act on. Currently: 'sandbox'. */ - target: string; - /** Format output as JSON */ - json?: boolean; -} - -/** - * Report an execution target's readiness, capabilities, deployments, and top-level guest windows. Inspects only: never starts, connects, or repairs a target, and reports plainly when none is running. Writes only to stdout: no screenshots and no files. - */ -export async function targetSnapshot(options: TargetSnapshotOptions): Promise { - const args: string[] = ['target', 'snapshot']; - const positionals: string[] = []; - positionals.push(options.target); - if (options.json) args.push('--json'); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// tool -// --------------------------------------------------------------------------- - -export interface ToolOptions extends CommonOptions { - /** Arguments to pass to the SDK tool, e.g. ['makeappx', 'pack', '/d', './folder', '/p', './out.msix']. */ - toolArgs?: string | string[]; -} - -/** - * 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 - */ -export async function tool(options: ToolOptions = {}): Promise { - const args: string[] = ['tool']; - if (options.toolArgs !== undefined) { - const toolArgsArr = Array.isArray(options.toolArgs) ? options.toolArgs : [options.toolArgs]; - if (toolArgsArr.length > 0) { - args.push('--', ...toolArgsArr); - } - } - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui click -// --------------------------------------------------------------------------- - -export interface UiClickOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Perform a double-click instead of a single click */ - double?: boolean; - /** Format output as JSON */ - json?: boolean; - /** Perform a right-click instead of a left click */ - right?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * 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. - */ -export async function uiClick(options: UiClickOptions = {}): Promise { - const args: string[] = ['ui', 'click']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.double) args.push('--double'); - if (options.json) args.push('--json'); - if (options.right) args.push('--right'); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui drag -// --------------------------------------------------------------------------- - -export interface UiDragOptions extends CommonOptions { - /** Start point — an element selector (drags from its center) or screen coordinates x,y as reported by 'ui inspect' (e.g. pn-list-d736 or 100,200). */ - from?: string; - /** End point — an element selector (drops at its center) or screen coordinates x,y as reported by 'ui inspect' (e.g. pn-target-d746 or 300,400). */ - to?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Milliseconds to dwell at the destination after moving, before releasing (default: 0). Lets drop targets / merge overlays that arm from a sustained hover latch before release. */ - dwellMs?: number; - /** Milliseconds to hold the button down at the start before moving (default: 0). With == (no movement) this performs a press-and-hold / long-press gesture. */ - holdMs?: number; - /** Format output as JSON */ - json?: boolean; - /** Drag with the right mouse button instead of the left button */ - right?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Press the mouse button at one point, move to another, then release. 'drag ', where / are each an element selector (uses the element's center) or screen x,y coordinates as reported by 'ui inspect'. Useful for reorder/resize/slider gestures and drag-and-drop. Use --right for a right-button drag, --hold-ms for press-and-hold/long-press, and --dwell-ms to settle on a drop target before releasing. - */ -export async function uiDrag(options: UiDragOptions = {}): Promise { - const args: string[] = ['ui', 'drag']; - const positionals: string[] = []; - if (options.from) positionals.push(options.from); - if (options.to) positionals.push(options.to); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.dwellMs !== undefined) args.push('--dwell-ms', options.dwellMs.toString()); - if (options.holdMs !== undefined) args.push('--hold-ms', options.holdMs.toString()); - if (options.json) args.push('--json'); - if (options.right) args.push('--right'); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui focus -// --------------------------------------------------------------------------- - -export interface UiFocusOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Format output as JSON */ - json?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Activate the specified element's window, focus the element, and verify foreground and keyboard focus. Fails if Windows refuses activation or focus cannot be confirmed. - */ -export async function uiFocus(options: UiFocusOptions): Promise { - const args: string[] = ['ui', 'focus']; - const positionals: string[] = []; - positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.json) args.push('--json'); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui get-focused -// --------------------------------------------------------------------------- - -export interface UiGetFocusedOptions extends CommonOptions { - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Format output as JSON */ - json?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Show the element that currently has keyboard focus in the target app. With -w, focus must belong to that exact top-level window; owned popups are excluded. - */ -export async function uiGetFocused(options: UiGetFocusedOptions = {}): Promise { - const args: string[] = ['ui', 'get-focused']; - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.json) args.push('--json'); - if (options.window !== undefined) args.push('--window', options.window.toString()); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui get-property -// --------------------------------------------------------------------------- - -export interface UiGetPropertyOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). */ - className?: string; - /** Format output as JSON */ - json?: boolean; - /** Property name to read or filter on */ - property?: string; - /** Search only descendants of this uniquely matching selector (excludes the root). */ - root?: string; - /** UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. */ - type?: string; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Read UIA property values from an element. Specify --property for a single property or omit for all. Includes whole-document TextPattern formatting: FontWeight, FontName, FontSize, ForegroundColor, IsItalic, StrikethroughStyle. - */ -export async function uiGetProperty(options: UiGetPropertyOptions = {}): Promise { - const args: string[] = ['ui', 'get-property']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.className !== undefined) args.push('--class-name', options.className); - if (options.json) args.push('--json'); - if (options.property !== undefined) args.push('--property', options.property); - if (options.root !== undefined) args.push('--root', options.root); - if (options.type !== undefined) args.push('--type', options.type); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui get-value -// --------------------------------------------------------------------------- - -export interface UiGetValueOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). */ - className?: string; - /** Format output as JSON */ - json?: boolean; - /** Search only descendants of this uniquely matching selector (excludes the root). */ - root?: string; - /** UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. */ - type?: string; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Read the current value from an element. Tries TextPattern (RichEditBox, Document), ValuePattern (TextBox, ComboBox, Slider), then Name (labels). Usage: winapp ui get-value -a - */ -export async function uiGetValue(options: UiGetValueOptions = {}): Promise { - const args: string[] = ['ui', 'get-value']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.className !== undefined) args.push('--class-name', options.className); - if (options.json) args.push('--json'); - if (options.root !== undefined) args.push('--root', options.root); - if (options.type !== undefined) args.push('--type', options.type); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui hover -// --------------------------------------------------------------------------- - -export interface UiHoverOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Time in milliseconds to wait after hovering for hover effects to appear (default: 800) */ - dwellTime?: number; - /** Format output as JSON */ - json?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Move the mouse to an element's center to trigger hover effects (tooltips, flyouts, visual states). Uses SendInput for realistic mouse movement and waits for a configurable dwell time. - */ -export async function uiHover(options: UiHoverOptions = {}): Promise { - const args: string[] = ['ui', 'hover']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.dwellTime !== undefined) args.push('--dwell-time', options.dwellTime.toString()); - if (options.json) args.push('--json'); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui inspect -// --------------------------------------------------------------------------- - -export interface UiInspectOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Walk up the tree from the specified element to the root */ - ancestors?: boolean; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Tree inspection depth */ - depth?: number; - /** Hide disabled elements from output */ - hideDisabled?: boolean; - /** Hide offscreen elements from output */ - hideOffscreen?: boolean; - /** Show only interactive/invokable elements (buttons, links, inputs, list items). Increases default depth to 8. */ - interactive?: boolean; - /** Format output as JSON */ - json?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * View the UI element tree with semantic slugs, element types, names, and bounds. - */ -export async function uiInspect(options: UiInspectOptions = {}): Promise { - const args: string[] = ['ui', 'inspect']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.ancestors) args.push('--ancestors'); - if (options.app !== undefined) args.push('--app', options.app); - if (options.depth !== undefined) args.push('--depth', options.depth.toString()); - if (options.hideDisabled) args.push('--hide-disabled'); - if (options.hideOffscreen) args.push('--hide-offscreen'); - if (options.interactive) args.push('--interactive'); - if (options.json) args.push('--json'); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui invoke -// --------------------------------------------------------------------------- - -export interface UiInvokeOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Perform exactly this action on the selected element, without pattern or ancestor fallback: invoke, select, toggle, toggle-on, toggle-off, expand, collapse. */ - action?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Format output as JSON */ - json?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Activate an element by slug or text search. Without --action, tries InvokePattern, TogglePattern, SelectionItemPattern, and ExpandCollapsePattern in order, then an invokable ancestor. Use --action for an exact operation on only the selected element. - */ -export async function uiInvoke(options: UiInvokeOptions = {}): Promise { - const args: string[] = ['ui', 'invoke']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.action !== undefined) args.push('--action', options.action); - if (options.app !== undefined) args.push('--app', options.app); - if (options.json) args.push('--json'); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui list-windows -// --------------------------------------------------------------------------- - -export interface UiListWindowsOptions extends CommonOptions { - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Format output as JSON */ - json?: boolean; - /** Include untitled zero-size windows that are hidden by default */ - showHidden?: boolean; -} - -/** - * 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. - */ -export async function uiListWindows(options: UiListWindowsOptions = {}): Promise { - const args: string[] = ['ui', 'list-windows']; - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.json) args.push('--json'); - if (options.showHidden) args.push('--show-hidden'); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui pen -// --------------------------------------------------------------------------- - -export interface UiPenOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Pen contact point as screen coordinates x,y (as reported by 'ui inspect'). Defaults to the selector's element center. Ignored when --path is given. */ - at?: string; - /** Total glide time in milliseconds distributed across the stroke path segments (default: ~10 ms per segment). */ - durationMs?: number; - /** Use the eraser end of the pen instead of the tip. */ - eraser?: boolean; - /** Format output as JSON */ - json?: boolean; - /** Ink stroke path as a whitespace-separated list of x,y pairs, e.g. "10,10 20,30 40,50". */ - path?: string; - /** Pen pressure from 0.0 to 1.0 (default: 0.5). */ - pressure?: number; - /** Pen tilt along the x-axis in degrees (-90 to 90, default: 0). */ - tiltX?: number; - /** Pen tilt along the y-axis in degrees (-90 to 90, default: 0). */ - tiltY?: number; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Inject synthetic pen/stylus input using the Windows synthetic-pointer API. Taps or draws ink strokes with configurable pressure, tilt and eraser mode, at an element's center or explicit screen x,y coordinates. Requires an unlocked, interactive desktop with the target window foregroundable (Windows 10 1809+). - */ -export async function uiPen(options: UiPenOptions = {}): Promise { - const args: string[] = ['ui', 'pen']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.at !== undefined) args.push('--at', options.at); - if (options.durationMs !== undefined) args.push('--duration-ms', options.durationMs.toString()); - if (options.eraser) args.push('--eraser'); - if (options.json) args.push('--json'); - if (options.path !== undefined) args.push('--path', options.path); - if (options.pressure !== undefined) args.push('--pressure', options.pressure.toString()); - if (options.tiltX !== undefined) args.push('--tilt-x', options.tiltX.toString()); - if (options.tiltY !== undefined) args.push('--tilt-y', options.tiltY.toString()); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui record -// --------------------------------------------------------------------------- - -export interface UiRecordOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Capture from screen DC via BitBlt (includes popups/overlays not owned by the target). */ - captureScreen?: boolean; - /** Recording duration in seconds. 0 records until Ctrl+C or redirected-stdin newline/EOF. */ - durationSec?: number; - /** Frames per second to capture */ - fps?: number; - /** Write timestamped JPEGs, frames.ndjson, and manifest.json to .frames. Supports 1-30 fps and max-edge 64-4096 (default 1280), with a 1 GiB frame-data cap. */ - frames?: boolean; - /** Format output as JSON */ - json?: boolean; - /** Downscale so the longest edge is at most this many pixels (0 = no downscale) */ - maxEdge?: number; - /** Save output to this file path. */ - output?: string; - /** Replace an existing recording only after the new take finishes. Previous frame bundles are retained under a .previous- directory. */ - overwrite?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -// _uiRecordGenerated: options interface exported above; function body omitted — use the -// public guarded wrapper (e.g. uiRecord from ui-record-guard.ts) instead. - -// --------------------------------------------------------------------------- -// ui screenshot -// --------------------------------------------------------------------------- - -export interface UiScreenshotOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Capture from screen DC via BitBlt (includes popups/overlays not owned by the target). */ - captureScreen?: boolean; - /** Bring the target window to the foreground before capture. Already implied by --capture-screen. */ - focus?: boolean; - /** Format output as JSON */ - json?: boolean; - /** Save output to this file path. */ - output?: string; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Capture the target window or element as a PNG image. Without an element selector, combines multiple windows into one labeled composite: --app by process name or PID includes the app's windows and their owned windows; a title match or --window selects one window plus its owned windows. With --json, returns file path and dimensions. Use --capture-screen with --window to capture one screen region, including visible overlays in place. - */ -export async function uiScreenshot(options: UiScreenshotOptions = {}): Promise { - const args: string[] = ['ui', 'screenshot']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.captureScreen) args.push('--capture-screen'); - if (options.focus) args.push('--focus'); - if (options.json) args.push('--json'); - if (options.output !== undefined) args.push('--output', options.output); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui scroll -// --------------------------------------------------------------------------- - -export interface UiScrollOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Scroll direction: up, down, left, right */ - direction?: string; - /** Format output as JSON */ - json?: boolean; - /** Scroll to position: top, bottom */ - to?: string; - /** Rotate the mouse wheel over the element by this many notches (1 = one notch up, -1 = one notch down). Synthesizes real wheel input instead of using ScrollPattern. */ - wheel?: number; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Scroll a container element using ScrollPattern. Use --direction to scroll incrementally, --to to jump to top/bottom, or --wheel to synthesize mouse-wheel input. - */ -export async function uiScroll(options: UiScrollOptions = {}): Promise { - const args: string[] = ['ui', 'scroll']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.direction !== undefined) args.push('--direction', options.direction); - if (options.json) args.push('--json'); - if (options.to !== undefined) args.push('--to', options.to); - if (options.wheel !== undefined) args.push('--wheel', options.wheel.toString()); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui scroll-into-view -// --------------------------------------------------------------------------- - -export interface UiScrollIntoViewOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Format output as JSON */ - json?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Scroll the specified element into the visible area using UIA ScrollItemPattern. - */ -export async function uiScrollIntoView(options: UiScrollIntoViewOptions = {}): Promise { - const args: string[] = ['ui', 'scroll-into-view']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.json) args.push('--json'); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui search -// --------------------------------------------------------------------------- - -export interface UiSearchOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). */ - className?: string; - /** Format output as JSON */ - json?: boolean; - /** Maximum search results */ - max?: number; - /** Search only descendants of this uniquely matching selector (excludes the root). */ - root?: string; - /** UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. */ - type?: string; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Search the element tree for elements matching a text query. Returns all matches with semantic slugs. - */ -export async function uiSearch(options: UiSearchOptions = {}): Promise { - const args: string[] = ['ui', 'search']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.className !== undefined) args.push('--class-name', options.className); - if (options.json) args.push('--json'); - if (options.max !== undefined) args.push('--max', options.max.toString()); - if (options.root !== undefined) args.push('--root', options.root); - if (options.type !== undefined) args.push('--type', options.type); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui send-keys -// --------------------------------------------------------------------------- - -export interface UiSendKeysOptions extends CommonOptions { - /** Keys to send. Whitespace-separated tokens: named keys (down, enter, tab, esc, f5), modifier combos (ctrl+shift+t, alt+f4), raw virtual keys (vk=0x42), or literal text (hello). Hold capslock or insert for screen-reader commands (ctrl+capslock+f12 toggles Narrator developer mode); these require --via send-input. Use text= to type a single value verbatim when it would otherwise be read as a key name or combo (text=enter types "enter"; text=ctrl+a types "ctrl+a"); backslash escapes \s \t \n \r \\ are supported (text=a\s\sb types "a b"). To type the whole argument literally without escaping each token, pass --verbatim instead. Quote multi-token strings, e.g. "ctrl+a delete". */ - keys?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Allow synthesizing system-/shell-reserved combos (win+, alt+f4, alt+tab, ctrl+esc, …) via --via send-input, which are refused by default because they act on the OS/shell beyond the target app. Opt in to drive global hotkeys (e.g. PowerToys' win+shift+v, win+r). No effect on --via post-message (already window-scoped; a warning is emitted if set without send-input). Note: win+l and ctrl+alt+del stay blocked even with this flag — win+l locks the workstation (LockWorkStation() via the shell hook), which is unrecoverable from automation, and ctrl+alt+del is a Secure Attention Sequence (SAS) that Windows drops from injected input regardless of this flag, so it can never take effect. */ - allowSystemKeys?: boolean; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Format output as JSON */ - json?: boolean; - /** Optional selector (slug or text) to focus before sending keys. */ - target?: string; - /** Type the entire keys argument as literal text — no named-key, combo, or vk= interpretation, and exact whitespace preserved. The whole-argument form of the per-token text= escape: --verbatim "down down enter" types the words instead of pressing Down, Down, Enter. */ - verbatim?: boolean; - /** Transport: post-message (default, HWND-targeted, bypasses UIPI; typed text raises TextChanged but not a per-character KeyDown) or send-input (OS-wide; typed text raises a real per-character KeyDown + TextChanged). Named keys and combos raise KeyDown on both, but keyboard accelerators/shortcuts (KeyboardAccelerator, e.g. ctrl+t) only fire via send-input. post-message targets the focused child control and works for classic Win32/WinForms controls, but WinUI 3 / UWP / XAML controls are windowless and ignore posted messages — use send-input for those (a warning is emitted when the target looks like a XAML app). */ - via?: string; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Send synthetic keyboard input to a window. Supports named keys (down, enter, tab), modifier combos (ctrl+shift+t), raw virtual keys (vk=0xNN), and literal text. Use --verbatim to type the whole argument literally, or --target to focus an element first. Two transports via --via: post-message (default, HWND-targeted, bypasses UIPI) or send-input (OS-wide). For per-keystroke KeyDown on typed text (e.g. a WinUI 3/WPF TextBox), use --via send-input. - */ -export async function uiSendKeys(options: UiSendKeysOptions = {}): Promise { - const args: string[] = ['ui', 'send-keys']; - const positionals: string[] = []; - if (options.keys) positionals.push(options.keys); - if (options.on !== undefined) args.push('--on', options.on); - if (options.allowSystemKeys) args.push('--allow-system-keys'); - if (options.app !== undefined) args.push('--app', options.app); - if (options.json) args.push('--json'); - if (options.target !== undefined) args.push('--target', options.target); - if (options.verbatim) args.push('--verbatim'); - if (options.via !== undefined) args.push('--via', options.via); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui set-value -// --------------------------------------------------------------------------- - -export interface UiSetValueOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Value to set (text for TextBox/ComboBox, number for Slider) */ - value?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Format output as JSON */ - json?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Set a value on an element programmatically. Works for TextBox, ComboBox, Slider, and other editable controls via UIA ValuePattern/RangeValuePattern, with a LegacyIAccessible (put_accValue) fallback for TextPattern-only edit controls — no app foreground required. Some rich text controls (e.g. WinUI 3 RichEditBox and WPF RichTextBox) don't support setting their value programmatically — use the 'send-keys' command with '--via send-input' to type into them instead. Usage: winapp ui set-value -a - */ -export async function uiSetValue(options: UiSetValueOptions = {}): Promise { - const args: string[] = ['ui', 'set-value']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.value) positionals.push(options.value); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.json) args.push('--json'); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui status -// --------------------------------------------------------------------------- - -export interface UiStatusOptions extends CommonOptions { - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Format output as JSON */ - json?: boolean; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Connect to a target app and display connection info. - */ -export async function uiStatus(options: UiStatusOptions = {}): Promise { - const args: string[] = ['ui', 'status']; - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.json) args.push('--json'); - if (options.window !== undefined) args.push('--window', options.window.toString()); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui touch -// --------------------------------------------------------------------------- - -export interface UiTouchOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Explicit start point as screen coordinates x,y (as reported by 'ui inspect'). Defaults to the selector's element center. */ - at?: string; - /** Swipe direction: right (default), left, up, or down. Combined with --distance to compute the end point when --to-point is not given. */ - direction?: string; - /** Distance in pixels for pinch/stretch (finger spread) or swipe. */ - distance?: number; - /** Glide time in milliseconds for moving gestures (swipe/pinch/stretch). */ - durationMs?: number; - /** Number of touch contacts (default: 1). Pinch/stretch always use 2. */ - fingers?: number; - /** Gesture to perform: tap, double-tap, long-press, swipe, pinch, stretch (default: tap). */ - gesture?: string; - /** Milliseconds to hold contacts down before lifting (long-press hold time). Defaults to 500 ms when --gesture long-press is used and this option is not set. */ - holdMs?: number; - /** Format output as JSON */ - json?: boolean; - /** End point x,y for a swipe (screen coordinates). Takes precedence over --direction. */ - toPoint?: string; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Inject synthetic touch input using the Windows touch-injection API. Supports tap, double-tap, long-press, swipe, pinch and stretch gestures at an element's center or explicit screen x,y coordinates. Requires an unlocked, interactive desktop with the target window foregroundable. - */ -export async function uiTouch(options: UiTouchOptions = {}): Promise { - const args: string[] = ['ui', 'touch']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.at !== undefined) args.push('--at', options.at); - if (options.direction !== undefined) args.push('--direction', options.direction); - if (options.distance !== undefined) args.push('--distance', options.distance.toString()); - if (options.durationMs !== undefined) args.push('--duration-ms', options.durationMs.toString()); - if (options.fingers !== undefined) args.push('--fingers', options.fingers.toString()); - if (options.gesture !== undefined) args.push('--gesture', options.gesture); - if (options.holdMs !== undefined) args.push('--hold-ms', options.holdMs.toString()); - if (options.json) args.push('--json'); - if (options.toPoint !== undefined) args.push('--to-point', options.toPoint); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui wait-for -// --------------------------------------------------------------------------- - -export interface UiWaitForOptions extends CommonOptions { - /** Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId */ - selector?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target app (process name, window title, or PID). Lists windows if ambiguous. */ - app?: string; - /** Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). */ - className?: string; - /** Use substring matching for --value instead of exact match */ - contains?: boolean; - /** Wait for element to disappear instead of appear */ - gone?: boolean; - /** Format output as JSON */ - json?: boolean; - /** Property name to read or filter on */ - property?: string; - /** Search only descendants of this uniquely matching selector (excludes the root). */ - root?: string; - /** Timeout in milliseconds */ - timeout?: number; - /** UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. */ - type?: string; - /** Wait for element value to equal this string. Uses smart fallback (TextPattern -> ValuePattern -> Name). Combine with --property to check a specific property instead. */ - value?: string; - /** Target window by HWND (stable handle from list output). Takes precedence over --app. */ - window?: number; -} - -/** - * Wait for an element to appear, disappear, or have a property reach a target value. Polls at 100ms intervals until condition met or timeout. - */ -export async function uiWaitFor(options: UiWaitForOptions = {}): Promise { - const args: string[] = ['ui', 'wait-for']; - const positionals: string[] = []; - if (options.selector) positionals.push(options.selector); - if (options.on !== undefined) args.push('--on', options.on); - if (options.app !== undefined) args.push('--app', options.app); - if (options.className !== undefined) args.push('--class-name', options.className); - if (options.contains) args.push('--contains'); - if (options.gone) args.push('--gone'); - if (options.json) args.push('--json'); - if (options.property !== undefined) args.push('--property', options.property); - if (options.root !== undefined) args.push('--root', options.root); - if (options.timeout !== undefined) args.push('--timeout', options.timeout.toString()); - if (options.type !== undefined) args.push('--type', options.type); - if (options.value !== undefined) args.push('--value', options.value); - if (options.window !== undefined) args.push('--window', options.window.toString()); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// ui yield -// --------------------------------------------------------------------------- - -export interface UiYieldOptions extends CommonOptions { - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Format output as JSON */ - json?: boolean; -} - -/** - * Release the current workflow's idle UI turn early. A workflow with WINAPP_UI_WORKFLOW_ID keeps the desktop for a few seconds after each command so a burst of commands reads as one workflow; run this after the final command of a workflow to hand the desktop to waiting workflows straight away. Requires WINAPP_UI_WORKFLOW_ID; targets no app and takes no selector. - */ -export async function uiYield(options: UiYieldOptions = {}): Promise { - const args: string[] = ['ui', 'yield']; - if (options.on !== undefined) args.push('--on', options.on); - if (options.json) args.push('--json'); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// unregister -// --------------------------------------------------------------------------- - -export interface UnregisterOptions extends CommonOptions { - /** Path to a .NET file-based app (a single .cs) whose package should be unregistered. Its identity is resolved the same way 'winapp run' resolves it, so no manifest path is needed. Omit to use --manifest or auto-detect a manifest in the current directory. Cannot be combined with --manifest. */ - input?: string; - /** Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. */ - on?: string; - /** Target architecture (x64, arm64, x86) used when resolving a .cs file-based app's identity (default: the current process architecture). Pass the same architecture the run used, since a Directory.Build.props can key identity off $(RuntimeIdentifier). Only applies to a .cs input. */ - arch?: string; - /** Build configuration used when resolving a .cs file-based app's identity (default: Debug). Pass the same configuration the run used: a Directory.Build.props beside the .cs can set WinAppPackageName or WinAppManifestPath conditionally on $(Configuration). Only applies to a .cs input. */ - configuration?: string; - /** Skip the install-location directory check and unregister even if the package was registered from a different project tree. Candidates are matched by Identity/@Name alone, so with --force a same-named package from a different publisher is also removed, along with its application data — prefer --prune for registrations whose files are gone. With --prune, also skips the confirmation prompt. */ - force?: boolean; - /** Format output as JSON */ - json?: boolean; - /** Path to the Package.appxmanifest (default: auto-detect from current directory) */ - manifest?: string; - /** The AppX layout directory the package was registered from. Only needed when the run used --output-appx-directory, since nothing on the package records which run option produced its layout; without it the registration looks like it came from a different tree and is skipped. */ - outputAppxDirectory?: string; - /** MSBuild property (Name=Value) used when resolving a .cs file-based app's identity. Repeatable. Pass the same identity-affecting properties the run used (e.g. -p WinAppPackageName=...), since a command-line property overrides the file's own #:property directives. Only applies to a .cs input. */ - property?: string | string[]; - /** Remove every development-mode registration whose files are gone. These can never launch — Windows keeps the identity and its Start menu entry, but activation silently does nothing. Lists what it found and asks before removing; pass --force to skip the prompt. Cannot be combined with an input or --manifest. */ - prune?: boolean; - /** Target .NET runtime identifier (e.g. win-x64) used when resolving a .cs file-based app's identity. Only its architecture is used, and it overrides --arch. Only applies to a .cs input. */ - runtime?: string; -} - -/** - * Unregisters a sideloaded development package. Only removes packages registered in development mode (e.g., via 'winapp run' or 'create-debug-identity'). - */ -export async function unregister(options: UnregisterOptions = {}): Promise { - const args: string[] = ['unregister']; - const positionals: string[] = []; - if (options.input) positionals.push(options.input); - if (options.on !== undefined) args.push('--on', options.on); - if (options.arch !== undefined) args.push('--arch', options.arch); - if (options.configuration !== undefined) args.push('--configuration', options.configuration); - if (options.force) args.push('--force'); - if (options.json) args.push('--json'); - if (options.manifest !== undefined) args.push('--manifest', options.manifest); - if (options.outputAppxDirectory !== undefined) args.push('--output-appx-directory', options.outputAppxDirectory); - if (options.property) { - const propertyArr = Array.isArray(options.property) ? options.property : [options.property]; - for (const v of propertyArr) args.push('--property', v); - } - if (options.prune) args.push('--prune'); - if (options.runtime !== undefined) args.push('--runtime', options.runtime); - if (positionals.length > 0) args.push('--', ...positionals); - return execCommand(args, options); -} - -// --------------------------------------------------------------------------- -// update -// --------------------------------------------------------------------------- - -export interface UpdateOptions extends CommonOptions { - /** SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation) */ - setupSdks?: SdkInstallMode; -} - -/** - * 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. - */ -export async function update(options: UpdateOptions = {}): Promise { - const args: string[] = ['update']; - if (options.setupSdks !== undefined) args.push('--setup-sdks', options.setupSdks); - return execCommand(args, options); -} From edcaaca8468b1d3027223e79b6b61240a33139ea Mon Sep 17 00:00:00 2001 From: Nikola Metulev <711864+nmetulev@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:40:11 -0700 Subject: [PATCH 2/2] Remove tracked generated documentation dependencies Use live CLI schemas for npm generation and validation, and maintain the npm API guide by hand. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../pr-review/dimensions/ship-surfaces.md | 10 +- .github/skills/spec-review/SKILL.md | 6 +- .../dimensions/dx-and-user-impact.md | 2 +- .../dimensions/feasibility-vs-reality.md | 2 +- .../spec-review/dimensions/multi-model.md | 2 +- .../dimensions/necessity-and-scope.md | 4 +- .github/workflows/docs-check.yml | 4 +- .github/workflows/plugin-check.yml | 5 +- .gitignore | 3 + AGENTS.md | 39 +- README.md | 2 +- docs/cli-schema.json | 6734 ----------------- docs/fragments/node-commands.md | 83 - docs/npm-usage.md | 2684 +------ llms.txt | 2 +- scripts/build-cli.ps1 | 20 +- scripts/generate-llm-docs.ps1 | 35 +- scripts/start-release.ps1 | 2 +- scripts/tests/build-cli.Tests.ps1 | 8 +- scripts/tests/live-schema.Tests.ps1 | 123 + scripts/tests/npm-codegen.Tests.ps1 | 66 +- scripts/validate-llm-docs.ps1 | 71 +- scripts/validate-plugin-package.ps1 | 25 +- src/winapp-npm/README.md | 58 +- src/winapp-npm/package.json | 6 +- src/winapp-npm/scripts/generate-commands.mjs | 23 +- src/winapp-npm/scripts/generate-docs.mjs | 440 -- .../scripts/markdown-table-cell.mjs | 39 - .../test/markdown-table-cell.test.ts | 105 - src/winapp-npm/test/npm-usage-doc.test.ts | 112 +- 30 files changed, 494 insertions(+), 10221 deletions(-) delete mode 100644 docs/cli-schema.json delete mode 100644 docs/fragments/node-commands.md create mode 100644 scripts/tests/live-schema.Tests.ps1 delete mode 100644 src/winapp-npm/scripts/generate-docs.mjs delete mode 100644 src/winapp-npm/scripts/markdown-table-cell.mjs delete mode 100644 src/winapp-npm/test/markdown-table-cell.test.ts diff --git a/.github/skills/pr-review/dimensions/ship-surfaces.md b/.github/skills/pr-review/dimensions/ship-surfaces.md index 5ab8b225e..08e285285 100644 --- a/.github/skills/pr-review/dimensions/ship-surfaces.md +++ b/.github/skills/pr-review/dimensions/ship-surfaces.md @@ -19,7 +19,9 @@ internal and user-invisible, none of it applies — say so and return clean. ## Generated and hand-authored surfaces -- `docs/cli-schema.json` is regenerated by `scripts/build-cli.ps1`. +- The CLI schema is generated into ignored `artifacts/docs/cli-schema.json` by + `scripts/build-cli.ps1`. There is no tracked snapshot to update. +- `docs/npm-usage.md` is a hand-authored task guide; npm tests type-check its examples. - `plugins/winapp/skills/winapp-*/SKILL.md` are hand-authored shipped files. Update them directly when a workflow or command changes. - `src/winapp-npm/src/winapp-commands.ts` regenerates via @@ -30,14 +32,14 @@ internal and user-invisible, none of it applies — say so and return clean. that adds a top-level field outside `$schema`, `name`, `version`, `description`, `author`, `homepage`, `repository`, `license`, `keywords`, `extensions`. -If `Commands/` changed and the generated schema or npm command types did not, -flag the mismatch. Review hand-authored skills only when the command changes a +If `Commands/` changed, verify that live schema extraction and npm generation +cover the change; do not require generated files in the diff. Review hand-authored skills only when the command changes a documented workflow, example, or troubleshooting path. Do not run the scripts yourself. ## Match the change to affected surfaces -- CLI syntax or help changes regenerate `docs/cli-schema.json` through the build; +- CLI syntax or help changes appear in the live schema and generated npm wrappers; update `docs/usage.md` when it is the canonical hand-authored reference. - The relevant shipped skill in `plugins/winapp/skills/winapp-/SKILL.md` when its workflow, examples, or troubleshooting changed. diff --git a/.github/skills/spec-review/SKILL.md b/.github/skills/spec-review/SKILL.md index 503249f72..8b7a5d39a 100644 --- a/.github/skills/spec-review/SKILL.md +++ b/.github/skills/spec-review/SKILL.md @@ -79,13 +79,13 @@ report header — one line, not an analysis). ### 2. Map the impacted codebase areas The sub-agents need to know **where in the real repo to research.** Skim the -spec, then use `grep` / `glob` / `view` (and `docs/cli-schema.json`) to locate +spec, then use `grep` / `glob` / `view` (and `winapp --cli-schema` when built) to locate the actual files, commands, services, tools, and docs the proposal would touch. Build a short **area map** to include in every sub-agent prompt. Common buckets: | Area | Where to look | |------|---------------| -| CLI commands / options | `src/winapp-CLI/WinApp.Cli/Commands/`, `docs/cli-schema.json` | +| CLI commands / options | `src/winapp-CLI/WinApp.Cli/Commands/`, `winapp --cli-schema` when built | | Services & helpers | `src/winapp-CLI/WinApp.Cli/Services/`, `*Helper.cs`, `AppxManifestDocument` | | Packaging / MSIX / signing | `MsixService`, cert/signing services, `makeappx`/`signtool` usage | | Manifest handling | `AppxManifestDocument`, `ManifestHelper` | @@ -194,7 +194,7 @@ experiments stay in temp directories. State each conclusion once. # Spec Review — ## Decision - — + — diff --git a/.github/skills/spec-review/dimensions/dx-and-user-impact.md b/.github/skills/spec-review/dimensions/dx-and-user-impact.md index 4910d1ef0..b0a7c1f43 100644 --- a/.github/skills/spec-review/dimensions/dx-and-user-impact.md +++ b/.github/skills/spec-review/dimensions/dx-and-user-impact.md @@ -7,7 +7,7 @@ understandable to users?** Apply the shared output contract in `_shared-contract.md`. Set `Domain: dx-and-user-impact` on every finding. Verify conventions against the real CLI, not your assumptions — skim -`docs/cli-schema.json` and `src/winapp-CLI/WinApp.Cli/Commands/` to see how +`winapp --cli-schema` when built and `src/winapp-CLI/WinApp.Cli/Commands/` to see how existing commands and options actually look before judging the proposal. ## Conventions to check the proposal against diff --git a/.github/skills/spec-review/dimensions/feasibility-vs-reality.md b/.github/skills/spec-review/dimensions/feasibility-vs-reality.md index e2a5f2b03..d53b0d324 100644 --- a/.github/skills/spec-review/dimensions/feasibility-vs-reality.md +++ b/.github/skills/spec-review/dimensions/feasibility-vs-reality.md @@ -39,7 +39,7 @@ confidently, and do not stop at "the code looks like it does X"; where you can - API existence/shape/requirements → prefer authoritative vendor docs; where feasible, a tiny throwaway call. Keep experiments cheap and confined to temp dirs; never touch the repo tree. - Reading the repo's own code (`Commands/`, `Services/`, `docs/cli-schema.json`, + Reading the repo's own code (`Commands/`, `Services/`, `AppxManifestDocument`, `scripts/build-cli.ps1`) is still valuable for *repo-internal* behavior — but it is not a substitute for an experiment on an external tool/API/build mechanic. diff --git a/.github/skills/spec-review/dimensions/multi-model.md b/.github/skills/spec-review/dimensions/multi-model.md index 0585d4c1b..899569531 100644 --- a/.github/skills/spec-review/dimensions/multi-model.md +++ b/.github/skills/spec-review/dimensions/multi-model.md @@ -26,7 +26,7 @@ This is not a rubber stamp. Do your **own** research against reality before you look at anyone's conclusions. 1. **Independently research the spec** the way the specialists were asked to: - read the real code (`Commands/`, `Services/`, `docs/cli-schema.json`) **and, + read the real code (`Commands/`, `Services/`) and inspect `winapp --cli-schema` when built **and, for anything mechanical, run your own cheap experiment** — invoke the real tool, build a throwaway project in a temp dir, test the real command behavior — rather than only re-reasoning over the specialists' text. Form your own view diff --git a/.github/skills/spec-review/dimensions/necessity-and-scope.md b/.github/skills/spec-review/dimensions/necessity-and-scope.md index 789bea646..f1b8c86dd 100644 --- a/.github/skills/spec-review/dimensions/necessity-and-scope.md +++ b/.github/skills/spec-review/dimensions/necessity-and-scope.md @@ -32,7 +32,7 @@ it is individually well-designed. recurring manual workaround, a documented user pain, linked issues — or is it "someone might want this someday" generality? Prefer concrete need. - **Duplication.** Does the CLI (or the npm/NuGet/VSC surfaces) already do this, - fully or partially? Independently check: read `docs/cli-schema.json` and skim + fully or partially? Independently check: run `winapp --cli-schema` when built and skim `src/winapp-CLI/WinApp.Cli/Commands/` for an existing command that overlaps. - **Smaller / staged.** Is there a minimal version that delivers most of the value now, with the rest deferred until the need is proven? Name the leanest @@ -45,7 +45,7 @@ it is individually well-designed. Do not take the spec's framing of "why we need this" at face value. Verify: -- Grep `Commands/` and `docs/cli-schema.json` for existing overlapping +- Grep `Commands/` and inspect `winapp --cli-schema` when built for existing overlapping functionality. - Check whether an existing Windows SDK tool, Windows App SDK API, or standard OS mechanism already covers the need (so winapp would just be a thin, diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml index 74dc4036c..158c17426 100644 --- a/.github/workflows/docs-check.yml +++ b/.github/workflows/docs-check.yml @@ -56,9 +56,9 @@ jobs: return; } - // Check if any docs were updated (excluding cli-schema.json which is auto-generated) + // Check if any user-facing docs were updated. const docsChanges = changedPaths.filter(p => - (p.startsWith('docs/') && p !== 'docs/cli-schema.json') || + p.startsWith('docs/') || p === 'README.md' ); diff --git a/.github/workflows/plugin-check.yml b/.github/workflows/plugin-check.yml index 458083989..d3d7d35c8 100644 --- a/.github/workflows/plugin-check.yml +++ b/.github/workflows/plugin-check.yml @@ -1,6 +1,7 @@ name: Plugin Check -# Fast, build-free check of plugin manifests and skills. It always runs on PRs, so it is +# Fast, build-free structural check of plugin manifests and skills. Command examples +# are checked against the built CLI in the post-build validate-docs job. This always runs on PRs, so it is # safe to mark as a required check, and skips quickly when no relevant file changed. # The post-build validate-docs job in build-package.yml still runs the same script. on: @@ -30,7 +31,7 @@ jobs: - name: Validate plugin packages and skills shell: pwsh run: | - $relevant = '^(plugins/|scripts/validate-plugin-package\.ps1$|docs/cli-schema\.json$|src/winapp-npm/src/cli\.ts$|\.github/workflows/plugin-check\.yml$)' + $relevant = '^(plugins/|scripts/validate-plugin-package\.ps1$|\.github/workflows/plugin-check\.yml$)' $changed = @(git diff --name-only HEAD^1 HEAD) if ($LASTEXITCODE -ne 0) { throw "Could not list the files this PR changes." } $matched = @($changed | Where-Object { $_ -match $relevant }) diff --git a/.gitignore b/.gitignore index 438324119..eef8b3fb9 100644 --- a/.gitignore +++ b/.gitignore @@ -207,6 +207,9 @@ CMakeCache.txt /artifacts +# Retired generated documentation snapshot; schemas now live under artifacts. +/docs/cli-schema.json + # Development certificate devcert.pfx *.msix diff --git a/AGENTS.md b/AGENTS.md index 11bd7c8e3..222e44f69 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -90,7 +90,7 @@ cd src/winapp-npm && npm run build-copy-only # copies already-built Release b cd src/winapp-npm && npm install node cli.js help -# Always call the build script at the end to ensure everything builds and all autogenerated docs are generated +# Always call the build script at the end to ensure everything builds .\scripts\build-cli.ps1 # Produce packages first, then validate them without republishing or deleting artifacts. @@ -101,12 +101,13 @@ node cli.js help `src\winapp-npm\src\winapp-commands.ts` is ignored generated output. Change the CLI commands or `src\winapp-npm\scripts\generate-commands.mjs`, not that file. -Standalone npm compile, watch, test, and documentation commands regenerate it -automatically, using an available CLI binary or the tracked `docs\cli-schema.json` -on a fresh checkout. The repository build extracts its current CLI schema -explicitly and skips these npm pre-hooks to preserve that schema. Keep the -generated schema and npm API documentation tracked; do not add the generated -TypeScript wrappers to a commit. +Standalone npm compile, watch, and test commands regenerate it from an available +CLI binary. With no binary, they build and run the Debug CLI using the .NET SDK, +so fresh-checkout npm development requires Windows and .NET as well as Node. +The repository build extracts its current CLI schema explicitly and skips these +npm pre-hooks to preserve that schema. Schemas under `artifacts\` are also ignored; +never commit generated wrappers or schemas. `docs\npm-usage.md` is a hand-written +guide, and its TypeScript examples are checked against the public API by npm tests. ### Running tests on a Microsoft corporate machine @@ -297,7 +298,7 @@ token; the privileged comment workflow reads metrics as data, never executes it. | Services | `src/winapp-CLI/WinApp.Cli/Services/*.cs` | | Node CLI | `src/winapp-npm/cli.js`, `winapp-cli-utils.js` | | Config example | `winapp.example.yaml` | -| CLI schema | `docs/cli-schema.json` | +| CLI schema | `winapp --cli-schema`; command definitions in `src/winapp-CLI/WinApp.Cli/Commands/` | | Shipped agent skills | `plugins/winapp/skills/` | | Plugin (Copilot + Claude) | `plugins/winapp/` | | Copilot-specific plugin components | `plugins/winapp/com.github.copilot/` | @@ -325,7 +326,8 @@ same way, and write the condition as a positive test for `rel/v*` so it fails cl ## CLI command semantics -Look at the `docs\cli-schema.json` for the full schema to know what the cli can do +Run `winapp --cli-schema` for the complete command tree. If the CLI is not built, +read the command definitions under `src\winapp-CLI\WinApp.Cli\Commands\`. ## Quick change checklist @@ -343,8 +345,10 @@ Look at the `docs\cli-schema.json` for the full schema to know what the cli can ## Documentation and skill maintenance -`docs/cli-schema.json` is generated from the CLI by `scripts/generate-llm-docs.ps1`. -Do not edit it directly; run `scripts/build-cli.ps1` to regenerate it. +`scripts/generate-llm-docs.ps1` writes the built CLI's schema to +`artifacts\docs\cli-schema.json` and synchronizes plugin versions by default. +There is no tracked schema snapshot. `docs\npm-usage.md` is maintained by hand; +use the installed npm package's declarations for the exhaustive API surface. The files under `plugins/winapp/skills/` are the hand-authored, shipped plugin skills shared by GitHub Copilot and Claude Code. Edit these files directly. @@ -390,20 +394,23 @@ root under `plugins/` (any folder holding `plugin.json` and a `skills/` folder, files inside the same plugin — use a full `https://` URL for repo docs, and name the owning skill for cross-skill paths (`` `winui-packaging`'s `references/x.md` ``); `winapp …` lines in fenced code blocks of skills and agents (including host wrappers in - outer `agents/` folders) use command paths from `docs/cli-schema.json` or the npm - wrapper's `node` subcommands. + outer `agents/` folders) use command paths from a current CLI schema when + `-CliSchemaPath` is supplied, plus the npm wrapper's `node` subcommands. - **Warnings:** descriptions over 300 characters (`$DescriptionWarnChars`). - **Report:** approximate token sizes per skill and agent, also written to the GitHub Actions job summary. -It needs no build output, so run it directly while editing plugin files: +Structural checks need no build output, so run them directly while editing plugin files: ```powershell .\scripts\validate-plugin-package.ps1 ``` -`validate-llm-docs.ps1` also invokes it, so CI fails on any conformance regression. The -`Plugin Check` workflow (`.github/workflows/plugin-check.yml`) also runs it on every PR +`validate-llm-docs.ps1` extracts a fresh schema from the built CLI and passes it to +the plugin validator, so the post-build CI job also checks command examples. Run +`.\scripts\validate-llm-docs.ps1` locally after building, or pass +`-CliSchemaPath .\artifacts\docs\cli-schema.json` to the plugin validator. +`Plugin Check` (`.github/workflows/plugin-check.yml`) runs structural checks on PRs without waiting for a CLI build, skipping quickly when no plugin-related file changed. ## C# service architecture guidelines diff --git a/README.md b/README.md index a20619c2f..3182e4e5a 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ See also: [Security guidance](./docs/security.md) — what development certifica - [`node clear-electron-debug-identity`](./docs/usage.md#node-clear-electron-debug-identity) - Remove identity from Electron processes The full CLI usage can be found here: [Documentation](/docs/usage.md) -The full NPM usage can be found here: [NPM Programmatic API Reference](/docs/npm-usage.md) +Use winapp from JavaScript or TypeScript: [NPM programmatic guide](/docs/npm-usage.md) ## 🧾 Samples diff --git a/docs/cli-schema.json b/docs/cli-schema.json deleted file mode 100644 index aa30b8321..000000000 --- a/docs/cli-schema.json +++ /dev/null @@ -1,6734 +0,0 @@ -{ - "name": "winapp", - "version": "0.7.2", - "schemaVersion": "1.0", - "description": "Create, run, debug, test, and package Windows apps from the command line. Works with WinUI and any other (cross-platform) app framework targeting Windows, and manages Windows SDKs, package identity, manifests, and certificates.", - "hidden": false, - "options": { - "--cli-schema": { - "description": "Output the complete CLI command structure as JSON for tooling, scripting, and LLM integration. Includes all commands, options, arguments, and their descriptions.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 0 - }, - "required": false, - "recursive": true - }, - "--help": { - "description": "Show help and usage information", - "hidden": false, - "aliases": [ - "-?", - "-h", - "/?", - "/h" - ], - "valueType": "System.Void", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 0 - }, - "required": false, - "recursive": true - }, - "--version": { - "description": "Show version information", - "hidden": false, - "valueType": "System.Void", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 0 - }, - "required": false, - "recursive": false - } - }, - "subcommands": { - "az-sign": { - "description": "Code-sign a file using Azure Trusted Signing. Signs executables, MSIX packages, or MSIX bundles using a cloud-managed signing identity. Example: winapp az-sign ./app.msix", - "hidden": false, - "arguments": { - "file-path": { - "description": "Path to the file to sign (exe, msix, or msixbundle)", - "order": 0, - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--account": { - "description": "Signing account name. Must be used with --resource-group", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--metadata-file": { - "description": "Path to an existing metadata.json file. Skips resource discovery and account/profile selection prompts and signs using this file directly. A non-interactive Azure credential should already be available; the CLI can otherwise fall back to an interactive tenant prompt or 'az login', but the npm programmatic API is always non-interactive and fails instead of prompting.", - "hidden": false, - "aliases": [ - "-m" - ], - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--profile": { - "description": "Certificate profile name. Must be used with --account", - "hidden": false, - "aliases": [ - "-p" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--resource-group": { - "description": "Resource group to narrow down signing accounts", - "hidden": false, - "aliases": [ - "-r" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--subscription": { - "description": "Azure subscription ID to use. If not provided and multiple subscriptions exist, you will be prompted.", - "hidden": false, - "aliases": [ - "-s" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "cert": { - "description": "Manage development certificates for code signing. Use 'cert generate' to create a self-signed certificate for testing, or 'cert install' (requires elevation) to trust an existing certificate on this machine.", - "hidden": false, - "subcommands": { - "generate": { - "description": "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.", - "hidden": false, - "options": { - "--export-cer": { - "description": "Export a .cer file (public key only) alongside the .pfx", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--if-exists": { - "description": "Behavior when output file exists: 'error' (fail, default), 'skip' (keep existing), or 'overwrite' (replace)", - "hidden": false, - "helpName": "error|overwrite|skip", - "valueType": "WinApp.Cli.Models.IfExists", - "hasDefaultValue": true, - "defaultValue": "Error", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--install": { - "description": "Install the certificate to the local machine store after generation", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--manifest": { - "description": "Path to Package.appxmanifest or appxmanifest.xml file to extract publisher information from", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--output": { - "description": "Output path for the generated PFX file", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--password": { - "description": "Password for the generated PFX file. Defaults to 'password', which is publicly known — a certificate left with that password is development-only, because anyone who obtains the .pfx can sign as you.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "password", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--publisher": { - "description": "Publisher distinguished name (DN) for the generated certificate (e.g., CN=MyCompany or OU=Team, O=Corp, C=US). Components must be single-valued and comma-separated; multi-valued '+' RDNs, ';' separators, and backslashes are not supported. If not specified, will be inferred from manifest. Bare names are auto-wrapped as CN=.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--valid-days": { - "description": "Number of days the certificate is valid", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 365, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "info": { - "description": "Display certificate details (subject, thumbprint, expiry). Useful for verifying a certificate matches your manifest before signing.", - "hidden": false, - "arguments": { - "cert-path": { - "description": "Path to the certificate file (PFX or CER)", - "order": 0, - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--password": { - "description": "Password for the PFX file (ignored for a public CER)", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "password", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "install": { - "description": "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.", - "hidden": false, - "arguments": { - "cert-path": { - "description": "Path to the certificate file (PFX or CER)", - "order": 0, - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--force": { - "description": "Force installation even if the certificate already exists", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--password": { - "description": "Password for the PFX file", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "password", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - } - } - }, - "create-debug-identity": { - "description": "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/.", - "hidden": false, - "arguments": { - "entrypoint": { - "description": "Path to the .exe that will need to run with identity, or entrypoint script.", - "order": 0, - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--keep-identity": { - "description": "Keep the package identity from the manifest as-is, without appending '.debug' to the package name and application ID.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--manifest": { - "description": "Path to the Package.appxmanifest or appxmanifest.xml", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--no-install": { - "description": "Do not install the package after creation.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "create-external-catalog": { - "description": "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.", - "hidden": false, - "arguments": { - "input-folder": { - "description": "List of input folders with executable files to process (separated by semicolons)", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--compute-flat-hashes": { - "description": "Include flat hashes when generating the catalog", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--if-exists": { - "description": "Behavior when output file already exists", - "hidden": false, - "helpName": "error|overwrite|skip", - "valueType": "WinApp.Cli.Models.IfExists", - "hasDefaultValue": true, - "defaultValue": "Error", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--output": { - "description": "Output catalog file path. If not specified, the default CodeIntegrityExternal.cat name is used.", - "hidden": false, - "aliases": [ - "-o" - ], - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--recursive": { - "description": "Include files from subdirectories", - "hidden": false, - "aliases": [ - "-r" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--use-page-hashes": { - "description": "Include page hashes when generating the catalog", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "embed-identity": { - "description": "Connect a desktop exe to its sparse identity package by embedding the element. Reads identity (packageName, publisher, applicationId) from a sparse appxmanifest.xml and writes it into the target's side-by-side (fusion) manifest. EXE targets are updated with mt.exe; .xml/.manifest targets are edited directly. Example: winapp embed-identity ./bin/MyApp.exe. This is step 3 of the sparse packaging workflow (after 'winapp init --exe --sparse' and 'winapp pack').", - "hidden": false, - "arguments": { - "target": { - "description": "Path to the .exe (embeds identity into its side-by-side manifest via mt.exe) or an .xml/.manifest side-by-side manifest file (inserts/replaces the element; created if it doesn't exist).", - "order": 0, - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--manifest": { - "description": "Path to the sparse appxmanifest.xml to read identity from. When omitted, searched in a 'sparse/' folder (where 'winapp init --exe --sparse' writes it by default) beside the target first, then in the current directory, then beside the target and in the current directory.", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "find-api": { - "description": "Agent-first: built primarily for AI coding agents to ground code generation in the API surface a project actually references instead of guessing (pair it with --json); it works just as well typed by hand. Search and inspect the Windows/WinRT API surface (types, members, enums) available to a project, resolved from its referenced .winmd/.dll metadata. The bare form searches; sub-verbs drill into a specific type or the index itself. Search, members, enums, and check-property each accept several subjects in one call — batch your lookups rather than issuing one call per question. The index is built from the project's restored NuGet/SDK packages and refreshed automatically when the project is restored.", - "hidden": false, - "arguments": { - "query": { - "description": "What to search for, e.g. \"acrylic brush\" or \"NavigationView\". Matched lexically against type and member names across the project's indexed API metadata. Pass several quoted queries to run them in a single call.", - "order": 0, - "hidden": false, - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 0 - } - } - }, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--max": { - "description": "Maximum number of namespace-grouped results to return.", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 5, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project": { - "description": "Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project-dir": { - "description": "Project directory to query (defaults to the current directory). Used to locate the indexed project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - }, - "subcommands": { - "check-property": { - "description": "Validate that one or more properties exist on a type before you write XAML/code against it. Pass several property names to check them in one call. On a miss, suggests similar properties on the type, attached-property forms, and other types that declare the property. Exits non-zero when any property does not exist.", - "hidden": false, - "arguments": { - "type": { - "description": "The type to check.", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - }, - "property": { - "description": "One or more property names to validate on the type. Pass several to check them all in a single call.", - "order": 1, - "hidden": false, - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 0 - } - } - }, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project": { - "description": "Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project-dir": { - "description": "Project directory to query (defaults to the current directory). Used to locate the indexed project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "enums": { - "description": "List the values of one or more enum types. Pass several type names to list them in one call. Exits non-zero when a type exists but is not an enum.", - "hidden": false, - "arguments": { - "type": { - "description": "One or more enum types to list, e.g. Symbol or Microsoft.UI.Xaml.Controls.Symbol. Pass several to list them in a single call.", - "order": 0, - "hidden": false, - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 0 - } - } - }, - "options": { - "--filter": { - "description": "Only list values whose name contains this text (case-insensitive), e.g. --filter folder. The unfiltered total is still reported. Prefer listing the whole enum once over repeated filtered calls — most enums are small enough that the full list is cheaper than several narrowed lookups.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project": { - "description": "Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project-dir": { - "description": "Project directory to query (defaults to the current directory). Used to locate the indexed project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "members": { - "description": "List the properties, events, and methods of one or more types (with XML-doc descriptions and inherited members), resolved from the project's indexed API metadata. Pass several type names to inspect them all in one call.", - "hidden": false, - "arguments": { - "type": { - "description": "One or more types to inspect. Accepts short names (NavigationView) or fully-qualified names (Microsoft.UI.Xaml.Controls.NavigationView). Pass several to resolve them in a single call.", - "order": 0, - "hidden": false, - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 0 - } - } - }, - "options": { - "--all": { - "description": "List the complete member surface: include dependency-property identifier statics (BackgroundProperty) and per-member descriptions, both of which an unfiltered listing omits to save context. Implied by --verbose, and usable together with --json (--verbose is not).", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--filter": { - "description": "Only list members whose name contains this text (case-insensitive), e.g. --filter background. Totals for the unfiltered type are still reported. Applies to every type in the call.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project": { - "description": "Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project-dir": { - "description": "Project directory to query (defaults to the current directory). Used to locate the indexed project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "packages": { - "description": "List the NuGet/SDK packages whose API metadata is indexed for a project, with per-package type and member counts.", - "hidden": false, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project": { - "description": "Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project-dir": { - "description": "Project directory to query (defaults to the current directory). Used to locate the indexed project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "refresh": { - "description": "Rebuild the API metadata index for a project from its restored packages. Runs automatically when a project is restored; run it manually to force a re-index or to index a project for the first time.", - "hidden": false, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project": { - "description": "Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project-dir": { - "description": "Project directory to query (defaults to the current directory). Used to locate the indexed project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--scan": { - "description": "Recursively discover and index every project under the directory instead of just the top-level project(s).", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "stats": { - "description": "Show aggregate statistics for a project's API index: package, namespace, type, member, and .winmd file counts.", - "hidden": false, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project": { - "description": "Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project-dir": { - "description": "Project directory to query (defaults to the current directory). Used to locate the indexed project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - } - } - }, - "find-ui": { - "description": "Agent-first: built primarily for AI coding agents to pull a real WinUI sample into the editor instead of inventing markup (pair it with --json); it works just as well typed by hand. Search WinUI controls and samples for a working code example. WinUI-only: covers the WinUI 3 Gallery and the Windows Community Toolkit by default (plus the microsoft-ui-reactor ReactorGallery as an opt-in source via --source reactor); not WPF/WinForms. A corpus is baked into the CLI, so this works offline and behind proxies; when GitHub is reachable it refreshes to the latest samples and caches them per-user.", - "hidden": false, - "arguments": { - "query": { - "description": "What you're looking for, e.g. \"tabbed layout\" or \"color picker\". Matched lexically against WinUI control names, sample headers, and tags.", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--id": { - "description": "Fetch the code (Gallery/Toolkit return XAML and/or C#; Reactor is C#-only) plus prerequisite notes for one or more scenario ids from a prior search (e.g. gallery-tabview-1).", - "hidden": false, - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--list": { - "description": "List every discoverable control/sample id instead of searching. Covers Gallery, Toolkit, and core; the opt-in Reactor source is excluded (search it with --source reactor).", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--max": { - "description": "Maximum number of matched controls to return. Applies to search only; ignored with --list and --id.", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 3, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--refresh": { - "description": "Bypass the local cache and re-fetch the WinUI corpus from GitHub.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--source": { - "description": "Restrict results to a single source: gallery (WinUI 3 Gallery), toolkit (Windows Community Toolkit), reactor (microsoft-ui-reactor, C#-only declarative WinUI), or core (curated patterns). Reactor is opt-in — it is excluded from a normal search, so pass --source reactor to search it (only do this for a Reactor/MVU project; its C#-only samples don't paste into a standard XAML app).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "get-winapp-path": { - "description": "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.", - "hidden": false, - "options": { - "--global": { - "description": "Get the global .winapp directory instead of local", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "init": { - "description": "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.", - "hidden": false, - "arguments": { - "base-directory": { - "description": "Base/root directory for the winapp workspace, for consumption or installation.", - "order": 0, - "hidden": false, - "valueType": "System.IO.DirectoryInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--config-dir": { - "description": "Directory to read/store configuration (default: the selected project directory, or current directory if no project is detected)", - "hidden": false, - "valueType": "System.IO.DirectoryInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--config-only": { - "description": "Only handle configuration file operations (create if missing, validate if exists). Skip package installation and other workspace setup steps.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--exe": { - "description": "Path to the application executable. Requires --sparse. Generates an identity-only sparse manifest for the exe instead of a full package/SDK setup.", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--force": { - "description": "Overwrite an existing appxmanifest.xml in the target directory (sparse only). Without this, init fails instead of replacing existing manifest/asset files.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--ignore-config": { - "description": "Don't use configuration file for version management", - "hidden": false, - "aliases": [ - "--no-config" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--name": { - "description": "Override the package name (sparse only; default: inferred from the exe)", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--no-gitignore": { - "description": "Don't update .gitignore file", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--output-dir": { - "description": "Directory to write the sparse manifest and Assets/ (sparse only; default: a 'sparse/' folder in the current directory)", - "hidden": false, - "valueType": "System.IO.DirectoryInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--publisher": { - "description": "Override the publisher CN (sparse only; default: inferred from the exe's company name). Bare names are auto-wrapped as CN=.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--setup-sdks": { - "description": "SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation)", - "hidden": false, - "helpName": "stable|preview|experimental|none", - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--sparse": { - "description": "Generate a sparse identity manifest (appxmanifest.xml) for an existing desktop exe instead of a full package manifest. Use with --exe. Skips SDK/package installation.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--use-defaults": { - "description": "Skip interactive prompts and use default answers. Normal init targets the positional project directory if given, otherwise the current directory (e.g., winapp init . --use-defaults). Sparse init (--exe --sparse) ignores the positional directory and writes to --output-dir instead.", - "hidden": false, - "aliases": [ - "--no-prompt" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "manifest": { - "description": "Create and modify Package.appxmanifest files for package identity and MSIX packaging. Use 'manifest generate' to create a new manifest, 'manifest update-assets' to regenerate app icons, or 'manifest add-alias' to add an execution alias.", - "hidden": false, - "subcommands": { - "add-alias": { - "description": "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).", - "hidden": false, - "options": { - "--app-id": { - "description": "Application Id to add the alias to (default: first Application element)", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--manifest": { - "description": "Path to Package.appxmanifest or appxmanifest.xml file (default: search current directory)", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--name": { - "description": "Alias name (e.g. 'myapp.exe'). Default: inferred from the Executable attribute in the manifest.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "generate": { - "description": "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).", - "hidden": false, - "arguments": { - "directory": { - "description": "Directory to generate manifest in", - "order": 0, - "hidden": false, - "valueType": "System.IO.DirectoryInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--description": { - "description": "Human-readable app description shown during installation and in Windows Settings", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "My Application", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--executable": { - "description": "Path to the application's executable. Default: .exe", - "hidden": false, - "aliases": [ - "--entrypoint" - ], - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--if-exists": { - "description": "Behavior when output file exists: 'error' (fail, default), 'skip' (keep existing), or 'overwrite' (replace)", - "hidden": false, - "helpName": "error|overwrite|skip", - "valueType": "WinApp.Cli.Models.IfExists", - "hasDefaultValue": true, - "defaultValue": "Error", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--logo-path": { - "description": "Path to logo image file", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--package-name": { - "description": "Package name (default: folder name)", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--publisher-name": { - "description": "Publisher distinguished name (DN) (default: CN=). Accepts an X.500 DN with single-valued, comma-separated components (multi-valued '+' RDNs, ';' separators, and backslashes are not supported); bare names are auto-wrapped as CN=.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--template": { - "description": "Manifest template type: 'packaged' (full MSIX app, default) or 'sparse' (desktop app with package identity for Windows APIs)", - "hidden": false, - "helpName": "packaged|sparse", - "valueType": "WinApp.Cli.Models.ManifestTemplates", - "hasDefaultValue": true, - "defaultValue": "Packaged", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--version": { - "description": "App version in Major.Minor.Build.Revision format (e.g., 1.0.0.0).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "1.0.0.0", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "update-assets": { - "description": "Generate new assets for images referenced in a Package.appxmanifest from a single source image. Source image should be at least 400x400 pixels.", - "hidden": false, - "arguments": { - "image-path": { - "description": "Path to source image file (SVG, PNG, ICO, JPG, BMP, GIF)", - "order": 0, - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--light-image": { - "description": "Path to source image for light theme variants (SVG, PNG, ICO, JPG, BMP, GIF)", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--manifest": { - "description": "Path to Package.appxmanifest or appxmanifest.xml file (default: search current directory)", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - } - } - }, - "new": { - "description": "Create a new WinUI app from an official Windows App SDK template. Templates cover both markup-based XAML apps (blank, NavigationView, TabView, MVVM) and the experimental Reactor apps (C#-only, MVU) — pick one interactively, then a name (the output directory defaults to ./). Automatically uses defaults in non-interactive environments (use --use-defaults to skip prompts explicitly). Requires the .NET SDK; installs the WinUI template pack on demand (grabbing the latest, or offering to update a stale one) and delegates scaffolding to 'dotnet new'. Use --list to see the available templates. Scaffolds against the installed SDK's target framework and prints a template-specific next step when done (e.g. 'dotnet run' for app templates).", - "hidden": false, - "options": { - "--force": { - "description": "Scaffold even if the output directory already contains files.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--list": { - "description": "List the available WinUI templates and exit (installs the latest template pack if none is installed).", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--name": { - "description": "Name for the new app/project (default: derived from --output, else 'WinUIApp').", - "hidden": false, - "aliases": [ - "-n" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--output": { - "description": "Directory to create the app in (default: ./). Created if it doesn't exist.", - "hidden": false, - "aliases": [ - "-o" - ], - "valueType": "System.IO.DirectoryInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--template": { - "description": "Template short name. XAML templates: winui, winui-navview, winui-tabview, winui-mvvm, winui-lib, winui-unittest. Experimental Reactor (C#-only, MVU) templates: reactor, reactor-mvu, reactor-navview, reactor-tabview. Run 'winapp new --list' to see all.", - "hidden": false, - "aliases": [ - "-t" - ], - "helpName": "short-name", - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--template-version": { - "description": "WinUI template pack version: 'latest' (install newest), 'installed' (keep what's installed), or an explicit version. Default: install latest if none, else prompt to update a stale pack.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--use-defaults": { - "description": "Do not prompt; use defaults (blank template, name from --output/--name, keep installed templates).", - "hidden": false, - "aliases": [ - "--no-prompt" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "package": { - "description": "Create an MSIX installer from a built app folder or directly from a .csproj. Pass a package-layout folder (run after building your app; a manifest must be in the current directory, passed as --manifest, or in the input folder), or pass a .csproj to build and package it in one step (e.g. winapp package ./MyApp.csproj -c Release). Use --cert devcert.pfx to sign for testing.", - "hidden": false, - "aliases": [ - "pack" - ], - "arguments": { - "input-folder": { - "description": "A single .csproj to build and package (project mode), one or more input folders with package layout, or a single sparse appxmanifest.xml file (an identity-only package with AllowExternalContent). Pass multiple folders to create an MSIX bundle (e.g., winapp pack ./publish/x64 ./publish/arm64).", - "order": 0, - "hidden": false, - "valueType": "System.IO.DirectoryInfo[]", - "hasDefaultValue": false, - "arity": { - "minimum": 1 - } - } - }, - "options": { - "--arch": { - "description": "Project mode: target architecture (x64, arm64, or x86). Repeatable — pass two or more to publish each and produce one architecture .msixbundle. Requires a .csproj input; rejected for folder/bundle/manifest inputs. Default: the current process architecture.", - "hidden": false, - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 0 - }, - "required": false, - "recursive": false - }, - "--cert": { - "description": "Path to signing certificate (will auto-sign if provided)", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--cert-password": { - "description": "Certificate password (default: password)", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "password", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--configuration": { - "description": "Project mode: build configuration (e.g., Debug, Release). Requires a .csproj input; rejected for folder/bundle/manifest inputs. Default: Release.", - "hidden": false, - "aliases": [ - "-c" - ], - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "Release", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--executable": { - "description": "Path to the executable relative to the input folder.", - "hidden": false, - "aliases": [ - "--exe" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--framework": { - "description": "Project mode: target framework moniker for multi-targeted projects (e.g. net10.0-windows10.0.26100.0). Requires a .csproj input; rejected for folder/bundle/manifest inputs.", - "hidden": false, - "aliases": [ - "-f" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--generate-cert": { - "description": "Generate a new development certificate", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--install-cert": { - "description": "Install certificate to machine", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--manifest": { - "description": "Path to AppX manifest file (default: auto-detect from input folder or current directory)", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--name": { - "description": "Package name (default: from manifest)", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--no-build": { - "description": "Project mode: skip building and package the existing build output (still evaluates output properties). Requires a .csproj input; rejected for folder/bundle/manifest inputs.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--no-restore": { - "description": "Project mode: skip restoring the project before building. Requires a .csproj input; rejected for folder/bundle/manifest inputs.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--no-sign": { - "description": "Deliver the package unsigned, overriding any project signing configuration (e.g. for Store submission or an external signing pipeline). Cannot be combined with --cert or --generate-cert.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--output": { - "description": "Output file name for the generated package (.msix) or bundle (.msixbundle). Defaults to __.msix for single packages, or ___.msixbundle for bundles.", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--property": { - "description": "Project mode: MSBuild property as Name=Value, forwarded to both build and evaluation. Repeatable (e.g. -p WindowsPackageType=None). Use -c for configuration, -f for framework, and --arch for architecture; a -p Configuration/TargetFramework is dropped in favor of those flags, while a lone -p RuntimeIdentifier (no --arch) selects an exact RID. Requires a .csproj input; rejected for folder/bundle/manifest inputs.", - "hidden": false, - "aliases": [ - "-p" - ], - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 0 - }, - "required": false, - "recursive": false - }, - "--publisher": { - "description": "Publisher distinguished name (DN) for certificate generation (e.g., CN=MyCompany). Bare names are auto-wrapped as CN=.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--self-contained": { - "description": "Bundle Windows App SDK runtime for self-contained deployment", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--skip-pri": { - "description": "Skip PRI file generation", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "restore": { - "description": "Use after cloning a repo or when .winapp/ folder is missing. Reinstalls SDK packages without changing versions, reading them from winapp.yaml or, for a .NET project initialized by 'init', from the .csproj via 'dotnet restore'. Requires a project already initialized by 'init'. To check for newer SDK versions, use 'update' instead.", - "hidden": false, - "arguments": { - "base-directory": { - "description": "Base/root directory for the winapp workspace", - "order": 0, - "hidden": false, - "valueType": "System.IO.DirectoryInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--config-dir": { - "description": "Directory to read configuration from (default: base-directory)", - "hidden": false, - "valueType": "System.IO.DirectoryInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "run": { - "description": "Builds or Native AOT-publishes and runs a Windows app from a .cs file-based app, a .csproj/.sln, or a build-output folder. In project mode, invokes dotnet build — or the project's configured Native AOT publish with --aot — then launches the app (packaged or unpackaged); in single-file mode, builds the .cs and launches it, generating a manifest from its #:property directives when the app is packaged; in folder mode, creates a debug-signed layout, registers the package, and launches it.", - "hidden": false, - "arguments": { - "input": { - "description": "Path to the app to run: a build-output folder, a .cs .NET file-based app, a .csproj project, a .sln/.slnx solution, or a directory containing one of those at its top level (default: current directory).", - "order": 0, - "hidden": false, - "valueType": "System.IO.FileSystemInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - }, - "app-args": { - "description": "Arguments to pass to the launched application. Provide after -- (e.g., winapp run . -- --flag value).", - "order": 1, - "hidden": true, - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 0 - } - } - }, - "options": { - "--aot": { - "description": "Project mode: run the project's configured .NET Native AOT publish. Requires effective PublishAot=true.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--arch": { - "description": "Project mode: target architecture (x64, arm64, or x86). Sets the canonical Windows RID and selects a matching platform-dependent publish profile when required by the effective build. Ignored in folder mode. Honored for a .cs file-based app too; when omitted, winapp builds for the current process architecture. Default: the current process architecture.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--args": { - "description": "Command-line arguments to pass to the application. Alternatively, use -- followed by arguments to avoid escaping (e.g., winapp run . -- --flag value).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--clean": { - "description": "Remove the existing package's application data (LocalState, settings, etc.) before re-deploying. By default, application data is preserved across re-deployments.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--configuration": { - "description": "Project and single-file mode: build configuration (e.g., Debug, Release). Ignored in folder mode. Default: Debug.", - "hidden": false, - "aliases": [ - "-c" - ], - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "Debug", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--debug-output": { - "description": "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. For WinUI apps, a crash also triggers a stowed-exception triage pass; the first run downloads debugger components (cached under the winapp global directory) and can be pointed at an existing debugger install via the WINAPP_DBGTOOLS_DIR environment variable. Cannot be combined with --no-launch or --json.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--detach": { - "description": "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. Local runs print the PID; target runs print the scoped UI target. JSON includes the PID and target scope.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--executable": { - "description": "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.", - "hidden": false, - "aliases": [ - "--exe" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--framework": { - "description": "Project mode: target framework moniker for multi-targeted projects (e.g. net10.0-windows10.0.26100.0). Ignored in folder mode. Rejected for a .cs file-based app, which declares its own with '#:property TargetFramework=...'.", - "hidden": false, - "aliases": [ - "-f" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--manifest": { - "description": "Path to the Package.appxmanifest (default: auto-detect from input folder or current directory)", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--no-build": { - "description": "Project and single-file mode: skip building and run the existing build output (still evaluates output properties). Ignored in folder mode.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--no-launch": { - "description": "Only create the debug identity and register the package without launching the application", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--no-restore": { - "description": "Project and single-file mode: skip restoring before build or Native AOT publish. Ignored in folder mode.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--on": { - "description": "Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": true - }, - "--output-appx-directory": { - "description": "Output directory for the loose layout package. If not specified, a directory named AppX inside the input directory will be used.", - "hidden": false, - "valueType": "System.IO.DirectoryInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--project": { - "description": "Project mode: when the input is a solution (.sln/.slnx) or a directory with multiple runnable app projects, selects which project to launch (by name or path). Ignored in folder mode. Rejected for a .cs file-based app, which is itself the project.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--property": { - "description": "Project and single-file mode: MSBuild property as Name=Value, forwarded to both build and evaluation. Repeatable. Ignored in folder mode.", - "hidden": false, - "aliases": [ - "-p" - ], - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 0 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--runtime": { - "description": "Project mode: target .NET runtime identifier (RID), e.g. win-x64. Project mode uses only the RID's architecture, always builds the canonical win-, rejects non-Windows RIDs (e.g. linux-x64), and can select a required architecture-dependent publish profile; it overrides --arch. Ignored in folder mode. Honored for a .cs file-based app too.", - "hidden": false, - "aliases": [ - "-r" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--symbols": { - "description": "Download symbols from Microsoft Symbol Server for richer native crash analysis, including the WinUI stowed-exception dispatch stack. Only used with --debug-output. First run downloads symbols and caches them locally; subsequent runs use the cache.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--unregister-on-exit": { - "description": "Unregister the development package after the application exits. Only removes packages registered in development mode.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--with-alias": { - "description": "Launch the app using its execution alias instead of AUMID activation. The app runs in the current terminal with inherited stdin/stdout/stderr. Console apps (OutputType=Exe) already do this by default; pass this to force it for a windowed app. winapp adds a uap5:ExecutionAlias to the manifest it stages for you, so no manifest edit is needed.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--without-alias": { - "description": "Launch via AUMID activation even for a console app, instead of the default execution alias. The app then runs without a console, so it prints nothing to this terminal.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "sign": { - "description": "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.", - "hidden": false, - "arguments": { - "file-path": { - "description": "Path to the file/package to sign", - "order": 0, - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - }, - "cert-path": { - "description": "Path to the certificate file (PFX format)", - "order": 1, - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--password": { - "description": "Certificate password", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "password", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--timestamp": { - "description": "Timestamp server URL", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "store": { - "description": "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", - "hidden": false - }, - "target": { - "description": "Run commands and copy files on an execution target such as the Windows Sandbox winapp manages. Use these to prepare dependencies or diagnose an application that 'winapp run --on ' cannot resolve on its own.", - "hidden": false, - "subcommands": { - "exec": { - "description": "Run a command on an execution target, as that target's interactive user. Streams stdin, stdout, and stderr, and returns the command's own exit code. Does not provide a full terminal, so interactive console applications may see redirected pipes.", - "hidden": false, - "arguments": { - "target": { - "description": "Execution target to act on. Currently: 'sandbox'.", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - }, - "command": { - "description": "Executable and arguments to run on the target, after '--'.", - "order": 1, - "hidden": false, - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 1 - } - } - }, - "options": { - "--cwd": { - "description": "Working directory on the target.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "pull": { - "description": "Copy files or directories from an execution target to this machine. Directory structure and useful timestamps are preserved, unchanged files are skipped, and changed files are replaced atomically.", - "hidden": false, - "arguments": { - "target": { - "description": "Execution target to act on. Currently: 'sandbox'.", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - }, - "source": { - "description": "File or directory on the target to copy, relative to its managed work area.", - "order": 1, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - }, - "destination": { - "description": "Destination path on this machine.", - "order": 2, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "push": { - "description": "Copy files or directories from this machine to an execution target. Directory structure and useful timestamps are preserved, unchanged files are skipped, and changed files are replaced atomically.", - "hidden": false, - "arguments": { - "target": { - "description": "Execution target to act on. Currently: 'sandbox'.", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - }, - "source": { - "description": "File or directory on this machine to copy.", - "order": 1, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - }, - "destination": { - "description": "Destination path on the target, relative to its managed work area.", - "order": 2, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "record": { - "description": "Record an execution target's entire desktop without activating a host or guest window. The MP4 and optional frame bundle are delivered to this machine when recording finishes. JSON and the frame manifest describe the guest-screen mapping after scaling or padding. Prefer --duration-sec; otherwise stop with Ctrl+C or a newline on redirected stdin.", - "hidden": false, - "arguments": { - "target": { - "description": "Execution target to act on. Currently: 'sandbox'.", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--duration-sec": { - "description": "Recording duration in seconds. 0 records until Ctrl+C or redirected-stdin newline/EOF.", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--fps": { - "description": "Frames per second to capture", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 15, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--frames": { - "description": "Write timestamped JPEGs, frames.ndjson, and manifest.json to .frames. Supports 1-30 fps and max-edge 64-4096 (default 1280), with a 1 GiB frame-data cap.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--max-edge": { - "description": "Downscale so the longest edge is at most this many pixels (0 = no downscale)", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--output": { - "description": "Save output to this file path.", - "hidden": false, - "aliases": [ - "-o" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--overwrite": { - "description": "Replace an existing recording only after the new take finishes. Previous frame bundles are retained under a .previous- directory.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "screenshot": { - "description": "Capture an execution target's entire desktop at its native pixel size. Saves a PNG on this machine without activating a host or guest window. JSON includes the guest screen origin and pixel-coordinate mapping.", - "hidden": false, - "arguments": { - "target": { - "description": "Execution target to act on. Currently: 'sandbox'.", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--output": { - "description": "Save output to this file path.", - "hidden": false, - "aliases": [ - "-o" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "snapshot": { - "description": "Report an execution target's readiness, capabilities, deployments, and top-level guest windows. Inspects only: never starts, connects, or repairs a target, and reports plainly when none is running. Writes only to stdout: no screenshots and no files.", - "hidden": false, - "arguments": { - "target": { - "description": "Execution target to act on. Currently: 'sandbox'.", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - } - } - }, - "tool": { - "description": "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", - "hidden": false, - "aliases": [ - "run-buildtool" - ], - "options": { - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "ui": { - "description": "Inspect and interact with any running Windows app using UI Automation (UIA). Works with WPF, WinForms, Win32, Electron, and WinUI 3 apps.", - "hidden": false, - "options": { - "--on": { - "description": "Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": true - } - }, - "subcommands": { - "click": { - "description": "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.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--double": { - "description": "Perform a double-click instead of a single click", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--right": { - "description": "Perform a right-click instead of a left click", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "drag": { - "description": "Press the mouse button at one point, move to another, then release. 'drag ', where / are each an element selector (uses the element's center) or screen x,y coordinates as reported by 'ui inspect'. Useful for reorder/resize/slider gestures and drag-and-drop. Use --right for a right-button drag, --hold-ms for press-and-hold/long-press, and --dwell-ms to settle on a drop target before releasing.", - "hidden": false, - "arguments": { - "from": { - "description": "Start point — an element selector (drags from its center) or screen coordinates x,y as reported by 'ui inspect' (e.g. pn-list-d736 or 100,200).", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - }, - "to": { - "description": "End point — an element selector (drops at its center) or screen coordinates x,y as reported by 'ui inspect' (e.g. pn-target-d746 or 300,400).", - "order": 1, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--dwell-ms": { - "description": "Milliseconds to dwell at the destination after moving, before releasing (default: 0). Lets drop targets / merge overlays that arm from a sustained hover latch before release.", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--hold-ms": { - "description": "Milliseconds to hold the button down at the start before moving (default: 0). With == (no movement) this performs a press-and-hold / long-press gesture.", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--right": { - "description": "Drag with the right mouse button instead of the left button", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "focus": { - "description": "Activate the specified element's window, focus the element, and verify foreground and keyboard focus. Fails if Windows refuses activation or focus cannot be confirmed.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "get-focused": { - "description": "Show the element that currently has keyboard focus in the target app. With -w, focus must belong to that exact top-level window; owned popups are excluded.", - "hidden": false, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "get-property": { - "description": "Read UIA property values from an element. Specify --property for a single property or omit for all. Includes whole-document TextPattern formatting: FontWeight, FontName, FontSize, ForegroundColor, IsItalic, StrikethroughStyle.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--class-name": { - "description": "Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--property": { - "description": "Property name to read or filter on", - "hidden": false, - "aliases": [ - "-p" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--root": { - "description": "Search only descendants of this uniquely matching selector (excludes the root).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--type": { - "description": "UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "get-value": { - "description": "Read the current value from an element. Tries TextPattern (RichEditBox, Document), ValuePattern (TextBox, ComboBox, Slider), then Name (labels). Usage: winapp ui get-value -a ", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--class-name": { - "description": "Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--root": { - "description": "Search only descendants of this uniquely matching selector (excludes the root).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--type": { - "description": "UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "hover": { - "description": "Move the mouse to an element's center to trigger hover effects (tooltips, flyouts, visual states). Uses SendInput for realistic mouse movement and waits for a configurable dwell time.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--dwell-time": { - "description": "Time in milliseconds to wait after hovering for hover effects to appear (default: 800)", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 800, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "inspect": { - "description": "View the UI element tree with semantic slugs, element types, names, and bounds.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--ancestors": { - "description": "Walk up the tree from the specified element to the root", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--depth": { - "description": "Tree inspection depth", - "hidden": false, - "aliases": [ - "-d" - ], - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 4, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--hide-disabled": { - "description": "Hide disabled elements from output", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--hide-offscreen": { - "description": "Hide offscreen elements from output", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--interactive": { - "description": "Show only interactive/invokable elements (buttons, links, inputs, list items). Increases default depth to 8.", - "hidden": false, - "aliases": [ - "-i" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "invoke": { - "description": "Activate an element by slug or text search. Without --action, tries InvokePattern, TogglePattern, SelectionItemPattern, and ExpandCollapsePattern in order, then an invokable ancestor. Use --action for an exact operation on only the selected element.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--action": { - "description": "Perform exactly this action on the selected element, without pattern or ancestor fallback: invoke, select, toggle, toggle-on, toggle-off, expand, collapse.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "list-windows": { - "description": "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.", - "hidden": false, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--show-hidden": { - "description": "Include untitled zero-size windows that are hidden by default", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "pen": { - "description": "Inject synthetic pen/stylus input using the Windows synthetic-pointer API. Taps or draws ink strokes with configurable pressure, tilt and eraser mode, at an element's center or explicit screen x,y coordinates. Requires an unlocked, interactive desktop with the target window foregroundable (Windows 10 1809+).", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--at": { - "description": "Pen contact point as screen coordinates x,y (as reported by 'ui inspect'). Defaults to the selector's element center. Ignored when --path is given.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--duration-ms": { - "description": "Total glide time in milliseconds distributed across the stroke path segments (default: ~10 ms per segment).", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--eraser": { - "description": "Use the eraser end of the pen instead of the tip.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--path": { - "description": "Ink stroke path as a whitespace-separated list of x,y pairs, e.g. \"10,10 20,30 40,50\".", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--pressure": { - "description": "Pen pressure from 0.0 to 1.0 (default: 0.5).", - "hidden": false, - "valueType": "System.Single", - "hasDefaultValue": true, - "defaultValue": 0.5, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--tilt-x": { - "description": "Pen tilt along the x-axis in degrees (-90 to 90, default: 0).", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--tilt-y": { - "description": "Pen tilt along the y-axis in degrees (-90 to 90, default: 0).", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "record": { - "description": "Record the target window (or an element's region) to an H.264 MP4 video. By default records until Ctrl+C or redirected-stdin newline/EOF. Use --duration-sec for a timed run, --frames for timestamped JPEG evidence, and --capture-screen for overlays and popups.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--capture-screen": { - "description": "Capture from screen DC via BitBlt (includes popups/overlays not owned by the target).", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--duration-sec": { - "description": "Recording duration in seconds. 0 records until Ctrl+C or redirected-stdin newline/EOF.", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--fps": { - "description": "Frames per second to capture", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 15, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--frames": { - "description": "Write timestamped JPEGs, frames.ndjson, and manifest.json to .frames. Supports 1-30 fps and max-edge 64-4096 (default 1280), with a 1 GiB frame-data cap.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--max-edge": { - "description": "Downscale so the longest edge is at most this many pixels (0 = no downscale)", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--output": { - "description": "Save output to this file path.", - "hidden": false, - "aliases": [ - "-o" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--overwrite": { - "description": "Replace an existing recording only after the new take finishes. Previous frame bundles are retained under a .previous- directory.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "screenshot": { - "description": "Capture the target window or element as a PNG image. Without an element selector, combines multiple windows into one labeled composite: --app by process name or PID includes the app's windows and their owned windows; a title match or --window selects one window plus its owned windows. With --json, returns file path and dimensions. Use --capture-screen with --window to capture one screen region, including visible overlays in place.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--capture-screen": { - "description": "Capture from screen DC via BitBlt (includes popups/overlays not owned by the target).", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--focus": { - "description": "Bring the target window to the foreground before capture. Already implied by --capture-screen.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--output": { - "description": "Save output to this file path.", - "hidden": false, - "aliases": [ - "-o" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "scroll": { - "description": "Scroll a container element using ScrollPattern. Use --direction to scroll incrementally, --to to jump to top/bottom, or --wheel to synthesize mouse-wheel input.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--direction": { - "description": "Scroll direction: up, down, left, right", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--to": { - "description": "Scroll to position: top, bottom", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--wheel": { - "description": "Rotate the mouse wheel over the element by this many notches (1 = one notch up, -1 = one notch down). Synthesizes real wheel input instead of using ScrollPattern.", - "hidden": false, - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "scroll-into-view": { - "description": "Scroll the specified element into the visible area using UIA ScrollItemPattern.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "search": { - "description": "Search the element tree for elements matching a text query. Returns all matches with semantic slugs.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--class-name": { - "description": "Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--max": { - "description": "Maximum search results", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 50, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--root": { - "description": "Search only descendants of this uniquely matching selector (excludes the root).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--type": { - "description": "UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "send-keys": { - "description": "Send synthetic keyboard input to a window. Supports named keys (down, enter, tab), modifier combos (ctrl+shift+t), raw virtual keys (vk=0xNN), and literal text. Use --verbatim to type the whole argument literally, or --target to focus an element first. Two transports via --via: post-message (default, HWND-targeted, bypasses UIPI) or send-input (OS-wide). For per-keystroke KeyDown on typed text (e.g. a WinUI 3/WPF TextBox), use --via send-input.", - "hidden": false, - "arguments": { - "keys": { - "description": "Keys to send. Whitespace-separated tokens: named keys (down, enter, tab, esc, f5), modifier combos (ctrl+shift+t, alt+f4), raw virtual keys (vk=0x42), or literal text (hello). Hold capslock or insert for screen-reader commands (ctrl+capslock+f12 toggles Narrator developer mode); these require --via send-input. Use text= to type a single value verbatim when it would otherwise be read as a key name or combo (text=enter types \"enter\"; text=ctrl+a types \"ctrl+a\"); backslash escapes \\s \\t \\n \\r \\\\ are supported (text=a\\s\\sb types \"a b\"). To type the whole argument literally without escaping each token, pass --verbatim instead. Quote multi-token strings, e.g. \"ctrl+a delete\".", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--allow-system-keys": { - "description": "Allow synthesizing system-/shell-reserved combos (win+, alt+f4, alt+tab, ctrl+esc, …) via --via send-input, which are refused by default because they act on the OS/shell beyond the target app. Opt in to drive global hotkeys (e.g. PowerToys' win+shift+v, win+r). No effect on --via post-message (already window-scoped; a warning is emitted if set without send-input). Note: win+l and ctrl+alt+del stay blocked even with this flag — win+l locks the workstation (LockWorkStation() via the shell hook), which is unrecoverable from automation, and ctrl+alt+del is a Secure Attention Sequence (SAS) that Windows drops from injected input regardless of this flag, so it can never take effect.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--target": { - "description": "Optional selector (slug or text) to focus before sending keys.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbatim": { - "description": "Type the entire keys argument as literal text — no named-key, combo, or vk= interpretation, and exact whitespace preserved. The whole-argument form of the per-token text= escape: --verbatim \"down down enter\" types the words instead of pressing Down, Down, Enter.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--via": { - "description": "Transport: post-message (default, HWND-targeted, bypasses UIPI; typed text raises TextChanged but not a per-character KeyDown) or send-input (OS-wide; typed text raises a real per-character KeyDown + TextChanged). Named keys and combos raise KeyDown on both, but keyboard accelerators/shortcuts (KeyboardAccelerator, e.g. ctrl+t) only fire via send-input. post-message targets the focused child control and works for classic Win32/WinForms controls, but WinUI 3 / UWP / XAML controls are windowless and ignore posted messages — use send-input for those (a warning is emitted when the target looks like a XAML app).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "post-message", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "set-value": { - "description": "Set a value on an element programmatically. Works for TextBox, ComboBox, Slider, and other editable controls via UIA ValuePattern/RangeValuePattern, with a LegacyIAccessible (put_accValue) fallback for TextPattern-only edit controls — no app foreground required. Some rich text controls (e.g. WinUI 3 RichEditBox and WPF RichTextBox) don't support setting their value programmatically — use the 'send-keys' command with '--via send-input' to type into them instead. Usage: winapp ui set-value -a ", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - }, - "value": { - "description": "Value to set (text for TextBox/ComboBox, number for Slider)", - "order": 1, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "status": { - "description": "Connect to a target app and display connection info.", - "hidden": false, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "touch": { - "description": "Inject synthetic touch input using the Windows touch-injection API. Supports tap, double-tap, long-press, swipe, pinch and stretch gestures at an element's center or explicit screen x,y coordinates. Requires an unlocked, interactive desktop with the target window foregroundable.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--at": { - "description": "Explicit start point as screen coordinates x,y (as reported by 'ui inspect'). Defaults to the selector's element center.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--direction": { - "description": "Swipe direction: right (default), left, up, or down. Combined with --distance to compute the end point when --to-point is not given.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "right", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--distance": { - "description": "Distance in pixels for pinch/stretch (finger spread) or swipe.", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--duration-ms": { - "description": "Glide time in milliseconds for moving gestures (swipe/pinch/stretch).", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 300, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--fingers": { - "description": "Number of touch contacts (default: 1). Pinch/stretch always use 2.", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 1, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--gesture": { - "description": "Gesture to perform: tap, double-tap, long-press, swipe, pinch, stretch (default: tap).", - "hidden": false, - "aliases": [ - "-g" - ], - "valueType": "System.String", - "hasDefaultValue": true, - "defaultValue": "tap", - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--hold-ms": { - "description": "Milliseconds to hold contacts down before lifting (long-press hold time). Defaults to 500 ms when --gesture long-press is used and this option is not set.", - "hidden": false, - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 0, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--to-point": { - "description": "End point x,y for a swipe (screen coordinates). Takes precedence over --direction.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "wait-for": { - "description": "Wait for an element to appear, disappear, or have a property reach a target value. Polls at 100ms intervals until condition met or timeout.", - "hidden": false, - "arguments": { - "selector": { - "description": "Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId", - "order": 0, - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--app": { - "description": "Target app (process name, window title, or PID). Lists windows if ambiguous.", - "hidden": false, - "aliases": [ - "-a" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--class-name": { - "description": "Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--contains": { - "description": "Use substring matching for --value instead of exact match", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--gone": { - "description": "Wait for element to disappear instead of appear", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--property": { - "description": "Property name to read or filter on", - "hidden": false, - "aliases": [ - "-p" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--root": { - "description": "Search only descendants of this uniquely matching selector (excludes the root).", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--timeout": { - "description": "Timeout in milliseconds", - "hidden": false, - "aliases": [ - "-t" - ], - "valueType": "System.Int32", - "hasDefaultValue": true, - "defaultValue": 5000, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--type": { - "description": "UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--value": { - "description": "Wait for element value to equal this string. Uses smart fallback (TextPattern -> ValuePattern -> Name). Combine with --property to check a specific property instead.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--window": { - "description": "Target window by HWND (stable handle from list output). Takes precedence over --app.", - "hidden": false, - "aliases": [ - "-w" - ], - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "yield": { - "description": "Release the current workflow's idle UI turn early. A workflow with WINAPP_UI_WORKFLOW_ID keeps the desktop for a few seconds after each command so a burst of commands reads as one workflow; run this after the final command of a workflow to hand the desktop to waiting workflows straight away. Requires WINAPP_UI_WORKFLOW_ID; targets no app and takes no selector.", - "hidden": false, - "options": { - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - } - } - }, - "unregister": { - "description": "Unregisters a sideloaded development package. Only removes packages registered in development mode (e.g., via 'winapp run' or 'create-debug-identity').", - "hidden": false, - "arguments": { - "input": { - "description": "Path to a .NET file-based app (a single .cs) whose package should be unregistered. Its identity is resolved the same way 'winapp run' resolves it, so no manifest path is needed. Omit to use --manifest or auto-detect a manifest in the current directory. Cannot be combined with --manifest.", - "order": 0, - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - } - } - }, - "options": { - "--arch": { - "description": "Target architecture (x64, arm64, x86) used when resolving a .cs file-based app's identity (default: the current process architecture). Pass the same architecture the run used, since a Directory.Build.props can key identity off $(RuntimeIdentifier). Only applies to a .cs input.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--configuration": { - "description": "Build configuration used when resolving a .cs file-based app's identity (default: Debug). Pass the same configuration the run used: a Directory.Build.props beside the .cs can set WinAppPackageName or WinAppManifestPath conditionally on $(Configuration). Only applies to a .cs input.", - "hidden": false, - "aliases": [ - "-c" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--force": { - "description": "Skip the install-location directory check and unregister even if the package was registered from a different project tree. Candidates are matched by Identity/@Name alone, so with --force a same-named package from a different publisher is also removed, along with its application data — prefer --prune for registrations whose files are gone. With --prune, also skips the confirmation prompt.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--json": { - "description": "Format output as JSON", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--manifest": { - "description": "Path to the Package.appxmanifest (default: auto-detect from current directory)", - "hidden": false, - "valueType": "System.IO.FileInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--on": { - "description": "Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here.", - "hidden": false, - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": true - }, - "--output-appx-directory": { - "description": "The AppX layout directory the package was registered from. Only needed when the run used --output-appx-directory, since nothing on the package records which run option produced its layout; without it the registration looks like it came from a different tree and is skipped.", - "hidden": false, - "valueType": "System.IO.DirectoryInfo", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--property": { - "description": "MSBuild property (Name=Value) used when resolving a .cs file-based app's identity. Repeatable. Pass the same identity-affecting properties the run used (e.g. -p WinAppPackageName=...), since a command-line property overrides the file's own #:property directives. Only applies to a .cs input.", - "hidden": false, - "aliases": [ - "-p" - ], - "valueType": "System.String[]", - "hasDefaultValue": false, - "arity": { - "minimum": 0 - }, - "required": false, - "recursive": false - }, - "--prune": { - "description": "Remove every development-mode registration whose files are gone. These can never launch — Windows keeps the identity and its Start menu entry, but activation silently does nothing. Lists what it found and asks before removing; pass --force to skip the prompt. Cannot be combined with an input or --manifest.", - "hidden": false, - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--runtime": { - "description": "Target .NET runtime identifier (e.g. win-x64) used when resolving a .cs file-based app's identity. Only its architecture is used, and it overrides --arch. Only applies to a .cs input.", - "hidden": false, - "aliases": [ - "-r" - ], - "valueType": "System.String", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - }, - "update": { - "description": "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.", - "hidden": false, - "options": { - "--quiet": { - "description": "Suppress progress messages", - "hidden": false, - "aliases": [ - "-q" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--setup-sdks": { - "description": "SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation)", - "hidden": false, - "helpName": "stable|preview|experimental|none", - "valueType": "System.Nullable", - "hasDefaultValue": false, - "arity": { - "minimum": 1, - "maximum": 1 - }, - "required": false, - "recursive": false - }, - "--verbose": { - "description": "Enable verbose output", - "hidden": false, - "aliases": [ - "-v" - ], - "valueType": "System.Boolean", - "hasDefaultValue": true, - "defaultValue": false, - "arity": { - "minimum": 0, - "maximum": 1 - }, - "required": false, - "recursive": false - } - } - } - } -} diff --git a/docs/fragments/node-commands.md b/docs/fragments/node-commands.md deleted file mode 100644 index 62bc5d891..000000000 --- a/docs/fragments/node-commands.md +++ /dev/null @@ -1,83 +0,0 @@ -## Node.js CLI commands - -These commands are available exclusively via `npx winapp node ` and are not exported as programmatic functions. - -### `node create-addon` - -Generate native addon files for an Electron project. Supports C++ (node-gyp) and C# (node-api-dotnet) templates. - -```bash -npx winapp node create-addon [options] -``` - -**Options:** - -| Flag | Description | -|------|-------------| -| `--name ` | Addon name (default depends on template) | -| `--template ` | Addon template: `cpp` or `cs` (default: `cpp`) | -| `--verbose` | Enable verbose output | - -> **Note:** Must be run from the root of an Electron project (directory containing `package.json`). - -**Examples:** - -```bash -npx winapp node create-addon -npx winapp node create-addon --name myAddon -npx winapp node create-addon --template cs --name MyCsAddon -``` - ---- - -### `node add-electron-debug-identity` - -Add package identity to the Electron debug process using sparse packaging. Creates a backup of `electron.exe`, generates a sparse MSIX manifest, adds identity to the executable, and registers the sparse package. Requires a `Package.appxmanifest` (create one with `winapp init` or `winapp manifest generate`). - -```bash -npx winapp node add-electron-debug-identity [options] -``` - -**Options:** - -| Flag | Description | -|------|-------------| -| `--manifest ` | Path to custom `Package.appxmanifest` (default: `Package.appxmanifest` in current directory) | -| `--no-install` | Do not install the package after creation | -| `--keep-identity` | Keep the manifest identity as-is, without appending `.debug` suffix | -| `--verbose` | Enable verbose output | - -> **Note:** Must be run from the root of an Electron project (directory containing `node_modules/electron`). To undo, use `npx winapp node clear-electron-debug-identity`. - -**Examples:** - -```bash -npx winapp node add-electron-debug-identity -npx winapp node add-electron-debug-identity --manifest ./custom/Package.appxmanifest -``` - ---- - -### `node clear-electron-debug-identity` - -Remove package identity from the Electron debug process. Restores `electron.exe` from the backup created by `add-electron-debug-identity` and removes the backup files. - -```bash -npx winapp node clear-electron-debug-identity [options] -``` - -**Options:** - -| Flag | Description | -|------|-------------| -| `--verbose` | Enable verbose output | - -> **Note:** Must be run from the root of an Electron project (directory containing `node_modules/electron`). - -**Examples:** - -```bash -npx winapp node clear-electron-debug-identity -``` - ---- diff --git a/docs/npm-usage.md b/docs/npm-usage.md index 164b405b3..1fca2b14d 100644 --- a/docs/npm-usage.md +++ b/docs/npm-usage.md @@ -1,2612 +1,202 @@ --- ms.custom: mslearn --- - - -# NPM Package — Programmatic API +# Use winapp from TypeScript or JavaScript -TypeScript/JavaScript API reference for `@microsoft/winappcli`. -Each CLI command is available as an async function that captures stdout/stderr and returns a typed result. -Helper utilities for MSIX identity, Electron debug identity, and build tools are also exported. - -## Installation - -```bash +```powershell npm install @microsoft/winappcli ``` -## Quick start - -```typescript -import { init, packageApp, certGenerate } from '@microsoft/winappcli'; - -// Initialize a new project with defaults -await init({ useDefaults: true }); - -// Generate a dev certificate -await certGenerate({ install: true }); - -// Package the built app -await packageApp({ inputFolder: './dist', cert: './devcert.pfx' }); -``` - -## Common types - -Every CLI command wrapper accepts an options object extending `CommonOptions` and returns `Promise`. - -### `CommonOptions` - -Base options shared by most commands. - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `WinappResult` - -Result returned by every command wrapper. - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `exitCode` | `number` | Yes | Process exit code (always 0 on success – non-zero throws). | -| `stdout` | `string` | Yes | Captured standard output. | -| `stderr` | `string` | Yes | Captured standard error. | - -## CLI command wrappers - -These functions wrap native `winapp` CLI commands. All accept [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`). - -### `azSign()` - -Code-sign a file using Azure Trusted Signing. Signs executables, MSIX packages, or MSIX bundles using a cloud-managed signing identity. Example: winapp az-sign ./app.msix - -```typescript -function azSign(options: AzSignOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `filePath` | `string` | Yes | Path to the file to sign (exe, msix, or msixbundle) | -| `account` | `string \| undefined` | No | Signing account name. Must be used with --resource-group | -| `metadataFile` | `string \| undefined` | No | Path to an existing metadata.json file. Skips resource discovery and account/profile selection prompts and signs using this file directly. A non-interactive Azure credential should already be available; the CLI can otherwise fall back to an interactive tenant prompt or 'az login', but the npm programmatic API is always non-interactive and fails instead of prompting. | -| `profile` | `string \| undefined` | No | Certificate profile name. Must be used with --account | -| `resourceGroup` | `string \| undefined` | No | Resource group to narrow down signing accounts | -| `subscription` | `string \| undefined` | No | Azure subscription ID to use. If not provided and multiple subscriptions exist, you will be prompted. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `certGenerate()` - -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. - -```typescript -function certGenerate(options?: CertGenerateOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `exportCer` | `boolean \| undefined` | No | Export a .cer file (public key only) alongside the .pfx | -| `ifExists` | `IfExists \| undefined` | No | Behavior when output file exists: 'error' (fail, default), 'skip' (keep existing), or 'overwrite' (replace) | -| `install` | `boolean \| undefined` | No | Install the certificate to the local machine store after generation | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `manifest` | `string \| undefined` | No | Path to Package.appxmanifest or appxmanifest.xml file to extract publisher information from | -| `output` | `string \| undefined` | No | Output path for the generated PFX file | -| `password` | `string \| undefined` | No | Password for the generated PFX file. Defaults to 'password', which is publicly known — a certificate left with that password is development-only, because anyone who obtains the .pfx can sign as you. | -| `publisher` | `string \| undefined` | No | Publisher distinguished name (DN) for the generated certificate (e.g., CN=MyCompany or OU=Team, O=Corp, C=US). Components must be single-valued and comma-separated; multi-valued '+' RDNs, ';' separators, and backslashes are not supported. If not specified, will be inferred from manifest. Bare names are auto-wrapped as CN=. | -| `validDays` | `number \| undefined` | No | Number of days the certificate is valid | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `certInfo()` - -Display certificate details (subject, thumbprint, expiry). Useful for verifying a certificate matches your manifest before signing. - -```typescript -function certInfo(options: CertInfoOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `certPath` | `string` | Yes | Path to the certificate file (PFX or CER) | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `password` | `string \| undefined` | No | Password for the PFX file (ignored for a public CER) | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `certInstall()` - -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. - -```typescript -function certInstall(options: CertInstallOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `certPath` | `string` | Yes | Path to the certificate file (PFX or CER) | -| `force` | `boolean \| undefined` | No | Force installation even if the certificate already exists | -| `password` | `string \| undefined` | No | Password for the PFX file | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `createDebugIdentity()` - -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/. - -```typescript -function createDebugIdentity(options?: CreateDebugIdentityOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `entrypoint` | `string \| undefined` | No | Path to the .exe that will need to run with identity, or entrypoint script. | -| `keepIdentity` | `boolean \| undefined` | No | Keep the package identity from the manifest as-is, without appending '.debug' to the package name and application ID. | -| `manifest` | `string \| undefined` | No | Path to the Package.appxmanifest or appxmanifest.xml | -| `noInstall` | `boolean \| undefined` | No | Do not install the package after creation. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `createExternalCatalog()` - -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. - -```typescript -function createExternalCatalog(options: CreateExternalCatalogOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `inputFolder` | `string` | Yes | List of input folders with executable files to process (separated by semicolons) | -| `computeFlatHashes` | `boolean \| undefined` | No | Include flat hashes when generating the catalog | -| `ifExists` | `IfExists \| undefined` | No | Behavior when output file already exists | -| `output` | `string \| undefined` | No | Output catalog file path. If not specified, the default CodeIntegrityExternal.cat name is used. | -| `recursive` | `boolean \| undefined` | No | Include files from subdirectories | -| `usePageHashes` | `boolean \| undefined` | No | Include page hashes when generating the catalog | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `embedIdentity()` - -Connect a desktop exe to its sparse identity package by embedding the element. Reads identity (packageName, publisher, applicationId) from a sparse appxmanifest.xml and writes it into the target's side-by-side (fusion) manifest. EXE targets are updated with mt.exe; .xml/.manifest targets are edited directly. Example: winapp embed-identity ./bin/MyApp.exe. This is step 3 of the sparse packaging workflow (after 'winapp init --exe --sparse' and 'winapp pack'). - -```typescript -function embedIdentity(options: EmbedIdentityOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Path to the .exe (embeds identity into its side-by-side manifest via mt.exe) or an .xml/.manifest side-by-side manifest file (inserts/replaces the element; created if it doesn't exist). | -| `manifest` | `string \| undefined` | No | Path to the sparse appxmanifest.xml to read identity from. When omitted, searched in a 'sparse/' folder (where 'winapp init --exe --sparse' writes it by default) beside the target first, then in the current directory, then beside the target and in the current directory. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `findApi()` - -Agent-first: built primarily for AI coding agents to ground code generation in the API surface a project actually references instead of guessing (pair it with --json); it works just as well typed by hand. Search and inspect the Windows/WinRT API surface (types, members, enums) available to a project, resolved from its referenced .winmd/.dll metadata. The bare form searches; sub-verbs drill into a specific type or the index itself. Search, members, enums, and check-property each accept several subjects in one call — batch your lookups rather than issuing one call per question. The index is built from the project's restored NuGet/SDK packages and refreshed automatically when the project is restored. - -```typescript -function findApi(options?: FindApiOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `query` | `string \| string[] \| undefined` | No | What to search for, e.g. "acrylic brush" or "NavigationView". Matched lexically against type and member names across the project's indexed API metadata. Pass several quoted queries to run them in a single call. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `max` | `number \| undefined` | No | Maximum number of namespace-grouped results to return. | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `findApiCheckProperty()` - -Validate that one or more properties exist on a type before you write XAML/code against it. Pass several property names to check them in one call. On a miss, suggests similar properties on the type, attached-property forms, and other types that declare the property. Exits non-zero when any property does not exist. - -```typescript -function findApiCheckProperty(options?: FindApiCheckPropertyOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `type` | `string \| undefined` | No | The type to check. | -| `property` | `string \| string[] \| undefined` | No | One or more property names to validate on the type. Pass several to check them all in a single call. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `findApiEnums()` - -List the values of one or more enum types. Pass several type names to list them in one call. Exits non-zero when a type exists but is not an enum. - -```typescript -function findApiEnums(options?: FindApiEnumsOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `type` | `string \| string[] \| undefined` | No | One or more enum types to list, e.g. Symbol or Microsoft.UI.Xaml.Controls.Symbol. Pass several to list them in a single call. | -| `filter` | `string \| undefined` | No | Only list values whose name contains this text (case-insensitive), e.g. --filter folder. The unfiltered total is still reported. Prefer listing the whole enum once over repeated filtered calls — most enums are small enough that the full list is cheaper than several narrowed lookups. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `findApiMembers()` - -List the properties, events, and methods of one or more types (with XML-doc descriptions and inherited members), resolved from the project's indexed API metadata. Pass several type names to inspect them all in one call. - -```typescript -function findApiMembers(options?: FindApiMembersOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `type` | `string \| string[] \| undefined` | No | One or more types to inspect. Accepts short names (NavigationView) or fully-qualified names (Microsoft.UI.Xaml.Controls.NavigationView). Pass several to resolve them in a single call. | -| `all` | `boolean \| undefined` | No | List the complete member surface: include dependency-property identifier statics (BackgroundProperty) and per-member descriptions, both of which an unfiltered listing omits to save context. Implied by --verbose, and usable together with --json (--verbose is not). | -| `filter` | `string \| undefined` | No | Only list members whose name contains this text (case-insensitive), e.g. --filter background. Totals for the unfiltered type are still reported. Applies to every type in the call. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `findApiPackages()` - -List the NuGet/SDK packages whose API metadata is indexed for a project, with per-package type and member counts. - -```typescript -function findApiPackages(options?: FindApiPackagesOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `findApiRefresh()` - -Rebuild the API metadata index for a project from its restored packages. Runs automatically when a project is restored; run it manually to force a re-index or to index a project for the first time. - -```typescript -function findApiRefresh(options?: FindApiRefreshOptions): Promise -``` - -**Options:** +The package includes the Windows CLI and typed functions for calling it from Node.js. +Run your script on Windows. Each command function launches the CLI, waits for it to +finish, and captures its output. -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | -| `scan` | `boolean \| undefined` | No | Recursively discover and index every project under the directory instead of just the top-level project(s). | +## Initialize a project -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `findApiStats()` - -Show aggregate statistics for a project's API index: package, namespace, type, member, and .winmd file counts. - -```typescript -function findApiStats(options?: FindApiStatsOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `findUi()` - -Agent-first: built primarily for AI coding agents to pull a real WinUI sample into the editor instead of inventing markup (pair it with --json); it works just as well typed by hand. Search WinUI controls and samples for a working code example. WinUI-only: covers the WinUI 3 Gallery and the Windows Community Toolkit by default (plus the microsoft-ui-reactor ReactorGallery as an opt-in source via --source reactor); not WPF/WinForms. A corpus is baked into the CLI, so this works offline and behind proxies; when GitHub is reachable it refreshes to the latest samples and caches them per-user. +Run this script from your application's directory: ```typescript -function findUi(options?: FindUiOptions): Promise -``` - -**Options:** +import { init } from '@microsoft/winappcli'; -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `query` | `string \| undefined` | No | What you're looking for, e.g. "tabbed layout" or "color picker". Matched lexically against WinUI control names, sample headers, and tags. | -| `id` | `string \| string[] \| undefined` | No | Fetch the code (Gallery/Toolkit return XAML and/or C#; Reactor is C#-only) plus prerequisite notes for one or more scenario ids from a prior search (e.g. gallery-tabview-1). | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `list` | `boolean \| undefined` | No | List every discoverable control/sample id instead of searching. Covers Gallery, Toolkit, and core; the opt-in Reactor source is excluded (search it with --source reactor). | -| `max` | `number \| undefined` | No | Maximum number of matched controls to return. Applies to search only; ignored with --list and --id. | -| `refresh` | `boolean \| undefined` | No | Bypass the local cache and re-fetch the WinUI corpus from GitHub. | -| `source` | `string \| undefined` | No | Restrict results to a single source: gallery (WinUI 3 Gallery), toolkit (Windows Community Toolkit), reactor (microsoft-ui-reactor, C#-only declarative WinUI), or core (curated patterns). Reactor is opt-in — it is excluded from a normal search, so pass --source reactor to search it (only do this for a Reactor/MVU project; its C#-only samples don't paste into a standard XAML app). | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `getWinappPath()` - -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. - -```typescript -function getWinappPath(options?: GetWinappPathOptions): Promise +const result = await init({ useDefaults: true }); +console.log(result.stdout); ``` -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `global` | `boolean \| undefined` | No | Get the global .winapp directory instead of local | +`init` creates the manifest and assets and sets up SDK packages and projections. +After cloning a project that already has `winapp.yaml`, use `restore()` instead. +See [initialization and restore](usage.md#init) for the CLI workflow. -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* +To work in another existing directory, pass `cwd`. Paths passed to the command +are resolved in that directory, not relative to your script. ---- - -### `init()` +## Package build output -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. +Build your application first, then package its output folder: ```typescript -function init(options?: InitOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `baseDirectory` | `string \| undefined` | No | Base/root directory for the winapp workspace, for consumption or installation. | -| `configDir` | `string \| undefined` | No | Directory to read/store configuration (default: the selected project directory, or current directory if no project is detected) | -| `configOnly` | `boolean \| undefined` | No | Only handle configuration file operations (create if missing, validate if exists). Skip package installation and other workspace setup steps. | -| `exe` | `string \| undefined` | No | Path to the application executable. Requires --sparse. Generates an identity-only sparse manifest for the exe instead of a full package/SDK setup. | -| `force` | `boolean \| undefined` | No | Overwrite an existing appxmanifest.xml in the target directory (sparse only). Without this, init fails instead of replacing existing manifest/asset files. | -| `ignoreConfig` | `boolean \| undefined` | No | Don't use configuration file for version management | -| `name` | `string \| undefined` | No | Override the package name (sparse only; default: inferred from the exe) | -| `noGitignore` | `boolean \| undefined` | No | Don't update .gitignore file | -| `outputDir` | `string \| undefined` | No | Directory to write the sparse manifest and Assets/ (sparse only; default: a 'sparse/' folder in the current directory) | -| `publisher` | `string \| undefined` | No | Override the publisher CN (sparse only; default: inferred from the exe's company name). Bare names are auto-wrapped as CN=. | -| `setupSdks` | `SdkInstallMode \| undefined` | No | SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation) | -| `sparse` | `boolean \| undefined` | No | Generate a sparse identity manifest (appxmanifest.xml) for an existing desktop exe instead of a full package manifest. Use with --exe. Skips SDK/package installation. | -| `useDefaults` | `boolean \| undefined` | No | Skip interactive prompts and use default answers. Normal init targets the positional project directory if given, otherwise the current directory (e.g., winapp init . --use-defaults). Sparse init (--exe --sparse) ignores the positional directory and writes to --output-dir instead. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- +import { certGenerate, packageApp } from '@microsoft/winappcli'; -### `manifestAddAlias()` - -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). - -```typescript -function manifestAddAlias(options?: ManifestAddAliasOptions): Promise +await certGenerate({ output: './devcert.pfx' }); +const result = await packageApp({ + inputFolder: './dist', + cert: './devcert.pfx', + output: './MyApp.msix', +}); +console.log(result.stdout); ``` -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `appId` | `string \| undefined` | No | Application Id to add the alias to (default: first Application element) | -| `manifest` | `string \| undefined` | No | Path to Package.appxmanifest or appxmanifest.xml file (default: search current directory) | -| `name` | `string \| undefined` | No | Alias name (e.g. 'myapp.exe'). Default: inferred from the Executable attribute in the manifest. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `manifestGenerate()` +The manifest must describe your executable and match the signing certificate's +publisher. The generated certificate is for local development only; it uses the +publicly known default password `password`. See [packaging](usage.md#pack), +[code signing](usage.md#sign), and the [Electron guides](guides/electron/index.md) +for preparing your application's package layout and trusting a development certificate. -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). +## Find command functions and options -```typescript -function manifestGenerate(options?: ManifestGenerateOptions): Promise -``` +Use your editor's completion and the package's `.d.ts` declarations for the full +API of the version you installed. Command paths generally become camel-case +functions, and hyphenated options become camel-case properties: -**Options:** +| CLI | JavaScript/TypeScript | +|-----|-----------------------| +| `winapp init --use-defaults` | `init({ useDefaults: true })` | +| `winapp restore` | `restore()` | +| `winapp cert generate --output devcert.pfx` | `certGenerate({ output: 'devcert.pfx' })` | +| `winapp pack ./dist` | `packageApp({ inputFolder: './dist' })` | +| `winapp run MyApp.csproj` | `run({ input: 'MyApp.csproj' })` | +| `winapp ui inspect --app notepad` | `uiInspect({ app: 'notepad' })` | -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `directory` | `string \| undefined` | No | Directory to generate manifest in | -| `description` | `string \| undefined` | No | Human-readable app description shown during installation and in Windows Settings | -| `executable` | `string \| undefined` | No | Path to the application's executable. Default: .exe | -| `ifExists` | `IfExists \| undefined` | No | Behavior when output file exists: 'error' (fail, default), 'skip' (keep existing), or 'overwrite' (replace) | -| `logoPath` | `string \| undefined` | No | Path to logo image file | -| `packageName` | `string \| undefined` | No | Package name (default: folder name) | -| `publisherName` | `string \| undefined` | No | Publisher distinguished name (DN) (default: CN=). Accepts an X.500 DN with single-valued, comma-separated components (multi-valued '+' RDNs, ';' separators, and backslashes are not supported); bare names are auto-wrapped as CN=. | -| `template` | `ManifestTemplates \| undefined` | No | Manifest template type: 'packaged' (full MSIX app, default) or 'sparse' (desktop app with package identity for Windows APIs) | -| `version` | `string \| undefined` | No | App version in Major.Minor.Build.Revision format (e.g., 1.0.0.0). | +`packageApp` is the function for packaging; it is not named `pack` or `package`. +See [CLI usage](usage.md) for each command's behavior, prerequisites, and examples. +Use `npx winapp --help` for exact flags or `npx winapp --cli-schema` +for the installed CLI's complete machine-readable command tree. -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* +Every command's options object also accepts these shared properties: ---- +| Property | Use | +|----------|-----| +| `cwd` | Run in a specified existing directory. Defaults to the current working directory. | +| `quiet` | Suppress progress messages. | +| `verbose` | Include diagnostic output. | +| `signal` | Cancel the native process with an `AbortSignal`. | +| `workflowId` | Group related UI calls into one desktop workflow. | -### `manifestUpdateAssets()` +## Read results and handle failures -Generate new assets for images referenced in a Package.appxmanifest from a single source image. Source image should be at least 400x400 pixels. +Command functions return a promise containing `exitCode`, `stdout`, and `stderr`. +Successful calls have `exitCode: 0`. A nonzero CLI exit rejects the promise with +an `Error` carrying those same three properties; launch failures reject with an +error message, and cancellation rejects with an `AbortError`. ```typescript -function manifestUpdateAssets(options: ManifestUpdateAssetsOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `imagePath` | `string` | Yes | Path to source image file (SVG, PNG, ICO, JPG, BMP, GIF) | -| `lightImage` | `string \| undefined` | No | Path to source image for light theme variants (SVG, PNG, ICO, JPG, BMP, GIF) | -| `manifest` | `string \| undefined` | No | Path to Package.appxmanifest or appxmanifest.xml file (default: search current directory) | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `newCommand()` +import { packageApp } from '@microsoft/winappcli'; -Create a new WinUI app from an official Windows App SDK template. Templates cover both markup-based XAML apps (blank, NavigationView, TabView, MVVM) and the experimental Reactor apps (C#-only, MVU) — pick one interactively, then a name (the output directory defaults to ./). Automatically uses defaults in non-interactive environments (use --use-defaults to skip prompts explicitly). Requires the .NET SDK; installs the WinUI template pack on demand (grabbing the latest, or offering to update a stale one) and delegates scaffolding to 'dotnet new'. Use --list to see the available templates. Scaffolds against the installed SDK's target framework and prints a template-specific next step when done (e.g. 'dotnet run' for app templates). - -```typescript -function newCommand(options?: NewOptions): Promise +try { + const result = await packageApp({ inputFolder: './dist', noSign: true }); + console.log(result.stdout); +} catch (error) { + if (error instanceof Error) { + console.error(error.message); + } + throw error; +} ``` -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `force` | `boolean \| undefined` | No | Scaffold even if the output directory already contains files. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `list` | `boolean \| undefined` | No | List the available WinUI templates and exit (installs the latest template pack if none is installed). | -| `name` | `string \| undefined` | No | Name for the new app/project (default: derived from --output, else 'WinUIApp'). | -| `output` | `string \| undefined` | No | Directory to create the app in (default: ./). Created if it doesn't exist. | -| `template` | `string \| undefined` | No | Template short name. XAML templates: winui, winui-navview, winui-tabview, winui-mvvm, winui-lib, winui-unittest. Experimental Reactor (C#-only, MVU) templates: reactor, reactor-mvu, reactor-navview, reactor-tabview. Run 'winapp new --list' to see all. | -| `templateVersion` | `string \| undefined` | No | WinUI template pack version: 'latest' (install newest), 'installed' (keep what's installed), or an explicit version. Default: install latest if none, else prompt to update a stale pack. | -| `useDefaults` | `boolean \| undefined` | No | Do not prompt; use defaults (blank template, name from --output/--name, keep installed templates). | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `packageApp()` - -Create an MSIX installer from a built app folder or directly from a .csproj. Pass a package-layout folder (run after building your app; a manifest must be in the current directory, passed as --manifest, or in the input folder), or pass a .csproj to build and package it in one step (e.g. winapp package ./MyApp.csproj -c Release). Use --cert devcert.pfx to sign for testing. +If a call fails, inspect its error message and captured output, correct the +reported path, manifest, signing, or prerequisite problem, and retry. +The wrapper captures text; it does not automatically parse JSON output. +For commands that support `json`, request it and parse `stdout`: ```typescript -function packageApp(options: PackageOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `inputFolder` | `string \| string[]` | Yes | A single .csproj to build and package (project mode), one or more input folders with package layout, or a single sparse appxmanifest.xml file (an identity-only package with AllowExternalContent). Pass multiple folders to create an MSIX bundle (e.g., winapp pack ./publish/x64 ./publish/arm64). | -| `arch` | `string \| string[] \| undefined` | No | Project mode: target architecture (x64, arm64, or x86). Repeatable — pass two or more to publish each and produce one architecture .msixbundle. Requires a .csproj input; rejected for folder/bundle/manifest inputs. Default: the current process architecture. | -| `cert` | `string \| undefined` | No | Path to signing certificate (will auto-sign if provided) | -| `certPassword` | `string \| undefined` | No | Certificate password (default: password) | -| `configuration` | `string \| undefined` | No | Project mode: build configuration (e.g., Debug, Release). Requires a .csproj input; rejected for folder/bundle/manifest inputs. Default: Release. | -| `executable` | `string \| undefined` | No | Path to the executable relative to the input folder. | -| `framework` | `string \| undefined` | No | Project mode: target framework moniker for multi-targeted projects (e.g. net10.0-windows10.0.26100.0). Requires a .csproj input; rejected for folder/bundle/manifest inputs. | -| `generateCert` | `boolean \| undefined` | No | Generate a new development certificate | -| `installCert` | `boolean \| undefined` | No | Install certificate to machine | -| `manifest` | `string \| undefined` | No | Path to AppX manifest file (default: auto-detect from input folder or current directory) | -| `name` | `string \| undefined` | No | Package name (default: from manifest) | -| `noBuild` | `boolean \| undefined` | No | Project mode: skip building and package the existing build output (still evaluates output properties). Requires a .csproj input; rejected for folder/bundle/manifest inputs. | -| `noRestore` | `boolean \| undefined` | No | Project mode: skip restoring the project before building. Requires a .csproj input; rejected for folder/bundle/manifest inputs. | -| `noSign` | `boolean \| undefined` | No | Deliver the package unsigned, overriding any project signing configuration (e.g. for Store submission or an external signing pipeline). Cannot be combined with --cert or --generate-cert. | -| `output` | `string \| undefined` | No | Output file name for the generated package (.msix) or bundle (.msixbundle). Defaults to __.msix for single packages, or ___.msixbundle for bundles. | -| `property` | `string \| string[] \| undefined` | No | Project mode: MSBuild property as Name=Value, forwarded to both build and evaluation. Repeatable (e.g. -p WindowsPackageType=None). Use -c for configuration, -f for framework, and --arch for architecture; a -p Configuration/TargetFramework is dropped in favor of those flags, while a lone -p RuntimeIdentifier (no --arch) selects an exact RID. Requires a .csproj input; rejected for folder/bundle/manifest inputs. | -| `publisher` | `string \| undefined` | No | Publisher distinguished name (DN) for certificate generation (e.g., CN=MyCompany). Bare names are auto-wrapped as CN=. | -| `selfContained` | `boolean \| undefined` | No | Bundle Windows App SDK runtime for self-contained deployment | -| `skipPri` | `boolean \| undefined` | No | Skip PRI file generation | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- +import { uiInspect } from '@microsoft/winappcli'; -### `restore()` - -Use after cloning a repo or when .winapp/ folder is missing. Reinstalls SDK packages without changing versions, reading them from winapp.yaml or, for a .NET project initialized by 'init', from the .csproj via 'dotnet restore'. Requires a project already initialized by 'init'. To check for newer SDK versions, use 'update' instead. - -```typescript -function restore(options?: RestoreOptions): Promise +const result = await uiInspect({ app: 'notepad', json: true }); +const tree: unknown = JSON.parse(result.stdout); +console.log(tree); ``` -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `baseDirectory` | `string \| undefined` | No | Base/root directory for the winapp workspace | -| `configDir` | `string \| undefined` | No | Directory to read configuration from (default: base-directory) | +Open the target application before inspecting it. -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* +### Calls are non-interactive ---- - -### `run()` +Command wrappers capture output and use piped stdin, so commands cannot ask for +interactive input. Supply the options and credentials your command needs. +`init` and `newCommand` support defaults; other commands may fail if a required +selection is missing. For Azure signing, supply `metadataFile` or all of +`subscription`, `resourceGroup`, `account`, and `profile`, and authenticate +non-interactively beforehand. See [Azure signing](usage.md#az-sign). -Builds or Native AOT-publishes and runs a Windows app from a .cs file-based app, a .csproj/.sln, or a build-output folder. In project mode, invokes dotnet build — or the project's configured Native AOT publish with --aot — then launches the app (packaged or unpackaged); in single-file mode, builds the .cs and launches it, generating a manifest from its #:property directives when the app is packaged; in folder mode, creates a debug-signed layout, registers the package, and launches it. +## Cancel a call ```typescript -function run(options?: RunOptions): Promise -``` - -**Options:** +import { uiInspect } from '@microsoft/winappcli'; -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `input` | `string \| undefined` | No | Path to the app to run: a build-output folder, a .cs .NET file-based app, a .csproj project, a .sln/.slnx solution, or a directory containing one of those at its top level (default: current directory). | -| `inputFolder` | `string \| undefined` | No | | -| `aot` | `boolean \| undefined` | No | Project mode: run the project's configured .NET Native AOT publish. Requires effective PublishAot=true. | -| `arch` | `string \| undefined` | No | Project mode: target architecture (x64, arm64, or x86). Sets the canonical Windows RID and selects a matching platform-dependent publish profile when required by the effective build. Ignored in folder mode. Honored for a .cs file-based app too; when omitted, winapp builds for the current process architecture. Default: the current process architecture. | -| `args` | `string \| undefined` | No | Command-line arguments to pass to the application. Alternatively, use -- followed by arguments to avoid escaping (e.g., winapp run . -- --flag value). | -| `clean` | `boolean \| undefined` | No | Remove the existing package's application data (LocalState, settings, etc.) before re-deploying. By default, application data is preserved across re-deployments. | -| `configuration` | `string \| undefined` | No | Project and single-file mode: build configuration (e.g., Debug, Release). Ignored in folder mode. Default: Debug. | -| `debugOutput` | `boolean \| undefined` | No | 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. For WinUI apps, a crash also triggers a stowed-exception triage pass; the first run downloads debugger components (cached under the winapp global directory) and can be pointed at an existing debugger install via the WINAPP_DBGTOOLS_DIR environment variable. Cannot be combined with --no-launch or --json. | -| `detach` | `boolean \| undefined` | No | 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. Local runs print the PID; target runs print the scoped UI target. JSON includes the PID and target scope. | -| `executable` | `string \| undefined` | No | 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. | -| `framework` | `string \| undefined` | No | Project mode: target framework moniker for multi-targeted projects (e.g. net10.0-windows10.0.26100.0). Ignored in folder mode. Rejected for a .cs file-based app, which declares its own with '#:property TargetFramework=...'. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `manifest` | `string \| undefined` | No | Path to the Package.appxmanifest (default: auto-detect from input folder or current directory) | -| `noBuild` | `boolean \| undefined` | No | Project and single-file mode: skip building and run the existing build output (still evaluates output properties). Ignored in folder mode. | -| `noLaunch` | `boolean \| undefined` | No | Only create the debug identity and register the package without launching the application | -| `noRestore` | `boolean \| undefined` | No | Project and single-file mode: skip restoring before build or Native AOT publish. Ignored in folder mode. | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `outputAppxDirectory` | `string \| undefined` | No | Output directory for the loose layout package. If not specified, a directory named AppX inside the input directory will be used. | -| `project` | `string \| undefined` | No | Project mode: when the input is a solution (.sln/.slnx) or a directory with multiple runnable app projects, selects which project to launch (by name or path). Ignored in folder mode. Rejected for a .cs file-based app, which is itself the project. | -| `property` | `string \| string[] \| undefined` | No | Project and single-file mode: MSBuild property as Name=Value, forwarded to both build and evaluation. Repeatable. Ignored in folder mode. | -| `runtime` | `string \| undefined` | No | Project mode: target .NET runtime identifier (RID), e.g. win-x64. Project mode uses only the RID's architecture, always builds the canonical win-, rejects non-Windows RIDs (e.g. linux-x64), and can select a required architecture-dependent publish profile; it overrides --arch. Ignored in folder mode. Honored for a .cs file-based app too. | -| `symbols` | `boolean \| undefined` | No | Download symbols from Microsoft Symbol Server for richer native crash analysis, including the WinUI stowed-exception dispatch stack. Only used with --debug-output. First run downloads symbols and caches them locally; subsequent runs use the cache. | -| `unregisterOnExit` | `boolean \| undefined` | No | Unregister the development package after the application exits. Only removes packages registered in development mode. | -| `withAlias` | `boolean \| undefined` | No | Launch the app using its execution alias instead of AUMID activation. The app runs in the current terminal with inherited stdin/stdout/stderr. Console apps (OutputType=Exe) already do this by default; pass this to force it for a windowed app. winapp adds a uap5:ExecutionAlias to the manifest it stages for you, so no manifest edit is needed. | -| `withoutAlias` | `boolean \| undefined` | No | Launch via AUMID activation even for a console app, instead of the default execution alias. The app then runs without a console, so it prints nothing to this terminal. | -| `appArgs` | `string \| string[] \| undefined` | No | Arguments to pass to the launched application (forwarded after --). | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `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. - -```typescript -function sign(options: SignOptions): Promise +const controller = new AbortController(); +const timeout = setTimeout(() => controller.abort(), 30_000); +try { + await uiInspect({ app: 'notepad', signal: controller.signal }); +} catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + console.error('The inspection was cancelled.'); + } + throw error; +} finally { + clearTimeout(timeout); +} ``` -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `filePath` | `string` | Yes | Path to the file/package to sign | -| `certPath` | `string` | Yes | Path to the certificate file (PFX format) | -| `password` | `string \| undefined` | No | Certificate password | -| `timestamp` | `string \| undefined` | No | Timestamp server URL | +Cancellation stops the whole native invocation, including a wait for the desktop. +On Windows it force-terminates the process, so cleanup may not run and completed +UI actions are not undone. Cancelling a recording can leave an incomplete video. -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* +## Coordinate UI calls and record a bounded video ---- - -### `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 +Desktop-sensitive commands coordinate automatically, even without `workflowId`. +Pass the same ID to related calls when they should retain the desktop between +commands. Give each independent workflow its own ID. ```typescript -function store(options?: StoreOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `storeArgs` | `string \| string[] \| undefined` | No | Arguments to pass through to the Microsoft Store Developer CLI. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `targetExec()` +import { randomUUID } from 'node:crypto'; +import { uiInspect, uiYield } from '@microsoft/winappcli'; -Run a command on an execution target, as that target's interactive user. Streams stdin, stdout, and stderr, and returns the command's own exit code. Does not provide a full terminal, so interactive console applications may see redirected pipes. - -```typescript -function targetExec(options: TargetExecOptions): Promise +const workflowId = randomUUID(); +try { + const result = await uiInspect({ app: 'notepad', workflowId }); + console.log(result.stdout); + // Use selectors from this inspection for subsequent UI calls with the same ID. +} finally { + await uiYield({ workflowId }); +} ``` -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Execution target to act on. Currently: 'sandbox'. | -| `targetCwd` | `string \| undefined` | No | Working directory on the target. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `command` | `string \| string[] \| undefined` | No | Executable and arguments to run on the target, e.g. ['dotnet', '--info'] (forwarded after --). | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `targetPull()` +`workflowId` is passed only to the child process; it does not change `process.env`. +See [coordinating concurrent UI workflows](ui-automation.md#coordinating-concurrent-ui-workflows) +for how desktop turns are shared. -Copy files or directories from an execution target to this machine. Directory structure and useful timestamps are preserved, unchanged files are skipped, and changed files are replaced atomically. +`uiRecord` and `targetRecord` require a finite, positive `durationSec`: ```typescript -function targetPull(options: TargetPullOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Execution target to act on. Currently: 'sandbox'. | -| `source` | `string` | Yes | File or directory on the target to copy, relative to its managed work area. | -| `destination` | `string` | Yes | Destination path on this machine. | -| `json` | `boolean \| undefined` | No | Format output as JSON | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `targetPush()` +import { uiRecord } from '@microsoft/winappcli'; -Copy files or directories from this machine to an execution target. Directory structure and useful timestamps are preserved, unchanged files are skipped, and changed files are replaced atomically. - -```typescript -function targetPush(options: TargetPushOptions): Promise +await uiRecord({ + app: 'notepad', + durationSec: 5, + output: './notepad.mp4', +}); ``` -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Execution target to act on. Currently: 'sandbox'. | -| `source` | `string` | Yes | File or directory on this machine to copy. | -| `destination` | `string` | Yes | Destination path on the target, relative to its managed work area. | -| `json` | `boolean \| undefined` | No | Format output as JSON | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `targetScreenshot()` - -Capture an execution target's entire desktop at its native pixel size. Saves a PNG on this machine without activating a host or guest window. JSON includes the guest screen origin and pixel-coordinate mapping. - -```typescript -function targetScreenshot(options: TargetScreenshotOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Execution target to act on. Currently: 'sandbox'. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `output` | `string \| undefined` | No | Save output to this file path. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `targetSnapshot()` - -Report an execution target's readiness, capabilities, deployments, and top-level guest windows. Inspects only: never starts, connects, or repairs a target, and reports plainly when none is running. Writes only to stdout: no screenshots and no files. - -```typescript -function targetSnapshot(options: TargetSnapshotOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Execution target to act on. Currently: 'sandbox'. | -| `json` | `boolean \| undefined` | No | Format output as JSON | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `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 - -```typescript -function tool(options?: ToolOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `toolArgs` | `string \| string[] \| undefined` | No | Arguments to pass to the SDK tool, e.g. ['makeappx', 'pack', '/d', './folder', '/p', './out.msix']. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiClick()` - -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. - -```typescript -function uiClick(options?: UiClickOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `double` | `boolean \| undefined` | No | Perform a double-click instead of a single click | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `right` | `boolean \| undefined` | No | Perform a right-click instead of a left click | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiDrag()` - -Press the mouse button at one point, move to another, then release. 'drag ', where / are each an element selector (uses the element's center) or screen x,y coordinates as reported by 'ui inspect'. Useful for reorder/resize/slider gestures and drag-and-drop. Use --right for a right-button drag, --hold-ms for press-and-hold/long-press, and --dwell-ms to settle on a drop target before releasing. - -```typescript -function uiDrag(options?: UiDragOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `from` | `string \| undefined` | No | Start point — an element selector (drags from its center) or screen coordinates x,y as reported by 'ui inspect' (e.g. pn-list-d736 or 100,200). | -| `to` | `string \| undefined` | No | End point — an element selector (drops at its center) or screen coordinates x,y as reported by 'ui inspect' (e.g. pn-target-d746 or 300,400). | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `dwellMs` | `number \| undefined` | No | Milliseconds to dwell at the destination after moving, before releasing (default: 0). Lets drop targets / merge overlays that arm from a sustained hover latch before release. | -| `holdMs` | `number \| undefined` | No | Milliseconds to hold the button down at the start before moving (default: 0). With == (no movement) this performs a press-and-hold / long-press gesture. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `right` | `boolean \| undefined` | No | Drag with the right mouse button instead of the left button | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiFocus()` - -Activate the specified element's window, focus the element, and verify foreground and keyboard focus. Fails if Windows refuses activation or focus cannot be confirmed. - -```typescript -function uiFocus(options: UiFocusOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string` | Yes | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiGetFocused()` - -Show the element that currently has keyboard focus in the target app. With -w, focus must belong to that exact top-level window; owned popups are excluded. - -```typescript -function uiGetFocused(options?: UiGetFocusedOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiGetProperty()` - -Read UIA property values from an element. Specify --property for a single property or omit for all. Includes whole-document TextPattern formatting: FontWeight, FontName, FontSize, ForegroundColor, IsItalic, StrikethroughStyle. - -```typescript -function uiGetProperty(options?: UiGetPropertyOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `className` | `string \| undefined` | No | Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `property` | `string \| undefined` | No | Property name to read or filter on | -| `root` | `string \| undefined` | No | Search only descendants of this uniquely matching selector (excludes the root). | -| `type` | `string \| undefined` | No | UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiGetValue()` - -Read the current value from an element. Tries TextPattern (RichEditBox, Document), ValuePattern (TextBox, ComboBox, Slider), then Name (labels). Usage: winapp ui get-value -a - -```typescript -function uiGetValue(options?: UiGetValueOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `className` | `string \| undefined` | No | Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `root` | `string \| undefined` | No | Search only descendants of this uniquely matching selector (excludes the root). | -| `type` | `string \| undefined` | No | UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiHover()` - -Move the mouse to an element's center to trigger hover effects (tooltips, flyouts, visual states). Uses SendInput for realistic mouse movement and waits for a configurable dwell time. - -```typescript -function uiHover(options?: UiHoverOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `dwellTime` | `number \| undefined` | No | Time in milliseconds to wait after hovering for hover effects to appear (default: 800) | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiInspect()` - -View the UI element tree with semantic slugs, element types, names, and bounds. - -```typescript -function uiInspect(options?: UiInspectOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `ancestors` | `boolean \| undefined` | No | Walk up the tree from the specified element to the root | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `depth` | `number \| undefined` | No | Tree inspection depth | -| `hideDisabled` | `boolean \| undefined` | No | Hide disabled elements from output | -| `hideOffscreen` | `boolean \| undefined` | No | Hide offscreen elements from output | -| `interactive` | `boolean \| undefined` | No | Show only interactive/invokable elements (buttons, links, inputs, list items). Increases default depth to 8. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiInvoke()` - -Activate an element by slug or text search. Without --action, tries InvokePattern, TogglePattern, SelectionItemPattern, and ExpandCollapsePattern in order, then an invokable ancestor. Use --action for an exact operation on only the selected element. - -```typescript -function uiInvoke(options?: UiInvokeOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `action` | `string \| undefined` | No | Perform exactly this action on the selected element, without pattern or ancestor fallback: invoke, select, toggle, toggle-on, toggle-off, expand, collapse. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiListWindows()` - -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. - -```typescript -function uiListWindows(options?: UiListWindowsOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `showHidden` | `boolean \| undefined` | No | Include untitled zero-size windows that are hidden by default | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiPen()` - -Inject synthetic pen/stylus input using the Windows synthetic-pointer API. Taps or draws ink strokes with configurable pressure, tilt and eraser mode, at an element's center or explicit screen x,y coordinates. Requires an unlocked, interactive desktop with the target window foregroundable (Windows 10 1809+). - -```typescript -function uiPen(options?: UiPenOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `at` | `string \| undefined` | No | Pen contact point as screen coordinates x,y (as reported by 'ui inspect'). Defaults to the selector's element center. Ignored when --path is given. | -| `durationMs` | `number \| undefined` | No | Total glide time in milliseconds distributed across the stroke path segments (default: ~10 ms per segment). | -| `eraser` | `boolean \| undefined` | No | Use the eraser end of the pen instead of the tip. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `path` | `string \| undefined` | No | Ink stroke path as a whitespace-separated list of x,y pairs, e.g. "10,10 20,30 40,50". | -| `pressure` | `number \| undefined` | No | Pen pressure from 0.0 to 1.0 (default: 0.5). | -| `tiltX` | `number \| undefined` | No | Pen tilt along the x-axis in degrees (-90 to 90, default: 0). | -| `tiltY` | `number \| undefined` | No | Pen tilt along the y-axis in degrees (-90 to 90, default: 0). | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiScreenshot()` - -Capture the target window or element as a PNG image. Without an element selector, combines multiple windows into one labeled composite: --app by process name or PID includes the app's windows and their owned windows; a title match or --window selects one window plus its owned windows. With --json, returns file path and dimensions. Use --capture-screen with --window to capture one screen region, including visible overlays in place. - -```typescript -function uiScreenshot(options?: UiScreenshotOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `captureScreen` | `boolean \| undefined` | No | Capture from screen DC via BitBlt (includes popups/overlays not owned by the target). | -| `focus` | `boolean \| undefined` | No | Bring the target window to the foreground before capture. Already implied by --capture-screen. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `output` | `string \| undefined` | No | Save output to this file path. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiScroll()` - -Scroll a container element using ScrollPattern. Use --direction to scroll incrementally, --to to jump to top/bottom, or --wheel to synthesize mouse-wheel input. - -```typescript -function uiScroll(options?: UiScrollOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `direction` | `string \| undefined` | No | Scroll direction: up, down, left, right | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `to` | `string \| undefined` | No | Scroll to position: top, bottom | -| `wheel` | `number \| undefined` | No | Rotate the mouse wheel over the element by this many notches (1 = one notch up, -1 = one notch down). Synthesizes real wheel input instead of using ScrollPattern. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiScrollIntoView()` - -Scroll the specified element into the visible area using UIA ScrollItemPattern. - -```typescript -function uiScrollIntoView(options?: UiScrollIntoViewOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiSearch()` - -Search the element tree for elements matching a text query. Returns all matches with semantic slugs. - -```typescript -function uiSearch(options?: UiSearchOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `className` | `string \| undefined` | No | Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `max` | `number \| undefined` | No | Maximum search results | -| `root` | `string \| undefined` | No | Search only descendants of this uniquely matching selector (excludes the root). | -| `type` | `string \| undefined` | No | UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiSendKeys()` - -Send synthetic keyboard input to a window. Supports named keys (down, enter, tab), modifier combos (ctrl+shift+t), raw virtual keys (vk=0xNN), and literal text. Use --verbatim to type the whole argument literally, or --target to focus an element first. Two transports via --via: post-message (default, HWND-targeted, bypasses UIPI) or send-input (OS-wide). For per-keystroke KeyDown on typed text (e.g. a WinUI 3/WPF TextBox), use --via send-input. - -```typescript -function uiSendKeys(options?: UiSendKeysOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `keys` | `string \| undefined` | No | Keys to send. Whitespace-separated tokens: named keys (down, enter, tab, esc, f5), modifier combos (ctrl+shift+t, alt+f4), raw virtual keys (vk=0x42), or literal text (hello). Hold capslock or insert for screen-reader commands (ctrl+capslock+f12 toggles Narrator developer mode); these require --via send-input. Use text= to type a single value verbatim when it would otherwise be read as a key name or combo (text=enter types "enter"; text=ctrl+a types "ctrl+a"); backslash escapes \\s \\t \\n \\r \\\\ are supported (text=a\\s\\sb types "a b"). To type the whole argument literally without escaping each token, pass --verbatim instead. Quote multi-token strings, e.g. "ctrl+a delete". | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `allowSystemKeys` | `boolean \| undefined` | No | Allow synthesizing system-/shell-reserved combos (win+, alt+f4, alt+tab, ctrl+esc, …) via --via send-input, which are refused by default because they act on the OS/shell beyond the target app. Opt in to drive global hotkeys (e.g. PowerToys' win+shift+v, win+r). No effect on --via post-message (already window-scoped; a warning is emitted if set without send-input). Note: win+l and ctrl+alt+del stay blocked even with this flag — win+l locks the workstation (LockWorkStation() via the shell hook), which is unrecoverable from automation, and ctrl+alt+del is a Secure Attention Sequence (SAS) that Windows drops from injected input regardless of this flag, so it can never take effect. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `target` | `string \| undefined` | No | Optional selector (slug or text) to focus before sending keys. | -| `verbatim` | `boolean \| undefined` | No | Type the entire keys argument as literal text — no named-key, combo, or vk= interpretation, and exact whitespace preserved. The whole-argument form of the per-token text= escape: --verbatim "down down enter" types the words instead of pressing Down, Down, Enter. | -| `via` | `string \| undefined` | No | Transport: post-message (default, HWND-targeted, bypasses UIPI; typed text raises TextChanged but not a per-character KeyDown) or send-input (OS-wide; typed text raises a real per-character KeyDown + TextChanged). Named keys and combos raise KeyDown on both, but keyboard accelerators/shortcuts (KeyboardAccelerator, e.g. ctrl+t) only fire via send-input. post-message targets the focused child control and works for classic Win32/WinForms controls, but WinUI 3 / UWP / XAML controls are windowless and ignore posted messages — use send-input for those (a warning is emitted when the target looks like a XAML app). | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiSetValue()` - -Set a value on an element programmatically. Works for TextBox, ComboBox, Slider, and other editable controls via UIA ValuePattern/RangeValuePattern, with a LegacyIAccessible (put_accValue) fallback for TextPattern-only edit controls — no app foreground required. Some rich text controls (e.g. WinUI 3 RichEditBox and WPF RichTextBox) don't support setting their value programmatically — use the 'send-keys' command with '--via send-input' to type into them instead. Usage: winapp ui set-value -a - -```typescript -function uiSetValue(options?: UiSetValueOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `value` | `string \| undefined` | No | Value to set (text for TextBox/ComboBox, number for Slider) | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiStatus()` - -Connect to a target app and display connection info. - -```typescript -function uiStatus(options?: UiStatusOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiTouch()` - -Inject synthetic touch input using the Windows touch-injection API. Supports tap, double-tap, long-press, swipe, pinch and stretch gestures at an element's center or explicit screen x,y coordinates. Requires an unlocked, interactive desktop with the target window foregroundable. - -```typescript -function uiTouch(options?: UiTouchOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `at` | `string \| undefined` | No | Explicit start point as screen coordinates x,y (as reported by 'ui inspect'). Defaults to the selector's element center. | -| `direction` | `string \| undefined` | No | Swipe direction: right (default), left, up, or down. Combined with --distance to compute the end point when --to-point is not given. | -| `distance` | `number \| undefined` | No | Distance in pixels for pinch/stretch (finger spread) or swipe. | -| `durationMs` | `number \| undefined` | No | Glide time in milliseconds for moving gestures (swipe/pinch/stretch). | -| `fingers` | `number \| undefined` | No | Number of touch contacts (default: 1). Pinch/stretch always use 2. | -| `gesture` | `string \| undefined` | No | Gesture to perform: tap, double-tap, long-press, swipe, pinch, stretch (default: tap). | -| `holdMs` | `number \| undefined` | No | Milliseconds to hold contacts down before lifting (long-press hold time). Defaults to 500 ms when --gesture long-press is used and this option is not set. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `toPoint` | `string \| undefined` | No | End point x,y for a swipe (screen coordinates). Takes precedence over --direction. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiWaitFor()` - -Wait for an element to appear, disappear, or have a property reach a target value. Polls at 100ms intervals until condition met or timeout. - -```typescript -function uiWaitFor(options?: UiWaitForOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `className` | `string \| undefined` | No | Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). | -| `contains` | `boolean \| undefined` | No | Use substring matching for --value instead of exact match | -| `gone` | `boolean \| undefined` | No | Wait for element to disappear instead of appear | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `property` | `string \| undefined` | No | Property name to read or filter on | -| `root` | `string \| undefined` | No | Search only descendants of this uniquely matching selector (excludes the root). | -| `timeout` | `number \| undefined` | No | Timeout in milliseconds | -| `type` | `string \| undefined` | No | UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. | -| `value` | `string \| undefined` | No | Wait for element value to equal this string. Uses smart fallback (TextPattern -> ValuePattern -> Name). Combine with --property to check a specific property instead. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `uiYield()` - -Release the current workflow's idle UI turn early. A workflow with WINAPP_UI_WORKFLOW_ID keeps the desktop for a few seconds after each command so a burst of commands reads as one workflow; run this after the final command of a workflow to hand the desktop to waiting workflows straight away. Requires WINAPP_UI_WORKFLOW_ID; targets no app and takes no selector. - -```typescript -function uiYield(options?: UiYieldOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `json` | `boolean \| undefined` | No | Format output as JSON | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `unregister()` - -Unregisters a sideloaded development package. Only removes packages registered in development mode (e.g., via 'winapp run' or 'create-debug-identity'). - -```typescript -function unregister(options?: UnregisterOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `input` | `string \| undefined` | No | Path to a .NET file-based app (a single .cs) whose package should be unregistered. Its identity is resolved the same way 'winapp run' resolves it, so no manifest path is needed. Omit to use --manifest or auto-detect a manifest in the current directory. Cannot be combined with --manifest. | -| `arch` | `string \| undefined` | No | Target architecture (x64, arm64, x86) used when resolving a .cs file-based app's identity (default: the current process architecture). Pass the same architecture the run used, since a Directory.Build.props can key identity off $(RuntimeIdentifier). Only applies to a .cs input. | -| `configuration` | `string \| undefined` | No | Build configuration used when resolving a .cs file-based app's identity (default: Debug). Pass the same configuration the run used: a Directory.Build.props beside the .cs can set WinAppPackageName or WinAppManifestPath conditionally on $(Configuration). Only applies to a .cs input. | -| `force` | `boolean \| undefined` | No | Skip the install-location directory check and unregister even if the package was registered from a different project tree. Candidates are matched by Identity/@Name alone, so with --force a same-named package from a different publisher is also removed, along with its application data — prefer --prune for registrations whose files are gone. With --prune, also skips the confirmation prompt. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `manifest` | `string \| undefined` | No | Path to the Package.appxmanifest (default: auto-detect from current directory) | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `outputAppxDirectory` | `string \| undefined` | No | The AppX layout directory the package was registered from. Only needed when the run used --output-appx-directory, since nothing on the package records which run option produced its layout; without it the registration looks like it came from a different tree and is skipped. | -| `property` | `string \| string[] \| undefined` | No | MSBuild property (Name=Value) used when resolving a .cs file-based app's identity. Repeatable. Pass the same identity-affecting properties the run used (e.g. -p WinAppPackageName=...), since a command-line property overrides the file's own #:property directives. Only applies to a .cs input. | -| `prune` | `boolean \| undefined` | No | Remove every development-mode registration whose files are gone. These can never launch — Windows keeps the identity and its Start menu entry, but activation silently does nothing. Lists what it found and asks before removing; pass --force to skip the prompt. Cannot be combined with an input or --manifest. | -| `runtime` | `string \| undefined` | No | Target .NET runtime identifier (e.g. win-x64) used when resolving a .cs file-based app's identity. Only its architecture is used, and it overrides --arch. Only applies to a .cs input. | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -### `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. - -```typescript -function update(options?: UpdateOptions): Promise -``` - -**Options:** - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `setupSdks` | `SdkInstallMode \| undefined` | No | SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation) | - -*Also accepts [CommonOptions](#commonoptions) (`quiet`, `verbose`, `cwd`, `signal`, `workflowId`).* - ---- - -## Utility functions - -### `uiRecord()` - -Record a window or element region to an H.264 MP4. - -**`durationSec` is required and must be > 0.** Unbounded recording (durationSec == 0) is only -supported via the CLI with Ctrl+C or piped stdin. The npm wrapper has no mechanism to stop -an unbounded spawn, so passing durationSec == 0 or omitting it will throw a clear error. -Set `frames` to write timestamped JPEG evidence beside the MP4. - -```typescript -function uiRecord(options: UiRecordOptions): Promise -``` - -**Parameters:** - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `options` | `UiRecordOptions` | Yes | | - ---- - -### `targetRecord()` - -Record an execution target's entire desktop to an H.264 MP4 on this machine. - -**`durationSec` is required and must be > 0.** Unbounded recording (`durationSec == 0`) is only -supported from the CLI, where Ctrl+C or closing redirected stdin ends it; this wrapper has no -graceful stop channel. Aborting can leave partial output. -Set `frames` to write timestamped JPEG evidence beside the MP4. - -```typescript -function targetRecord(options: TargetRecordOptions): Promise -``` - -**Parameters:** - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `options` | `TargetRecordOptions` | Yes | | - ---- - -### `execWithBuildTools()` - -Execute a command with BuildTools bin path added to PATH environment - -```typescript -function execWithBuildTools(command: string, options?: ExecSyncOptions): string | Buffer -``` - -**Parameters:** - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `command` | `string` | Yes | The command to execute | -| `options` | `ExecSyncOptions` | No | Options to pass to execSync (optional) | - -**Returns:** The output from execSync - ---- - -### `addMsixIdentityToExe()` - -Adds package identity information from a Package.appxmanifest or appxmanifest.xml file to an executable's embedded manifest - -```typescript -function addMsixIdentityToExe(exePath: string, appxManifestPath?: string | undefined, options?: MsixIdentityOptions): Promise -``` - -**Parameters:** - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `exePath` | `string` | Yes | Path to the executable file | -| `appxManifestPath` | `string \| undefined` | No | Path to the Package.appxmanifest or appxmanifest.xml file containing package identity data | -| `options` | `MsixIdentityOptions` | No | Optional configuration | - ---- - -### `addElectronDebugIdentity()` - -Adds package identity to the Electron debug process - -```typescript -function addElectronDebugIdentity(options?: MsixIdentityOptions): Promise -``` - -**Parameters:** - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `options` | `MsixIdentityOptions` | No | Configuration options | - ---- - -### `clearElectronDebugIdentity()` - -Clears/removes package identity from the Electron debug process by restoring from backup - -```typescript -function clearElectronDebugIdentity(options?: MsixIdentityOptions): Promise -``` - -**Parameters:** - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `options` | `MsixIdentityOptions` | No | Configuration options | - ---- - -### `getGlobalWinappPath()` - -Get the path to the global .winapp directory - -```typescript -function getGlobalWinappPath(): string -``` - -**Returns:** The full path to the global .winapp directory - ---- - -### `getLocalWinappPath()` - -Get the path to the local .winapp directory - -```typescript -function getLocalWinappPath(): string -``` - -**Returns:** The full path to the local .winapp directory - ---- - -## Node.js CLI commands - -These commands are available exclusively via `npx winapp node ` and are not exported as programmatic functions. - -### `node create-addon` - -Generate native addon files for an Electron project. Supports C++ (node-gyp) and C# (node-api-dotnet) templates. - -```bash -npx winapp node create-addon [options] -``` - -**Options:** - -| Flag | Description | -|------|-------------| -| `--name ` | Addon name (default depends on template) | -| `--template ` | Addon template: `cpp` or `cs` (default: `cpp`) | -| `--verbose` | Enable verbose output | - -> **Note:** Must be run from the root of an Electron project (directory containing `package.json`). - -**Examples:** - -```bash -npx winapp node create-addon -npx winapp node create-addon --name myAddon -npx winapp node create-addon --template cs --name MyCsAddon -``` - ---- - -### `node add-electron-debug-identity` - -Add package identity to the Electron debug process using sparse packaging. Creates a backup of `electron.exe`, generates a sparse MSIX manifest, adds identity to the executable, and registers the sparse package. Requires a `Package.appxmanifest` (create one with `winapp init` or `winapp manifest generate`). - -```bash -npx winapp node add-electron-debug-identity [options] -``` - -**Options:** - -| Flag | Description | -|------|-------------| -| `--manifest ` | Path to custom `Package.appxmanifest` (default: `Package.appxmanifest` in current directory) | -| `--no-install` | Do not install the package after creation | -| `--keep-identity` | Keep the manifest identity as-is, without appending `.debug` suffix | -| `--verbose` | Enable verbose output | - -> **Note:** Must be run from the root of an Electron project (directory containing `node_modules/electron`). To undo, use `npx winapp node clear-electron-debug-identity`. - -**Examples:** - -```bash -npx winapp node add-electron-debug-identity -npx winapp node add-electron-debug-identity --manifest ./custom/Package.appxmanifest -``` - ---- - -### `node clear-electron-debug-identity` - -Remove package identity from the Electron debug process. Restores `electron.exe` from the backup created by `add-electron-debug-identity` and removes the backup files. - -```bash -npx winapp node clear-electron-debug-identity [options] -``` - -**Options:** - -| Flag | Description | -|------|-------------| -| `--verbose` | Enable verbose output | - -> **Note:** Must be run from the root of an Electron project (directory containing `node_modules/electron`). - -**Examples:** - -```bash -npx winapp node clear-electron-debug-identity -``` - ---- - -## Types reference - -### `ExecSyncOptions` - -Re-exported from Node.js for convenience. See [Node.js docs](https://nodejs.org/api/child_process.html). - -### `MsixIdentityOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `verbose` | `boolean \| undefined` | No | | -| `noInstall` | `boolean \| undefined` | No | | -| `keepIdentity` | `boolean \| undefined` | No | | -| `manifest` | `string \| undefined` | No | | - -### `MsixIdentityResult` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `success` | `boolean` | Yes | | - -### `ElectronDebugIdentityResult` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `success` | `boolean` | Yes | | -| `electronExePath` | `string` | Yes | | -| `backupPath` | `string` | Yes | | -| `manifestPath` | `string` | Yes | | -| `assetsDir` | `string` | Yes | | - -### `ClearElectronDebugIdentityResult` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `success` | `boolean` | Yes | | -| `electronExePath` | `string` | Yes | | -| `restoredFromBackup` | `boolean` | Yes | | - -### `CallWinappCliOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `exitOnError` | `boolean \| undefined` | No | | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

On Windows, Node force-terminates the child, so the CLI's own cleanup may not run. That is safe: Windows closes the process's coordination file handles and deletes its `DeleteOnClose` participant lease, and other `winapp ui` processes prune the entry through lease and PID/start validation. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial or invalid output — this wrapper does not promise graceful MP4 finalization.

Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls that pass the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not you set this. What a workflow id adds is *continuity*: commands sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input.

Without it each call is a self-contained one-shot that releases the desktop the moment it finishes.

Applied to the spawned child only; `process.env` is never modified. | - -### `CallWinappCliResult` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `exitCode` | `number` | Yes | | - -### `CallWinappCliCaptureOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()) | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation. See {@link CallWinappCliOptions.signal} for the exact contract, including what is and is not guaranteed after an abort. | -| `workflowId` | `string \| undefined` | No | Groups this call into one logical UI workflow. See {@link CallWinappCliOptions.workflowId} for what continuity buys and why arbitration does not depend on it. | - -### `CallWinappCliCaptureResult` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `exitCode` | `number` | Yes | | -| `stdout` | `string` | Yes | | -| `stderr` | `string` | Yes | | - -### `GenerateCppAddonOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `name` | `string \| undefined` | No | | -| `projectRoot` | `string \| undefined` | No | | -| `verbose` | `boolean \| undefined` | No | | - -### `GenerateCppAddonResult` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `success` | `boolean` | Yes | | -| `addonName` | `string` | Yes | | -| `addonPath` | `string` | Yes | | -| `needsTerminalRestart` | `boolean` | Yes | | -| `files` | `string[]` | Yes | | - -### `GenerateCsAddonOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `name` | `string \| undefined` | No | | -| `projectRoot` | `string \| undefined` | No | | -| `verbose` | `boolean \| undefined` | No | | - -### `GenerateCsAddonResult` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `success` | `boolean` | Yes | | -| `addonName` | `string` | Yes | | -| `addonPath` | `string` | Yes | | -| `needsTerminalRestart` | `boolean` | Yes | | -| `files` | `string[]` | Yes | | - -### `UiRecordOptions` - -Stricter version of `UiRecordOptions` where `durationSec` is **required** (not optional). -This type is the public surface of `uiRecord`; the generated type has it optional. -Survives regeneration because it is defined here in the hand-written guard module. - -```typescript -type UiRecordOptions = Omit & { durationSec: number; overwrite?: boolean; } -``` - -### `TargetRecordOptions` - -Stricter version of the generated `TargetRecordOptions` where `durationSec` is **required**. -This type is the public surface of `targetRecord`; the generated type has it optional. -Survives regeneration because it is defined here in the hand-written guard module. - -```typescript -type TargetRecordOptions = Omit & { durationSec: number; overwrite?: boolean; } -``` - -### `IfExists` - -IfExists values. - -```typescript -type IfExists = "error" | "overwrite" | "skip" -``` - -### `SdkInstallMode` - -SdkInstallMode values. - -```typescript -type SdkInstallMode = "stable" | "preview" | "experimental" | "none" -``` - -### `ManifestTemplates` - -ManifestTemplates values. - -```typescript -type ManifestTemplates = "packaged" | "sparse" -``` - -### `AzSignOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `filePath` | `string` | Yes | Path to the file to sign (exe, msix, or msixbundle) | -| `account` | `string \| undefined` | No | Signing account name. Must be used with --resource-group | -| `metadataFile` | `string \| undefined` | No | Path to an existing metadata.json file. Skips resource discovery and account/profile selection prompts and signs using this file directly. A non-interactive Azure credential should already be available; the CLI can otherwise fall back to an interactive tenant prompt or 'az login', but the npm programmatic API is always non-interactive and fails instead of prompting. | -| `profile` | `string \| undefined` | No | Certificate profile name. Must be used with --account | -| `resourceGroup` | `string \| undefined` | No | Resource group to narrow down signing accounts | -| `subscription` | `string \| undefined` | No | Azure subscription ID to use. If not provided and multiple subscriptions exist, you will be prompted. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `CertGenerateOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `exportCer` | `boolean \| undefined` | No | Export a .cer file (public key only) alongside the .pfx | -| `ifExists` | `IfExists \| undefined` | No | Behavior when output file exists: 'error' (fail, default), 'skip' (keep existing), or 'overwrite' (replace) | -| `install` | `boolean \| undefined` | No | Install the certificate to the local machine store after generation | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `manifest` | `string \| undefined` | No | Path to Package.appxmanifest or appxmanifest.xml file to extract publisher information from | -| `output` | `string \| undefined` | No | Output path for the generated PFX file | -| `password` | `string \| undefined` | No | Password for the generated PFX file. Defaults to 'password', which is publicly known — a certificate left with that password is development-only, because anyone who obtains the .pfx can sign as you. | -| `publisher` | `string \| undefined` | No | Publisher distinguished name (DN) for the generated certificate (e.g., CN=MyCompany or OU=Team, O=Corp, C=US). Components must be single-valued and comma-separated; multi-valued '+' RDNs, ';' separators, and backslashes are not supported. If not specified, will be inferred from manifest. Bare names are auto-wrapped as CN=. | -| `validDays` | `number \| undefined` | No | Number of days the certificate is valid | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `CertInfoOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `certPath` | `string` | Yes | Path to the certificate file (PFX or CER) | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `password` | `string \| undefined` | No | Password for the PFX file (ignored for a public CER) | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `CertInstallOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `certPath` | `string` | Yes | Path to the certificate file (PFX or CER) | -| `force` | `boolean \| undefined` | No | Force installation even if the certificate already exists | -| `password` | `string \| undefined` | No | Password for the PFX file | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `CreateDebugIdentityOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `entrypoint` | `string \| undefined` | No | Path to the .exe that will need to run with identity, or entrypoint script. | -| `keepIdentity` | `boolean \| undefined` | No | Keep the package identity from the manifest as-is, without appending '.debug' to the package name and application ID. | -| `manifest` | `string \| undefined` | No | Path to the Package.appxmanifest or appxmanifest.xml | -| `noInstall` | `boolean \| undefined` | No | Do not install the package after creation. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `CreateExternalCatalogOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `inputFolder` | `string` | Yes | List of input folders with executable files to process (separated by semicolons) | -| `computeFlatHashes` | `boolean \| undefined` | No | Include flat hashes when generating the catalog | -| `ifExists` | `IfExists \| undefined` | No | Behavior when output file already exists | -| `output` | `string \| undefined` | No | Output catalog file path. If not specified, the default CodeIntegrityExternal.cat name is used. | -| `recursive` | `boolean \| undefined` | No | Include files from subdirectories | -| `usePageHashes` | `boolean \| undefined` | No | Include page hashes when generating the catalog | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `EmbedIdentityOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Path to the .exe (embeds identity into its side-by-side manifest via mt.exe) or an .xml/.manifest side-by-side manifest file (inserts/replaces the element; created if it doesn't exist). | -| `manifest` | `string \| undefined` | No | Path to the sparse appxmanifest.xml to read identity from. When omitted, searched in a 'sparse/' folder (where 'winapp init --exe --sparse' writes it by default) beside the target first, then in the current directory, then beside the target and in the current directory. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `FindApiOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `query` | `string \| string[] \| undefined` | No | What to search for, e.g. "acrylic brush" or "NavigationView". Matched lexically against type and member names across the project's indexed API metadata. Pass several quoted queries to run them in a single call. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `max` | `number \| undefined` | No | Maximum number of namespace-grouped results to return. | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `FindApiCheckPropertyOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `type` | `string \| undefined` | No | The type to check. | -| `property` | `string \| string[] \| undefined` | No | One or more property names to validate on the type. Pass several to check them all in a single call. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `FindApiEnumsOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `type` | `string \| string[] \| undefined` | No | One or more enum types to list, e.g. Symbol or Microsoft.UI.Xaml.Controls.Symbol. Pass several to list them in a single call. | -| `filter` | `string \| undefined` | No | Only list values whose name contains this text (case-insensitive), e.g. --filter folder. The unfiltered total is still reported. Prefer listing the whole enum once over repeated filtered calls — most enums are small enough that the full list is cheaper than several narrowed lookups. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `FindApiMembersOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `type` | `string \| string[] \| undefined` | No | One or more types to inspect. Accepts short names (NavigationView) or fully-qualified names (Microsoft.UI.Xaml.Controls.NavigationView). Pass several to resolve them in a single call. | -| `all` | `boolean \| undefined` | No | List the complete member surface: include dependency-property identifier statics (BackgroundProperty) and per-member descriptions, both of which an unfiltered listing omits to save context. Implied by --verbose, and usable together with --json (--verbose is not). | -| `filter` | `string \| undefined` | No | Only list members whose name contains this text (case-insensitive), e.g. --filter background. Totals for the unfiltered type are still reported. Applies to every type in the call. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `FindApiPackagesOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `FindApiRefreshOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | -| `scan` | `boolean \| undefined` | No | Recursively discover and index every project under the directory instead of just the top-level project(s). | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `FindApiStatsOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `project` | `string \| undefined` | No | Project name to query (matches the .csproj/.vcxproj name), or 'sdk' to query the machine-wide Windows SDK scope instead of a project. | -| `projectDir` | `string \| undefined` | No | Project directory to query (defaults to the current directory). Used to locate the indexed project. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `FindUiOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `query` | `string \| undefined` | No | What you're looking for, e.g. "tabbed layout" or "color picker". Matched lexically against WinUI control names, sample headers, and tags. | -| `id` | `string \| string[] \| undefined` | No | Fetch the code (Gallery/Toolkit return XAML and/or C#; Reactor is C#-only) plus prerequisite notes for one or more scenario ids from a prior search (e.g. gallery-tabview-1). | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `list` | `boolean \| undefined` | No | List every discoverable control/sample id instead of searching. Covers Gallery, Toolkit, and core; the opt-in Reactor source is excluded (search it with --source reactor). | -| `max` | `number \| undefined` | No | Maximum number of matched controls to return. Applies to search only; ignored with --list and --id. | -| `refresh` | `boolean \| undefined` | No | Bypass the local cache and re-fetch the WinUI corpus from GitHub. | -| `source` | `string \| undefined` | No | Restrict results to a single source: gallery (WinUI 3 Gallery), toolkit (Windows Community Toolkit), reactor (microsoft-ui-reactor, C#-only declarative WinUI), or core (curated patterns). Reactor is opt-in — it is excluded from a normal search, so pass --source reactor to search it (only do this for a Reactor/MVU project; its C#-only samples don't paste into a standard XAML app). | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `GetWinappPathOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `global` | `boolean \| undefined` | No | Get the global .winapp directory instead of local | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `InitOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `baseDirectory` | `string \| undefined` | No | Base/root directory for the winapp workspace, for consumption or installation. | -| `configDir` | `string \| undefined` | No | Directory to read/store configuration (default: the selected project directory, or current directory if no project is detected) | -| `configOnly` | `boolean \| undefined` | No | Only handle configuration file operations (create if missing, validate if exists). Skip package installation and other workspace setup steps. | -| `exe` | `string \| undefined` | No | Path to the application executable. Requires --sparse. Generates an identity-only sparse manifest for the exe instead of a full package/SDK setup. | -| `force` | `boolean \| undefined` | No | Overwrite an existing appxmanifest.xml in the target directory (sparse only). Without this, init fails instead of replacing existing manifest/asset files. | -| `ignoreConfig` | `boolean \| undefined` | No | Don't use configuration file for version management | -| `name` | `string \| undefined` | No | Override the package name (sparse only; default: inferred from the exe) | -| `noGitignore` | `boolean \| undefined` | No | Don't update .gitignore file | -| `outputDir` | `string \| undefined` | No | Directory to write the sparse manifest and Assets/ (sparse only; default: a 'sparse/' folder in the current directory) | -| `publisher` | `string \| undefined` | No | Override the publisher CN (sparse only; default: inferred from the exe's company name). Bare names are auto-wrapped as CN=. | -| `setupSdks` | `SdkInstallMode \| undefined` | No | SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation) | -| `sparse` | `boolean \| undefined` | No | Generate a sparse identity manifest (appxmanifest.xml) for an existing desktop exe instead of a full package manifest. Use with --exe. Skips SDK/package installation. | -| `useDefaults` | `boolean \| undefined` | No | Skip interactive prompts and use default answers. Normal init targets the positional project directory if given, otherwise the current directory (e.g., winapp init . --use-defaults). Sparse init (--exe --sparse) ignores the positional directory and writes to --output-dir instead. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `ManifestAddAliasOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `appId` | `string \| undefined` | No | Application Id to add the alias to (default: first Application element) | -| `manifest` | `string \| undefined` | No | Path to Package.appxmanifest or appxmanifest.xml file (default: search current directory) | -| `name` | `string \| undefined` | No | Alias name (e.g. 'myapp.exe'). Default: inferred from the Executable attribute in the manifest. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `ManifestGenerateOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `directory` | `string \| undefined` | No | Directory to generate manifest in | -| `description` | `string \| undefined` | No | Human-readable app description shown during installation and in Windows Settings | -| `executable` | `string \| undefined` | No | Path to the application's executable. Default: .exe | -| `ifExists` | `IfExists \| undefined` | No | Behavior when output file exists: 'error' (fail, default), 'skip' (keep existing), or 'overwrite' (replace) | -| `logoPath` | `string \| undefined` | No | Path to logo image file | -| `packageName` | `string \| undefined` | No | Package name (default: folder name) | -| `publisherName` | `string \| undefined` | No | Publisher distinguished name (DN) (default: CN=). Accepts an X.500 DN with single-valued, comma-separated components (multi-valued '+' RDNs, ';' separators, and backslashes are not supported); bare names are auto-wrapped as CN=. | -| `template` | `ManifestTemplates \| undefined` | No | Manifest template type: 'packaged' (full MSIX app, default) or 'sparse' (desktop app with package identity for Windows APIs) | -| `version` | `string \| undefined` | No | App version in Major.Minor.Build.Revision format (e.g., 1.0.0.0). | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `ManifestUpdateAssetsOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `imagePath` | `string` | Yes | Path to source image file (SVG, PNG, ICO, JPG, BMP, GIF) | -| `lightImage` | `string \| undefined` | No | Path to source image for light theme variants (SVG, PNG, ICO, JPG, BMP, GIF) | -| `manifest` | `string \| undefined` | No | Path to Package.appxmanifest or appxmanifest.xml file (default: search current directory) | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `NewOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `force` | `boolean \| undefined` | No | Scaffold even if the output directory already contains files. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `list` | `boolean \| undefined` | No | List the available WinUI templates and exit (installs the latest template pack if none is installed). | -| `name` | `string \| undefined` | No | Name for the new app/project (default: derived from --output, else 'WinUIApp'). | -| `output` | `string \| undefined` | No | Directory to create the app in (default: ./). Created if it doesn't exist. | -| `template` | `string \| undefined` | No | Template short name. XAML templates: winui, winui-navview, winui-tabview, winui-mvvm, winui-lib, winui-unittest. Experimental Reactor (C#-only, MVU) templates: reactor, reactor-mvu, reactor-navview, reactor-tabview. Run 'winapp new --list' to see all. | -| `templateVersion` | `string \| undefined` | No | WinUI template pack version: 'latest' (install newest), 'installed' (keep what's installed), or an explicit version. Default: install latest if none, else prompt to update a stale pack. | -| `useDefaults` | `boolean \| undefined` | No | Do not prompt; use defaults (blank template, name from --output/--name, keep installed templates). | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `PackageOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `inputFolder` | `string \| string[]` | Yes | A single .csproj to build and package (project mode), one or more input folders with package layout, or a single sparse appxmanifest.xml file (an identity-only package with AllowExternalContent). Pass multiple folders to create an MSIX bundle (e.g., winapp pack ./publish/x64 ./publish/arm64). | -| `arch` | `string \| string[] \| undefined` | No | Project mode: target architecture (x64, arm64, or x86). Repeatable — pass two or more to publish each and produce one architecture .msixbundle. Requires a .csproj input; rejected for folder/bundle/manifest inputs. Default: the current process architecture. | -| `cert` | `string \| undefined` | No | Path to signing certificate (will auto-sign if provided) | -| `certPassword` | `string \| undefined` | No | Certificate password (default: password) | -| `configuration` | `string \| undefined` | No | Project mode: build configuration (e.g., Debug, Release). Requires a .csproj input; rejected for folder/bundle/manifest inputs. Default: Release. | -| `executable` | `string \| undefined` | No | Path to the executable relative to the input folder. | -| `framework` | `string \| undefined` | No | Project mode: target framework moniker for multi-targeted projects (e.g. net10.0-windows10.0.26100.0). Requires a .csproj input; rejected for folder/bundle/manifest inputs. | -| `generateCert` | `boolean \| undefined` | No | Generate a new development certificate | -| `installCert` | `boolean \| undefined` | No | Install certificate to machine | -| `manifest` | `string \| undefined` | No | Path to AppX manifest file (default: auto-detect from input folder or current directory) | -| `name` | `string \| undefined` | No | Package name (default: from manifest) | -| `noBuild` | `boolean \| undefined` | No | Project mode: skip building and package the existing build output (still evaluates output properties). Requires a .csproj input; rejected for folder/bundle/manifest inputs. | -| `noRestore` | `boolean \| undefined` | No | Project mode: skip restoring the project before building. Requires a .csproj input; rejected for folder/bundle/manifest inputs. | -| `noSign` | `boolean \| undefined` | No | Deliver the package unsigned, overriding any project signing configuration (e.g. for Store submission or an external signing pipeline). Cannot be combined with --cert or --generate-cert. | -| `output` | `string \| undefined` | No | Output file name for the generated package (.msix) or bundle (.msixbundle). Defaults to __.msix for single packages, or ___.msixbundle for bundles. | -| `property` | `string \| string[] \| undefined` | No | Project mode: MSBuild property as Name=Value, forwarded to both build and evaluation. Repeatable (e.g. -p WindowsPackageType=None). Use -c for configuration, -f for framework, and --arch for architecture; a -p Configuration/TargetFramework is dropped in favor of those flags, while a lone -p RuntimeIdentifier (no --arch) selects an exact RID. Requires a .csproj input; rejected for folder/bundle/manifest inputs. | -| `publisher` | `string \| undefined` | No | Publisher distinguished name (DN) for certificate generation (e.g., CN=MyCompany). Bare names are auto-wrapped as CN=. | -| `selfContained` | `boolean \| undefined` | No | Bundle Windows App SDK runtime for self-contained deployment | -| `skipPri` | `boolean \| undefined` | No | Skip PRI file generation | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `RestoreOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `baseDirectory` | `string \| undefined` | No | Base/root directory for the winapp workspace | -| `configDir` | `string \| undefined` | No | Directory to read configuration from (default: base-directory) | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `RunOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `input` | `string \| undefined` | No | Path to the app to run: a build-output folder, a .cs .NET file-based app, a .csproj project, a .sln/.slnx solution, or a directory containing one of those at its top level (default: current directory). | -| `inputFolder` | `string \| undefined` | No | | -| `aot` | `boolean \| undefined` | No | Project mode: run the project's configured .NET Native AOT publish. Requires effective PublishAot=true. | -| `arch` | `string \| undefined` | No | Project mode: target architecture (x64, arm64, or x86). Sets the canonical Windows RID and selects a matching platform-dependent publish profile when required by the effective build. Ignored in folder mode. Honored for a .cs file-based app too; when omitted, winapp builds for the current process architecture. Default: the current process architecture. | -| `args` | `string \| undefined` | No | Command-line arguments to pass to the application. Alternatively, use -- followed by arguments to avoid escaping (e.g., winapp run . -- --flag value). | -| `clean` | `boolean \| undefined` | No | Remove the existing package's application data (LocalState, settings, etc.) before re-deploying. By default, application data is preserved across re-deployments. | -| `configuration` | `string \| undefined` | No | Project and single-file mode: build configuration (e.g., Debug, Release). Ignored in folder mode. Default: Debug. | -| `debugOutput` | `boolean \| undefined` | No | 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. For WinUI apps, a crash also triggers a stowed-exception triage pass; the first run downloads debugger components (cached under the winapp global directory) and can be pointed at an existing debugger install via the WINAPP_DBGTOOLS_DIR environment variable. Cannot be combined with --no-launch or --json. | -| `detach` | `boolean \| undefined` | No | 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. Local runs print the PID; target runs print the scoped UI target. JSON includes the PID and target scope. | -| `executable` | `string \| undefined` | No | 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. | -| `framework` | `string \| undefined` | No | Project mode: target framework moniker for multi-targeted projects (e.g. net10.0-windows10.0.26100.0). Ignored in folder mode. Rejected for a .cs file-based app, which declares its own with '#:property TargetFramework=...'. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `manifest` | `string \| undefined` | No | Path to the Package.appxmanifest (default: auto-detect from input folder or current directory) | -| `noBuild` | `boolean \| undefined` | No | Project and single-file mode: skip building and run the existing build output (still evaluates output properties). Ignored in folder mode. | -| `noLaunch` | `boolean \| undefined` | No | Only create the debug identity and register the package without launching the application | -| `noRestore` | `boolean \| undefined` | No | Project and single-file mode: skip restoring before build or Native AOT publish. Ignored in folder mode. | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `outputAppxDirectory` | `string \| undefined` | No | Output directory for the loose layout package. If not specified, a directory named AppX inside the input directory will be used. | -| `project` | `string \| undefined` | No | Project mode: when the input is a solution (.sln/.slnx) or a directory with multiple runnable app projects, selects which project to launch (by name or path). Ignored in folder mode. Rejected for a .cs file-based app, which is itself the project. | -| `property` | `string \| string[] \| undefined` | No | Project and single-file mode: MSBuild property as Name=Value, forwarded to both build and evaluation. Repeatable. Ignored in folder mode. | -| `runtime` | `string \| undefined` | No | Project mode: target .NET runtime identifier (RID), e.g. win-x64. Project mode uses only the RID's architecture, always builds the canonical win-, rejects non-Windows RIDs (e.g. linux-x64), and can select a required architecture-dependent publish profile; it overrides --arch. Ignored in folder mode. Honored for a .cs file-based app too. | -| `symbols` | `boolean \| undefined` | No | Download symbols from Microsoft Symbol Server for richer native crash analysis, including the WinUI stowed-exception dispatch stack. Only used with --debug-output. First run downloads symbols and caches them locally; subsequent runs use the cache. | -| `unregisterOnExit` | `boolean \| undefined` | No | Unregister the development package after the application exits. Only removes packages registered in development mode. | -| `withAlias` | `boolean \| undefined` | No | Launch the app using its execution alias instead of AUMID activation. The app runs in the current terminal with inherited stdin/stdout/stderr. Console apps (OutputType=Exe) already do this by default; pass this to force it for a windowed app. winapp adds a uap5:ExecutionAlias to the manifest it stages for you, so no manifest edit is needed. | -| `withoutAlias` | `boolean \| undefined` | No | Launch via AUMID activation even for a console app, instead of the default execution alias. The app then runs without a console, so it prints nothing to this terminal. | -| `appArgs` | `string \| string[] \| undefined` | No | Arguments to pass to the launched application (forwarded after --). | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `SignOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `filePath` | `string` | Yes | Path to the file/package to sign | -| `certPath` | `string` | Yes | Path to the certificate file (PFX format) | -| `password` | `string \| undefined` | No | Certificate password | -| `timestamp` | `string \| undefined` | No | Timestamp server URL | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `StoreOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `storeArgs` | `string \| string[] \| undefined` | No | Arguments to pass through to the Microsoft Store Developer CLI. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `TargetExecOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Execution target to act on. Currently: 'sandbox'. | -| `targetCwd` | `string \| undefined` | No | Working directory on the target. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `command` | `string \| string[] \| undefined` | No | Executable and arguments to run on the target, e.g. ['dotnet', '--info'] (forwarded after --). | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `TargetPullOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Execution target to act on. Currently: 'sandbox'. | -| `source` | `string` | Yes | File or directory on the target to copy, relative to its managed work area. | -| `destination` | `string` | Yes | Destination path on this machine. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `TargetPushOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Execution target to act on. Currently: 'sandbox'. | -| `source` | `string` | Yes | File or directory on this machine to copy. | -| `destination` | `string` | Yes | Destination path on the target, relative to its managed work area. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `TargetScreenshotOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Execution target to act on. Currently: 'sandbox'. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `output` | `string \| undefined` | No | Save output to this file path. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `TargetSnapshotOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `target` | `string` | Yes | Execution target to act on. Currently: 'sandbox'. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `ToolOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `toolArgs` | `string \| string[] \| undefined` | No | Arguments to pass to the SDK tool, e.g. ['makeappx', 'pack', '/d', './folder', '/p', './out.msix']. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiClickOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `double` | `boolean \| undefined` | No | Perform a double-click instead of a single click | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `right` | `boolean \| undefined` | No | Perform a right-click instead of a left click | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiDragOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `from` | `string \| undefined` | No | Start point — an element selector (drags from its center) or screen coordinates x,y as reported by 'ui inspect' (e.g. pn-list-d736 or 100,200). | -| `to` | `string \| undefined` | No | End point — an element selector (drops at its center) or screen coordinates x,y as reported by 'ui inspect' (e.g. pn-target-d746 or 300,400). | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `dwellMs` | `number \| undefined` | No | Milliseconds to dwell at the destination after moving, before releasing (default: 0). Lets drop targets / merge overlays that arm from a sustained hover latch before release. | -| `holdMs` | `number \| undefined` | No | Milliseconds to hold the button down at the start before moving (default: 0). With == (no movement) this performs a press-and-hold / long-press gesture. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `right` | `boolean \| undefined` | No | Drag with the right mouse button instead of the left button | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiFocusOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string` | Yes | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiGetFocusedOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiGetPropertyOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `className` | `string \| undefined` | No | Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `property` | `string \| undefined` | No | Property name to read or filter on | -| `root` | `string \| undefined` | No | Search only descendants of this uniquely matching selector (excludes the root). | -| `type` | `string \| undefined` | No | UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiGetValueOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `className` | `string \| undefined` | No | Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `root` | `string \| undefined` | No | Search only descendants of this uniquely matching selector (excludes the root). | -| `type` | `string \| undefined` | No | UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiHoverOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `dwellTime` | `number \| undefined` | No | Time in milliseconds to wait after hovering for hover effects to appear (default: 800) | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiInspectOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `ancestors` | `boolean \| undefined` | No | Walk up the tree from the specified element to the root | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `depth` | `number \| undefined` | No | Tree inspection depth | -| `hideDisabled` | `boolean \| undefined` | No | Hide disabled elements from output | -| `hideOffscreen` | `boolean \| undefined` | No | Hide offscreen elements from output | -| `interactive` | `boolean \| undefined` | No | Show only interactive/invokable elements (buttons, links, inputs, list items). Increases default depth to 8. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiInvokeOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `action` | `string \| undefined` | No | Perform exactly this action on the selected element, without pattern or ancestor fallback: invoke, select, toggle, toggle-on, toggle-off, expand, collapse. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiListWindowsOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `showHidden` | `boolean \| undefined` | No | Include untitled zero-size windows that are hidden by default | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiPenOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `at` | `string \| undefined` | No | Pen contact point as screen coordinates x,y (as reported by 'ui inspect'). Defaults to the selector's element center. Ignored when --path is given. | -| `durationMs` | `number \| undefined` | No | Total glide time in milliseconds distributed across the stroke path segments (default: ~10 ms per segment). | -| `eraser` | `boolean \| undefined` | No | Use the eraser end of the pen instead of the tip. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `path` | `string \| undefined` | No | Ink stroke path as a whitespace-separated list of x,y pairs, e.g. "10,10 20,30 40,50". | -| `pressure` | `number \| undefined` | No | Pen pressure from 0.0 to 1.0 (default: 0.5). | -| `tiltX` | `number \| undefined` | No | Pen tilt along the x-axis in degrees (-90 to 90, default: 0). | -| `tiltY` | `number \| undefined` | No | Pen tilt along the y-axis in degrees (-90 to 90, default: 0). | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiScreenshotOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `captureScreen` | `boolean \| undefined` | No | Capture from screen DC via BitBlt (includes popups/overlays not owned by the target). | -| `focus` | `boolean \| undefined` | No | Bring the target window to the foreground before capture. Already implied by --capture-screen. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `output` | `string \| undefined` | No | Save output to this file path. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiScrollOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `direction` | `string \| undefined` | No | Scroll direction: up, down, left, right | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `to` | `string \| undefined` | No | Scroll to position: top, bottom | -| `wheel` | `number \| undefined` | No | Rotate the mouse wheel over the element by this many notches (1 = one notch up, -1 = one notch down). Synthesizes real wheel input instead of using ScrollPattern. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiScrollIntoViewOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiSearchOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `className` | `string \| undefined` | No | Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `max` | `number \| undefined` | No | Maximum search results | -| `root` | `string \| undefined` | No | Search only descendants of this uniquely matching selector (excludes the root). | -| `type` | `string \| undefined` | No | UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiSendKeysOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `keys` | `string \| undefined` | No | Keys to send. Whitespace-separated tokens: named keys (down, enter, tab, esc, f5), modifier combos (ctrl+shift+t, alt+f4), raw virtual keys (vk=0x42), or literal text (hello). Hold capslock or insert for screen-reader commands (ctrl+capslock+f12 toggles Narrator developer mode); these require --via send-input. Use text= to type a single value verbatim when it would otherwise be read as a key name or combo (text=enter types "enter"; text=ctrl+a types "ctrl+a"); backslash escapes \\s \\t \\n \\r \\\\ are supported (text=a\\s\\sb types "a b"). To type the whole argument literally without escaping each token, pass --verbatim instead. Quote multi-token strings, e.g. "ctrl+a delete". | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `allowSystemKeys` | `boolean \| undefined` | No | Allow synthesizing system-/shell-reserved combos (win+, alt+f4, alt+tab, ctrl+esc, …) via --via send-input, which are refused by default because they act on the OS/shell beyond the target app. Opt in to drive global hotkeys (e.g. PowerToys' win+shift+v, win+r). No effect on --via post-message (already window-scoped; a warning is emitted if set without send-input). Note: win+l and ctrl+alt+del stay blocked even with this flag — win+l locks the workstation (LockWorkStation() via the shell hook), which is unrecoverable from automation, and ctrl+alt+del is a Secure Attention Sequence (SAS) that Windows drops from injected input regardless of this flag, so it can never take effect. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `target` | `string \| undefined` | No | Optional selector (slug or text) to focus before sending keys. | -| `verbatim` | `boolean \| undefined` | No | Type the entire keys argument as literal text — no named-key, combo, or vk= interpretation, and exact whitespace preserved. The whole-argument form of the per-token text= escape: --verbatim "down down enter" types the words instead of pressing Down, Down, Enter. | -| `via` | `string \| undefined` | No | Transport: post-message (default, HWND-targeted, bypasses UIPI; typed text raises TextChanged but not a per-character KeyDown) or send-input (OS-wide; typed text raises a real per-character KeyDown + TextChanged). Named keys and combos raise KeyDown on both, but keyboard accelerators/shortcuts (KeyboardAccelerator, e.g. ctrl+t) only fire via send-input. post-message targets the focused child control and works for classic Win32/WinForms controls, but WinUI 3 / UWP / XAML controls are windowless and ignore posted messages — use send-input for those (a warning is emitted when the target looks like a XAML app). | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiSetValueOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `value` | `string \| undefined` | No | Value to set (text for TextBox/ComboBox, number for Slider) | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiStatusOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiTouchOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `at` | `string \| undefined` | No | Explicit start point as screen coordinates x,y (as reported by 'ui inspect'). Defaults to the selector's element center. | -| `direction` | `string \| undefined` | No | Swipe direction: right (default), left, up, or down. Combined with --distance to compute the end point when --to-point is not given. | -| `distance` | `number \| undefined` | No | Distance in pixels for pinch/stretch (finger spread) or swipe. | -| `durationMs` | `number \| undefined` | No | Glide time in milliseconds for moving gestures (swipe/pinch/stretch). | -| `fingers` | `number \| undefined` | No | Number of touch contacts (default: 1). Pinch/stretch always use 2. | -| `gesture` | `string \| undefined` | No | Gesture to perform: tap, double-tap, long-press, swipe, pinch, stretch (default: tap). | -| `holdMs` | `number \| undefined` | No | Milliseconds to hold contacts down before lifting (long-press hold time). Defaults to 500 ms when --gesture long-press is used and this option is not set. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `toPoint` | `string \| undefined` | No | End point x,y for a swipe (screen coordinates). Takes precedence over --direction. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiWaitForOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `selector` | `string \| undefined` | No | Semantic slug (e.g., btn-minimize-d1a0) or text to search by name/automationId | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `app` | `string \| undefined` | No | Target app (process name, window title, or PID). Lists windows if ambiguous. | -| `className` | `string \| undefined` | No | Exact, case-insensitive UIA ClassName (literal, not a substring or wildcard). | -| `contains` | `boolean \| undefined` | No | Use substring matching for --value instead of exact match | -| `gone` | `boolean \| undefined` | No | Wait for element to disappear instead of appear | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `property` | `string \| undefined` | No | Property name to read or filter on | -| `root` | `string \| undefined` | No | Search only descendants of this uniquely matching selector (excludes the root). | -| `timeout` | `number \| undefined` | No | Timeout in milliseconds | -| `type` | `string \| undefined` | No | UIA control type, case-insensitive. Supports all 41 official types; aliases: TextBox -> Edit, TextBlock -> Text. | -| `value` | `string \| undefined` | No | Wait for element value to equal this string. Uses smart fallback (TextPattern -> ValuePattern -> Name). Combine with --property to check a specific property instead. | -| `window` | `number \| undefined` | No | Target window by HWND (stable handle from list output). Takes precedence over --app. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UiYieldOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | - -### `UnregisterOptions` - -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `input` | `string \| undefined` | No | Path to a .NET file-based app (a single .cs) whose package should be unregistered. Its identity is resolved the same way 'winapp run' resolves it, so no manifest path is needed. Omit to use --manifest or auto-detect a manifest in the current directory. Cannot be combined with --manifest. | -| `arch` | `string \| undefined` | No | Target architecture (x64, arm64, x86) used when resolving a .cs file-based app's identity (default: the current process architecture). Pass the same architecture the run used, since a Directory.Build.props can key identity off $(RuntimeIdentifier). Only applies to a .cs input. | -| `configuration` | `string \| undefined` | No | Build configuration used when resolving a .cs file-based app's identity (default: Debug). Pass the same configuration the run used: a Directory.Build.props beside the .cs can set WinAppPackageName or WinAppManifestPath conditionally on $(Configuration). Only applies to a .cs input. | -| `force` | `boolean \| undefined` | No | Skip the install-location directory check and unregister even if the package was registered from a different project tree. Candidates are matched by Identity/@Name alone, so with --force a same-named package from a different publisher is also removed, along with its application data — prefer --prune for registrations whose files are gone. With --prune, also skips the confirmation prompt. | -| `json` | `boolean \| undefined` | No | Format output as JSON | -| `manifest` | `string \| undefined` | No | Path to the Package.appxmanifest (default: auto-detect from current directory) | -| `on` | `string \| undefined` | No | Run this command on the named execution target instead of this machine. Supported: 'sandbox' (the Windows Sandbox winapp manages) and 'local' (the default). There is no fallback: if the target cannot be prepared, the command fails rather than running here. | -| `outputAppxDirectory` | `string \| undefined` | No | The AppX layout directory the package was registered from. Only needed when the run used --output-appx-directory, since nothing on the package records which run option produced its layout; without it the registration looks like it came from a different tree and is skipped. | -| `property` | `string \| string[] \| undefined` | No | MSBuild property (Name=Value) used when resolving a .cs file-based app's identity. Repeatable. Pass the same identity-affecting properties the run used (e.g. -p WinAppPackageName=...), since a command-line property overrides the file's own #:property directives. Only applies to a .cs input. | -| `prune` | `boolean \| undefined` | No | Remove every development-mode registration whose files are gone. These can never launch — Windows keeps the identity and its Start menu entry, but activation silently does nothing. Lists what it found and asks before removing; pass --force to skip the prompt. Cannot be combined with an input or --manifest. | -| `runtime` | `string \| undefined` | No | Target .NET runtime identifier (e.g. win-x64) used when resolving a .cs file-based app's identity. Only its architecture is used, and it overrides --arch. Only applies to a .cs input. | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | +Use the same `workflowId` for recording and concurrent UI actions when they belong +to one workflow. Do not use cancellation as normal recording completion. +See [UI recording](ui-automation.md) for capture options. -### `UpdateOptions` +## Electron and Node-only tools -| Property | Type | Required | Description | -|----------|------|----------|-------------| -| `setupSdks` | `SdkInstallMode \| undefined` | No | SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation) | -| `quiet` | `boolean \| undefined` | No | Suppress progress messages. | -| `verbose` | `boolean \| undefined` | No | Enable verbose output. | -| `cwd` | `string \| undefined` | No | Working directory for the CLI process (defaults to process.cwd()). | -| `signal` | `AbortSignal \| undefined` | No | Cancels the whole native invocation, not just a wait for the shared desktop.

`winapp ui` commands take cooperative turns on the desktop, so a command may wait for another workflow to finish. Aborting force-terminates the child on Windows; the CLI's own cleanup may not run, but Windows releases its coordination handles and deletes its participant lease, and other processes reclaim the queue entry. If the abort lands after the command acquired the desktop, UI side effects may already have happened, and aborting an active recording can leave partial output. Rejects with an `AbortError`. | -| `workflowId` | `string \| undefined` | No | Groups this call with other `winapp ui` calls passing the same value into one logical workflow.

Collision arbitration is always on — every desktop-sensitive `winapp ui` command takes a turn whether or not this is set. A workflow id adds *continuity*: calls sharing one keep the desktop reserved between invocations for a short idle grace, may overlap with each other (a recording and the clicks it is recording), and are never interleaved with another workflow's input. Without it, each call is a self-contained one-shot that releases the desktop as soon as it finishes.

Applied to the spawned child process only; `process.env` is never modified. | +The package also exports `addElectronDebugIdentity`, `clearElectronDebugIdentity`, +`addMsixIdentityToExe`, and `execWithBuildTools`. These utilities have their own +result types; use the installed declarations and [Electron guides](guides/electron/index.md) +for their options and prerequisites. +Some npm CLI operations are not command-function exports. Use +[the `node` commands](usage.md#node-create-addon) for native addon scaffolding +and JavaScript binding generation. diff --git a/llms.txt b/llms.txt index 6dfe994bc..7b351dc21 100644 --- a/llms.txt +++ b/llms.txt @@ -26,7 +26,7 @@ A single shared plugin at `plugins/winapp/` serves both GitHub Copilot and Claud ## Docs -- [CLI Schema](https://raw.githubusercontent.com/microsoft/WinAppCli/main/docs/cli-schema.json): Machine-readable JSON schema of all commands, options, and types +- CLI Schema: Run `winapp --cli-schema` to get the installed version's machine-readable JSON command and option definitions. - [WinUI Sample Index Schema](https://raw.githubusercontent.com/microsoft/WinAppCli/main/docs/winui-sample-index.schema.json): Machine-readable contract a WinUI sample gallery publishes so `winapp find-ui` can index it without scraping the repository layout - [Usage Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/usage.md): Full documentation for humans - [Debugging Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/debugging.md): `winapp run` vs `create-debug-identity`, IDE setup, and debugging workflows diff --git a/scripts/build-cli.ps1 b/scripts/build-cli.ps1 index 7d277c53e..4aa7e7d66 100644 --- a/scripts/build-cli.ps1 +++ b/scripts/build-cli.ps1 @@ -19,7 +19,7 @@ .PARAMETER SkipMsix Skip MSIX packages creation .PARAMETER SkipDocs - Skip documentation schema generation, plugin manifest version synchronization, and npm API docs + Skip artifact schema generation and plugin manifest version synchronization .PARAMETER SkipAll Skip NuGet, MSIX, npm, tests, and docs (only builds the CLI) .PARAMETER OnlyDocs @@ -733,7 +733,8 @@ try & $GenerateLlmDocsScript -CliPath $CliExePath -CalledFromBuildScript if ($LASTEXITCODE -ne 0) { - Write-Warning "CLI schema generation failed, but continuing..." + Write-Error "CLI schema generation failed" + exit 1 } else { Write-Host "[DOCS] CLI schema generated successfully!" -ForegroundColor Green } @@ -756,21 +757,6 @@ try exit 1 } - # Generate npm API documentation from TypeScript source (after npm build so codegen is fresh) - if (-not $SkipDocs) { - Write-Host "[NPM] Generating npm API documentation..." -ForegroundColor Blue - Push-Location (Join-Path $ProjectRoot "src\winapp-npm") - try { - npm run generate-docs --ignore-scripts - if ($LASTEXITCODE -ne 0) { - Write-Warning "npm API documentation generation failed, but continuing..." - } else { - Write-Host "[NPM] npm API documentation generated successfully!" -ForegroundColor Green - } - } finally { - Pop-Location - } - } } else { Write-Host "" Write-Host "[NPM] Skipping npm package creation (use -SkipNpm:`$false to enable)" -ForegroundColor Gray diff --git a/scripts/generate-llm-docs.ps1 b/scripts/generate-llm-docs.ps1 index 2cdf61809..7e549d773 100644 --- a/scripts/generate-llm-docs.ps1 +++ b/scripts/generate-llm-docs.ps1 @@ -3,13 +3,13 @@ .SYNOPSIS Generate the CLI schema from the CLI binary .DESCRIPTION - This script writes docs/cli-schema.json from the CLI's --cli-schema output. + This script writes artifacts/docs/cli-schema.json from the CLI's --cli-schema output. Plugin skills are hand-authored directly under plugins/winapp/skills and are not generated. .PARAMETER CliPath Path to the winapp.exe CLI binary (default: artifacts/cli/win-x64/winapp.exe) -.PARAMETER DocsPath - Path to the docs folder (default: docs) +.PARAMETER OutputPath + Schema output folder (default: artifacts/docs). An explicit path skips plugin version synchronization. .EXAMPLE .\scripts\generate-llm-docs.ps1 .EXAMPLE @@ -18,20 +18,21 @@ param( [string]$CliPath = "", - [string]$DocsPath = "", + [string]$OutputPath = "", [switch]$CalledFromBuildScript = $false ) $ProjectRoot = $PSScriptRoot | Split-Path -Parent -$DefaultDocsPath = Join-Path $ProjectRoot "docs" -$UsingDefaultPaths = (-not $CliPath -and -not $DocsPath) +$DefaultOutputPath = Join-Path $ProjectRoot "artifacts\docs" +$UsingDefaultPaths = (-not $CliPath -and -not $OutputPath) +$SyncPluginVersions = (-not $OutputPath) if (-not $CliPath) { $CliPath = Join-Path $ProjectRoot "artifacts\cli\win-x64\winapp.exe" } -if (-not $DocsPath) { - $DocsPath = $DefaultDocsPath +if (-not $OutputPath) { + $OutputPath = $DefaultOutputPath } if (-not (Test-Path $CliPath)) { @@ -40,12 +41,12 @@ if (-not (Test-Path $CliPath)) { exit 1 } -New-Item -ItemType Directory -Path $DocsPath -Force | Out-Null -$SchemaOutputPath = Join-Path $DocsPath "cli-schema.json" +New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null +$SchemaOutputPath = Join-Path $OutputPath "cli-schema.json" Write-Host "[DOCS] Generating CLI schema..." -ForegroundColor Blue Write-Host "CLI path: $CliPath" -ForegroundColor Gray -Write-Host "Docs path: $DocsPath" -ForegroundColor Gray +Write-Host "Output path: $OutputPath" -ForegroundColor Gray $prevEncoding = [Console]::OutputEncoding try { @@ -63,7 +64,11 @@ if ($LASTEXITCODE -ne 0) { $SchemaJson = ($SchemaJsonLines -join "`n").TrimEnd() + "`n" try { - $null = $SchemaJson | ConvertFrom-Json -Depth 100 + $schema = $SchemaJson | ConvertFrom-Json -Depth 100 -ErrorAction Stop + if ($schema.name -ne 'winapp' -or $schema.subcommands -isnot [System.Management.Automation.PSCustomObject] -or + @($schema.subcommands.PSObject.Properties).Count -eq 0) { + throw "Expected the winapp command tree with at least one subcommand." + } } catch { Write-Error "CLI returned invalid schema JSON: $($_.Exception.Message)" @@ -73,10 +78,8 @@ catch { [System.IO.File]::WriteAllText($SchemaOutputPath, $SchemaJson, [System.Text.UTF8Encoding]::new($false)) Write-Host "[DOCS] Saved: $SchemaOutputPath" -ForegroundColor Green -# Default-path builds also keep the installable plugin metadata aligned with the CLI. -# Custom DocsPath runs, such as validation into a temp directory, must not mutate the repo. -$IsDefaultDocsPath = [System.IO.Path]::GetFullPath($DocsPath) -eq [System.IO.Path]::GetFullPath($DefaultDocsPath) -if ($IsDefaultDocsPath) { +# Validation uses an explicit output path so it does not update tracked manifests. +if ($SyncPluginVersions) { $PluginVersion = (Get-Content (Join-Path $ProjectRoot "version.json") | ConvertFrom-Json).version $ManifestPaths = @( (Join-Path $ProjectRoot "plugin.json"), diff --git a/scripts/start-release.ps1 b/scripts/start-release.ps1 index 48dc8ab6a..7a524649a 100644 --- a/scripts/start-release.ps1 +++ b/scripts/start-release.ps1 @@ -291,7 +291,7 @@ try { $newVersionJson = @{ version = $releaseVersion } | ConvertTo-Json Set-Content -Path $VersionFilePath -Value $newVersionJson -NoNewline - # Regenerate version-dependent files (cli-schema.json, etc.) + # Synchronize version-dependent plugin manifests and build release artifacts. Write-Info "Running build to regenerate version-dependent files..." $buildScript = Join-Path $PSScriptRoot "build-cli.ps1" & $buildScript -SkipTests -SkipNpm -SkipMsix diff --git a/scripts/tests/build-cli.Tests.ps1 b/scripts/tests/build-cli.Tests.ps1 index 648ae9414..6ca63e14d 100644 --- a/scripts/tests/build-cli.Tests.ps1 +++ b/scripts/tests/build-cli.Tests.ps1 @@ -488,7 +488,7 @@ Describe 'build-cli.ps1 control flow' { foreach ($name in @('build-number', 'prerelease-label', 'stand-down', 'generate-llm-docs', 'package-npm', 'package-nuget', 'nuget-pester', 'scripts-pester', 'package-msix')) { $result.Calls.Name | Should -Contain $name } - $result.Trace | Should -Match 'npm run generate-docs' + $result.Trace | Should -Not -Match 'npm run generate-docs' $result.Output | Should -Match 'Ready for distribution' Join-Path $root 'artifacts\keep.txt' | Should -Not -Exist Join-Path $root 'artifacts\setup-winapprun.ps1' | Should -Exist @@ -505,6 +505,12 @@ Describe 'build-cli.ps1 control flow' { $result.Trace | Should -Match 'package-msix 1.2.3.17 Stable=True' } + It 'fails the build when live schema generation fails instead of reporting success' { + $result = Invoke-BuildFixture $root -Flags @{ SkipTests = $true } -Fail 'generate-llm-docs' + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Not -Match '\[SUCCESS\]' + } + It 'keeps legacy OnlyTests publishing without packaging or NuGet tests' { $result = Invoke-BuildFixture $root -Flags @{ OnlyTests = $true } diff --git a/scripts/tests/live-schema.Tests.ps1 b/scripts/tests/live-schema.Tests.ps1 new file mode 100644 index 000000000..8ee75b8b8 --- /dev/null +++ b/scripts/tests/live-schema.Tests.ps1 @@ -0,0 +1,123 @@ +#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } + +BeforeAll { + $script:repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent + + function New-SchemaFixture { + $root = Join-Path $TestDrive ([guid]::NewGuid().ToString('N')) + foreach ($directory in @('scripts', 'docs', 'plugins\winapp\skills\fixture', 'plugins\winapp\com.github.copilot\agents', + 'plugins\winapp\.claude-plugin', '.github\plugin', '.claude-plugin', 'src\winapp-npm\src', 'artifacts\cli\win-x64')) { + New-Item -ItemType Directory -Path (Join-Path $root $directory) -Force | Out-Null + } + + foreach ($name in @('generate-llm-docs', 'validate-llm-docs', 'validate-plugin-package')) { + Copy-Item "$repoRoot\scripts\$name.ps1" "$root\scripts" + } + Set-Content "$root\plugin.json" '{"version":"1.2.3","skills":["plugins/winapp/skills"],"agents":["plugins/winapp/com.github.copilot/agents/winapp.agent.md"]}' + Set-Content "$root\plugins\winapp\plugin.json" '{"$schema":"https://agent-plugins.org/schemas/1.0.0/plugin.schema.json","name":"winapp","version":"1.2.3"}' + Set-Content "$root\plugins\winapp\.claude-plugin\plugin.json" '{"version":"1.2.3","agents":["com.github.copilot/agents/winapp.agent.md"]}' + Set-Content "$root\.github\plugin\marketplace.json", "$root\.claude-plugin\marketplace.json" '{"version":"1.2.3"}' + Set-Content "$root\version.json" '{"version":"1.2.3"}' + Set-Content "$root\src\winapp-npm\src\cli.ts" "const NODE_SUBCOMMANDS = ['create-addon'];" + $content = @' +--- +name: fixture +description: Fixture skill for validating command examples. +--- +```powershell +winapp init +``` +'@ + Set-Content "$root\plugins\winapp\skills\fixture\SKILL.md" $content + Set-Content "$root\plugins\winapp\com.github.copilot\agents\winapp.agent.md" $content + Set-Content "$root\artifacts\cli\win-x64\winapp.exe" 'fixture' + Set-Content "$root\schema.json" '{"name":"winapp","version":"9.9.9","schemaVersion":"1.0","subcommands":{"init":{}}}' + Set-Content "$root\run.ps1" @' +param([string]$Operation, [string]$SchemaPath = '') +$ErrorActionPreference = 'Stop' +$cli = Join-Path $PSScriptRoot 'artifacts\cli\win-x64\winapp.exe' +Set-Item -LiteralPath "Function:\$cli" -Value { + Get-Content (Join-Path $PSScriptRoot 'schema.json') -Raw + $global:LASTEXITCODE = 0 +} +try { + if ($Operation -eq 'generate') { + & "$PSScriptRoot\scripts\generate-llm-docs.ps1" -CliPath $cli -CalledFromBuildScript + } elseif ($Operation -eq 'validate') { + & "$PSScriptRoot\scripts\validate-llm-docs.ps1" -CliPath $cli + } else { + $options = if ($SchemaPath) { @{ CliSchemaPath = $SchemaPath } } else { @{} } + & "$PSScriptRoot\scripts\validate-plugin-package.ps1" @options + } + exit $LASTEXITCODE +} catch { + Write-Host $_ + exit 1 +} +'@ + return $root + } + + function Invoke-SchemaFixture { + param([string]$Root, [string]$Operation, [string]$SchemaPath = '') + $output = & pwsh -NoProfile -File "$Root\run.ps1" -Operation $Operation -SchemaPath $SchemaPath 2>&1 + return @{ ExitCode = $LASTEXITCODE; Output = $output -join "`n" } + } +} + +Describe 'Live CLI schema consumers' { + BeforeEach { + $root = New-SchemaFixture + } + + It 'generates the schema only under ignored artifacts without modifying hand-written docs' { + Set-Content "$root\docs\npm-usage.md" 'Hand-written guide' + $result = Invoke-SchemaFixture $root 'generate' + $result.ExitCode | Should -Be 0 -Because $result.Output + "$root\artifacts\docs\cli-schema.json" | Should -Exist + "$root\docs\cli-schema.json" | Should -Not -Exist + Get-Content "$root\docs\npm-usage.md" -Raw | Should -BeLike '*Hand-written guide*' + } + + It 'runs build-free structural plugin checks without a schema snapshot' { + $result = Invoke-SchemaFixture $root 'plugin' + $result.ExitCode | Should -Be 0 -Because $result.Output + $result.Output | Should -Match 'Command examples.*not checked' + } + + It 'validates plugins using the built CLI without a documentation snapshot' { + $result = Invoke-SchemaFixture $root 'validate' + $result.ExitCode | Should -Be 0 -Because $result.Output + $result.Output | Should -Match 'command examples' + "$root\docs\cli-schema.json" | Should -Not -Exist + } + + It 'rejects unknown skill commands against the current CLI schema' { + (Get-Content "$root\plugins\winapp\skills\fixture\SKILL.md" -Raw).Replace('winapp init', 'winapp nonexistent') | + Set-Content "$root\plugins\winapp\skills\fixture\SKILL.md" + $result = Invoke-SchemaFixture $root 'validate' + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match "unknown command 'winapp nonexistent'" + } + + It 'rejects an explicitly missing schema rather than skipping command validation' { + $result = Invoke-SchemaFixture $root 'plugin' "$root\missing.json" + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'schema.*not found' + } + + It 'rejects invalid schema data returned by the CLI' -ForEach @('{invalid', '{}') { + Set-Content "$root\schema.json" $_ + $result = Invoke-SchemaFixture $root 'validate' + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'invalid.*schema|schema.*invalid' + } +} + +Describe 'AI documentation command reference' { + It 'uses the live CLI schema instead of linking to the retired snapshot' { + $content = Get-Content "$repoRoot\llms.txt" -Raw + $content | Should -Not -Match 'https?://\S+/docs/cli-schema\.json' + $content | Should -Match 'winapp --cli-schema' + } +} diff --git a/scripts/tests/npm-codegen.Tests.ps1 b/scripts/tests/npm-codegen.Tests.ps1 index dab865f64..771860194 100644 --- a/scripts/tests/npm-codegen.Tests.ps1 +++ b/scripts/tests/npm-codegen.Tests.ps1 @@ -7,12 +7,25 @@ BeforeAll { function New-NpmFixture { $root = Join-Path $TestDrive ([guid]::NewGuid().ToString('N')) $npmRoot = Join-Path $root 'src\winapp-npm' - New-Item -ItemType Directory -Path "$npmRoot\src", "$npmRoot\scripts", "$root\docs" -Force | Out-Null + New-Item -ItemType Directory -Path "$npmRoot\src", "$npmRoot\scripts", "$root\docs", "$root\src\winapp-CLI\WinApp.Cli" -Force | Out-Null Copy-Item "$repoRoot\src\winapp-npm\scripts\generate-commands.mjs" "$npmRoot\scripts" - Copy-Item "$repoRoot\docs\cli-schema.json" "$root\docs" + Set-Content "$root\src\winapp-CLI\WinApp.Cli\WinApp.Cli.csproj" '' + Set-Content "$npmRoot\schema.json" '{"name":"winapp","version":"1.2.3","subcommands":{"init":{"description":"Initialize a project"}}}' + Set-Content "$npmRoot\fake-cli.cjs" @' +const fs = require('node:fs'); +const cp = require('node:child_process'); +const original = cp.execFileSync; +cp.execFileSync = (file, args, options) => { + if (file !== 'dotnet' && !file.endsWith('winapp.exe')) return original(file, args, options); + fs.appendFileSync('cli-trace.jsonl', JSON.stringify({file, args}) + '\n'); + if (process.env.FIXTURE_FAIL_BUILD && args[0] === 'build') throw new Error('CLI build failed'); + return args[0] === 'build' ? '' : fs.readFileSync('schema.json', 'utf8'); +}; +require('node:module').syncBuiltinESMExports(); +'@ $package = Get-Content "$repoRoot\src\winapp-npm\package.json" -Raw | ConvertFrom-Json - foreach ($entryPoint in @('compile', 'compile:watch', 'test', 'generate-docs', 'generate-docs:check')) { + foreach ($entryPoint in @('compile', 'compile:watch', 'test')) { $package.scripts.$entryPoint = 'node verify-generated.mjs' } $package | ConvertTo-Json -Depth 10 | Set-Content "$npmRoot\package.json" @@ -30,13 +43,17 @@ writeFileSync('action-ran.txt', source); function Invoke-NpmFixture { param([string]$Root, [string]$EntryPoint) Push-Location $Root + $savedNodeOptions = $env:NODE_OPTIONS try { + $preload = (Join-Path $Root 'fake-cli.cjs').Replace('\', '\\') + $env:NODE_OPTIONS = "--require `"$preload`"" $output = & $npm run $EntryPoint --ignore-scripts=false 2>&1 return @{ ExitCode = $LASTEXITCODE Output = $output -join "`n" } } finally { + $env:NODE_OPTIONS = $savedNodeOptions Pop-Location } } @@ -60,8 +77,6 @@ Describe 'Standalone npm entry points' { @{ EntryPoint = 'compile' } @{ EntryPoint = 'compile:watch' } @{ EntryPoint = 'test' } - @{ EntryPoint = 'generate-docs' } - @{ EntryPoint = 'generate-docs:check' } @{ EntryPoint = 'prepublishOnly' } ) { Join-Path $root 'src\winapp-commands.ts' | Should -Not -Exist @@ -69,21 +84,56 @@ Describe 'Standalone npm entry points' { $result.ExitCode | Should -Be 0 -Because $result.Output Join-Path $root 'src\winapp-commands.ts' | Should -Exist Join-Path $root 'action-ran.txt' | Should -Exist + $trace = Get-Content (Join-Path $root 'cli-trace.jsonl') | ForEach-Object { $_ | ConvertFrom-Json } + $trace.Count | Should -Be 2 + $trace[0].args[0] | Should -Be 'build' + $trace[1].args | Should -Contain '--no-build' + $trace[1].args | Should -Contain '--cli-schema' } It ' stops before consuming commands when generation fails' -ForEach @( @{ EntryPoint = 'compile' } @{ EntryPoint = 'compile:watch' } @{ EntryPoint = 'test' } - @{ EntryPoint = 'generate-docs' } - @{ EntryPoint = 'generate-docs:check' } @{ EntryPoint = 'prepublishOnly' } ) { - Set-Content (Join-Path $root '..\..\docs\cli-schema.json') '{invalid' + Set-Content (Join-Path $root 'schema.json') '{invalid' $result = Invoke-NpmFixture $root $EntryPoint $result.ExitCode | Should -Not -Be 0 $result.Output | Should -Match 'SyntaxError' Join-Path $root 'src\winapp-commands.ts' | Should -Not -Exist Join-Path $root 'action-ran.txt' | Should -Not -Exist } + + It 'does not use an obsolete documentation snapshot when bootstrapping' { + Set-Content (Join-Path $root '..\..\docs\cli-schema.json') '{invalid' + $result = Invoke-NpmFixture $root 'compile' + $result.ExitCode | Should -Be 0 -Because $result.Output + Get-Content (Join-Path $root 'src\winapp-commands.ts') -Raw | Should -Match 'export async function init' + } + + It 'uses a built CLI without rebuilding it' { + $arch = if ((node -p 'process.arch') -eq 'arm64') { 'win-arm64' } else { 'win-x64' } + New-Item -ItemType Directory -Path "$root\bin\$arch" -Force | Out-Null + Set-Content "$root\bin\$arch\winapp.exe" 'fixture' + $result = Invoke-NpmFixture $root 'compile' + $result.ExitCode | Should -Be 0 -Because $result.Output + $trace = @(Get-Content (Join-Path $root 'cli-trace.jsonl') | ForEach-Object { $_ | ConvertFrom-Json }) + $trace.Count | Should -Be 1 + $trace[0].file | Should -BeLike '*winapp.exe' + } + + It 'does not consume stale generated commands after a CLI build failure' { + Set-Content (Join-Path $root 'src\winapp-commands.ts') '// AUTO-GENERATED: export interface CommonOptions' + $saved = $env:FIXTURE_FAIL_BUILD + try { + $env:FIXTURE_FAIL_BUILD = '1' + $result = Invoke-NpmFixture $root 'compile' + } finally { + $env:FIXTURE_FAIL_BUILD = $saved + } + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'CLI build failed' + Join-Path $root 'action-ran.txt' | Should -Not -Exist + } } diff --git a/scripts/validate-llm-docs.ps1 b/scripts/validate-llm-docs.ps1 index 863da42cb..e7fd1bd6b 100644 --- a/scripts/validate-llm-docs.ps1 +++ b/scripts/validate-llm-docs.ps1 @@ -1,10 +1,10 @@ #!/usr/bin/env pwsh <# .SYNOPSIS - Validate that the generated CLI schema and plugin manifest versions are current + Validate plugin examples against the built CLI and check plugin manifest versions .DESCRIPTION - This script compares docs/cli-schema.json with the CLI's --cli-schema output - and verifies that every plugin manifest version matches version.json. Plugin + This script extracts a fresh CLI schema into ignored artifacts and verifies + that every plugin manifest version matches version.json. Plugin skills are hand-authored and are not generated or drift-checked. It also runs scripts/validate-plugin-package.ps1, which enforces Agent Plugins @@ -25,7 +25,8 @@ if (-not $CliPath) { $CliPath = Join-Path $ProjectRoot "artifacts\cli\win-x64\winapp.exe" } -$SchemaPath = Join-Path $ProjectRoot "docs\cli-schema.json" +$SchemaDirectory = Join-Path $ProjectRoot "artifacts\docs" +$SchemaPath = Join-Path $SchemaDirectory "cli-schema.json" $BaseVersion = (Get-Content (Join-Path $ProjectRoot "version.json") | ConvertFrom-Json).version $HasDrift = $false @@ -40,58 +41,10 @@ Write-Host "CLI path: $CliPath" -ForegroundColor Gray $PluginPackageScript = Join-Path $PSScriptRoot "validate-plugin-package.ps1" -if (-not (Test-Path $SchemaPath)) { - Write-Host "::error::docs/cli-schema.json not found. Run 'scripts/build-cli.ps1' to regenerate it." -ForegroundColor Red - $HasDrift = $true -} -else { - $prevEncoding = [Console]::OutputEncoding - try { - [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) - $FreshSchemaLines = & $CliPath --cli-schema - } - finally { - [Console]::OutputEncoding = $prevEncoding - } - - if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to extract CLI schema" - exit 1 - } - - $FreshSchema = (($FreshSchemaLines -join "`n") -replace "`r`n", "`n") - $CommittedSchema = ([System.IO.File]::ReadAllText($SchemaPath, [System.Text.UTF8Encoding]::new($false))) -replace "`r`n", "`n" - - try { - $FreshObj = $FreshSchema | ConvertFrom-Json -Depth 100 - } - catch { - Write-Error "CLI returned invalid schema JSON: $($_.Exception.Message)" - exit 1 - } - - $CommittedObj = $null - try { - $CommittedObj = $CommittedSchema | ConvertFrom-Json -Depth 100 - } - catch { - Write-Host "::error::docs/cli-schema.json contains invalid JSON: $($_.Exception.Message)" -ForegroundColor Red - $HasDrift = $true - } - - if ($CommittedObj) { - $FreshObj.version = $BaseVersion - $FreshNormalized = $FreshObj | ConvertTo-Json -Depth 100 -Compress - $CommittedNormalized = $CommittedObj | ConvertTo-Json -Depth 100 -Compress - - if ($FreshNormalized -ne $CommittedNormalized) { - Write-Host "::error::docs/cli-schema.json is out of sync with CLI!" -ForegroundColor Red - $HasDrift = $true - } - else { - Write-Host "[VALIDATE] docs/cli-schema.json is up-to-date" -ForegroundColor Green - } - } +& (Join-Path $PSScriptRoot "generate-llm-docs.ps1") -CliPath $CliPath -OutputPath $SchemaDirectory -CalledFromBuildScript +if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to generate the current CLI schema" + exit 1 } $ManifestPaths = @( @@ -130,7 +83,7 @@ foreach ($manifestPath in $ManifestPaths) { if (Test-Path $PluginPackageScript) { Write-Host "" # Let the child signal failure via its exit code; -FailOnDrift decides whether it is fatal. - & $PluginPackageScript + & $PluginPackageScript -CliSchemaPath $SchemaPath if ($LASTEXITCODE -ne 0) { $HasDrift = $true } @@ -142,13 +95,13 @@ else { if ($HasDrift) { Write-Host "" - Write-Host "Run 'scripts/build-cli.ps1' locally, then commit the regenerated schema and manifests." -ForegroundColor Yellow + Write-Host "Fix the reported plugin errors; run 'scripts/build-cli.ps1' to synchronize manifest versions." -ForegroundColor Yellow if ($FailOnDrift) { exit 1 } } else { - Write-Host "[VALIDATE] CLI schema and plugin manifests are up-to-date!" -ForegroundColor Green + Write-Host "[VALIDATE] Plugin examples match the current CLI and manifest versions are up-to-date!" -ForegroundColor Green } exit 0 diff --git a/scripts/validate-plugin-package.ps1 b/scripts/validate-plugin-package.ps1 index cf9c2da6d..9ab7904c1 100644 --- a/scripts/validate-plugin-package.ps1 +++ b/scripts/validate-plugin-package.ps1 @@ -14,7 +14,7 @@ It also checks the skills of every plugin root under -PluginsRoot (any folder holding plugin.json and a skills/ folder): SKILL.md frontmatter and placement, description length, relative links staying inside the plugin, and `winapp` command examples in - skills and agents matching docs/cli-schema.json. It then prints an approximate size report. + skills and agents matching an explicitly supplied CLI schema. It then prints an approximate size report. Requires no build output and can be run standalone: .\scripts\validate-plugin-package.ps1 @@ -24,11 +24,14 @@ Path to the winapp plugin package root (default: /winapp) .PARAMETER FailOnError Exit with code 1 when a conformance error is found (default: true) +.PARAMETER CliSchemaPath + Current CLI schema for command-example validation. Without it, only structural checks run. #> param( [string]$PluginsRoot = "", [string]$PluginRoot = "", + [string]$CliSchemaPath = "", [switch]$FailOnError = $true ) @@ -309,14 +312,22 @@ function New-CommandNode($Schema) { } $CommandTree = $null -$CliSchemaPath = Join-Path $ProjectRoot "docs/cli-schema.json" $NpmCliPath = Join-Path $ProjectRoot "src/winapp-npm/src/cli.ts" -if (-not (Test-Path $CliSchemaPath -PathType Leaf)) { - Add-Failure "docs/cli-schema.json not found; it is needed to check winapp command examples in skills. Run scripts/build-cli.ps1 to regenerate it." +if (-not $CliSchemaPath) { + Write-Host "[VALIDATE] Command examples are not checked without -CliSchemaPath; the post-build validation checks them against the current CLI." -ForegroundColor Yellow +} +elseif (-not (Test-Path $CliSchemaPath -PathType Leaf)) { + Add-Failure "CLI schema not found at $CliSchemaPath." } else { - $cliSchema = Read-JsonFile $CliSchemaPath "docs/cli-schema.json" - if ($cliSchema) { + $cliSchema = Read-JsonFile $CliSchemaPath "CLI schema" + if ($cliSchema -and ($cliSchema.name -ne 'winapp' -or + $cliSchema.subcommands -isnot [System.Management.Automation.PSCustomObject] -or + @($cliSchema.subcommands.PSObject.Properties).Count -eq 0)) { + Add-Failure "CLI schema is invalid: expected the winapp command tree with at least one subcommand." + } + elseif ($cliSchema) { + Write-Host "[VALIDATE] Checking command examples against $CliSchemaPath" -ForegroundColor Blue $CommandTree = New-CommandNode $cliSchema $npmCli = if (Test-Path $NpmCliPath -PathType Leaf) { [System.IO.File]::ReadAllText($NpmCliPath, $Utf8) } else { "" } if ($npmCli -match 'const NODE_SUBCOMMANDS = \[([^\]]*)\]') { @@ -363,7 +374,7 @@ function Test-CommandExample([string]$Line, [string]$Where) { } # Only a command that has subcommands and no positional arguments makes this an error. if ($node.Subcommands.Count -gt 0 -and -not $node.TakesArguments) { - Add-Failure "$Where uses unknown command '$path $token'. Use a command listed in docs/cli-schema.json (or an npm wrapper command from src/winapp-npm/src/cli.ts)." + Add-Failure "$Where uses unknown command '$path $token'. Use a command listed by 'winapp --cli-schema' (or an npm wrapper command from src/winapp-npm/src/cli.ts)." } break } diff --git a/src/winapp-npm/README.md b/src/winapp-npm/README.md index 2bf09b64c..2ea26b09d 100644 --- a/src/winapp-npm/README.md +++ b/src/winapp-npm/README.md @@ -71,7 +71,7 @@ The full CLI usage can be found here: [Documentation](https://github.com/microso ### Programmatic API -The package also exports typed async functions for all CLI commands and utility helpers, so you can use them directly from TypeScript/JavaScript without spawning a CLI process: +The package also exports typed async functions for all CLI commands and utility helpers, so you can use them directly from TypeScript/JavaScript without managing child processes yourself: ```typescript import { init, packageApp, certGenerate } from '@microsoft/winappcli'; @@ -81,58 +81,12 @@ await certGenerate({ install: true }); await packageApp({ inputFolder: './dist', cert: './devcert.pfx' }); ``` -Full programmatic API reference: [NPM API Documentation](https://github.com/microsoft/WinAppCli/blob/main/docs/npm-usage.md) +For results, errors, cancellation, and UI workflows, see the [NPM programmatic guide](https://github.com/microsoft/WinAppCli/blob/main/docs/npm-usage.md). Your editor's completion and the installed TypeScript declarations provide the full API for your package version. -> **Note — the programmatic API runs the CLI non-interactively.** The wrapper functions capture output and give the native process piped stdin, so commands that would normally prompt cannot do so. For `azSign` in particular this means you must pass either a `metadataFile` or a fully specified identity (`subscription`, `resourceGroup`, `account`, and `profile`), and a non-interactive Azure credential must already be available (for example `AZURE_TENANT_ID`/`AZURE_CLIENT_ID`/`AZURE_CLIENT_SECRET`, OIDC, a managed identity, or an existing `az login` session). Calls that would otherwise require a selection prompt or an interactive `az login` fail instead of prompting. - -#### Cancelling a call - -Every command option object accepts a `signal`. It cancels the whole native invocation and rejects -with an `AbortError`: - -```typescript -const controller = new AbortController(); -setTimeout(() => controller.abort(), 30_000); - -await uiClick({ app: 'notepad', selector: 'btn-save-c3d4', signal: controller.signal }); -``` - -On Windows the child is force-terminated, so the CLI's own cleanup may not run. That is safe — -Windows releases the process's coordination handles and other `winapp ui` processes reclaim its queue -entry — but if the abort lands after the command already had the desktop, UI side effects may already -have happened, and aborting an active recording can leave partial output with no graceful MP4 -finalization. - -#### Driving UI from several workflows - -`winapp ui` commands that touch the physical desktop always arbitrate for it — that is on by default -and cannot be turned off, so two agents can never type into each other's windows. - -What is opt-in is *continuity*. Pass the same `workflowId` to every call that belongs to one logical -workflow and they keep the desktop reserved between invocations for a short idle grace, may overlap -with each other (a recording and the clicks it is recording), and are never interleaved with another -workflow's input: - -```typescript -const workflowId = crypto.randomUUID(); - -await uiClick({ app: 'notepad', selector: 'btn-file-a1b2', workflowId }); -await uiSendKeys({ app: 'notepad', keys: 'hello', workflowId }); -``` - -`workflowId` is applied to the spawned child only — the wrapper never mutates `process.env`, so it -cannot leak into unrelated concurrent calls. Setting `WINAPP_UI_WORKFLOW_ID` in the environment works -too and is inherited by every child. - -Without a `workflowId` each call is a self-contained one-shot: it still waits its turn, but releases -the desktop the moment it finishes. That also means a no-`workflowId` `uiRecord` blocks every other -workflow for its whole duration — to record and click at the same time, give both calls the same -`workflowId`. - -See [UI Automation → Coordinating concurrent UI workflows](https://github.com/microsoft/WinAppCli/blob/main/docs/ui-automation.md#coordinating-concurrent-ui-workflows). - -`uiRecord` still requires a finite positive `durationSec`: `signal` can only stop a recording by -killing it, which does not produce a valid MP4. +Command wrappers run non-interactively. See the guide for +[required inputs](https://github.com/microsoft/WinAppCli/blob/main/docs/npm-usage.md#calls-are-non-interactive), +[cancellation](https://github.com/microsoft/WinAppCli/blob/main/docs/npm-usage.md#cancel-a-call), and +[coordinated UI calls and bounded recordings](https://github.com/microsoft/WinAppCli/blob/main/docs/npm-usage.md#coordinate-ui-calls-and-record-a-bounded-video). ## 🔧 Feedback diff --git a/src/winapp-npm/package.json b/src/winapp-npm/package.json index b0c20bb60..5267a69ff 100644 --- a/src/winapp-npm/package.json +++ b/src/winapp-npm/package.json @@ -10,16 +10,12 @@ "scripts": { "generate-commands": "node scripts/generate-commands.mjs", "generate-commands:check": "node scripts/generate-commands.mjs --check", - "pregenerate-docs": "npm run generate-commands", - "generate-docs": "node scripts/generate-docs.mjs", - "pregenerate-docs:check": "npm run generate-commands", - "generate-docs:check": "node scripts/generate-docs.mjs --check", "precompile": "npm run generate-commands", "compile": "tsc", "precompile:watch": "npm run generate-commands", "compile:watch": "tsc --watch", "pretest": "npm run generate-commands", - "test": "tsc -p tsconfig.test.json && node --test \"dist-test/test/**/*.test.js\"", + "test": "node -e \"require('fs').rmSync('dist-test', {recursive: true, force: true})\" && tsc -p tsconfig.test.json && node --test \"dist-test/test/**/*.test.js\"", "lint": "eslint src/", "lint:fix": "eslint src/ --fix", "format": "prettier --write src/", diff --git a/src/winapp-npm/scripts/generate-commands.mjs b/src/winapp-npm/scripts/generate-commands.mjs index 08668b610..37ffa5945 100644 --- a/src/winapp-npm/scripts/generate-commands.mjs +++ b/src/winapp-npm/scripts/generate-commands.mjs @@ -9,7 +9,7 @@ * node scripts/generate-commands.mjs --check # exit 1 if file would change * node scripts/generate-commands.mjs --schema path # use a specific schema JSON file */ -import { execSync } from 'node:child_process'; +import { execFileSync } from 'node:child_process'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { resolve, join } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -43,20 +43,21 @@ function loadSchema() { const cliPath = candidates.find((p) => existsSync(p)); if (cliPath) { - const raw = execSync(`"${cliPath}" --cli-schema`, { encoding: 'utf8' }); + const raw = execFileSync(cliPath, ['--cli-schema'], { encoding: 'utf8' }); return JSON.parse(raw); } - // Fallback: checked-in schema - const fallback = resolve(NPM_ROOT, '../../docs/cli-schema.json'); - if (existsSync(fallback)) { - return JSON.parse(readFileSync(fallback, 'utf8')); - } - - throw new Error( - 'Cannot locate winapp CLI binary or docs/cli-schema.json.\n' + - 'Build the CLI first (scripts/build-cli.ps1) or ensure docs/cli-schema.json exists.' + const project = resolve(NPM_ROOT, '../../src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj'); + console.error('[generate-commands] No built CLI found. Building the Debug CLI with the .NET SDK.'); + execFileSync('dotnet', ['build', project, '-c', 'Debug', '--nologo', '--verbosity', 'quiet'], { + stdio: ['ignore', 'inherit', 'inherit'], + }); + const raw = execFileSync( + 'dotnet', + ['run', '--project', project, '-c', 'Debug', '--no-build', '--', '--cli-schema'], + { encoding: 'utf8' } ); + return JSON.parse(raw); } // --------------------------------------------------------------------------- diff --git a/src/winapp-npm/scripts/generate-docs.mjs b/src/winapp-npm/scripts/generate-docs.mjs deleted file mode 100644 index 789fef8b2..000000000 --- a/src/winapp-npm/scripts/generate-docs.mjs +++ /dev/null @@ -1,440 +0,0 @@ -#!/usr/bin/env node -/** - * generate-docs.mjs - * - * Auto-generates `docs/npm-usage.md` from the TypeScript source code using - * the TypeScript Compiler API. Extracts all publicly exported functions, - * interfaces, and type aliases from `src/index.ts` (and re-exported modules) - * together with their JSDoc comments and full type information. - * - * Usage: - * node scripts/generate-docs.mjs # generate docs/npm-usage.md - * node scripts/generate-docs.mjs --check # exit 1 if the file would change - */ -import { createRequire } from 'node:module'; -import { existsSync, readFileSync, writeFileSync } from 'node:fs'; -import { resolve } from 'node:path'; - -import { tableCell } from './markdown-table-cell.mjs'; - -const require = createRequire(import.meta.url); -const ts = require('typescript'); - -// --------------------------------------------------------------------------- -// CLI arg parsing -// --------------------------------------------------------------------------- -const cliArgs = process.argv.slice(2); -const checkOnly = cliArgs.includes('--check'); - -const SCRIPT_DIR = import.meta.dirname; -const NPM_ROOT = resolve(SCRIPT_DIR, '..'); -const OUTPUT = resolve(NPM_ROOT, '../../docs/npm-usage.md'); - -// --------------------------------------------------------------------------- -// CommonOptions properties — documented once, skipped in per-function tables -// --------------------------------------------------------------------------- -const COMMON_OPTION_NAMES = new Set(['quiet', 'verbose', 'cwd', 'signal', 'workflowId']); - -// Rendered wherever a section says which inherited options also apply. -const COMMON_OPTION_NOTE = '(`quiet`, `verbose`, `cwd`, `signal`, `workflowId`)'; - -// --------------------------------------------------------------------------- -// Create TypeScript program from tsconfig.json -// --------------------------------------------------------------------------- -const configPath = ts.findConfigFile(NPM_ROOT, ts.sys.fileExists, 'tsconfig.json'); -if (!configPath) throw new Error('tsconfig.json not found'); -const configFile = ts.readConfigFile(configPath, ts.sys.readFile); -const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, NPM_ROOT); -const program = ts.createProgram(parsed.fileNames, parsed.options); -const checker = program.getTypeChecker(); - -// Get module exports of src/index.ts -const indexPath = resolve(NPM_ROOT, 'src/index.ts'); -const indexSource = program.getSourceFile(indexPath); -if (!indexSource) throw new Error('Could not find src/index.ts in program'); -const moduleSym = checker.getSymbolAtLocation(indexSource); -if (!moduleSym) throw new Error('Could not resolve module symbol for index.ts'); -const allExports = checker.getExportsOfModule(moduleSym); - -// --------------------------------------------------------------------------- -// Categorize exports -// --------------------------------------------------------------------------- -function resolveSymbol(sym) { - return sym.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym; -} - -function getSourcePath(sym) { - const decl = sym.declarations?.[0]; - return decl ? decl.getSourceFile().fileName : ''; -} - -function isExternal(sym) { - return getSourcePath(sym).includes('node_modules'); -} - -const cliCommandFns = []; // functions from winapp-commands.ts -const utilityFns = []; // other functions -const typeExports = []; // interfaces & type aliases - -for (const sym of allExports) { - const name = sym.getName(); - if (name === 'default') continue; - // Skip internal helpers — underscore-prefixed names are not part of the public API. - if (name.startsWith('_')) continue; - - const resolved = resolveSymbol(sym); - const src = getSourcePath(resolved); - - if (resolved.flags & (ts.SymbolFlags.Function | ts.SymbolFlags.Method)) { - if (src.includes('winapp-commands')) { - cliCommandFns.push({ name, symbol: resolved }); - } else { - utilityFns.push({ name, symbol: resolved }); - } - } else if (resolved.flags & (ts.SymbolFlags.Interface | ts.SymbolFlags.TypeAlias)) { - typeExports.push({ name, symbol: resolved, external: isExternal(resolved) }); - } -} - -// --------------------------------------------------------------------------- -// Extraction helpers -// --------------------------------------------------------------------------- -function getDoc(sym) { - return ts.displayPartsToString(sym.getDocumentationComment(checker)).trim(); -} - -function getJsDocTags(sym) { - return sym.getJsDocTags(checker) || []; -} - -/** Extract the description text from a @param JSDoc tag */ -function paramTagDesc(tag) { - const text = ts.displayPartsToString(tag.text || []); - // text is typically "paramName - description" - const m = text.match(/^\w+\s*[-–—]\s*/); - if (m) return text.slice(m[0].length).trim(); - const sp = text.indexOf(' '); - return sp !== -1 ? text.slice(sp + 1).trim() : ''; -} - -function isOptionalDecl(decl) { - if (!decl) return false; - if (ts.isPropertySignature(decl) || ts.isPropertyDeclaration(decl)) return !!decl.questionToken; - if (ts.isParameter(decl)) return !!decl.questionToken || !!decl.initializer; - return false; -} - -function getSymType(sym) { - const decl = sym.valueDeclaration || sym.declarations?.[0]; - return checker.getTypeOfSymbolAtLocation(sym, decl || indexSource); -} - -function typeStr(type) { - return checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation); -} - -// --------------------------------------------------------------------------- -// Emit a function section -// --------------------------------------------------------------------------- -function emitFunction(lines, name, symbol, isCLIWrapper) { - const type = getSymType(symbol); - const sigs = type.getCallSignatures(); - if (sigs.length === 0) return; - - const sig = sigs[0]; - const doc = getDoc(symbol); - const retType = typeStr(sig.getReturnType()); - const tags = getJsDocTags(symbol); - - lines.push(`### \`${name}()\``); - lines.push(''); - if (doc) { - lines.push(doc); - lines.push(''); - } - - // --- Signature --- - const paramSegments = sig.parameters.map((p) => { - const pType = typeStr(getSymType(p)); - const decl = p.valueDeclaration; - const opt = isOptionalDecl(decl); - return `${p.getName()}${opt ? '?' : ''}: ${pType}`; - }); - lines.push('```typescript'); - lines.push(`function ${name}(${paramSegments.join(', ')}): ${retType}`); - lines.push('```'); - lines.push(''); - - // --- Parameters / options --- - if (isCLIWrapper) { - // Single options-bag pattern: expand interface props, skip common ones - const param = sig.parameters[0]; - if (param) { - const pType = getSymType(param); - const props = pType.getProperties(); - const filtered = props.filter((p) => !COMMON_OPTION_NAMES.has(p.getName())); - - if (filtered.length === 0) { - lines.push('*Inherits [CommonOptions](#commonoptions) only.*'); - lines.push(''); - } else { - lines.push('**Options:**'); - lines.push(''); - lines.push('| Property | Type | Required | Description |'); - lines.push('|----------|------|----------|-------------|'); - for (const prop of filtered) { - const propDecl = prop.valueDeclaration || prop.declarations?.[0]; - let pt = typeStr(getSymType(prop)); - pt = pt.replace(/\\/g, '\\\\').replace(/\|/g, '\\|'); - const pdoc = tableCell(getDoc(prop)); - const opt = isOptionalDecl(propDecl); - lines.push(`| \`${prop.getName()}\` | \`${pt}\` | ${opt ? 'No' : 'Yes'} | ${pdoc} |`); - } - lines.push(''); - lines.push(`*Also accepts [CommonOptions](#commonoptions) ${COMMON_OPTION_NOTE}.*`); - lines.push(''); - } - } - } else if (sig.parameters.length > 0) { - // Multi-parameter utility function: show a Parameters table - const paramTags = tags.filter((t) => t.name === 'param'); - lines.push('**Parameters:**'); - lines.push(''); - lines.push('| Parameter | Type | Required | Description |'); - lines.push('|-----------|------|----------|-------------|'); - for (const param of sig.parameters) { - const pType = typeStr(getSymType(param)); - const decl = param.valueDeclaration; - const opt = isOptionalDecl(decl); - const pTag = paramTags.find((t) => { - const text = ts.displayPartsToString(t.text || []); - return text.startsWith(param.getName()); - }); - const desc = pTag ? tableCell(paramTagDesc(pTag)) : ''; - lines.push(`| \`${param.getName()}\` | \`${pType.replace(/\\/g, '\\\\').replace(/\|/g, '\\|')}\` | ${opt ? 'No' : 'Yes'} | ${desc} |`); - } - lines.push(''); - } - - // --- @returns tag --- - const retTag = tags.find((t) => t.name === 'returns'); - if (retTag) { - lines.push(`**Returns:** ${ts.displayPartsToString(retTag.text || [])}`); - lines.push(''); - } - - // --- @example tags --- - const exampleTags = tags.filter((t) => t.name === 'example'); - if (exampleTags.length > 0) { - lines.push('**Example:**'); - lines.push(''); - for (const ex of exampleTags) { - const text = ts.displayPartsToString(ex.text || []).trim(); - lines.push('```typescript'); - lines.push(text); - lines.push('```'); - lines.push(''); - } - } - - lines.push('---'); - lines.push(''); -} - -// --------------------------------------------------------------------------- -// Emit an interface / type-alias section -// --------------------------------------------------------------------------- -function emitType(lines, name, symbol, external) { - if (external) { - lines.push(`### \`${name}\``); - lines.push(''); - lines.push('Re-exported from Node.js for convenience. See [Node.js docs](https://nodejs.org/api/child_process.html).'); - lines.push(''); - return; - } - - const doc = getDoc(symbol); - - if (symbol.flags & ts.SymbolFlags.Interface) { - const type = checker.getDeclaredTypeOfSymbol(symbol); - const props = type.getProperties(); - - lines.push(`### \`${name}\``); - lines.push(''); - if (doc) { - lines.push(doc); - lines.push(''); - } - - if (props.length > 0) { - lines.push('| Property | Type | Required | Description |'); - lines.push('|----------|------|----------|-------------|'); - for (const prop of props) { - const propDecl = prop.valueDeclaration || prop.declarations?.[0]; - let pt = typeStr(getSymType(prop)); - pt = pt.replace(/\\/g, '\\\\').replace(/\|/g, '\\|'); - const pdoc = tableCell(getDoc(prop)); - const opt = isOptionalDecl(propDecl); - lines.push(`| \`${prop.getName()}\` | \`${pt}\` | ${opt ? 'No' : 'Yes'} | ${pdoc} |`); - } - lines.push(''); - } - } else if (symbol.flags & ts.SymbolFlags.TypeAlias) { - // For type aliases, get the RHS from the declaration node to avoid circular display - const decl = symbol.declarations?.[0]; - let aliasText = ''; - if (decl && ts.isTypeAliasDeclaration(decl)) { - const rhsType = checker.getTypeAtLocation(decl.type); - // For union types, checker.typeToString expands them properly when given the RHS node type - aliasText = checker.typeToString( - rhsType, - decl, - ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.InTypeAlias - ); - } else { - const type = checker.getDeclaredTypeOfSymbol(symbol); - aliasText = typeStr(type); - } - - lines.push(`### \`${name}\``); - lines.push(''); - if (doc) { - lines.push(doc); - lines.push(''); - } - lines.push('```typescript'); - lines.push(`type ${name} = ${aliasText}`); - lines.push('```'); - lines.push(''); - } -} - -// --------------------------------------------------------------------------- -// npx winapp node commands — read from docs/fragments/node-commands.md -// --------------------------------------------------------------------------- - -const NODE_COMMANDS_FRAGMENT = resolve(NPM_ROOT, '../../docs/fragments/node-commands.md'); - -function emitNodeCommands(lines) { - if (!existsSync(NODE_COMMANDS_FRAGMENT)) { - console.warn(`[generate-docs] Warning: ${NODE_COMMANDS_FRAGMENT} not found — skipping node commands section.`); - return; - } - const fragment = readFileSync(NODE_COMMANDS_FRAGMENT, 'utf8').trimEnd(); - lines.push(fragment); - lines.push(''); -} - -// --------------------------------------------------------------------------- -// Generate the full markdown document -// --------------------------------------------------------------------------- -function generate() { - const lines = []; - const L = (s = '') => lines.push(s); - - L('---'); - L('ms.custom: mslearn'); - L('---'); - L(''); - L(''); - L(); - L('# NPM Package — Programmatic API'); - L(); - L('TypeScript/JavaScript API reference for `@microsoft/winappcli`.'); - L('Each CLI command is available as an async function that captures stdout/stderr and returns a typed result.'); - L('Helper utilities for MSIX identity, Electron debug identity, and build tools are also exported.'); - L(); - - // --- Installation --- - L('## Installation'); - L(); - L('```bash'); - L('npm install @microsoft/winappcli'); - L('```'); - L(); - - // --- Quick start --- - L('## Quick start'); - L(); - L('```typescript'); - L("import { init, packageApp, certGenerate } from '@microsoft/winappcli';"); - L(); - L('// Initialize a new project with defaults'); - L('await init({ useDefaults: true });'); - L(); - L('// Generate a dev certificate'); - L('await certGenerate({ install: true });'); - L(); - L('// Package the built app'); - L("await packageApp({ inputFolder: './dist', cert: './devcert.pfx' });"); - L('```'); - L(); - - // --- Common types (always-present; documented first) --- - L('## Common types'); - L(); - L('Every CLI command wrapper accepts an options object extending `CommonOptions` and returns `Promise`.'); - L(); - const commonTypeNames = ['CommonOptions', 'WinappResult']; - for (const tName of commonTypeNames) { - const entry = typeExports.find((t) => t.name === tName); - if (entry) emitType(lines, entry.name, entry.symbol, entry.external); - } - - // --- CLI command wrappers --- - L('## CLI command wrappers'); - L(); - L(`These functions wrap native \`winapp\` CLI commands. All accept [CommonOptions](#commonoptions) ${COMMON_OPTION_NOTE}.`); - L(); - - for (const { name, symbol } of cliCommandFns) { - emitFunction(lines, name, symbol, true); - } - - // --- Utility functions --- - L('## Utility functions'); - L(); - - for (const { name, symbol } of utilityFns) { - emitFunction(lines, name, symbol, false); - } - - // --- npx winapp node commands (CLI-only, not programmatic exports) --- - emitNodeCommands(lines); - - // --- Types reference (everything not yet documented above) --- - L('## Types reference'); - L(); - - const skipTypes = new Set([...commonTypeNames, 'default']); - for (const { name, symbol, external } of typeExports) { - if (skipTypes.has(name)) continue; - emitType(lines, name, symbol, external); - } - - return lines.join('\n') + '\n'; -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- -const output = generate(); - -if (checkOnly) { - if (!existsSync(OUTPUT)) { - console.error(`[generate-docs] ${OUTPUT} does not exist. Run without --check to generate.`); - process.exit(1); - } - const existing = readFileSync(OUTPUT, 'utf8'); - if (existing !== output) { - console.error( - '[generate-docs] docs/npm-usage.md is out of date.\n' + - 'Run `cd src/winapp-npm && npm run generate-docs` to regenerate.' - ); - process.exit(1); - } - console.log('[generate-docs] docs/npm-usage.md is up to date.'); -} else { - writeFileSync(OUTPUT, output, 'utf8'); - console.log(`[generate-docs] Generated ${OUTPUT}`); -} diff --git a/src/winapp-npm/scripts/markdown-table-cell.mjs b/src/winapp-npm/scripts/markdown-table-cell.mjs deleted file mode 100644 index c6d87c7c1..000000000 --- a/src/winapp-npm/scripts/markdown-table-cell.mjs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation and Contributors. All rights reserved. -// Licensed under the MIT License. - -/** - * Markdown table-cell escaping for the docs generator. - * - * Kept in its own module so it can be unit-tested directly: importing generate-docs.mjs would run - * the whole generator, which builds a TypeScript program and rewrites docs/npm-usage.md. - */ - -/** - * Make arbitrary JSDoc text safe for a single Markdown table cell. - * - * Two separate hazards, both of which have broken this file's tables before: - * - * - A multi-paragraph comment (such as `CommonOptions.signal`) ends the row at its first newline and - * dumps the remainder as body text. - * - A literal `|` splits the row into extra columns. - * - * Backslashes are escaped *first*. Escaping only the pipe is incomplete: text containing `\|` would - * become `\\|`, which Markdown renders as a literal backslash followed by an unescaped pipe — the - * exact breakage the pipe escaping exists to prevent. A trailing lone backslash would likewise - * escape the row's own closing delimiter. - * - * This is for plain Markdown text. Values rendered inside a code span need different treatment, - * because a code span does not process backslash escapes and would show the doubled backslashes. - * - * @param {string | undefined | null} text Raw documentation text. - * @returns {string} A single-line, table-safe cell value. - */ -export function tableCell(text) { - if (!text) return ''; - return text - .replace(/\\/g, '\\\\') - .replace(/\|/g, '\\|') - .replace(/\r?\n\s*\r?\n/g, '

') - .replace(/\r?\n\s*/g, ' ') - .trim(); -} diff --git a/src/winapp-npm/test/markdown-table-cell.test.ts b/src/winapp-npm/test/markdown-table-cell.test.ts deleted file mode 100644 index bab95190a..000000000 --- a/src/winapp-npm/test/markdown-table-cell.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Microsoft Corporation and Contributors. All rights reserved. -// Licensed under the MIT License. - -import { test } from 'node:test'; -import * as assert from 'node:assert/strict'; -import * as path from 'path'; -import { pathToFileURL } from 'url'; - -type TableCell = (text: string | undefined | null) => string; - -// The npm package compiles to CommonJS, so a plain `await import()` would be transpiled to require() -// and fail on an ESM .mjs module. This indirection keeps a real dynamic import at runtime. -const importEsm = new Function('specifier', 'return import(specifier)') as ( - specifier: string -) => Promise<{ tableCell: TableCell }>; - -// npm scripts run from src/winapp-npm, matching how ui-record-guard.test.ts resolves generated files. -const MODULE_URL = pathToFileURL(path.resolve(process.cwd(), 'scripts', 'markdown-table-cell.mjs')).href; - -function loadTableCell(): Promise { - return importEsm(MODULE_URL).then((mod) => mod.tableCell); -} - -// String.raw cannot express a trailing backslash, and stacked escapes are easy to misread, so the -// cases below build their strings from this constant and annotate the characters they contain. -const BS = '\\'; - -/** - * Counts pipes that would still split a Markdown table row. A backslash escapes the character that - * follows it, so `\\` is a literal backslash that protects nothing and `\|` is a safe pipe. - */ -function countUnescapedPipes(cell: string): number { - let count = 0; - for (let i = 0; i < cell.length; i++) { - if (cell[i] === BS) { - i++; // the next character is escaped, whatever it is - continue; - } - if (cell[i] === '|') count++; - } - return count; -} - -test('a backslash before a pipe cannot cancel the pipe escape', async () => { - const tableCell = await loadTableCell(); - - // Escaping only the pipe turns `\|` into `\\|`, which Markdown renders as a literal backslash - // followed by an *unescaped* pipe — so the row splits anyway. - const cell = tableCell(`a${BS}|b`); // a \ | b - - assert.equal(cell, `a${BS}${BS}${BS}|b`); // a \ \ \ | b - assert.equal(countUnescapedPipes(cell), 0); -}); - -test('a trailing backslash cannot escape the row delimiter', async () => { - const tableCell = await loadTableCell(); - - const cell = tableCell(`a path ending in C:${BS}`); // ...C:\ - - assert.equal(cell, `a path ending in C:${BS}${BS}`); // ...C:\\ - assert.ok(cell.endsWith(`${BS}${BS}`), 'the closing pipe this generator emits after the cell must survive'); -}); - -test('Windows paths keep their backslashes literal while pipes stay escaped', async () => { - const tableCell = await loadTableCell(); - - const cell = tableCell(`use C:${BS}Users${BS}me | or D:${BS}tmp`); - - assert.equal(cell, `use C:${BS}${BS}Users${BS}${BS}me ${BS}| or D:${BS}${BS}tmp`); - assert.equal(countUnescapedPipes(cell), 0); -}); - -test('multi-paragraph text collapses to a single line', async () => { - const tableCell = await loadTableCell(); - - const cell = tableCell('First paragraph\nwrapped here.\n\nSecond paragraph.'); - - assert.equal(cell, 'First paragraph wrapped here.

Second paragraph.'); - assert.ok(!/\r|\n/.test(cell), 'a table row must not contain a line break'); -}); - -test('backslash, pipe and newlines survive together', async () => { - const tableCell = await loadTableCell(); - - const cell = tableCell(`Pass a${BS}|b to the filter.\r\n\r\nSee C:${BS}logs\nfor output.`); - - assert.equal(countUnescapedPipes(cell), 0); - assert.ok(!/\r|\n/.test(cell)); - assert.ok(cell.includes('

'), 'the paragraph break must be preserved'); - assert.ok(cell.includes(`C:${BS}${BS}logs`), 'path backslashes must stay literal'); -}); - -test('empty input yields an empty cell', async () => { - const tableCell = await loadTableCell(); - - assert.equal(tableCell(''), ''); - assert.equal(tableCell(undefined), ''); - assert.equal(tableCell(null), ''); -}); - -test('ordinary prose is passed through untouched apart from trimming', async () => { - const tableCell = await loadTableCell(); - - assert.equal(tableCell(' Suppress progress messages. '), 'Suppress progress messages.'); -}); diff --git a/src/winapp-npm/test/npm-usage-doc.test.ts b/src/winapp-npm/test/npm-usage-doc.test.ts index 02e49550e..5a2519539 100644 --- a/src/winapp-npm/test/npm-usage-doc.test.ts +++ b/src/winapp-npm/test/npm-usage-doc.test.ts @@ -3,74 +3,62 @@ import { test } from 'node:test'; import * as assert from 'node:assert/strict'; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as ts from 'typescript'; -// docs/npm-usage.md is generated by scripts/generate-docs.mjs from the TypeScript API surface. A -// multi-paragraph JSDoc comment (CommonOptions.signal) used to be emitted verbatim, so its embedded -// newlines terminated the table row and dumped the rest as body text — in every command table. -// npm scripts run from src/winapp-npm, matching how ui-record-guard.test.ts resolves generated files. -const DOC_PATH = path.resolve(process.cwd(), '..', '..', 'docs', 'npm-usage.md'); +const NPM_ROOT = process.cwd(); +const DOC_PATH = path.resolve(NPM_ROOT, '..', '..', 'docs', 'npm-usage.md'); +const doc = fs.readFileSync(DOC_PATH, 'utf8'); -function readDoc(): string[] { - return fs.readFileSync(DOC_PATH, 'utf8').split(/\r?\n/); -} - -test('every generated Markdown table row is a single well-formed line', () => { - const lines = readDoc(); - const broken: string[] = []; - let inFence = false; - - for (const line of lines) { - if (line.startsWith('```')) { - inFence = !inFence; - continue; - } - if (inFence || !line.startsWith('|')) continue; - if (!line.trimEnd().endsWith('|')) broken.push(line); +test('npm usage is a maintained guide rather than a generated reference', () => { + assert.ok(!doc.includes('AUTO-GENERATED')); + assert.ok(!doc.includes('npm run generate-docs')); + for (const contract of ['stdout', 'stderr', 'AbortError', 'workflowId', 'durationSec', 'non-interactive']) { + assert.ok(doc.includes(contract), `guide must explain ${contract}`); } - - assert.deepEqual(broken, [], 'table rows must open and close with a pipe on one line'); -}); - -test('CommonOptions properties are not repeated in per-command option tables', () => { - const lines = readDoc(); - const start = lines.indexOf('## CLI command wrappers'); - const end = lines.indexOf('## Utility functions'); - assert.ok(start >= 0 && end > start, 'expected a CLI command wrappers section'); - - // The trailing "Types reference" section deliberately expands every exported interface, inherited - // members included; only the per-command tables must stay free of CommonOptions noise. - const wrapperSection = lines.slice(start, end); - const leaked = wrapperSection.filter((line) => - ['quiet', 'verbose', 'cwd', 'signal'].some((prop) => line.startsWith(`| \`${prop}\``)) - ); - - assert.deepEqual(leaked, [], 'CommonOptions belong in the shared note, not in each command table'); }); -test('the single CommonOptions signal row carries the full cancellation contract', () => { - const lines = readDoc(); - const start = lines.indexOf('### `CommonOptions`'); - const end = lines.indexOf('### `WinappResult`'); - assert.ok(start >= 0 && end > start, 'expected a CommonOptions section'); - - const signalRows = lines.slice(start, end).filter((line) => line.startsWith('| `signal`')); - assert.equal(signalRows.length, 1); - assert.ok( - signalRows[0].includes('AbortError'), - 'the flattened signal row must keep its whole multi-paragraph contract' - ); +test('npm usage relative links resolve to tracked documentation', () => { + for (const match of doc.matchAll(/\]\(([^)]+)\)/g)) { + const target = match[1].split('#')[0]; + if (!target || /^[a-z]+:/i.test(target)) continue; + assert.ok(fs.existsSync(path.resolve(path.dirname(DOC_PATH), target)), `missing link: ${target}`); + } }); -test('the shared-options note lists every CommonOptions property', () => { - const doc = fs.readFileSync(DOC_PATH, 'utf8'); - const notes = doc.match(/\[CommonOptions\]\(#commonoptions\) \(([^)]*)\)/g) ?? []; - - assert.ok(notes.length > 0, 'expected at least one shared-options note'); - for (const note of notes) { - for (const prop of ['quiet', 'verbose', 'cwd', 'signal']) { - assert.ok(note.includes(`\`${prop}\``), `${note} must mention ${prop}`); - } +test('every TypeScript example in the npm guide type-checks against the public API', () => { + const examples = [...doc.matchAll(/```typescript\r?\n([\s\S]*?)```/g)]; + assert.ok(examples.length > 0, 'guide must include runnable TypeScript examples'); + for (const [index, match] of examples.entries()) { + const file = path.resolve(NPM_ROOT, `guide-example-${index}.mts`); + const options: ts.CompilerOptions = { + strict: true, + noEmit: true, + skipLibCheck: true, + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + paths: { '@microsoft/winappcli': [path.resolve(NPM_ROOT, 'src', 'index.ts')] }, + }; + const host = ts.createCompilerHost(options); + const fileExists = host.fileExists.bind(host); + host.fileExists = (name) => path.resolve(name) === file || fileExists(name); + const readSource = host.getSourceFile.bind(host); + host.getSourceFile = (name, languageVersion, onError, shouldCreateNewSourceFile) => + path.resolve(name) === file + ? ts.createSourceFile(name, match[1], languageVersion) + : readSource(name, languageVersion, onError, shouldCreateNewSourceFile); + const program = ts.createProgram([file], options, host); + const diagnostics = ts.getPreEmitDiagnostics(program); + assert.equal( + diagnostics.length, + 0, + `example ${index + 1}: ${ts.formatDiagnosticsWithColorAndContext(diagnostics, { + getCanonicalFileName: (name) => name, + getCurrentDirectory: () => NPM_ROOT, + getNewLine: () => '\n', + })}` + ); } });