Skip to main content

mobilerun-core is the programmatic Python API behind Mobilerun.
One sync facade — `Mobilerun()` — for driving Android and iOS devices, whether they live in the Mobilerun cloud, on USB, or behind a local Portal HTTP URL. Pick a backend, get a `Device`, call tap_text, scroll_until, wait_for_app. No async/await ceremony, no SDK juggling.

  • 🧰 One API for cloud and local — same helpers run against cloud, local Android ADB with optional Portal, local Android Portal HTTP-only, or local iOS Portal HTTP.
  • 🪄 Explicit backends with auto-detection — pass a UUID/ADB serial, or pin backend="cloud", backend="local-android-adb", backend="local-android-http", or backend="local-ios-http".
  • 🎯 High-level helpers — tap_text, tap_node, scroll_until, wait_for_app, open_and_settle, find_nodes, assert_on, screen_size, … built on top of the raw verbs.
  • 🛡️ HITL gate — destructive verbs (uninstall_app, local install_app) route through a callable you control. Default denies; you opt in per turn.
  • 🧭 Agent-friendly errors — when a backend doesn't support a verb, you get a structured UnsupportedOperation with verb, backend, and an alternative field instead of an opaque traceback.
  • 🪶 Pure library — sync, heredoc-friendly, no daemon, no server, no agent loop.

Use the library when you want to script a device directly from Python — locally or in the cloud. Use Mobilerun Framework when you want a full LLM agent driving the device. Use Mobilerun Cloud when you want hosted devices and managed infrastructure.

📦 Installation

Note: Python 3.14 is not currently supported. Please use Python >=3.11,<3.14.

# cloud-only is enough to start
uv add mobilerun-core
# add local Android/iOS driver support via the `[local]` extra
uv add 'mobilerun-core[local]'
# equivalent to: uv add mobilerun-core mobilerun-core-local

Local requirements:

  • adb on PATH and the device showing as device (not unauthorized / offline) in adb devices.
  • The screen unlocked before driving the device. The library can't bypass the keyguard, and a locked screen makes the a11y tree return only the lockscreen overlay regardless of what activity you launched.
  • Android Portal HTTP-only requires url= and token= or MOBILERUN_ANDROID_PORTAL_URL plus MOBILERUN_ANDROID_PORTAL_TOKEN.
  • iOS Portal HTTP requires url= or MOBILERUN_IOS_PORTAL_URL; the portal must already be running.

Cloud credentials are picked up lazily — Mobilerun() does not touch the environment until you make a cloud call. For local-only use, no env vars are required.

# only needed for cloud
export MOBILERUN_CLOUD_API_KEY=...
export MOBILERUN_API_BASE_URL=https://api.mobilerun.ai/v1

🚀 Quickstart

from mobilerun_core import Mobilerun

m = Mobilerun()

# auto-detect legacy cloud/local from the device id
d = m.connect("550e8400-e29b-41d4-a716-446655440000")   # cloud (UUID)
d = m.connect("R5CT123456")                              # local Android ADB serial
d = m.connect(some_id, cloud=True)                       # explicit compatibility override

# explicit local backends
d = m.connect("R5CT123456", backend="local-android-adb")
d = m.connect(
    backend="local-android-http",
    url="http://127.0.0.1:18080",
    token="...",
)
d = m.connect(backend="local-ios-http", url="http://127.0.0.1:6643")

# drive the device
d.start_app("com.instagram.android")
d.tap_text("Search")
d.type("droidrun")
d.key("enter")
png_b64 = d.screenshot()

if d.supports("stop_app"):
    d.stop_app("com.instagram.android")

Cloud-side discovery is supported too:

m = Mobilerun()
d = m.ensure_device(filters={"name": ["pixel - test"]})  # one matching ready device
all_devices = m.list_devices(filters={"state": ["ready"]})
local_devices = m.list_devices(scope="local")

🧱 Concepts

Mobilerun()                             single user-facing facade
   │
   │  .connect(id)  →  Device           helpers (tap_text, scroll_until, …)
   │                     │
   │                     ▼
   │              Connection (Protocol)
   │                     │
   │             ┌──────────┴──────────┐
   │             ▼                     ▼
   │      MobilerunCloud       Local driver connections
   │      (mobilerun-sdk)      (mobilerun-core-local)
   │      id = UUID            ADB serial or Portal URL
  • Mobilerun — the only class users construct. Lazy cloud creds; local-only use needs no cloud env vars.
  • Device — what you get back from .connect() / .ensure_device(). All the helpers live here.
  • Connection — sync per-device contract (tap, swipe, type, ui, screenshot, app_*). One implementation per transport.

🪄 Backend selection

Explicit override always wins. Otherwise:

Selector Result
UUID cloud
ADB serial / emulator / IP:port local-android-adb
backend="local-android-http" local-android-http
backend="local-ios-http" local-ios-http
url=... without platform= error; pass platform="android" or platform="ios"

adb devices lookups filter state=="device" — offline / unauthorized rows don't count.

🛡️ HITL gate

Destructive verbs route through a HitlGate callable. Default is deny_all — pass your own gate to allow specific actions per turn.

Gated today:

  • device.uninstall_app(app_id)
  • device.install_app(path, …)

Not gated: tap, swipe, type, key("power"), stop_app. Those are either non-destructive or trivially reversible.

from mobilerun_core import Mobilerun, HitlDenied

def my_gate(action: str, args: dict) -> None:
    if not user_approved(action, args):
        raise HitlDenied(action)

m = Mobilerun(hitl_gate=my_gate)

🧭 Agent-friendly unsupported verbs

The Connection Protocol is one surface, but each backend supports only a subset by design. When a verb isn't supported on the active backend, UnsupportedOperation is raised — subclasses NotImplementedError for back-compat, but carries structured fields an agent can branch on:

from mobilerun_core import UnsupportedOperation

try:
    device.install_app("/tmp/app.apk")   # not supported on a cloud device
except UnsupportedOperation as e:
    payload = e.as_dict()
    # {
    #   "error": "unsupported_operation",
    #   "verb": "install_app",
    #   "backend": "cloud",
    #   "reason": "...",
    #   "alternative": null,
    #   "hint": null,
    # }

Introspect connection-level methods without calling:

UnsupportedOperation.is_supported(MobilerunCloud, "app_install")  # False
UnsupportedOperation.describe(MobilerunCloud, "app_install")

⚙️ Features

  • Backend-neutral control — MobilerunCloud wraps mobilerun-sdk; local connections wrap mobilerun-core-local drivers.
  • Sync API — heredoc-friendly. No await, no event loop.
  • Helpers, not just verbs — tap_text, tap_and_wait, scroll_until, wait_for_app, wait_for_idle, open_and_settle, find_nodes, assert_on, screen_size, …
  • Capabilities — device.capabilities and device.supports(action) tell agents which verbs work on the active backend.
  • Normalized return shapes — ui() always returns a plain dict; screenshot() always returns base64 PNG.
  • Lazy cloud credentials — Mobilerun() doesn't touch the env until you make a cloud call.
  • Single ctor — Device(connection, hitl_gate). The 0.2.x three-argument form (Device(device_id, sdk_client, hitl_gate)) was removed in 0.5.0.

☁️ Framework vs Cloud vs Core

mobilerun-core (this lib) Mobilerun Framework Mobilerun Cloud
What Programmatic device-control API Full LLM agent + CLI Hosted devices + REST + dashboard
Best for Code-level scripting, custom tools, custom agents Natural-language tasks, reasoning, vision Managed phones, fleet workflows, APIs
Where it runs Wherever your Python runs Wherever your Python runs Managed by Mobilerun
LLM included? No (you bring it) Yes (OpenAI / Anthropic / etc) N/A

Most users start with the Framework. Reach for mobilerun-core when you want to build something the Framework doesn't ship — a custom agent loop, a test runner, a recording / replay tool, or batch automation.

💡 Example use cases

  • Mobile app QA and regression testing.
  • End-to-end flows in CI that target a real device or an emulator.
  • Hybrid dev/CI workflows: same script targets your phone over USB locally, and a cloud device in CI.
  • Building higher-level agent frameworks on top of a stable device API.
  • Recording / replaying user flows for benchmarking.

🤝 Contributing

Issues and PRs welcome. The library aims to stay small and sharply-scoped — please open an issue before adding new surface.

git clone https://github.com/droidrun/mobilerun-core.git
cd mobilerun-core
uv venv
uv pip install -e . pytest ruff
.venv/bin/python -m pytest tests/test_abstraction.py -v
.venv/bin/ruff check mobilerun_core tests

📄 License

Apache-2.0. See LICENSE.

Release files for mobilerun-core 1.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mobilerun-core 1.5.1
File Size Uploaded
mobilerun_core-1.5.1.tar.gz 99.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mobilerun-core 1.5.1
File Interpreter ABI Platform
mobilerun_core-1.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 152.4 kB

Release files / mobilerun_core-1.5.1.tar.gz

Download URL mobilerun_core-1.5.1.tar.gz
Size 99.0 kB
Tags Source
SHA-256 checksum
How to use checksums
9df9ed4e8deea4357cbb6febb471f3f3df91d2f2676ba122f99d5c93e7005932
BLAKE2b-256 checksum
How to use checksums
060d67073a45066f0dd4afae1f24a80c504d3ee28ad8e902b3bb3548282a8175
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Release files / mobilerun_core-1.5.1-py3-none-any.whl

Download URL mobilerun_core-1.5.1-py3-none-any.whl
Size 53.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e19b9eca38ad3c202c2e6617a495cbb4cafb1de79a3ec1172c7a01f793f0afaf
BLAKE2b-256 checksum
How to use checksums
9113481e326a79d1def777af119e75c0ad98965c519f9f3064fc0301a7ae4a11
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Release history Release notifications | RSS feed

1.7.0

2 release files

1.6.2

2 release files

1.6.1

2 release files

1.6.0

2 release files

This release

1.5.1 This release

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page