droidrun 0.3.10.dev6__py3-none-any.whl → 0.3.10.dev8__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.
@@ -0,0 +1,434 @@
1
+ """
2
+ Portal Client - Unified communication layer for DroidRun Portal app.
3
+
4
+ This module provides automatic TCP/Content Provider fallback for Portal communication.
5
+ """
6
+
7
+ import base64
8
+ import io
9
+ import json
10
+ import logging
11
+ import re
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ import requests
15
+ from adbutils import AdbDevice
16
+
17
+ logger = logging.getLogger("droidrun-tools")
18
+
19
+ PORTAL_REMOTE_PORT = 8080 # Port on device where Portal HTTP server runs
20
+
21
+
22
+ class PortalClient:
23
+ """
24
+ Unified client for DroidRun Portal communication.
25
+
26
+ Automatically handles TCP vs Content Provider fallback with the following strategy:
27
+ - On init, checks for existing port forward and reuses it
28
+ - If no forward exists, creates new one
29
+ - Tests connection and sets tcp_available flag
30
+ - All methods auto-select TCP or content provider based on availability
31
+ - No cleanup needed - forwards persist until device disconnect
32
+
33
+ Key features:
34
+ - Reuses existing port forwards (no cleanup needed)
35
+ - Automatic fallback to content provider if TCP fails
36
+ - Zero explicit resource management
37
+ - Graceful degradation
38
+ """
39
+
40
+ def __init__(self, device: AdbDevice, prefer_tcp: bool = False):
41
+ """
42
+ Initialize Portal client.
43
+
44
+ Args:
45
+ device: ADB device instance
46
+ prefer_tcp: Whether to prefer TCP communication (will fallback to content provider if unavailable)
47
+ """
48
+ self.device = device
49
+ self.tcp_available = False
50
+ self.tcp_base_url = None
51
+ self.local_tcp_port = None
52
+
53
+ if prefer_tcp:
54
+ self._try_enable_tcp()
55
+
56
+ def _try_enable_tcp(self) -> None:
57
+ """
58
+ Try to enable TCP communication. Fails silently and falls back to content provider.
59
+
60
+ Strategy:
61
+ 1. Check if forward already exists → reuse
62
+ 2. If not, create new forward
63
+ 3. Test connection with ping
64
+ 4. Set tcp_available flag
65
+ """
66
+ try:
67
+ # Step 1: Check for existing forward
68
+ local_port = self._find_existing_forward()
69
+
70
+ # Step 2: If no forward exists, create one
71
+ if local_port is None:
72
+ logger.debug(f"No existing forward found, creating new forward for port {PORTAL_REMOTE_PORT}")
73
+ local_port = self.device.forward_port(PORTAL_REMOTE_PORT)
74
+ logger.debug(f"Created forward: localhost:{local_port} -> device:{PORTAL_REMOTE_PORT}")
75
+ else:
76
+ logger.debug(f"Reusing existing forward: localhost:{local_port} -> device:{PORTAL_REMOTE_PORT}")
77
+
78
+ # Store local port
79
+ self.local_tcp_port = local_port
80
+
81
+ # Step 3: Test connection
82
+ self.tcp_base_url = f"http://localhost:{local_port}"
83
+ if self._test_connection():
84
+ self.tcp_available = True
85
+ logger.info(f"✓ TCP mode enabled: {self.tcp_base_url}")
86
+ else:
87
+ logger.warning("TCP connection test failed, falling back to content provider")
88
+ self.tcp_available = False
89
+
90
+ except Exception as e:
91
+ logger.warning(f"Failed to setup TCP forwarding: {e}. Using content provider fallback.")
92
+ self.tcp_available = False
93
+
94
+ def _find_existing_forward(self) -> Optional[int]:
95
+ """
96
+ Check if a forward already exists for the Portal remote port.
97
+
98
+ Returns:
99
+ Local port number if forward exists, None otherwise
100
+ """
101
+ try:
102
+ forwards = self.device.forward_list()
103
+ # Format: ['serial tcp:local_port tcp:remote_port', ...]
104
+ for forward in forwards:
105
+ if self.device.serial in forward and f"tcp:{PORTAL_REMOTE_PORT}" in forward:
106
+ # Extract local port: "serial tcp:12345 tcp:8080"
107
+ match = re.search(r'tcp:(\d+)\s+tcp:' + str(PORTAL_REMOTE_PORT), forward)
108
+ if match:
109
+ local_port = int(match.group(1))
110
+ logger.debug(f"Found existing forward: localhost:{local_port} -> {PORTAL_REMOTE_PORT}")
111
+ return local_port
112
+ except Exception as e:
113
+ logger.debug(f"Failed to check existing forwards: {e}")
114
+
115
+ return None
116
+
117
+ def _test_connection(self) -> bool:
118
+ """Test if TCP connection to Portal is working."""
119
+ try:
120
+ response = requests.get(f"{self.tcp_base_url}/ping", timeout=3)
121
+ return response.status_code == 200
122
+ except Exception as e:
123
+ logger.debug(f"TCP connection test failed: {e}")
124
+ return False
125
+
126
+ def _parse_content_provider_output(self, raw_output: str) -> Optional[Dict[str, Any]]:
127
+ """
128
+ Parse the raw ADB content provider output and extract JSON data.
129
+
130
+ Args:
131
+ raw_output: Raw output from ADB content query command
132
+
133
+ Returns:
134
+ Parsed JSON data or None if parsing failed
135
+ """
136
+ lines = raw_output.strip().split("\n")
137
+
138
+ # Try line-by-line parsing
139
+ for line in lines:
140
+ line = line.strip()
141
+
142
+ # Look for "result=" pattern (common content provider format)
143
+ if "result=" in line:
144
+ result_start = line.find("result=") + 7
145
+ json_str = line[result_start:]
146
+ try:
147
+ json_data = json.loads(json_str)
148
+ # Handle nested "data" field with JSON string
149
+ if isinstance(json_data, dict) and "data" in json_data:
150
+ if isinstance(json_data["data"], str):
151
+ return json.loads(json_data["data"])
152
+ return json_data
153
+ except json.JSONDecodeError:
154
+ continue
155
+
156
+ # Fallback: try lines starting with JSON
157
+ elif line.startswith("{") or line.startswith("["):
158
+ try:
159
+ return json.loads(line)
160
+ except json.JSONDecodeError:
161
+ continue
162
+
163
+ # Last resort: try parsing entire output
164
+ try:
165
+ return json.loads(raw_output.strip())
166
+ except json.JSONDecodeError:
167
+ return None
168
+
169
+
170
+ def get_state(self) -> Dict[str, Any]:
171
+ """
172
+ Get device state (accessibility tree + phone state).
173
+ Auto-selects TCP or content provider.
174
+
175
+ Returns:
176
+ Dictionary containing 'a11y_tree' and 'phone_state' keys
177
+ """
178
+ if self.tcp_available:
179
+ return self._get_state_tcp()
180
+ return self._get_state_content_provider()
181
+
182
+ def _get_state_tcp(self) -> Dict[str, Any]:
183
+ """Get state via TCP."""
184
+ try:
185
+ response = requests.get(f"{self.tcp_base_url}/state", timeout=10)
186
+ if response.status_code == 200:
187
+ data = response.json()
188
+
189
+ # Handle nested "data" field
190
+ if isinstance(data, dict) and "data" in data:
191
+ if isinstance(data["data"], str):
192
+ return json.loads(data["data"])
193
+ return data
194
+ else:
195
+ logger.warning(f"TCP get_state failed ({response.status_code}), falling back")
196
+ return self._get_state_content_provider()
197
+ except Exception as e:
198
+ logger.warning(f"TCP get_state error: {e}, falling back")
199
+ return self._get_state_content_provider()
200
+
201
+ def _get_state_content_provider(self) -> Dict[str, Any]:
202
+ """Get state via content provider (fallback)."""
203
+ try:
204
+ output = self.device.shell("content query --uri content://com.droidrun.portal/state")
205
+ state_data = self._parse_content_provider_output(output)
206
+
207
+ if state_data is None:
208
+ return {
209
+ "error": "Parse Error",
210
+ "message": "Failed to parse state data from ContentProvider"
211
+ }
212
+
213
+ # Handle nested "data" field if present
214
+ if isinstance(state_data, dict) and "data" in state_data:
215
+ if isinstance(state_data["data"], str):
216
+ try:
217
+ return json.loads(state_data["data"])
218
+ except json.JSONDecodeError:
219
+ return {
220
+ "error": "Parse Error",
221
+ "message": "Failed to parse nested JSON data"
222
+ }
223
+
224
+ return state_data
225
+
226
+ except Exception as e:
227
+ return {
228
+ "error": "ContentProvider Error",
229
+ "message": str(e)
230
+ }
231
+
232
+
233
+ def input_text(self, text: str, clear: bool = False) -> bool:
234
+ """
235
+ Input text via keyboard.
236
+ Auto-selects TCP or content provider.
237
+
238
+ Args:
239
+ text: Text to input
240
+ clear: Whether to clear existing text first
241
+
242
+ Returns:
243
+ True if successful, False otherwise
244
+ """
245
+ if self.tcp_available:
246
+ return self._input_text_tcp(text, clear)
247
+ return self._input_text_content_provider(text, clear)
248
+
249
+ def _input_text_tcp(self, text: str, clear: bool) -> bool:
250
+ """Input text via TCP."""
251
+ try:
252
+ encoded = base64.b64encode(text.encode()).decode()
253
+ payload = {"base64_text": encoded, "clear": clear}
254
+ response = requests.post(
255
+ f"{self.tcp_base_url}/keyboard/input",
256
+ json=payload,
257
+ headers={"Content-Type": "application/json"},
258
+ timeout=10
259
+ )
260
+ if response.status_code == 200:
261
+ logger.debug(f"TCP input_text successful")
262
+ return True
263
+ else:
264
+ logger.warning(f"TCP input_text failed ({response.status_code}), falling back")
265
+ return self._input_text_content_provider(text, clear)
266
+ except Exception as e:
267
+ logger.warning(f"TCP input_text error: {e}, falling back")
268
+ return self._input_text_content_provider(text, clear)
269
+
270
+ def _input_text_content_provider(self, text: str, clear: bool) -> bool:
271
+ """Input text via content provider (fallback)."""
272
+ try:
273
+ encoded = base64.b64encode(text.encode()).decode()
274
+ clear_str = "true" if clear else "false"
275
+ cmd = (
276
+ f'content insert --uri "content://com.droidrun.portal/keyboard/input" '
277
+ f'--bind base64_text:s:"{encoded}" '
278
+ f'--bind clear:b:{clear_str}'
279
+ )
280
+ self.device.shell(cmd)
281
+ logger.debug("Content provider input_text successful")
282
+ return True
283
+ except Exception as e:
284
+ logger.error(f"Content provider input_text error: {e}")
285
+ return False
286
+
287
+
288
+ def take_screenshot(self, hide_overlay: bool = True) -> bytes:
289
+ """
290
+ Take screenshot of device.
291
+ Auto-selects TCP or ADB screencap.
292
+
293
+ Args:
294
+ hide_overlay: Whether to hide Portal overlay during screenshot
295
+
296
+ Returns:
297
+ Screenshot image bytes (PNG format)
298
+ """
299
+ if self.tcp_available:
300
+ return self._take_screenshot_tcp(hide_overlay)
301
+ return self._take_screenshot_adb()
302
+
303
+ def _take_screenshot_tcp(self, hide_overlay: bool) -> bytes:
304
+ """Take screenshot via TCP."""
305
+ try:
306
+ url = f"{self.tcp_base_url}/screenshot"
307
+ if not hide_overlay:
308
+ url += "?hideOverlay=false"
309
+
310
+ response = requests.get(url, timeout=10)
311
+ if response.status_code == 200:
312
+ data = response.json()
313
+ if data.get("status") == "success" and "data" in data:
314
+ logger.debug("Screenshot taken via TCP")
315
+ return base64.b64decode(data["data"])
316
+ else:
317
+ logger.warning("TCP screenshot failed (invalid response), falling back")
318
+ return self._take_screenshot_adb()
319
+ else:
320
+ logger.warning(f"TCP screenshot failed ({response.status_code}), falling back")
321
+ return self._take_screenshot_adb()
322
+ except Exception as e:
323
+ logger.warning(f"TCP screenshot error: {e}, falling back")
324
+ return self._take_screenshot_adb()
325
+
326
+ def _take_screenshot_adb(self) -> bytes:
327
+ """Take screenshot via ADB screencap (fallback)."""
328
+ img = self.device.screenshot()
329
+ buf = io.BytesIO()
330
+ img.save(buf, format="PNG")
331
+ logger.debug("Screenshot taken via ADB")
332
+ return buf.getvalue()
333
+
334
+ def get_apps(self, include_system: bool = True) -> List[Dict[str, str]]:
335
+ """
336
+ Get installed apps with package name and label.
337
+
338
+ Note: Currently only supports content provider (no TCP endpoint exists yet)
339
+
340
+ Args:
341
+ include_system: Whether to include system apps
342
+
343
+ Returns:
344
+ List of dicts with 'package' and 'label' keys
345
+ """
346
+ try:
347
+ logger.debug("Getting apps via content provider")
348
+
349
+ # Query content provider
350
+ output = self.device.shell("content query --uri content://com.droidrun.portal/packages")
351
+ packages_data = self._parse_content_provider_output(output)
352
+
353
+ if not packages_data or "packages" not in packages_data:
354
+ logger.warning("No packages data found in content provider response")
355
+ return []
356
+
357
+ # Filter and format apps
358
+ apps = []
359
+ for package_info in packages_data["packages"]:
360
+ if not include_system and package_info.get("isSystemApp", False):
361
+ continue
362
+
363
+ apps.append({
364
+ "package": package_info.get("packageName", ""),
365
+ "label": package_info.get("label", "")
366
+ })
367
+
368
+ logger.debug(f"Found {len(apps)} apps")
369
+ return apps
370
+
371
+ except Exception as e:
372
+ logger.error(f"Error getting apps: {e}")
373
+ raise ValueError(f"Error getting apps: {e}") from e
374
+
375
+
376
+ def ping(self) -> Dict[str, Any]:
377
+ """
378
+ Test Portal connection.
379
+
380
+ Returns:
381
+ Dictionary with status and connection details
382
+ """
383
+ if self.tcp_available:
384
+ try:
385
+ response = requests.get(f"{self.tcp_base_url}/ping", timeout=5)
386
+ if response.status_code == 200:
387
+ try:
388
+ tcp_response = response.json() if response.content else {}
389
+ return {
390
+ "status": "success",
391
+ "method": "tcp",
392
+ "url": self.tcp_base_url,
393
+ "response": tcp_response
394
+ }
395
+ except json.JSONDecodeError:
396
+ return {
397
+ "status": "success",
398
+ "method": "tcp",
399
+ "url": self.tcp_base_url,
400
+ "response": response.text
401
+ }
402
+ else:
403
+ return {
404
+ "status": "error",
405
+ "method": "tcp",
406
+ "message": f"HTTP {response.status_code}: {response.text}"
407
+ }
408
+ except Exception as e:
409
+ return {
410
+ "status": "error",
411
+ "method": "tcp",
412
+ "message": str(e)
413
+ }
414
+ else:
415
+ # Test content provider
416
+ try:
417
+ output = self.device.shell("content query --uri content://com.droidrun.portal/state")
418
+ if "Row: 0 result=" in output:
419
+ return {
420
+ "status": "success",
421
+ "method": "content_provider"
422
+ }
423
+ else:
424
+ return {
425
+ "status": "error",
426
+ "method": "content_provider",
427
+ "message": "Invalid response"
428
+ }
429
+ except Exception as e:
430
+ return {
431
+ "status": "error",
432
+ "method": "content_provider",
433
+ "message": str(e)
434
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: droidrun
3
- Version: 0.3.10.dev6
3
+ Version: 0.3.10.dev8
4
4
  Summary: A framework for controlling Android devices through LLM agents
5
5
  Project-URL: Homepage, https://github.com/droidrun/droidrun
6
6
  Project-URL: Bug Tracker, https://github.com/droidrun/droidrun/issues
@@ -4,23 +4,23 @@ droidrun/portal.py,sha256=QCfJecUpuyd9io_76ngJizs1ebKubMzVMmCWXpgC6PM,6737
4
4
  droidrun/agent/__init__.py,sha256=91sM0qTmdV5trlXOWE4D_nRhXVPgHKMnYU_9Stc_obQ,209
5
5
  droidrun/agent/usage.py,sha256=6PVeHctNa0EmHmNPTdOUv5e3-EK6AMu6D2Pz5OMqs5c,7145
6
6
  droidrun/agent/codeact/__init__.py,sha256=lagBdrury33kbHN1XEZ-xzJ-RywmpkUUoUidOno9ym8,96
7
- droidrun/agent/codeact/codeact_agent.py,sha256=KI9AKdBJ4ZdZQRIvTwj2lCOlQz8N23HTS4j4Rztd2zQ,20279
7
+ droidrun/agent/codeact/codeact_agent.py,sha256=7EkuazNIpTOX-W1oSG0XmtOwcmNOyaiPddgNxnK10No,20292
8
8
  droidrun/agent/codeact/events.py,sha256=kRKTQPzogPiQwmOCc_fGcg1g1zDXXVeBpDl45GTdpYU,734
9
9
  droidrun/agent/common/constants.py,sha256=q7ywmOXCsJZg8m9ctpzQ-nxvuj5GMn28Pr8z3dMj1Rg,94
10
10
  droidrun/agent/common/events.py,sha256=rbPWdlqNNMdnVjYhJOL2mJcNNORHhjXOkY8XiLPzp7c,1182
11
11
  droidrun/agent/context/__init__.py,sha256=-CiAv66qym_WgFy5vCRfNLxmiprmEbssu6S_2jj0LZw,452
12
12
  droidrun/agent/context/episodic_memory.py,sha256=0WKmOPe_KDWGdxudUXkmNVO1vj7L1g2zpyhAA54E1Lk,308
13
- droidrun/agent/context/task_manager.py,sha256=GXrGuBr8cukoLBRmvGIRsq0otwCC4s7N0NAVYql9oGY,5023
13
+ droidrun/agent/context/task_manager.py,sha256=A2COoBMXBOCtrASC4QI8ZdYizNVLOD04e4rzviVIMbk,5013
14
14
  droidrun/agent/droid/__init__.py,sha256=3Kzejs0p2hqKzgMc253W147P-Y84bYnQX7AZ4pybIsU,297
15
- droidrun/agent/droid/droid_agent.py,sha256=urCFKVyNz1jF_CFc7PTVEG3m_tFpwlmDOhwNZCa0lgk,21761
16
- droidrun/agent/droid/events.py,sha256=k3agPV6NzJF5ra9sviyNs3FfU1Kmpd8nY4VCBgul9k8,3988
15
+ droidrun/agent/droid/droid_agent.py,sha256=FRHNsuEyDY7n6jGwxnjoyAAgHhHWRQkbx5-Onen0Zvw,21722
16
+ droidrun/agent/droid/events.py,sha256=m7FoIpAb4Fd6pcFyoWf9Jx9mYhSV8qMCieHEfYS7Fsg,4059
17
17
  droidrun/agent/executor/__init__.py,sha256=2B531GoY7L1Hs_RJIVu62ARsP9mj86do8MiFl6ejpZ4,456
18
18
  droidrun/agent/executor/events.py,sha256=sYMs24at_VtikPKqSh_yNRYByDt4JpS1jiEob9UjNrs,1377
19
19
  droidrun/agent/executor/executor_agent.py,sha256=mBbL-NfZ59w-8TDz_etuku-fgPzrL9DE4h5vi-STEWM,13923
20
20
  droidrun/agent/executor/prompts.py,sha256=amHWdGV-q-lgFkwg8N8VGeshvUqkEdxTM8GHnt7uTUQ,1182
21
- droidrun/agent/manager/__init__.py,sha256=mXvIvRihVAlLXOVQgvA9wsMdcZ2ICvhEg4ZoV97O32w,525
21
+ droidrun/agent/manager/__init__.py,sha256=A8esHVpxzHd3Epzkl0j5seNkRQqwNEn1a97eeLmbsww,525
22
22
  droidrun/agent/manager/events.py,sha256=X0tUwCX2mU8I4bGR4JW2NmUqiOrX-Hrb017vGVPVyHw,855
23
- droidrun/agent/manager/manager_agent.py,sha256=LUJ5LKLojb2kc-Zmh7rOYtttb9-Y9mSw8daE5zocaIU,22256
23
+ droidrun/agent/manager/manager_agent.py,sha256=nXftmLlSLDs9LLB3rHE3EzpaCnUKa6v1dNfjFTMV9ys,22256
24
24
  droidrun/agent/manager/prompts.py,sha256=qfDYcSbpWpnUaavAuPE6qY6Df6w25LmtY1mEiBUMti0,2060
25
25
  droidrun/agent/oneflows/app_starter_workflow.py,sha256=MSJ6_jfbiCfSIjnw-qfSDFDuqsUS6rUGLsdKVj43wvY,3525
26
26
  droidrun/agent/oneflows/text_manipulator.py,sha256=mO59DF1uif9poUWy90UehrBmHbNxL9ph4Evtgt1ODbQ,8751
@@ -28,38 +28,39 @@ droidrun/agent/utils/__init__.py,sha256=Oro0oyiz1xzRpchWLDA1TZJELJNSwBOb2WdGgknt
28
28
  droidrun/agent/utils/async_utils.py,sha256=_JhZ_ZfCkRTfPsufFDhUUds_Vp6z1-TokzUG4H8G7pc,338
29
29
  droidrun/agent/utils/chat_utils.py,sha256=mginY1rbP5t06O3hz2RpJJJNzggaE8VhWjnFid844vw,13797
30
30
  droidrun/agent/utils/device_state_formatter.py,sha256=3MuR3XQulnrsdzmMYfTEegA_XkYTTiETXMRtOtyqoC0,6889
31
- droidrun/agent/utils/executer.py,sha256=vz6mLeV4xti3dd_bDBd4aWHDA6T-ym0EbdEicrtK0aA,4233
31
+ droidrun/agent/utils/executer.py,sha256=mCq-T9gekgFK9oSHz2H9ctHIyJQFzp9MWQgzuxP0TU0,4191
32
32
  droidrun/agent/utils/inference.py,sha256=dupCtMYXUGuBJz9RqTgSsLYe_MOSB0LEhfHIdtFC8x0,3893
33
- droidrun/agent/utils/llm_picker.py,sha256=iy3wYp8c64YSA0BnshJCQCTm_GCbyBe5SDs11ZY8Aro,9397
33
+ droidrun/agent/utils/llm_picker.py,sha256=KQzrRcHE38NwujDbNth5F9v5so9HVvHjfkQznMsv-cM,9397
34
34
  droidrun/agent/utils/message_utils.py,sha256=_wngf082gg232y_3pC_yn4fnPhHiyYAxhU4ewT78roo,2309
35
- droidrun/agent/utils/tools.py,sha256=pQRe1QLCZdLMb2FoFX6orX1h97mYT10-_1xB6qNZdsY,9930
35
+ droidrun/agent/utils/tools.py,sha256=anc10NAKmZx91JslHFpo6wfnUOZ2pnPXJS-5nMVHC_A,9930
36
36
  droidrun/agent/utils/trajectory.py,sha256=Z6C19Y9hsRxjLZWywqYWTApKU7PelvWM-5Tsl3h7KEw,19718
37
- droidrun/app_cards/app_card_provider.py,sha256=eh4IJ81kM9vpOtQsSkX9bMo6YY3RUkPF_osLznWsfss,828
38
- droidrun/app_cards/providers/__init__.py,sha256=bURQeH4uG7Lc-1og70jVWPcx_mlIJvzil7HD4igAfT0,372
39
- droidrun/app_cards/providers/composite_provider.py,sha256=bQWOwJlcCN9mGyCA9LTXso_ljSFMbzAP5Rl9d2CbW8Q,3174
40
- droidrun/app_cards/providers/local_provider.py,sha256=Xx7bR4w3nJAs-eoWrk6XugKnp6-6KP_XwJnJqoH3rfk,3934
41
- droidrun/app_cards/providers/server_provider.py,sha256=Lfp1DUhvlOQpxCr_5oJk8s1OBDvzHS3jkvGqdELphEk,4153
37
+ droidrun/app_cards/app_card_provider.py,sha256=wy7CGFnBd_EPU58xNdv4ZWUA9F4Plon71N4-5RT5vNg,827
38
+ droidrun/app_cards/providers/__init__.py,sha256=vN4TvBtsvfdvzgqbIJegIfHhct0aTFZjvJazWFDvdhg,372
39
+ droidrun/app_cards/providers/composite_provider.py,sha256=oi7dlkv_Hv2rEZMxQlO1jP9fQcTBydr40zCyunCNxQA,3156
40
+ droidrun/app_cards/providers/local_provider.py,sha256=RRGQ7VR7qHT9uKSOlSvqCTRq_p4W5HzlWue7B6tcbT0,3904
41
+ droidrun/app_cards/providers/server_provider.py,sha256=rOJyiCE_zTCCK9SAJeee3vLWISytoZrBUiXB6LaJEv8,4148
42
42
  droidrun/cli/__init__.py,sha256=5cO-QBcUl5w35zO18OENj4OShdglQjn8Ne9aqgSh-PM,167
43
- droidrun/cli/logs.py,sha256=lZX44S7pvrpbwfIXX6WYVPeDdvjjRozRb-oc6u-yCks,12686
44
- droidrun/cli/main.py,sha256=7D5K2yD9XN1P0Bpq1lexDaX58iIgy9aRe5bevV9AEWk,35373
43
+ droidrun/cli/logs.py,sha256=V8rn6oXgYObExX4dG8MUnQXxUdKOk1QlTkOQtI5e6wo,12686
44
+ droidrun/cli/main.py,sha256=lzGwWk8SbbxmVeyz2mqkPBT6Xs5SMxnQSxecuK-7L5s,35119
45
45
  droidrun/config_manager/__init__.py,sha256=SeLoEYVU5jMEtXLjx76VE_3rxzZXjCMlVPW7hodU128,460
46
- droidrun/config_manager/config_manager.py,sha256=gnHrSYOYpaWrWyHIkMp1jG2U17l6QW5DU7nTQQZ_U-U,19955
46
+ droidrun/config_manager/config_manager.py,sha256=hPETII_5wYvfb11e7sJlfCVk9p3WbA7nHPAV3bQQdmE,19930
47
47
  droidrun/config_manager/path_resolver.py,sha256=vQKT5XmnENtSK3B1D-iItL8CpOQTKzfKZ1wTO4khlTs,3421
48
48
  droidrun/config_manager/prompt_loader.py,sha256=JqGHjT4Ik5iwPfnaXkCc1W1cm4QmIqq2duPuye14jSc,2430
49
49
  droidrun/macro/__init__.py,sha256=TKRNlMJqClV1p1dkfES4eo-Bq1VkSiTC1DDMxMjnUWE,357
50
50
  droidrun/macro/__main__.py,sha256=MWdBvQVhOoeKlC8atDwjVbPSn0-XNt4PDbpCCoeJuUk,193
51
- droidrun/macro/cli.py,sha256=hlnQVY399El26cpFiXiP3-lcXB_gh6SDpAoPbCiMqsA,9161
51
+ droidrun/macro/cli.py,sha256=jQqnnrAnH_KTxuPsYsvIdftPlVcvpb5a5yn5v6A6cCg,9151
52
52
  droidrun/macro/replay.py,sha256=ILhnvN3VYhMK13wkaD5oDwP4wCYTniwcgesUON-9L5o,10721
53
53
  droidrun/telemetry/__init__.py,sha256=2G9PwAuVWsd6qRMKSscssvmL57ILlOK5EV0KezPiF1I,271
54
54
  droidrun/telemetry/events.py,sha256=y-i2d5KiPkikVXrzMQu87osy1LAZTBIx8DlPIWGAXG0,486
55
55
  droidrun/telemetry/phoenix.py,sha256=JHdFdRHXu7cleAb4X4_Y5yn5zPSIApwyKCOxoaj_gf4,7117
56
56
  droidrun/telemetry/tracker.py,sha256=YWOkyLE8XiHainVSB77JE37y-rloOYVYs6j53Aw1J8A,2735
57
57
  droidrun/tools/__init__.py,sha256=BbQFKuPn-5MwGzr-3urMDK8S1ZsP96D96y7WTJYB3AA,271
58
- droidrun/tools/adb.py,sha256=ziMbQ02TDBvm04ffG1wlYUstsYABNopZaq0aOBh0xtA,42985
58
+ droidrun/tools/adb.py,sha256=PRbQS1qhy_HFUVx78LYPwTa4GT4TgMrRTwc7NM4Tf4A,28950
59
59
  droidrun/tools/ios.py,sha256=GMYbiNNBeHLwVQAo4_fEZ7snr4JCHE6sG11rcuPvSpk,21831
60
+ droidrun/tools/portal_client.py,sha256=BthC-ryHtCxh3czmkTge5aaurinPZfFkV4DgbkD_wbw,16307
60
61
  droidrun/tools/tools.py,sha256=0eAZFTaY10eiiUcJM4AkURmTGX-O1RRXjpQ5MHj2Ydo,5241
61
- droidrun-0.3.10.dev6.dist-info/METADATA,sha256=QZOK5sFxL9-IeXYDiY1JQJ-9kMV_K24TY_pzaLuNXzw,7044
62
- droidrun-0.3.10.dev6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
63
- droidrun-0.3.10.dev6.dist-info/entry_points.txt,sha256=o259U66js8TIybQ7zs814Oe_LQ_GpZsp6a9Cr-xm5zE,51
64
- droidrun-0.3.10.dev6.dist-info/licenses/LICENSE,sha256=s-uxn9qChu-kFdRXUp6v_0HhsaJ_5OANmfNOFVm2zdk,1069
65
- droidrun-0.3.10.dev6.dist-info/RECORD,,
62
+ droidrun-0.3.10.dev8.dist-info/METADATA,sha256=MWB-CRMnxmQV4il8-OYdsGKn00IclAqvxPNRk_-LLxw,7044
63
+ droidrun-0.3.10.dev8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
64
+ droidrun-0.3.10.dev8.dist-info/entry_points.txt,sha256=o259U66js8TIybQ7zs814Oe_LQ_GpZsp6a9Cr-xm5zE,51
65
+ droidrun-0.3.10.dev8.dist-info/licenses/LICENSE,sha256=s-uxn9qChu-kFdRXUp6v_0HhsaJ_5OANmfNOFVm2zdk,1069
66
+ droidrun-0.3.10.dev8.dist-info/RECORD,,