Repository navigation
Releases: pironjulien/NexusAndroid
Release list
NexusAndroid 0.7.2
Added
capturetool, to show a problem to a support service:screenshotsaves the current screen at full resolution
(PNG, or JPEG when the name ends in.jpg) with security codes blacked out as onlookimages;recordfilms the
nextseconds(default 30),startandstopfilm while gestures go on (Android'sscreenrecord, MP4, 720p,
4 Mbit/s, 180 s at most, one recording at a time). The video is pulled to the PC then deleted from the phone; the
response gives the path, the length and the size. Files go to~\Downloads\Telephoneby default, with the same
folder rules asfiles pull, and never overwrite. Videos cannot be masked: no recording starts while a banking,
payment or identity app is in the foreground, and the response says codes may appear in the video. A screenshot
of such an app, or one without code masking (UI tree unreadable), needsconfirm=true. Black images (FLAG_SECURE,
screen off) are reported, and so is a video shorter than the recording time (Android adds frames only when the
screen changes, none while it is off). A recording still running when the server stops is stopped and saved.
Before any screenshot or video, a sleeping screen on an unlocked phone is turned on (KEYCODE_WAKEUP, counted as an
agent gesture and kept on like afteract); a locked phone, screen on or off, is refused without capturing, with a
message to ask the user to unlock it (the agent never types a code): no more black screenshots or empty videos.logstool (read-only), to understand why an app crashed or misbehaves:kind="crash"groups the crash log one
block per crash (time, package, Java exception and eachCaused by, the top frame and the first app frames; native
crashes with signal, cause and backtrace);kind="anr"lists "app not responding" events from Android's drop box
(kept for days) and the event log, without duplicates, with their reason;kind="recent"gives the lastlines
log lines (200 by default, 2000 at most), for one app withapp(resolved likelaunch; its running processes,
those started earlier, and lines naming it; an uninstalled app by its full package name).since(15m,2h,
13:05,2026-10-19 13:05, phone time) andquery(case-insensitive, applied after masking) filter; newest
last, about 30,000 characters. Any security code, tokens and secrets (known key prefixes, JWT, random hex or
base64,Bearer,password=…), e-mail addresses and phone numbers are masked; stack frames are kept as is.
Banking, payment and identity apps:recentis refused for them and their lines are left out of the general
view; their crashes and ANRs show only exception types and app frames, no message.logs kind="crash": identical crashes (same process, type and text once object hashes, pids, bracketed numbers
and times are ignored) are shown as one block,×N (first …, last …, pids …), placed at the newest one; the
exception Android re-prints after "Couldn't report crash" (processes without a package) is dropped, and the
"Error reporting crash" cause is one short line. 21 crashes, 19 of them identical, went from 22,500 to 2,100 characters.
Fixed
- When another automation tool holds the phone's single accessibility connection (UiAutomation), such as the
mobile-mcp servercom.mobilenext.mobilecli.DeviceServerthat stays alive 30 minutes, Appium UiAutomator2,
uiautomator2/atx-agent or an instrumentation run,lookand the gestures aimed by text now say so at once, with
the tool and its pid: "Another automation tool is using the phone's accessibility connection (…, pid N): close it
or stop it, then retry." Onepscall, no more 3 to 4 seconds ofuiautomator dumpretries ending in a misleading
"screen moving or protected". The other tool's process is never stopped automatically.
NexusAndroid 0.7.1
Added
act(then="changes"): after the gestures, only what changed since the last element list (+added,~changed
text, state or position beyond 10 px,-removed; unchanged elements counted, not repeated). Kept elements keep
their[N], new ones get fresh numbers, so the nextactaims on the current screen. Full list, said in one line,
when there is no previous list, the foreground app changed or more than 60 % of the elements differ. Codes stay
masked (compared masked). About 97 % fewer characters on a 90-element settings screen after one toggle.
Fixed
- Phone numbers (10 digits or more, or after
+, such as06 11 22 33 44) are no longer masked as security codes
(seen on a real helpline button); codes of 4 to 9 digits still are. - Screenshot marks are larger (18 px on the 1280 px side, was 14) and easier to read.
NexusAndroid 0.7.0
Security
- Payment pages behind plain
httpslinks needconfirm=trueeven when they open in the browser: revolut.me,
paypal.me, paypal.com/paypalme (and /checkoutnow, /donate), cash.app, lydia-app.com, lydia.me, buy/checkout/donate
.stripe.com, venmo.com, wise.com/pay, SumUp links, bunq.me, monzo.me (host and subdomains, any case). - Shutdown, reboot and reset intents (
ACTION_REQUEST_SHUTDOWN,REBOOT,MASTER_CLEAR,FACTORY_RESETand vendor
variants with SHUTDOWN, REBOOT or WIPE in the action) needconfirm=true. - A link that Android would send to one app is still checked against every app that could take it: a banking or
identity app among them is refused as before. Any component whose class name contains "Chooser" or "Resolver" is
treated as a chooser, not a final target (One UI answersandroid/com.android.internal.app.ResolverActivity). - Clipboard: fewer false alarms and fewer misses.
.,-and_no longer count as password symbols, so file
names, versions, model numbers, plates and host names are shown; known key prefixes (sk-,ghp_,github_pat_,
AKIA,xox…-,AIza,glpat-…), JWTs, PEM blocks, long random hex or base64 tokens and short words mixing
letters with password punctuation (!@#$%^&*+=?~…) or three kinds of characters are hidden. - Forged messages: an SMS whose text contained
\nRow: 1 _id=999, type=2, body=…could add a fake (even outgoing)
message to a conversation. The phone helper no longer returns the text output ofcontent: it queries the
providers itself (sameActivityManagerpath ascontent) and serializes the cursor rows with length-prefixed
values (protocolNXQ2), decoded as such on the PC. On theadb shellfallback, a read is refused, and reported,
when itsRow: Nnumbers do not run strictly from 0, exceed the requestedLIMIT, or differ from the row count of
the same read reduced to_id(no free text). Columns are split along the requested projection, so a contact named
"Dupont, Jean" is no longer cut to "Dupont". - Predictable helper socket: each start draws a random 128-bit token, passed to
app_process, part of the abstract
socket name and sent back in every answer. The PC refuses a server that does not return its token; a name already
taken is no longer considered normal (the PC relaunches with a new token, then falls back toadb shell). A
forward left by another process is adopted with its token instead of piling up. - Shared safeguards policy updated from
NexusHarness/shared: post, reply, tweet, subscribe, free trial, book,
reserve, donate, answer and accept a call, dial, transfer and price buttons (4,99 €,$12) needconfirm=true;
plain_labelspells out €, $, £ and ¥ as eur, usd, gbp and jpy like the sharedplain. - Security codes are now also masked in English, Spanish, German and Italian messages ("Use 482913 to sign in",
"Tu código: 482-913", "Ihr Bestätigungscode lautet 482913", "Il tuo codice è 482913", "Token: 482913", "log in",
"clave"), without masking dates (12/10/2026, 2026-10-12) or amounts followed by a currency (1 250 €). - A short swipe whose start and end fall inside the same clickable, non-scrollable element is judged as a tap, with
the strict safeguard (confirmation word, unnamed icon in a messaging app, nothing readable). fileson the PC side: keys, secrets and papers (.ssh,.gnupg,.aws,.azure,.nexus,NexusSecure,
Administratif,.envfiles) and the Windows Startup folders are never read or written, even with
confirm=true; outside Downloads, Documents, Desktop, Pictures and Videos (OneDrive included),confirm=trueis
required. The real path (symlinks,..) is resolved before the check.- User back on the phone during a batch: before every gesture after the first, a light activity read
(dumpsys powerfiltered on the phone) stops the batch at once if the user touched the screen since the agent's last
gesture, and the report lists the steps already done. A one-gesture batch costs nothing more. - Screen kept on: the raised
screen_off_timeoutis now 5 minutes instead of 30 (Android counts it again from every
injected gesture; always longer than the idle delay). A server killed outright leaves the phone unlocked 5 minutes
at most. The 30-minute value set by older versions is still recognised and restored. During a longlook-only
phase, more than 3 minutes after the last gesture or poke, a screen that is on getsKEYCODE_WAKEUP(no visible
effect, counted by Android as activity), recorded as an agent gesture, never as the user's touch.
Added
-
look(marks=...): withshow=both, the image carries small[N]badges (the element numbers of the list) at the
top-left of each element, with a thin outline of its bounds.auto(default) numbers every element when there are
21 or fewer, otherwise only the unnamed ones (icons without text or description);all;none. Drawn after the
security codes are blacked out (a badge only covers, never reveals), never on an image that is refused, and not
withshow=image(no list for the numbers to refer to). About 4 ms for 80 elements on a Fold screenshot. -
clipboard(action="read"|"write", text): reads the phone's clipboard (security codes masked like messages; a lone
code, or a single word that looks like a password or a token, is never shown; says when it is empty or not text)
or sets it without pasting, through the same scrcpy server used to type accents (started for the call, stopped
after). Typing accents still gives the user their previous clipboard back. -
launch(action, data, extras, confirm): opens a precise screen (android.settings.WIFI_SETTINGS, a deep link,
extras as string, integer or boolean;appthen limits the target to that app). The target is resolved first
(cmd package resolve-activity), started by its exact component, and reported with the foreground read back.
Calling, sending or paying directly (CALL,RESPOND_VIA_MESSAGE, uninstall and reset actions, payment actions
such asorg.chromium.intent.action.PAY,upi:/bitcoin:/payto:… links, auto-send extras) needs
confirm=true; a banking, payment or identity app, as the resolved target or as one of the candidates of
Android's chooser, is refused even withconfirm=true.act open_urlrefuses payment links withoutconfirm=true
too. -
Tests that tool descriptions, titles and schema descriptions contain no French, that offline errors and refusals
(phone absent, unknown target, step needingconfirm=true, missing or invalid arguments, locked phone) are in
English, and that the instructions and descriptions never compare the server with other tools. -
Typing accents and emoji without
install.ps1: when the pinned scrcpy server (v5.0.1) is missing or altered, the
server downloads it once from the official Genymobile/scrcpy GitHub release into~/.nexus/nexus-android/tools,
checks it against the SHA-256 written in the code and writes it atomically. If the download fails or the digest
differs, the step fails with a message giving the URL, the expected digest and the folder; nothing is typed, and
the text is never transliterated. -
Windows installer for the public repository (
install.ps1): uv environment under~\.nexus\nexus-android\venv
(package copied, not editable), pinned scrcpy server, stdio registration with Codex (~\.codex\config.toml,
previousnexus-androidtable replaced, file backed up first) and Antigravity (~\.gemini\config\mcp_config.json,
backed up first) when they are installed, theclaude mcp addcommand printed for Claude Code (no Claude CLI call),
thentest.ps1. No OneDrive, daemon supervisor or author-specific path;-NoRegister,-SkipTests,-Live. -
Python 3.12 and 3.13 support:
requires-python = ">=3.12"(3.11 lacks thetypestatement); the CI runs
test.ps1 -Python 3.12|3.13|3.14on Windows.test.ps1 -Python X.Ykeeps a separate test environment per version. -
server.jsonlistsNEXUS_ANDROID_AWAKE_IDLE. -
messages(before=...): reads older messages (or older conversations) from the ISO date given at the end of the
previous answer. Answers ofmessages,notificationsandfiles listare capped at about 60,000 characters, cut
between two messages or lines, and say how to read the rest. -
A call cancelled by the client (MCP
notifications/cancelled, or the client disconnecting) stops the batch before
its next step, stops awaitstep, and stops waiting for the phone.
Changed
- Private installer: Antigravity now gets nexus-android as a plugin (
~\.gemini\config\plugins\nexus-android, with
the skill and its ownmcp_config.jsonpointing at the shared daemon with the token, written on the PC only), like
the other Nexus tools. The old entry in the globalmcp_config.jsonis removed after a backup; the plugin is
enabled inconfig.jsononly while Antigravity is closed. lookin about 0.17 s instead of 3 s over Wi-Fi: the phone helper keeps one accessibility connection open and
returns the UI tree, the screenshot (JPEG made on the phone, already at the size sent to the agent) and the screen
state with the fold posture in one exchange, instead ofuiautomator dump,screencapand severaldumpsys. The
tree is read likeuiautomator dumpdoes (active window, visible nodes, bounds clipped to the display, same
attributes), so the element list, numbering, masking and refusals do not change; the target check before each
gesture uses it too. As withuiautomator dump, the tree waits for one second without accessibility events, but
only after a change: a still screen is read at once, and a screen that never settles (video) is read after 3 s
instead of failing. Read-only (no gesture, no global action); the user's accessibility services are not suspended.
The connection is released before anyuiautomator dumpfallback, after 45 s with...
NexusAndroid 0.6.2
Changed
- Server instructions sent to every MCP client are now in English and neutral ("the user"): no phone model, no
mention of a shared policy between named agents. Same rules and flow as before. The startup test checks that the
instructions stay under the 2048-character client limit and that nothing personal reachesinitializeor
tools/list.
NexusAndroid 0.6.1
Added
- Cable-free use without a shared Wi-Fi network: when the phone runs a hotspot (One UI
ap_br_swlan0/swlan0)
and the PC is connected to it, the address recorded at each USB connection is the hotspot address (wlan0still
comes first). Afteradb tcpip 5555, the server finds the phone again over the hotspot once the cable is removed.
README section for this setup.
NexusAndroid 0.6.0
Performance
- Message, contact, call log and calendar reads no longer start a Java process on the phone for each
content query(about 1 s per wave of queries). At the first read the server pushes a 7 KB helper
(phone/NexusQuery.java, built intosrc/nexus_android/nexus-query.dexbyphone/build.ps1) to
/data/local/tmpand starts it once withapp_processunder the shell identity, reached throughadb forward
on a local abstract socket: the scrcpy server's method (mobile-mcp's device server works the same way; DroidMind
stays on plainadb shell). No app to install. It runs each query with the code of Android's owncontent
command (/system/framework/content.jar), so the output is identical to the character. Measured on a Galaxy
Fold over USB: conversation list 3.1 s → 0.07 s (0.14 s from a fresh process, 0.73 s when the helper must first
be pushed and started), one conversation 2.1 s → 0.03 s. - One port forward per phone, reused by every process and after a daemon restart.
Security
- The helper is read-only (only
queryis served; insert, update, delete and call are refused), answers only
connections from the shell user or root (adbd; kernel-checked peer credentials, which no app on the phone can
have), and stops by itself after 10 minutes without a request. Each dex version has its own socket name, so an
old helper is never taken for the current one; older ones are stopped and removed. - If the helper cannot be started or answers badly, reads fall back to the 0.5 method (
adb shell), with the same
output and the same masking of security codes, and the helper is retried a minute later.
Changed (Codex plugin, 2026-10-19)
install.ps1no longer writes[mcp_servers.nexus-android]to Codex'sconfig.toml: Codex gets the server from a
personal Codex plugin.
NexusAndroid 0.5.1
Security
- Shared policy (
garde-fous.json) synced with NexusHarness: every "Réinitialiser…" / reset button needs
confirm=trueagain, including the bare "Réinitialiser" confirmation button of One UI and Windows and
"Réinitialiser les paramètres réseau". Only harmless resets stay free (filters, zoom, view, sort, search, layout,
form, fields, text, selection, colours). This reverts the 0.5.0 narrowing, which let the final One UI reset button
through without confirmation.
[0.5.0] - 2026-10-19
Performance
messages()is about 5 times faster. Everycontent querystarts a Java process on the phone (about 1 s), and
the tool used to run them one after another: 34 for the conversation list, 7 to 11 per conversation. Queries now
run in parallel on the phone inside a singleadb shell(30 cost about as much as one), the phone returns only the
last N rows (LIMIT), MMS parts and addresses are chained on the phone instead of going back to the PC, and
contact names are cached for 10 minutes, shared by all sessions of the daemon. Measured on a Galaxy Fold over USB:
conversation list 35 s → 4.6 s (3.1 s with names cached), one conversation 7-11 s → 2.1 s, with 4 images
11.6 s → 3.3 s, by contact name 11.4 s → 4.3 s. Output is byte-for-byte identical, security codes still masked.info(kind="calls")reads only the last N calls and resolves their names in one exchange.
Security
- Shared policy (
garde-fous.json) synced with NexusHarness: "reset" needsconfirm=trueonly for the whole device
("Réinitialiser le téléphone", "Réinitialisation d'usine"); other "Réinitialiser…" buttons inside an app are free.
Changed (Claude Code plugin, 2026-10-19)
install.ps1no longer registers the server with Claude Code and never calls a Claude CLI: Claude Code gets
nexus-androidfrom a Claude Code plugin (shared daemon URL, token read from an environment variable).
Fixed
install.ps1: withoutclaudeon the PATH, Claude Code was skipped on a new PC. Falls back to theclaude.exe
shipped with the Claude desktop app, like NexusClaude and NexusBrowser.
Changed
- Repository renamed to
pironjulien/NexusAndroid; GitHub redirects the old address. - Published on PyPI (
nexus-android) and in the official MCP registry (io.github.pironjulien/nexus-android);
the quick start installs withuvx --python 3.14 nexus-android.
Added
- Shared daemon mode:
nexus-android --http [port]serves MCP Streamable HTTP on 127.0.0.1 (default port 16180),
so Claude Code, Codex and Antigravity share one process instead of one per session. A bearer token is required
(NEXUS_MCP_TOKENorNEXUS_MCP_TOKEN_FILE, 32 characters or more); each MCP session keeps its own[N]
numbering and conversation list, while the ADB link and the phone lock stay shared. Clients reconnect on their
own after a daemon restart. install.ps1declares the daemon to the Nexus daemon supervisor and registersnexus-androidby URL for Claude Code,
Codex and Antigravity (instead of a stdio process per session).- README: animated demo recorded from the real tool on a real phone (docs/assets/demo.webp).
NexusAndroid 0.4.1
Project page: https://julienpiron.fr/nexusandroid/
Security
- Wiping, resetting or formatting the whole device now needs
confirm=true: "Reset phone", "Reset device",
"Reset tablet", "Reset PC", "Formater le téléphone", "Formater l'appareil", "Format phone" (shared policy). "Tout
effacer", "Effacer tout" and "Tout supprimer" need it on a reset screen (factory reset, erase all data) and stay free
elsewhere (calculator, notifications). One test per label.
NexusAndroid 0.4.0
Project page: https://julienpiron.fr/nexusandroid/
Fixes from the 18/10 audit, aligned on the safeguard agreement between Claude and Antigravity.
Security
- Confirmation labels now come from the policy file shared with the desktop tools (
garde-fous.json, embedded copy;
a test checks it is identical to the shared one in the author's workspace). Every listed form is caught, accents
and case ignored: "Envoyez", "Payez", "Paiement", "Achetez", "Commandez", "Publish", "Purchase", "Checkout",
"Virer" no longer pass withoutconfirm=true. One test per pattern. - A clickable icon with no name at all in a messaging, mail, social or phone app (often the send or call button,
whose resource id does not always say so) needsconfirm=true,x,ytaps included. lookno longer sends the image when the UI tree cannot be read (security codes could not be blacked out), unless
confirm=true.open_urlrefuses a link that Android would open in a protected app (banking, payment, identity), or that offers
one in its app chooser.- The lock state and the foreground app are read in the same round trip as the UI tree, just before each targeted
gesture: an app opened by the previous step of a batch is seen before the next one.
Changed
- Aligned on the shared policy: deleting (to the trash), accepting, sharing, validating and allowing no longer need
confirmation; formatting, cancelling a subscription and making a repository public do. Calling stays under
confirmation (a real call on a phone; added to the shared policy). A generic
"OK"/"Validate" needs it on a screen about a payment or a format. An explicit order from the user for a gesture
counts as confirmation (tool descriptions and refusals say so). - Protected apps are readable:
scrollandwaitare free in them; taps, typing and swipes needconfirm=true
(navigation on the user's order); a money or send button, or an unnamed icon, is refused there even with
confirm=true: the user does it. - Faster batches: phone state read once per
actbatch, in one adb round trip instead of three, then together with
each UI read. Measured on a Galaxy Z Fold over Wi-Fi (6 steps): 28 → 8 adb round trips, 11.0 s → 9.9 s.
Added
- Structured output (
outputSchemaandstructuredContent) forphone,infoandsettings, next to the text.
NexusAndroid 0.3.1 — first public release
Changed
- Ready for anyone's phone: messages, docs and tool descriptions speak of "the user" instead of the author.
test.ps1works from any copy of the repository: each checkout gets its own test environment (created on first
run) and never touches the environment of the installed server.- Docs and changelog in English; MIT license.
Security
- Test fixtures anonymized (phone numbers, serial number, display identifier, Wi-Fi addresses, location, contact and
computer names). - Protected apps: the apps of the main French retail banks added (Crédit Agricole, BNP Paribas, Société Générale,
Caisse d'Epargne, Banque Populaire, Crédit Mutuel, CIC, LCL), package names checked on Google Play.