screenhand 0.1.1 → 0.2.0

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 (177) hide show
  1. package/README.md +458 -93
  2. package/dist/.audit-log.jsonl +55 -0
  3. package/dist/.screenhand/memory/.lock +1 -0
  4. package/dist/.screenhand/memory/actions.jsonl +85 -0
  5. package/dist/.screenhand/memory/errors.jsonl +5 -0
  6. package/dist/.screenhand/memory/errors.jsonl.bak +4 -0
  7. package/dist/.screenhand/memory/state.json +35 -0
  8. package/dist/.screenhand/memory/state.json.bak +35 -0
  9. package/dist/.screenhand/memory/strategies.jsonl +12 -0
  10. package/dist/agent/cli.js +73 -0
  11. package/dist/agent/loop.js +258 -0
  12. package/dist/config.js +9 -0
  13. package/dist/index.js +56 -0
  14. package/dist/logging/timeline-logger.js +29 -0
  15. package/dist/mcp/mcp-stdio-server.js +448 -0
  16. package/dist/mcp/server.js +347 -0
  17. package/dist/mcp-desktop.js +2731 -0
  18. package/dist/mcp-entry.js +59 -0
  19. package/dist/memory/recall.js +160 -0
  20. package/dist/memory/research.js +98 -0
  21. package/dist/memory/seeds.js +89 -0
  22. package/dist/memory/session.js +161 -0
  23. package/dist/memory/store.js +391 -0
  24. package/dist/memory/types.js +4 -0
  25. package/dist/monitor/codex-monitor.js +377 -0
  26. package/dist/monitor/task-queue.js +84 -0
  27. package/dist/monitor/types.js +49 -0
  28. package/dist/native/bridge-client.js +174 -0
  29. package/dist/native/macos-bridge-client.js +5 -0
  30. package/dist/npm-publish-helper.js +117 -0
  31. package/dist/npm-token-cdp.js +113 -0
  32. package/dist/npm-token-create.js +135 -0
  33. package/dist/npm-token-finish.js +126 -0
  34. package/dist/playbook/engine.js +193 -0
  35. package/dist/playbook/index.js +4 -0
  36. package/dist/playbook/recorder.js +519 -0
  37. package/dist/playbook/runner.js +392 -0
  38. package/dist/playbook/store.js +166 -0
  39. package/dist/playbook/types.js +4 -0
  40. package/dist/runtime/accessibility-adapter.js +377 -0
  41. package/dist/runtime/app-adapter.js +48 -0
  42. package/dist/runtime/applescript-adapter.js +283 -0
  43. package/dist/runtime/ax-role-map.js +80 -0
  44. package/dist/runtime/browser-adapter.js +36 -0
  45. package/dist/runtime/cdp-chrome-adapter.js +505 -0
  46. package/dist/runtime/composite-adapter.js +205 -0
  47. package/dist/runtime/executor.js +250 -0
  48. package/dist/runtime/locator-cache.js +12 -0
  49. package/dist/runtime/planning-loop.js +47 -0
  50. package/dist/runtime/service.js +372 -0
  51. package/dist/runtime/session-manager.js +28 -0
  52. package/dist/runtime/state-observer.js +105 -0
  53. package/dist/runtime/vision-adapter.js +208 -0
  54. package/dist/scripts/codex-monitor-daemon.js +335 -0
  55. package/dist/scripts/supervisor-daemon.js +272 -0
  56. package/dist/scripts/worker-daemon.js +228 -0
  57. package/dist/src/agent/cli.js +82 -0
  58. package/dist/src/agent/loop.js +274 -0
  59. package/{src/config.ts → dist/src/config.js} +5 -10
  60. package/{src/index.ts → dist/src/index.js} +32 -52
  61. package/dist/src/jobs/manager.js +237 -0
  62. package/dist/src/jobs/runner.js +683 -0
  63. package/dist/src/jobs/store.js +102 -0
  64. package/dist/src/jobs/types.js +30 -0
  65. package/dist/src/jobs/worker.js +97 -0
  66. package/dist/src/logging/timeline-logger.js +45 -0
  67. package/dist/src/mcp/mcp-stdio-server.js +464 -0
  68. package/dist/src/mcp/server.js +363 -0
  69. package/dist/src/mcp-entry.js +60 -0
  70. package/dist/src/memory/recall.js +170 -0
  71. package/dist/src/memory/research.js +104 -0
  72. package/dist/src/memory/seeds.js +101 -0
  73. package/dist/src/memory/service.js +421 -0
  74. package/dist/src/memory/session.js +169 -0
  75. package/dist/src/memory/store.js +422 -0
  76. package/dist/src/memory/types.js +17 -0
  77. package/dist/src/monitor/codex-monitor.js +382 -0
  78. package/dist/src/monitor/task-queue.js +97 -0
  79. package/dist/src/monitor/types.js +62 -0
  80. package/dist/src/native/bridge-client.js +190 -0
  81. package/{src/native/macos-bridge-client.ts → dist/src/native/macos-bridge-client.js} +0 -1
  82. package/dist/src/playbook/engine.js +201 -0
  83. package/dist/src/playbook/index.js +20 -0
  84. package/dist/src/playbook/recorder.js +535 -0
  85. package/dist/src/playbook/runner.js +408 -0
  86. package/dist/src/playbook/store.js +183 -0
  87. package/dist/src/playbook/types.js +17 -0
  88. package/dist/src/runtime/accessibility-adapter.js +393 -0
  89. package/dist/src/runtime/app-adapter.js +64 -0
  90. package/dist/src/runtime/applescript-adapter.js +299 -0
  91. package/dist/src/runtime/ax-role-map.js +96 -0
  92. package/dist/src/runtime/browser-adapter.js +52 -0
  93. package/dist/src/runtime/cdp-chrome-adapter.js +521 -0
  94. package/dist/src/runtime/composite-adapter.js +221 -0
  95. package/dist/src/runtime/execution-contract.js +159 -0
  96. package/dist/src/runtime/executor.js +266 -0
  97. package/{src/runtime/locator-cache.ts → dist/src/runtime/locator-cache.js} +10 -15
  98. package/dist/src/runtime/planning-loop.js +63 -0
  99. package/dist/src/runtime/service.js +388 -0
  100. package/dist/src/runtime/session-manager.js +60 -0
  101. package/dist/src/runtime/state-observer.js +121 -0
  102. package/dist/src/runtime/vision-adapter.js +224 -0
  103. package/dist/src/supervisor/locks.js +186 -0
  104. package/dist/src/supervisor/supervisor.js +403 -0
  105. package/dist/src/supervisor/types.js +30 -0
  106. package/dist/src/test-mcp-protocol.js +154 -0
  107. package/dist/src/types.js +17 -0
  108. package/dist/src/util/atomic-write.js +118 -0
  109. package/dist/test-mcp-protocol.js +138 -0
  110. package/dist/types.js +1 -0
  111. package/package.json +18 -4
  112. package/.claude/commands/automate.md +0 -28
  113. package/.claude/commands/debug-ui.md +0 -19
  114. package/.claude/commands/screenshot.md +0 -15
  115. package/.github/FUNDING.yml +0 -1
  116. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
  117. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  118. package/.mcp.json +0 -8
  119. package/DESKTOP_MCP_GUIDE.md +0 -92
  120. package/SECURITY.md +0 -44
  121. package/docs/architecture.md +0 -47
  122. package/install-skills.sh +0 -19
  123. package/mcp-bridge.ts +0 -271
  124. package/mcp-desktop.ts +0 -1221
  125. package/native/macos-bridge/Package.swift +0 -21
  126. package/native/macos-bridge/Sources/AccessibilityBridge.swift +0 -261
  127. package/native/macos-bridge/Sources/AppManagement.swift +0 -129
  128. package/native/macos-bridge/Sources/CoreGraphicsBridge.swift +0 -242
  129. package/native/macos-bridge/Sources/ObserverBridge.swift +0 -120
  130. package/native/macos-bridge/Sources/VisionBridge.swift +0 -80
  131. package/native/macos-bridge/Sources/main.swift +0 -345
  132. package/native/windows-bridge/AppManagement.cs +0 -234
  133. package/native/windows-bridge/InputBridge.cs +0 -436
  134. package/native/windows-bridge/Program.cs +0 -265
  135. package/native/windows-bridge/ScreenCapture.cs +0 -329
  136. package/native/windows-bridge/UIAutomationBridge.cs +0 -571
  137. package/native/windows-bridge/WindowsBridge.csproj +0 -17
  138. package/playbooks/devpost.json +0 -186
  139. package/playbooks/instagram.json +0 -41
  140. package/playbooks/instagram_v2.json +0 -201
  141. package/playbooks/x_v1.json +0 -211
  142. package/scripts/devpost-live-loop.mjs +0 -421
  143. package/src/logging/timeline-logger.ts +0 -55
  144. package/src/mcp/server.ts +0 -449
  145. package/src/memory/recall.ts +0 -191
  146. package/src/memory/research.ts +0 -146
  147. package/src/memory/seeds.ts +0 -123
  148. package/src/memory/session.ts +0 -201
  149. package/src/memory/store.ts +0 -434
  150. package/src/memory/types.ts +0 -69
  151. package/src/native/bridge-client.ts +0 -239
  152. package/src/runtime/accessibility-adapter.ts +0 -487
  153. package/src/runtime/app-adapter.ts +0 -169
  154. package/src/runtime/applescript-adapter.ts +0 -376
  155. package/src/runtime/ax-role-map.ts +0 -102
  156. package/src/runtime/browser-adapter.ts +0 -129
  157. package/src/runtime/cdp-chrome-adapter.ts +0 -676
  158. package/src/runtime/composite-adapter.ts +0 -274
  159. package/src/runtime/executor.ts +0 -396
  160. package/src/runtime/planning-loop.ts +0 -81
  161. package/src/runtime/service.ts +0 -448
  162. package/src/runtime/session-manager.ts +0 -50
  163. package/src/runtime/state-observer.ts +0 -136
  164. package/src/runtime/vision-adapter.ts +0 -297
  165. package/src/types.ts +0 -297
  166. package/tests/bridge-client.test.ts +0 -176
  167. package/tests/browser-stealth.test.ts +0 -210
  168. package/tests/composite-adapter.test.ts +0 -64
  169. package/tests/mcp-server.test.ts +0 -151
  170. package/tests/memory-recall.test.ts +0 -339
  171. package/tests/memory-research.test.ts +0 -159
  172. package/tests/memory-seeds.test.ts +0 -120
  173. package/tests/memory-store.test.ts +0 -392
  174. package/tests/types.test.ts +0 -92
  175. package/tsconfig.check.json +0 -17
  176. package/tsconfig.json +0 -19
  177. package/vitest.config.ts +0 -8
@@ -1,571 +0,0 @@
1
- using System.Windows.Automation;
2
- using System.Runtime.InteropServices;
3
-
4
- namespace WindowsBridge;
5
-
6
- /// <summary>
7
- /// UI Automation wrapper — equivalent to macOS AccessibilityBridge.swift.
8
- /// Uses the Windows UI Automation framework to inspect and interact with UI elements.
9
- /// </summary>
10
- class UIAutomationBridge
11
- {
12
- /// <summary>
13
- /// Get the full UI element tree for a process.
14
- /// </summary>
15
- public Dictionary<string, object?> GetElementTree(int pid, int maxDepth)
16
- {
17
- var rootElement = GetRootElementForProcess(pid);
18
- return BuildTree(rootElement, 0, maxDepth, new List<int>());
19
- }
20
-
21
- /// <summary>
22
- /// Find an element by role, title, value, or identifier.
23
- /// </summary>
24
- public Dictionary<string, object?> FindElement(int pid, string? role, string? title,
25
- string? value, string? identifier, bool exact)
26
- {
27
- var rootElement = GetRootElementForProcess(pid);
28
-
29
- // Build conditions
30
- var conditions = new List<Condition>();
31
-
32
- if (!string.IsNullOrEmpty(role))
33
- {
34
- var controlType = MapRoleToControlType(role!);
35
- if (controlType != null)
36
- conditions.Add(new PropertyCondition(AutomationElement.ControlTypeProperty, controlType));
37
- }
38
-
39
- if (!string.IsNullOrEmpty(title))
40
- {
41
- if (exact)
42
- conditions.Add(new PropertyCondition(AutomationElement.NameProperty, title));
43
- // For non-exact, we'll filter after search
44
- }
45
-
46
- if (!string.IsNullOrEmpty(identifier))
47
- {
48
- conditions.Add(new PropertyCondition(AutomationElement.AutomationIdProperty, identifier));
49
- }
50
-
51
- Condition searchCondition;
52
- if (conditions.Count == 0)
53
- searchCondition = Condition.TrueCondition;
54
- else if (conditions.Count == 1)
55
- searchCondition = conditions[0];
56
- else
57
- searchCondition = new AndCondition(conditions.ToArray());
58
-
59
- AutomationElement? found;
60
-
61
- if (!string.IsNullOrEmpty(title) && !exact)
62
- {
63
- // For partial match, walk the tree manually
64
- found = FindElementByPartialName(rootElement, title!, role, 10);
65
- }
66
- else
67
- {
68
- found = rootElement.FindFirst(TreeScope.Descendants, searchCondition);
69
- }
70
-
71
- if (found == null)
72
- throw new BridgeException($"Element not found: role={role}, title={title}, value={value}");
73
-
74
- // Build element path for later reference
75
- var elementPath = GetElementPath(rootElement, found);
76
-
77
- var result = new Dictionary<string, object?>
78
- {
79
- ["role"] = MapControlTypeToRole(found.Current.ControlType),
80
- ["title"] = found.Current.Name,
81
- ["elementPath"] = elementPath,
82
- };
83
-
84
- try
85
- {
86
- var bounds = found.Current.BoundingRectangle;
87
- if (!bounds.IsEmpty)
88
- {
89
- result["bounds"] = new Dictionary<string, object>
90
- {
91
- ["x"] = bounds.X,
92
- ["y"] = bounds.Y,
93
- ["width"] = bounds.Width,
94
- ["height"] = bounds.Height,
95
- };
96
- }
97
- }
98
- catch { }
99
-
100
- // Try to get value
101
- try
102
- {
103
- if (found.TryGetCurrentPattern(ValuePattern.Pattern, out object? pattern))
104
- {
105
- result["value"] = ((ValuePattern)pattern).Current.Value;
106
- }
107
- }
108
- catch { }
109
-
110
- return result;
111
- }
112
-
113
- /// <summary>
114
- /// Perform an action on an element at the given path.
115
- /// Maps macOS AX actions to Windows UIA patterns.
116
- /// </summary>
117
- public Dictionary<string, object> PerformAction(int pid, int[] elementPath, string action)
118
- {
119
- var rootElement = GetRootElementForProcess(pid);
120
- var element = NavigateToElement(rootElement, elementPath);
121
-
122
- switch (action)
123
- {
124
- case "AXPress":
125
- case "press":
126
- case "click":
127
- if (element.TryGetCurrentPattern(InvokePattern.Pattern, out object? invokePattern))
128
- {
129
- ((InvokePattern)invokePattern).Invoke();
130
- }
131
- else if (element.TryGetCurrentPattern(TogglePattern.Pattern, out object? togglePattern))
132
- {
133
- ((TogglePattern)togglePattern).Toggle();
134
- }
135
- else if (element.TryGetCurrentPattern(SelectionItemPattern.Pattern, out object? selPattern))
136
- {
137
- ((SelectionItemPattern)selPattern).Select();
138
- }
139
- else if (element.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out object? ecPattern))
140
- {
141
- var p = (ExpandCollapsePattern)ecPattern;
142
- if (p.Current.ExpandCollapseState == ExpandCollapseState.Collapsed)
143
- p.Expand();
144
- else
145
- p.Collapse();
146
- }
147
- else
148
- {
149
- // Fallback: click at element center
150
- var bounds = element.Current.BoundingRectangle;
151
- if (!bounds.IsEmpty)
152
- {
153
- var x = bounds.X + bounds.Width / 2;
154
- var y = bounds.Y + bounds.Height / 2;
155
- new InputBridge().MouseClick(x, y, "left", 1);
156
- }
157
- else
158
- {
159
- throw new BridgeException($"Element does not support any click pattern and has no bounds");
160
- }
161
- }
162
- break;
163
-
164
- case "AXShowMenu":
165
- case "showMenu":
166
- if (element.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out object? expandPattern))
167
- {
168
- ((ExpandCollapsePattern)expandPattern).Expand();
169
- }
170
- break;
171
-
172
- case "AXScrollToVisible":
173
- case "scrollToVisible":
174
- if (element.TryGetCurrentPattern(ScrollItemPattern.Pattern, out object? scrollPattern))
175
- {
176
- ((ScrollItemPattern)scrollPattern).ScrollIntoView();
177
- }
178
- break;
179
-
180
- default:
181
- throw new BridgeException($"Unsupported action: {action}");
182
- }
183
-
184
- return new Dictionary<string, object> { ["ok"] = true };
185
- }
186
-
187
- /// <summary>
188
- /// Set value of a text field or similar element.
189
- /// </summary>
190
- public Dictionary<string, object> SetElementValue(int pid, int[] elementPath, string value)
191
- {
192
- var rootElement = GetRootElementForProcess(pid);
193
- var element = NavigateToElement(rootElement, elementPath);
194
-
195
- if (element.TryGetCurrentPattern(ValuePattern.Pattern, out object? pattern))
196
- {
197
- ((ValuePattern)pattern).SetValue(value);
198
- }
199
- else
200
- {
201
- // Fallback: focus and type
202
- try { element.SetFocus(); } catch { }
203
- System.Threading.Thread.Sleep(50);
204
- // Select all and type
205
- new InputBridge().KeyCombo(new[] { "ctrl", "a" });
206
- System.Threading.Thread.Sleep(50);
207
- new InputBridge().TypeText(value);
208
- }
209
-
210
- return new Dictionary<string, object> { ["ok"] = true };
211
- }
212
-
213
- /// <summary>
214
- /// Get value of an element.
215
- /// </summary>
216
- public Dictionary<string, object?> GetElementValue(int pid, int[] elementPath)
217
- {
218
- var rootElement = GetRootElementForProcess(pid);
219
- var element = NavigateToElement(rootElement, elementPath);
220
-
221
- string? val = null;
222
- if (element.TryGetCurrentPattern(ValuePattern.Pattern, out object? pattern))
223
- {
224
- val = ((ValuePattern)pattern).Current.Value;
225
- }
226
- else
227
- {
228
- val = element.Current.Name;
229
- }
230
-
231
- return new Dictionary<string, object?> { ["value"] = val };
232
- }
233
-
234
- /// <summary>
235
- /// Click a menu item by path (e.g., ["File", "New"]).
236
- /// </summary>
237
- public Dictionary<string, object> MenuClick(int pid, string[] menuPath)
238
- {
239
- var rootElement = GetRootElementForProcess(pid);
240
-
241
- // Find the menu bar
242
- var menuBar = rootElement.FindFirst(TreeScope.Children,
243
- new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.MenuBar));
244
-
245
- if (menuBar == null)
246
- {
247
- // Try looking in the window's children
248
- var window = rootElement.FindFirst(TreeScope.Children,
249
- new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
250
- if (window != null)
251
- {
252
- menuBar = window.FindFirst(TreeScope.Children,
253
- new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.MenuBar));
254
- }
255
- }
256
-
257
- if (menuBar == null)
258
- throw new BridgeException("Menu bar not found");
259
-
260
- AutomationElement current = menuBar;
261
-
262
- for (int i = 0; i < menuPath.Length; i++)
263
- {
264
- var menuName = menuPath[i];
265
-
266
- // Find the menu item by name
267
- var menuItem = current.FindFirst(TreeScope.Children,
268
- new PropertyCondition(AutomationElement.NameProperty, menuName));
269
-
270
- if (menuItem == null)
271
- {
272
- // Try partial match
273
- var children = current.FindAll(TreeScope.Children, Condition.TrueCondition);
274
- foreach (AutomationElement child in children)
275
- {
276
- if (child.Current.Name.Contains(menuName, StringComparison.OrdinalIgnoreCase))
277
- {
278
- menuItem = child;
279
- break;
280
- }
281
- }
282
- }
283
-
284
- if (menuItem == null)
285
- throw new BridgeException($"Menu item not found: {menuName}");
286
-
287
- if (i < menuPath.Length - 1)
288
- {
289
- // Expand submenu
290
- if (menuItem.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out object? ecPattern))
291
- {
292
- ((ExpandCollapsePattern)ecPattern).Expand();
293
- System.Threading.Thread.Sleep(100);
294
- }
295
- else if (menuItem.TryGetCurrentPattern(InvokePattern.Pattern, out object? invPattern))
296
- {
297
- ((InvokePattern)invPattern).Invoke();
298
- System.Threading.Thread.Sleep(100);
299
- }
300
-
301
- // After expanding, the submenu items should be children or in a popup
302
- current = menuItem;
303
- }
304
- else
305
- {
306
- // Click the final menu item
307
- if (menuItem.TryGetCurrentPattern(InvokePattern.Pattern, out object? invPattern))
308
- {
309
- ((InvokePattern)invPattern).Invoke();
310
- }
311
- else if (menuItem.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out object? ecPattern))
312
- {
313
- ((ExpandCollapsePattern)ecPattern).Expand();
314
- }
315
- }
316
- }
317
-
318
- return new Dictionary<string, object> { ["ok"] = true };
319
- }
320
-
321
- // ── Helpers ──
322
-
323
- private AutomationElement GetRootElementForProcess(int pid)
324
- {
325
- var root = AutomationElement.RootElement;
326
- var condition = new PropertyCondition(AutomationElement.ProcessIdProperty, pid);
327
- var element = root.FindFirst(TreeScope.Children, condition);
328
-
329
- if (element == null)
330
- {
331
- // Try finding any window with this PID
332
- var allWindows = root.FindAll(TreeScope.Children, Condition.TrueCondition);
333
- foreach (AutomationElement win in allWindows)
334
- {
335
- try
336
- {
337
- if (win.Current.ProcessId == pid)
338
- {
339
- element = win;
340
- break;
341
- }
342
- }
343
- catch { }
344
- }
345
- }
346
-
347
- if (element == null)
348
- throw new BridgeException($"No window found for PID {pid}");
349
-
350
- return element;
351
- }
352
-
353
- private Dictionary<string, object?> BuildTree(AutomationElement element, int depth, int maxDepth, List<int> path)
354
- {
355
- var node = new Dictionary<string, object?>
356
- {
357
- ["role"] = MapControlTypeToRole(element.Current.ControlType),
358
- };
359
-
360
- var name = element.Current.Name;
361
- if (!string.IsNullOrEmpty(name))
362
- node["title"] = name;
363
-
364
- // Get value if available
365
- try
366
- {
367
- if (element.TryGetCurrentPattern(ValuePattern.Pattern, out object? pattern))
368
- {
369
- var val = ((ValuePattern)pattern).Current.Value;
370
- if (!string.IsNullOrEmpty(val))
371
- node["value"] = val;
372
- }
373
- }
374
- catch { }
375
-
376
- // Get bounds
377
- try
378
- {
379
- var bounds = element.Current.BoundingRectangle;
380
- if (!bounds.IsEmpty)
381
- {
382
- node["bounds"] = new Dictionary<string, object>
383
- {
384
- ["x"] = bounds.X,
385
- ["y"] = bounds.Y,
386
- ["width"] = bounds.Width,
387
- ["height"] = bounds.Height,
388
- };
389
- }
390
- }
391
- catch { }
392
-
393
- node["path"] = path.ToArray();
394
-
395
- // Recurse into children
396
- if (depth < maxDepth)
397
- {
398
- try
399
- {
400
- var children = element.FindAll(TreeScope.Children, Condition.TrueCondition);
401
- if (children.Count > 0)
402
- {
403
- var childNodes = new List<Dictionary<string, object?>>();
404
- for (int i = 0; i < children.Count && i < 100; i++) // Cap at 100 children
405
- {
406
- var childPath = new List<int>(path) { i };
407
- try
408
- {
409
- childNodes.Add(BuildTree(children[i], depth + 1, maxDepth, childPath));
410
- }
411
- catch
412
- {
413
- // Skip inaccessible children
414
- }
415
- }
416
- if (childNodes.Count > 0)
417
- node["children"] = childNodes;
418
- }
419
- }
420
- catch { }
421
- }
422
-
423
- return node;
424
- }
425
-
426
- private AutomationElement NavigateToElement(AutomationElement root, int[] path)
427
- {
428
- var current = root;
429
- foreach (var index in path)
430
- {
431
- var children = current.FindAll(TreeScope.Children, Condition.TrueCondition);
432
- if (index >= children.Count)
433
- throw new BridgeException($"Element path index {index} out of range (count={children.Count})");
434
- current = children[index];
435
- }
436
- return current;
437
- }
438
-
439
- private int[] GetElementPath(AutomationElement root, AutomationElement target)
440
- {
441
- // BFS to find the path from root to target
442
- var queue = new Queue<(AutomationElement element, List<int> path)>();
443
- queue.Enqueue((root, new List<int>()));
444
-
445
- while (queue.Count > 0)
446
- {
447
- var (current, path) = queue.Dequeue();
448
-
449
- if (Automation.Compare(current, target))
450
- return path.ToArray();
451
-
452
- try
453
- {
454
- var children = current.FindAll(TreeScope.Children, Condition.TrueCondition);
455
- for (int i = 0; i < children.Count && i < 100; i++)
456
- {
457
- var childPath = new List<int>(path) { i };
458
- queue.Enqueue((children[i], childPath));
459
- }
460
- }
461
- catch { }
462
- }
463
-
464
- // Fallback: return empty path
465
- return Array.Empty<int>();
466
- }
467
-
468
- private AutomationElement? FindElementByPartialName(AutomationElement root, string partialName,
469
- string? role, int maxDepth)
470
- {
471
- if (maxDepth <= 0) return null;
472
-
473
- try
474
- {
475
- var name = root.Current.Name;
476
- if (!string.IsNullOrEmpty(name) &&
477
- name.Contains(partialName, StringComparison.OrdinalIgnoreCase))
478
- {
479
- if (role == null || MapControlTypeToRole(root.Current.ControlType)
480
- .Equals(role, StringComparison.OrdinalIgnoreCase))
481
- {
482
- return root;
483
- }
484
- }
485
- }
486
- catch { }
487
-
488
- try
489
- {
490
- var children = root.FindAll(TreeScope.Children, Condition.TrueCondition);
491
- foreach (AutomationElement child in children)
492
- {
493
- var found = FindElementByPartialName(child, partialName, role, maxDepth - 1);
494
- if (found != null) return found;
495
- }
496
- }
497
- catch { }
498
-
499
- return null;
500
- }
501
-
502
- // Map macOS AX roles to Windows UIA ControlTypes
503
- private static ControlType? MapRoleToControlType(string role)
504
- {
505
- return role.ToLowerInvariant() switch
506
- {
507
- "button" or "axbutton" => ControlType.Button,
508
- "checkbox" or "axcheckbox" => ControlType.CheckBox,
509
- "combobox" or "axcombobox" => ControlType.ComboBox,
510
- "textfield" or "axtextfield" or "textarea" or "axtextarea" => ControlType.Edit,
511
- "group" or "axgroup" => ControlType.Group,
512
- "image" or "aximage" => ControlType.Image,
513
- "link" or "axlink" => ControlType.Hyperlink,
514
- "list" or "axlist" => ControlType.List,
515
- "menu" or "axmenu" => ControlType.Menu,
516
- "menuitem" or "axmenuitem" => ControlType.MenuItem,
517
- "menubar" or "axmenubar" => ControlType.MenuBar,
518
- "radiobutton" or "axradiobutton" => ControlType.RadioButton,
519
- "scrollbar" or "axscrollbar" => ControlType.ScrollBar,
520
- "slider" or "axslider" => ControlType.Slider,
521
- "statictext" or "axstatictext" => ControlType.Text,
522
- "tab" or "axtab" or "tabgroup" or "axtabgroup" => ControlType.Tab,
523
- "table" or "axtable" => ControlType.Table,
524
- "toolbar" or "axtoolbar" => ControlType.ToolBar,
525
- "tree" or "axtree" or "outline" or "axoutline" => ControlType.Tree,
526
- "window" or "axwindow" => ControlType.Window,
527
- _ => null,
528
- };
529
- }
530
-
531
- // Map Windows UIA ControlTypes to macOS-style role strings
532
- private static string MapControlTypeToRole(ControlType ct)
533
- {
534
- if (ct == ControlType.Button) return "AXButton";
535
- if (ct == ControlType.CheckBox) return "AXCheckBox";
536
- if (ct == ControlType.ComboBox) return "AXComboBox";
537
- if (ct == ControlType.Edit) return "AXTextField";
538
- if (ct == ControlType.Group) return "AXGroup";
539
- if (ct == ControlType.Image) return "AXImage";
540
- if (ct == ControlType.Hyperlink) return "AXLink";
541
- if (ct == ControlType.List) return "AXList";
542
- if (ct == ControlType.ListItem) return "AXCell";
543
- if (ct == ControlType.Menu) return "AXMenu";
544
- if (ct == ControlType.MenuItem) return "AXMenuItem";
545
- if (ct == ControlType.MenuBar) return "AXMenuBar";
546
- if (ct == ControlType.Pane) return "AXGroup";
547
- if (ct == ControlType.RadioButton) return "AXRadioButton";
548
- if (ct == ControlType.ScrollBar) return "AXScrollBar";
549
- if (ct == ControlType.Slider) return "AXSlider";
550
- if (ct == ControlType.StatusBar) return "AXStaticText";
551
- if (ct == ControlType.Tab) return "AXTabGroup";
552
- if (ct == ControlType.TabItem) return "AXTab";
553
- if (ct == ControlType.Table) return "AXTable";
554
- if (ct == ControlType.Text) return "AXStaticText";
555
- if (ct == ControlType.ToolBar) return "AXToolbar";
556
- if (ct == ControlType.ToolTip) return "AXStaticText";
557
- if (ct == ControlType.Tree) return "AXOutline";
558
- if (ct == ControlType.TreeItem) return "AXRow";
559
- if (ct == ControlType.Window) return "AXWindow";
560
- if (ct == ControlType.Document) return "AXWebArea";
561
- if (ct == ControlType.Header) return "AXGroup";
562
- if (ct == ControlType.DataGrid) return "AXTable";
563
- if (ct == ControlType.DataItem) return "AXCell";
564
- if (ct == ControlType.SplitButton) return "AXButton";
565
- if (ct == ControlType.Spinner) return "AXIncrementor";
566
- if (ct == ControlType.Thumb) return "AXHandle";
567
- if (ct == ControlType.TitleBar) return "AXStaticText";
568
- if (ct == ControlType.Custom) return "AXGroup";
569
- return "AXGroup"; // Default fallback
570
- }
571
- }
@@ -1,17 +0,0 @@
1
- <Project Sdk="Microsoft.NET.Sdk">
2
-
3
- <PropertyGroup>
4
- <OutputType>Exe</OutputType>
5
- <TargetFramework>net8.0-windows</TargetFramework>
6
- <AssemblyName>windows-bridge</AssemblyName>
7
- <RootNamespace>WindowsBridge</RootNamespace>
8
- <Nullable>enable</Nullable>
9
- <ImplicitUsings>enable</ImplicitUsings>
10
- <UseWindowsForms>true</UseWindowsForms>
11
- </PropertyGroup>
12
-
13
- <ItemGroup>
14
- <PackageReference Include="System.Text.Json" Version="8.0.5" />
15
- </ItemGroup>
16
-
17
- </Project>