mobilerun-core-local 0.3.0__tar.gz
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.
- mobilerun_core_local-0.3.0/.github/workflows/publish.yml +90 -0
- mobilerun_core_local-0.3.0/.gitignore +27 -0
- mobilerun_core_local-0.3.0/LICENSE +21 -0
- mobilerun_core_local-0.3.0/PKG-INFO +213 -0
- mobilerun_core_local-0.3.0/README.md +185 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/__init__.py +35 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/driver/__init__.py +31 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/driver/android/__init__.py +33 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/driver/android/adb.py +225 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/driver/android/http.py +280 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/driver/android/portal.py +798 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/driver/base.py +195 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/driver/ios/__init__.py +15 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/driver/ios/http.py +445 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/transport/__init__.py +3 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/transport/android/__init__.py +5 -0
- mobilerun_core_local-0.3.0/mobilerun_core_local/transport/android/portal_client.py +758 -0
- mobilerun_core_local-0.3.0/pyproject.toml +39 -0
- mobilerun_core_local-0.3.0/tests/test_local_drivers.py +295 -0
- mobilerun_core_local-0.3.0/uv.lock +368 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
target:
|
|
10
|
+
description: "Where to publish"
|
|
11
|
+
required: true
|
|
12
|
+
default: "pypi"
|
|
13
|
+
type: choice
|
|
14
|
+
options: [pypi, testpypi]
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
name: Build distribution
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v3
|
|
25
|
+
|
|
26
|
+
- name: Set up Python
|
|
27
|
+
run: uv python install 3.12
|
|
28
|
+
|
|
29
|
+
- name: Verify tag matches pyproject version
|
|
30
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
31
|
+
run: |
|
|
32
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
33
|
+
VER=$(uv run --with tomli python -c "import tomli; print(tomli.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
34
|
+
if [ "$TAG" != "$VER" ]; then
|
|
35
|
+
echo "::error::Tag v$TAG does not match pyproject version $VER"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
echo "tag and pyproject agree on $VER"
|
|
39
|
+
|
|
40
|
+
- name: Build sdist + wheel
|
|
41
|
+
run: uv build
|
|
42
|
+
|
|
43
|
+
- name: Show built artifacts
|
|
44
|
+
run: ls -la dist/
|
|
45
|
+
|
|
46
|
+
- uses: actions/upload-artifact@v4
|
|
47
|
+
with:
|
|
48
|
+
name: dist
|
|
49
|
+
path: dist/
|
|
50
|
+
if-no-files-found: error
|
|
51
|
+
|
|
52
|
+
publish-pypi:
|
|
53
|
+
name: Publish to PyPI
|
|
54
|
+
needs: build
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
|
|
57
|
+
environment:
|
|
58
|
+
name: pypi
|
|
59
|
+
url: https://pypi.org/p/mobilerun-core-local
|
|
60
|
+
permissions:
|
|
61
|
+
id-token: write # required for trusted publishing
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/download-artifact@v4
|
|
64
|
+
with:
|
|
65
|
+
name: dist
|
|
66
|
+
path: dist/
|
|
67
|
+
|
|
68
|
+
- name: Publish via Trusted Publishing
|
|
69
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
70
|
+
|
|
71
|
+
publish-testpypi:
|
|
72
|
+
name: Publish to TestPyPI
|
|
73
|
+
needs: build
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
|
|
76
|
+
environment:
|
|
77
|
+
name: testpypi
|
|
78
|
+
url: https://test.pypi.org/p/mobilerun-core-local
|
|
79
|
+
permissions:
|
|
80
|
+
id-token: write
|
|
81
|
+
steps:
|
|
82
|
+
- uses: actions/download-artifact@v4
|
|
83
|
+
with:
|
|
84
|
+
name: dist
|
|
85
|
+
path: dist/
|
|
86
|
+
|
|
87
|
+
- name: Publish to TestPyPI
|
|
88
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
89
|
+
with:
|
|
90
|
+
repository-url: https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Secrets — never commit
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.pyc
|
|
9
|
+
*.pyo
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.coverage
|
|
14
|
+
htmlcov/
|
|
15
|
+
|
|
16
|
+
# Virtualenvs / build artifacts
|
|
17
|
+
.venv/
|
|
18
|
+
venv/
|
|
19
|
+
.venv-test/
|
|
20
|
+
build/
|
|
21
|
+
dist/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
|
|
24
|
+
# Editor / OS noise
|
|
25
|
+
.DS_Store
|
|
26
|
+
.idea/
|
|
27
|
+
.vscode/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 droidrun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mobilerun-core-local
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Slim async local device drivers for Android Portal and iOS Portal, extracted from mobilerun.
|
|
5
|
+
Project-URL: Homepage, https://github.com/droidrun/mobilerun-core-local
|
|
6
|
+
Project-URL: Repository, https://github.com/droidrun/mobilerun-core-local
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/droidrun/mobilerun-core-local/issues
|
|
8
|
+
Project-URL: Upstream, https://github.com/droidrun/mobilerun
|
|
9
|
+
Author: droidrun
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: System :: Hardware
|
|
22
|
+
Requires-Python: <3.14,>=3.11
|
|
23
|
+
Requires-Dist: async-adbutils
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Requires-Dist: requests>=2.31
|
|
26
|
+
Requires-Dist: rich>=14.1.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
<picture align="center">
|
|
30
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/droidrun/mobilerun/main/static/mobilerun-dark.png">
|
|
31
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/droidrun/mobilerun/main/static/mobilerun.png">
|
|
32
|
+
<img src="https://raw.githubusercontent.com/droidrun/mobilerun/main/static/mobilerun.png" width="full">
|
|
33
|
+
</picture>
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<strong>mobilerun-core-local is the slim async local-driver core of <a href="https://github.com/droidrun/mobilerun">mobilerun</a>.</strong><br>
|
|
37
|
+
No CLI, no agent, no LLM providers — just local Android/iOS drivers for higher-level tools such as <code>mobilerun-core</code>.
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<div align="center">
|
|
41
|
+
|
|
42
|
+
<a href="https://docs.mobilerun.ai">📕 Documentation</a>
|
|
43
|
+
·
|
|
44
|
+
<a href="https://github.com/droidrun/mobilerun">🧠 mobilerun framework</a>
|
|
45
|
+
·
|
|
46
|
+
<a href="https://pypi.org/project/mobilerun-core-local/">📦 PyPI</a>
|
|
47
|
+
|
|
48
|
+
[](https://pypi.org/project/mobilerun-core-local/)
|
|
49
|
+
[](https://pypi.org/project/mobilerun-core-local/)
|
|
50
|
+
[](./LICENSE)
|
|
51
|
+
[](https://discord.gg/ZZbKEZZkwK)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
- 📱 Drive local devices — Android over ADB+Portal, Android Portal HTTP-only, or iOS Portal HTTP.
|
|
58
|
+
- ⚡ TCP-with-content-provider fallback — fast Android HTTP path over `adb forward`, transparent fallback to content-provider RPC.
|
|
59
|
+
- 🔌 HTTP-only drivers — connect to already-running Android/iOS portals without taking over setup.
|
|
60
|
+
- 🛠 Portal lifecycle — download, install, accessibility enablement, auto-upgrade.
|
|
61
|
+
- 🪶 Slim — a small async driver package with four runtime deps (`async_adbutils`, `httpx`, `requests`, `rich`).
|
|
62
|
+
- 🔌 Embeddable — designed to be wrapped by sync facades (e.g. `mobilerun-core`) or used directly.
|
|
63
|
+
- 🤝 In sync with upstream — verbatim slice of [`droidrun/mobilerun`](https://github.com/droidrun/mobilerun); behaviour and API track upstream.
|
|
64
|
+
|
|
65
|
+
## 📦 Installation
|
|
66
|
+
|
|
67
|
+
> **Note:** Python `>=3.11,<3.14`. The Android ADB driver requires [ADB](https://developer.android.com/studio/releases/platform-tools) on `PATH` and a device with USB debugging enabled. HTTP-only drivers require an already-running portal URL.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv pip install mobilerun-core-local
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 🚀 Quick usage
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import asyncio
|
|
77
|
+
from async_adbutils import adb
|
|
78
|
+
from mobilerun_core_local import AndroidDriver
|
|
79
|
+
from mobilerun_core_local.driver.android import ensure_portal_ready
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def main():
|
|
83
|
+
# 1. Make sure Portal is installed + accessibility is on.
|
|
84
|
+
device = await adb.device()
|
|
85
|
+
await ensure_portal_ready(device)
|
|
86
|
+
|
|
87
|
+
# 2. Drive the device.
|
|
88
|
+
driver = AndroidDriver(serial=device.serial, use_tcp=True)
|
|
89
|
+
await driver.connect()
|
|
90
|
+
|
|
91
|
+
await driver.tap(540, 1200)
|
|
92
|
+
await driver.swipe(540, 1600, 540, 400, duration_ms=300)
|
|
93
|
+
await driver.input_text("hello", clear=True)
|
|
94
|
+
png_bytes = await driver.screenshot()
|
|
95
|
+
tree = await driver.get_ui_tree()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
asyncio.run(main())
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
HTTP-only Android:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import asyncio
|
|
105
|
+
from mobilerun_core_local import AndroidPortalHttpDriver
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def main():
|
|
109
|
+
driver = AndroidPortalHttpDriver(
|
|
110
|
+
url="http://127.0.0.1:18080",
|
|
111
|
+
token="...",
|
|
112
|
+
)
|
|
113
|
+
await driver.connect()
|
|
114
|
+
await driver.tap(540, 1200)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
asyncio.run(main())
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
iOS Portal:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import asyncio
|
|
124
|
+
from mobilerun_core_local import IOSPortalDriver
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def main():
|
|
128
|
+
driver = IOSPortalDriver("http://127.0.0.1:6643")
|
|
129
|
+
await driver.connect()
|
|
130
|
+
await driver.start_app("com.apple.Preferences")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
asyncio.run(main())
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 🧱 Layout
|
|
137
|
+
|
|
138
|
+
Platform-specific code is namespaced under `driver/<platform>/` and
|
|
139
|
+
`transport/<platform>/`. Adding a new backend (e.g. iOS over USB, a new
|
|
140
|
+
Android transport) does not pollute the package root.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
mobilerun_core_local/
|
|
144
|
+
├── __init__.py Re-exports the public driver surface
|
|
145
|
+
├── driver/
|
|
146
|
+
│ ├── base.py DeviceDriver ABC, DeviceDisconnectedError
|
|
147
|
+
│ ├── android/
|
|
148
|
+
│ │ ├── adb.py AndroidDriver — ADB-backed driver
|
|
149
|
+
│ │ ├── http.py AndroidPortalHttpDriver — HTTP-only driver
|
|
150
|
+
│ │ └── portal.py Portal APK lifecycle + content-provider helpers
|
|
151
|
+
│ └── ios/
|
|
152
|
+
│ └── http.py IOSPortalDriver — ios-portal HTTP driver
|
|
153
|
+
└── transport/
|
|
154
|
+
└── android/
|
|
155
|
+
└── portal_client.py PortalClient — TCP-with-content-provider fallback
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 📚 Public API
|
|
159
|
+
|
|
160
|
+
Re-exported from `mobilerun_core_local`:
|
|
161
|
+
|
|
162
|
+
| Symbol | What it is |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `AndroidDriver` | ADB+Portal device driver. Async methods: `tap`, `swipe`, `input_text`, `press_button`, `start_app`, `install_app`, `screenshot`, `get_ui_tree`, `get_apps`, `list_packages`, `get_date`. |
|
|
165
|
+
| `AndroidPortalHttpDriver` | HTTP-only Android Portal driver. Requires `url` and bearer `token`; does not use ADB at runtime. |
|
|
166
|
+
| `IOSPortalDriver` / `IOSDriver` | iOS Portal HTTP driver. Requires an already-running `ios-portal` URL. |
|
|
167
|
+
| `DeviceDriver` | Abstract base for drivers. `supported: set[str]` declares which verbs a subclass implements. |
|
|
168
|
+
| `DeviceDisconnectedError` | Raised when the device drops mid-call. |
|
|
169
|
+
| `validate_android_portal_url(url)` / `validate_ios_portal_url(url)` | Normalize + sanity-check portal URLs. |
|
|
170
|
+
| `discover_ios_portal(...)` | Scan localhost for a reachable ios-portal HTTP endpoint. |
|
|
171
|
+
|
|
172
|
+
Android Portal helpers live under `mobilerun_core_local.driver.android` (not at
|
|
173
|
+
the package root, since they're Android-specific):
|
|
174
|
+
|
|
175
|
+
| Symbol | What it is |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `setup_portal(device)` | Download + install + enable the Portal APK on a device. |
|
|
178
|
+
| `ensure_portal_ready(device)` | Idempotent: install/upgrade Portal and enable accessibility if needed. |
|
|
179
|
+
| `setup_keyboard(device)` | Switch the device to the Mobilerun IME. |
|
|
180
|
+
| `ping_portal(device)` | Verify Portal is installed and reachable. |
|
|
181
|
+
| `PORTAL_PACKAGE_NAME`, `A11Y_SERVICE_NAME` | Portal identifiers. |
|
|
182
|
+
| `portal_content_uri(pkg, path)` | Build `content://<pkg>/<path>` URIs. |
|
|
183
|
+
| `portal_a11y_service(pkg)`, `portal_ime_id(pkg)` | Accessibility service / IME component names. |
|
|
184
|
+
|
|
185
|
+
`PortalClient` (the TCP-with-content-provider transport) lives at
|
|
186
|
+
`mobilerun_core_local.transport.android.PortalClient` — used internally by
|
|
187
|
+
`AndroidDriver`, exposed for low-level access.
|
|
188
|
+
|
|
189
|
+
`AndroidDriver` accepts:
|
|
190
|
+
|
|
191
|
+
- `serial: str | None` — ADB serial; `None` picks the only connected device.
|
|
192
|
+
- `use_tcp: bool = False` — when `True`, the underlying `PortalClient` port-forwards Portal's HTTP server (`localhost:N → device:8080`) and uses it instead of the content provider. Faster but requires a working forward; falls back transparently.
|
|
193
|
+
|
|
194
|
+
## 🪵 Logging
|
|
195
|
+
|
|
196
|
+
All output goes through the `"mobilerun_core_local"` logger. Configure it yourself; the package attaches no handlers.
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
import logging
|
|
200
|
+
logging.basicConfig(level=logging.INFO)
|
|
201
|
+
logging.getLogger("mobilerun_core_local").setLevel(logging.DEBUG)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## 🔗 Relationship to upstream `mobilerun`
|
|
205
|
+
|
|
206
|
+
This package owns local execution drivers. The full framework imports these drivers for CLI/agent flows; `mobilerun-core` wraps them behind a sync, backend-neutral API.
|
|
207
|
+
|
|
208
|
+
Use `mobilerun-core-local` when you need async local drivers directly.
|
|
209
|
+
Use [`mobilerun`](https://github.com/droidrun/mobilerun) when you want the full LLM-agent experience, CLI/TUI, and multi-platform support out of the box.
|
|
210
|
+
|
|
211
|
+
## 📄 License
|
|
212
|
+
|
|
213
|
+
MIT — see [`LICENSE`](./LICENSE).
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<picture align="center">
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/droidrun/mobilerun/main/static/mobilerun-dark.png">
|
|
3
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/droidrun/mobilerun/main/static/mobilerun.png">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/droidrun/mobilerun/main/static/mobilerun.png" width="full">
|
|
5
|
+
</picture>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>mobilerun-core-local is the slim async local-driver core of <a href="https://github.com/droidrun/mobilerun">mobilerun</a>.</strong><br>
|
|
9
|
+
No CLI, no agent, no LLM providers — just local Android/iOS drivers for higher-level tools such as <code>mobilerun-core</code>.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<div align="center">
|
|
13
|
+
|
|
14
|
+
<a href="https://docs.mobilerun.ai">📕 Documentation</a>
|
|
15
|
+
·
|
|
16
|
+
<a href="https://github.com/droidrun/mobilerun">🧠 mobilerun framework</a>
|
|
17
|
+
·
|
|
18
|
+
<a href="https://pypi.org/project/mobilerun-core-local/">📦 PyPI</a>
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/mobilerun-core-local/)
|
|
21
|
+
[](https://pypi.org/project/mobilerun-core-local/)
|
|
22
|
+
[](./LICENSE)
|
|
23
|
+
[](https://discord.gg/ZZbKEZZkwK)
|
|
24
|
+
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
- 📱 Drive local devices — Android over ADB+Portal, Android Portal HTTP-only, or iOS Portal HTTP.
|
|
30
|
+
- ⚡ TCP-with-content-provider fallback — fast Android HTTP path over `adb forward`, transparent fallback to content-provider RPC.
|
|
31
|
+
- 🔌 HTTP-only drivers — connect to already-running Android/iOS portals without taking over setup.
|
|
32
|
+
- 🛠 Portal lifecycle — download, install, accessibility enablement, auto-upgrade.
|
|
33
|
+
- 🪶 Slim — a small async driver package with four runtime deps (`async_adbutils`, `httpx`, `requests`, `rich`).
|
|
34
|
+
- 🔌 Embeddable — designed to be wrapped by sync facades (e.g. `mobilerun-core`) or used directly.
|
|
35
|
+
- 🤝 In sync with upstream — verbatim slice of [`droidrun/mobilerun`](https://github.com/droidrun/mobilerun); behaviour and API track upstream.
|
|
36
|
+
|
|
37
|
+
## 📦 Installation
|
|
38
|
+
|
|
39
|
+
> **Note:** Python `>=3.11,<3.14`. The Android ADB driver requires [ADB](https://developer.android.com/studio/releases/platform-tools) on `PATH` and a device with USB debugging enabled. HTTP-only drivers require an already-running portal URL.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv pip install mobilerun-core-local
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 🚀 Quick usage
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import asyncio
|
|
49
|
+
from async_adbutils import adb
|
|
50
|
+
from mobilerun_core_local import AndroidDriver
|
|
51
|
+
from mobilerun_core_local.driver.android import ensure_portal_ready
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def main():
|
|
55
|
+
# 1. Make sure Portal is installed + accessibility is on.
|
|
56
|
+
device = await adb.device()
|
|
57
|
+
await ensure_portal_ready(device)
|
|
58
|
+
|
|
59
|
+
# 2. Drive the device.
|
|
60
|
+
driver = AndroidDriver(serial=device.serial, use_tcp=True)
|
|
61
|
+
await driver.connect()
|
|
62
|
+
|
|
63
|
+
await driver.tap(540, 1200)
|
|
64
|
+
await driver.swipe(540, 1600, 540, 400, duration_ms=300)
|
|
65
|
+
await driver.input_text("hello", clear=True)
|
|
66
|
+
png_bytes = await driver.screenshot()
|
|
67
|
+
tree = await driver.get_ui_tree()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
HTTP-only Android:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import asyncio
|
|
77
|
+
from mobilerun_core_local import AndroidPortalHttpDriver
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def main():
|
|
81
|
+
driver = AndroidPortalHttpDriver(
|
|
82
|
+
url="http://127.0.0.1:18080",
|
|
83
|
+
token="...",
|
|
84
|
+
)
|
|
85
|
+
await driver.connect()
|
|
86
|
+
await driver.tap(540, 1200)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
asyncio.run(main())
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
iOS Portal:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import asyncio
|
|
96
|
+
from mobilerun_core_local import IOSPortalDriver
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def main():
|
|
100
|
+
driver = IOSPortalDriver("http://127.0.0.1:6643")
|
|
101
|
+
await driver.connect()
|
|
102
|
+
await driver.start_app("com.apple.Preferences")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
asyncio.run(main())
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 🧱 Layout
|
|
109
|
+
|
|
110
|
+
Platform-specific code is namespaced under `driver/<platform>/` and
|
|
111
|
+
`transport/<platform>/`. Adding a new backend (e.g. iOS over USB, a new
|
|
112
|
+
Android transport) does not pollute the package root.
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
mobilerun_core_local/
|
|
116
|
+
├── __init__.py Re-exports the public driver surface
|
|
117
|
+
├── driver/
|
|
118
|
+
│ ├── base.py DeviceDriver ABC, DeviceDisconnectedError
|
|
119
|
+
│ ├── android/
|
|
120
|
+
│ │ ├── adb.py AndroidDriver — ADB-backed driver
|
|
121
|
+
│ │ ├── http.py AndroidPortalHttpDriver — HTTP-only driver
|
|
122
|
+
│ │ └── portal.py Portal APK lifecycle + content-provider helpers
|
|
123
|
+
│ └── ios/
|
|
124
|
+
│ └── http.py IOSPortalDriver — ios-portal HTTP driver
|
|
125
|
+
└── transport/
|
|
126
|
+
└── android/
|
|
127
|
+
└── portal_client.py PortalClient — TCP-with-content-provider fallback
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 📚 Public API
|
|
131
|
+
|
|
132
|
+
Re-exported from `mobilerun_core_local`:
|
|
133
|
+
|
|
134
|
+
| Symbol | What it is |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `AndroidDriver` | ADB+Portal device driver. Async methods: `tap`, `swipe`, `input_text`, `press_button`, `start_app`, `install_app`, `screenshot`, `get_ui_tree`, `get_apps`, `list_packages`, `get_date`. |
|
|
137
|
+
| `AndroidPortalHttpDriver` | HTTP-only Android Portal driver. Requires `url` and bearer `token`; does not use ADB at runtime. |
|
|
138
|
+
| `IOSPortalDriver` / `IOSDriver` | iOS Portal HTTP driver. Requires an already-running `ios-portal` URL. |
|
|
139
|
+
| `DeviceDriver` | Abstract base for drivers. `supported: set[str]` declares which verbs a subclass implements. |
|
|
140
|
+
| `DeviceDisconnectedError` | Raised when the device drops mid-call. |
|
|
141
|
+
| `validate_android_portal_url(url)` / `validate_ios_portal_url(url)` | Normalize + sanity-check portal URLs. |
|
|
142
|
+
| `discover_ios_portal(...)` | Scan localhost for a reachable ios-portal HTTP endpoint. |
|
|
143
|
+
|
|
144
|
+
Android Portal helpers live under `mobilerun_core_local.driver.android` (not at
|
|
145
|
+
the package root, since they're Android-specific):
|
|
146
|
+
|
|
147
|
+
| Symbol | What it is |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `setup_portal(device)` | Download + install + enable the Portal APK on a device. |
|
|
150
|
+
| `ensure_portal_ready(device)` | Idempotent: install/upgrade Portal and enable accessibility if needed. |
|
|
151
|
+
| `setup_keyboard(device)` | Switch the device to the Mobilerun IME. |
|
|
152
|
+
| `ping_portal(device)` | Verify Portal is installed and reachable. |
|
|
153
|
+
| `PORTAL_PACKAGE_NAME`, `A11Y_SERVICE_NAME` | Portal identifiers. |
|
|
154
|
+
| `portal_content_uri(pkg, path)` | Build `content://<pkg>/<path>` URIs. |
|
|
155
|
+
| `portal_a11y_service(pkg)`, `portal_ime_id(pkg)` | Accessibility service / IME component names. |
|
|
156
|
+
|
|
157
|
+
`PortalClient` (the TCP-with-content-provider transport) lives at
|
|
158
|
+
`mobilerun_core_local.transport.android.PortalClient` — used internally by
|
|
159
|
+
`AndroidDriver`, exposed for low-level access.
|
|
160
|
+
|
|
161
|
+
`AndroidDriver` accepts:
|
|
162
|
+
|
|
163
|
+
- `serial: str | None` — ADB serial; `None` picks the only connected device.
|
|
164
|
+
- `use_tcp: bool = False` — when `True`, the underlying `PortalClient` port-forwards Portal's HTTP server (`localhost:N → device:8080`) and uses it instead of the content provider. Faster but requires a working forward; falls back transparently.
|
|
165
|
+
|
|
166
|
+
## 🪵 Logging
|
|
167
|
+
|
|
168
|
+
All output goes through the `"mobilerun_core_local"` logger. Configure it yourself; the package attaches no handlers.
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import logging
|
|
172
|
+
logging.basicConfig(level=logging.INFO)
|
|
173
|
+
logging.getLogger("mobilerun_core_local").setLevel(logging.DEBUG)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## 🔗 Relationship to upstream `mobilerun`
|
|
177
|
+
|
|
178
|
+
This package owns local execution drivers. The full framework imports these drivers for CLI/agent flows; `mobilerun-core` wraps them behind a sync, backend-neutral API.
|
|
179
|
+
|
|
180
|
+
Use `mobilerun-core-local` when you need async local drivers directly.
|
|
181
|
+
Use [`mobilerun`](https://github.com/droidrun/mobilerun) when you want the full LLM-agent experience, CLI/TUI, and multi-platform support out of the box.
|
|
182
|
+
|
|
183
|
+
## 📄 License
|
|
184
|
+
|
|
185
|
+
MIT — see [`LICENSE`](./LICENSE).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Slim async local-device driver core, extracted from droidrun/mobilerun."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.2.0"
|
|
4
|
+
|
|
5
|
+
from mobilerun_core_local.driver.android import (
|
|
6
|
+
AndroidDriver,
|
|
7
|
+
AndroidPortalHttpDriver,
|
|
8
|
+
validate_android_portal_url,
|
|
9
|
+
)
|
|
10
|
+
from mobilerun_core_local.driver.base import (
|
|
11
|
+
DeviceDisconnectedError,
|
|
12
|
+
DeviceDriver,
|
|
13
|
+
PlatformUnsupportedError,
|
|
14
|
+
unsupported,
|
|
15
|
+
)
|
|
16
|
+
from mobilerun_core_local.driver.ios import (
|
|
17
|
+
IOSDriver,
|
|
18
|
+
IOSPortalDriver,
|
|
19
|
+
discover_ios_portal,
|
|
20
|
+
validate_ios_portal_url,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"AndroidDriver",
|
|
25
|
+
"AndroidPortalHttpDriver",
|
|
26
|
+
"DeviceDriver",
|
|
27
|
+
"DeviceDisconnectedError",
|
|
28
|
+
"IOSDriver",
|
|
29
|
+
"IOSPortalDriver",
|
|
30
|
+
"PlatformUnsupportedError",
|
|
31
|
+
"discover_ios_portal",
|
|
32
|
+
"unsupported",
|
|
33
|
+
"validate_android_portal_url",
|
|
34
|
+
"validate_ios_portal_url",
|
|
35
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from mobilerun_core_local.driver.android import (
|
|
2
|
+
AndroidDriver,
|
|
3
|
+
AndroidPortalHttpDriver,
|
|
4
|
+
validate_android_portal_url,
|
|
5
|
+
)
|
|
6
|
+
from mobilerun_core_local.driver.base import (
|
|
7
|
+
DeviceDisconnectedError,
|
|
8
|
+
DeviceDriver,
|
|
9
|
+
PlatformUnsupportedError,
|
|
10
|
+
unsupported,
|
|
11
|
+
)
|
|
12
|
+
from mobilerun_core_local.driver.ios import (
|
|
13
|
+
IOSDriver,
|
|
14
|
+
IOSPortalDriver,
|
|
15
|
+
discover_ios_portal,
|
|
16
|
+
validate_ios_portal_url,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"AndroidDriver",
|
|
21
|
+
"AndroidPortalHttpDriver",
|
|
22
|
+
"DeviceDriver",
|
|
23
|
+
"DeviceDisconnectedError",
|
|
24
|
+
"IOSDriver",
|
|
25
|
+
"IOSPortalDriver",
|
|
26
|
+
"PlatformUnsupportedError",
|
|
27
|
+
"discover_ios_portal",
|
|
28
|
+
"unsupported",
|
|
29
|
+
"validate_android_portal_url",
|
|
30
|
+
"validate_ios_portal_url",
|
|
31
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Android-side drivers and Portal helpers."""
|
|
2
|
+
|
|
3
|
+
from mobilerun_core_local.driver.android.adb import AndroidDriver
|
|
4
|
+
from mobilerun_core_local.driver.android.http import (
|
|
5
|
+
AndroidPortalHttpDriver,
|
|
6
|
+
validate_android_portal_url,
|
|
7
|
+
)
|
|
8
|
+
from mobilerun_core_local.driver.android.portal import (
|
|
9
|
+
A11Y_SERVICE_NAME,
|
|
10
|
+
PORTAL_PACKAGE_NAME,
|
|
11
|
+
ensure_portal_ready,
|
|
12
|
+
ping_portal,
|
|
13
|
+
portal_a11y_service,
|
|
14
|
+
portal_content_uri,
|
|
15
|
+
portal_ime_id,
|
|
16
|
+
setup_keyboard,
|
|
17
|
+
setup_portal,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"AndroidDriver",
|
|
22
|
+
"AndroidPortalHttpDriver",
|
|
23
|
+
"validate_android_portal_url",
|
|
24
|
+
"A11Y_SERVICE_NAME",
|
|
25
|
+
"PORTAL_PACKAGE_NAME",
|
|
26
|
+
"ensure_portal_ready",
|
|
27
|
+
"ping_portal",
|
|
28
|
+
"portal_a11y_service",
|
|
29
|
+
"portal_content_uri",
|
|
30
|
+
"portal_ime_id",
|
|
31
|
+
"setup_keyboard",
|
|
32
|
+
"setup_portal",
|
|
33
|
+
]
|