Skip to main content

MOABile — mother of all mobile

PyPI License: MIT Python 3.10+ Platform Tested on: Kali Linux Coverage

A multi-device terminal UI for mobile app testing. Android over adb, jailbroken iOS over usbmux and ssh, both at the same time, each attached device in its own panel.

MOABile Multi-Device Interface

MOABile Splashscreen Dual-Pane File Manager

It does not reimplement the toolchain: it drives adb, scrcpy, libimobiledevice, iproxy, ioscpy, frida, objection, curl and xz, which have to be on PATH already. The startup screen reports what is missing and which of the two device families that leaves usable — an Android-only machine never needs libimobiledevice installed to get past it.

An iPhone is reached with libimobiledevice for everything that needs no cooperation from the phone (device info, syslog, installing an ipa) and with ssh down an iproxy tunnel for a shell, the filesystem and frida-server. For SSH features on jailbroken iOS, the phone must have OpenSSH installed (available in standard repositories via Sileo, Zebra, or Cydia) with sshd listening on port 22. The ssh password is asked for once and kept in memory. No key is ever installed on the phone: that would be a file of ours left behind on someone else's device.

Nothing is assumed to be installed on the phone either. Jailbreaks ship different halves of a userland, so where a tool the panel asks for is not there it says so once and falls back — the address row reads usb for a phone on the end of a cable with nothing on the network, because that is what there is to say: usbmux carries no address, and lockdownd hands out the wifi MAC and never the lease. What the phone does have is found, though: every command carries the PATH a non-interactive ssh leaves out — /usr/sbin, and everything a rootless jailbreak keeps under /var/jb — inside sudo as well as outside it, so grep and open are not reported missing on a phone that has them.

Installation & Run

Run directly on the fly without permanent installation:

uvx moabile

Or install globally in an isolated environment:

uv tool install moabile

# Run
moabile

# Upgrade
uv tool upgrade moabile

Alternative: pipx (Isolated CLI)

# Install globally in an isolated environment
pipx install moabile

# Run
moabile

# Or run directly on the fly without permanent installation:
pipx run moabile

# Upgrade
pipx upgrade moabile

Via pip (or uv pip)

# With uv
uv pip install moabile

# With pip
pip install moabile

# Run
moabile

From Source

Clone the repository and install requirements:

# With uv (fast & isolated)
uv venv
uv pip install -r requirements.txt
python3 moabile.py

# Or with python venv & pip
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
python3 moabile.py

Python 3.10 or newer, and Textual 6.2 or newer — the floor requirements.txt names, and the oldest release the suite passes on. Linux and macOS: it needs a real pty, so it does not run on Windows outside WSL.

Tested and fully verified on Kali Linux (x86_64).

No arguments and no options — --help says as much as there is to say, and everything else is a key inside.

Tools it drives

The startup screen checks for these and reports each one's version. Nothing here is installed for you — which package manager your machine has, and what it calls a package, is your business.

Tool For Family From
frida instrumentation, and its version both frida-tools
objection exploration REPL both
curl fetch frida-server and codeshare both
xz unpack frida-server both XZ Utils
adb device control android android platform-tools
scrcpy screen mirroring android
idevice_id device discovery, info and syslog ios libimobiledevice
ideviceinstaller ipa install and app list ios
iproxy ssh tunnel over usb ios libusbmuxd
ssh shell, filesystem and frida-server ios OpenSSH
ioscpy screen mirroring ios lautarovculic/ioscpy

Either family works on its own. Both missing, or a core tool missing, and the gate does not let you through — there would be nothing past it to do.

Keys

r rescan for devices i device info dump
b the sidebar, on and off u ssh user for this device
f frida-server, on and off p purge frida (device & client venvs)
s spawn the app under frida, or attach k clear this panel's log
o explore the app with objection / filter log stream by keyword
w mirror the screen in a window x wipe app data (without uninstall)
t terminal on the device v save an svg of the interface
l stream the device log m dark/light mode
d files: browse host ↔ device h help: keys and widgets
a add an app: install an apk or ipa f8 return focus from tool pane
e export the app's apk/ipa q, ctrl+q quit

x clears the selected app's user data, cache, and preferences without uninstalling it (pm clear on Android; on iOS it wipes the application data container, associated App Groups, and resets NSUserDefaults by purging cfprefsd, recreating clean directories with proper ownership).

w mirrors the screen in an external window (scrcpy on Android, ioscpy on iOS). On iOS, screen mirroring requires the ioscpy server tweak (com.ioscpy.device) from https://lautarovculic.github.io/ioscpy-repo/ installed on the device. Note that iOS requires at least one initial physical tap on the iPhone touchscreen after connecting or respringing before mouse click and drag events can be injected by ioscpy.

s spawns the app under frida, which is what a script that has to be in place before the app starts needs. Where the app is already running it offers to attach to it instead — the app keeps whatever state it is in, and a script on the running process sees what the device log does not carry: the unified log's debug and info entries never reach idevicesyslog. Enter and escape keep the spawn. f toggles frida-server, offering to match the host client, keep what is installed, or install a specific custom version.

MOABile supports multiple concurrent Frida client versions: each panel tracks its own target Frida version and automatically manages matching client virtual environments using uv (or venv) under ~/.cache/moabile/clients/. This allows using Frida 17 on Android and Frida 16 on iOS within the same session. p purges frida-server and can wipe all cached client environments.

On Android, MOABile automatically detects Google Play system updates to com.google.android.art that break Frida (issues frida/frida#2958 and frida/frida#3639), alerting the user and offering to uninstall the update and reboot.

An app that is off screen is suspended on iOS, and attaching to a suspended process is a prompt that never arrives — so s and o bring the app to the front first, with open on the phone. A jailbreak that has no open is asked to do it by hand rather than left looking stuck, and o says so at once instead of watching a process table that is not going to change. Android needs none of this: a process there runs whether it is on screen or not.

o prompts for optional startup parameters (such as --startup-command "android sslpinning disable"), with Ctrl+O opening an interactive file picker to select a local --startup-script. Parameters are validated and remembered per device panel.

Text selection and clipboard integration are available across all panels:

  • Copy on Select Everywhere: Dragging the mouse highlights text and immediately copies it to the system clipboard upon release with a toast confirmation — across device command and event logs, terminal sessions, and modal text viewers.
  • Interactive Terminal Scrolling (t, s, o): Scroll through terminal history with the mouse wheel or Shift+PageUp, Shift+PageDown, Shift+Home, and Shift+End. Any keypress instantly snaps the viewport back to the active prompt.
  • Smart Copy & Paste: Ctrl+C copies selected text when a selection is active, or sends SIGINT (\x03) to the process in terminal panes. Ctrl+Shift+C / Shift+Ctrl+C copies active selection or screen/log content. In terminal panes, native bracketed paste (Shift+Ctrl+V, Cmd+V, or middle-click) sends clipboard text directly into the running session.

Modal prompts (Frida arguments s, Objection startup parameters o, log keyword filters /, and custom Frida version installs) support terminal-style Readline command history:

  • ↑ / ↓ Navigation: Cycle backwards and forwards through session command history.
  • Draft Preservation: Typing partial input and pressing ↑ saves the draft in progress; navigating ↓ back down restores the typed draft intact.
  • Automatic Deduplication: Repeating an earlier command moves it to the front without duplicates.
  • Strictly In-Memory: Preserves the "Nothing is left behind" guarantee — history lives only in RAM for the running session and is never written to disk. Passwords (secret=True) are strictly excluded from history.

The device log, l, is pinned to the app's pid rather than its name: logcat is asked for --pid, and on iOS idevicesyslog has no pid filter at all — its -p matches process names, and a process merely named something similar comes with it — so the pid is applied here, on the bracket every syslog line carries after the process name. Which is why the whole-device-or-one-app question comes up only while the app is running: with no pid there is no filter to be had, so the stream is the whole device and the panel says why. / filters the active stream in real time by keyword. Mouse selection across log lines highlights and immediately copies the selected text to the clipboard, with drag auto-scroll allowing selection across the full buffer.

Inside the file browser (d): p push host → device, l pull device → host, a jumps to the app's own data directory and h back to where the device side opened (/sdcard or /var/mobile), n rename, d delete, ←/→ switch side, backspace up, r reload, esc close, and enter opens a directory or transfers the file under the cursor.

Every panel keeps its own selected app and frida arguments, so multiple devices can be worked in parallel without crossing over. The sidebar highlights the active device at the top, and lists its installed packages at the bottom.

Nothing is left behind

Not a design goal that happened to fall out — the point. Nothing is written to disk between runs: no config, no history, no selected app, no ssh account. What you were looking at is a record of the work, and this is a tool for leaving none of that. The single exception is the frida-server download, cached one version deep where caches go.

While an iOS panel is open, ssh's multiplexing socket lives in the temporary directory — an empty file holding no data of yours, unlinked when the panel closes. A crash leaves it there, and the next run clears the one it finds.

Tests

Headless, driven by fake tools on PATH, no device required:

python3 test_moabile.py

It prints a PASS line per check and all good at the end. Run it as a script, not under pytest: the module executes the suite on import. One copy at a time — it opens real local ports for the usb tunnel, so two runs at once collide.

Coverage (100% statement coverage):

python3 -m coverage run --source=moabile test_moabile.py && python3 -m coverage report -m

Lint & Type Check:

ruff check .
mypy moabile.py test_moabile.py

Scope

A testing tool for devices you own or are authorised to test. It talks to whatever is plugged in over the phone's own debug interfaces — that is the job, and it is yours to have permission for.

License

MIT — see LICENSE.

Metadata

Release files for moabile 1.3.0

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

Source distribution (sdist)

Source distribution for moabile 1.3.0
File Size Uploaded
moabile-1.3.0.tar.gz 99.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for moabile 1.3.0
File Interpreter ABI Platform
moabile-1.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 198.8 kB

Release files / moabile-1.3.0.tar.gz

Download URL moabile-1.3.0.tar.gz
Size 99.3 kB
Tags Source
SHA-256 checksum
How to use checksums
042cdb1fa372b9f069c867864035ee5ee4ec2c131770ab7bd81409620d7eec41
BLAKE2b-256 checksum
How to use checksums
0d365f9749d6d98124b2d3673c598738ed29c319d01ffeab5775c8cdd21f3995
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 Oct 7, 2026.

Transparency log

Release files / moabile-1.3.0-py3-none-any.whl

Download URL moabile-1.3.0-py3-none-any.whl
Size 99.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1ef3b5c7568a0e8a82fc26d1ac5eb406d65e5889d46e99622e1c896744e67ff0
BLAKE2b-256 checksum
How to use checksums
cf6b99f8071bbed33210eedeffd33f681a73f0848fef4373e2539b1f41e08cfc
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 Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.3.0 This release

2 release files

1.2.0

2 release files

1.1.1

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