agent-device 0.11.1 → 0.11.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.
@@ -1,11 +1,15 @@
1
1
  import AppKit
2
2
  import ApplicationServices
3
+ import CoreGraphics
3
4
  import Foundation
4
5
 
5
6
  private enum SnapshotTraversalLimits {
6
7
  static let maxDesktopApps = 24
7
8
  static let maxNodes = 1500
8
9
  static let maxDepth = 12
10
+ static let maxMenuBarBandY = 64.0
11
+ static let maxMenuBarBandHeight = 64.0
12
+ static let maxMenuBarExtraWidth = 256.0
9
13
  }
10
14
 
11
15
  struct RectResponse: Encodable {
@@ -62,7 +66,17 @@ private struct SnapshotTraversalState {
62
66
  var truncated = false
63
67
  }
64
68
 
65
- func captureSnapshotResponse(surface: String) throws -> SnapshotResponse {
69
+ private struct MenuBarWindowFallbackCandidate {
70
+ let windowNumber: Int
71
+ let rect: RectResponse
72
+ let layer: Int
73
+
74
+ var area: Double {
75
+ rect.width * rect.height
76
+ }
77
+ }
78
+
79
+ func captureSnapshotResponse(surface: String, bundleId: String? = nil) throws -> SnapshotResponse {
66
80
  let result: SnapshotBuildResult
67
81
  switch surface {
68
82
  case "frontmost-app":
@@ -70,7 +84,7 @@ func captureSnapshotResponse(surface: String) throws -> SnapshotResponse {
70
84
  case "desktop":
71
85
  result = snapshotDesktop()
72
86
  case "menubar":
73
- result = snapshotMenuBar()
87
+ result = try snapshotMenuBar(bundleId: bundleId)
74
88
  default:
75
89
  throw HelperError.invalidArgs("snapshot requires --surface <frontmost-app|desktop|menubar>")
76
90
  }
@@ -200,8 +214,9 @@ private func appendApplicationSnapshot(
200
214
  return true
201
215
  }
202
216
 
203
- private func snapshotMenuBar() -> SnapshotBuildResult {
217
+ private func snapshotMenuBar(bundleId: String?) throws -> SnapshotBuildResult {
204
218
  var state = SnapshotTraversalState()
219
+ let screenRect = mainScreenRectResponse()
205
220
  guard
206
221
  let rootIndex = appendSyntheticSnapshotNode(
207
222
  into: &state,
@@ -209,29 +224,54 @@ private func snapshotMenuBar() -> SnapshotBuildResult {
209
224
  label: "Menu Bar",
210
225
  depth: 0,
211
226
  parentIndex: nil,
212
- surface: "menubar"
227
+ surface: "menubar",
228
+ rect: screenRect
213
229
  )
214
230
  else {
215
231
  return SnapshotBuildResult(nodes: state.nodes, truncated: true)
216
232
  }
217
233
 
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,
234
+ if let bundleId {
235
+ let targetApp = try resolveTargetApplication(bundleId: bundleId, surface: nil)
236
+ let appendedExtras = appendMenuBarSnapshot(
237
+ targetApp,
238
+ attribute: kAXExtrasMenuBarAttribute as String,
239
+ depth: 1,
240
+ parentIndex: rootIndex,
241
+ surface: "menubar",
242
+ state: &state
243
+ )
244
+ if !appendedExtras {
245
+ let appendedMenuBar = appendMenuBarSnapshot(
246
+ targetApp,
247
+ attribute: kAXMenuBarAttribute as String,
223
248
  depth: 1,
224
249
  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
- ),
250
+ surface: "menubar",
232
251
  state: &state
233
252
  )
253
+ if !appendedMenuBar {
254
+ _ = appendMenuBarWindowFallback(
255
+ targetApp,
256
+ depth: 1,
257
+ parentIndex: rootIndex,
258
+ surface: "menubar",
259
+ state: &state
260
+ )
261
+ }
234
262
  }
263
+ return SnapshotBuildResult(nodes: state.nodes, truncated: state.truncated)
264
+ }
265
+
266
+ if let frontmost = NSWorkspace.shared.frontmostApplication {
267
+ _ = appendMenuBarSnapshot(
268
+ frontmost,
269
+ attribute: kAXMenuBarAttribute as String,
270
+ depth: 1,
271
+ parentIndex: rootIndex,
272
+ surface: "menubar",
273
+ state: &state
274
+ )
235
275
  }
236
276
 
237
277
  if !state.truncated,
@@ -239,20 +279,24 @@ private func snapshotMenuBar() -> SnapshotBuildResult {
239
279
  withBundleIdentifier: "com.apple.systemuiserver"
240
280
  ).first
241
281
  {
242
- let systemUiElement = AXUIElementCreateApplication(systemUiServer.processIdentifier)
243
- if let menuExtras = elementAttribute(systemUiElement, attribute: kAXMenuBarAttribute as String) {
244
- _ = appendElementSnapshot(
245
- menuExtras,
282
+ let appendedExtras = appendMenuBarSnapshot(
283
+ systemUiServer,
284
+ attribute: kAXExtrasMenuBarAttribute as String,
285
+ depth: 1,
286
+ parentIndex: rootIndex,
287
+ surface: "menubar",
288
+ state: &state,
289
+ windowTitle: "System Menu Extras"
290
+ )
291
+ if !appendedExtras {
292
+ _ = appendMenuBarSnapshot(
293
+ systemUiServer,
294
+ attribute: kAXMenuBarAttribute as String,
246
295
  depth: 1,
247
296
  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
297
+ surface: "menubar",
298
+ state: &state,
299
+ windowTitle: "System Menu Extras"
256
300
  )
257
301
  }
258
302
  }
@@ -260,6 +304,67 @@ private func snapshotMenuBar() -> SnapshotBuildResult {
260
304
  return SnapshotBuildResult(nodes: state.nodes, truncated: state.truncated)
261
305
  }
262
306
 
307
+ @discardableResult
308
+ private func appendMenuBarSnapshot(
309
+ _ app: NSRunningApplication,
310
+ attribute: String,
311
+ depth: Int,
312
+ parentIndex: Int,
313
+ surface: String,
314
+ state: inout SnapshotTraversalState,
315
+ windowTitle: String? = nil
316
+ ) -> Bool {
317
+ let appElement = AXUIElementCreateApplication(app.processIdentifier)
318
+ guard let menuBar = elementAttribute(appElement, attribute: attribute) else {
319
+ return false
320
+ }
321
+
322
+ let nodeCountBefore = state.nodes.count
323
+ _ = appendElementSnapshot(
324
+ menuBar,
325
+ depth: depth,
326
+ parentIndex: parentIndex,
327
+ context: SnapshotContext(
328
+ surface: surface,
329
+ pid: Int32(app.processIdentifier),
330
+ bundleId: app.bundleIdentifier,
331
+ appName: app.localizedName,
332
+ windowTitle: windowTitle ?? app.localizedName
333
+ ),
334
+ state: &state
335
+ )
336
+ return state.nodes.count > nodeCountBefore
337
+ }
338
+
339
+ @discardableResult
340
+ private func appendMenuBarWindowFallback(
341
+ _ app: NSRunningApplication,
342
+ depth: Int,
343
+ parentIndex: Int,
344
+ surface: String,
345
+ state: inout SnapshotTraversalState
346
+ ) -> Bool {
347
+ guard let candidate = menuBarWindowFallbackCandidate(for: app) else {
348
+ return false
349
+ }
350
+
351
+ return appendSyntheticSnapshotNode(
352
+ into: &state,
353
+ type: "MenuBarItem",
354
+ label: app.localizedName ?? app.bundleIdentifier ?? "Menu Bar Item",
355
+ depth: depth,
356
+ parentIndex: parentIndex,
357
+ surface: surface,
358
+ identifier: "cgwindow:\(candidate.windowNumber)",
359
+ pid: Int32(app.processIdentifier),
360
+ bundleId: app.bundleIdentifier,
361
+ appName: app.localizedName,
362
+ windowTitle: app.localizedName,
363
+ rect: candidate.rect,
364
+ hittable: true
365
+ ) != nil
366
+ }
367
+
263
368
  @discardableResult
264
369
  private func appendSyntheticSnapshotNode(
265
370
  into state: inout SnapshotTraversalState,
@@ -272,7 +377,9 @@ private func appendSyntheticSnapshotNode(
272
377
  pid: Int32? = nil,
273
378
  bundleId: String? = nil,
274
379
  appName: String? = nil,
275
- windowTitle: String? = nil
380
+ windowTitle: String? = nil,
381
+ rect: RectResponse? = nil,
382
+ hittable: Bool = false
276
383
  ) -> Int? {
277
384
  guard reserveSnapshotNodeCapacity(&state) else {
278
385
  return nil
@@ -288,10 +395,10 @@ private func appendSyntheticSnapshotNode(
288
395
  label: label,
289
396
  value: nil,
290
397
  identifier: identifier ?? "surface:\(surface):\(type.lowercased())",
291
- rect: nil,
398
+ rect: rect,
292
399
  enabled: true,
293
400
  selected: nil,
294
- hittable: false,
401
+ hittable: hittable && rect != nil,
295
402
  depth: depth,
296
403
  parentIndex: parentIndex,
297
404
  pid: pid,
@@ -304,6 +411,83 @@ private func appendSyntheticSnapshotNode(
304
411
  return index
305
412
  }
306
413
 
414
+ private func mainScreenRectResponse() -> RectResponse? {
415
+ guard let screenFrame = NSScreen.main?.frame, screenFrame.width > 0, screenFrame.height > 0 else {
416
+ return nil
417
+ }
418
+ return RectResponse(
419
+ x: Double(screenFrame.origin.x),
420
+ y: Double(screenFrame.origin.y),
421
+ width: Double(screenFrame.width),
422
+ height: Double(screenFrame.height)
423
+ )
424
+ }
425
+
426
+ private func menuBarWindowFallbackCandidate(
427
+ for app: NSRunningApplication
428
+ ) -> MenuBarWindowFallbackCandidate? {
429
+ guard
430
+ let windowInfoList = CGWindowListCopyWindowInfo([.optionAll], kCGNullWindowID)
431
+ as? [[String: Any]]
432
+ else {
433
+ return nil
434
+ }
435
+
436
+ let pid = Int(app.processIdentifier)
437
+ let allCandidates = windowInfoList.compactMap { info -> MenuBarWindowFallbackCandidate? in
438
+ let ownerPid = (info[kCGWindowOwnerPID as String] as? NSNumber)?.intValue
439
+ guard ownerPid == pid else {
440
+ return nil
441
+ }
442
+ guard let boundsDictionary = info[kCGWindowBounds as String] as? NSDictionary,
443
+ let bounds = CGRect(dictionaryRepresentation: boundsDictionary)
444
+ else {
445
+ return nil
446
+ }
447
+ guard bounds.width > 0, bounds.height > 0 else {
448
+ return nil
449
+ }
450
+ let alpha = (info[kCGWindowAlpha as String] as? NSNumber)?.doubleValue ?? 1
451
+ guard alpha > 0 else {
452
+ return nil
453
+ }
454
+ let rect = RectResponse(
455
+ x: Double(bounds.origin.x),
456
+ y: Double(bounds.origin.y),
457
+ width: Double(bounds.width),
458
+ height: Double(bounds.height)
459
+ )
460
+ let windowNumber = (info[kCGWindowNumber as String] as? NSNumber)?.intValue ?? 0
461
+ let layer = (info[kCGWindowLayer as String] as? NSNumber)?.intValue ?? 0
462
+ return MenuBarWindowFallbackCandidate(
463
+ windowNumber: windowNumber,
464
+ rect: rect,
465
+ layer: layer
466
+ )
467
+ }
468
+
469
+ // CGWindowList can surface multiple app-owned utility windows. Prefer the small
470
+ // top-band window that matches typical menu bar extra geometry before ranking by area.
471
+ let menuBarBandCandidates = allCandidates.filter { candidate in
472
+ candidate.rect.y <= SnapshotTraversalLimits.maxMenuBarBandY
473
+ && candidate.rect.height <= SnapshotTraversalLimits.maxMenuBarBandHeight
474
+ }
475
+ let narrowCandidates = menuBarBandCandidates.filter { candidate in
476
+ candidate.rect.width <= SnapshotTraversalLimits.maxMenuBarExtraWidth
477
+ }
478
+ let rankedCandidates = (narrowCandidates.isEmpty ? menuBarBandCandidates : narrowCandidates)
479
+ .sorted { left, right in
480
+ if left.area != right.area {
481
+ return left.area < right.area
482
+ }
483
+ if left.layer != right.layer {
484
+ return left.layer < right.layer
485
+ }
486
+ return left.windowNumber < right.windowNumber
487
+ }
488
+ return rankedCandidates.first
489
+ }
490
+
307
491
  @discardableResult
308
492
  private func appendElementSnapshot(
309
493
  _ element: AXUIElement,
@@ -361,7 +545,7 @@ private func appendElementSnapshot(
361
545
  return index
362
546
  }
363
547
 
364
- for child in children(of: element) {
548
+ for child in snapshotChildren(of: element, role: role) {
365
549
  if state.truncated {
366
550
  break
367
551
  }
@@ -522,9 +706,9 @@ private func accessibilityAxValue(_ value: CFTypeRef?) -> AXValue? {
522
706
  return (value as! AXValue)
523
707
  }
524
708
 
525
- func children(of element: AXUIElement) -> [AXUIElement] {
709
+ private func elementArrayAttribute(_ element: AXUIElement, attribute: String) -> [AXUIElement] {
526
710
  var value: CFTypeRef?
527
- guard AXUIElementCopyAttributeValue(element, kAXChildrenAttribute as CFString, &value) == .success,
711
+ guard AXUIElementCopyAttributeValue(element, attribute as CFString, &value) == .success,
528
712
  let children = value as? [AXUIElement]
529
713
  else {
530
714
  return []
@@ -532,6 +716,23 @@ func children(of element: AXUIElement) -> [AXUIElement] {
532
716
  return children
533
717
  }
534
718
 
719
+ func children(of element: AXUIElement) -> [AXUIElement] {
720
+ return elementArrayAttribute(element, attribute: kAXChildrenAttribute as String)
721
+ }
722
+
723
+ private func snapshotChildren(of element: AXUIElement, role: String?) -> [AXUIElement] {
724
+ let directChildren = children(of: element)
725
+ if !directChildren.isEmpty {
726
+ return directChildren
727
+ }
728
+ switch role {
729
+ case "AXMenuBar", "AXMenuBarItem", "AXMenu":
730
+ return elementArrayAttribute(element, attribute: kAXVisibleChildrenAttribute as String)
731
+ default:
732
+ return []
733
+ }
734
+ }
735
+
535
736
  func windows(of appElement: AXUIElement) -> [AXUIElement] {
536
737
  var value: CFTypeRef?
537
738
  guard AXUIElementCopyAttributeValue(appElement, "AXWindows" as CFString, &value) == .success,
@@ -2,6 +2,9 @@ import AppKit
2
2
  import ApplicationServices
3
3
  import CoreGraphics
4
4
  import Foundation
5
+ import ImageIO
6
+ import ScreenCaptureKit
7
+ import UniformTypeIdentifiers
5
8
 
6
9
  enum HelperError: Error {
7
10
  case invalidArgs(String)
@@ -57,6 +60,19 @@ struct ReadResponse: Encodable {
57
60
  let text: String
58
61
  }
59
62
 
63
+ struct PressResponse: Encodable {
64
+ let x: Double
65
+ let y: Double
66
+ let bundleId: String?
67
+ let surface: String?
68
+ }
69
+
70
+ struct ScreenshotResponse: Encodable {
71
+ let path: String
72
+ let surface: String?
73
+ let fullscreen: Bool
74
+ }
75
+
60
76
  struct AgentDeviceMacOSHelper {
61
77
  static func main() {
62
78
  do {
@@ -100,6 +116,10 @@ struct AgentDeviceMacOSHelper {
100
116
  return try handleSnapshot(arguments: Array(arguments.dropFirst()))
101
117
  case "read":
102
118
  return try handleRead(arguments: Array(arguments.dropFirst()))
119
+ case "press":
120
+ return try handlePress(arguments: Array(arguments.dropFirst()))
121
+ case "screenshot":
122
+ return try handleScreenshot(arguments: Array(arguments.dropFirst()))
103
123
  default:
104
124
  throw HelperError.invalidArgs("unknown command: \(command)")
105
125
  }
@@ -315,11 +335,13 @@ struct AgentDeviceMacOSHelper {
315
335
  throw HelperError.invalidArgs("snapshot requires --surface <frontmost-app|desktop|menubar>")
316
336
  }
317
337
 
338
+ let bundleId = try optionValue(arguments: arguments, name: "--bundle-id").map(validatedBundleId)
339
+
318
340
  switch surface {
319
341
  case "frontmost-app":
320
- return SuccessEnvelope(data: try captureSnapshotResponse(surface: surface))
342
+ return SuccessEnvelope(data: try captureSnapshotResponse(surface: surface, bundleId: bundleId))
321
343
  case "desktop", "menubar":
322
- return SuccessEnvelope(data: try captureSnapshotResponse(surface: surface))
344
+ return SuccessEnvelope(data: try captureSnapshotResponse(surface: surface, bundleId: bundleId))
323
345
  default:
324
346
  throw HelperError.invalidArgs("snapshot requires --surface <frontmost-app|desktop|menubar>")
325
347
  }
@@ -339,6 +361,35 @@ struct AgentDeviceMacOSHelper {
339
361
  let text = try readTextAtPosition(bundleId: bundleId, surface: surface, x: x, y: y)
340
362
  return SuccessEnvelope(data: ReadResponse(text: text))
341
363
  }
364
+
365
+ static func handlePress(arguments: [String]) throws -> any Encodable {
366
+ guard let rawX = optionValue(arguments: arguments, name: "--x"),
367
+ let rawY = optionValue(arguments: arguments, name: "--y"),
368
+ let x = Double(rawX),
369
+ let y = Double(rawY)
370
+ else {
371
+ throw HelperError.invalidArgs("press requires --x <number> --y <number>")
372
+ }
373
+
374
+ let bundleId = try optionValue(arguments: arguments, name: "--bundle-id").map(validatedBundleId)
375
+ let surface = optionValue(arguments: arguments, name: "--surface")
376
+ try pressAtPosition(bundleId: bundleId, surface: surface, x: x, y: y)
377
+ return SuccessEnvelope(data: PressResponse(x: x, y: y, bundleId: bundleId, surface: surface))
378
+ }
379
+
380
+ static func handleScreenshot(arguments: [String]) throws -> any Encodable {
381
+ guard let outPath = optionValue(arguments: arguments, name: "--out")?
382
+ .trimmingCharacters(in: .whitespacesAndNewlines),
383
+ !outPath.isEmpty
384
+ else {
385
+ throw HelperError.invalidArgs("screenshot requires --out <path>")
386
+ }
387
+
388
+ let surface = optionValue(arguments: arguments, name: "--surface")
389
+ let fullscreen = arguments.contains("--fullscreen")
390
+ try captureSurfaceScreenshot(surface: surface, outPath: outPath, fullscreen: fullscreen)
391
+ return SuccessEnvelope(data: ScreenshotResponse(path: outPath, surface: surface, fullscreen: fullscreen))
392
+ }
342
393
  }
343
394
 
344
395
  private func optionValue(arguments: [String], name: String) -> String? {
@@ -395,6 +446,74 @@ private func readTextAtPosition(bundleId: String?, surface: String?, x: Double,
395
446
  throw HelperError.commandFailed("read did not resolve text")
396
447
  }
397
448
 
449
+ private func pressAtPosition(bundleId: String?, surface: String?, x: Double, y: Double) throws {
450
+ _ = bundleId
451
+ _ = surface
452
+ let point = CGPoint(x: x, y: y)
453
+ guard let move = CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, mouseCursorPosition: point, mouseButton: .left),
454
+ let down = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: point, mouseButton: .left),
455
+ let up = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: point, mouseButton: .left)
456
+ else {
457
+ throw HelperError.commandFailed("press action failed", details: ["reason": "event_creation_failed"])
458
+ }
459
+ move.post(tap: .cghidEventTap)
460
+ down.post(tap: .cghidEventTap)
461
+ up.post(tap: .cghidEventTap)
462
+ }
463
+
464
+ private func captureSurfaceScreenshot(surface: String?, outPath: String, fullscreen: Bool) throws {
465
+ _ = fullscreen
466
+ guard #available(macOS 15.2, *) else {
467
+ throw HelperError.commandFailed(
468
+ "screenshot on macOS desktop and menubar surfaces requires macOS 15.2 or newer"
469
+ )
470
+ }
471
+ guard let screenFrame = NSScreen.main?.frame, screenFrame.width > 0, screenFrame.height > 0 else {
472
+ throw HelperError.commandFailed("screenshot could not resolve main screen bounds")
473
+ }
474
+
475
+ let rect = CGRect(origin: screenFrame.origin, size: screenFrame.size)
476
+ let semaphore = DispatchSemaphore(value: 0)
477
+ var capturedImage: CGImage?
478
+ var capturedError: Error?
479
+ SCScreenshotManager.captureImage(in: rect) { image, error in
480
+ capturedImage = image
481
+ capturedError = error
482
+ semaphore.signal()
483
+ }
484
+ semaphore.wait()
485
+
486
+ if let error = capturedError as NSError? {
487
+ if error.domain == "com.apple.ScreenCaptureKit.SCStreamErrorDomain", error.code == -3801 {
488
+ throw HelperError.commandFailed(
489
+ "screenshot requires Screen Recording permission on macOS desktop and menubar surfaces",
490
+ details: ["surface": surface ?? "", "permission": "screen-recording"]
491
+ )
492
+ }
493
+ throw HelperError.commandFailed("screenshot failed", details: ["error": error.localizedDescription])
494
+ }
495
+ guard let capturedImage else {
496
+ throw HelperError.commandFailed("screenshot failed")
497
+ }
498
+
499
+ let outputURL = URL(fileURLWithPath: outPath)
500
+ if let parent = outputURL.deletingLastPathComponent().path.removingPercentEncoding, !parent.isEmpty {
501
+ try FileManager.default.createDirectory(atPath: parent, withIntermediateDirectories: true)
502
+ }
503
+ guard let destination = CGImageDestinationCreateWithURL(
504
+ outputURL as CFURL,
505
+ UTType.png.identifier as CFString,
506
+ 1,
507
+ nil
508
+ ) else {
509
+ throw HelperError.commandFailed("screenshot could not create PNG destination")
510
+ }
511
+ CGImageDestinationAddImage(destination, capturedImage, nil)
512
+ guard CGImageDestinationFinalize(destination) else {
513
+ throw HelperError.commandFailed("screenshot could not write PNG file")
514
+ }
515
+ }
516
+
398
517
  private func readableText(for element: AXUIElement) -> String? {
399
518
  return
400
519
  stringAttribute(element, attribute: kAXValueAttribute as String)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -30,7 +30,12 @@
30
30
  "build:macos-helper": "swift build -c release --package-path macos-helper",
31
31
  "build:all": "pnpm build:node && pnpm build:xcuitest",
32
32
  "ad": "node bin/agent-device.mjs",
33
- "format": "prettier --write src test skills",
33
+ "lint": "oxlint . --deny-warnings",
34
+ "format": "oxfmt --write src test skills package.json tsconfig.json .oxlintrc.json .oxfmtrc.json",
35
+ "check:quick": "pnpm lint && pnpm typecheck",
36
+ "check:tooling": "pnpm lint && pnpm typecheck && pnpm build",
37
+ "check:unit": "pnpm test:unit && pnpm test:smoke",
38
+ "check": "pnpm check:tooling && pnpm check:unit",
34
39
  "prepack": "pnpm build:all",
35
40
  "typecheck": "tsc -p tsconfig.json",
36
41
  "test": "vitest run",
@@ -66,22 +71,17 @@
66
71
  "ios",
67
72
  "android"
68
73
  ],
69
- "prettier": {
70
- "singleQuote": true,
71
- "semi": true,
72
- "trailingComma": "all",
73
- "printWidth": 100
74
- },
75
74
  "dependencies": {
76
75
  "pngjs": "^7.0.0"
77
76
  },
78
77
  "devDependencies": {
79
78
  "@microsoft/api-extractor": "^7.52.10",
80
- "@rslib/core": "0.19.4",
79
+ "@rslib/core": "0.20.1",
81
80
  "@types/node": "^22.0.0",
82
81
  "@types/pngjs": "^6.0.5",
83
- "prettier": "^3.3.3",
84
- "typescript": "^5.9.3",
82
+ "oxfmt": "^0.42.0",
83
+ "oxlint": "^1.57.0",
84
+ "typescript": "^6.0.2",
85
85
  "vitest": "^4.1.2"
86
86
  }
87
87
  }
@@ -30,7 +30,8 @@ agent-device close
30
30
  - `app`: default surface and the normal choice for `click`, `fill`, `press`, `scroll`, `screenshot`, and `record`.
31
31
  - `frontmost-app`: inspect the currently focused app without naming it first.
32
32
  - `desktop`: inspect visible desktop windows across apps.
33
- - `menubar`: inspect the active app menu bar and system menu extras.
33
+ - `menubar`: inspect the active app menu bar and system menu extras. Use `open <app> --platform macos --surface menubar` when you need one menu bar app's extras, such as a status-item app.
34
+ - Menu bar apps can expose a sparse or empty default `app` tree. Prefer the `menubar` surface first when the app lives entirely in the top bar.
34
35
 
35
36
  Use inspect-first surfaces to understand desktop-global UI, then switch back to `app` when you need to act in one app.
36
37
 
@@ -81,6 +82,6 @@ Troubleshooting:
81
82
 
82
83
  - If visible content is missing from `snapshot -i`, re-snapshot after the UI settles.
83
84
  - If `desktop` is too broad, retry with `frontmost-app`.
84
- - If `menubar` is missing the expected menu, make the app frontmost first and retry.
85
+ - If `menubar` is missing the expected menu, retry with `open <app> --platform macos --surface menubar` for menu bar apps, or make the app frontmost first and retry the generic menubar surface.
85
86
  - If the wrong menu opened, retry secondary-clicking the row or cell wrapper rather than the nested text node.
86
87
  - If the app has multiple windows, make the correct window frontmost before relying on refs.
package/dist/src/306.js DELETED
@@ -1,3 +0,0 @@
1
- import{AsyncLocalStorage as e}from"node:async_hooks";import t,{createHash as r}from"node:crypto";import n,{existsSync as i,promises as o}from"node:fs";import a from"node:os";import s from"node:path";import{PNG as l}from"pngjs";import{URL as d,fileURLToPath as u,pathToFileURL as c}from"node:url";import{spawn as p,spawnSync as f}from"node:child_process";let m=new e,h=/(token|secret|password|authorization|cookie|api[_-]?key|access[_-]?key|private[_-]?key)/i,g=/(bearer\s+[a-z0-9._-]+|(?:api[_-]?key|token|secret|password)\s*[=:]\s*\S+)/i;function w(){return t.randomBytes(8).toString("hex")}async function I(e,r){let n={...e,diagnosticId:`${Date.now().toString(36)}-${t.randomBytes(4).toString("hex")}`,events:[]};return await m.run(n,r)}function S(){let e=m.getStore();return e?{diagnosticId:e.diagnosticId,requestId:e.requestId,session:e.session,command:e.command,debug:e.debug}:{}}function v(e){let t=m.getStore();if(!t)return;let r={ts:new Date().toISOString(),level:e.level??"info",phase:e.phase,session:t.session,requestId:t.requestId,command:t.command,durationMs:e.durationMs,data:e.data?b(e.data):void 0};if(t.events.push(r),!t.debug)return;let i=`[agent-device][diag] ${JSON.stringify(r)}
2
- `;try{t.logPath&&n.appendFile(t.logPath,i,()=>{}),t.traceLogPath&&n.appendFile(t.traceLogPath,i,()=>{}),t.logPath||t.traceLogPath||process.stderr.write(i)}catch{}}async function y(e,t,r){let n=Date.now();try{let i=await t();return v({level:"info",phase:e,durationMs:Date.now()-n,data:r}),i}catch(t){throw v({level:"error",phase:e,durationMs:Date.now()-n,data:{...r??{},error:t instanceof Error?t.message:String(t)}}),t}}function A(e={}){let t=m.getStore();if(!t||!e.force&&!t.debug||0===t.events.length)return null;try{let e=(t.session??"default").replace(/[^a-zA-Z0-9._-]/g,"_"),r=new Date().toISOString().slice(0,10),i=s.join(a.homedir(),".agent-device","logs",e,r);n.mkdirSync(i,{recursive:!0});let o=new Date().toISOString().replace(/[:.]/g,"-"),l=s.join(i,`${o}-${t.diagnosticId}.ndjson`),d=t.events.map(e=>JSON.stringify(b(e)));return n.writeFileSync(l,`${d.join("\n")}
3
- `),t.events=[],l}catch{return null}}function b(e){return function e(t,r,n){if(null==t)return t;if("string"==typeof t){var i=t,o=n;let e=i.trim();if(!e)return i;if(o&&h.test(o)||g.test(e))return"[REDACTED]";let r=function(e){try{let t=new URL(e);return t.search&&(t.search="?REDACTED"),(t.username||t.password)&&(t.username="REDACTED",t.password="REDACTED"),t.toString()}catch{return null}}(e);return r||(e.length>400?`${e.slice(0,200)}...<truncated>`:e)}if("object"!=typeof t)return t;if(r.has(t))return"[Circular]";if(r.add(t),Array.isArray(t))return t.map(t=>e(t,r));let a={};for(let[n,i]of Object.entries(t)){if(h.test(n)){a[n]="[REDACTED]";continue}a[n]=e(i,r,n)}return a}(e,new WeakSet)}class E extends Error{code;details;cause;constructor(e,t,r,n){super(t),this.code=e,this.details=r,this.cause=n}}function $(e){return e instanceof E?e:e instanceof Error?new E("UNKNOWN",e.message,void 0,e):new E("UNKNOWN","Unknown error",{err:e})}function N(e,t={}){let r=$(e),n=r.details?b(r.details):void 0,i=n&&"string"==typeof n.hint?n.hint:void 0,o=(n&&"string"==typeof n.diagnosticId?n.diagnosticId:void 0)??t.diagnosticId,a=(n&&"string"==typeof n.logPath?n.logPath:void 0)??t.logPath,s=i??function(e){switch(e){case"INVALID_ARGS":return"Check command arguments and run --help for usage examples.";case"SESSION_NOT_FOUND":return"Run open first or pass an explicit device selector.";case"TOOL_MISSING":return"Install required platform tooling and ensure it is available in PATH.";case"DEVICE_NOT_FOUND":return"Verify the target device is booted/connected and selectors match.";case"UNSUPPORTED_OPERATION":return"This command is not available for the selected platform/device.";case"COMMAND_FAILED":default:return"Retry with --debug and inspect diagnostics log for details.";case"UNAUTHORIZED":return"Refresh daemon metadata and retry the command."}}(r.code),l=function(e){if(!e)return;let t={...e};return delete t.hint,delete t.diagnosticId,delete t.logPath,Object.keys(t).length>0?t:void 0}(n),d=function(e,t,r){if("COMMAND_FAILED"!==e||r?.processExitError!==!0)return t;let n=function(e){let t=[/^an error was encountered processing the command/i,/^underlying error\b/i,/^simulator device failed to complete the requested operation/i];for(let r of e.split("\n")){let e=r.trim();if(e&&!t.some(t=>t.test(e)))return e.length>200?`${e.slice(0,200)}...`:e}return null}("string"==typeof r?.stderr?r.stderr:"");return n||t}(r.code,r.message,n);return{code:r.code,message:d,hint:s,diagnosticId:o,logPath:a,details:l}}let D="<wifi|airplane|location> <on|off>",x="appearance <light|dark|toggle>",_="faceid <match|nonmatch|enroll|unenroll>",T="touchid <match|nonmatch|enroll|unenroll>",L="fingerprint <match|nonmatch>",O="permission <grant|deny|reset> <camera|microphone|photos|contacts|contacts-limited|notifications|calendar|location|location-always|media-library|motion|reminders|siri> [full|limited]",M="permission <grant|reset> <accessibility|screen-recording|input-monitoring>",C=`settings ${D} | settings ${x} | settings ${_} | settings ${T} | settings ${L} | settings ${O} | settings ${M}`,k=`settings requires ${D}, ${x}, ${_}, ${T}, ${L}, ${O}, or ${M}`,P=["app","frontmost-app","desktop","menubar"];function R(e){let t=e?.trim().toLowerCase();if("app"===t||"frontmost-app"===t||"desktop"===t||"menubar"===t)return t;throw new E("INVALID_ARGS",`Invalid surface: ${e}. Use ${P.join("|")}.`)}function F(e){var t;let r,n=B(e.label),i=B(e.value),o=B(e.identifier),a=(t=o)&&!/^[\w.]+:id\/[\w.-]+$/i.test(t)&&!/^_?NS:\d+$/i.test(t)?o:"";return(r=j(e.type??"")).includes("textfield")||r.includes("securetextfield")||r.includes("searchfield")||r.includes("edittext")||r.includes("textview")||r.includes("textarea")?i||n||a:n||i||a}function B(e){return"string"==typeof e?e.trim():""}function j(e){let t=e.trim().replace(/XCUIElementType/gi,"").replace(/^AX/,"").toLowerCase(),r=Math.max(t.lastIndexOf("."),t.lastIndexOf("/"));return -1!==r&&(t=t.slice(r+1)),t}function G(e,t={}){let r=[],n=[];for(let i of e){let e=i.depth??0;for(;r.length>0&&e<=r[r.length-1];)r.pop();let o=i.label?.trim()||i.value?.trim()||i.identifier?.trim()||"",a=V(i.type??"Element"),s="group"===a&&!o;s&&r.push(e);let l=s?e:Math.max(0,e-r.length);n.push({node:i,depth:l,type:a,text:z(i,l,s,a,t)})}return n}function z(e,t,r,n,i={}){var o,a,s,l,d;let u,c,p=n??V(e.type??"Element"),f=(u=F(e),{text:u,isLargeSurface:c=function(e,t){if("text-view"===t||"text-field"===t||"search"===t)return!0;let r=j(e.type??""),n=`${e.role??""} ${e.subrole??""}`.toLowerCase();return r.includes("textview")||r.includes("textarea")||r.includes("textfield")||r.includes("securetextfield")||r.includes("searchfield")||r.includes("edittext")||n.includes("text area")||n.includes("text field")}(e,p),shouldSummarize:c&&!!(o=u)&&(o.length>80||/[\r\n]/.test(o))}),m=(a=e,s=p,l=i,d=f,l.summarizeTextSurfaces&&d.shouldSummarize&&function(e,t,r){let n=B(e.label);if(n&&n!==r)return n;let i=B(e.identifier);if(i&&!q(i)&&i!==r)return i;switch(t){case"text":case"text-view":return"Text view";case"text-field":return"Text field";case"search":return"Search field";default:return""}}(a,s,d.text)||U(a,s)),h=" ".repeat(t),g=e.ref?`@${e.ref}`:"",w=(function(e,t,r,n){let i,o=[];if(!1===e.enabled&&o.push("disabled"),!r.summarizeTextSurfaces||(!0===e.selected&&o.push("selected"),H(t)&&o.push("editable"),function(e,t){if("scroll-area"===t)return!0;let r=(e.type??"").toLowerCase(),n=`${e.role??""} ${e.subrole??""}`.toLowerCase();return r.includes("scroll")||n.includes("scroll")}(e,t)&&o.push("scrollable"),!n.shouldSummarize))return o;return o.push(`preview:"${((i=n.text.replace(/\s+/g," ").trim()).length<=48?i:`${i.slice(0,45)}...`).replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`),o.push("truncated"),[...new Set(o)]})(e,p,i,f).map(e=>` [${e}]`).join(""),I=m?` "${m}"`:"";return r?`${h}${g} [${p}]${w}`.trimEnd():`${h}${g} [${p}]${I}${w}`.trimEnd()}function U(e,t){let r=e.label?.trim(),n=e.value?.trim();if(H(t)){if(n)return n;if(r)return r}else if(r)return r;if(n)return n;let i=e.identifier?.trim();return!i||q(i)&&("group"===t||"image"===t||"list"===t||"collection"===t)?"":i}function V(e){let t=e.replace(/XCUIElementType/gi,"").toLowerCase(),r=e.includes(".")&&(e.startsWith("android.")||e.startsWith("androidx.")||e.startsWith("com."));switch(t.includes(".")&&(t=t.replace(/^android\.widget\./,"").replace(/^android\.view\./,"").replace(/^android\.webkit\./,"").replace(/^androidx\./,"").replace(/^com\.google\.android\./,"").replace(/^com\.android\./,"")),t){case"application":return"application";case"navigationbar":return"navigation-bar";case"tabbar":return"tab-bar";case"button":case"imagebutton":return"button";case"link":return"link";case"cell":return"cell";case"statictext":case"checkedtextview":return"text";case"textfield":case"edittext":return"text-field";case"textview":return r?"text":"text-view";case"textarea":return"text-view";case"switch":return"switch";case"slider":return"slider";case"image":case"imageview":return"image";case"webview":return"webview";case"framelayout":case"linearlayout":case"relativelayout":case"constraintlayout":case"viewgroup":case"view":case"group":return"group";case"listview":case"recyclerview":return"list";case"collectionview":return"collection";case"searchfield":return"search";case"segmentedcontrol":return"segmented-control";case"window":return"window";case"checkbox":return"checkbox";case"radio":return"radio";case"menuitem":return"menu-item";case"toolbar":return"toolbar";case"scrollarea":case"scrollview":case"nestedscrollview":return"scroll-area";case"table":return"table";default:return t||"element"}}function H(e){return"text-field"===e||"text-view"===e||"search"===e}function q(e){return/^[\w.]+:id\/[\w.-]+$/i.test(e)}function J(){try{let e=W();return JSON.parse(n.readFileSync(s.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}function W(){let e=s.dirname(u(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=s.join(t,"package.json");if(n.existsSync(e))return t;t=s.dirname(t)}return e}function K(e){return e?{message:e}:{}}function X(e,t){return t?{...e,message:t}:e}function Z(e){return"string"==typeof e?.message&&e.message.length>0?e.message:null}async function Q(e,t,r={}){return new Promise((n,i)=>{let o=p(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],detached:r.detached}),a="",s=r.binaryStdout?Buffer.alloc(0):void 0,l="",d=!1,u=ei(r.timeoutMs),c=u?setTimeout(()=>{d=!0,o.kill("SIGKILL")},u):null;r.binaryStdout||o.stdout.setEncoding("utf8"),o.stderr.setEncoding("utf8"),void 0!==r.stdin&&o.stdin.write(r.stdin),o.stdin.end(),o.stdout.on("data",e=>{r.binaryStdout?s=Buffer.concat([s??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]):a+=e}),o.stderr.on("data",e=>{l+=e}),o.on("error",r=>{(c&&clearTimeout(c),"ENOENT"===r.code)?i(new E("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):i(new E("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),o.on("close",o=>{c&&clearTimeout(c);let p=o??1;d&&u?i(new E("COMMAND_FAILED",`${e} timed out after ${u}ms`,{cmd:e,args:t,stdout:a,stderr:l,exitCode:p,timeoutMs:u})):0===p||r.allowFailure?n({stdout:a,stderr:l,exitCode:p,stdoutBuffer:s}):i(new E("COMMAND_FAILED",`${e} exited with code ${p}`,{cmd:e,args:t,stdout:a,stderr:l,exitCode:p,processExitError:!0}))})})}async function Y(e){try{var t;let{shell:r,args:n}=(t=e,"win32"===process.platform?{shell:"cmd.exe",args:["/c","where",t]}:{shell:"bash",args:["-lc",`command -v ${t}`]}),i=await Q(r,n,{allowFailure:!0});return 0===i.exitCode&&i.stdout.trim().length>0}catch{return!1}}function ee(e,t,r={}){let n=f(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],encoding:r.binaryStdout?void 0:"utf8",input:r.stdin,timeout:ei(r.timeoutMs)});if(n.error){let i=n.error.code;if("ETIMEDOUT"===i)throw new E("COMMAND_FAILED",`${e} timed out after ${ei(r.timeoutMs)}ms`,{cmd:e,args:t,timeoutMs:ei(r.timeoutMs)},n.error);if("ENOENT"===i)throw new E("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},n.error);throw new E("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},n.error)}let i=r.binaryStdout?Buffer.isBuffer(n.stdout)?n.stdout:Buffer.from(n.stdout??""):void 0,o=r.binaryStdout?"":"string"==typeof n.stdout?n.stdout:(n.stdout??"").toString(),a="string"==typeof n.stderr?n.stderr:(n.stderr??"").toString(),s=n.status??1;if(0!==s&&!r.allowFailure)throw new E("COMMAND_FAILED",`${e} exited with code ${s}`,{cmd:e,args:t,stdout:o,stderr:a,exitCode:s,processExitError:!0});return{stdout:o,stderr:a,exitCode:s,stdoutBuffer:i}}function et(e,t,r={}){p(e,t,{cwd:r.cwd,env:r.env,stdio:"ignore",detached:!0}).unref()}async function er(e,t,r={}){return new Promise((n,i)=>{let o=p(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],detached:r.detached});r.onSpawn?.(o);let a="",s="",l=r.binaryStdout?Buffer.alloc(0):void 0;r.binaryStdout||o.stdout.setEncoding("utf8"),o.stderr.setEncoding("utf8"),void 0!==r.stdin&&o.stdin.write(r.stdin),o.stdin.end(),o.stdout.on("data",e=>{if(r.binaryStdout){l=Buffer.concat([l??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]);return}let t=String(e);a+=t,r.onStdoutChunk?.(t)}),o.stderr.on("data",e=>{let t=String(e);s+=t,r.onStderrChunk?.(t)}),o.on("error",r=>{"ENOENT"===r.code?i(new E("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):i(new E("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),o.on("close",o=>{let d=o??1;0===d||r.allowFailure?n({stdout:a,stderr:s,exitCode:d,stdoutBuffer:l}):i(new E("COMMAND_FAILED",`${e} exited with code ${d}`,{cmd:e,args:t,stdout:a,stderr:s,exitCode:d,processExitError:!0}))})})}function en(e,t,r={}){let n=p(e,t,{cwd:r.cwd,env:r.env,stdio:["ignore","pipe","pipe"],detached:r.detached}),i="",o="";n.stdout.setEncoding("utf8"),n.stderr.setEncoding("utf8"),n.stdout.on("data",e=>{i+=e}),n.stderr.on("data",e=>{o+=e});let a=new Promise((a,s)=>{n.on("error",r=>{"ENOENT"===r.code?s(new E("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):s(new E("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),n.on("close",n=>{let l=n??1;0===l||r.allowFailure?a({stdout:i,stderr:o,exitCode:l}):s(new E("COMMAND_FAILED",`${e} exited with code ${l}`,{cmd:e,args:t,stdout:i,stderr:o,exitCode:l,processExitError:!0}))})});return{child:n,wait:a}}function ei(e){if(!Number.isFinite(e))return;let t=Math.floor(e);if(!(t<=0))return t}let eo=[/(^|[\/\s"'=])dist\/src\/daemon\.js($|[\s"'])/,/(^|[\/\s"'=])src\/daemon\.ts($|[\s"'])/];function ea(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function es(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=ee("ps",["-p",String(e),"-o","lstart="],{allowFailure:!0,timeoutMs:1e3});if(0!==t.exitCode)return null;let r=t.stdout.trim();return r.length>0?r:null}catch{return null}}function el(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=ee("ps",["-p",String(e),"-o","command="],{allowFailure:!0,timeoutMs:1e3});if(0!==t.exitCode)return null;let r=t.stdout.trim();return r.length>0?r:null}catch{return null}}function ed(e,t){let r;if(!ea(e))return!1;if(t){let r=es(e);if(!r||r!==t)return!1}let n=el(e);return!!n&&!!(r=n.toLowerCase().replaceAll("\\","/")).includes("agent-device")&&eo.some(e=>e.test(r))}function eu(e,t){try{return process.kill(e,t),!0}catch(t){let e=t.code;if("ESRCH"===e||"EPERM"===e)return!1;throw t}}async function ec(e,t){if(!ea(e))return!0;let r=Date.now();for(;Date.now()-r<t;)if(await new Promise(e=>setTimeout(e,50)),!ea(e))return!0;return!ea(e)}async function ep(e,t){!ed(e,t.expectedStartTime)||!eu(e,"SIGTERM")||await ec(e,t.termTimeoutMs)||eu(e,"SIGKILL")&&await ec(e,t.killTimeoutMs)}function ef(e){return e?.HOME?.trim()||a.homedir()}function em(e,t={}){return"~"===e?ef(t.env):e.startsWith("~/")?s.join(ef(t.env),e.slice(2)):e}function eh(e,t={}){let r=em(e,t);return s.isAbsolute(r)?r:s.resolve(t.cwd??process.cwd(),r)}function eg(e){let t,r=(t=(e??"").trim())?eh(t):s.join(em("~"),".agent-device");return{baseDir:r,infoPath:s.join(r,"daemon.json"),lockPath:s.join(r,"daemon.lock"),logPath:s.join(r,"daemon.log"),sessionsDir:s.join(r,"sessions")}}function ew(e){let t=(e??"").trim().toLowerCase();return"http"===t?"http":"dual"===t?"dual":"socket"}function eI(e){let t=(e??"").trim().toLowerCase();return"auto"===t?"auto":"socket"===t?"socket":"http"===t?"http":"auto"}function eS(e){return"tenant"===(e??"").trim().toLowerCase()?"tenant":"none"}function ev(e){if(!e)return;let t=e.trim();if(t&&/^[a-zA-Z0-9._-]{1,128}$/.test(t))return t}let ey=100,eA=new Set(["batch","replay"]),eb=new Set(["command","positionals","flags","runtime"]);function eE(e){let t;try{t=JSON.parse(e)}catch{throw new E("INVALID_ARGS","Batch steps must be valid JSON.")}if(!Array.isArray(t)||0===t.length)throw new E("INVALID_ARGS","Batch steps must be a non-empty JSON array.");return t}function e$(e,t){if(!Array.isArray(e)||0===e.length)throw new E("INVALID_ARGS","batch requires a non-empty batchSteps array.");if(e.length>t)throw new E("INVALID_ARGS",`batch has ${e.length} steps; max allowed is ${t}.`);let r=[];for(let t=0;t<e.length;t+=1){let n=e[t];if(!n||"object"!=typeof n)throw new E("INVALID_ARGS",`Invalid batch step at index ${t}.`);let i=Object.keys(n).filter(e=>!eb.has(e));if(i.length>0){let e=i.map(e=>`"${e}"`).join(", ");throw new E("INVALID_ARGS",`Batch step ${t+1} has unknown field(s): ${e}. Allowed fields: command, positionals, flags, runtime.`)}let o="string"==typeof n.command?n.command.trim().toLowerCase():"";if(!o)throw new E("INVALID_ARGS",`Batch step ${t+1} requires command.`);if(eA.has(o))throw new E("INVALID_ARGS",`Batch step ${t+1} cannot run ${o}.`);if(void 0!==n.positionals&&!Array.isArray(n.positionals))throw new E("INVALID_ARGS",`Batch step ${t+1} positionals must be an array.`);let a=n.positionals??[];if(a.some(e=>"string"!=typeof e))throw new E("INVALID_ARGS",`Batch step ${t+1} positionals must contain only strings.`);if(void 0!==n.flags&&("object"!=typeof n.flags||Array.isArray(n.flags)||!n.flags))throw new E("INVALID_ARGS",`Batch step ${t+1} flags must be an object.`);if(void 0!==n.runtime&&("object"!=typeof n.runtime||Array.isArray(n.runtime)||!n.runtime))throw new E("INVALID_ARGS",`Batch step ${t+1} runtime must be an object.`);r.push({command:o,positionals:a,flags:n.flags??{},runtime:n.runtime})}return r}function eN(e){let t=e.appId??e.bundleId??e.packageName;return{session:e.session,appId:t,appBundleId:e.bundleId,package:e.packageName}}function eD(e,t,r){return{deviceId:t,deviceName:r,..."android"===e?{serial:t}:"ios"===e?{udid:t}:{}}}function ex(e,t={}){let r=t.includeAndroidSerial??!0;return{platform:e.platform,target:e.target,device:e.name,id:e.id,..."ios"===e.platform?{device_udid:e.ios?.udid??e.id,ios_simulator_device_set:e.ios?.simulatorSetPath??null}:{},..."android"===e.platform&&r?{serial:e.android?.serial??e.id}:{}}}function e_(e){return{name:e.name,...ex(e.device,{includeAndroidSerial:!1}),createdAt:e.createdAt}}function eT(e){return{platform:e.platform,id:e.id,name:e.name,kind:e.kind,target:e.target,..."boolean"==typeof e.booted?{booted:e.booted}:{}}}function eL(e){let t=e.created?"Created":"Reused",r=e.booted?" (booted)":"";return X({udid:e.udid,device:e.device,runtime:e.runtime,ios_simulator_device_set:e.iosSimulatorDeviceSet??null,created:e.created,booted:e.booted},`${t}: ${e.device} ${e.udid}${r}`)}function eO(e){return e.bundleId??e.package??e.app}function eM(e){return X({app:e.app,appPath:e.appPath,platform:e.platform,...e.appId?{appId:e.appId}:{},...e.bundleId?{bundleId:e.bundleId}:{},...e.package?{package:e.package}:{}},`Installed: ${eO(e)}`)}function eC(e){return e.appName??e.bundleId??e.packageName??e.launchTarget}function ek(e){return X({launchTarget:e.launchTarget,...e.appName?{appName:e.appName}:{},...e.appId?{appId:e.appId}:{},...e.bundleId?{bundleId:e.bundleId}:{},...e.packageName?{package:e.packageName}:{},...e.installablePath?{installablePath:e.installablePath}:{},...e.archivePath?{archivePath:e.archivePath}:{},...e.materializationId?{materializationId:e.materializationId}:{},...e.materializationExpiresAt?{materializationExpiresAt:e.materializationExpiresAt}:{}},`Installed: ${eC(e)}`)}function eP(e){let t=e.appName??e.appBundleId??e.session;return X({session:e.session,...e.appName?{appName:e.appName}:{},...e.appBundleId?{appBundleId:e.appBundleId}:{},...e.startup?{startup:e.startup}:{},...e.runtime?{runtime:e.runtime}:{},...e.device?ex(e.device):{}},t?`Opened: ${t}`:"Opened")}function eR(e){return{session:e.session,...e.shutdown?{shutdown:e.shutdown}:{},...K(e.session?`Closed: ${e.session}`:"Closed")}}function eF(e){return{nodes:e.nodes,truncated:e.truncated,...e.appName?{appName:e.appName}:{},...e.appBundleId?{appBundleId:e.appBundleId}:{}}}function eB(e,t){try{return l.sync.read(e)}catch(e){throw new E("COMMAND_FAILED",`Failed to decode ${t} as PNG`,{label:t,reason:e instanceof Error?e.message:String(e)})}}export{TextDecoder,styleText}from"node:util";export{default as node_net}from"node:net";export{default as node_http}from"node:http";export{default as node_https}from"node:https";export{E as AppError,ey as DEFAULT_BATCH_MAX_STEPS,l as PNG,P as SESSION_SURFACES,k as SETTINGS_INVALID_ARGS_MESSAGE,C as SETTINGS_USAGE_OVERRIDE,$ as asAppError,eN as buildAppIdentifiers,eD as buildDeviceIdentifiers,G as buildSnapshotDisplayLines,r as createHash,w as createRequestId,eB as decodePng,U as displayLabel,v as emitDiagnostic,i as existsSync,em as expandUserHomePath,d as external_node_url_URL,F as extractReadableText,u as fileURLToPath,W as findProjectRoot,A as flushDiagnosticsToSessionFile,V as formatRole,z as formatSnapshotLine,S as getDiagnosticsMeta,ed as isAgentDeviceDaemonProcess,ea as isProcessAlive,t as node_crypto,n as node_fs,a as node_os,s as node_path,N as normalizeError,ev as normalizeTenantId,eE as parseBatchStepsJson,R as parseSessionSurface,c as pathToFileURL,o as promises,Z as readCommandMessage,el as readProcessCommand,es as readProcessStartTime,J as readVersion,eg as resolveDaemonPaths,ew as resolveDaemonServerMode,eI as resolveDaemonTransportPreference,eO as resolveDeployResultTarget,eC as resolveInstallFromSourceResultTarget,eS as resolveSessionIsolationMode,eh as resolveUserPath,Q as runCmd,en as runCmdBackground,et as runCmdDetached,er as runCmdStreaming,ee as runCmdSync,eR as serializeCloseResult,eM as serializeDeployResult,eT as serializeDevice,eL as serializeEnsureSimulatorResult,ek as serializeInstallFromSourceResult,eP as serializeOpenResult,e_ as serializeSessionListEntry,eF as serializeSnapshotResult,p as spawn,ep as stopProcessForTakeover,K as successText,e$ as validateAndNormalizeBatchSteps,Y as whichCmd,y as withDiagnosticTimer,I as withDiagnosticsScope,X as withSuccessText};