minitap-mobile-use 3.3.0__py3-none-any.whl

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 (115) hide show
  1. minitap/mobile_use/__init__.py +0 -0
  2. minitap/mobile_use/agents/contextor/contextor.md +55 -0
  3. minitap/mobile_use/agents/contextor/contextor.py +175 -0
  4. minitap/mobile_use/agents/contextor/types.py +36 -0
  5. minitap/mobile_use/agents/cortex/cortex.md +135 -0
  6. minitap/mobile_use/agents/cortex/cortex.py +152 -0
  7. minitap/mobile_use/agents/cortex/types.py +15 -0
  8. minitap/mobile_use/agents/executor/executor.md +42 -0
  9. minitap/mobile_use/agents/executor/executor.py +87 -0
  10. minitap/mobile_use/agents/executor/tool_node.py +152 -0
  11. minitap/mobile_use/agents/hopper/hopper.md +15 -0
  12. minitap/mobile_use/agents/hopper/hopper.py +44 -0
  13. minitap/mobile_use/agents/orchestrator/human.md +12 -0
  14. minitap/mobile_use/agents/orchestrator/orchestrator.md +21 -0
  15. minitap/mobile_use/agents/orchestrator/orchestrator.py +134 -0
  16. minitap/mobile_use/agents/orchestrator/types.py +11 -0
  17. minitap/mobile_use/agents/outputter/human.md +25 -0
  18. minitap/mobile_use/agents/outputter/outputter.py +85 -0
  19. minitap/mobile_use/agents/outputter/test_outputter.py +167 -0
  20. minitap/mobile_use/agents/planner/human.md +14 -0
  21. minitap/mobile_use/agents/planner/planner.md +126 -0
  22. minitap/mobile_use/agents/planner/planner.py +101 -0
  23. minitap/mobile_use/agents/planner/types.py +51 -0
  24. minitap/mobile_use/agents/planner/utils.py +70 -0
  25. minitap/mobile_use/agents/summarizer/summarizer.py +35 -0
  26. minitap/mobile_use/agents/video_analyzer/__init__.py +5 -0
  27. minitap/mobile_use/agents/video_analyzer/human.md +5 -0
  28. minitap/mobile_use/agents/video_analyzer/video_analyzer.md +37 -0
  29. minitap/mobile_use/agents/video_analyzer/video_analyzer.py +111 -0
  30. minitap/mobile_use/clients/browserstack_client.py +477 -0
  31. minitap/mobile_use/clients/idb_client.py +429 -0
  32. minitap/mobile_use/clients/ios_client.py +332 -0
  33. minitap/mobile_use/clients/ios_client_config.py +141 -0
  34. minitap/mobile_use/clients/ui_automator_client.py +330 -0
  35. minitap/mobile_use/clients/wda_client.py +526 -0
  36. minitap/mobile_use/clients/wda_lifecycle.py +367 -0
  37. minitap/mobile_use/config.py +413 -0
  38. minitap/mobile_use/constants.py +3 -0
  39. minitap/mobile_use/context.py +106 -0
  40. minitap/mobile_use/controllers/__init__.py +0 -0
  41. minitap/mobile_use/controllers/android_controller.py +524 -0
  42. minitap/mobile_use/controllers/controller_factory.py +46 -0
  43. minitap/mobile_use/controllers/device_controller.py +182 -0
  44. minitap/mobile_use/controllers/ios_controller.py +436 -0
  45. minitap/mobile_use/controllers/platform_specific_commands_controller.py +199 -0
  46. minitap/mobile_use/controllers/types.py +106 -0
  47. minitap/mobile_use/controllers/unified_controller.py +193 -0
  48. minitap/mobile_use/graph/graph.py +160 -0
  49. minitap/mobile_use/graph/state.py +115 -0
  50. minitap/mobile_use/main.py +309 -0
  51. minitap/mobile_use/sdk/__init__.py +12 -0
  52. minitap/mobile_use/sdk/agent.py +1294 -0
  53. minitap/mobile_use/sdk/builders/__init__.py +10 -0
  54. minitap/mobile_use/sdk/builders/agent_config_builder.py +307 -0
  55. minitap/mobile_use/sdk/builders/index.py +15 -0
  56. minitap/mobile_use/sdk/builders/task_request_builder.py +236 -0
  57. minitap/mobile_use/sdk/constants.py +1 -0
  58. minitap/mobile_use/sdk/examples/README.md +83 -0
  59. minitap/mobile_use/sdk/examples/__init__.py +1 -0
  60. minitap/mobile_use/sdk/examples/app_lock_messaging.py +54 -0
  61. minitap/mobile_use/sdk/examples/platform_manual_task_example.py +67 -0
  62. minitap/mobile_use/sdk/examples/platform_minimal_example.py +48 -0
  63. minitap/mobile_use/sdk/examples/simple_photo_organizer.py +76 -0
  64. minitap/mobile_use/sdk/examples/smart_notification_assistant.py +225 -0
  65. minitap/mobile_use/sdk/examples/video_transcription_example.py +117 -0
  66. minitap/mobile_use/sdk/services/cloud_mobile.py +656 -0
  67. minitap/mobile_use/sdk/services/platform.py +434 -0
  68. minitap/mobile_use/sdk/types/__init__.py +51 -0
  69. minitap/mobile_use/sdk/types/agent.py +84 -0
  70. minitap/mobile_use/sdk/types/exceptions.py +138 -0
  71. minitap/mobile_use/sdk/types/platform.py +183 -0
  72. minitap/mobile_use/sdk/types/task.py +269 -0
  73. minitap/mobile_use/sdk/utils.py +29 -0
  74. minitap/mobile_use/services/accessibility.py +100 -0
  75. minitap/mobile_use/services/llm.py +247 -0
  76. minitap/mobile_use/services/telemetry.py +421 -0
  77. minitap/mobile_use/tools/index.py +67 -0
  78. minitap/mobile_use/tools/mobile/back.py +52 -0
  79. minitap/mobile_use/tools/mobile/erase_one_char.py +56 -0
  80. minitap/mobile_use/tools/mobile/focus_and_clear_text.py +317 -0
  81. minitap/mobile_use/tools/mobile/focus_and_input_text.py +153 -0
  82. minitap/mobile_use/tools/mobile/launch_app.py +86 -0
  83. minitap/mobile_use/tools/mobile/long_press_on.py +169 -0
  84. minitap/mobile_use/tools/mobile/open_link.py +62 -0
  85. minitap/mobile_use/tools/mobile/press_key.py +83 -0
  86. minitap/mobile_use/tools/mobile/stop_app.py +62 -0
  87. minitap/mobile_use/tools/mobile/swipe.py +156 -0
  88. minitap/mobile_use/tools/mobile/tap.py +154 -0
  89. minitap/mobile_use/tools/mobile/video_recording.py +177 -0
  90. minitap/mobile_use/tools/mobile/wait_for_delay.py +81 -0
  91. minitap/mobile_use/tools/scratchpad.py +147 -0
  92. minitap/mobile_use/tools/test_utils.py +413 -0
  93. minitap/mobile_use/tools/tool_wrapper.py +16 -0
  94. minitap/mobile_use/tools/types.py +35 -0
  95. minitap/mobile_use/tools/utils.py +336 -0
  96. minitap/mobile_use/utils/app_launch_utils.py +173 -0
  97. minitap/mobile_use/utils/cli_helpers.py +37 -0
  98. minitap/mobile_use/utils/cli_selection.py +143 -0
  99. minitap/mobile_use/utils/conversations.py +31 -0
  100. minitap/mobile_use/utils/decorators.py +124 -0
  101. minitap/mobile_use/utils/errors.py +6 -0
  102. minitap/mobile_use/utils/file.py +13 -0
  103. minitap/mobile_use/utils/logger.py +183 -0
  104. minitap/mobile_use/utils/media.py +186 -0
  105. minitap/mobile_use/utils/recorder.py +52 -0
  106. minitap/mobile_use/utils/requests_utils.py +37 -0
  107. minitap/mobile_use/utils/shell_utils.py +20 -0
  108. minitap/mobile_use/utils/test_ui_hierarchy.py +178 -0
  109. minitap/mobile_use/utils/time.py +6 -0
  110. minitap/mobile_use/utils/ui_hierarchy.py +132 -0
  111. minitap/mobile_use/utils/video.py +281 -0
  112. minitap_mobile_use-3.3.0.dist-info/METADATA +329 -0
  113. minitap_mobile_use-3.3.0.dist-info/RECORD +115 -0
  114. minitap_mobile_use-3.3.0.dist-info/WHEEL +4 -0
  115. minitap_mobile_use-3.3.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,367 @@
1
+ import asyncio
2
+ import re
3
+ import shutil
4
+ import subprocess
5
+
6
+ import httpx
7
+
8
+ from minitap.mobile_use.utils.logger import get_logger
9
+
10
+ logger = get_logger(__name__)
11
+
12
+ # WDA bundle ID (default for Appium's WDA)
13
+ WDA_BUNDLE_ID = "com.facebook.WebDriverAgentRunner.xctrunner"
14
+ WDA_BUNDLE_ID_ALT = "com.apple.test.WebDriverAgentRunner-Runner"
15
+
16
+ # Default ports
17
+ DEFAULT_WDA_PORT = 8100
18
+ DEFAULT_DEVICE_WDA_PORT = 8100
19
+
20
+
21
+ async def check_wda_running(port: int = DEFAULT_WDA_PORT, timeout: float = 5.0) -> bool:
22
+ """Check if WDA server is running and responding.
23
+
24
+ Args:
25
+ port: WDA port to check (default: 8100)
26
+ timeout: Request timeout in seconds
27
+
28
+ Returns:
29
+ True if WDA is running and responding, False otherwise
30
+ """
31
+ url = f"http://localhost:{port}/status"
32
+ try:
33
+ async with httpx.AsyncClient(timeout=timeout) as client:
34
+ response = await client.get(url)
35
+ if response.status_code == 200:
36
+ data = response.json()
37
+ # WDA returns a status object with sessionId
38
+ if "value" in data or "sessionId" in data:
39
+ logger.debug(f"WDA is running on port {port}")
40
+ return True
41
+ return False
42
+ except httpx.ConnectError:
43
+ logger.debug(f"WDA not responding on port {port} (connection refused)")
44
+ return False
45
+ except httpx.TimeoutException:
46
+ logger.debug(f"WDA not responding on port {port} (timeout)")
47
+ return False
48
+ except Exception as e:
49
+ logger.debug(f"Error checking WDA status: {e}")
50
+ return False
51
+
52
+
53
+ def check_iproxy_running(port: int = DEFAULT_WDA_PORT) -> bool:
54
+ """Check if iproxy is running for the specified port.
55
+
56
+ Args:
57
+ port: Local port to check (default: 8100)
58
+
59
+ Returns:
60
+ True if iproxy is running for this port, False otherwise
61
+ """
62
+ try:
63
+ # Check for iproxy process listening on the port
64
+ result = subprocess.run(
65
+ ["pgrep", "-f", f"iproxy.*{port}"],
66
+ capture_output=True,
67
+ text=True,
68
+ timeout=5,
69
+ )
70
+ return result.returncode == 0
71
+ except Exception as e:
72
+ logger.debug(f"Error checking iproxy status: {e}")
73
+ return False
74
+
75
+
76
+ def get_iproxy_pid(port: int = DEFAULT_WDA_PORT) -> int | None:
77
+ """Get the PID of iproxy process for the specified port.
78
+
79
+ Args:
80
+ port: Local port to check
81
+
82
+ Returns:
83
+ PID if found, None otherwise
84
+ """
85
+ try:
86
+ result = subprocess.run(
87
+ ["pgrep", "-f", f"iproxy.*{port}"],
88
+ capture_output=True,
89
+ text=True,
90
+ timeout=5,
91
+ )
92
+ if result.returncode == 0 and result.stdout.strip():
93
+ return int(result.stdout.strip().split()[0])
94
+ return None
95
+ except Exception:
96
+ return None
97
+
98
+
99
+ async def start_iproxy(
100
+ local_port: int = DEFAULT_WDA_PORT,
101
+ device_port: int = DEFAULT_DEVICE_WDA_PORT,
102
+ udid: str | None = None,
103
+ ) -> subprocess.Popen | None:
104
+ """Start iproxy for port forwarding.
105
+
106
+ Args:
107
+ local_port: Local port to forward to (default: 8100)
108
+ device_port: Device port to forward from (default: 8100)
109
+ udid: Optional device UDID (uses first device if not specified)
110
+
111
+ Returns:
112
+ Popen process object if started successfully, None otherwise
113
+ """
114
+ if not shutil.which("iproxy"):
115
+ logger.error("iproxy not found. Install libimobiledevice:\n brew install libimobiledevice")
116
+ return None
117
+
118
+ # Check if already running
119
+ if check_iproxy_running(local_port):
120
+ logger.info(f"iproxy already running on port {local_port}")
121
+ return None
122
+
123
+ try:
124
+ cmd = ["iproxy", str(local_port), str(device_port)]
125
+ if udid:
126
+ cmd.extend(["-u", udid])
127
+
128
+ logger.info(f"Starting iproxy: {' '.join(cmd)}")
129
+ process = subprocess.Popen(
130
+ cmd,
131
+ stdout=subprocess.PIPE,
132
+ stderr=subprocess.PIPE,
133
+ start_new_session=True,
134
+ )
135
+
136
+ # Give it a moment to start
137
+ await asyncio.sleep(0.5)
138
+
139
+ # Check if it's still running
140
+ if process.poll() is not None:
141
+ stderr = process.stderr.read().decode() if process.stderr else ""
142
+ logger.error(f"iproxy failed to start: {stderr}")
143
+ return None
144
+
145
+ logger.info(f"iproxy started successfully (PID: {process.pid})")
146
+ return process
147
+
148
+ except Exception as e:
149
+ logger.error(f"Failed to start iproxy: {e}")
150
+ return None
151
+
152
+
153
+ def find_wda_project() -> str | None:
154
+ """Find WebDriverAgent.xcodeproj in common locations.
155
+
156
+ Returns:
157
+ Path to WebDriverAgent.xcodeproj or None if not found
158
+ """
159
+ import os
160
+ from pathlib import Path
161
+
162
+ # Common locations to search
163
+ search_paths = [
164
+ # Current directory
165
+ Path.cwd() / "WebDriverAgent" / "WebDriverAgent.xcodeproj",
166
+ Path.cwd() / "WebDriverAgent.xcodeproj",
167
+ # Home directory
168
+ Path.home() / "WebDriverAgent" / "WebDriverAgent.xcodeproj",
169
+ # Appium installation
170
+ Path.home()
171
+ / ".appium"
172
+ / "node_modules"
173
+ / "appium-xcuitest-driver"
174
+ / "node_modules"
175
+ / "appium-webdriveragent"
176
+ / "WebDriverAgent.xcodeproj",
177
+ # Common dev locations
178
+ Path.home() / "Developer" / "WebDriverAgent" / "WebDriverAgent.xcodeproj",
179
+ Path.home() / "Projects" / "WebDriverAgent" / "WebDriverAgent.xcodeproj",
180
+ Path(
181
+ "/usr/local/lib/node_modules/appium/node_modules/appium-webdriveragent"
182
+ "/WebDriverAgent.xcodeproj"
183
+ ),
184
+ ]
185
+
186
+ # Also check WDA_PROJECT_PATH environment variable
187
+ env_path = os.environ.get("WDA_PROJECT_PATH")
188
+ if env_path:
189
+ search_paths.insert(0, Path(env_path))
190
+
191
+ for path in search_paths:
192
+ if path.exists():
193
+ logger.debug(f"Found WDA project at: {path}")
194
+ return str(path)
195
+
196
+ return None
197
+
198
+
199
+ async def build_and_run_wda(
200
+ udid: str,
201
+ project_path: str | None = None,
202
+ timeout: float = 120.0,
203
+ ) -> subprocess.Popen | None:
204
+ """Build and run WebDriverAgent on the device using xcodebuild.
205
+
206
+ This starts xcodebuild test in the background, which builds WDA if needed
207
+ and runs it on the device.
208
+
209
+ Args:
210
+ udid: Device UDID
211
+ project_path: Path to WebDriverAgent.xcodeproj (auto-detected if None)
212
+ timeout: Build timeout in seconds (default: 120)
213
+
214
+ Returns:
215
+ Popen process object if started successfully, None otherwise
216
+ """
217
+ if not shutil.which("xcodebuild"):
218
+ logger.error("xcodebuild not found. Please install Xcode.")
219
+ return None
220
+
221
+ # Find WDA project
222
+ if project_path is None:
223
+ project_path = find_wda_project()
224
+
225
+ if project_path is None:
226
+ logger.error(
227
+ "WebDriverAgent.xcodeproj not found.\n"
228
+ "Please clone it first:\n"
229
+ " git clone https://github.com/appium/WebDriverAgent.git\n"
230
+ "Or set WDA_PROJECT_PATH environment variable."
231
+ )
232
+ return None
233
+
234
+ logger.info(f"Building and running WDA from: {project_path}")
235
+ logger.info(f"Target device: {udid[:16]}...")
236
+ logger.info("This may take a minute on first run...")
237
+
238
+ try:
239
+ # Build and test command
240
+ cmd = [
241
+ "xcodebuild",
242
+ "test",
243
+ "-project",
244
+ project_path,
245
+ "-scheme",
246
+ "WebDriverAgentRunner",
247
+ "-destination",
248
+ f"id={udid}",
249
+ "-allowProvisioningUpdates",
250
+ ]
251
+
252
+ # Start xcodebuild in background
253
+ process = subprocess.Popen(
254
+ cmd,
255
+ stdout=subprocess.PIPE,
256
+ stderr=subprocess.STDOUT,
257
+ text=True,
258
+ start_new_session=True,
259
+ )
260
+
261
+ # Wait for WDA to start (look for "ServerURLHere" in output)
262
+ start_time = asyncio.get_event_loop().time()
263
+ server_started = False
264
+
265
+ while asyncio.get_event_loop().time() - start_time < timeout:
266
+ if process.poll() is not None:
267
+ # Process ended - check if it failed
268
+ output = process.stdout.read() if process.stdout else ""
269
+ if "BUILD FAILED" in output or "error:" in output.lower():
270
+ logger.error(f"WDA build failed:\n{output[-1000:]}")
271
+ return None
272
+ break
273
+
274
+ # Check output for server start indicator
275
+ if process.stdout:
276
+ line = process.stdout.readline()
277
+ if line:
278
+ logger.debug(f"xcodebuild: {line.strip()}")
279
+ if "ServerURLHere" in line or "WebDriverAgent" in line:
280
+ server_started = True
281
+ logger.info("WDA server starting on device...")
282
+
283
+ # If server started, give it a moment then return
284
+ if server_started:
285
+ await asyncio.sleep(3)
286
+ if process.poll() is None:
287
+ logger.info("WDA build and run started successfully")
288
+ return process
289
+
290
+ await asyncio.sleep(0.5)
291
+
292
+ if process.poll() is None:
293
+ # Still running after timeout - assume it's working
294
+ logger.info("WDA process running (build may still be in progress)")
295
+ return process
296
+
297
+ return None
298
+
299
+ except Exception as e:
300
+ logger.error(f"Failed to start xcodebuild: {e}")
301
+ return None
302
+
303
+
304
+ def get_wda_setup_instructions(udid: str) -> str:
305
+ """Get WDA setup instructions (only manual steps not handled by code)."""
306
+ return f"""WebDriverAgent Setup (device: {udid[:16]}...)
307
+
308
+ 1. Clone WebDriverAgent:
309
+ git clone https://github.com/appium/WebDriverAgent.git
310
+
311
+ 2. Configure code signing in Xcode:
312
+ - Open WebDriverAgent.xcodeproj
313
+ - Select WebDriverAgentLib target → Signing & Capabilities
314
+ - Set your Team and Bundle Identifier
315
+ - Repeat for WebDriverAgentRunner target
316
+
317
+ Build and run are handled automatically after signing is configured.
318
+ """
319
+
320
+
321
+ async def wait_for_wda(
322
+ port: int = DEFAULT_WDA_PORT,
323
+ timeout: float = 60.0,
324
+ poll_interval: float = 2.0,
325
+ ) -> bool:
326
+ """Wait for WDA to become available.
327
+
328
+ Args:
329
+ port: WDA port to check
330
+ timeout: Maximum time to wait in seconds
331
+ poll_interval: Time between checks in seconds
332
+
333
+ Returns:
334
+ True if WDA became available, False if timeout
335
+ """
336
+ logger.info(
337
+ f"Waiting for WDA on port {port} (unlock your device, password may be required) "
338
+ f"(timeout: {timeout}s)..."
339
+ )
340
+ elapsed = 0.0
341
+
342
+ while elapsed < timeout:
343
+ if await check_wda_running(port):
344
+ logger.info(f"WDA is ready on port {port}")
345
+ return True
346
+
347
+ await asyncio.sleep(poll_interval)
348
+ elapsed += poll_interval
349
+ logger.debug(f"Waiting for WDA... ({elapsed:.0f}s/{timeout:.0f}s)")
350
+
351
+ logger.error(f"Timeout waiting for WDA on port {port}")
352
+ return False
353
+
354
+
355
+ def parse_wda_port_from_url(url: str) -> int:
356
+ """Extract port number from WDA URL.
357
+
358
+ Args:
359
+ url: WDA URL (e.g., "http://localhost:8100")
360
+
361
+ Returns:
362
+ Port number (default: 8100 if not found)
363
+ """
364
+ match = re.search(r":(\d+)", url)
365
+ if match:
366
+ return int(match.group(1))
367
+ return DEFAULT_WDA_PORT