agent-device 0.10.1 → 0.10.3

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 (210) hide show
  1. package/README.md +4 -1
  2. package/dist/src/376.js +3 -0
  3. package/dist/src/bin.js +74 -73
  4. package/dist/src/daemon.js +39 -39
  5. package/dist/src/index.d.ts +559 -5
  6. package/dist/src/index.js +3 -1
  7. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +8 -0
  8. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +60 -0
  9. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +1 -1
  10. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +4 -0
  11. package/macos-helper/Package.swift +18 -0
  12. package/macos-helper/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift +543 -0
  13. package/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +545 -0
  14. package/package.json +5 -1
  15. package/skills/agent-device/SKILL.md +57 -343
  16. package/skills/agent-device/references/bootstrap-install.md +207 -0
  17. package/skills/agent-device/references/coordinate-system.md +24 -4
  18. package/skills/agent-device/references/debugging.md +115 -0
  19. package/skills/agent-device/references/exploration.md +235 -0
  20. package/skills/agent-device/references/macos-desktop.md +55 -58
  21. package/skills/agent-device/references/remote-tenancy.md +69 -47
  22. package/skills/agent-device/references/verification.md +102 -0
  23. package/dist/src/224.js +0 -2
  24. package/dist/src/274.js +0 -1
  25. package/dist/src/331.js +0 -3
  26. package/dist/src/bin.d.ts +0 -1
  27. package/dist/src/cli-client-commands.d.ts +0 -8
  28. package/dist/src/cli.d.ts +0 -6
  29. package/dist/src/client-metro.d.ts +0 -64
  30. package/dist/src/client-normalizers.d.ts +0 -20
  31. package/dist/src/client-shared.d.ts +0 -20
  32. package/dist/src/client-types.d.ts +0 -269
  33. package/dist/src/client.d.ts +0 -5
  34. package/dist/src/core/app-events.d.ts +0 -8
  35. package/dist/src/core/batch.d.ts +0 -17
  36. package/dist/src/core/capabilities.d.ts +0 -3
  37. package/dist/src/core/click-button.d.ts +0 -20
  38. package/dist/src/core/dispatch-payload.d.ts +0 -1
  39. package/dist/src/core/dispatch-resolve.d.ts +0 -29
  40. package/dist/src/core/dispatch-series.d.ts +0 -7
  41. package/dist/src/core/dispatch.d.ts +0 -35
  42. package/dist/src/core/open-target.d.ts +0 -4
  43. package/dist/src/core/settings-contract.d.ts +0 -8
  44. package/dist/src/daemon/action-utils.d.ts +0 -3
  45. package/dist/src/daemon/android-system-dialog.d.ts +0 -11
  46. package/dist/src/daemon/app-log-android.d.ts +0 -4
  47. package/dist/src/daemon/app-log-ios.d.ts +0 -6
  48. package/dist/src/daemon/app-log-process.d.ts +0 -15
  49. package/dist/src/daemon/app-log-stream.d.ts +0 -19
  50. package/dist/src/daemon/app-log.d.ts +0 -28
  51. package/dist/src/daemon/artifact-archive.d.ts +0 -12
  52. package/dist/src/daemon/artifact-download.d.ts +0 -12
  53. package/dist/src/daemon/artifact-materialization.d.ts +0 -17
  54. package/dist/src/daemon/artifact-registry.d.ts +0 -12
  55. package/dist/src/daemon/config.d.ts +0 -16
  56. package/dist/src/daemon/context.d.ts +0 -23
  57. package/dist/src/daemon/device-ready.d.ts +0 -6
  58. package/dist/src/daemon/handlers/find.d.ts +0 -40
  59. package/dist/src/daemon/handlers/install-source.d.ts +0 -44
  60. package/dist/src/daemon/handlers/interaction-common.d.ts +0 -12
  61. package/dist/src/daemon/handlers/interaction-fill.d.ts +0 -3
  62. package/dist/src/daemon/handlers/interaction-flags.d.ts +0 -4
  63. package/dist/src/daemon/handlers/interaction-get.d.ts +0 -3
  64. package/dist/src/daemon/handlers/interaction-is.d.ts +0 -3
  65. package/dist/src/daemon/handlers/interaction-press.d.ts +0 -3
  66. package/dist/src/daemon/handlers/interaction-scroll.d.ts +0 -3
  67. package/dist/src/daemon/handlers/interaction-selector.d.ts +0 -27
  68. package/dist/src/daemon/handlers/interaction-snapshot.d.ts +0 -8
  69. package/dist/src/daemon/handlers/interaction-targeting.d.ts +0 -28
  70. package/dist/src/daemon/handlers/interaction-touch.d.ts +0 -46
  71. package/dist/src/daemon/handlers/interaction.d.ts +0 -16
  72. package/dist/src/daemon/handlers/lease.d.ts +0 -8
  73. package/dist/src/daemon/handlers/parse-utils.d.ts +0 -3
  74. package/dist/src/daemon/handlers/record-trace-android.d.ts +0 -18
  75. package/dist/src/daemon/handlers/record-trace-ios.d.ts +0 -52
  76. package/dist/src/daemon/handlers/record-trace-recording.d.ts +0 -32
  77. package/dist/src/daemon/handlers/record-trace.d.ts +0 -10
  78. package/dist/src/daemon/handlers/session-batch.d.ts +0 -2
  79. package/dist/src/daemon/handlers/session-close.d.ts +0 -31
  80. package/dist/src/daemon/handlers/session-deploy.d.ts +0 -37
  81. package/dist/src/daemon/handlers/session-device-utils.d.ts +0 -26
  82. package/dist/src/daemon/handlers/session-open-target.d.ts +0 -3
  83. package/dist/src/daemon/handlers/session-open.d.ts +0 -22
  84. package/dist/src/daemon/handlers/session-perf.d.ts +0 -2
  85. package/dist/src/daemon/handlers/session-replay-heal.d.ts +0 -8
  86. package/dist/src/daemon/handlers/session-replay-script.d.ts +0 -3
  87. package/dist/src/daemon/handlers/session-runtime-command.d.ts +0 -9
  88. package/dist/src/daemon/handlers/session-runtime.d.ts +0 -36
  89. package/dist/src/daemon/handlers/session-startup-metrics.d.ts +0 -11
  90. package/dist/src/daemon/handlers/session.d.ts +0 -50
  91. package/dist/src/daemon/handlers/snapshot-alert.d.ts +0 -13
  92. package/dist/src/daemon/handlers/snapshot-capture.d.ts +0 -27
  93. package/dist/src/daemon/handlers/snapshot-session.d.ts +0 -15
  94. package/dist/src/daemon/handlers/snapshot-settings.d.ts +0 -24
  95. package/dist/src/daemon/handlers/snapshot-wait.d.ts +0 -37
  96. package/dist/src/daemon/handlers/snapshot.d.ts +0 -16
  97. package/dist/src/daemon/http-server.d.ts +0 -26
  98. package/dist/src/daemon/install-source-resolution.d.ts +0 -5
  99. package/dist/src/daemon/is-predicates.d.ts +0 -15
  100. package/dist/src/daemon/lease-context.d.ts +0 -9
  101. package/dist/src/daemon/lease-registry.d.ts +0 -63
  102. package/dist/src/daemon/materialized-path-registry.d.ts +0 -15
  103. package/dist/src/daemon/network-log.d.ts +0 -32
  104. package/dist/src/daemon/record-trace-errors.d.ts +0 -6
  105. package/dist/src/daemon/recording-gestures.d.ts +0 -3
  106. package/dist/src/daemon/recording-telemetry.d.ts +0 -20
  107. package/dist/src/daemon/recording-timing.d.ts +0 -24
  108. package/dist/src/daemon/request-cancel.d.ts +0 -9
  109. package/dist/src/daemon/request-lock-policy.d.ts +0 -2
  110. package/dist/src/daemon/request-router.d.ts +0 -23
  111. package/dist/src/daemon/runtime-hints.d.ts +0 -19
  112. package/dist/src/daemon/script-utils.d.ts +0 -28
  113. package/dist/src/daemon/scroll-planner.d.ts +0 -12
  114. package/dist/src/daemon/selectors-build.d.ts +0 -5
  115. package/dist/src/daemon/selectors-match.d.ts +0 -6
  116. package/dist/src/daemon/selectors-parse.d.ts +0 -29
  117. package/dist/src/daemon/selectors-resolve.d.ts +0 -33
  118. package/dist/src/daemon/selectors.d.ts +0 -5
  119. package/dist/src/daemon/server-lifecycle.d.ts +0 -23
  120. package/dist/src/daemon/session-open-script.d.ts +0 -7
  121. package/dist/src/daemon/session-routing.d.ts +0 -3
  122. package/dist/src/daemon/session-selector.d.ts +0 -10
  123. package/dist/src/daemon/session-store.d.ts +0 -33
  124. package/dist/src/daemon/snapshot-diff.d.ts +0 -20
  125. package/dist/src/daemon/snapshot-processing.d.ts +0 -9
  126. package/dist/src/daemon/touch-reference-frame.d.ts +0 -7
  127. package/dist/src/daemon/transport.d.ts +0 -6
  128. package/dist/src/daemon/types.d.ts +0 -171
  129. package/dist/src/daemon/upload-registry.d.ts +0 -7
  130. package/dist/src/daemon/upload.d.ts +0 -5
  131. package/dist/src/daemon-client.d.ts +0 -40
  132. package/dist/src/daemon.d.ts +0 -1
  133. package/dist/src/platforms/android/adb.d.ts +0 -5
  134. package/dist/src/platforms/android/app-lifecycle.d.ts +0 -31
  135. package/dist/src/platforms/android/device-input-state.d.ts +0 -19
  136. package/dist/src/platforms/android/devices.d.ts +0 -26
  137. package/dist/src/platforms/android/index.d.ts +0 -8
  138. package/dist/src/platforms/android/input-actions.d.ts +0 -16
  139. package/dist/src/platforms/android/install-artifact.d.ts +0 -11
  140. package/dist/src/platforms/android/manifest.d.ts +0 -1
  141. package/dist/src/platforms/android/notifications.d.ts +0 -11
  142. package/dist/src/platforms/android/open-target.d.ts +0 -4
  143. package/dist/src/platforms/android/screenshot.d.ts +0 -16
  144. package/dist/src/platforms/android/sdk.d.ts +0 -2
  145. package/dist/src/platforms/android/settings.d.ts +0 -3
  146. package/dist/src/platforms/android/snapshot.d.ts +0 -7
  147. package/dist/src/platforms/android/ui-hierarchy.d.ts +0 -21
  148. package/dist/src/platforms/appearance.d.ts +0 -2
  149. package/dist/src/platforms/boot-diagnostics.d.ts +0 -14
  150. package/dist/src/platforms/install-source.d.ts +0 -29
  151. package/dist/src/platforms/ios/app-filter.d.ts +0 -2
  152. package/dist/src/platforms/ios/apps.d.ts +0 -34
  153. package/dist/src/platforms/ios/config.d.ts +0 -10
  154. package/dist/src/platforms/ios/devicectl.d.ts +0 -13
  155. package/dist/src/platforms/ios/devices.d.ts +0 -40
  156. package/dist/src/platforms/ios/ensure-simulator.d.ts +0 -18
  157. package/dist/src/platforms/ios/index.d.ts +0 -3
  158. package/dist/src/platforms/ios/install-artifact.d.ts +0 -18
  159. package/dist/src/platforms/ios/launch-diagnostics.d.ts +0 -11
  160. package/dist/src/platforms/ios/macos-apps.d.ts +0 -12
  161. package/dist/src/platforms/ios/plist.d.ts +0 -1
  162. package/dist/src/platforms/ios/runner-client.d.ts +0 -38
  163. package/dist/src/platforms/ios/runner-errors.d.ts +0 -20
  164. package/dist/src/platforms/ios/runner-macos-products.d.ts +0 -3
  165. package/dist/src/platforms/ios/runner-session.d.ts +0 -30
  166. package/dist/src/platforms/ios/runner-transport.d.ts +0 -10
  167. package/dist/src/platforms/ios/runner-xctestrun-products.d.ts +0 -2
  168. package/dist/src/platforms/ios/runner-xctestrun.d.ts +0 -38
  169. package/dist/src/platforms/ios/screenshot-status-bar.d.ts +0 -2
  170. package/dist/src/platforms/ios/screenshot.d.ts +0 -14
  171. package/dist/src/platforms/ios/simctl.d.ts +0 -7
  172. package/dist/src/platforms/ios/simulator.d.ts +0 -11
  173. package/dist/src/platforms/permission-utils.d.ts +0 -9
  174. package/dist/src/recording/overlay.d.ts +0 -10
  175. package/dist/src/upload-client.d.ts +0 -7
  176. package/dist/src/utils/args.d.ts +0 -27
  177. package/dist/src/utils/cli-config.d.ts +0 -10
  178. package/dist/src/utils/cli-option-schema.d.ts +0 -19
  179. package/dist/src/utils/cli-options.d.ts +0 -13
  180. package/dist/src/utils/command-schema.d.ts +0 -122
  181. package/dist/src/utils/device-isolation.d.ts +0 -3
  182. package/dist/src/utils/device.d.ts +0 -35
  183. package/dist/src/utils/diagnostics.d.ts +0 -30
  184. package/dist/src/utils/errors.d.ts +0 -26
  185. package/dist/src/utils/exec.d.ts +0 -32
  186. package/dist/src/utils/finders.d.ts +0 -12
  187. package/dist/src/utils/interactors.d.ts +0 -38
  188. package/dist/src/utils/json-input.d.ts +0 -1
  189. package/dist/src/utils/keyed-lock.d.ts +0 -1
  190. package/dist/src/utils/output.d.ts +0 -27
  191. package/dist/src/utils/path-resolution.d.ts +0 -8
  192. package/dist/src/utils/payload-input.d.ts +0 -12
  193. package/dist/src/utils/process-identity.d.ts +0 -11
  194. package/dist/src/utils/remote-config.d.ts +0 -15
  195. package/dist/src/utils/remote-open.d.ts +0 -9
  196. package/dist/src/utils/retry.d.ts +0 -54
  197. package/dist/src/utils/screenshot-diff.d.ts +0 -23
  198. package/dist/src/utils/session-binding.d.ts +0 -18
  199. package/dist/src/utils/snapshot-lines.d.ts +0 -12
  200. package/dist/src/utils/snapshot.d.ts +0 -42
  201. package/dist/src/utils/timeouts.d.ts +0 -3
  202. package/dist/src/utils/version.d.ts +0 -2
  203. package/dist/src/utils/video.d.ts +0 -9
  204. package/skills/agent-device/references/batching.md +0 -79
  205. package/skills/agent-device/references/logs-and-debug.md +0 -113
  206. package/skills/agent-device/references/perf-metrics.md +0 -53
  207. package/skills/agent-device/references/permissions.md +0 -70
  208. package/skills/agent-device/references/session-management.md +0 -101
  209. package/skills/agent-device/references/snapshot-refs.md +0 -102
  210. package/skills/agent-device/references/video-recording.md +0 -49
@@ -0,0 +1,545 @@
1
+ import AppKit
2
+ import ApplicationServices
3
+ import CoreGraphics
4
+ import Foundation
5
+
6
+ enum HelperError: Error {
7
+ case invalidArgs(String)
8
+ case commandFailed(String, details: [String: String] = [:])
9
+ }
10
+
11
+ struct ErrorPayload: Encodable {
12
+ let message: String
13
+ let details: [String: String]?
14
+ }
15
+
16
+ struct SuccessEnvelope<T: Encodable>: Encodable {
17
+ let ok = true
18
+ let data: T
19
+ }
20
+
21
+ struct FailureEnvelope: Encodable {
22
+ let ok = false
23
+ let error: ErrorPayload
24
+ }
25
+
26
+ struct FrontmostAppResponse: Encodable {
27
+ let bundleId: String?
28
+ let appName: String?
29
+ let pid: Int32?
30
+ }
31
+
32
+ struct QuitAppResponse: Encodable {
33
+ let bundleId: String
34
+ let running: Bool
35
+ let terminated: Bool
36
+ let forceTerminated: Bool
37
+ }
38
+
39
+ struct PermissionResponse: Encodable {
40
+ let target: String
41
+ let action: String
42
+ let granted: Bool
43
+ let requested: Bool
44
+ let openedSettings: Bool
45
+ let message: String?
46
+ }
47
+
48
+ struct AlertResponse: Encodable {
49
+ let title: String?
50
+ let role: String?
51
+ let buttons: [String]
52
+ let action: String?
53
+ let bundleId: String?
54
+ }
55
+
56
+ struct ReadResponse: Encodable {
57
+ let text: String
58
+ }
59
+
60
+ struct AgentDeviceMacOSHelper {
61
+ static func main() {
62
+ do {
63
+ let output = try run(arguments: Array(CommandLine.arguments.dropFirst()))
64
+ try writeJSON(output)
65
+ Foundation.exit(0)
66
+ } catch let error as HelperError {
67
+ let payload: FailureEnvelope
68
+ switch error {
69
+ case .invalidArgs(let message):
70
+ payload = FailureEnvelope(error: ErrorPayload(message: message, details: nil))
71
+ case .commandFailed(let message, let details):
72
+ payload = FailureEnvelope(
73
+ error: ErrorPayload(message: message, details: details.isEmpty ? nil : details)
74
+ )
75
+ }
76
+ try? writeJSON(payload)
77
+ Foundation.exit(1)
78
+ } catch {
79
+ let payload = FailureEnvelope(
80
+ error: ErrorPayload(message: String(describing: error), details: nil)
81
+ )
82
+ try? writeJSON(payload)
83
+ Foundation.exit(1)
84
+ }
85
+ }
86
+
87
+ static func run(arguments: [String]) throws -> any Encodable {
88
+ guard let command = arguments.first else {
89
+ throw HelperError.invalidArgs("missing command")
90
+ }
91
+
92
+ switch command {
93
+ case "app":
94
+ return try handleApp(arguments: Array(arguments.dropFirst()))
95
+ case "permission":
96
+ return try handlePermission(arguments: Array(arguments.dropFirst()))
97
+ case "alert":
98
+ return try handleAlert(arguments: Array(arguments.dropFirst()))
99
+ case "snapshot":
100
+ return try handleSnapshot(arguments: Array(arguments.dropFirst()))
101
+ case "read":
102
+ return try handleRead(arguments: Array(arguments.dropFirst()))
103
+ default:
104
+ throw HelperError.invalidArgs("unknown command: \(command)")
105
+ }
106
+ }
107
+
108
+ static func handleApp(arguments: [String]) throws -> any Encodable {
109
+ guard let action = arguments.first else {
110
+ throw HelperError.invalidArgs("app requires frontmost|quit")
111
+ }
112
+ switch action {
113
+ case "frontmost":
114
+ let app = NSWorkspace.shared.frontmostApplication
115
+ return SuccessEnvelope(
116
+ data: FrontmostAppResponse(
117
+ bundleId: app?.bundleIdentifier,
118
+ appName: app?.localizedName,
119
+ pid: app.map { Int32($0.processIdentifier) }
120
+ )
121
+ )
122
+ case "quit":
123
+ guard let rawBundleId = optionValue(arguments: Array(arguments.dropFirst()), name: "--bundle-id"),
124
+ !rawBundleId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
125
+ else {
126
+ throw HelperError.invalidArgs("app quit requires --bundle-id <id>")
127
+ }
128
+ let bundleId = try validatedBundleId(rawBundleId)
129
+ let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleId)
130
+ guard let app = apps.first else {
131
+ return SuccessEnvelope(
132
+ data: QuitAppResponse(
133
+ bundleId: bundleId,
134
+ running: false,
135
+ terminated: false,
136
+ forceTerminated: false
137
+ )
138
+ )
139
+ }
140
+ let terminated = app.terminate()
141
+ if terminated {
142
+ return SuccessEnvelope(
143
+ data: QuitAppResponse(
144
+ bundleId: bundleId,
145
+ running: true,
146
+ terminated: true,
147
+ forceTerminated: false
148
+ )
149
+ )
150
+ }
151
+ let forceTerminated = app.forceTerminate()
152
+ return SuccessEnvelope(
153
+ data: QuitAppResponse(
154
+ bundleId: bundleId,
155
+ running: true,
156
+ terminated: false,
157
+ forceTerminated: forceTerminated
158
+ )
159
+ )
160
+ default:
161
+ throw HelperError.invalidArgs("app requires frontmost|quit")
162
+ }
163
+ }
164
+
165
+ static func handlePermission(arguments: [String]) throws -> any Encodable {
166
+ guard arguments.count >= 2 else {
167
+ throw HelperError.invalidArgs(
168
+ "permission requires <grant|reset> <accessibility|screen-recording|input-monitoring>"
169
+ )
170
+ }
171
+ let action = arguments[0]
172
+ let target = arguments[1]
173
+ switch (action, target) {
174
+ case ("grant", "accessibility"):
175
+ let before = AXIsProcessTrusted()
176
+ let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true]
177
+ let after = AXIsProcessTrustedWithOptions(options as CFDictionary)
178
+ return SuccessEnvelope(
179
+ data: PermissionResponse(
180
+ target: target,
181
+ action: action,
182
+ granted: before || after,
183
+ requested: !before,
184
+ openedSettings: false,
185
+ message: before ? "Accessibility access already granted." : "Requested Accessibility access."
186
+ )
187
+ )
188
+ case ("reset", "accessibility"):
189
+ let opened = openPrivacyPane(anchor: "Privacy_Accessibility")
190
+ return SuccessEnvelope(
191
+ data: PermissionResponse(
192
+ target: target,
193
+ action: action,
194
+ granted: AXIsProcessTrusted(),
195
+ requested: false,
196
+ openedSettings: opened,
197
+ message: "macOS requires Accessibility access to be changed manually in System Settings."
198
+ )
199
+ )
200
+ case ("grant", "screen-recording"):
201
+ let before = CGPreflightScreenCaptureAccess()
202
+ let requested = !before
203
+ let after = before || CGRequestScreenCaptureAccess()
204
+ return SuccessEnvelope(
205
+ data: PermissionResponse(
206
+ target: target,
207
+ action: action,
208
+ granted: after,
209
+ requested: requested,
210
+ openedSettings: requested && !after ? openPrivacyPane(anchor: "Privacy_ScreenCapture") : false,
211
+ message: after ? "Screen Recording access is available." : "Grant Screen Recording access in System Settings."
212
+ )
213
+ )
214
+ case ("reset", "screen-recording"):
215
+ let opened = openPrivacyPane(anchor: "Privacy_ScreenCapture")
216
+ return SuccessEnvelope(
217
+ data: PermissionResponse(
218
+ target: target,
219
+ action: action,
220
+ granted: CGPreflightScreenCaptureAccess(),
221
+ requested: false,
222
+ openedSettings: opened,
223
+ message: "macOS requires Screen Recording access to be changed manually in System Settings."
224
+ )
225
+ )
226
+ case ("grant", "input-monitoring"):
227
+ let before = CGPreflightPostEventAccess()
228
+ let requested = !before
229
+ let after = before || CGRequestPostEventAccess()
230
+ return SuccessEnvelope(
231
+ data: PermissionResponse(
232
+ target: target,
233
+ action: action,
234
+ granted: after,
235
+ requested: requested,
236
+ openedSettings: requested && !after ? openPrivacyPane(anchor: "Privacy_ListenEvent") : false,
237
+ message: after ? "Input Monitoring access is available." : "Grant Input Monitoring access in System Settings."
238
+ )
239
+ )
240
+ case ("reset", "input-monitoring"):
241
+ let opened = openPrivacyPane(anchor: "Privacy_ListenEvent")
242
+ return SuccessEnvelope(
243
+ data: PermissionResponse(
244
+ target: target,
245
+ action: action,
246
+ granted: CGPreflightPostEventAccess(),
247
+ requested: false,
248
+ openedSettings: opened,
249
+ message: "macOS requires Input Monitoring access to be changed manually in System Settings."
250
+ )
251
+ )
252
+ default:
253
+ throw HelperError.invalidArgs(
254
+ "permission requires <grant|reset> <accessibility|screen-recording|input-monitoring>"
255
+ )
256
+ }
257
+ }
258
+
259
+ static func handleAlert(arguments: [String]) throws -> any Encodable {
260
+ let action = arguments.first ?? "get"
261
+ guard action == "get" || action == "accept" || action == "dismiss" else {
262
+ throw HelperError.invalidArgs("alert requires get|accept|dismiss")
263
+ }
264
+ let bundleId = optionValue(arguments: Array(arguments.dropFirst()), name: "--bundle-id")
265
+ let surface = optionValue(arguments: Array(arguments.dropFirst()), name: "--surface")
266
+ let app = try resolveTargetApplication(bundleId: bundleId, surface: surface)
267
+ guard let alertElement = findAlertElement(appElement: AXUIElementCreateApplication(app.processIdentifier)) else {
268
+ throw HelperError.commandFailed(
269
+ "alert not found",
270
+ details: ["bundleId": app.bundleIdentifier ?? "", "appName": app.localizedName ?? ""]
271
+ )
272
+ }
273
+ let buttons = collectButtons(root: alertElement)
274
+ let labels = buttons.map(resolveElementLabel)
275
+ let role = stringAttribute(alertElement, attribute: kAXRoleAttribute as String)
276
+ let title =
277
+ stringAttribute(alertElement, attribute: kAXTitleAttribute as String)
278
+ ?? stringAttribute(alertElement, attribute: kAXDescriptionAttribute as String)
279
+
280
+ if action == "accept" || action == "dismiss" {
281
+ guard let button = resolveAlertActionButton(
282
+ root: alertElement,
283
+ buttons: buttons,
284
+ action: action
285
+ )
286
+ else {
287
+ throw HelperError.commandFailed("alert action failed", details: ["reason": "missing_button"])
288
+ }
289
+ let status = AXUIElementPerformAction(button, kAXPressAction as CFString)
290
+ guard status == .success else {
291
+ throw HelperError.commandFailed(
292
+ "alert action failed",
293
+ details: ["status": "\(status.rawValue)"]
294
+ )
295
+ }
296
+ }
297
+
298
+ return SuccessEnvelope(
299
+ data: AlertResponse(
300
+ title: title,
301
+ role: role,
302
+ buttons: labels,
303
+ action: action == "get" ? nil : action,
304
+ bundleId: app.bundleIdentifier
305
+ )
306
+ )
307
+ }
308
+
309
+ static func handleSnapshot(arguments: [String]) throws -> any Encodable {
310
+ guard let surface = optionValue(arguments: arguments, name: "--surface")?
311
+ .trimmingCharacters(in: .whitespacesAndNewlines)
312
+ .lowercased(),
313
+ !surface.isEmpty
314
+ else {
315
+ throw HelperError.invalidArgs("snapshot requires --surface <frontmost-app|desktop|menubar>")
316
+ }
317
+
318
+ switch surface {
319
+ case "frontmost-app":
320
+ return SuccessEnvelope(data: try captureSnapshotResponse(surface: surface))
321
+ case "desktop", "menubar":
322
+ return SuccessEnvelope(data: try captureSnapshotResponse(surface: surface))
323
+ default:
324
+ throw HelperError.invalidArgs("snapshot requires --surface <frontmost-app|desktop|menubar>")
325
+ }
326
+ }
327
+
328
+ static func handleRead(arguments: [String]) throws -> any Encodable {
329
+ guard let rawX = optionValue(arguments: arguments, name: "--x"),
330
+ let rawY = optionValue(arguments: arguments, name: "--y"),
331
+ let x = Double(rawX),
332
+ let y = Double(rawY)
333
+ else {
334
+ throw HelperError.invalidArgs("read requires --x <number> --y <number>")
335
+ }
336
+
337
+ let bundleId = optionValue(arguments: arguments, name: "--bundle-id")
338
+ let surface = optionValue(arguments: arguments, name: "--surface")
339
+ let text = try readTextAtPosition(bundleId: bundleId, surface: surface, x: x, y: y)
340
+ return SuccessEnvelope(data: ReadResponse(text: text))
341
+ }
342
+ }
343
+
344
+ private func optionValue(arguments: [String], name: String) -> String? {
345
+ guard let index = arguments.firstIndex(of: name), arguments.indices.contains(index + 1) else {
346
+ return nil
347
+ }
348
+ return arguments[index + 1]
349
+ }
350
+
351
+ private func readTextAtPosition(bundleId: String?, surface: String?, x: Double, y: Double) throws -> String {
352
+ let targetApp: NSRunningApplication?
353
+ if surface == "frontmost-app" || (surface == nil && bundleId != nil) {
354
+ targetApp = try resolveTargetApplication(bundleId: bundleId, surface: surface)
355
+ } else {
356
+ targetApp = nil
357
+ }
358
+
359
+ let systemWide = AXUIElementCreateSystemWide()
360
+ var hitElement: AXUIElement?
361
+ guard AXUIElementCopyElementAtPosition(systemWide, Float(x), Float(y), &hitElement) == .success,
362
+ let hitElement
363
+ else {
364
+ throw HelperError.commandFailed("read did not resolve an accessibility element")
365
+ }
366
+
367
+ if let targetApp {
368
+ var pid: pid_t = 0
369
+ guard AXUIElementGetPid(hitElement, &pid) == .success else {
370
+ throw HelperError.commandFailed("read could not resolve element owner")
371
+ }
372
+ guard pid == targetApp.processIdentifier else {
373
+ throw HelperError.commandFailed(
374
+ "read resolved text from a different app",
375
+ details: [
376
+ "expectedPid": String(targetApp.processIdentifier),
377
+ "actualPid": String(pid)
378
+ ]
379
+ )
380
+ }
381
+ }
382
+
383
+ var current: AXUIElement? = hitElement
384
+ while let element = current {
385
+ if let text = readableText(for: element) {
386
+ return text
387
+ }
388
+ let parent = elementAttribute(element, attribute: kAXParentAttribute as String)
389
+ if let parent, CFEqual(parent, element) {
390
+ break
391
+ }
392
+ current = parent
393
+ }
394
+
395
+ throw HelperError.commandFailed("read did not resolve text")
396
+ }
397
+
398
+ private func readableText(for element: AXUIElement) -> String? {
399
+ return
400
+ stringAttribute(element, attribute: kAXValueAttribute as String)
401
+ ?? stringAttribute(element, attribute: kAXTitleAttribute as String)
402
+ ?? stringAttribute(element, attribute: kAXDescriptionAttribute as String)
403
+ }
404
+
405
+ private func openPrivacyPane(anchor: String) -> Bool {
406
+ if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?\(anchor)") {
407
+ if NSWorkspace.shared.open(url) {
408
+ return true
409
+ }
410
+ }
411
+ if let appUrl = URL(string: "file:///System/Applications/System%20Settings.app") {
412
+ return NSWorkspace.shared.open(appUrl)
413
+ }
414
+ return false
415
+ }
416
+
417
+ private func writeJSON<T: Encodable>(_ value: T) throws {
418
+ let encoder = JSONEncoder()
419
+ encoder.outputFormatting = [.sortedKeys]
420
+ let data = try encoder.encode(value)
421
+ FileHandle.standardOutput.write(data)
422
+ FileHandle.standardOutput.write(Data([0x0A]))
423
+ }
424
+
425
+ func resolveTargetApplication(bundleId: String?, surface: String?) throws -> NSRunningApplication {
426
+ let normalizedSurface = surface?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
427
+ if normalizedSurface == "desktop" || normalizedSurface == "menubar" {
428
+ throw HelperError.commandFailed(
429
+ "alert surface is not supported yet",
430
+ details: ["surface": normalizedSurface ?? ""]
431
+ )
432
+ }
433
+ if normalizedSurface == "frontmost-app" {
434
+ if let frontmost = NSWorkspace.shared.frontmostApplication {
435
+ return frontmost
436
+ }
437
+ throw HelperError.commandFailed("unable to resolve frontmost app")
438
+ }
439
+ if let bundleId, !bundleId.isEmpty {
440
+ let validatedBundleId = try validatedBundleId(bundleId)
441
+ if let app = NSRunningApplication.runningApplications(withBundleIdentifier: validatedBundleId).first {
442
+ return app
443
+ }
444
+ throw HelperError.commandFailed("app is not running", details: ["bundleId": validatedBundleId])
445
+ }
446
+ if let frontmost = NSWorkspace.shared.frontmostApplication {
447
+ return frontmost
448
+ }
449
+ throw HelperError.commandFailed("unable to resolve target app")
450
+ }
451
+
452
+ private func validatedBundleId(_ rawBundleId: String) throws -> String {
453
+ let bundleId = rawBundleId.trimmingCharacters(in: .whitespacesAndNewlines)
454
+ let pattern = #"^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)+$"#
455
+ guard bundleId.range(of: pattern, options: .regularExpression) != nil else {
456
+ throw HelperError.invalidArgs("bundle id must use reverse-DNS form like com.example.App")
457
+ }
458
+ return bundleId
459
+ }
460
+
461
+ private func findAlertElement(appElement: AXUIElement) -> AXUIElement? {
462
+ for window in windows(of: appElement) {
463
+ if let role = stringAttribute(window, attribute: kAXRoleAttribute as String),
464
+ role == "AXSheet" || role == "AXDialog"
465
+ {
466
+ return window
467
+ }
468
+ if let nested = findAlertElementRecursively(root: window, depth: 0) {
469
+ return nested
470
+ }
471
+ }
472
+ return nil
473
+ }
474
+
475
+ private func findAlertElementRecursively(root: AXUIElement, depth: Int) -> AXUIElement? {
476
+ if depth > 4 {
477
+ return nil
478
+ }
479
+ for child in children(of: root) {
480
+ if let role = stringAttribute(child, attribute: kAXRoleAttribute as String),
481
+ role == "AXSheet" || role == "AXDialog"
482
+ {
483
+ return child
484
+ }
485
+ if let nested = findAlertElementRecursively(root: child, depth: depth + 1) {
486
+ return nested
487
+ }
488
+ }
489
+ return nil
490
+ }
491
+
492
+ private func collectButtons(root: AXUIElement) -> [AXUIElement] {
493
+ var buttons: [AXUIElement] = []
494
+ collectButtons(root: root, depth: 0, results: &buttons)
495
+ return buttons
496
+ }
497
+
498
+ private func collectButtons(root: AXUIElement, depth: Int, results: inout [AXUIElement]) {
499
+ if depth > 5 {
500
+ return
501
+ }
502
+ if stringAttribute(root, attribute: kAXRoleAttribute as String) == "AXButton" {
503
+ results.append(root)
504
+ }
505
+ for child in children(of: root) {
506
+ collectButtons(root: child, depth: depth + 1, results: &results)
507
+ }
508
+ }
509
+
510
+ private func resolveElementLabel(_ element: AXUIElement) -> String {
511
+ return
512
+ stringAttribute(element, attribute: kAXTitleAttribute as String)
513
+ ?? stringAttribute(element, attribute: kAXDescriptionAttribute as String)
514
+ ?? stringAttribute(element, attribute: kAXValueAttribute as String)
515
+ ?? "button"
516
+ }
517
+
518
+ private func resolveAlertActionButton(root: AXUIElement, buttons: [AXUIElement], action: String) -> AXUIElement? {
519
+ if action == "accept",
520
+ let defaultButton = elementAttribute(root, attribute: kAXDefaultButtonAttribute as String)
521
+ {
522
+ return defaultButton
523
+ }
524
+ if action == "dismiss",
525
+ let cancelButton = elementAttribute(root, attribute: kAXCancelButtonAttribute as String)
526
+ {
527
+ return cancelButton
528
+ }
529
+
530
+ let buttonEntries = buttons.map { (element: $0, label: resolveElementLabel($0).lowercased()) }
531
+ let preferredLabels =
532
+ action == "accept"
533
+ ? ["allow", "ok", "open", "continue", "yes", "save", "install", "trust", "enable"]
534
+ : ["don't allow", "deny", "cancel", "not now", "no", "close", "later", "ignore"]
535
+
536
+ for preferredLabel in preferredLabels {
537
+ if let match = buttonEntries.first(where: { $0.label.contains(preferredLabel) }) {
538
+ return match.element
539
+ }
540
+ }
541
+
542
+ return action == "accept" ? buttons.first : buttons.last
543
+ }
544
+
545
+ AgentDeviceMacOSHelper.main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -27,6 +27,7 @@
27
27
  "build:xcuitest:ios": "rm -rf ~/.agent-device/ios-runner/derived/device && xcodebuild build-for-testing -project ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj -scheme AgentDeviceRunner -destination \"generic/platform=iOS Simulator\" -derivedDataPath ~/.agent-device/ios-runner/derived",
28
28
  "build:xcuitest:macos": "rm -rf ~/.agent-device/ios-runner/derived/macos && xcodebuild build-for-testing -project ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj -scheme AgentDeviceRunner -destination \"platform=macOS,arch=$(uname -m)\" -derivedDataPath ~/.agent-device/ios-runner/derived/macos CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=\"\" DEVELOPMENT_TEAM=\"\" COMPILER_INDEX_STORE_ENABLE=NO ENABLE_CODE_COVERAGE=NO",
29
29
  "build:xcuitest:tvos": "rm -rf ~/.agent-device/ios-runner/derived/tvos && xcodebuild build-for-testing -project ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj -scheme AgentDeviceRunner -destination \"generic/platform=tvOS Simulator\" -derivedDataPath ~/.agent-device/ios-runner/derived/tvos",
30
+ "build:macos-helper": "swift build -c release --package-path macos-helper",
30
31
  "build:all": "pnpm build:node && pnpm build:xcuitest",
31
32
  "ad": "node bin/agent-device.mjs",
32
33
  "format": "prettier --write src test skills",
@@ -46,6 +47,8 @@
46
47
  "!ios-runner/**/.swiftpm",
47
48
  "!ios-runner/**/xcuserdata",
48
49
  "!ios-runner/**/*.xcuserstate",
50
+ "macos-helper",
51
+ "!macos-helper/**/.build",
49
52
  "skills",
50
53
  "README.md",
51
54
  "LICENSE"
@@ -70,6 +73,7 @@
70
73
  "pngjs": "^7.0.0"
71
74
  },
72
75
  "devDependencies": {
76
+ "@microsoft/api-extractor": "^7.52.10",
73
77
  "@rslib/core": "0.19.4",
74
78
  "@types/node": "^22.0.0",
75
79
  "@types/pngjs": "^6.0.5",