@phuetz/code-buddy 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +49 -33
  2. package/dist/agent/tool-handler.js +21 -1
  3. package/dist/agent/tool-handler.js.map +1 -1
  4. package/dist/browser-automation/browser-manager.js +0 -2
  5. package/dist/browser-automation/browser-manager.js.map +1 -1
  6. package/dist/codebuddy/client.js +24 -0
  7. package/dist/codebuddy/client.js.map +1 -1
  8. package/dist/codebuddy/tool-definitions/computer-control-tools.js +2 -0
  9. package/dist/codebuddy/tool-definitions/computer-control-tools.js.map +1 -1
  10. package/dist/desktop-automation/automation-manager.d.ts +8 -0
  11. package/dist/desktop-automation/automation-manager.js +52 -0
  12. package/dist/desktop-automation/automation-manager.js.map +1 -1
  13. package/dist/desktop-automation/base-native-provider.d.ts +72 -0
  14. package/dist/desktop-automation/base-native-provider.js +75 -0
  15. package/dist/desktop-automation/base-native-provider.js.map +1 -0
  16. package/dist/desktop-automation/index.d.ts +4 -0
  17. package/dist/desktop-automation/index.js +5 -0
  18. package/dist/desktop-automation/index.js.map +1 -1
  19. package/dist/desktop-automation/linux-native-provider.d.ts +48 -0
  20. package/dist/desktop-automation/linux-native-provider.js +515 -0
  21. package/dist/desktop-automation/linux-native-provider.js.map +1 -0
  22. package/dist/desktop-automation/macos-native-provider.d.ts +61 -0
  23. package/dist/desktop-automation/macos-native-provider.js +768 -0
  24. package/dist/desktop-automation/macos-native-provider.js.map +1 -0
  25. package/dist/desktop-automation/smart-snapshot.d.ts +21 -2
  26. package/dist/desktop-automation/smart-snapshot.js +237 -12
  27. package/dist/desktop-automation/smart-snapshot.js.map +1 -1
  28. package/dist/desktop-automation/types.d.ts +1 -1
  29. package/dist/desktop-automation/types.js +2 -2
  30. package/dist/desktop-automation/types.js.map +1 -1
  31. package/dist/desktop-automation/windows-native-provider.d.ts +71 -0
  32. package/dist/desktop-automation/windows-native-provider.js +653 -0
  33. package/dist/desktop-automation/windows-native-provider.js.map +1 -0
  34. package/dist/index.js +0 -0
  35. package/dist/tools/computer-control-tool.d.ts +6 -1
  36. package/dist/tools/computer-control-tool.js +97 -1
  37. package/dist/tools/computer-control-tool.js.map +1 -1
  38. package/dist/tools/screenshot-tool.d.ts +23 -0
  39. package/dist/tools/screenshot-tool.js +224 -1
  40. package/dist/tools/screenshot-tool.js.map +1 -1
  41. package/dist/tools/web-search.d.ts +2 -0
  42. package/dist/tools/web-search.js +12 -2
  43. package/dist/tools/web-search.js.map +1 -1
  44. package/package.json +14 -5
@@ -0,0 +1,653 @@
1
+ /**
2
+ * Windows Native Desktop Automation Provider
3
+ *
4
+ * Uses PowerShell P/Invoke (user32.dll) for native Windows desktop automation.
5
+ * Supports WSL2 interop via powershell.exe.
6
+ */
7
+ import { BaseNativeProvider } from './base-native-provider.js';
8
+ // Virtual key code mapping
9
+ const VK_CODES = {
10
+ 'return': 0x0D,
11
+ 'enter': 0x0D,
12
+ 'escape': 0x1B,
13
+ 'esc': 0x1B,
14
+ 'tab': 0x09,
15
+ 'backspace': 0x08,
16
+ 'delete': 0x2E,
17
+ 'space': 0x20,
18
+ 'up': 0x26,
19
+ 'down': 0x28,
20
+ 'left': 0x25,
21
+ 'right': 0x27,
22
+ 'control': 0x11,
23
+ 'ctrl': 0x11,
24
+ 'alt': 0x12,
25
+ 'menu': 0x12,
26
+ 'shift': 0x10,
27
+ 'win': 0x5B,
28
+ 'meta': 0x5B,
29
+ 'command': 0x5B,
30
+ 'home': 0x24,
31
+ 'end': 0x23,
32
+ 'pageup': 0x21,
33
+ 'pagedown': 0x22,
34
+ 'insert': 0x2D,
35
+ 'printscreen': 0x2C,
36
+ 'capslock': 0x14,
37
+ 'numlock': 0x90,
38
+ 'scrolllock': 0x91,
39
+ 'pause': 0x13,
40
+ 'f1': 0x70,
41
+ 'f2': 0x71,
42
+ 'f3': 0x72,
43
+ 'f4': 0x73,
44
+ 'f5': 0x74,
45
+ 'f6': 0x75,
46
+ 'f7': 0x76,
47
+ 'f8': 0x77,
48
+ 'f9': 0x78,
49
+ 'f10': 0x79,
50
+ 'f11': 0x7A,
51
+ 'f12': 0x7B,
52
+ // a-z (populated below)
53
+ // 0-9 (populated below)
54
+ };
55
+ // Populate a-z
56
+ for (let i = 0; i < 26; i++) {
57
+ const letter = String.fromCharCode(97 + i); // 'a' to 'z'
58
+ VK_CODES[letter] = 0x41 + i;
59
+ }
60
+ // Populate 0-9
61
+ for (let i = 0; i <= 9; i++) {
62
+ VK_CODES[String(i)] = 0x30 + i;
63
+ }
64
+ // Modifier keys to VK codes
65
+ const MODIFIER_VK = {
66
+ 'ctrl': 0x11,
67
+ 'control': 0x11,
68
+ 'alt': 0x12,
69
+ 'shift': 0x10,
70
+ 'meta': 0x5B,
71
+ 'command': 0x5B,
72
+ 'win': 0x5B,
73
+ };
74
+ // Mouse event flags
75
+ const MOUSEEVENTF_LEFTDOWN = 0x2;
76
+ const MOUSEEVENTF_LEFTUP = 0x4;
77
+ const MOUSEEVENTF_RIGHTDOWN = 0x8;
78
+ const MOUSEEVENTF_RIGHTUP = 0x10;
79
+ const MOUSEEVENTF_MIDDLEDOWN = 0x20;
80
+ const MOUSEEVENTF_MIDDLEUP = 0x40;
81
+ const MOUSEEVENTF_WHEEL = 0x800;
82
+ const MOUSEEVENTF_HWHEEL = 0x1000;
83
+ // Keyboard event flags
84
+ const KEYEVENTF_KEYUP = 0x2;
85
+ export class WindowsNativeProvider extends BaseNativeProvider {
86
+ platformName = 'Windows';
87
+ capabilities = {
88
+ mouse: true,
89
+ keyboard: true,
90
+ windows: true,
91
+ apps: true,
92
+ screenshots: true,
93
+ colorPicker: true,
94
+ clipboard: true,
95
+ ocr: false,
96
+ };
97
+ psCmd;
98
+ wsl;
99
+ P_INVOKE_BLOCK = `Add-Type @"
100
+ using System;
101
+ using System.Runtime.InteropServices;
102
+ public class NativeInput {
103
+ [DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y);
104
+ [DllImport("user32.dll")] public static extern bool GetCursorPos(out POINT lpPoint);
105
+ [DllImport("user32.dll")] public static extern void mouse_event(uint dwFlags, int dx, int dy, int dwData, IntPtr dwExtraInfo);
106
+ [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, IntPtr dwExtraInfo);
107
+ [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
108
+ [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
109
+ [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
110
+ [DllImport("user32.dll")] public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
111
+ [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
112
+ [DllImport("user32.dll")] public static extern int GetWindowTextLength(IntPtr hWnd);
113
+ [DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
114
+ [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
115
+ [DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
116
+ [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; }
117
+ [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
118
+ }
119
+ "@`;
120
+ constructor(options) {
121
+ super();
122
+ this.wsl = options?.wsl ?? false;
123
+ this.psCmd = this.wsl ? 'powershell.exe' : 'powershell';
124
+ }
125
+ // ---------------------------------------------------------------------------
126
+ // PowerShell execution helper
127
+ // ---------------------------------------------------------------------------
128
+ /**
129
+ * Execute a PowerShell script via the configured PS command.
130
+ * Escapes double quotes in the script for shell invocation.
131
+ */
132
+ async ps(script) {
133
+ const escapedScript = script.replace(/"/g, '\\"');
134
+ return this.exec(`${this.psCmd} -NoProfile -NonInteractive -Command "${escapedScript}"`, 15000);
135
+ }
136
+ /**
137
+ * Validate that a value is a finite number to prevent injection.
138
+ */
139
+ validateNumber(value, name) {
140
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
141
+ throw new Error(`Invalid ${name}: must be a finite number`);
142
+ }
143
+ }
144
+ /**
145
+ * Escape a string for use inside PowerShell single quotes (double the single quotes).
146
+ */
147
+ escapePsSingleQuote(s) {
148
+ return s.replace(/'/g, "''");
149
+ }
150
+ /**
151
+ * Get mouse button event flags.
152
+ */
153
+ getMouseFlags(button = 'left') {
154
+ switch (button) {
155
+ case 'right':
156
+ return { down: MOUSEEVENTF_RIGHTDOWN, up: MOUSEEVENTF_RIGHTUP };
157
+ case 'middle':
158
+ return { down: MOUSEEVENTF_MIDDLEDOWN, up: MOUSEEVENTF_MIDDLEUP };
159
+ case 'left':
160
+ default:
161
+ return { down: MOUSEEVENTF_LEFTDOWN, up: MOUSEEVENTF_LEFTUP };
162
+ }
163
+ }
164
+ /**
165
+ * Resolve a key name to its virtual key code.
166
+ */
167
+ resolveVK(key) {
168
+ const normalized = key.toLowerCase();
169
+ const vk = VK_CODES[normalized];
170
+ if (vk === undefined) {
171
+ // If single character, use its char code
172
+ if (key.length === 1) {
173
+ return key.toUpperCase().charCodeAt(0);
174
+ }
175
+ throw new Error(`Unknown key: ${key}`);
176
+ }
177
+ return vk;
178
+ }
179
+ // ---------------------------------------------------------------------------
180
+ // Lifecycle
181
+ // ---------------------------------------------------------------------------
182
+ async initialize() {
183
+ try {
184
+ await this.ps('Write-Output ok');
185
+ this.initialized = true;
186
+ }
187
+ catch (err) {
188
+ throw new Error(`Failed to initialize Windows native provider: ${err instanceof Error ? err.message : String(err)}`);
189
+ }
190
+ }
191
+ async isAvailable() {
192
+ try {
193
+ await this.exec(`${this.psCmd} -NoProfile -NonInteractive -Command "Write-Output ok"`, 5000);
194
+ return true;
195
+ }
196
+ catch {
197
+ return false;
198
+ }
199
+ }
200
+ async shutdown() {
201
+ this.initialized = false;
202
+ }
203
+ // ---------------------------------------------------------------------------
204
+ // Mouse
205
+ // ---------------------------------------------------------------------------
206
+ async getMousePosition() {
207
+ this.ensureInitialized();
208
+ const result = await this.ps(`${this.P_INVOKE_BLOCK}; $p = New-Object NativeInput+POINT; [NativeInput]::GetCursorPos([ref]$p) | Out-Null; Write-Output "$($p.X),$($p.Y)"`);
209
+ const parts = result.trim().split(',');
210
+ return {
211
+ x: parseInt(parts[0], 10),
212
+ y: parseInt(parts[1], 10),
213
+ };
214
+ }
215
+ async moveMouse(x, y, _options) {
216
+ this.ensureInitialized();
217
+ this.validateNumber(x, 'x');
218
+ this.validateNumber(y, 'y');
219
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::SetCursorPos(${Math.round(x)}, ${Math.round(y)})`);
220
+ }
221
+ async click(options) {
222
+ this.ensureInitialized();
223
+ const button = options?.button ?? 'left';
224
+ const clicks = options?.clicks ?? 1;
225
+ const clickDelay = options?.delay ?? 50;
226
+ const flags = this.getMouseFlags(button);
227
+ for (let i = 0; i < clicks; i++) {
228
+ if (i > 0) {
229
+ await this.delay(clickDelay);
230
+ }
231
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::mouse_event(${flags.down}, 0, 0, 0, [IntPtr]::Zero); [NativeInput]::mouse_event(${flags.up}, 0, 0, 0, [IntPtr]::Zero)`);
232
+ }
233
+ }
234
+ async doubleClick(button) {
235
+ this.ensureInitialized();
236
+ const flags = this.getMouseFlags(button ?? 'left');
237
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::mouse_event(${flags.down}, 0, 0, 0, [IntPtr]::Zero); [NativeInput]::mouse_event(${flags.up}, 0, 0, 0, [IntPtr]::Zero)`);
238
+ await this.delay(50);
239
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::mouse_event(${flags.down}, 0, 0, 0, [IntPtr]::Zero); [NativeInput]::mouse_event(${flags.up}, 0, 0, 0, [IntPtr]::Zero)`);
240
+ }
241
+ async rightClick() {
242
+ this.ensureInitialized();
243
+ const flags = this.getMouseFlags('right');
244
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::mouse_event(${flags.down}, 0, 0, 0, [IntPtr]::Zero); [NativeInput]::mouse_event(${flags.up}, 0, 0, 0, [IntPtr]::Zero)`);
245
+ }
246
+ async drag(fromX, fromY, toX, toY, options) {
247
+ this.ensureInitialized();
248
+ this.validateNumber(fromX, 'fromX');
249
+ this.validateNumber(fromY, 'fromY');
250
+ this.validateNumber(toX, 'toX');
251
+ this.validateNumber(toY, 'toY');
252
+ const button = options?.button ?? 'left';
253
+ const flags = this.getMouseFlags(button);
254
+ const duration = options?.duration ?? 300;
255
+ // Move to start position
256
+ await this.moveMouse(fromX, fromY);
257
+ await this.delay(50);
258
+ // Mouse down
259
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::mouse_event(${flags.down}, 0, 0, 0, [IntPtr]::Zero)`);
260
+ await this.delay(duration);
261
+ // Move to end position
262
+ await this.moveMouse(toX, toY);
263
+ await this.delay(50);
264
+ // Mouse up
265
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::mouse_event(${flags.up}, 0, 0, 0, [IntPtr]::Zero)`);
266
+ }
267
+ async scroll(options) {
268
+ this.ensureInitialized();
269
+ const deltaY = options.deltaY ?? 0;
270
+ const deltaX = options.deltaX ?? 0;
271
+ if (deltaY !== 0) {
272
+ this.validateNumber(deltaY, 'deltaY');
273
+ const amount = Math.round(deltaY * 120);
274
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::mouse_event(${MOUSEEVENTF_WHEEL}, 0, 0, ${amount}, [IntPtr]::Zero)`);
275
+ }
276
+ if (deltaX !== 0) {
277
+ this.validateNumber(deltaX, 'deltaX');
278
+ const amount = Math.round(deltaX * 120);
279
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::mouse_event(${MOUSEEVENTF_HWHEEL}, 0, 0, ${amount}, [IntPtr]::Zero)`);
280
+ }
281
+ }
282
+ // ---------------------------------------------------------------------------
283
+ // Keyboard
284
+ // ---------------------------------------------------------------------------
285
+ async keyPress(key, options) {
286
+ this.ensureInitialized();
287
+ const vk = this.resolveVK(key);
288
+ const modifiers = options?.modifiers ?? [];
289
+ const modVKs = modifiers.map(m => {
290
+ const mvk = MODIFIER_VK[m];
291
+ if (mvk === undefined)
292
+ throw new Error(`Unknown modifier: ${m}`);
293
+ return mvk;
294
+ });
295
+ // Build PS script: modifiers down, key down/up, modifiers up
296
+ const lines = [this.P_INVOKE_BLOCK];
297
+ for (const mvk of modVKs) {
298
+ lines.push(`[NativeInput]::keybd_event(${mvk}, 0, 0, [IntPtr]::Zero)`);
299
+ }
300
+ lines.push(`[NativeInput]::keybd_event(${vk}, 0, 0, [IntPtr]::Zero)`);
301
+ if (options?.delay) {
302
+ lines.push(`Start-Sleep -Milliseconds ${Math.round(options.delay)}`);
303
+ }
304
+ lines.push(`[NativeInput]::keybd_event(${vk}, 0, ${KEYEVENTF_KEYUP}, [IntPtr]::Zero)`);
305
+ for (const mvk of modVKs.reverse()) {
306
+ lines.push(`[NativeInput]::keybd_event(${mvk}, 0, ${KEYEVENTF_KEYUP}, [IntPtr]::Zero)`);
307
+ }
308
+ await this.ps(lines.join('; '));
309
+ }
310
+ async keyDown(key) {
311
+ this.ensureInitialized();
312
+ const vk = this.resolveVK(key);
313
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::keybd_event(${vk}, 0, 0, [IntPtr]::Zero)`);
314
+ }
315
+ async keyUp(key) {
316
+ this.ensureInitialized();
317
+ const vk = this.resolveVK(key);
318
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::keybd_event(${vk}, 0, ${KEYEVENTF_KEYUP}, [IntPtr]::Zero)`);
319
+ }
320
+ async type(text, _options) {
321
+ this.ensureInitialized();
322
+ // Escape special SendKeys characters: +^%~(){}[]
323
+ const escaped = text.replace(/([+^%~(){}[\]])/g, '{$1}');
324
+ const psEscaped = this.escapePsSingleQuote(escaped);
325
+ await this.ps(`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${psEscaped}')`);
326
+ }
327
+ async hotkey(sequence) {
328
+ this.ensureInitialized();
329
+ const modifiers = sequence.modifiers ?? [];
330
+ const modVKs = modifiers.map(m => {
331
+ const mvk = MODIFIER_VK[m];
332
+ if (mvk === undefined)
333
+ throw new Error(`Unknown modifier: ${m}`);
334
+ return mvk;
335
+ });
336
+ const lines = [this.P_INVOKE_BLOCK];
337
+ // Modifiers down
338
+ for (const mvk of modVKs) {
339
+ lines.push(`[NativeInput]::keybd_event(${mvk}, 0, 0, [IntPtr]::Zero)`);
340
+ }
341
+ // Key presses
342
+ for (const key of sequence.keys) {
343
+ const vk = this.resolveVK(key);
344
+ lines.push(`[NativeInput]::keybd_event(${vk}, 0, 0, [IntPtr]::Zero)`);
345
+ lines.push(`[NativeInput]::keybd_event(${vk}, 0, ${KEYEVENTF_KEYUP}, [IntPtr]::Zero)`);
346
+ }
347
+ // Modifiers up (reverse order)
348
+ for (const mvk of [...modVKs].reverse()) {
349
+ lines.push(`[NativeInput]::keybd_event(${mvk}, 0, ${KEYEVENTF_KEYUP}, [IntPtr]::Zero)`);
350
+ }
351
+ await this.ps(lines.join('; '));
352
+ }
353
+ // ---------------------------------------------------------------------------
354
+ // Windows
355
+ // ---------------------------------------------------------------------------
356
+ async getActiveWindow() {
357
+ this.ensureInitialized();
358
+ try {
359
+ const handleStr = await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::GetForegroundWindow().ToInt64()`);
360
+ const handle = handleStr.trim();
361
+ if (!handle || handle === '0')
362
+ return null;
363
+ return this.getWindow(handle);
364
+ }
365
+ catch {
366
+ return null;
367
+ }
368
+ }
369
+ async getWindows(_options) {
370
+ this.ensureInitialized();
371
+ const result = await this.ps('Get-Process | Where-Object {$_.MainWindowHandle -ne 0} | ForEach-Object { Write-Output "$($_.MainWindowHandle)|$($_.Id)|$($_.ProcessName)|$($_.MainWindowTitle)" }');
372
+ const windows = [];
373
+ const lines = result.split('\n').filter(l => l.trim());
374
+ for (const line of lines) {
375
+ const parts = line.split('|');
376
+ if (parts.length < 4)
377
+ continue;
378
+ const handle = parts[0].trim();
379
+ const pid = parseInt(parts[1].trim(), 10);
380
+ const processName = parts[2].trim();
381
+ const title = parts.slice(3).join('|').trim();
382
+ windows.push({
383
+ handle,
384
+ title,
385
+ pid,
386
+ processName,
387
+ bounds: { x: 0, y: 0, width: 0, height: 0 },
388
+ focused: false,
389
+ visible: true,
390
+ minimized: false,
391
+ maximized: false,
392
+ fullscreen: false,
393
+ });
394
+ }
395
+ // Apply search filters
396
+ if (_options) {
397
+ return windows.filter(w => {
398
+ if (_options.title) {
399
+ const titleMatch = _options.title instanceof RegExp
400
+ ? _options.title.test(w.title)
401
+ : w.title.includes(_options.title);
402
+ if (!titleMatch)
403
+ return false;
404
+ }
405
+ if (_options.processName && w.processName !== _options.processName)
406
+ return false;
407
+ if (_options.pid && w.pid !== _options.pid)
408
+ return false;
409
+ return true;
410
+ });
411
+ }
412
+ return windows;
413
+ }
414
+ async getWindow(handle) {
415
+ this.ensureInitialized();
416
+ const handleNum = parseInt(handle, 10);
417
+ this.validateNumber(handleNum, 'handle');
418
+ try {
419
+ const script = `${this.P_INVOKE_BLOCK};
420
+ $h = [IntPtr]${handleNum};
421
+ $r = New-Object NativeInput+RECT;
422
+ [NativeInput]::GetWindowRect($h, [ref]$r) | Out-Null;
423
+ $len = [NativeInput]::GetWindowTextLength($h);
424
+ $sb = New-Object System.Text.StringBuilder($len + 1);
425
+ [NativeInput]::GetWindowText($h, $sb, $sb.Capacity) | Out-Null;
426
+ $pid = [uint32]0;
427
+ [NativeInput]::GetWindowThreadProcessId($h, [ref]$pid) | Out-Null;
428
+ $fg = [NativeInput]::GetForegroundWindow();
429
+ $pname = '';
430
+ try { $pname = (Get-Process -Id $pid -ErrorAction SilentlyContinue).ProcessName } catch {}
431
+ Write-Output "$($r.Left)|$($r.Top)|$($r.Right)|$($r.Bottom)|$($sb.ToString())|$pid|$pname|$($fg -eq $h)"`;
432
+ const result = await this.ps(script);
433
+ const parts = result.trim().split('|');
434
+ if (parts.length < 8)
435
+ return null;
436
+ const left = parseInt(parts[0], 10);
437
+ const top = parseInt(parts[1], 10);
438
+ const right = parseInt(parts[2], 10);
439
+ const bottom = parseInt(parts[3], 10);
440
+ const title = parts[4];
441
+ const pid = parseInt(parts[5], 10);
442
+ const processName = parts[6];
443
+ const focused = parts[7].trim().toLowerCase() === 'true';
444
+ return {
445
+ handle,
446
+ title,
447
+ pid,
448
+ processName,
449
+ bounds: {
450
+ x: left,
451
+ y: top,
452
+ width: right - left,
453
+ height: bottom - top,
454
+ },
455
+ focused,
456
+ visible: true,
457
+ minimized: false,
458
+ maximized: false,
459
+ fullscreen: false,
460
+ };
461
+ }
462
+ catch {
463
+ return null;
464
+ }
465
+ }
466
+ async focusWindow(handle) {
467
+ this.ensureInitialized();
468
+ const handleNum = parseInt(handle, 10);
469
+ this.validateNumber(handleNum, 'handle');
470
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::SetForegroundWindow([IntPtr]${handleNum})`);
471
+ }
472
+ async minimizeWindow(handle) {
473
+ this.ensureInitialized();
474
+ const handleNum = parseInt(handle, 10);
475
+ this.validateNumber(handleNum, 'handle');
476
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::ShowWindow([IntPtr]${handleNum}, 6)`);
477
+ }
478
+ async maximizeWindow(handle) {
479
+ this.ensureInitialized();
480
+ const handleNum = parseInt(handle, 10);
481
+ this.validateNumber(handleNum, 'handle');
482
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::ShowWindow([IntPtr]${handleNum}, 3)`);
483
+ }
484
+ async restoreWindow(handle) {
485
+ this.ensureInitialized();
486
+ const handleNum = parseInt(handle, 10);
487
+ this.validateNumber(handleNum, 'handle');
488
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::ShowWindow([IntPtr]${handleNum}, 9)`);
489
+ }
490
+ async closeWindow(handle) {
491
+ this.ensureInitialized();
492
+ const handleNum = parseInt(handle, 10);
493
+ this.validateNumber(handleNum, 'handle');
494
+ await this.ps(`${this.P_INVOKE_BLOCK}; [NativeInput]::SendMessage([IntPtr]${handleNum}, 0x10, [IntPtr]::Zero, [IntPtr]::Zero)`);
495
+ }
496
+ async setWindow(handle, options) {
497
+ this.ensureInitialized();
498
+ const handleNum = parseInt(handle, 10);
499
+ this.validateNumber(handleNum, 'handle');
500
+ const lines = [this.P_INVOKE_BLOCK];
501
+ if (options.position || options.size) {
502
+ // Need current rect to fill in missing values
503
+ const current = await this.getWindow(handle);
504
+ const x = options.position?.x ?? current?.bounds.x ?? 0;
505
+ const y = options.position?.y ?? current?.bounds.y ?? 0;
506
+ const w = options.size?.width ?? current?.bounds.width ?? 800;
507
+ const h = options.size?.height ?? current?.bounds.height ?? 600;
508
+ this.validateNumber(x, 'x');
509
+ this.validateNumber(y, 'y');
510
+ this.validateNumber(w, 'width');
511
+ this.validateNumber(h, 'height');
512
+ lines.push(`[NativeInput]::MoveWindow([IntPtr]${handleNum}, ${Math.round(x)}, ${Math.round(y)}, ${Math.round(w)}, ${Math.round(h)}, $true)`);
513
+ }
514
+ if (options.focus) {
515
+ lines.push(`[NativeInput]::SetForegroundWindow([IntPtr]${handleNum})`);
516
+ }
517
+ if (lines.length > 1) {
518
+ await this.ps(lines.join('; '));
519
+ }
520
+ }
521
+ // ---------------------------------------------------------------------------
522
+ // Applications
523
+ // ---------------------------------------------------------------------------
524
+ async getRunningApps() {
525
+ this.ensureInitialized();
526
+ const result = await this.ps('Get-Process | Where-Object {$_.MainWindowHandle -ne 0} | Select-Object Id,ProcessName,Path | ConvertTo-Json -Compress');
527
+ try {
528
+ const parsed = JSON.parse(result);
529
+ const items = Array.isArray(parsed) ? parsed : [parsed];
530
+ return items.map((item) => ({
531
+ name: item.ProcessName ?? '',
532
+ path: item.Path ?? '',
533
+ pid: item.Id,
534
+ running: true,
535
+ }));
536
+ }
537
+ catch {
538
+ return [];
539
+ }
540
+ }
541
+ async launchApp(appPath, options) {
542
+ this.ensureInitialized();
543
+ const escapedPath = this.escapePsSingleQuote(appPath);
544
+ let cmd = `Start-Process '${escapedPath}' -PassThru`;
545
+ if (options?.args && options.args.length > 0) {
546
+ const args = options.args.map(a => this.escapePsSingleQuote(a)).join(' ');
547
+ cmd += ` -ArgumentList '${args}'`;
548
+ }
549
+ if (options?.cwd) {
550
+ cmd += ` -WorkingDirectory '${this.escapePsSingleQuote(options.cwd)}'`;
551
+ }
552
+ if (options?.hidden) {
553
+ cmd += ' -WindowStyle Hidden';
554
+ }
555
+ cmd += ' | Select-Object Id,ProcessName | ConvertTo-Json -Compress';
556
+ const result = await this.ps(cmd);
557
+ try {
558
+ const parsed = JSON.parse(result);
559
+ return {
560
+ name: parsed.ProcessName ?? '',
561
+ path: appPath,
562
+ pid: parsed.Id,
563
+ running: true,
564
+ };
565
+ }
566
+ catch {
567
+ return {
568
+ name: appPath,
569
+ path: appPath,
570
+ running: true,
571
+ };
572
+ }
573
+ }
574
+ async closeApp(pid) {
575
+ this.ensureInitialized();
576
+ this.validateNumber(pid, 'pid');
577
+ await this.ps(`Stop-Process -Id ${Math.round(pid)}`);
578
+ }
579
+ // ---------------------------------------------------------------------------
580
+ // Screens
581
+ // ---------------------------------------------------------------------------
582
+ async getScreens() {
583
+ this.ensureInitialized();
584
+ const result = await this.ps('Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::AllScreens | ForEach-Object { Write-Output "$($_.DeviceName)|$($_.Bounds.X)|$($_.Bounds.Y)|$($_.Bounds.Width)|$($_.Bounds.Height)|$($_.WorkingArea.X)|$($_.WorkingArea.Y)|$($_.WorkingArea.Width)|$($_.WorkingArea.Height)|$($_.Primary)" }');
585
+ const screens = [];
586
+ const lines = result.split('\n').filter(l => l.trim());
587
+ for (let i = 0; i < lines.length; i++) {
588
+ const parts = lines[i].split('|');
589
+ if (parts.length < 10)
590
+ continue;
591
+ screens.push({
592
+ id: i,
593
+ name: parts[0].trim(),
594
+ bounds: {
595
+ x: parseInt(parts[1], 10),
596
+ y: parseInt(parts[2], 10),
597
+ width: parseInt(parts[3], 10),
598
+ height: parseInt(parts[4], 10),
599
+ },
600
+ workArea: {
601
+ x: parseInt(parts[5], 10),
602
+ y: parseInt(parts[6], 10),
603
+ width: parseInt(parts[7], 10),
604
+ height: parseInt(parts[8], 10),
605
+ },
606
+ scaleFactor: 1,
607
+ primary: parts[9].trim().toLowerCase() === 'true',
608
+ });
609
+ }
610
+ return screens;
611
+ }
612
+ async getPixelColor(x, y) {
613
+ this.ensureInitialized();
614
+ this.validateNumber(x, 'x');
615
+ this.validateNumber(y, 'y');
616
+ const result = await this.ps(`Add-Type -AssemblyName System.Drawing; $bmp = New-Object System.Drawing.Bitmap(1, 1); $g = [System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen(${Math.round(x)}, ${Math.round(y)}, 0, 0, (New-Object System.Drawing.Size(1, 1))); $c = $bmp.GetPixel(0, 0); Write-Output "$($c.R)|$($c.G)|$($c.B)|$($c.A)"; $g.Dispose(); $bmp.Dispose()`);
617
+ const parts = result.trim().split('|');
618
+ const r = parseInt(parts[0], 10);
619
+ const g = parseInt(parts[1], 10);
620
+ const b = parseInt(parts[2], 10);
621
+ const a = parts.length > 3 ? parseInt(parts[3], 10) : 255;
622
+ const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
623
+ return { r, g, b, a, hex };
624
+ }
625
+ // ---------------------------------------------------------------------------
626
+ // Clipboard
627
+ // ---------------------------------------------------------------------------
628
+ async getClipboard() {
629
+ this.ensureInitialized();
630
+ try {
631
+ const text = await this.ps('Get-Clipboard');
632
+ return {
633
+ text: text || undefined,
634
+ formats: text ? ['text'] : [],
635
+ };
636
+ }
637
+ catch {
638
+ return { formats: [] };
639
+ }
640
+ }
641
+ async setClipboard(content) {
642
+ this.ensureInitialized();
643
+ if (content.text !== undefined) {
644
+ const escaped = this.escapePsSingleQuote(content.text);
645
+ await this.ps(`Set-Clipboard -Value '${escaped}'`);
646
+ }
647
+ }
648
+ async clearClipboard() {
649
+ this.ensureInitialized();
650
+ await this.ps('Set-Clipboard -Value $null');
651
+ }
652
+ }
653
+ //# sourceMappingURL=windows-native-provider.js.map