AgentSkillsCN

Wpf Driver

Wpf 驱动程序

SKILL.md

WPF Driver Skill

Overview

This skill enables Claude Code to autonomously control WPF applications through the WpfPilot.CLI daemon process. The daemon uses DLL injection to attach to running WPF applications and provides a JSON-based protocol for UI inspection and interaction.

Prerequisites

The WpfPilot.CLI.exe binary must be built and available in the output directory:

code
WpfPilot.CLI\bin\Release\net9.0-windows\WpfPilot.CLI.exe

Starting the Daemon

Option 1: Attach to Running Process by Name (Recommended)

Use this when the WPF application is already running and you know its process name:

bash
WpfPilot.CLI.exe attach --name MyApp.exe

This is the recommended method as it automatically finds the correct process ID.

Option 2: Attach to Running Process by PID

Use this when you need to attach to a specific process by ID:

bash
WpfPilot.CLI.exe start --pid 12345

Option 3: Launch New Application

Use this to launch a WPF application and attach automatically:

bash
WpfPilot.CLI.exe launch --path "C:\Path\To\App.exe"

Daemon Lifecycle

  1. Startup: The daemon initializes the AppDriver (DLL injection takes 2-5 seconds)
  2. Ready Signal: When ready, it outputs: {"status": "ready"}
  3. REPL Loop: Enter the JSON command processing loop
  4. Shutdown: Terminate the process or close stdin to disconnect

Communication Protocol

Sending Commands

Send JSON commands to the daemon's STDIN. Each command must be on a single line.

Node.js Example:

javascript
child.stdin.write('{"cmd": "inspect"}\n');
child.stdin.write('{"cmd": "click", "id": "SubmitBtn"}\n');

Python Example:

python
process.stdin.write(b'{"cmd": "inspect"}\n')
process.stdin.write(b'{"cmd": "click", "id": "SubmitBtn"}\n')

PowerShell Example:

powershell
$process.StandardInput.WriteLine('{"cmd": "inspect"}')
$process.StandardInput.WriteLine('{"cmd": "click", "id": "SubmitBtn"}')

Reading Responses

Read responses from the daemon's STDOUT. Each response is a single JSON object followed by a newline.

Node.js Example:

javascript
child.stdout.on('data', (data) => {
  const response = JSON.parse(data.toString());
  console.log(response);
});

JSON Protocol Commands

inspect

Get a filtered list of visible UI elements. Non-visible elements and layout containers (unless they have AutomationId) are automatically excluded.

json
{"cmd": "inspect"}

Response:

json
{
  "status": "success",
  "elements": [
    {
      "ControlType": "Button",
      "ClassName": "System.Windows.Controls.Button",
      "AutomationId": "SubmitBtn",
      "Name": "Submit",
      "IsEnabled": true,
      "IsOffscreen": false,
      "BoundingRectangle": "100,200,80,30"
    }
  ]
}

click

Click an element. Specify either id (AutomationId) or name (Name property).

json
{"cmd": "click", "id": "SubmitBtn"}

or

json
{"cmd": "click", "name": "Submit"}

Response:

json
{"status": "success"}

Error Response:

json
{"status": "error", "message": "Element not found: id=SubmitBtn, name="}

type

Type text into a text input element (TextBox, ComboBox, etc.).

json
{"cmd": "type", "id": "UsernameBox", "text": "john.doe"}

or

json
{"cmd": "type", "name": "Username", "text": "john.doe"}

Response:

json
{"status": "success"}

Error Response:

json
{"status": "error", "message": "Element not found: id=UsernameBox, name="}

wait

Wait for a specified number of milliseconds. Use sparingly - prefer to verify UI state explicitly.

json
{"cmd": "wait", "ms": 1000}

Complete Example: Node.js

javascript
const { spawn } = require('child_process');

// Start the daemon
const child = spawn('WpfPilot.CLI.exe', ['start', '--pid', '12345']);

let buffer = '';

child.stdout.on('data', (data) => {
  buffer += data.toString();

  // Process complete JSON objects
  const lines = buffer.split('\n');
  buffer = lines.pop() || ''; // Keep incomplete line in buffer

  for (const line of lines) {
    if (!line.trim()) continue;
    try {
      const response = JSON.parse(line);
      console.log('Response:', response);
    } catch (e) {
      console.error('Failed to parse:', line);
    }
  }
});

// Send commands after ready signal
setTimeout(() => {
  child.stdin.write('{"cmd": "inspect"}\n');
}, 3000);

// Clean up
child.on('close', (code) => {
  console.log(`Daemon exited with code ${code}`);
});

Complete Example: PowerShell

powershell
# Start the daemon
$process = Start-Process -FilePath "WpfPilot.CLI.exe" -ArgumentList "start","--pid","12345" -PassThru -NoNewWindow -RedirectStandardInput "in.txt" -RedirectStandardOutput "out.txt"

# Write commands to input file
'{"cmd": "inspect"}' | Out-File -FilePath "in.txt" -Encoding UTF8

# Read responses (simplified - in production use async reading)
Get-Content "out.txt" -Wait | ForEach-Object {
    if ($_ -match '^{.*}$') {
        $response = $_ | ConvertFrom-Json
        Write-Host "Response: $($response | ConvertTo-Json -Compress)"
    }
}

Error Handling

All error responses follow this format:

json
{"status": "error", "message": "Error description"}

Always check the status field before processing results.

Troubleshooting

Daemon fails to start

  • Verify the target process is a WPF application
  • Ensure you have permissions to attach to the process
  • Try running as Administrator

Elements not found

  • The element may not be visible (check IsVisible in inspect output)
  • The AutomationId or Name may not match exactly
  • The element may be in a different window/frame

DLL injection timeout

  • WPF applications take time to initialize after launch
  • Add a delay before attaching to newly launched apps
  • The ready signal indicates when injection is complete