agent-device 0.14.8 → 0.14.9

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 (32) hide show
  1. package/README.md +1 -2
  2. package/android-snapshot-helper/README.md +4 -2
  3. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.14.8.apk → agent-device-android-snapshot-helper-0.14.9.apk} +0 -0
  4. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.9.apk.sha256 +1 -0
  5. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.14.8.manifest.json → agent-device-android-snapshot-helper-0.14.9.manifest.json} +6 -6
  6. package/dist/src/6108.js +17 -17
  7. package/dist/src/7462.js +1 -1
  8. package/dist/src/9542.js +1 -1
  9. package/dist/src/9639.js +2 -2
  10. package/dist/src/9818.js +1 -1
  11. package/dist/src/android-adb.d.ts +11 -2
  12. package/dist/src/android-snapshot-helper.d.ts +12 -2
  13. package/dist/src/cli.js +46 -46
  14. package/dist/src/command-schema.js +1 -0
  15. package/dist/src/contracts.d.ts +1 -0
  16. package/dist/src/finders.d.ts +1 -0
  17. package/dist/src/index.d.ts +6 -0
  18. package/dist/src/internal/daemon.js +20 -20
  19. package/dist/src/selectors.d.ts +1 -0
  20. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +86 -13
  21. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +160 -93
  22. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +1 -0
  23. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +3 -0
  24. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +15 -0
  25. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +1 -0
  26. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +185 -0
  27. package/package.json +1 -1
  28. package/server.json +3 -3
  29. package/skills/agent-device/SKILL.md +11 -1
  30. package/skills/dogfood/SKILL.md +3 -1
  31. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.8.apk.sha256 +0 -1
  32. package/skills/react-devtools/SKILL.md +0 -48
@@ -28,6 +28,7 @@ extension RunnerTests {
28
28
  let identifier: String
29
29
  let valueText: String?
30
30
  let hittable: Bool
31
+ let focused: Bool
31
32
  let visible: Bool
32
33
  }
33
34
 
@@ -341,6 +342,7 @@ extension RunnerTests {
341
342
  identifier: identifier,
342
343
  valueText: valueText,
343
344
  hittable: computedSnapshotHittable(snapshot, viewport: context.viewport, laterNodes: laterNodes),
345
+ focused: snapshotHasFocus(snapshot),
344
346
  visible: isVisibleInViewport(snapshot.frame, context.viewport)
345
347
  )
346
348
  }
@@ -360,6 +362,7 @@ extension RunnerTests {
360
362
  value: evaluation.valueText,
361
363
  rect: snapshotRect(from: snapshot.frame),
362
364
  enabled: snapshot.isEnabled,
365
+ focused: evaluation.focused ? true : nil,
363
366
  hittable: evaluation.hittable,
364
367
  depth: depth,
365
368
  parentIndex: parentIndex,
@@ -525,6 +528,7 @@ extension RunnerTests {
525
528
  value: node.value,
526
529
  rect: node.rect,
527
530
  enabled: node.enabled,
531
+ focused: node.focused,
528
532
  hittable: node.hittable,
529
533
  depth: depth,
530
534
  parentIndex: parentIndex,
@@ -575,6 +579,7 @@ extension RunnerTests {
575
579
  value: valueText,
576
580
  rect: snapshotRect(from: frame),
577
581
  enabled: element.isEnabled,
582
+ focused: elementHasFocus(element) ? true : nil,
578
583
  hittable: element.isHittable,
579
584
  depth: 0,
580
585
  parentIndex: nil,
@@ -592,6 +597,16 @@ extension RunnerTests {
592
597
  return node
593
598
  }
594
599
 
600
+ private func snapshotHasFocus(_ snapshot: XCUIElementSnapshot) -> Bool {
601
+ var focused = false
602
+ _ = RunnerObjCExceptionCatcher.catchException({
603
+ if let value = (snapshot as! NSObject).value(forKey: "hasFocus") as? Bool {
604
+ focused = value
605
+ }
606
+ })
607
+ return focused
608
+ }
609
+
595
610
  private func shouldExpandCollapsedTabContainer(_ snapshot: XCUIElementSnapshot) -> Bool {
596
611
  let frame = snapshot.frame
597
612
  if frame.isNull || frame.isEmpty { return false }
@@ -186,6 +186,7 @@ extension RunnerTests {
186
186
  value: nil,
187
187
  rect: snapshotRect(from: element.frame),
188
188
  enabled: element.isEnabled,
189
+ focused: elementHasFocus(element) ? true : nil,
189
190
  hittable: hittableOverride ?? element.isHittable,
190
191
  depth: depth,
191
192
  parentIndex: nil,
@@ -0,0 +1,185 @@
1
+ import XCTest
2
+
3
+ enum RunnerInteractionOutcome {
4
+ case performed
5
+ case unsupported(String)
6
+ }
7
+
8
+ enum TvRemoteButton {
9
+ case select
10
+ case menu
11
+ case home
12
+ case up
13
+ case down
14
+ case left
15
+ case right
16
+ }
17
+
18
+ extension RunnerTests {
19
+ func resolveTvRemoteDoublePressDelay() -> TimeInterval {
20
+ guard
21
+ let raw = ProcessInfo.processInfo.environment["AGENT_DEVICE_TV_REMOTE_DOUBLE_PRESS_DELAY_MS"],
22
+ !raw.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
23
+ else {
24
+ return tvRemoteDoublePressDelayDefault
25
+ }
26
+ guard let parsedMs = Double(raw), parsedMs >= 0 else {
27
+ return tvRemoteDoublePressDelayDefault
28
+ }
29
+ return min(parsedMs, 1000) / 1000.0
30
+ }
31
+
32
+ @discardableResult
33
+ func pressTvRemote(_ button: TvRemoteButton, duration: TimeInterval? = nil) -> Bool {
34
+ #if os(tvOS)
35
+ let remoteButton = xcuiRemoteButton(button)
36
+ if let duration, duration > 0 {
37
+ XCUIRemote.shared.press(remoteButton, forDuration: duration)
38
+ } else {
39
+ XCUIRemote.shared.press(remoteButton)
40
+ }
41
+ return true
42
+ #else
43
+ return false
44
+ #endif
45
+ }
46
+
47
+ func tvRemoteButton(from raw: String?) -> TvRemoteButton? {
48
+ switch raw?.lowercased() {
49
+ case "select":
50
+ return .select
51
+ case "menu":
52
+ return .menu
53
+ case "home":
54
+ return .home
55
+ case "up":
56
+ return .up
57
+ case "down":
58
+ return .down
59
+ case "left":
60
+ return .left
61
+ case "right":
62
+ return .right
63
+ default:
64
+ return nil
65
+ }
66
+ }
67
+
68
+ func elementHasFocus(_ element: XCUIElement) -> Bool {
69
+ var focused = false
70
+ _ = RunnerObjCExceptionCatcher.catchException({
71
+ if let value = (element as NSObject).value(forKey: "hasFocus") as? Bool {
72
+ focused = value
73
+ }
74
+ })
75
+ return focused
76
+ }
77
+
78
+ func activateElement(app: XCUIApplication, element: XCUIElement, action: String) -> RunnerInteractionOutcome {
79
+ if let outcome = selectFocusedTvElement(app: app, element: element, action: action) {
80
+ return outcome
81
+ }
82
+ return performElementTap(element)
83
+ }
84
+
85
+ func selectFocusedTvElement(app: XCUIApplication, point: CGPoint, action: String) -> RunnerInteractionOutcome? {
86
+ #if os(tvOS)
87
+ guard let focused = focusedTvElement(app: app), !focused.frame.isEmpty, focused.frame.contains(point) else {
88
+ return .unsupported("\(action) is supported on tvOS only when the requested point is inside the focused element")
89
+ }
90
+ _ = pressTvRemote(.select)
91
+ return .performed
92
+ #else
93
+ return nil
94
+ #endif
95
+ }
96
+
97
+ func longSelectFocusedTvElement(app: XCUIApplication, point: CGPoint, duration: TimeInterval) -> RunnerInteractionOutcome? {
98
+ #if os(tvOS)
99
+ guard let focused = focusedTvElement(app: app), !focused.frame.isEmpty, focused.frame.contains(point) else {
100
+ return .unsupported("long press is supported on tvOS only when the requested point is inside the focused element")
101
+ }
102
+ _ = pressTvRemote(.select, duration: duration)
103
+ return .performed
104
+ #else
105
+ return nil
106
+ #endif
107
+ }
108
+
109
+ private func performElementTap(_ element: XCUIElement) -> RunnerInteractionOutcome {
110
+ #if os(tvOS)
111
+ return .unsupported("element tap is not supported on tvOS; move focus with swipe or scroll, then select the focused element")
112
+ #else
113
+ element.tap()
114
+ return .performed
115
+ #endif
116
+ }
117
+
118
+ private func selectFocusedTvElement(app: XCUIApplication, element: XCUIElement, action: String) -> RunnerInteractionOutcome? {
119
+ #if os(tvOS)
120
+ guard tvFocusedElementMatches(app: app, target: element) else {
121
+ return .unsupported("\(action) is supported on tvOS only when the requested element is focused")
122
+ }
123
+ _ = pressTvRemote(.select)
124
+ return .performed
125
+ #else
126
+ return nil
127
+ #endif
128
+ }
129
+
130
+ private func tvFocusedElementMatches(app: XCUIApplication, target: XCUIElement) -> Bool {
131
+ #if os(tvOS)
132
+ if target.hasFocus {
133
+ return true
134
+ }
135
+ guard let focused = focusedTvElement(app: app) else {
136
+ return false
137
+ }
138
+ let targetFrame = target.frame
139
+ let focusedFrame = focused.frame
140
+ guard !targetFrame.isEmpty && !focusedFrame.isEmpty else {
141
+ return false
142
+ }
143
+ let focusedCenter = CGPoint(x: focusedFrame.midX, y: focusedFrame.midY)
144
+ let targetCenter = CGPoint(x: targetFrame.midX, y: targetFrame.midY)
145
+ return targetFrame.contains(focusedCenter)
146
+ || focusedFrame.contains(targetCenter)
147
+ || targetFrame.intersects(focusedFrame)
148
+ #else
149
+ return false
150
+ #endif
151
+ }
152
+
153
+ private func focusedTvElement(app: XCUIApplication) -> XCUIElement? {
154
+ #if os(tvOS)
155
+ let focused = app
156
+ .descendants(matching: .any)
157
+ .matching(NSPredicate(format: "hasFocus == true"))
158
+ .firstMatch
159
+ return focused.exists ? focused : nil
160
+ #else
161
+ return nil
162
+ #endif
163
+ }
164
+
165
+ #if os(tvOS)
166
+ private func xcuiRemoteButton(_ button: TvRemoteButton) -> XCUIRemote.Button {
167
+ switch button {
168
+ case .select:
169
+ return .select
170
+ case .menu:
171
+ return .menu
172
+ case .home:
173
+ return .home
174
+ case .up:
175
+ return .up
176
+ case .down:
177
+ return .down
178
+ case .left:
179
+ return .left
180
+ case .right:
181
+ return .right
182
+ }
183
+ }
184
+ #endif
185
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.14.8",
3
+ "version": "0.14.9",
4
4
  "description": "Agent-native CLI for AI mobile testing and app automation across iOS, Android, tvOS, Android TV, macOS, and Linux.",
5
5
  "mcpName": "io.github.callstackincubator/agent-device",
6
6
  "license": "MIT",
package/server.json CHANGED
@@ -2,17 +2,17 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.callstackincubator/agent-device",
4
4
  "title": "agent-device",
5
- "description": "Official MCP discovery router for the agent-device CLI. It exposes status, install guidance, version-matched CLI help, prompts, and resources without exposing device automation over MCP.",
5
+ "description": "Discovery router for the agent-device CLI with status, install, help, prompts, and resources.",
6
6
  "repository": {
7
7
  "url": "https://github.com/callstackincubator/agent-device",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.14.8",
10
+ "version": "0.14.9",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "agent-device",
15
- "version": "0.14.8",
15
+ "version": "0.14.9",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
@@ -11,7 +11,15 @@ Router only. Private setup before using this skill:
11
11
  agent-device --version
12
12
  ```
13
13
 
14
- Require `agent-device >= 0.14.0`; older CLIs lack these help topics. If older, run `npm install -g agent-device@latest`, recheck, then continue. If you cannot upgrade, stop and tell the user. Do not include version/upgrade commands in final plans.
14
+ If that fails but the user installed `agent-device` globally, try the user's login shell before using `npx`:
15
+
16
+ ```bash
17
+ zsh -lic 'command -v agent-device'
18
+ ```
19
+
20
+ If it prints a path, run that absolute path instead of `agent-device`. For non-zsh shells, use the equivalent login-shell command.
21
+
22
+ Require `agent-device >= 0.14.0`; older CLIs lack these help topics. If older, stop and tell the user to upgrade the trusted install or approve an exact-version npm command. Do not run `npm install -g agent-device@latest` or `npx -y agent-device@latest` autonomously, and do not include version/upgrade commands in final plans.
15
23
 
16
24
  Before your first agent-device command or plan, read the version-matched CLI guide:
17
25
 
@@ -32,3 +40,5 @@ agent-device help dogfood
32
40
  Default loop: `open -> snapshot/-i -> get/is/find or press/fill/scroll/wait -> verify -> close`.
33
41
 
34
42
  Use this skill only to route into version-matched CLI help. Let `help workflow` provide exact command shapes, platform limits, and current workflow guidance.
43
+
44
+ For precise location workflows, read the installed `settings` help before planning so coordinate support and platform limits come from the active CLI version.
@@ -12,7 +12,9 @@ Router for exploratory QA. Private setup before using this skill:
12
12
  agent-device --version
13
13
  ```
14
14
 
15
- Require `agent-device >= 0.14.0`; older CLIs lack these help topics. If older, run `npm install -g agent-device@latest`, recheck, then continue. If you cannot upgrade, stop and tell the user. Do not include version/upgrade commands in final plans.
15
+ If that fails, stop and tell the user to expose a trusted `agent-device` binary on PATH or approve an exact-version npm command. This skill intentionally keeps allowed tools restricted to `agent-device` and `npx agent-device`.
16
+
17
+ Require `agent-device >= 0.14.0`; older CLIs lack these help topics. If older, stop and tell the user to upgrade the trusted install or approve an exact-version npm command. Do not run `npm install -g agent-device@latest` or `npx -y agent-device@latest` autonomously, and do not include version/upgrade commands in final plans.
16
18
 
17
19
  Read current CLI guidance:
18
20
 
@@ -1 +0,0 @@
1
- 0669bbeb4c3b549a9084dc3d75e4afa8f055408424c40c2fd9db4b75eb1f6e53 agent-device-android-snapshot-helper-0.14.8.apk
@@ -1,48 +0,0 @@
1
- ---
2
- name: react-devtools
3
- description: Inspect and profile React Native component trees from agent-device. Use for React Native performance, profiling, props, state, hooks, render causes, slow components, excessive rerenders, or questions like why a component rerendered.
4
- ---
5
-
6
- # react-devtools
7
-
8
- Router for React Native internals. Private setup before using this skill:
9
-
10
- ```bash
11
- agent-device --version
12
- ```
13
-
14
- Require `agent-device >= 0.14.0`; older CLIs lack these help topics. If older, run `npm install -g agent-device@latest`, recheck, then continue. If you cannot upgrade, stop and tell the user. Do not include version/upgrade commands in final plans.
15
-
16
- Read current CLI guidance:
17
-
18
- ```bash
19
- agent-device help react-devtools
20
- ```
21
-
22
- Use `agent-device react-devtools ...` for component tree, props, state, hooks, render ownership, performance profiling, slow components, or rerenders. It dynamically runs pinned `agent-react-devtools@0.4.0`. Use normal `agent-device` commands for visible UI, refs, screenshots, logs, network, or device-level perf.
23
-
24
- Core loop:
25
-
26
- ```bash
27
- agent-device react-devtools status
28
- agent-device react-devtools wait --connected
29
- agent-device react-devtools get tree --depth 3
30
- agent-device react-devtools profile start
31
- # perform the interaction with normal agent-device commands
32
- agent-device react-devtools profile stop
33
- agent-device react-devtools profile slow --limit 5
34
- agent-device react-devtools profile rerenders --limit 5
35
- ```
36
-
37
- Remote iOS bridge order:
38
-
39
- ```bash
40
- agent-device open <bundle-id> --platform ios --relaunch
41
- agent-device react-devtools start
42
- agent-device open <bundle-id> --platform ios --relaunch
43
- agent-device react-devtools wait --connected
44
- ```
45
-
46
- Rules:
47
-
48
- Keep reads bounded with `--depth`/`find`, treat `@c` refs as reload-local, profile only the investigated interaction, and run the same command in remote Android sessions; the CLI manages the needed local service tunnel. For remote iOS, relaunch after `react-devtools start` because React Native opens the legacy DevTools websocket during JavaScript startup.