Metadata-Version: 2.4
Name: mobilerun-core
Version: 0.3.0
Summary: Unified Python facade for mobilerun cloud and local device connections, providing high-level automation actions, human-in-the-loop approval hooks for destructive operations, and structured capability-aware error handling.
Project-URL: Homepage, https://github.com/droidrun/mobilerun-core
Project-URL: Repository, https://github.com/droidrun/mobilerun-core
Project-URL: Issues, https://github.com/droidrun/mobilerun-core/issues
Author: droidrun
License: Apache-2.0
Keywords: adb,android,automation,device,ios,mobile,ui-automation
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Testing
Requires-Python: <3.14,>=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: mobilerun-sdk>=3.1.0
Description-Content-Type: text/markdown

<picture align="center">
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/droidrun/mobilerun/main/static/mobilerun-dark.png">
  <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/droidrun/mobilerun/main/static/mobilerun.png">
  <img src="https://raw.githubusercontent.com/droidrun/mobilerun/main/static/mobilerun.png" width="full">
</picture>

<p align="center">
  <strong>mobilerun-core is the programmatic Python API behind Mobilerun.</strong><br>
  One sync facade — `Mobilerun()` — for driving Android (and iOS, on cloud) devices, whether they live in the Mobilerun cloud or on a USB cable. Pick a device id, get a `Device`, call <code>tap_text</code>, <code>scroll_until</code>, <code>wait_for_app</code>. No async/await ceremony, no SDK juggling.
</p>

<div align="center">

<a href="https://docs.mobilerun.ai">📕 Documentation</a>
·
<a href="https://cloud.mobilerun.ai">☁️ Mobilerun Cloud</a>
·
<a href="https://github.com/droidrun/mobilerun">🤖 Mobilerun Framework</a>

[![GitHub stars](https://img.shields.io/github/stars/droidrun/mobilerun-core?style=social)](https://github.com/droidrun/mobilerun-core/stargazers)
[![PyPI](https://img.shields.io/pypi/v/mobilerun-core?color=white)](https://pypi.org/project/mobilerun-core/)
[![Python](https://img.shields.io/pypi/pyversions/mobilerun-core?color=white)](https://pypi.org/project/mobilerun-core/)
[![mobilerun.ai](https://img.shields.io/badge/mobilerun.ai-white)](https://mobilerun.ai)
[![Twitter Follow](https://img.shields.io/twitter/follow/mobilerun_ai?style=social)](https://x.com/mobilerun_ai)
[![Discord](https://img.shields.io/discord/1360219330318696488?color=white&label=Discord&logo=discord&logoColor=white)](https://discord.gg/ZZbKEZZkwK)

</div>

- 🧰 **One API for two transports** — same helpers run against a cloud device (over `mobilerun-sdk`) or a local Android phone (over `mobilerun-core-cli`).
- 🪄 **Auto-detection** — pass any device id, the library figures out cloud vs local. Or override explicitly.
- 🎯 **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`, local `install_apk`) 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](https://github.com/droidrun/mobilerun) when you want a full LLM agent driving the device. Use [Mobilerun Cloud](https://cloud.mobilerun.ai) when you want hosted devices and managed infrastructure.

## 📦 Installation

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

```bash
# cloud-only is enough to start
uv add mobilerun-core
```

```bash
# add local-Android (ADB) support
uv add mobilerun-core mobilerun-core-cli
```

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.

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

## 🚀 Quickstart

```python
from mobilerun_core import Mobilerun

m = Mobilerun()

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

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

Cloud-side discovery is supported too:

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

## 🧱 Concepts

```
Mobilerun()                             single user-facing facade
   │
   │  .connect(id)  →  Device           helpers (tap_text, scroll_until, …)
   │                     │
   │                     ▼
   │              Connection (Protocol)
   │                     │
   │             ┌───────┴────────┐
   │             ▼                ▼
   │      MobilerunCloud    MobilerunFramework
   │      (mobilerun-sdk)   (mobilerun-core-cli)
   │      id = UUID          id = ADB serial
```

- **`Mobilerun`** — the only class users construct. Lazy cloud creds; framework-only use needs no 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:

| `device_id` shape                  | `adb devices` shows it?  | Result        |
| ---------------------------------- | ------------------------ | ------------- |
| UUID                               | no                       | cloud         |
| UUID                               | yes, cloud creds set     | error (pin it)|
| UUID                               | yes, no cloud creds      | framework     |
| ADB serial / emulator / `IP:port`  | yes                      | framework     |
| anything else                      | no                       | error         |

`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(package)`
- `device.install_apk(path, …)` (framework / local APK installs only)

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

```python
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:

```python
from mobilerun_core import UnsupportedOperation

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

Introspect without calling:

```python
UnsupportedOperation.is_supported(MobilerunCloud, "app_install_apk")  # False
UnsupportedOperation.describe(MobilerunCloud, "app_install_apk")
```

## ⚙️ Features

- **Two interchangeable transports** — `MobilerunCloud` wraps `mobilerun-sdk`; `MobilerunFramework` wraps `mobilerun-core-cli` for USB / wireless Android.
- **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`, …
- **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.
- **Back-compat** — `Device(device_id, sdk_client, hitl_gate)` constructor still works for callers pinned to 0.2.x.

## ☁️ Framework vs Cloud vs Core

| | mobilerun-core (this lib) | [Mobilerun Framework](https://github.com/droidrun/mobilerun) | [Mobilerun Cloud](https://cloud.mobilerun.ai) |
| --- | --- | --- | --- |
| 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.

```bash
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](./LICENSE).
