droidrun 0.3.10.dev5__py3-none-any.whl → 0.3.10.dev7__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 (31) hide show
  1. droidrun/agent/codeact/codeact_agent.py +21 -29
  2. droidrun/agent/context/task_manager.py +0 -1
  3. droidrun/agent/droid/droid_agent.py +1 -3
  4. droidrun/agent/droid/events.py +6 -3
  5. droidrun/agent/executor/executor_agent.py +24 -38
  6. droidrun/agent/executor/prompts.py +0 -108
  7. droidrun/agent/manager/__init__.py +1 -1
  8. droidrun/agent/manager/manager_agent.py +104 -87
  9. droidrun/agent/utils/executer.py +11 -10
  10. droidrun/agent/utils/llm_picker.py +63 -1
  11. droidrun/agent/utils/tools.py +30 -1
  12. droidrun/app_cards/app_card_provider.py +26 -0
  13. droidrun/app_cards/providers/__init__.py +7 -0
  14. droidrun/app_cards/providers/composite_provider.py +97 -0
  15. droidrun/app_cards/providers/local_provider.py +115 -0
  16. droidrun/app_cards/providers/server_provider.py +126 -0
  17. droidrun/cli/logs.py +4 -4
  18. droidrun/cli/main.py +244 -34
  19. droidrun/config_manager/__init__.py +0 -2
  20. droidrun/config_manager/config_manager.py +45 -102
  21. droidrun/config_manager/path_resolver.py +1 -1
  22. droidrun/config_manager/prompt_loader.py +48 -51
  23. droidrun/macro/cli.py +0 -1
  24. droidrun/portal.py +17 -0
  25. droidrun/tools/adb.py +13 -34
  26. {droidrun-0.3.10.dev5.dist-info → droidrun-0.3.10.dev7.dist-info}/METADATA +2 -9
  27. {droidrun-0.3.10.dev5.dist-info → droidrun-0.3.10.dev7.dist-info}/RECORD +30 -26
  28. droidrun/config_manager/app_card_loader.py +0 -148
  29. {droidrun-0.3.10.dev5.dist-info → droidrun-0.3.10.dev7.dist-info}/WHEEL +0 -0
  30. {droidrun-0.3.10.dev5.dist-info → droidrun-0.3.10.dev7.dist-info}/entry_points.txt +0 -0
  31. {droidrun-0.3.10.dev5.dist-info → droidrun-0.3.10.dev7.dist-info}/licenses/LICENSE +0 -0
@@ -1,75 +1,72 @@
1
1
  """
2
- Prompt loading utility for markdown-based prompts.
3
-
4
- Supports variable substitution with escape sequences:
5
- - {variable} replaced with value
6
- - {{variable}} literal {variable}
7
- - Missing variables left as {variable}
2
+ Prompt loading utility using Jinja2 templates.
3
+
4
+ Features:
5
+ - Loads from absolute file paths (resolved by AgentConfig + PathResolver)
6
+ - Conditional rendering: {% if variable %}...{% endif %}
7
+ - Loops with slicing: {% for item in items[-5:] %}...{% endfor %}
8
+ - Filters: {{ variable|default("fallback") }}
9
+ - Missing variables: silently ignored (renders as empty string)
10
+ - Extra variables: silently ignored
8
11
  """
9
12
 
10
- import re
11
13
  from pathlib import Path
12
14
  from typing import Any, Dict
13
15
 
14
- from droidrun.config_manager.path_resolver import PathResolver
16
+ from jinja2 import Environment
15
17
 
16
18
 
17
19
  class PromptLoader:
18
- """Load and format markdown prompts with variable substitution."""
20
+ """Simple Jinja2 template renderer - loads from absolute file paths."""
21
+
22
+ _env = None # Cached Jinja2 environment
23
+
24
+ @classmethod
25
+ def _get_environment(cls) -> Environment:
26
+ """Get or create cached Jinja2 environment."""
27
+ if cls._env is None:
28
+ cls._env = Environment(
29
+ trim_blocks=True, # Remove first newline after block
30
+ lstrip_blocks=True, # Strip leading whitespace before blocks
31
+ keep_trailing_newline=False,
32
+ )
33
+
34
+ return cls._env
19
35
 
20
36
  @staticmethod
21
- def load_prompt(path: str, variables: Dict[str, Any] = None) -> str:
37
+ def load_prompt(file_path: str, variables: Dict[str, Any] = None) -> str:
22
38
  """
23
- Load prompt from .md file and substitute variables.
24
-
25
- Path resolution:
26
- - Checks working directory first (for user overrides)
27
- - Falls back to project directory (for default prompts)
28
- - Example: "config/prompts/codeact/system.md"
39
+ Load and render Jinja2 template from absolute file path.
29
40
 
30
- Variable substitution:
31
- - {variable} replaced with value from variables dict
32
- - {{variable}} → becomes literal {variable} in output
33
- - Missing variables → left as {variable} (no error)
41
+ Path resolution is handled by AgentConfig + PathResolver.
42
+ This method just loads and renders.
34
43
 
35
44
  Args:
36
- path: Path to prompt file (relative or absolute)
37
- variables: Dict of variable names to values
45
+ file_path: ABSOLUTE path to template file (from AgentConfig methods)
46
+ variables: Dict of variables to pass to template
47
+ - Missing variables: silently ignored (render as empty string)
48
+ - Extra variables: silently ignored
38
49
 
39
50
  Returns:
40
- Formatted prompt string
51
+ Rendered prompt string
41
52
 
42
53
  Raises:
43
- FileNotFoundError: If prompt file doesn't exist
44
- """
45
- # Resolve path (checks working dir, then project dir)
46
- prompt_path = PathResolver.resolve(path, must_exist=True)
47
-
48
- prompt_text = prompt_path.read_text(encoding="utf-8")
49
-
50
- if variables is None:
51
- return prompt_text
54
+ FileNotFoundError: If template file doesn't exist
52
55
 
53
- # Handle escaped braces: {{variable}} → {variable}
54
- # Strategy: Replace {{...}} with placeholder, do substitution, restore
55
- escaped_pattern = re.compile(r'\{\{([^}]+)\}\}')
56
- placeholders = {}
57
- counter = [0]
58
-
59
- def escape_replacer(match):
60
- placeholder = f"__ESCAPED_{counter[0]}__"
61
- placeholders[placeholder] = "{" + match.group(1) + "}"
62
- counter[0] += 1
63
- return placeholder
56
+ """
57
+ path = Path(file_path)
64
58
 
65
- prompt_text = escaped_pattern.sub(escape_replacer, prompt_text)
59
+ if not path.exists():
60
+ raise FileNotFoundError(f"Prompt file not found: {file_path}")
66
61
 
67
- # Substitute variables
68
- for key, value in variables.items():
69
- prompt_text = prompt_text.replace(f"{{{key}}}", str(value))
62
+ # Read template content
63
+ template_content = path.read_text(encoding="utf-8")
70
64
 
71
- # Restore escaped braces
72
- for placeholder, original in placeholders.items():
73
- prompt_text = prompt_text.replace(placeholder, original)
65
+ # Get cached environment and create template from string
66
+ env = PromptLoader._get_environment()
67
+ template = env.from_string(template_content)
74
68
 
75
- return prompt_text
69
+ # Render with variables (empty dict if None)
70
+ # Missing variables render as empty string (default Undefined behavior)
71
+ # Extra variables are silently ignored
72
+ return template.render(**(variables or {}))
droidrun/macro/cli.py CHANGED
@@ -4,7 +4,6 @@ Command-line interface for DroidRun macro replay.
4
4
 
5
5
  import asyncio
6
6
  import logging
7
- import os
8
7
  from typing import Optional
9
8
 
10
9
  import click
droidrun/portal.py CHANGED
@@ -190,6 +190,23 @@ def setup_keyboard(device: AdbDevice):
190
190
  except Exception as e:
191
191
  raise Exception("Error setting up keyboard") from e
192
192
 
193
+ def disable_keyboard(device: AdbDevice, target_ime: str = "com.droidrun.portal/.DroidrunKeyboardIME"):
194
+ """
195
+ Disable a specific IME (keyboard) and optionally switch to another.
196
+ By default, disables the DroidRun keyboard.
197
+
198
+ Args:
199
+ target_ime: The IME package/activity to disable (default: DroidRun keyboard)
200
+
201
+ Returns:
202
+ bool: True if disabled successfully, False otherwise
203
+ """
204
+ try:
205
+ device.shell(f"ime disable {target_ime}")
206
+ return True
207
+ except Exception as e:
208
+ raise Exception("Error disabling keyboard") from e
209
+
193
210
  def test():
194
211
  device = adb.device()
195
212
  ping_portal(device, debug=False)
droidrun/tools/adb.py CHANGED
@@ -71,7 +71,9 @@ class AdbTools(Tools):
71
71
  self.app_opener_llm = app_opener_llm
72
72
  self.text_manipulator_llm = text_manipulator_llm
73
73
 
74
- self.setup_keyboard()
74
+ # Set up keyboard
75
+ from droidrun.portal import setup_keyboard
76
+ setup_keyboard(self.device)
75
77
 
76
78
  # Set up TCP forwarding if requested
77
79
  if self.use_tcp:
@@ -150,24 +152,6 @@ class AdbTools(Tools):
150
152
  logger.error(f"Failed to remove TCP port forwarding: {e}")
151
153
  return False
152
154
 
153
- def setup_keyboard(self) -> bool:
154
- """
155
- Set up the DroidRun keyboard as the default input method.
156
- Simple setup that just switches to DroidRun keyboard without saving/restoring.
157
-
158
- Returns:
159
- bool: True if setup was successful, False otherwise
160
- """
161
- try:
162
- self.device.shell("ime enable com.droidrun.portal/.DroidrunKeyboardIME")
163
- self.device.shell("ime set com.droidrun.portal/.DroidrunKeyboardIME")
164
- logger.debug("DroidRun keyboard setup completed")
165
- return True
166
-
167
- except Exception as e:
168
- logger.error(f"Failed to setup DroidRun keyboard: {e}")
169
- return False
170
-
171
155
  def __del__(self):
172
156
  """Cleanup when the object is destroyed."""
173
157
  if hasattr(self, "tcp_forwarded") and self.tcp_forwarded:
@@ -312,12 +296,9 @@ class AdbTools(Tools):
312
296
  # Extract coordinates using the helper function
313
297
  x, y = self._extract_element_coordinates_by_index(index)
314
298
 
315
- logger.debug(
316
- f"Tapping element with index {index} at coordinates ({x}, {y})"
317
- )
318
299
  # Get the device and tap at the coordinates
319
300
  self.device.click(x, y)
320
- logger.debug(f"Tapped element with index {index} at coordinates ({x}, {y})")
301
+ print(f"Tapped element with index {index} at coordinates ({x}, {y})")
321
302
 
322
303
  # Emit coordinate action event for trajectory recording
323
304
  if self._ctx:
@@ -350,8 +331,7 @@ class AdbTools(Tools):
350
331
  )
351
332
  self._ctx.write_event_to_stream(tap_event)
352
333
 
353
- # Add a small delay to allow UI to update
354
- time.sleep(0.5)
334
+
355
335
 
356
336
  # Create a descriptive response
357
337
  def find_element_by_index(elements, target_index):
@@ -463,7 +443,7 @@ class AdbTools(Tools):
463
443
 
464
444
  self.device.swipe(start_x, start_y, end_x, end_y, float(duration_ms / 1000))
465
445
  time.sleep(duration_ms / 1000)
466
- logger.debug(
446
+ print(
467
447
  f"Swiped from ({start_x}, {start_y}) to ({end_x}, {end_y}) in {duration_ms} milliseconds"
468
448
  )
469
449
  return True
@@ -546,7 +526,7 @@ class AdbTools(Tools):
546
526
  timeout=10,
547
527
  )
548
528
 
549
- logger.debug(
529
+ print(
550
530
  f"Keyboard input TCP response: {response.status_code}, {response.text}"
551
531
  )
552
532
 
@@ -575,7 +555,7 @@ class AdbTools(Tools):
575
555
 
576
556
  # Execute the command and capture output for better error handling
577
557
  result = self.device.shell(cmd)
578
- logger.debug(f"Content provider result: {result}")
558
+ print(f"Content provider result: {result}")
579
559
 
580
560
  if self._ctx:
581
561
  input_event = InputTextActionEvent(
@@ -585,7 +565,7 @@ class AdbTools(Tools):
585
565
  )
586
566
  self._ctx.write_event_to_stream(input_event)
587
567
 
588
- logger.debug(
568
+ print(
589
569
  f"Text input completed (clear={clear}): {text[:50]}{'...' if len(text) > 50 else ''}"
590
570
  )
591
571
  return f"Text input completed (clear={clear}): {text[:50]}{'...' if len(text) > 50 else ''}"
@@ -604,7 +584,7 @@ class AdbTools(Tools):
604
584
  This presses the Android back button.
605
585
  """
606
586
  try:
607
- logger.debug("Pressing key BACK")
587
+ print("Pressing key BACK")
608
588
  self.device.keyevent(4)
609
589
 
610
590
  if self._ctx:
@@ -652,9 +632,8 @@ class AdbTools(Tools):
652
632
  )
653
633
  self._ctx.write_event_to_stream(key_event)
654
634
 
655
- logger.debug(f"Pressing key {key_name}")
656
635
  self.device.keyevent(keycode)
657
- logger.debug(f"Pressed key {key_name}")
636
+ print(f"Pressed key {key_name}")
658
637
  return f"Pressed key {key_name}"
659
638
  except ValueError as e:
660
639
  return f"Error: {str(e)}"
@@ -670,7 +649,7 @@ class AdbTools(Tools):
670
649
  """
671
650
  try:
672
651
 
673
- logger.debug(f"Starting app {package} with activity {activity}")
652
+ print(f"Starting app {package} with activity {activity}")
674
653
  if not activity:
675
654
  dumpsys_output = self.device.shell(
676
655
  f"cmd package resolve-activity --brief {package}"
@@ -689,7 +668,7 @@ class AdbTools(Tools):
689
668
  print(f"Activity: {activity}")
690
669
 
691
670
  self.device.app_start(package, activity)
692
- logger.debug(f"App started: {package} with activity {activity}")
671
+ print(f"App started: {package} with activity {activity}")
693
672
  return f"App started: {package} with activity {activity}"
694
673
  except Exception as e:
695
674
  return f"Error: {str(e)}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: droidrun
3
- Version: 0.3.10.dev5
3
+ Version: 0.3.10.dev7
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
@@ -29,6 +29,7 @@ Requires-Python: >=3.11
29
29
  Requires-Dist: adbutils>=2.10.2
30
30
  Requires-Dist: apkutils==2.0.0
31
31
  Requires-Dist: arize-phoenix>=12.3.0
32
+ Requires-Dist: httpx>=0.27.0
32
33
  Requires-Dist: llama-index==0.14.4
33
34
  Requires-Dist: posthog>=6.7.6
34
35
  Requires-Dist: pydantic>=2.11.10
@@ -36,14 +37,6 @@ Requires-Dist: rich>=14.1.0
36
37
  Provides-Extra: anthropic
37
38
  Requires-Dist: anthropic>=0.67.0; extra == 'anthropic'
38
39
  Requires-Dist: llama-index-llms-anthropic<0.9.0,>=0.8.6; extra == 'anthropic'
39
- Provides-Extra: backend
40
- Requires-Dist: aiohttp>=3.9.0; extra == 'backend'
41
- Requires-Dist: fastapi>=0.104.0; extra == 'backend'
42
- Requires-Dist: pydantic-settings>=2.0.0; extra == 'backend'
43
- Requires-Dist: python-dotenv>=1.0.0; extra == 'backend'
44
- Requires-Dist: python-multipart>=0.0.6; extra == 'backend'
45
- Requires-Dist: uvicorn[standard]>=0.24.0; extra == 'backend'
46
- Requires-Dist: websockets>=12.0; extra == 'backend'
47
40
  Provides-Extra: deepseek
48
41
  Requires-Dist: llama-index-llms-deepseek>=0.2.1; extra == 'deepseek'
49
42
  Provides-Extra: dev
@@ -1,26 +1,26 @@
1
1
  droidrun/__init__.py,sha256=c2exmv8MuTJ6wWXqzPR30pKGENeIgWe5ytY5hSQYuBc,621
2
2
  droidrun/__main__.py,sha256=8HpscyKn49EH05wXIdoxMiU43xPKb1czq8Aze2vIIqI,106
3
- droidrun/portal.py,sha256=Z4daRlMOpsiGxtJ4csuEaEiXAfGM5otbbrcrHvGhAB4,6161
3
+ 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=r7sjVhsmgMdXdrCwCDxLFJE9Eeh9_E1Jd39Fs7T7iX8,20810
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=eAxrGzA1yUGutInecFML0ms_361NgYmunK8XJ5-AEjE,3876
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
- droidrun/agent/executor/executor_agent.py,sha256=OQIVd7n0EjUZYwefbNRrhKheohLygQW9zDEpC4pDeq0,15153
20
- droidrun/agent/executor/prompts.py,sha256=nUsr4oF1RjmByOVvBIgjw6_739hO3C916QEVYFjXuqw,7530
21
- droidrun/agent/manager/__init__.py,sha256=mXvIvRihVAlLXOVQgvA9wsMdcZ2ICvhEg4ZoV97O32w,525
19
+ droidrun/agent/executor/executor_agent.py,sha256=mBbL-NfZ59w-8TDz_etuku-fgPzrL9DE4h5vi-STEWM,13923
20
+ droidrun/agent/executor/prompts.py,sha256=amHWdGV-q-lgFkwg8N8VGeshvUqkEdxTM8GHnt7uTUQ,1182
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=fCDIFnpHRq75iEEvGtnbB7DnEjtZ9ZTvdWRBjJzTEXU,21094
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,34 +28,38 @@ 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=m5PHBe8iex5n_a5fPwfPd5Qdup_atx4C6iv0-wTCGzY,7232
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=SW-nBSfKTgPxDafV8dVX345JwmW378y_x86sLxiISj4,9243
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=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
37
42
  droidrun/cli/__init__.py,sha256=5cO-QBcUl5w35zO18OENj4OShdglQjn8Ne9aqgSh-PM,167
38
- droidrun/cli/logs.py,sha256=lZX44S7pvrpbwfIXX6WYVPeDdvjjRozRb-oc6u-yCks,12686
39
- droidrun/cli/main.py,sha256=dJdKleue-kLZs8xfQ3s2m_XiWm3ShrFy46Ne0S5P0JU,26171
40
- droidrun/config_manager/__init__.py,sha256=-3FZJ2K985yhrIq2uPtlutCImdVzWvckdL1X4XCFi5s,547
41
- droidrun/config_manager/app_card_loader.py,sha256=WKU3knxOcUo4v2mNh0XPljUW03Sc1fK4yBJVDERAeJY,5055
42
- droidrun/config_manager/config_manager.py,sha256=4XUBTJnkoO_9AFU13j5nNygFIQFHyZ_Q_b3R6b796m4,21529
43
- droidrun/config_manager/path_resolver.py,sha256=R9Yf3JWdrdvNXSg-6yXBO2NSr8HTuSvJfGcuyz0dY-I,3417
44
- droidrun/config_manager/prompt_loader.py,sha256=nzlL3YMyteY91fOMryQD3XRny_2oytAmaXRZfLBNH2M,2450
43
+ droidrun/cli/logs.py,sha256=V8rn6oXgYObExX4dG8MUnQXxUdKOk1QlTkOQtI5e6wo,12686
44
+ droidrun/cli/main.py,sha256=0-63OyiNlT944eOBqjw3fG8Edove88tqNEFkCKVHb8w,35372
45
+ droidrun/config_manager/__init__.py,sha256=SeLoEYVU5jMEtXLjx76VE_3rxzZXjCMlVPW7hodU128,460
46
+ droidrun/config_manager/config_manager.py,sha256=hPETII_5wYvfb11e7sJlfCVk9p3WbA7nHPAV3bQQdmE,19930
47
+ droidrun/config_manager/path_resolver.py,sha256=vQKT5XmnENtSK3B1D-iItL8CpOQTKzfKZ1wTO4khlTs,3421
48
+ droidrun/config_manager/prompt_loader.py,sha256=JqGHjT4Ik5iwPfnaXkCc1W1cm4QmIqq2duPuye14jSc,2430
45
49
  droidrun/macro/__init__.py,sha256=TKRNlMJqClV1p1dkfES4eo-Bq1VkSiTC1DDMxMjnUWE,357
46
50
  droidrun/macro/__main__.py,sha256=MWdBvQVhOoeKlC8atDwjVbPSn0-XNt4PDbpCCoeJuUk,193
47
- droidrun/macro/cli.py,sha256=hlnQVY399El26cpFiXiP3-lcXB_gh6SDpAoPbCiMqsA,9161
51
+ droidrun/macro/cli.py,sha256=jQqnnrAnH_KTxuPsYsvIdftPlVcvpb5a5yn5v6A6cCg,9151
48
52
  droidrun/macro/replay.py,sha256=ILhnvN3VYhMK13wkaD5oDwP4wCYTniwcgesUON-9L5o,10721
49
53
  droidrun/telemetry/__init__.py,sha256=2G9PwAuVWsd6qRMKSscssvmL57ILlOK5EV0KezPiF1I,271
50
54
  droidrun/telemetry/events.py,sha256=y-i2d5KiPkikVXrzMQu87osy1LAZTBIx8DlPIWGAXG0,486
51
55
  droidrun/telemetry/phoenix.py,sha256=JHdFdRHXu7cleAb4X4_Y5yn5zPSIApwyKCOxoaj_gf4,7117
52
56
  droidrun/telemetry/tracker.py,sha256=YWOkyLE8XiHainVSB77JE37y-rloOYVYs6j53Aw1J8A,2735
53
57
  droidrun/tools/__init__.py,sha256=BbQFKuPn-5MwGzr-3urMDK8S1ZsP96D96y7WTJYB3AA,271
54
- droidrun/tools/adb.py,sha256=X9fvrQWBTCKkWTB4oXfRj5PRb__SA8zO0J0-TMbsJ1U,43908
58
+ droidrun/tools/adb.py,sha256=ziMbQ02TDBvm04ffG1wlYUstsYABNopZaq0aOBh0xtA,42985
55
59
  droidrun/tools/ios.py,sha256=GMYbiNNBeHLwVQAo4_fEZ7snr4JCHE6sG11rcuPvSpk,21831
56
60
  droidrun/tools/tools.py,sha256=0eAZFTaY10eiiUcJM4AkURmTGX-O1RRXjpQ5MHj2Ydo,5241
57
- droidrun-0.3.10.dev5.dist-info/METADATA,sha256=VAVgYvK3zhdx2jLhH8DdvYrU4Oba3d2453aUOt4MQKc,7429
58
- droidrun-0.3.10.dev5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
59
- droidrun-0.3.10.dev5.dist-info/entry_points.txt,sha256=o259U66js8TIybQ7zs814Oe_LQ_GpZsp6a9Cr-xm5zE,51
60
- droidrun-0.3.10.dev5.dist-info/licenses/LICENSE,sha256=s-uxn9qChu-kFdRXUp6v_0HhsaJ_5OANmfNOFVm2zdk,1069
61
- droidrun-0.3.10.dev5.dist-info/RECORD,,
61
+ droidrun-0.3.10.dev7.dist-info/METADATA,sha256=gac2d1ul1MhI-nSmx1ttR3XtWJoIhq6lIyZE4mufMRE,7044
62
+ droidrun-0.3.10.dev7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
63
+ droidrun-0.3.10.dev7.dist-info/entry_points.txt,sha256=o259U66js8TIybQ7zs814Oe_LQ_GpZsp6a9Cr-xm5zE,51
64
+ droidrun-0.3.10.dev7.dist-info/licenses/LICENSE,sha256=s-uxn9qChu-kFdRXUp6v_0HhsaJ_5OANmfNOFVm2zdk,1069
65
+ droidrun-0.3.10.dev7.dist-info/RECORD,,
@@ -1,148 +0,0 @@
1
- """
2
- App card loading utility for package-specific prompts.
3
-
4
- Supports flexible file path resolution and caches loaded content.
5
- """
6
-
7
- import json
8
- from pathlib import Path
9
- from typing import Dict, Optional
10
-
11
- from droidrun.config_manager.path_resolver import PathResolver
12
-
13
-
14
- class AppCardLoader:
15
- """Load app cards based on package names with content caching."""
16
-
17
- _mapping_cache: Optional[Dict[str, str]] = None
18
- _cache_dir: Optional[str] = None
19
- _content_cache: Dict[str, str] = {}
20
-
21
- @staticmethod
22
- def load_app_card(
23
- package_name: str, app_cards_dir: str = "config/app_cards"
24
- ) -> str:
25
- """
26
- Load app card for a package name.
27
-
28
- Path resolution:
29
- - Checks working directory first (for user overrides)
30
- - Falls back to project directory (for default cards)
31
- - Supports absolute paths (used as-is)
32
-
33
- File loading from app_cards.json:
34
- 1. Relative to app_cards_dir (most common):
35
- {"com.google.gm": "gmail.md"}
36
- → {app_cards_dir}/gmail.md
37
-
38
- 2. Relative path (checks working dir, then project dir):
39
- {"com.google.gm": "config/custom_cards/gmail.md"}
40
-
41
- 3. Absolute path:
42
- {"com.google.gm": "/usr/share/droidrun/cards/gmail.md"}
43
-
44
- Args:
45
- package_name: Android package name (e.g., "com.google.android.gm")
46
- app_cards_dir: Directory path (relative or absolute)
47
-
48
- Returns:
49
- App card content or empty string if not found
50
- """
51
- if not package_name:
52
- return ""
53
-
54
- # Check content cache first (key: package_name:app_cards_dir)
55
- cache_key = f"{package_name}:{app_cards_dir}"
56
- if cache_key in AppCardLoader._content_cache:
57
- return AppCardLoader._content_cache[cache_key]
58
-
59
- # Load mapping (with cache)
60
- mapping = AppCardLoader._load_mapping(app_cards_dir)
61
-
62
- # Get file path from mapping
63
- if package_name not in mapping:
64
- # Cache the empty result to avoid repeated lookups
65
- AppCardLoader._content_cache[cache_key] = ""
66
- return ""
67
-
68
- file_path_str = mapping[package_name]
69
- file_path = Path(file_path_str)
70
-
71
- # Determine resolution strategy
72
- if file_path.is_absolute():
73
- # Absolute path: use as-is
74
- app_card_path = file_path
75
- elif file_path_str.startswith(("config/", "prompts/", "docs/")):
76
- # Project-relative path: resolve with unified resolver
77
- app_card_path = PathResolver.resolve(file_path_str)
78
- else:
79
- # App_cards-relative: resolve dir first, then append filename
80
- cards_dir_resolved = PathResolver.resolve(app_cards_dir)
81
- app_card_path = cards_dir_resolved / file_path_str
82
-
83
- # Read file
84
- try:
85
- if not app_card_path.exists():
86
- # Cache the empty result
87
- AppCardLoader._content_cache[cache_key] = ""
88
- return ""
89
-
90
- content = app_card_path.read_text(encoding="utf-8")
91
- # Cache the content
92
- AppCardLoader._content_cache[cache_key] = content
93
- return content
94
- except Exception:
95
- # Cache the empty result on error
96
- AppCardLoader._content_cache[cache_key] = ""
97
- return ""
98
-
99
- @staticmethod
100
- def _load_mapping(app_cards_dir: str) -> Dict[str, str]:
101
- """Load and cache the app_cards.json mapping."""
102
- # Cache invalidation: if dir changed, reload
103
- if (
104
- AppCardLoader._mapping_cache is not None
105
- and AppCardLoader._cache_dir == app_cards_dir
106
- ):
107
- return AppCardLoader._mapping_cache
108
-
109
- # Resolve app cards directory
110
- cards_dir_resolved = PathResolver.resolve(app_cards_dir)
111
- mapping_path = cards_dir_resolved / "app_cards.json"
112
-
113
- try:
114
- if not mapping_path.exists():
115
- AppCardLoader._mapping_cache = {}
116
- AppCardLoader._cache_dir = app_cards_dir
117
- return {}
118
-
119
- with open(mapping_path, "r", encoding="utf-8") as f:
120
- mapping = json.load(f)
121
-
122
- AppCardLoader._mapping_cache = mapping
123
- AppCardLoader._cache_dir = app_cards_dir
124
- return mapping
125
- except Exception:
126
- AppCardLoader._mapping_cache = {}
127
- AppCardLoader._cache_dir = app_cards_dir
128
- return {}
129
-
130
- @staticmethod
131
- def clear_cache() -> None:
132
- """Clear all caches (useful for testing or runtime reloading)."""
133
- AppCardLoader._mapping_cache = None
134
- AppCardLoader._cache_dir = None
135
- AppCardLoader._content_cache.clear()
136
-
137
- @staticmethod
138
- def get_cache_stats() -> Dict[str, int]:
139
- """
140
- Get cache statistics.
141
-
142
- Returns:
143
- Dict with cache stats (useful for debugging)
144
- """
145
- return {
146
- "mapping_cached": 1 if AppCardLoader._mapping_cache is not None else 0,
147
- "content_entries": len(AppCardLoader._content_cache),
148
- }