# AutoShell

A Windows shell automation console application that provides a JSON-based command interface for controlling Windows applications, audio settings, themes, and window management.

## Overview

AutoShell is part of the [TypeAgent](https://github.com/microsoft/TypeAgent) project. It runs as a console application that reads JSON commands from stdin and executes Windows shell operations. This enables programmatic control of the Windows desktop environment.

## Features

- **Application Management**: Launch, close, and switch between applications using friendly names
- **Window Management**: Maximize, minimize, and tile windows side-by-side
- **Audio Control**: Set volume levels, mute/unmute, and restore previous volume
- **Theme & Personalization**: Apply themes, set wallpaper, toggle transparency, and configure title bar colors
- **Virtual Desktop Management**: Create, switch, pin, and move windows across virtual desktops
- **Display Settings**: Set resolution, brightness, scaling, orientation, color temperature, and blue light filter
- **Network & Connectivity**: Wi-Fi, Bluetooth, airplane mode, and metered connection controls
- **Mouse & Touchpad**: Cursor speed, pointer size, scroll lines, touchpad settings, and cursor trail
- **Taskbar Customization**: Alignment, auto-hide, badges, Task View, Widgets, and multi-monitor display
- **Accessibility**: Narrator, Magnifier, Sticky Keys, Filter Keys, and mono audio
- **Privacy Controls**: Manage camera, microphone, and location access
- **Power Management**: Battery saver levels and power mode configuration
- **System Settings**: Notifications, game mode, focus assist, time settings, and multi-monitor behavior
- **File Explorer**: Toggle file extensions and hidden/system file visibility

## Requirements

- Windows 10/11
- .NET 8
- [Microsoft.WindowsAPICodePack.Shell](https://www.nuget.org/packages/Microsoft.WindowsAPICodePack.Shell) NuGet package
- [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/api/system.text.json) (built-in)
- Node.js ≥ 20 and pnpm (for generating `.pas.json` schemas from TypeScript)

## Building

AutoShell requires `.pas.json` schema files generated by the TypeScript action schema compiler (`asc`). Build the TS side first, then the dotnet project:

```bash
# 1. Build TS desktop package (generates .pas.json schemas)
cd ts
pnpm install
pnpm run --filter @typeagent/action-schema-compiler... build
pnpm run -C packages/agents/desktop asc:all

# 2. Build AutoShell
cd ../dotnet
dotnet build autoShell/autoShell.csproj
```

If you've already run `pnpm run build` in the `ts/` workspace, step 1 is already done.

## Usage

AutoShell runs in two modes:

**Interactive mode** (default): Run the application and send JSON commands via stdin, one per line:
```
dotnet run --project autoShell.csproj
{"actionName":"Volume","parameters":{"targetVolume":50}}
{"actionName":"Mute","parameters":{"on":true}}
{"actionName":"quit","parameters":{}}
```

**Command-line mode**: Pass a JSON command (or array) as an argument for one-shot execution:
```
autoShell.exe {"actionName":"Volume","parameters":{"targetVolume":50}}
autoShell.exe [{"actionName":"Volume","parameters":{"targetVolume":50}},{"actionName":"Mute","parameters":{"on":true}}]
```

Each command returns a JSON `ActionResult` on stdout:
```json
{"success":true,"message":"Volume set to 50"}
{"success":false,"message":"Invalid parameters for 'Volume': ..."}
```

### Command Reference

#### Core Commands

| Command | Parameter | Description |
|---------|-----------|-------------|
| `ApplyTheme` | Theme name | Applies a Windows theme |
| `CloseProgram` | Application name | Closes an application |
| `ConnectWifi` | `{"ssid": "name", "password": "pass"}` | Connects to a Wi-Fi network |
| `CreateDesktop` | JSON array of names | Creates one or more virtual desktops |
| `Debug` | (none) | Launches the debugger |
| `DisconnectWifi` | (none) | Disconnects from the current Wi-Fi network |
| `LaunchProgram` | Application name | Opens an application (or raises if already running) |
| `ListAppNames` | (none) | Outputs installed applications as JSON |
| `ListResolutions` | (none) | Outputs available display resolutions as JSON |
| `ListThemes` | (none) | Outputs installed themes as JSON |
| `ListWifiNetworks` | (none) | Lists available Wi-Fi networks as JSON |
| `Maximize` | Application name | Maximizes the application window |
| `Minimize` | Application name | Minimizes the application window |
| `MoveWindowToDesktop` | `{"process": "app", "desktop": "name"}` | Moves a window to a specific virtual desktop |
| `Mute` | `true`/`false` | Mutes or unmutes system audio |
| `NextDesktop` | (none) | Switches to the next virtual desktop |
| `PinWindow` | Application name | Pins a window to appear on all virtual desktops |
| `PreviousDesktop` | (none) | Switches to the previous virtual desktop |
| `quit` | (none) | Exits the application |
| `RestoreVolume` | (none) | Restores previously saved volume level |
| `SetScreenResolution` | `"WIDTHxHEIGHT"` or `{"width": W, "height": H}` | Sets the display resolution |
| `SetTextSize` | `100-225` | Sets system text scaling percentage |
| `SetThemeMode` | `"light"`, `"dark"`, `"toggle"`, or boolean | Sets light/dark mode |
| `SetWallpaper` | File path | Sets the desktop wallpaper |
| `SwitchDesktop` | Index or name | Switches to a virtual desktop by index or name |
| `SwitchTo` | Application name | Brings application window to foreground |
| `Tile` | `"app1,app2"` | Tiles two applications side-by-side |
| `ToggleAirplaneMode` | `true`/`false` | Enables or disables Windows airplane mode |
| `ToggleNotifications` | (none) | Toggles the Windows notification center |
| `Volume` | `0-100` | Sets system volume percentage |
| `AdjustVolume` | `{"direction": "up"/"down", "amount": 10}` | Adjusts volume relatively (default ±10%) |

#### Settings Commands

##### Network Settings

| Command | Parameter | Description |
|---------|-----------|-------------|
| `BluetoothToggle` | `{"enableBluetooth": true/false}` | Toggles Bluetooth on/off |
| `EnableMeteredConnections` | (none) | Opens network status settings |
| `EnableWifi` | `{"enable": true/false}` | Enables or disables Wi-Fi |

##### Display Settings

| Command | Parameter | Description |
|---------|-----------|-------------|
| `AdjustColorTemperature` | (none) | Opens Night light settings |
| `AdjustScreenBrightness` | `{"brightnessLevel": "increase"/"decrease"}` | Adjusts screen brightness ±10% |
| `AdjustScreenOrientation` | (none) | Opens display settings |
| `DisplayResolutionAndAspectRatio` | (none) | Opens display settings |
| `DisplayScaling` | `{"sizeOverride": "150"}` | Opens display settings; targets a DPI percentage |
| `EnableBlueLightFilterSchedule` | `{"nightLightScheduleDisabled": true/false}` | Enables or disables blue light filter schedule |
| `RotationLock` | `{"enable": true/false}` | Enables or disables rotation lock |

##### Personalization Settings

| Command | Parameter | Description |
|---------|-----------|-------------|
| `ApplyColorToTitleBar` | `{"enableColor": true/false}` | Applies accent color to title bars |
| `EnableTransparency` | `{"enable": true/false}` | Enables or disables transparency effects |
| `HighContrastTheme` | (none) | Opens high contrast settings |
| `SystemThemeMode` | `{"mode": "light"/"dark"}` | Sets the system theme mode |

##### Taskbar Settings

| Command | Parameter | Description |
|---------|-----------|-------------|
| `AutoHideTaskbar` | `{"hideWhenNotUsing": true/false}` | Auto-hides the taskbar |
| `DisplaySecondsInSystrayClock` | `{"enable": true/false}` | Shows seconds in system tray clock |
| `DisplayTaskbarOnAllMonitors` | `{"enable": true/false}` | Displays taskbar on all monitors |
| `ShowBadgesOnTaskbar` | `{"enableBadging": true/false}` | Shows or hides badges on taskbar |
| `TaskbarAlignment` | `{"alignment": "left"/"center"}` | Sets taskbar alignment |
| `TaskViewVisibility` | `{"visibility": true/false}` | Shows or hides Task View button |
| `ToggleWidgetsButtonVisibility` | `{"visibility": "show"/"hide"}` | Shows or hides Widgets button |

##### Mouse & Touchpad Settings

| Command | Parameter | Description |
|---------|-----------|-------------|
| `AdjustMousePointerSize` | (none) | Opens mouse pointer settings |
| `CursorTrail` | `{"enable": true/false, "length": 2-12}` | Enables/disables cursor trail (length: 2–12) |
| `EnableTouchPad` | (none) | Opens touchpad settings |
| `EnhancePointerPrecision` | `{"enable": true/false}` | Enables or disables pointer precision |
| `MouseCursorSpeed` | `{"speedLevel": 1-20}` | Sets mouse cursor speed (default 10) |
| `MousePointerCustomization` | (none) | Opens mouse pointer settings |
| `MouseWheelScrollLines` | `{"scrollLines": 1-100}` | Sets mouse wheel scroll lines (default 3) |
| `SetPrimaryMouseButton` | `{"primaryButton": "left"/"right"}` | Sets primary mouse button |
| `ToggleMouseSonar` | `{"enable": true/false}` | Enables/disables "Find my pointer" Ctrl ring |
| `TouchpadCursorSpeed` | (none) | Opens touchpad settings |

##### Privacy Settings

| Command | Parameter | Description |
|---------|-----------|-------------|
| `ManageCameraAccess` | `{"accessSetting": "allow"/"deny"}` | Manages camera access |
| `ManageLocationAccess` | `{"accessSetting": "allow"/"deny"}` | Manages location access |
| `ManageMicrophoneAccess` | `{"accessSetting": "allow"/"deny"}` | Manages microphone access |

##### Power Settings

| Command | Parameter | Description |
|---------|-----------|-------------|
| `BatterySaverActivationLevel` | `{"thresholdValue": 0-100}` | Sets battery saver activation level |
| `SetPowerModeOnBattery` | (none) | Opens power settings |
| `SetPowerModePluggedIn` | (none) | Opens power settings |

##### Accessibility Settings

| Command | Parameter | Description |
|---------|-----------|-------------|
| `EnableFilterKeysAction` | (none) | Toggles Filter Keys |
| `EnableMagnifier` | (none) | Toggles Magnifier |
| `EnableNarratorAction` | (none) | Toggles Narrator |
| `EnableStickyKeys` | (none) | Toggles Sticky Keys |
| `MonoAudioToggle` | (none) | Toggles mono audio |

##### File Explorer Settings

| Command | Parameter | Description |
|---------|-----------|-------------|
| `ShowFileExtensions` | `{"enable": true/false}` | Shows or hides file extensions |
| `ShowHiddenAndSystemFiles` | `{"enable": true/false}` | Shows or hides hidden and system files |

##### System Settings

| Command | Parameter | Description |
|---------|-----------|-------------|
| `AutomaticDSTAdjustment` | `{"enable": true/false}` | Enables or disables automatic DST adjustment |
| `AutomaticTimeSettingAction` | (none) | Opens date/time settings |
| `EnableGameMode` | (none) | Opens Game Mode settings |
| `EnableQuietHours` | (none) | Opens quiet hours / focus assist settings |
| `MinimizeWindowsOnMonitorDisconnectAction` | (none) | Opens display settings |
| `RememberWindowLocations` | (none) | Opens display settings |

### Examples

Launch a program:
```json
{"LaunchProgram": "notepad"} 
```

Set the system volume at 50%:
```json
{"Volume": 50} 
```

Tile notepad on the left and calculator on the right of the screen:
```json
{"Tile": "notepad,calculator"} 
```

Apply the 'dark' Windows theme:
```json
{"ApplyTheme": "dark"} 
```

Set dark mode:
```json
{"SetThemeMode": "dark"}
```

Toggle between light and dark mode:
```json
{"SetThemeMode": "toggle"}
```

Mute the system audio:
```json
{"Mute": true} 
```

Set the desktop wallpaper:
```json
{"SetWallpaper": "C:\\Users\\Public\\Pictures\\wallpaper.jpg"}
```

Quit AutoShell:
```json
{"quit": null}
```

Create a new virtual desktop named "Design Work":
```json
{"CreateDesktop": "Design Work"}
```

Toggle the Windows notification center:
```json
{"ToggleNotifications": true}
```

Enable airplane mode:
```json
{"ToggleAirplaneMode": true}
```

Disable airplane mode:
```json
{"ToggleAirplaneMode": false}
```

List available Wi-Fi networks:
```json
{"ListWifiNetworks": true}
```

Connect to a Wi-Fi network:
```json
{"ConnectWifi": {"ssid": "MyNetwork", "password": "MyPassword123"}}
```

Set system text size to 125%:
```json
{"SetTextSize": 125}
```

List available display resolutions:
```json
{"ListResolutions": true}
```

Set display resolution to 1920x1080:
```json
{"SetScreenResolution": "1920x1080"}
```

Set display resolution with specific refresh rate:
```json
{"SetScreenResolution": "1920x1080@144"}
```

Set display resolution using JSON object:
```json
{"SetScreenResolution": {"width": 2560, "height": 1440, "refreshRate": 60}}
```

Enable cursor trail with length 7:
```json
{"CursorTrail": "{\"enable\":true,\"length\":7}"}
```

Disable cursor trail:
```json
{"CursorTrail": "{\"enable\":false}"}
```

### Supported Application Friendly Names

AutoShell recognizes these friendly names (case-insensitive):

- `chrome`, `edge`, `microsoft edge`
- `word`, `winword`, `excel`, `powerpoint`, `power point`, `outlook`
- `visual studio`, `visual studio code`
- `notepad`, `paint`, `paint 3d`, `calculator`
- `file explorer`, `control panel`, `task manager`
- `cmd`, `powershell`
- `snipping tool`, `magnifier`
- `spotify`, `copilot`, `m365 copilot`

Additionally, AutoShell automatically discovers all installed Windows Store applications by their display names.

## Architecture

AutoShell uses a handler-based architecture with dependency injection. All platform-specific (P/Invoke, COM, WMI) code is isolated behind service interfaces, keeping handlers thin and fully unit-testable.

A **Roslyn source generator** (`autoShell.Generators`) reads the `.pas.json` schema files at build time and generates strongly-typed C# record classes for each action's parameters. Handlers can use these generated types via `AddAction<T>` instead of manually extracting fields from `JsonElement`.

```
autoShell/
├── AutoShell.cs              # Entry point — stdin/stdout command loop
├── ActionDispatcher.cs       # Routes JSON keys to handlers; Create() wires all dependencies
├── IAppRegistry.cs           # App registry interface (shared across handlers)
├── WindowsAppRegistry.cs     # Maps friendly app names to paths and AppUserModelIDs
├── Handlers/
│   ├── IActionHandler.cs     # Handler interface (SupportedActions + Handle)
│   ├── ActionHandlerBase.cs  # Base class — AddAction/AddAction<T> registration + dispatch
│   ├── AppActionHandler.cs           # LaunchProgram, CloseProgram, ListAppNames
│   ├── AudioActionHandler.cs         # Volume, Mute, RestoreVolume
│   ├── DisplayActionHandler.cs       # SetScreenResolution, ListResolutions, SetTextSize
│   ├── NetworkActionHandler.cs       # ConnectWifi, ToggleAirplaneMode, etc.
│   ├── SystemActionHandler.cs        # Debug, ToggleNotifications
│   ├── ThemeActionHandler.cs         # ApplyTheme, ListThemes, SetThemeMode, SetWallpaper
│   ├── VirtualDesktopActionHandler.cs  # CreateDesktop, SwitchDesktop, PinWindow, etc.
│   ├── WindowActionHandler.cs        # Maximize, Minimize, SwitchTo, Tile
│   └── Settings/
│       ├── SettingsHandlerBase.cs     # Extends ActionHandlerBase with registry patterns
│       ├── AccessibilitySettingsHandler.cs
│       ├── DisplaySettingsHandler.cs
│       ├── FileExplorerSettingsHandler.cs
│       ├── MouseSettingsHandler.cs
│       ├── PersonalizationSettingsHandler.cs
│       ├── PowerSettingsHandler.cs
│       ├── PrivacySettingsHandler.cs
│       ├── SystemSettingsHandler.cs
│       └── TaskbarSettingsHandler.cs
├── Services/                 # Interfaces + Windows implementations
│   ├── IAudioService.cs / WindowsAudioService.cs
│   ├── IBrightnessService.cs / WindowsBrightnessService.cs
│   ├── IDebuggerService.cs / WindowsDebuggerService.cs
│   ├── IDisplayService.cs / WindowsDisplayService.cs
│   ├── INetworkService.cs / WindowsNetworkService.cs
│   ├── IProcessService.cs / WindowsProcessService.cs
│   ├── IRegistryService.cs / WindowsRegistryService.cs
│   ├── ISystemParametersService.cs / WindowsSystemParametersService.cs
│   ├── IVirtualDesktopService.cs / WindowsVirtualDesktopService.cs
│   ├── IWindowService.cs / WindowsWindowService.cs
│   └── Interop/
│       ├── CoreAudioInterop.cs   # COM interop definitions for Windows audio
│       └── UIAutomation.cs       # UI Automation helpers (last-resort)
├── Logging/
│   ├── ILogger.cs            # Logging interface (Error, Warning, Info, Debug)
│   └── ConsoleLogger.cs      # Colored console + diagnostics output
└── autoShell.Tests/          # unit, integration, and E2E tests

autoShell.Generators/         # Roslyn source generator (netstandard2.0)
├── ActionParamsGenerator.cs  # IIncrementalGenerator entry point
├── SchemaParser.cs           # Parses .pas.json → ActionDefinition list
└── RecordEmitter.cs          # Generates C# records from ActionDefinitions
```

### Key design decisions

- **ActionDispatcher.Create()** is the composition root — it creates all concrete services and wires them into handlers. Tests bypass this and inject mocks directly.
- **Single handler hierarchy** — All handlers inherit from `ActionHandlerBase`, which provides `AddAction(name, handler)` and `AddAction<T>(name, handler)` registration with dictionary-based dispatch. Actions are registered once in the constructor; `SupportedActions` and `Handle()` are auto-derived.
- **Source-generated parameter records** — The `.pas.json` schema files in `ts/packages/agents/desktop/dist/` are the single source of truth for action definitions. A Roslyn source generator reads them at build time and produces strongly-typed C# records (e.g., `MuteParams`, `VolumeParams`) in the `autoShell.Handlers.Generated` namespace. Handlers use `AddAction<T>` to get auto-deserialized parameters.
- **SettingsHandlerBase** extends `ActionHandlerBase` with registry-specific convenience methods: `AddRegistryToggleAction`, `AddRegistryMapAction`, and `AddOpenSettingsAction`. These internally delegate to `AddAction` with wrapper lambdas. Specialized actions use `AddSpecializedAction` directly.
- **Handlers are thin** — they receive typed parameters and delegate to services. No P/Invoke or COM code lives in handlers.
- **Services own all platform calls** — P/Invoke, COM, WMI, and registry access are encapsulated behind interfaces (`I*Service` / `Windows*Service`).
- **ILogger** abstracts all diagnostic output with four levels: Error (red), Warning (yellow), Info (cyan), and Debug (diagnostics only). `ConsoleLogger` preserves the original colored formatting.

### Source generator (`autoShell.Generators`)

The `autoShell.Generators` project is a [Roslyn incremental source generator](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) that runs automatically during every build of `autoShell.csproj`. It reads the `.pas.json` schema files and generates strongly-typed C# parameter records so handlers don't need to manually extract fields from raw JSON.

**How it works:**

1. The `.pas.json` files from `ts/packages/agents/desktop/dist/` are included as `AdditionalFiles` in `autoShell.csproj`
2. During compilation, the Roslyn compiler loads `autoShell.Generators.dll` as an analyzer
3. `ActionParamsGenerator` (the `IIncrementalGenerator` entry point) filters for `.pas.json` files and passes each to the pipeline
4. `SchemaParser` extracts action definitions — action name, parameter names, and types — from the schema JSON structure
5. `RecordEmitter` produces C# source for each action, emitting `internal record` classes with `[JsonPropertyName]` attributes
6. The compiler compiles the generated records alongside the hand-written source code

**Project structure:**

| File | Purpose |
|---|---|
| `ActionParamsGenerator.cs` | `IIncrementalGenerator` entry point — wires the pipeline |
| `SchemaParser.cs` | Parses `.pas.json` → list of `ActionDefinition` (action name + parameter fields) |
| `RecordEmitter.cs` | Generates C# `record` source from `ActionDefinition` list |

**Generated output:**

The generated `.g.cs` files are emitted to `autoShell/Generated/` for inspection (gitignored — they're build artifacts). Each `.pas.json` schema produces one file:

| Schema | Generated file | Example records |
|---|---|---|
| `desktopSchema.pas.json` | `desktopSchema.pas.g.cs` | `VolumeParams`, `MuteParams`, `LaunchProgramParams` |
| `taskbarSchema.pas.json` | `taskbarSchema.pas.g.cs` | `AutoHideTaskbarParams`, `TaskbarAlignmentParams` |
| `displaySchema.pas.json` | `displaySchema.pas.g.cs` | `DisplayScalingParams`, `RotationLockParams` |
| ... | ... | ... |

**Type mapping:**

| `.pas.json` type | C# type | Default |
|---|---|---|
| `number` | `int` | `0` |
| `boolean` | `bool` | `false` |
| `string` | `string` | `""` |
| `string-union` | `string` | `""` |
| `type-union` | resolved inner type | varies |
| Optional field | nullable (`int?`, `bool?`, `string?`) | `null` |

**Constraints:**

- Targets `netstandard2.0` (Roslyn requirement for source generators)
- References `Microsoft.CodeAnalysis.CSharp` and `System.Text.Json` as `PrivateAssets="all"`
- Referenced by `autoShell.csproj` as `OutputItemType="Analyzer"` with `ReferenceOutputAssembly="false"`

## Adding a New Action

This section walks through adding a new action end-to-end. There are three categories of actions, each with a different amount of work required.

### Option A: Registry-based settings action (simplest)

For actions that toggle a registry value or map a string parameter to a registry entry, you only need to add a line in an existing settings handler constructor. No new files, no new classes.

**Example — adding a new registry toggle:**

```csharp
// In an existing SettingsHandler constructor (e.g., SystemSettingsHandler.cs):
AddRegistryToggleAction("MyNewToggle", new RegistryToggleConfig(
    KeyPath: @"SOFTWARE\Microsoft\Windows\CurrentVersion\MyFeature",
    ValueName: "Enabled",
    ParameterName: "enable",
    OnValue: 1,
    OffValue: 0,
    DisplayName: "My New Feature"
));
```

That's it. The base class handles parameter extraction, registry writes, and success/failure responses. Other config-driven options:
- `AddRegistryMapAction` — maps a string parameter (e.g., `"left"/"center"`) to registry values
- `AddOpenSettingsAction` — opens a Windows Settings URI (e.g., `ms-settings:display`)

### Option B: Typed action in an existing handler (most common)

For actions that need custom logic but fit within an existing handler's domain.

**1. Define the action in the TypeScript schema** (e.g., `ts/packages/agents/desktop/src/actionsSchema.ts`):

The TypeScript type definitions are the single source of truth. The `asc` compiler transforms them into `.pas.json` files, which the Roslyn source generator then reads to produce C# parameter records.

```typescript
// In the appropriate *Schema.ts file:
export type MyNewAction = {
    actionName: "MyAction";
    parameters: {
        level: number;
        name?: string;
    };
};
```

Then regenerate the schema:
```bash
cd ts/packages/agents/desktop
pnpm run asc:all
```

After building, this generates:

```csharp
// Auto-generated in autoShell.Handlers.Generated namespace
internal record MyActionParams
{
    [JsonPropertyName("level")]
    public int Level { get; init; } = 0;

    [JsonPropertyName("name")]
    public string? Name { get; init; } = null;
}
```

**2. Register and handle the action** in the appropriate handler:

```csharp
using autoShell.Handlers.Generated;

internal class AudioActionHandler : ActionHandlerBase
{
    public AudioActionHandler(IAudioService audio)
    {
        // ... existing actions ...
        AddAction<MyActionParams>("MyAction", HandleMyAction);
    }

    private ActionResult HandleMyAction(MyActionParams p)
    {
        // p.Level and p.Name are already deserialized and typed
        return ActionResult.Ok($"Did something with level {p.Level}");
    }
}
```

**That's all the C# you need.** The dispatcher automatically discovers the action through the handler's `SupportedActions`.

### Option C: New handler with new service (rare)

For actions that require a new Windows API or a new domain.

**1. Define the schema** in the TypeScript schema file and run `pnpm run asc:all` (same as Option B, step 1).

**2. Create a service interface and implementation:**

```csharp
// Services/IMyService.cs
internal interface IMyService
{
    void DoSomething(int level);
}

// Services/WindowsMyService.cs
internal class WindowsMyService : IMyService
{
    public void DoSomething(int level)
    {
        // P/Invoke, COM, WMI, etc.
    }
}
```

**3. Create a handler:**

```csharp
// Handlers/MyActionHandler.cs
using autoShell.Handlers.Generated;
using autoShell.Services;

internal class MyActionHandler : ActionHandlerBase
{
    private readonly IMyService _myService;

    public MyActionHandler(IMyService myService)
    {
        _myService = myService;
        AddAction<MyActionParams>("MyAction", HandleMyAction);
    }

    private ActionResult HandleMyAction(MyActionParams p)
    {
        _myService.DoSomething(p.Level);
        return ActionResult.Ok($"Done at level {p.Level}");
    }
}
```

**4. Wire it in `ActionDispatcher.Create()`:**

```csharp
// In ActionDispatcher.cs, Create() method:
dispatcher.Register(
    // ... existing handlers ...
    new MyActionHandler(new WindowsMyService())
);
```

**5. Add the service to the testable `Create()` overload** so tests can inject a mock.

### Schema-to-code type mapping

The source generator maps `.pas.json` types to C# as follows:

| Schema type | C# type | Default value |
|---|---|---|
| `number` | `int` | `0` |
| `boolean` | `bool` | `false` |
| `string` | `string` | `""` |
| `string-union` | `string` | `""` |
| Optional field | nullable (`int?`, `bool?`, `string?`) | `null` |

### Checklist

- [ ] Action type defined in TypeScript schema (`*Schema.ts`)
- [ ] Schemas regenerated with `pnpm run asc:all`
- [ ] Handler method registered with `AddAction<T>` in the constructor
- [ ] Handler registered in `ActionDispatcher.Create()` (if new handler)
- [ ] Service interface + implementation created (if new Windows API)
- [ ] Unit tests added for the handler logic
- [ ] `dotnet test --filter "Category!=E2E"` passes

## License

Copyright (c) Microsoft Corporation. Licensed under the MIT License.

## Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
