ft-scout 9.0.4 → 9.0.6

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 (57) hide show
  1. package/.firebase/hosting.d2Vi.cache +8 -7
  2. package/AUTONOMOUS_FEATURES.md +192 -0
  3. package/README.md +67 -35
  4. package/bin/src/commands/agent.d.ts.map +1 -1
  5. package/bin/src/commands/agent.js +21 -8
  6. package/bin/src/commands/agent.js.map +1 -1
  7. package/bin/src/commands/login.d.ts.map +1 -1
  8. package/bin/src/commands/login.js +3 -1
  9. package/bin/src/commands/login.js.map +1 -1
  10. package/bin/src/engine/actionVerifier.d.ts.map +1 -1
  11. package/bin/src/engine/actionVerifier.js +9 -1
  12. package/bin/src/engine/actionVerifier.js.map +1 -1
  13. package/bin/src/engine/agentEngine.d.ts.map +1 -1
  14. package/bin/src/engine/agentEngine.js +319 -13
  15. package/bin/src/engine/agentEngine.js.map +1 -1
  16. package/bin/src/engine/browser/browserController.d.ts.map +1 -1
  17. package/bin/src/engine/browser/browserController.js +53 -13
  18. package/bin/src/engine/browser/browserController.js.map +1 -1
  19. package/bin/src/engine/computer/computerController.d.ts.map +1 -1
  20. package/bin/src/engine/computer/computerController.js +564 -296
  21. package/bin/src/engine/computer/computerController.js.map +1 -1
  22. package/bin/src/engine/computer/computerDiagnostic.d.ts.map +1 -0
  23. package/bin/src/engine/computer/computerDiagnostic.js +71 -0
  24. package/bin/src/engine/computer/computerDiagnostic.js.map +1 -0
  25. package/bin/src/engine/computer/index.d.ts.map +1 -1
  26. package/bin/src/engine/computer/index.js +1 -0
  27. package/bin/src/engine/computer/index.js.map +1 -1
  28. package/bin/src/engine/computer/mockComputer.d.ts.map +1 -1
  29. package/bin/src/engine/computer/mockComputer.js +104 -8
  30. package/bin/src/engine/computer/mockComputer.js.map +1 -1
  31. package/bin/src/engine/computer/scoutDriver.cs +1161 -0
  32. package/bin/src/engine/computer/scout_driver.exe +0 -0
  33. package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -1
  34. package/bin/src/engine/runtime/agentRuntime.js +375 -20
  35. package/bin/src/engine/runtime/agentRuntime.js.map +1 -1
  36. package/bin/src/engine/safety.d.ts.map +1 -1
  37. package/bin/src/engine/safety.js +17 -6
  38. package/bin/src/engine/safety.js.map +1 -1
  39. package/bin/src/engine/toolRegistry.d.ts.map +1 -1
  40. package/bin/src/engine/toolRegistry.js +209 -0
  41. package/bin/src/engine/toolRegistry.js.map +1 -1
  42. package/bin/src/engine/workspace/agentWorkspace.d.ts.map +1 -1
  43. package/bin/src/engine/workspace/agentWorkspace.js +21 -0
  44. package/bin/src/engine/workspace/agentWorkspace.js.map +1 -1
  45. package/bin/src/index.js +5 -4
  46. package/bin/src/index.js.map +1 -1
  47. package/bin/src/utils/auth.d.ts.map +1 -1
  48. package/bin/src/utils/auth.js +14 -1
  49. package/bin/src/utils/auth.js.map +1 -1
  50. package/bin/src/utils/branding.js +1 -1
  51. package/bin/src/utils/holoseneAuth.d.ts.map +1 -1
  52. package/bin/src/utils/holoseneAuth.js +169 -41
  53. package/bin/src/utils/holoseneAuth.js.map +1 -1
  54. package/firebase.json +8 -0
  55. package/package.json +1 -1
  56. package/web/app.js +3 -3
  57. package/web/styles.css +2 -2
@@ -0,0 +1,1161 @@
1
+ using System;
2
+ using System.IO;
3
+ using System.Text;
4
+ using System.Threading;
5
+ using System.Drawing;
6
+ using System.Drawing.Imaging;
7
+ using System.Windows.Forms;
8
+ using System.Runtime.InteropServices;
9
+ using System.Diagnostics;
10
+ using System.Collections.Generic;
11
+
12
+ namespace Scout.ComputerControl
13
+ {
14
+ public class ScoutDriver
15
+ {
16
+ [DllImport("user32.dll", SetLastError = true)]
17
+ public static extern IntPtr OpenWindowStation(string lpszWinSta, bool fInherit, uint dwDesiredAccess);
18
+
19
+ [DllImport("user32.dll", SetLastError = true)]
20
+ public static extern bool SetProcessWindowStation(IntPtr hWinSta);
21
+
22
+ [DllImport("user32.dll", SetLastError = true)]
23
+ public static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit, uint dwDesiredAccess);
24
+
25
+ [DllImport("user32.dll", SetLastError = true)]
26
+ public static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit, uint dwDesiredAccess);
27
+
28
+ [DllImport("user32.dll", SetLastError = true)]
29
+ public static extern bool SetThreadDesktop(IntPtr hDesktop);
30
+
31
+ [DllImport("user32.dll", SetLastError = true)]
32
+ public static extern bool CloseDesktop(IntPtr hDesktop);
33
+
34
+ [DllImport("user32.dll", SetLastError = true)]
35
+ public static extern bool GetCursorPos(out POINT lpPoint);
36
+
37
+ [DllImport("user32.dll", SetLastError = true)]
38
+ public static extern bool SetCursorPos(int X, int Y);
39
+
40
+ [DllImport("user32.dll", SetLastError = true)]
41
+ public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
42
+
43
+ [DllImport("user32.dll")]
44
+ public static extern int GetSystemMetrics(int nIndex);
45
+
46
+ [DllImport("user32.dll")]
47
+ public static extern bool SetProcessDPIAware();
48
+
49
+ [DllImport("user32.dll")]
50
+ public static extern IntPtr GetForegroundWindow();
51
+
52
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
53
+ public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
54
+
55
+ [DllImport("user32.dll")]
56
+ public static extern bool SetForegroundWindow(IntPtr hWnd);
57
+
58
+ [DllImport("user32.dll")]
59
+ public static extern bool BringWindowToTop(IntPtr hWnd);
60
+
61
+ [DllImport("user32.dll")]
62
+ public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
63
+
64
+ [DllImport("user32.dll")]
65
+ public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
66
+ public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
67
+
68
+ [DllImport("user32.dll")]
69
+ [return: MarshalAs(UnmanagedType.Bool)]
70
+ public static extern bool IsWindowVisible(IntPtr hWnd);
71
+
72
+ [DllImport("user32.dll")]
73
+ public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
74
+
75
+ [DllImport("user32.dll")]
76
+ [return: MarshalAs(UnmanagedType.Bool)]
77
+ public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
78
+
79
+ [DllImport("user32.dll")]
80
+ public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
81
+
82
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
83
+ public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
84
+
85
+ public const uint WM_CLOSE = 0x0010;
86
+ public const uint KEYEVENTF_EXTENDEDKEY = 0x0001;
87
+ public const uint KEYEVENTF_KEYUP = 0x0002;
88
+
89
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
90
+ public static extern bool CreateProcess(
91
+ string lpApplicationName,
92
+ string lpCommandLine,
93
+ IntPtr lpProcessAttributes,
94
+ IntPtr lpThreadAttributes,
95
+ bool bInheritHandles,
96
+ uint dwCreationFlags,
97
+ IntPtr lpEnvironment,
98
+ string lpCurrentDirectory,
99
+ ref STARTUPINFO lpStartupInfo,
100
+ out PROCESS_INFORMATION lpProcessInformation
101
+ );
102
+
103
+ public const uint MOUSEEVENTF_MOVE = 0x0001;
104
+ public const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
105
+ public const uint MOUSEEVENTF_LEFTUP = 0x0004;
106
+ public const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
107
+ public const uint MOUSEEVENTF_RIGHTUP = 0x0010;
108
+ public const uint MOUSEEVENTF_MIDDLEDOWN = 0x0020;
109
+ public const uint MOUSEEVENTF_MIDDLEUP = 0x0040;
110
+ public const uint MOUSEEVENTF_WHEEL = 0x0800;
111
+
112
+ [DllImport("user32.dll")]
113
+ [return: MarshalAs(UnmanagedType.Bool)]
114
+ public static extern bool IsIconic(IntPtr hWnd);
115
+
116
+ [DllImport("user32.dll")]
117
+ public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
118
+
119
+ [DllImport("kernel32.dll")]
120
+ public static extern uint GetCurrentThreadId();
121
+
122
+ public const int SM_CXSCREEN = 0;
123
+ public const int SM_CYSCREEN = 1;
124
+ public const int SM_CXVIRTUALSCREEN = 78;
125
+ public const int SM_CYVIRTUALSCREEN = 79;
126
+ public const int SW_SHOW = 5;
127
+ public const int SW_RESTORE = 9;
128
+
129
+ [StructLayout(LayoutKind.Sequential)]
130
+ public struct RECT
131
+ {
132
+ public int Left;
133
+ public int Top;
134
+ public int Right;
135
+ public int Bottom;
136
+ }
137
+
138
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
139
+ public struct STARTUPINFO
140
+ {
141
+ public int cb;
142
+ public string lpReserved;
143
+ public string lpDesktop;
144
+ public string lpTitle;
145
+ public int dwX;
146
+ public int dwY;
147
+ public int dwXSize;
148
+ public int dwYSize;
149
+ public int dwXCountChars;
150
+ public int dwYCountChars;
151
+ public int dwFillAttribute;
152
+ public int dwFlags;
153
+ public short wShowWindow;
154
+ public short cbReserved2;
155
+ public IntPtr lpReserved2;
156
+ public IntPtr hStdInput;
157
+ public IntPtr hStdOutput;
158
+ public IntPtr hStdError;
159
+ }
160
+
161
+ [StructLayout(LayoutKind.Sequential)]
162
+ public struct PROCESS_INFORMATION
163
+ {
164
+ public IntPtr hProcess;
165
+ public IntPtr hThread;
166
+ public int dwProcessId;
167
+ public int dwThreadId;
168
+ }
169
+
170
+ [StructLayout(LayoutKind.Sequential)]
171
+ public struct POINT
172
+ {
173
+ public int X;
174
+ public int Y;
175
+ }
176
+
177
+ public static void AttachInputDesktop()
178
+ {
179
+ try
180
+ {
181
+ SetProcessDPIAware();
182
+ IntPtr hwinsta = OpenWindowStation("WinSta0", false, 0x10000000);
183
+ if (hwinsta != IntPtr.Zero)
184
+ {
185
+ SetProcessWindowStation(hwinsta);
186
+ }
187
+ IntPtr hdesk = OpenInputDesktop(0, false, 0x10000000);
188
+ if (hdesk == IntPtr.Zero)
189
+ {
190
+ hdesk = OpenDesktop("Default", 0, false, 0x10000000);
191
+ }
192
+ if (hdesk != IntPtr.Zero)
193
+ {
194
+ SetThreadDesktop(hdesk);
195
+ }
196
+ }
197
+ catch { }
198
+ }
199
+
200
+ public static POINT GetPosition()
201
+ {
202
+ POINT pt = new POINT();
203
+ GetCursorPos(out pt);
204
+ return pt;
205
+ }
206
+
207
+ public static bool Move(int x, int y)
208
+ {
209
+ SetCursorPos(x, y);
210
+ Thread.Sleep(20);
211
+ POINT actual = GetPosition();
212
+ return (Math.Abs(actual.X - x) <= 5 && Math.Abs(actual.Y - y) <= 5);
213
+ }
214
+
215
+ public static bool Click(string button, int x, int y, int count)
216
+ {
217
+ Move(x, y);
218
+ Thread.Sleep(30);
219
+
220
+ uint downFlag = MOUSEEVENTF_LEFTDOWN;
221
+ uint upFlag = MOUSEEVENTF_LEFTUP;
222
+
223
+ if (button == "right")
224
+ {
225
+ downFlag = MOUSEEVENTF_RIGHTDOWN;
226
+ upFlag = MOUSEEVENTF_RIGHTUP;
227
+ }
228
+ else if (button == "middle")
229
+ {
230
+ downFlag = MOUSEEVENTF_MIDDLEDOWN;
231
+ upFlag = MOUSEEVENTF_MIDDLEUP;
232
+ }
233
+
234
+ for (int i = 0; i < count; i++)
235
+ {
236
+ mouse_event(downFlag, 0, 0, 0, 0);
237
+ Thread.Sleep(40);
238
+ mouse_event(upFlag, 0, 0, 0, 0);
239
+ if (count > 1 && i < count - 1)
240
+ {
241
+ Thread.Sleep(70);
242
+ }
243
+ }
244
+ return true;
245
+ }
246
+
247
+ public static bool MouseDown(string button, int x, int y)
248
+ {
249
+ if (x >= 0 && y >= 0)
250
+ {
251
+ Move(x, y);
252
+ Thread.Sleep(30);
253
+ }
254
+ uint downFlag = MOUSEEVENTF_LEFTDOWN;
255
+ if (button == "right") downFlag = MOUSEEVENTF_RIGHTDOWN;
256
+ else if (button == "middle") downFlag = MOUSEEVENTF_MIDDLEDOWN;
257
+
258
+ mouse_event(downFlag, 0, 0, 0, 0);
259
+ return true;
260
+ }
261
+
262
+ public static bool MouseUp(string button, int x, int y)
263
+ {
264
+ if (x >= 0 && y >= 0)
265
+ {
266
+ Move(x, y);
267
+ Thread.Sleep(30);
268
+ }
269
+ uint upFlag = MOUSEEVENTF_LEFTUP;
270
+ if (button == "right") upFlag = MOUSEEVENTF_RIGHTUP;
271
+ else if (button == "middle") upFlag = MOUSEEVENTF_MIDDLEUP;
272
+
273
+ mouse_event(upFlag, 0, 0, 0, 0);
274
+ return true;
275
+ }
276
+
277
+ public static bool Drag(int startX, int startY, int endX, int endY)
278
+ {
279
+ Move(startX, startY);
280
+ Thread.Sleep(50);
281
+ mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
282
+ Thread.Sleep(50);
283
+
284
+ int steps = 15;
285
+ for (int i = 1; i <= steps; i++)
286
+ {
287
+ int curX = startX + (int)((endX - startX) * ((double)i / steps));
288
+ int curY = startY + (int)((endY - startY) * ((double)i / steps));
289
+ SetCursorPos(curX, curY);
290
+ Thread.Sleep(15);
291
+ }
292
+
293
+ Move(endX, endY);
294
+ Thread.Sleep(50);
295
+ mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
296
+ Thread.Sleep(30);
297
+ return true;
298
+ }
299
+
300
+ public static bool Scroll(int deltaX, int deltaY)
301
+ {
302
+ if (deltaY != 0)
303
+ {
304
+ int amount = -deltaY * 120;
305
+ mouse_event(MOUSEEVENTF_WHEEL, 0, 0, (uint)amount, 0);
306
+ }
307
+ return true;
308
+ }
309
+
310
+ public static string GetActiveWindowTitle()
311
+ {
312
+ try
313
+ {
314
+ IntPtr hwnd = GetForegroundWindow();
315
+ if (hwnd == IntPtr.Zero) return "Desktop";
316
+ StringBuilder sb = new StringBuilder(512);
317
+ GetWindowText(hwnd, sb, sb.Capacity);
318
+ string title = sb.ToString().Trim();
319
+ return string.IsNullOrEmpty(title) ? "Desktop" : title;
320
+ }
321
+ catch
322
+ {
323
+ return "Desktop";
324
+ }
325
+ }
326
+
327
+ public static List<string> GetVisibleWindows()
328
+ {
329
+ List<string> list = new List<string>();
330
+ try
331
+ {
332
+ EnumWindows((hWnd, lParam) =>
333
+ {
334
+ if (IsWindowVisible(hWnd))
335
+ {
336
+ RECT rect;
337
+ if (GetWindowRect(hWnd, out rect))
338
+ {
339
+ int w = rect.Right - rect.Left;
340
+ int h = rect.Bottom - rect.Top;
341
+ if (w > 30 && h > 30)
342
+ {
343
+ StringBuilder sb = new StringBuilder(512);
344
+ GetWindowText(hWnd, sb, sb.Capacity);
345
+ string t = sb.ToString().Trim();
346
+ if (!string.IsNullOrEmpty(t) && t != "Default IME" && t != "MSCTFIME UI")
347
+ {
348
+ if (!list.Contains(t)) list.Add(t);
349
+ }
350
+ }
351
+ }
352
+ }
353
+ return true;
354
+ }, IntPtr.Zero);
355
+ }
356
+ catch { }
357
+ return list;
358
+ }
359
+
360
+ public static bool FocusWindow(string titleOrApp)
361
+ {
362
+ try
363
+ {
364
+ string target = titleOrApp.ToLower().Trim();
365
+ IntPtr foundHwnd = IntPtr.Zero;
366
+
367
+ EnumWindows((hWnd, lParam) =>
368
+ {
369
+ if (IsWindowVisible(hWnd))
370
+ {
371
+ StringBuilder sb = new StringBuilder(512);
372
+ GetWindowText(hWnd, sb, sb.Capacity);
373
+ string t = sb.ToString().Trim().ToLower();
374
+
375
+ uint pid;
376
+ GetWindowThreadProcessId(hWnd, out pid);
377
+ string pName = "";
378
+ try { pName = Process.GetProcessById((int)pid).ProcessName.ToLower(); } catch { }
379
+
380
+ if (t.Contains(target) || pName.Contains(target))
381
+ {
382
+ foundHwnd = hWnd;
383
+ return false;
384
+ }
385
+ }
386
+ return true;
387
+ }, IntPtr.Zero);
388
+
389
+ if (foundHwnd != IntPtr.Zero)
390
+ {
391
+ if (IsIconic(foundHwnd))
392
+ {
393
+ ShowWindow(foundHwnd, SW_RESTORE);
394
+ }
395
+ else
396
+ {
397
+ ShowWindow(foundHwnd, SW_SHOW);
398
+ }
399
+
400
+ IntPtr fgHwnd = GetForegroundWindow();
401
+ uint fgPid;
402
+ uint fgThreadId = GetWindowThreadProcessId(fgHwnd, out fgPid);
403
+ uint curThreadId = GetCurrentThreadId();
404
+ if (fgThreadId != 0 && fgThreadId != curThreadId)
405
+ {
406
+ AttachThreadInput(curThreadId, fgThreadId, true);
407
+ }
408
+
409
+ keybd_event(0x12, 0, 0, UIntPtr.Zero);
410
+ keybd_event(0x12, 0, 0x0002, UIntPtr.Zero);
411
+ SetForegroundWindow(foundHwnd);
412
+ BringWindowToTop(foundHwnd);
413
+
414
+ if (fgThreadId != 0 && fgThreadId != curThreadId)
415
+ {
416
+ AttachThreadInput(curThreadId, fgThreadId, false);
417
+ }
418
+
419
+ Thread.Sleep(150);
420
+ return true;
421
+ }
422
+ }
423
+ catch { }
424
+ return false;
425
+ }
426
+
427
+ public static RECT? GetWindowBounds(string titleOrApp)
428
+ {
429
+ try
430
+ {
431
+ string target = titleOrApp.ToLower().Trim();
432
+ IntPtr foundHwnd = IntPtr.Zero;
433
+
434
+ EnumWindows((hWnd, lParam) =>
435
+ {
436
+ if (IsWindowVisible(hWnd))
437
+ {
438
+ StringBuilder sb = new StringBuilder(512);
439
+ GetWindowText(hWnd, sb, sb.Capacity);
440
+ string t = sb.ToString().Trim().ToLower();
441
+
442
+ uint pid;
443
+ GetWindowThreadProcessId(hWnd, out pid);
444
+ string pName = "";
445
+ try { pName = Process.GetProcessById((int)pid).ProcessName.ToLower(); } catch { }
446
+
447
+ if (t.Contains(target) || pName.Contains(target))
448
+ {
449
+ foundHwnd = hWnd;
450
+ return false;
451
+ }
452
+ }
453
+ return true;
454
+ }, IntPtr.Zero);
455
+
456
+ if (foundHwnd != IntPtr.Zero)
457
+ {
458
+ RECT rect;
459
+ if (GetWindowRect(foundHwnd, out rect))
460
+ {
461
+ return rect;
462
+ }
463
+ }
464
+ }
465
+ catch { }
466
+ return null;
467
+ }
468
+
469
+ public static bool LaunchApp(string appName, out int pid)
470
+ {
471
+ pid = 0;
472
+ try
473
+ {
474
+ STARTUPINFO si = new STARTUPINFO();
475
+ si.cb = Marshal.SizeOf(si);
476
+ si.lpDesktop = @"WinSta0\Default";
477
+ PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
478
+
479
+ string cmd = appName;
480
+ if (!cmd.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
481
+ {
482
+ cmd += ".exe";
483
+ }
484
+
485
+ bool ok = CreateProcess(null, cmd, IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref si, out pi);
486
+ if (ok)
487
+ {
488
+ pid = pi.dwProcessId;
489
+ return true;
490
+ }
491
+
492
+ // Fallback to Process.Start with UseShellExecute
493
+ try
494
+ {
495
+ ProcessStartInfo psi = new ProcessStartInfo(appName);
496
+ psi.UseShellExecute = true;
497
+ Process p = Process.Start(psi);
498
+ if (p != null)
499
+ {
500
+ pid = p.Id;
501
+ return true;
502
+ }
503
+ }
504
+ catch { }
505
+ }
506
+ catch { }
507
+ return false;
508
+ }
509
+
510
+ public static string Screenshot(string savePath)
511
+ {
512
+ int width = GetSystemMetrics(SM_CXSCREEN);
513
+ int height = GetSystemMetrics(SM_CYSCREEN);
514
+ if (width <= 0) width = 1920;
515
+ if (height <= 0) height = 1080;
516
+
517
+ string dir = Path.GetDirectoryName(savePath);
518
+ if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
519
+ {
520
+ Directory.CreateDirectory(dir);
521
+ }
522
+
523
+ using (Bitmap bmp = new Bitmap(width, height))
524
+ {
525
+ using (Graphics g = Graphics.FromImage(bmp))
526
+ {
527
+ g.CopyFromScreen(0, 0, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
528
+ }
529
+ bmp.Save(savePath, ImageFormat.Png);
530
+ }
531
+
532
+ return string.Format("{{\"success\":true,\"action\":\"screenshot\",\"width\":{0},\"height\":{1},\"path\":{2},\"activeWindow\":{3}}}",
533
+ width, height, JsonEscape(savePath), JsonEscape(GetActiveWindowTitle()));
534
+ }
535
+
536
+ public static bool TypeText(string text)
537
+ {
538
+ if (string.IsNullOrEmpty(text)) return true;
539
+ try
540
+ {
541
+ SendKeys.SendWait(EscapeForSendKeys(text));
542
+ return true;
543
+ }
544
+ catch
545
+ {
546
+ return false;
547
+ }
548
+ }
549
+
550
+ public static byte GetVirtualKey(string key)
551
+ {
552
+ if (string.IsNullOrEmpty(key)) return 0;
553
+ string k = key.ToLower().Trim();
554
+
555
+ // Modifiers
556
+ if (k == "ctrl" || k == "control" || k == "lctrl" || k == "rctrl") return 0x11; // VK_CONTROL
557
+ if (k == "alt" || k == "alternate" || k == "menu" || k == "lalt" || k == "ralt") return 0x12; // VK_MENU
558
+ if (k == "shift" || k == "lshift" || k == "rshift") return 0x10; // VK_SHIFT
559
+ if (k == "win" || k == "windows" || k == "super" || k == "lwin" || k == "cmd" || k == "command") return 0x5B; // VK_LWIN
560
+ if (k == "rwin") return 0x5C; // VK_RWIN
561
+
562
+ // Function keys F1 - F24
563
+ if (k == "f1") return 0x70;
564
+ if (k == "f2") return 0x71;
565
+ if (k == "f3") return 0x72;
566
+ if (k == "f4") return 0x73;
567
+ if (k == "f5") return 0x74;
568
+ if (k == "f6") return 0x75;
569
+ if (k == "f7") return 0x76;
570
+ if (k == "f8") return 0x77;
571
+ if (k == "f9") return 0x78;
572
+ if (k == "f10") return 0x79;
573
+ if (k == "f11") return 0x7A;
574
+ if (k == "f12") return 0x7B;
575
+ if (k == "f13") return 0x7C;
576
+ if (k == "f14") return 0x7D;
577
+ if (k == "f15") return 0x7E;
578
+ if (k == "f16") return 0x7F;
579
+
580
+ // Common special keys
581
+ if (k == "enter" || k == "return") return 0x0D; // VK_RETURN
582
+ if (k == "esc" || k == "escape") return 0x1B; // VK_ESCAPE
583
+ if (k == "tab") return 0x09; // VK_TAB
584
+ if (k == "space" || k == "spacebar") return 0x20; // VK_SPACE
585
+ if (k == "backspace" || k == "back" || k == "bksp") return 0x08; // VK_BACK
586
+ if (k == "del" || k == "delete") return 0x2E; // VK_DELETE
587
+ if (k == "ins" || k == "insert") return 0x2D; // VK_INSERT
588
+ if (k == "home") return 0x24; // VK_HOME
589
+ if (k == "end") return 0x23; // VK_END
590
+ if (k == "pageup" || k == "pgup") return 0x21; // VK_PRIOR
591
+ if (k == "pagedown" || k == "pgdn") return 0x22; // VK_NEXT
592
+ if (k == "left") return 0x25; // VK_LEFT
593
+ if (k == "up") return 0x26; // VK_UP
594
+ if (k == "right") return 0x27; // VK_RIGHT
595
+ if (k == "down") return 0x28; // VK_DOWN
596
+ if (k == "prtscn" || k == "printscreen" || k == "snapshot") return 0x2C; // VK_SNAPSHOT
597
+
598
+ // Single letters a-z
599
+ if (k.Length == 1 && k[0] >= 'a' && k[0] <= 'z')
600
+ {
601
+ return (byte)('A' + (k[0] - 'a'));
602
+ }
603
+ // Single digits 0-9
604
+ if (k.Length == 1 && k[0] >= '0' && k[0] <= '9')
605
+ {
606
+ return (byte)k[0];
607
+ }
608
+
609
+ // Punctuation and symbols
610
+ if (k == "+" || k == "plus" || k == "add") return 0xBB; // VK_OEM_PLUS
611
+ if (k == "-" || k == "minus" || k == "subtract") return 0xBD; // VK_OEM_MINUS
612
+ if (k == "=" || k == "equal" || k == "equals") return 0xBB;
613
+ if (k == "," || k == "comma") return 0xBC; // VK_OEM_COMMA
614
+ if (k == "." || k == "period" || k == "dot") return 0xBE; // VK_OEM_PERIOD
615
+ if (k == "/" || k == "slash") return 0xBF; // VK_OEM_2
616
+ if (k == "\\" || k == "backslash") return 0xDC; // VK_OEM_5
617
+ if (k == ";" || k == "semicolon") return 0xBA; // VK_OEM_1
618
+ if (k == "'" || k == "quote" || k == "singlequote") return 0xDE; // VK_OEM_7
619
+ if (k == "[" || k == "openbracket") return 0xDB; // VK_OEM_4
620
+ if (k == "]" || k == "closebracket") return 0xDD; // VK_OEM_6
621
+ if (k == "`" || k == "backtick") return 0xC0; // VK_OEM_3
622
+
623
+ return 0;
624
+ }
625
+
626
+ public static string FormatKeyForSendKeys(string key)
627
+ {
628
+ if (string.IsNullOrEmpty(key)) return "";
629
+ string k = key.ToLower().Trim();
630
+ if (k == "enter" || k == "return") return "{ENTER}";
631
+ if (k == "tab") return "{TAB}";
632
+ if (k == "esc" || k == "escape") return "{ESC}";
633
+ if (k == "backspace" || k == "back" || k == "bksp") return "{BACKSPACE}";
634
+ if (k == "del" || k == "delete") return "{DEL}";
635
+ if (k == "ins" || k == "insert") return "{INS}";
636
+ if (k == "up") return "{UP}";
637
+ if (k == "down") return "{DOWN}";
638
+ if (k == "left") return "{LEFT}";
639
+ if (k == "right") return "{RIGHT}";
640
+ if (k == "space" || k == "spacebar") return " ";
641
+ if (k == "home") return "{HOME}";
642
+ if (k == "end") return "{END}";
643
+ if (k == "pageup" || k == "pgup") return "{PGUP}";
644
+ if (k == "pagedown" || k == "pgdn") return "{PGDN}";
645
+
646
+ if (k.Length >= 2 && k[0] == 'f')
647
+ {
648
+ int fnum;
649
+ if (int.TryParse(k.Substring(1), out fnum) && fnum >= 1 && fnum <= 24)
650
+ {
651
+ return "{" + k.ToUpper() + "}";
652
+ }
653
+ }
654
+
655
+ if (k.Length == 1)
656
+ {
657
+ char c = k[0];
658
+ if (c == '+' || c == '^' || c == '%' || c == '~' || c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']')
659
+ {
660
+ return "{" + c + "}";
661
+ }
662
+ return k;
663
+ }
664
+
665
+ return "{" + key.ToUpper() + "}";
666
+ }
667
+
668
+ public static bool KeyPress(string key)
669
+ {
670
+ byte vk = GetVirtualKey(key);
671
+ if (vk != 0)
672
+ {
673
+ keybd_event(vk, 0, 0, UIntPtr.Zero);
674
+ Thread.Sleep(20);
675
+ keybd_event(vk, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
676
+ return true;
677
+ }
678
+ try
679
+ {
680
+ string sendStr = FormatKeyForSendKeys(key);
681
+ SendKeys.SendWait(sendStr);
682
+ return true;
683
+ }
684
+ catch
685
+ {
686
+ return false;
687
+ }
688
+ }
689
+
690
+ public static bool Hotkey(string[] keys)
691
+ {
692
+ if (keys == null || keys.Length == 0) return false;
693
+
694
+ List<byte> vkList = new List<byte>();
695
+ foreach (string k in keys)
696
+ {
697
+ byte vk = GetVirtualKey(k);
698
+ if (vk != 0) vkList.Add(vk);
699
+ }
700
+
701
+ if (vkList.Count == keys.Length)
702
+ {
703
+ // Native virtual key press via keybd_event
704
+ for (int i = 0; i < vkList.Count; i++)
705
+ {
706
+ keybd_event(vkList[i], 0, 0, UIntPtr.Zero);
707
+ Thread.Sleep(20);
708
+ }
709
+ Thread.Sleep(40);
710
+ for (int i = vkList.Count - 1; i >= 0; i--)
711
+ {
712
+ keybd_event(vkList[i], 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
713
+ Thread.Sleep(20);
714
+ }
715
+ return true;
716
+ }
717
+
718
+ // Fallback to SendKeys with proper formatting
719
+ try
720
+ {
721
+ string prefix = "";
722
+ string mainKey = "";
723
+ foreach (string k in keys)
724
+ {
725
+ string lk = k.ToLower().Trim();
726
+ if (lk == "ctrl" || lk == "control") prefix += "^";
727
+ else if (lk == "alt" || lk == "alternate") prefix += "%";
728
+ else if (lk == "shift") prefix += "+";
729
+ else mainKey = k;
730
+ }
731
+ string sendStr = prefix + FormatKeyForSendKeys(mainKey);
732
+ SendKeys.SendWait(sendStr);
733
+ return true;
734
+ }
735
+ catch
736
+ {
737
+ return false;
738
+ }
739
+ }
740
+
741
+ public static bool CloseWindow(string titleOrApp)
742
+ {
743
+ try
744
+ {
745
+ IntPtr targetHwnd = IntPtr.Zero;
746
+ if (string.IsNullOrEmpty(titleOrApp))
747
+ {
748
+ targetHwnd = GetForegroundWindow();
749
+ }
750
+ else
751
+ {
752
+ string target = titleOrApp.ToLower().Trim();
753
+ EnumWindows((hWnd, lParam) =>
754
+ {
755
+ if (IsWindowVisible(hWnd))
756
+ {
757
+ StringBuilder sb = new StringBuilder(256);
758
+ GetWindowText(hWnd, sb, 256);
759
+ string title = sb.ToString();
760
+ if (!string.IsNullOrEmpty(title) && title.ToLower().Contains(target))
761
+ {
762
+ targetHwnd = hWnd;
763
+ return false;
764
+ }
765
+ }
766
+ return true;
767
+ }, IntPtr.Zero);
768
+ }
769
+
770
+ if (targetHwnd != IntPtr.Zero)
771
+ {
772
+ PostMessage(targetHwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
773
+ return true;
774
+ }
775
+ }
776
+ catch { }
777
+ return false;
778
+ }
779
+
780
+ public static string EscapeForSendKeys(string text)
781
+ {
782
+ if (string.IsNullOrEmpty(text)) return "";
783
+ StringBuilder sb = new StringBuilder();
784
+ foreach (char c in text)
785
+ {
786
+ if (c == '\r') continue;
787
+ if (c == '\n')
788
+ {
789
+ sb.Append("{ENTER}");
790
+ }
791
+ else if (c == '\t')
792
+ {
793
+ sb.Append("{TAB}");
794
+ }
795
+ else if (c == '+' || c == '^' || c == '%' || c == '~' || c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']')
796
+ {
797
+ sb.Append("{" + c + "}");
798
+ }
799
+ else
800
+ {
801
+ sb.Append(c);
802
+ }
803
+ }
804
+ return sb.ToString();
805
+ }
806
+
807
+ public static string JsonEscape(string text)
808
+ {
809
+ if (text == null) return "null";
810
+ StringBuilder sb = new StringBuilder();
811
+ sb.Append("\"");
812
+ foreach (char c in text)
813
+ {
814
+ if (c == '\\') sb.Append("\\\\");
815
+ else if (c == '\"') sb.Append("\\\"");
816
+ else if (c == '\n') sb.Append("\\n");
817
+ else if (c == '\r') sb.Append("\\r");
818
+ else if (c == '\t') sb.Append("\\t");
819
+ else if (c < ' ') sb.Append(string.Format("\\u{0:x4}", (int)c));
820
+ else sb.Append(c);
821
+ }
822
+ sb.Append("\"");
823
+ return sb.ToString();
824
+ }
825
+
826
+ public static string ExecuteAction(string action, string[] args)
827
+ {
828
+ try
829
+ {
830
+ string act = action.ToLower().Trim();
831
+
832
+ if (act == "move")
833
+ {
834
+ int x = int.Parse(args[0]);
835
+ int y = int.Parse(args[1]);
836
+ bool ok = Move(x, y);
837
+ POINT pos = GetPosition();
838
+ return string.Format("{{\"success\":{0},\"action\":\"move\",\"coordinates\":{{\"x\":{1},\"y\":{2}}},\"activeWindow\":{3}}}",
839
+ ok ? "true" : "false", pos.X, pos.Y, JsonEscape(GetActiveWindowTitle()));
840
+ }
841
+
842
+ if (act == "click" || act == "left_click")
843
+ {
844
+ int x = int.Parse(args[0]);
845
+ int y = int.Parse(args[1]);
846
+ string btn = (args.Length > 2 && !string.IsNullOrEmpty(args[2])) ? args[2] : "left";
847
+ bool ok = Click(btn, x, y, 1);
848
+ POINT pos = GetPosition();
849
+ return string.Format("{{\"success\":{0},\"action\":\"click\",\"button\":\"{1}\",\"coordinates\":{{\"x\":{2},\"y\":{3}}},\"activeWindow\":{4}}}",
850
+ ok ? "true" : "false", btn, pos.X, pos.Y, JsonEscape(GetActiveWindowTitle()));
851
+ }
852
+
853
+ if (act == "right_click")
854
+ {
855
+ int x = int.Parse(args[0]);
856
+ int y = int.Parse(args[1]);
857
+ bool ok = Click("right", x, y, 1);
858
+ POINT pos = GetPosition();
859
+ return string.Format("{{\"success\":{0},\"action\":\"right_click\",\"button\":\"right\",\"coordinates\":{{\"x\":{1},\"y\":{2}}},\"activeWindow\":{3}}}",
860
+ ok ? "true" : "false", pos.X, pos.Y, JsonEscape(GetActiveWindowTitle()));
861
+ }
862
+
863
+ if (act == "double_click")
864
+ {
865
+ int x = int.Parse(args[0]);
866
+ int y = int.Parse(args[1]);
867
+ bool ok = Click("left", x, y, 2);
868
+ POINT pos = GetPosition();
869
+ return string.Format("{{\"success\":{0},\"action\":\"double_click\",\"coordinates\":{{\"x\":{1},\"y\":{2}}},\"activeWindow\":{3}}}",
870
+ ok ? "true" : "false", pos.X, pos.Y, JsonEscape(GetActiveWindowTitle()));
871
+ }
872
+
873
+ if (act == "mouse_down")
874
+ {
875
+ int x = args.Length > 0 ? int.Parse(args[0]) : -1;
876
+ int y = args.Length > 1 ? int.Parse(args[1]) : -1;
877
+ string btn = (args.Length > 2 && !string.IsNullOrEmpty(args[2])) ? args[2] : "left";
878
+ bool ok = MouseDown(btn, x, y);
879
+ POINT pos = GetPosition();
880
+ return string.Format("{{\"success\":{0},\"action\":\"mouse_down\",\"button\":\"{1}\",\"coordinates\":{{\"x\":{2},\"y\":{3}}}}}",
881
+ ok ? "true" : "false", btn, pos.X, pos.Y);
882
+ }
883
+
884
+ if (act == "mouse_up")
885
+ {
886
+ int x = args.Length > 0 ? int.Parse(args[0]) : -1;
887
+ int y = args.Length > 1 ? int.Parse(args[1]) : -1;
888
+ string btn = (args.Length > 2 && !string.IsNullOrEmpty(args[2])) ? args[2] : "left";
889
+ bool ok = MouseUp(btn, x, y);
890
+ POINT pos = GetPosition();
891
+ return string.Format("{{\"success\":{0},\"action\":\"mouse_up\",\"button\":\"{1}\",\"coordinates\":{{\"x\":{2},\"y\":{3}}}}}",
892
+ ok ? "true" : "false", btn, pos.X, pos.Y);
893
+ }
894
+
895
+ if (act == "drag")
896
+ {
897
+ int sx = int.Parse(args[0]);
898
+ int sy = int.Parse(args[1]);
899
+ int ex = int.Parse(args[2]);
900
+ int ey = int.Parse(args[3]);
901
+ bool ok = Drag(sx, sy, ex, ey);
902
+ POINT pos = GetPosition();
903
+ return string.Format("{{\"success\":{0},\"action\":\"drag\",\"from\":{{\"x\":{1},\"y\":{2}}},\"to\":{{\"x\":{3},\"y\":{4}}},\"coordinates\":{{\"x\":{5},\"y\":{6}}}}}",
904
+ ok ? "true" : "false", sx, sy, ex, ey, pos.X, pos.Y);
905
+ }
906
+
907
+ if (act == "scroll")
908
+ {
909
+ int dx = args.Length > 0 ? int.Parse(args[0]) : 0;
910
+ int dy = args.Length > 1 ? int.Parse(args[1]) : 0;
911
+ bool ok = Scroll(dx, dy);
912
+ return string.Format("{{\"success\":{0},\"action\":\"scroll\",\"deltaX\":{1},\"deltaY\":{2}}}",
913
+ ok ? "true" : "false", dx, dy);
914
+ }
915
+
916
+ if (act == "position")
917
+ {
918
+ POINT pos = GetPosition();
919
+ return string.Format("{{\"success\":true,\"action\":\"position\",\"coordinates\":{{\"x\":{0},\"y\":{1}}}}}",
920
+ pos.X, pos.Y);
921
+ }
922
+
923
+ if (act == "screen_info")
924
+ {
925
+ int width = GetSystemMetrics(SM_CXSCREEN);
926
+ int height = GetSystemMetrics(SM_CYSCREEN);
927
+ POINT pos = GetPosition();
928
+ return string.Format("{{\"success\":true,\"width\":{0},\"height\":{1},\"cursor\":{{\"x\":{2},\"y\":{3}}},\"activeWindow\":{4}}}",
929
+ width, height, pos.X, pos.Y, JsonEscape(GetActiveWindowTitle()));
930
+ }
931
+
932
+ if (act == "screenshot")
933
+ {
934
+ string path = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "screen.png");
935
+ return Screenshot(path);
936
+ }
937
+
938
+ if (act == "type")
939
+ {
940
+ string text = args.Length > 0 ? args[0] : "";
941
+ bool ok = TypeText(text);
942
+ return string.Format("{{\"success\":{0},\"action\":\"type\",\"chars\":{1},\"activeWindow\":{2}}}",
943
+ ok ? "true" : "false", text.Length, JsonEscape(GetActiveWindowTitle()));
944
+ }
945
+
946
+ if (act == "key" || act == "key_press")
947
+ {
948
+ string key = args.Length > 0 ? args[0] : "";
949
+ bool ok = KeyPress(key);
950
+ return string.Format("{{\"success\":{0},\"action\":\"key_press\",\"key\":{1},\"activeWindow\":{2}}}",
951
+ ok ? "true" : "false", JsonEscape(key), JsonEscape(GetActiveWindowTitle()));
952
+ }
953
+
954
+ if (act == "hotkey")
955
+ {
956
+ string[] keys;
957
+ if (args.Length > 1)
958
+ {
959
+ keys = args;
960
+ }
961
+ else if (args.Length == 1)
962
+ {
963
+ keys = args[0].Split(new char[] { '+', ',' }, StringSplitOptions.RemoveEmptyEntries);
964
+ }
965
+ else
966
+ {
967
+ keys = new string[0];
968
+ }
969
+ for (int i = 0; i < keys.Length; i++)
970
+ {
971
+ keys[i] = keys[i].Trim();
972
+ }
973
+ bool ok = Hotkey(keys);
974
+ return string.Format("{{\"success\":{0},\"action\":\"hotkey\",\"keys\":{1},\"activeWindow\":{2}}}",
975
+ ok ? "true" : "false", JsonEscape(string.Join("+", keys)), JsonEscape(GetActiveWindowTitle()));
976
+ }
977
+
978
+ if (act == "active_window")
979
+ {
980
+ string title = GetActiveWindowTitle();
981
+ return string.Format("{{\"success\":true,\"action\":\"active_window\",\"activeWindow\":{0}}}",
982
+ JsonEscape(title));
983
+ }
984
+
985
+ if (act == "list_windows")
986
+ {
987
+ List<string> wins = GetVisibleWindows();
988
+ StringBuilder sb = new StringBuilder();
989
+ sb.Append("[");
990
+ for (int i = 0; i < wins.Count; i++)
991
+ {
992
+ sb.Append(JsonEscape(wins[i]));
993
+ if (i < wins.Count - 1) sb.Append(",");
994
+ }
995
+ sb.Append("]");
996
+ return string.Format("{{\"success\":true,\"action\":\"list_windows\",\"activeWindow\":{0},\"windows\":{1}}}",
997
+ JsonEscape(GetActiveWindowTitle()), sb.ToString());
998
+ }
999
+
1000
+ if (act == "focus_window")
1001
+ {
1002
+ string win = args.Length > 0 ? args[0] : "";
1003
+ bool ok = FocusWindow(win);
1004
+ return string.Format("{{\"success\":{0},\"action\":\"focus_window\",\"target\":{1},\"activeWindow\":{2}}}",
1005
+ ok ? "true" : "false", JsonEscape(win), JsonEscape(GetActiveWindowTitle()));
1006
+ }
1007
+
1008
+ if (act == "close_window" || act == "close_app")
1009
+ {
1010
+ string win = args.Length > 0 ? args[0] : "";
1011
+ bool ok = CloseWindow(win);
1012
+ return string.Format("{{\"success\":{0},\"action\":\"close_window\",\"target\":{1},\"activeWindow\":{2}}}",
1013
+ ok ? "true" : "false", JsonEscape(win), JsonEscape(GetActiveWindowTitle()));
1014
+ }
1015
+
1016
+ if (act == "launch" || act == "open_app")
1017
+ {
1018
+ string app = args.Length > 0 ? args[0] : "";
1019
+ int pid;
1020
+ bool ok = LaunchApp(app, out pid);
1021
+ return string.Format("{{\"success\":{0},\"action\":\"launch\",\"app\":{1},\"pid\":{2}}}",
1022
+ ok ? "true" : "false", JsonEscape(app), pid);
1023
+ }
1024
+
1025
+ if (act == "window_bounds")
1026
+ {
1027
+ string win = args.Length > 0 ? args[0] : "";
1028
+ RECT? r = GetWindowBounds(win);
1029
+ if (r.HasValue)
1030
+ {
1031
+ int w = r.Value.Right - r.Value.Left;
1032
+ int h = r.Value.Bottom - r.Value.Top;
1033
+ return string.Format("{{\"success\":true,\"action\":\"window_bounds\",\"x\":{0},\"y\":{1},\"width\":{2},\"height\":{3}}}",
1034
+ r.Value.Left, r.Value.Top, w, h);
1035
+ }
1036
+ return string.Format("{{\"success\":false,\"action\":\"window_bounds\",\"error\":{0}}}", JsonEscape("Window not found: " + win));
1037
+ }
1038
+
1039
+ if (act == "diagnostic")
1040
+ {
1041
+ int width = GetSystemMetrics(SM_CXSCREEN);
1042
+ int height = GetSystemMetrics(SM_CYSCREEN);
1043
+ POINT pos = GetPosition();
1044
+ string title = GetActiveWindowTitle();
1045
+ List<string> wins = GetVisibleWindows();
1046
+
1047
+ return string.Format("{{\"success\":true,\"action\":\"diagnostic\",\"width\":{0},\"height\":{1},\"mouse\":{{\"x\":{2},\"y\":{3}}},\"activeWindow\":{4},\"windowCount\":{5}}}",
1048
+ width, height, pos.X, pos.Y, JsonEscape(title), wins.Count);
1049
+ }
1050
+
1051
+ return string.Format("{{\"success\":false,\"error\":{0},\"recoverable\":true}}",
1052
+ JsonEscape("Unknown action: " + action));
1053
+ }
1054
+ catch (Exception ex)
1055
+ {
1056
+ return string.Format("{{\"success\":false,\"action\":{0},\"error\":{1},\"recoverable\":true}}",
1057
+ JsonEscape(action), JsonEscape(ex.Message));
1058
+ }
1059
+ }
1060
+
1061
+ public static void RunServer()
1062
+ {
1063
+ AttachInputDesktop();
1064
+ string line;
1065
+ while ((line = Console.ReadLine()) != null)
1066
+ {
1067
+ line = line.Trim();
1068
+ if (string.IsNullOrEmpty(line)) continue;
1069
+ if (line == "exit" || line == "quit") break;
1070
+
1071
+ // Simple format: action arg1 arg2 ... or base64
1072
+ string result = "";
1073
+ try
1074
+ {
1075
+ string[] parts = ParseCommandLine(line);
1076
+ if (parts.Length > 0)
1077
+ {
1078
+ string action = parts[0];
1079
+ string[] args = new string[parts.Length - 1];
1080
+ Array.Copy(parts, 1, args, 0, args.Length);
1081
+ result = ExecuteAction(action, args);
1082
+ }
1083
+ else
1084
+ {
1085
+ result = "{\"success\":false,\"error\":\"Empty command\"}";
1086
+ }
1087
+ }
1088
+ catch (Exception ex)
1089
+ {
1090
+ result = string.Format("{{\"success\":false,\"error\":{0}}}", JsonEscape(ex.Message));
1091
+ }
1092
+ Console.WriteLine(result);
1093
+ Console.Out.Flush();
1094
+ }
1095
+ }
1096
+
1097
+ private static string[] ParseCommandLine(string cmdLine)
1098
+ {
1099
+ List<string> args = new List<string>();
1100
+ bool inQuotes = false;
1101
+ StringBuilder current = new StringBuilder();
1102
+
1103
+ for (int i = 0; i < cmdLine.Length; i++)
1104
+ {
1105
+ char c = cmdLine[i];
1106
+ if (c == '\"')
1107
+ {
1108
+ inQuotes = !inQuotes;
1109
+ }
1110
+ else if (c == ' ' && !inQuotes)
1111
+ {
1112
+ if (current.Length > 0)
1113
+ {
1114
+ args.Add(current.ToString());
1115
+ current.Clear();
1116
+ }
1117
+ }
1118
+ else
1119
+ {
1120
+ current.Append(c);
1121
+ }
1122
+ }
1123
+ if (current.Length > 0)
1124
+ {
1125
+ args.Add(current.ToString());
1126
+ }
1127
+ return args.ToArray();
1128
+ }
1129
+
1130
+ [STAThread]
1131
+ public static void Main(string[] args)
1132
+ {
1133
+ // Thread execution with desktop attachment
1134
+ Thread t = new Thread(() =>
1135
+ {
1136
+ AttachInputDesktop();
1137
+
1138
+ if (args.Length > 0 && args[0] == "--server")
1139
+ {
1140
+ RunServer();
1141
+ return;
1142
+ }
1143
+
1144
+ if (args.Length == 0)
1145
+ {
1146
+ Console.WriteLine(ExecuteAction("diagnostic", new string[0]));
1147
+ return;
1148
+ }
1149
+
1150
+ string action = args[0];
1151
+ string[] actionArgs = new string[args.Length - 1];
1152
+ Array.Copy(args, 1, actionArgs, 0, actionArgs.Length);
1153
+ Console.WriteLine(ExecuteAction(action, actionArgs));
1154
+ });
1155
+
1156
+ t.SetApartmentState(ApartmentState.STA);
1157
+ t.Start();
1158
+ t.Join();
1159
+ }
1160
+ }
1161
+ }