droidrun 0.1.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.
droidrun/cli/main.py ADDED
@@ -0,0 +1,265 @@
1
+ """
2
+ DroidRun CLI - Command line interface for controlling Android devices through LLM agents.
3
+ """
4
+
5
+ import asyncio
6
+ import click
7
+ import os
8
+ from rich.console import Console
9
+ from rich import print as rprint
10
+ from droidrun.tools import DeviceManager
11
+ from droidrun.agent import run_agent
12
+ from functools import wraps
13
+
14
+ # Import the install_app function directly for the setup command
15
+ from droidrun.tools.actions import install_app
16
+
17
+ console = Console()
18
+ device_manager = DeviceManager()
19
+
20
+ def coro(f):
21
+ @wraps(f)
22
+ def wrapper(*args, **kwargs):
23
+ return asyncio.run(f(*args, **kwargs))
24
+ return wrapper
25
+
26
+ # Define the run command as a standalone function to be used as both a command and default
27
+ @coro
28
+ async def run_command(command: str, device: str | None, provider: str, model: str, steps: int, vision: bool):
29
+ """Run a command on your Android device using natural language."""
30
+ console.print(f"[bold blue]Executing command:[/] {command}")
31
+
32
+ # Auto-detect Gemini if model starts with "gemini-"
33
+ if model and model.startswith("gemini-"):
34
+ provider = "gemini"
35
+
36
+ # Print vision status
37
+ if vision:
38
+ console.print("[blue]Vision capabilities are enabled.[/]")
39
+ else:
40
+ console.print("[blue]Vision capabilities are disabled.[/]")
41
+
42
+ # Get API keys from environment variables
43
+ api_key = None
44
+ if provider.lower() == 'openai':
45
+ api_key = os.environ.get('OPENAI_API_KEY')
46
+ if not api_key:
47
+ console.print("[bold red]Error:[/] OPENAI_API_KEY environment variable not set")
48
+ return
49
+ if not model:
50
+ model = "gpt-4o-mini"
51
+ elif provider.lower() == 'anthropic':
52
+ api_key = os.environ.get('ANTHROPIC_API_KEY')
53
+ if not api_key:
54
+ console.print("[bold red]Error:[/] ANTHROPIC_API_KEY environment variable not set")
55
+ return
56
+ if not model:
57
+ model = "claude-3-sonnet-20240229"
58
+ elif provider.lower() == 'gemini':
59
+ api_key = os.environ.get('GEMINI_API_KEY')
60
+ if not api_key:
61
+ console.print("[bold red]Error:[/] GEMINI_API_KEY environment variable not set")
62
+ return
63
+ if not model:
64
+ model = "gemini-2.0-flash"
65
+ else:
66
+ console.print(f"[bold red]Error:[/] Unsupported provider: {provider}")
67
+ return
68
+
69
+ try:
70
+ # Try to find a device if none specified
71
+ if not device:
72
+ devices = await device_manager.list_devices()
73
+ if not devices:
74
+ console.print("[yellow]No devices connected.[/]")
75
+ return
76
+
77
+ device = devices[0].serial
78
+ console.print(f"[blue]Using device:[/] {device}")
79
+
80
+ # Set the device serial in the environment variable
81
+ os.environ["DROIDRUN_DEVICE_SERIAL"] = device
82
+ console.print(f"[blue]Set DROIDRUN_DEVICE_SERIAL to:[/] {device}")
83
+
84
+ # Run the agent
85
+ console.print("[bold blue]Running ReAct agent...[/]")
86
+ console.print("[yellow]Press Ctrl+C to stop execution[/]")
87
+
88
+ try:
89
+ steps = await run_agent(
90
+ task=command,
91
+ device_serial=device, # Still pass for backward compatibility
92
+ llm_provider=provider,
93
+ model_name=model,
94
+ api_key=api_key,
95
+ vision=vision
96
+ )
97
+
98
+ # Final message
99
+ console.print(f"[bold green]Execution completed with {len(steps)} steps[/]")
100
+ except ValueError as e:
101
+ if "does not support vision" in str(e):
102
+ console.print(f"[bold red]Vision Error:[/] {e}")
103
+ console.print("[yellow]Please specify a vision-capable model with the --model flag.[/]")
104
+ console.print("[blue]Recommended models:[/]")
105
+ console.print(" - OpenAI: gpt-4o or gpt-4-vision")
106
+ console.print(" - Anthropic: claude-3-opus-20240229 or claude-3-sonnet-20240229")
107
+ console.print(" - Gemini: gemini-pro-vision")
108
+ return
109
+ else:
110
+ raise # Re-raise other ValueError exceptions
111
+
112
+ except Exception as e:
113
+ console.print(f"[bold red]Error:[/] {e}")
114
+
115
+ # Custom Click multi-command class to handle both subcommands and default behavior
116
+ class DroidRunCLI(click.Group):
117
+ def parse_args(self, ctx, args):
118
+ # Check if the first argument might be a task rather than a command
119
+ if args and not args[0].startswith('-') and args[0] not in self.commands:
120
+ # Insert the 'run' command before the first argument if it's not a known command
121
+ args.insert(0, 'run')
122
+ return super().parse_args(ctx, args)
123
+
124
+ @click.group(cls=DroidRunCLI)
125
+ def cli():
126
+ """DroidRun - Control your Android device through LLM agents."""
127
+ pass
128
+
129
+ @cli.command()
130
+ @click.argument('command', type=str)
131
+ @click.option('--device', '-d', help='Device serial number or IP address', default=None)
132
+ @click.option('--provider', '-p', help='LLM provider (openai, anthropic, or gemini)', default='openai')
133
+ @click.option('--model', '-m', help='LLM model name', default=None)
134
+ @click.option('--steps', type=int, help='Maximum number of steps', default=15)
135
+ @click.option('--vision', is_flag=True, help='Enable vision capabilities')
136
+ def run(command: str, device: str | None, provider: str, model: str, steps: int, vision: bool):
137
+ """Run a command on your Android device using natural language."""
138
+ # Call our standalone function
139
+ return run_command(command, device, provider, model, steps, vision)
140
+
141
+ @cli.command()
142
+ @coro
143
+ async def devices():
144
+ """List connected Android devices."""
145
+ try:
146
+ devices = await device_manager.list_devices()
147
+ if not devices:
148
+ console.print("[yellow]No devices connected.[/]")
149
+ return
150
+
151
+ console.print(f"[green]Found {len(devices)} connected device(s):[/]")
152
+ for device in devices:
153
+ console.print(f" • [bold]{device.serial}[/]")
154
+ except Exception as e:
155
+ console.print(f"[red]Error listing devices: {e}[/]")
156
+
157
+ @cli.command()
158
+ @click.argument('ip_address')
159
+ @click.option('--port', '-p', default=5555, help='ADB port (default: 5555)')
160
+ @coro
161
+ async def connect(ip_address: str, port: int):
162
+ """Connect to a device over TCP/IP."""
163
+ try:
164
+ device = await device_manager.connect(ip_address, port)
165
+ if device:
166
+ console.print(f"[green]Successfully connected to {ip_address}:{port}[/]")
167
+ else:
168
+ console.print(f"[red]Failed to connect to {ip_address}:{port}[/]")
169
+ except Exception as e:
170
+ console.print(f"[red]Error connecting to device: {e}[/]")
171
+
172
+ @cli.command()
173
+ @click.argument('serial')
174
+ @coro
175
+ async def disconnect(serial: str):
176
+ """Disconnect from a device."""
177
+ try:
178
+ success = await device_manager.disconnect(serial)
179
+ if success:
180
+ console.print(f"[green]Successfully disconnected from {serial}[/]")
181
+ else:
182
+ console.print(f"[yellow]Device {serial} was not connected[/]")
183
+ except Exception as e:
184
+ console.print(f"[red]Error disconnecting from device: {e}[/]")
185
+
186
+ @cli.command()
187
+ @click.option('--path', required=True, help='Path to the APK file to install')
188
+ @click.option('--device', '-d', help='Device serial number or IP address', default=None)
189
+ @coro
190
+ async def setup(path: str, device: str | None):
191
+ """Install an APK file and enable it as an accessibility service."""
192
+ try:
193
+ # Check if APK file exists
194
+ if not os.path.exists(path):
195
+ console.print(f"[bold red]Error:[/] APK file not found at {path}")
196
+ return
197
+
198
+ # Try to find a device if none specified
199
+ if not device:
200
+ devices = await device_manager.list_devices()
201
+ if not devices:
202
+ console.print("[yellow]No devices connected.[/]")
203
+ return
204
+
205
+ device = devices[0].serial
206
+ console.print(f"[blue]Using device:[/] {device}")
207
+
208
+ # Set the device serial in the environment variable
209
+ os.environ["DROIDRUN_DEVICE_SERIAL"] = device
210
+ console.print(f"[blue]Set DROIDRUN_DEVICE_SERIAL to:[/] {device}")
211
+
212
+ # Get a device object for ADB commands
213
+ device_obj = await device_manager.get_device(device)
214
+ if not device_obj:
215
+ console.print(f"[bold red]Error:[/] Could not get device object for {device}")
216
+ return
217
+
218
+ # Step 1: Install the APK file
219
+ console.print(f"[bold blue]Step 1/2: Installing APK:[/] {path}")
220
+ result = await install_app(path, False, True, device)
221
+
222
+ if "Error" in result:
223
+ console.print(f"[bold red]Installation failed:[/] {result}")
224
+ return
225
+ else:
226
+ console.print(f"[bold green]Installation successful![/]")
227
+
228
+ # Step 2: Enable the accessibility service with the specific command
229
+ console.print(f"[bold blue]Step 2/2: Enabling accessibility service[/]")
230
+
231
+ # Package name for reference in error message
232
+ package = "com.droidrun.portal"
233
+
234
+ try:
235
+ # Use the exact command provided
236
+ await device_obj._adb.shell(device, "settings put secure enabled_accessibility_services com.droidrun.portal/com.droidrun.portal.DroidrunPortalService")
237
+
238
+ # Also enable accessibility services globally
239
+ await device_obj._adb.shell(device, "settings put secure accessibility_enabled 1")
240
+
241
+ console.print("[green]Accessibility service enabled successfully![/]")
242
+ console.print("\n[bold green]Setup complete![/] The DroidRun Portal is now installed and ready to use.")
243
+
244
+ except Exception as e:
245
+ console.print(f"[yellow]Could not automatically enable accessibility service: {e}[/]")
246
+ console.print("[yellow]Opening accessibility settings for manual configuration...[/]")
247
+
248
+ # Fallback: Open the accessibility settings page
249
+ await device_obj._adb.shell(device, "am start -a android.settings.ACCESSIBILITY_SETTINGS")
250
+
251
+ console.print("\n[yellow]Please complete the following steps on your device:[/]")
252
+ console.print(f"1. Find [bold]{package}[/] in the accessibility services list")
253
+ console.print("2. Tap on the service name")
254
+ console.print("3. Toggle the switch to [bold]ON[/] to enable the service")
255
+ console.print("4. Accept any permission dialogs that appear")
256
+
257
+ console.print("\n[bold green]APK installation complete![/] Please manually enable the accessibility service using the steps above.")
258
+
259
+ except Exception as e:
260
+ console.print(f"[bold red]Error:[/] {e}")
261
+ import traceback
262
+ traceback.print_exc()
263
+
264
+ if __name__ == '__main__':
265
+ cli()
@@ -0,0 +1,24 @@
1
+ """
2
+ DroidRun LLM Module.
3
+
4
+ This module provides LLM providers for the Agent to use for reasoning.
5
+ """
6
+
7
+ from droidrun.agent.llm_reasoning import LLMReasoner as BaseLLM
8
+
9
+ # Alias OpenAILLM and AnthropicLLM for backward compatibility
10
+ class OpenAILLM(BaseLLM):
11
+ """
12
+ OpenAI-based LLM provider.
13
+ """
14
+ def __init__(self, model="gpt-4o", **kwargs):
15
+ super().__init__(provider="openai", model=model, **kwargs)
16
+
17
+ class AnthropicLLM(BaseLLM):
18
+ """
19
+ Anthropic-based LLM provider.
20
+ """
21
+ def __init__(self, model="claude-3-opus-20240229", **kwargs):
22
+ super().__init__(provider="anthropic", model=model, **kwargs)
23
+
24
+ __all__ = ["BaseLLM", "OpenAILLM", "AnthropicLLM"]
@@ -0,0 +1,35 @@
1
+ """
2
+ DroidRun Tools - Core functionality for Android device control.
3
+ """
4
+
5
+ from .device import DeviceManager
6
+ from .actions import (
7
+ tap,
8
+ swipe,
9
+ input_text,
10
+ press_key,
11
+ start_app,
12
+ install_app,
13
+ uninstall_app,
14
+ take_screenshot,
15
+ list_packages,
16
+ get_clickables,
17
+ complete,
18
+ extract,
19
+ )
20
+
21
+ __all__ = [
22
+ 'DeviceManager',
23
+ 'tap',
24
+ 'swipe',
25
+ 'input_text',
26
+ 'press_key',
27
+ 'start_app',
28
+ 'install_app',
29
+ 'uninstall_app',
30
+ 'take_screenshot',
31
+ 'list_packages',
32
+ 'get_clickables',
33
+ 'complete',
34
+ 'extract',
35
+ ]