agent-device 0.10.0 → 0.10.2

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 (76) hide show
  1. package/README.md +4 -607
  2. package/dist/src/331.js +3 -3
  3. package/dist/src/425.js +1 -0
  4. package/dist/src/bin.js +28 -28
  5. package/dist/src/core/dispatch.d.ts +2 -0
  6. package/dist/src/core/session-surface.d.ts +3 -0
  7. package/dist/src/core/settings-contract.d.ts +2 -1
  8. package/dist/src/daemon/android-system-dialog.d.ts +11 -0
  9. package/dist/src/daemon/app-log-ios.d.ts +2 -1
  10. package/dist/src/daemon/app-log-process.d.ts +1 -1
  11. package/dist/src/daemon/app-log.d.ts +1 -1
  12. package/dist/src/daemon/context.d.ts +2 -0
  13. package/dist/src/daemon/handlers/interaction-common.d.ts +30 -1
  14. package/dist/src/daemon/handlers/interaction-read.d.ts +14 -0
  15. package/dist/src/daemon/handlers/interaction-touch.d.ts +45 -0
  16. package/dist/src/daemon/handlers/interaction.d.ts +2 -0
  17. package/dist/src/daemon/handlers/record-trace-android.d.ts +18 -0
  18. package/dist/src/daemon/handlers/record-trace-ios.d.ts +52 -0
  19. package/dist/src/daemon/handlers/record-trace-recording.d.ts +32 -0
  20. package/dist/src/daemon/handlers/record-trace.d.ts +2 -7
  21. package/dist/src/daemon/handlers/snapshot-capture.d.ts +11 -4
  22. package/dist/src/daemon/record-trace-errors.d.ts +6 -0
  23. package/dist/src/daemon/recording-gestures.d.ts +3 -0
  24. package/dist/src/daemon/recording-telemetry.d.ts +20 -0
  25. package/dist/src/daemon/recording-timing.d.ts +24 -0
  26. package/dist/src/daemon/request-router.d.ts +6 -0
  27. package/dist/src/daemon/script-utils.d.ts +1 -0
  28. package/dist/src/daemon/snapshot-processing.d.ts +1 -0
  29. package/dist/src/daemon/touch-reference-frame.d.ts +7 -0
  30. package/dist/src/daemon/types.d.ts +65 -11
  31. package/dist/src/daemon.js +62 -36
  32. package/dist/src/platforms/android/index.d.ts +1 -1
  33. package/dist/src/platforms/android/input-actions.d.ts +5 -0
  34. package/dist/src/platforms/android/settings.d.ts +1 -1
  35. package/dist/src/platforms/ios/apps.d.ts +1 -1
  36. package/dist/src/platforms/ios/macos-helper.d.ts +69 -0
  37. package/dist/src/platforms/ios/runner-client.d.ts +2 -2
  38. package/dist/src/platforms/ios/runner-session.d.ts +5 -0
  39. package/dist/src/platforms/ios/runner-xctestrun.d.ts +3 -1
  40. package/dist/src/recording/overlay.d.ts +10 -0
  41. package/dist/src/utils/command-schema.d.ts +2 -0
  42. package/dist/src/utils/interactors.d.ts +8 -8
  43. package/dist/src/utils/snapshot-lines.d.ts +5 -2
  44. package/dist/src/utils/snapshot.d.ts +8 -1
  45. package/dist/src/utils/text-surface.d.ts +19 -0
  46. package/dist/src/utils/video.d.ts +9 -0
  47. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +196 -51
  48. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +133 -0
  49. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +1 -1
  50. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +33 -1
  51. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +4 -6
  52. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +1 -0
  53. package/ios-runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +571 -0
  54. package/ios-runner/AgentDeviceRunner/RecordingScripts/recording-trim.swift +140 -0
  55. package/macos-helper/Package.swift +18 -0
  56. package/macos-helper/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift +543 -0
  57. package/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +545 -0
  58. package/package.json +4 -1
  59. package/skills/agent-device/SKILL.md +25 -334
  60. package/skills/agent-device/references/bootstrap-install.md +167 -0
  61. package/skills/agent-device/references/coordinate-system.md +24 -4
  62. package/skills/agent-device/references/debugging.md +115 -0
  63. package/skills/agent-device/references/exploration.md +193 -0
  64. package/skills/agent-device/references/macos-desktop.md +55 -57
  65. package/skills/agent-device/references/remote-tenancy.md +56 -47
  66. package/skills/agent-device/references/verification.md +103 -0
  67. package/dist/src/274.js +0 -1
  68. package/dist/src/daemon/handlers/interaction-fill.d.ts +0 -3
  69. package/dist/src/daemon/handlers/interaction-press.d.ts +0 -3
  70. package/skills/agent-device/references/batching.md +0 -79
  71. package/skills/agent-device/references/logs-and-debug.md +0 -113
  72. package/skills/agent-device/references/perf-metrics.md +0 -53
  73. package/skills/agent-device/references/permissions.md +0 -70
  74. package/skills/agent-device/references/session-management.md +0 -101
  75. package/skills/agent-device/references/snapshot-refs.md +0 -102
  76. package/skills/agent-device/references/video-recording.md +0 -41
@@ -0,0 +1,18 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "agent-device-macos-helper",
6
+ platforms: [.macOS(.v13)],
7
+ products: [
8
+ .executable(
9
+ name: "agent-device-macos-helper",
10
+ targets: ["AgentDeviceMacOSHelper"]
11
+ ),
12
+ ],
13
+ targets: [
14
+ .executableTarget(
15
+ name: "AgentDeviceMacOSHelper"
16
+ ),
17
+ ]
18
+ )
@@ -0,0 +1,543 @@
1
+ import AppKit
2
+ import ApplicationServices
3
+ import Foundation
4
+
5
+ private enum SnapshotTraversalLimits {
6
+ static let maxDesktopApps = 24
7
+ static let maxNodes = 1500
8
+ static let maxDepth = 12
9
+ }
10
+
11
+ struct RectResponse: Encodable {
12
+ let x: Double
13
+ let y: Double
14
+ let width: Double
15
+ let height: Double
16
+ }
17
+
18
+ struct SnapshotNodeResponse: Encodable {
19
+ let index: Int
20
+ let type: String?
21
+ let role: String?
22
+ let subrole: String?
23
+ let label: String?
24
+ let value: String?
25
+ let identifier: String?
26
+ let rect: RectResponse?
27
+ let enabled: Bool?
28
+ let selected: Bool?
29
+ let hittable: Bool?
30
+ let depth: Int
31
+ let parentIndex: Int?
32
+ let pid: Int32?
33
+ let bundleId: String?
34
+ let appName: String?
35
+ let windowTitle: String?
36
+ let surface: String?
37
+ }
38
+
39
+ struct SnapshotResponse: Encodable {
40
+ let surface: String
41
+ let nodes: [SnapshotNodeResponse]
42
+ let truncated: Bool
43
+ let backend = "macos-helper"
44
+ }
45
+
46
+ private struct SnapshotBuildResult {
47
+ let nodes: [SnapshotNodeResponse]
48
+ let truncated: Bool
49
+ }
50
+
51
+ private struct SnapshotContext {
52
+ let surface: String
53
+ let pid: Int32?
54
+ let bundleId: String?
55
+ let appName: String?
56
+ let windowTitle: String?
57
+ }
58
+
59
+ private struct SnapshotTraversalState {
60
+ var nodes: [SnapshotNodeResponse] = []
61
+ var visited: [AXUIElement] = []
62
+ var truncated = false
63
+ }
64
+
65
+ func captureSnapshotResponse(surface: String) throws -> SnapshotResponse {
66
+ let result: SnapshotBuildResult
67
+ switch surface {
68
+ case "frontmost-app":
69
+ result = try snapshotFrontmostApp()
70
+ case "desktop":
71
+ result = snapshotDesktop()
72
+ case "menubar":
73
+ result = snapshotMenuBar()
74
+ default:
75
+ throw HelperError.invalidArgs("snapshot requires --surface <frontmost-app|desktop|menubar>")
76
+ }
77
+
78
+ return SnapshotResponse(surface: surface, nodes: result.nodes, truncated: result.truncated)
79
+ }
80
+
81
+ private func snapshotFrontmostApp() throws -> SnapshotBuildResult {
82
+ let app = try resolveTargetApplication(bundleId: nil, surface: "frontmost-app")
83
+ var state = SnapshotTraversalState()
84
+ _ = appendApplicationSnapshot(
85
+ app,
86
+ depth: 0,
87
+ parentIndex: nil,
88
+ surface: "frontmost-app",
89
+ state: &state
90
+ )
91
+ return SnapshotBuildResult(nodes: state.nodes, truncated: state.truncated)
92
+ }
93
+
94
+ private func snapshotDesktop() -> SnapshotBuildResult {
95
+ var state = SnapshotTraversalState()
96
+ guard
97
+ let rootIndex = appendSyntheticSnapshotNode(
98
+ into: &state,
99
+ type: "DesktopSurface",
100
+ label: "Desktop",
101
+ depth: 0,
102
+ parentIndex: nil,
103
+ surface: "desktop"
104
+ )
105
+ else {
106
+ return SnapshotBuildResult(nodes: state.nodes, truncated: true)
107
+ }
108
+
109
+ var runningApps = NSWorkspace.shared.runningApplications.filter { app in
110
+ app.activationPolicy != .prohibited
111
+ && !app.isTerminated
112
+ && (app.bundleIdentifier?.isEmpty == false || app.localizedName?.isEmpty == false)
113
+ }
114
+ runningApps.sort { left, right in
115
+ if left.isActive != right.isActive {
116
+ return left.isActive && !right.isActive
117
+ }
118
+ return (left.localizedName ?? "") < (right.localizedName ?? "")
119
+ }
120
+
121
+ var includedApps = 0
122
+ for app in runningApps {
123
+ if includedApps >= SnapshotTraversalLimits.maxDesktopApps {
124
+ state.truncated = true
125
+ break
126
+ }
127
+ if state.truncated {
128
+ break
129
+ }
130
+
131
+ let included = appendApplicationSnapshot(
132
+ app,
133
+ depth: 1,
134
+ parentIndex: rootIndex,
135
+ surface: "desktop",
136
+ state: &state
137
+ )
138
+ if state.truncated {
139
+ break
140
+ }
141
+ if included {
142
+ includedApps += 1
143
+ }
144
+ }
145
+
146
+ return SnapshotBuildResult(nodes: state.nodes, truncated: state.truncated)
147
+ }
148
+
149
+ @discardableResult
150
+ private func appendApplicationSnapshot(
151
+ _ app: NSRunningApplication,
152
+ depth: Int,
153
+ parentIndex: Int?,
154
+ surface: String,
155
+ state: inout SnapshotTraversalState
156
+ ) -> Bool {
157
+ let appElement = AXUIElementCreateApplication(app.processIdentifier)
158
+ let visibleWindows = windows(of: appElement).filter(isVisibleSnapshotWindow)
159
+ if visibleWindows.isEmpty {
160
+ return false
161
+ }
162
+
163
+ guard
164
+ let appIndex = appendSyntheticSnapshotNode(
165
+ into: &state,
166
+ type: "Application",
167
+ label: app.localizedName ?? app.bundleIdentifier ?? "Application",
168
+ depth: depth,
169
+ parentIndex: parentIndex,
170
+ surface: surface,
171
+ identifier: app.bundleIdentifier,
172
+ pid: Int32(app.processIdentifier),
173
+ bundleId: app.bundleIdentifier,
174
+ appName: app.localizedName
175
+ )
176
+ else {
177
+ return false
178
+ }
179
+
180
+ for window in visibleWindows {
181
+ if state.truncated {
182
+ break
183
+ }
184
+ let windowTitle = stringAttribute(window, attribute: kAXTitleAttribute as String)
185
+ _ = appendElementSnapshot(
186
+ window,
187
+ depth: depth + 1,
188
+ parentIndex: appIndex,
189
+ context: SnapshotContext(
190
+ surface: surface,
191
+ pid: Int32(app.processIdentifier),
192
+ bundleId: app.bundleIdentifier,
193
+ appName: app.localizedName,
194
+ windowTitle: windowTitle
195
+ ),
196
+ state: &state
197
+ )
198
+ }
199
+
200
+ return true
201
+ }
202
+
203
+ private func snapshotMenuBar() -> SnapshotBuildResult {
204
+ var state = SnapshotTraversalState()
205
+ guard
206
+ let rootIndex = appendSyntheticSnapshotNode(
207
+ into: &state,
208
+ type: "MenuBarSurface",
209
+ label: "Menu Bar",
210
+ depth: 0,
211
+ parentIndex: nil,
212
+ surface: "menubar"
213
+ )
214
+ else {
215
+ return SnapshotBuildResult(nodes: state.nodes, truncated: true)
216
+ }
217
+
218
+ if let frontmost = NSWorkspace.shared.frontmostApplication {
219
+ let frontmostElement = AXUIElementCreateApplication(frontmost.processIdentifier)
220
+ if let menuBar = elementAttribute(frontmostElement, attribute: kAXMenuBarAttribute as String) {
221
+ _ = appendElementSnapshot(
222
+ menuBar,
223
+ depth: 1,
224
+ parentIndex: rootIndex,
225
+ context: SnapshotContext(
226
+ surface: "menubar",
227
+ pid: Int32(frontmost.processIdentifier),
228
+ bundleId: frontmost.bundleIdentifier,
229
+ appName: frontmost.localizedName,
230
+ windowTitle: frontmost.localizedName
231
+ ),
232
+ state: &state
233
+ )
234
+ }
235
+ }
236
+
237
+ if !state.truncated,
238
+ let systemUiServer = NSRunningApplication.runningApplications(
239
+ withBundleIdentifier: "com.apple.systemuiserver"
240
+ ).first
241
+ {
242
+ let systemUiElement = AXUIElementCreateApplication(systemUiServer.processIdentifier)
243
+ if let menuExtras = elementAttribute(systemUiElement, attribute: kAXMenuBarAttribute as String) {
244
+ _ = appendElementSnapshot(
245
+ menuExtras,
246
+ depth: 1,
247
+ parentIndex: rootIndex,
248
+ context: SnapshotContext(
249
+ surface: "menubar",
250
+ pid: Int32(systemUiServer.processIdentifier),
251
+ bundleId: systemUiServer.bundleIdentifier,
252
+ appName: systemUiServer.localizedName,
253
+ windowTitle: "System Menu Extras"
254
+ ),
255
+ state: &state
256
+ )
257
+ }
258
+ }
259
+
260
+ return SnapshotBuildResult(nodes: state.nodes, truncated: state.truncated)
261
+ }
262
+
263
+ @discardableResult
264
+ private func appendSyntheticSnapshotNode(
265
+ into state: inout SnapshotTraversalState,
266
+ type: String,
267
+ label: String,
268
+ depth: Int,
269
+ parentIndex: Int?,
270
+ surface: String,
271
+ identifier: String? = nil,
272
+ pid: Int32? = nil,
273
+ bundleId: String? = nil,
274
+ appName: String? = nil,
275
+ windowTitle: String? = nil
276
+ ) -> Int? {
277
+ guard reserveSnapshotNodeCapacity(&state) else {
278
+ return nil
279
+ }
280
+
281
+ let index = state.nodes.count
282
+ state.nodes.append(
283
+ SnapshotNodeResponse(
284
+ index: index,
285
+ type: type,
286
+ role: type,
287
+ subrole: nil,
288
+ label: label,
289
+ value: nil,
290
+ identifier: identifier ?? "surface:\(surface):\(type.lowercased())",
291
+ rect: nil,
292
+ enabled: true,
293
+ selected: nil,
294
+ hittable: false,
295
+ depth: depth,
296
+ parentIndex: parentIndex,
297
+ pid: pid,
298
+ bundleId: bundleId,
299
+ appName: appName,
300
+ windowTitle: windowTitle,
301
+ surface: surface
302
+ )
303
+ )
304
+ return index
305
+ }
306
+
307
+ @discardableResult
308
+ private func appendElementSnapshot(
309
+ _ element: AXUIElement,
310
+ depth: Int,
311
+ parentIndex: Int?,
312
+ context: SnapshotContext,
313
+ state: inout SnapshotTraversalState,
314
+ maxDepth: Int = SnapshotTraversalLimits.maxDepth
315
+ ) -> Int? {
316
+ if state.visited.contains(where: { CFEqual($0, element) }) {
317
+ return parentIndex
318
+ }
319
+ guard reserveSnapshotNodeCapacity(&state) else {
320
+ return parentIndex
321
+ }
322
+ state.visited.append(element)
323
+
324
+ let role = stringAttribute(element, attribute: kAXRoleAttribute as String)
325
+ let subrole = stringAttribute(element, attribute: kAXSubroleAttribute as String)
326
+ let title = stringAttribute(element, attribute: kAXTitleAttribute as String)
327
+ let description = stringAttribute(element, attribute: kAXDescriptionAttribute as String)
328
+ let value = stringAttribute(element, attribute: kAXValueAttribute as String)
329
+ let identifier = stringAttribute(element, attribute: "AXIdentifier")
330
+ let rect = rectAttribute(element)
331
+ let enabled = boolAttribute(element, attribute: kAXEnabledAttribute as String)
332
+ let selected = boolAttribute(element, attribute: kAXSelectedAttribute as String)
333
+ let type = normalizedSnapshotType(role: role, subrole: subrole)
334
+ let windowTitle = context.windowTitle ?? inferWindowTitle(for: element)
335
+
336
+ let index = state.nodes.count
337
+ state.nodes.append(
338
+ SnapshotNodeResponse(
339
+ index: index,
340
+ type: type,
341
+ role: role,
342
+ subrole: subrole,
343
+ label: title ?? description ?? value,
344
+ value: value,
345
+ identifier: identifier,
346
+ rect: rect,
347
+ enabled: enabled,
348
+ selected: selected,
349
+ hittable: (enabled ?? true) && rect != nil,
350
+ depth: depth,
351
+ parentIndex: parentIndex,
352
+ pid: context.pid,
353
+ bundleId: context.bundleId,
354
+ appName: context.appName,
355
+ windowTitle: windowTitle,
356
+ surface: context.surface
357
+ )
358
+ )
359
+
360
+ guard depth < maxDepth, !state.truncated else {
361
+ return index
362
+ }
363
+
364
+ for child in children(of: element) {
365
+ if state.truncated {
366
+ break
367
+ }
368
+ _ = appendElementSnapshot(
369
+ child,
370
+ depth: depth + 1,
371
+ parentIndex: index,
372
+ context: SnapshotContext(
373
+ surface: context.surface,
374
+ pid: context.pid,
375
+ bundleId: context.bundleId,
376
+ appName: context.appName,
377
+ windowTitle: windowTitle
378
+ ),
379
+ state: &state,
380
+ maxDepth: maxDepth
381
+ )
382
+ }
383
+
384
+ return index
385
+ }
386
+
387
+ private func reserveSnapshotNodeCapacity(_ state: inout SnapshotTraversalState) -> Bool {
388
+ if state.nodes.count >= SnapshotTraversalLimits.maxNodes {
389
+ state.truncated = true
390
+ return false
391
+ }
392
+ return true
393
+ }
394
+
395
+ private func normalizedSnapshotType(role: String?, subrole: String?) -> String? {
396
+ switch role {
397
+ case "AXApplication":
398
+ return "Application"
399
+ case "AXWindow":
400
+ return subrole == "AXStandardWindow" ? "Window" : (subrole ?? "Window")
401
+ case "AXSheet":
402
+ return "Sheet"
403
+ case "AXDialog":
404
+ return "Dialog"
405
+ case "AXButton":
406
+ return "Button"
407
+ case "AXStaticText":
408
+ return "StaticText"
409
+ case "AXTextField":
410
+ return "TextField"
411
+ case "AXTextArea":
412
+ return "TextArea"
413
+ case "AXScrollArea":
414
+ return "ScrollArea"
415
+ case "AXGroup":
416
+ return "Group"
417
+ case "AXMenuBar":
418
+ return "MenuBar"
419
+ case "AXMenuBarItem":
420
+ return "MenuBarItem"
421
+ case "AXMenu":
422
+ return "Menu"
423
+ case "AXMenuItem":
424
+ return "MenuItem"
425
+ default:
426
+ if let subrole, !subrole.isEmpty {
427
+ return subrole
428
+ }
429
+ return role
430
+ }
431
+ }
432
+
433
+ private func isVisibleSnapshotWindow(_ window: AXUIElement) -> Bool {
434
+ guard let rect = rectAttribute(window) else {
435
+ return false
436
+ }
437
+ if rect.width <= 0 || rect.height <= 0 {
438
+ return false
439
+ }
440
+ if boolAttribute(window, attribute: kAXMinimizedAttribute as String) == true {
441
+ return false
442
+ }
443
+ return true
444
+ }
445
+
446
+ private func inferWindowTitle(for element: AXUIElement) -> String? {
447
+ if let title = stringAttribute(element, attribute: kAXTitleAttribute as String) {
448
+ return title
449
+ }
450
+ if let window = elementAttribute(element, attribute: kAXWindowAttribute as String) {
451
+ return stringAttribute(window, attribute: kAXTitleAttribute as String)
452
+ }
453
+ return nil
454
+ }
455
+
456
+ func stringAttribute(_ element: AXUIElement, attribute: String) -> String? {
457
+ var value: CFTypeRef?
458
+ guard AXUIElementCopyAttributeValue(element, attribute as CFString, &value) == .success else {
459
+ return nil
460
+ }
461
+ if let text = value as? String {
462
+ let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
463
+ return trimmed.isEmpty ? nil : trimmed
464
+ }
465
+ return nil
466
+ }
467
+
468
+ func boolAttribute(_ element: AXUIElement, attribute: String) -> Bool? {
469
+ var value: CFTypeRef?
470
+ guard AXUIElementCopyAttributeValue(element, attribute as CFString, &value) == .success,
471
+ let number = value as? NSNumber
472
+ else {
473
+ return nil
474
+ }
475
+ return number.boolValue
476
+ }
477
+
478
+ func elementAttribute(_ element: AXUIElement, attribute: String) -> AXUIElement? {
479
+ var value: CFTypeRef?
480
+ guard AXUIElementCopyAttributeValue(element, attribute as CFString, &value) == .success,
481
+ let value,
482
+ CFGetTypeID(value) == AXUIElementGetTypeID()
483
+ else {
484
+ return nil
485
+ }
486
+ return (value as! AXUIElement)
487
+ }
488
+
489
+ func rectAttribute(_ element: AXUIElement) -> RectResponse? {
490
+ var positionValue: CFTypeRef?
491
+ var sizeValue: CFTypeRef?
492
+ guard AXUIElementCopyAttributeValue(element, kAXPositionAttribute as CFString, &positionValue) == .success,
493
+ AXUIElementCopyAttributeValue(element, kAXSizeAttribute as CFString, &sizeValue) == .success,
494
+ let positionAxValue = accessibilityAxValue(positionValue),
495
+ let sizeAxValue = accessibilityAxValue(sizeValue)
496
+ else {
497
+ return nil
498
+ }
499
+
500
+ var position = CGPoint.zero
501
+ var size = CGSize.zero
502
+ guard AXValueGetType(positionAxValue) == .cgPoint,
503
+ AXValueGetValue(positionAxValue, .cgPoint, &position),
504
+ AXValueGetType(sizeAxValue) == .cgSize,
505
+ AXValueGetValue(sizeAxValue, .cgSize, &size)
506
+ else {
507
+ return nil
508
+ }
509
+
510
+ return RectResponse(
511
+ x: Double(position.x),
512
+ y: Double(position.y),
513
+ width: Double(size.width),
514
+ height: Double(size.height)
515
+ )
516
+ }
517
+
518
+ private func accessibilityAxValue(_ value: CFTypeRef?) -> AXValue? {
519
+ guard let value, CFGetTypeID(value) == AXValueGetTypeID() else {
520
+ return nil
521
+ }
522
+ return (value as! AXValue)
523
+ }
524
+
525
+ func children(of element: AXUIElement) -> [AXUIElement] {
526
+ var value: CFTypeRef?
527
+ guard AXUIElementCopyAttributeValue(element, kAXChildrenAttribute as CFString, &value) == .success,
528
+ let children = value as? [AXUIElement]
529
+ else {
530
+ return []
531
+ }
532
+ return children
533
+ }
534
+
535
+ func windows(of appElement: AXUIElement) -> [AXUIElement] {
536
+ var value: CFTypeRef?
537
+ guard AXUIElementCopyAttributeValue(appElement, "AXWindows" as CFString, &value) == .success,
538
+ let windows = value as? [AXUIElement]
539
+ else {
540
+ return []
541
+ }
542
+ return windows
543
+ }