Live product
termux-tools
termux.party
A phone is a dev machine. Four incompatibilities said otherwise.
Android is Linux with the interesting parts filed off, and the standard workaround — proot or a chroot emulating a whole distro inside your phone — is slow, fragile, and defeats the point. So we went underneath instead. bun-termux is a userland exec written by hand in C: it maps glibc's ld-linux-aarch64.so.1 segment by segment, synthesizes a complete SysV process stack with all 19 auxv entries, and branches into the loader from inline aarch64 assembly. A 596-line LD_PRELOAD shim covers the rest — a fabricated /proc/stat served over memfd so os.cpus() works, shebang rewriting, and silent hardlink→copy fallback for f2fs. The result: the official, unmodified 100 MB Bun ARM64 binary runs natively. 79 of 80 tests pass.
Then the browser. Claude in Chrome talks to its extension over Chrome's Native Messaging host API, which does not exist on Android — no extension on the platform can reach a local process over stdio. We rebuilt the wire format instead: 4-byte little-endian length prefix, UTF-8 JSON body, a stateful stream decoder reassembling partial frames, all relayed over a localhost WebSocket to a Manifest V2 extension (MV3 service workers never start in sideloaded Android targets — uBlock and Dark Reader ship MV2 there for the same reason). Below that sits a Chrome DevTools Protocol path over an ADB-forwarded abstract socket, which is how screenshots work while Edge is backgrounded: Page.bringToFront wakes the renderer's compositor without foregrounding the app. The bridge carries its own PNG codec and GIF89a/LZW encoder — there was no dependency that worked here — and exposes 18 browser tools as MCP.
The last two problems were binaries nobody meant for you to edit. Claude Code now ships bun-compiled, its JavaScript sealed in an offset-keyed blob where any length-changing edit corrupts the file — but the blob isn't checksummed, so equal-length overwrites survive. That's how a fatal DNS bug got fixed: Bun's bundled c-ares opens /etc/resolv.conf through a raw syscall no LD_PRELOAD can intercept, and on Termux that path is read-only and empty, so OAuth login times out forever while inference keeps working. The fix is 16 bytes swapped for 16 bytes: /etc/resolv.conf → /sdcard/dns.conf. And Edge Canary goes through a config-driven APK pipeline — 64 manifest removals, 25 smali method stubs, 15 native libraries dropped, 35 telemetry endpoints redirected to loopback — reassembled by replacing only the touched files and re-signed with a stable keystore, so every rebuild updates in place instead of wiping your bookmarks. Built on a phone, in Termux. So was the landing page.
// Technical highlights
- Hand-written userland exec in 1,037 lines of C — mmaps glibc's
ld.soPT_LOAD segments, builds a SysV stack with 19 auxv entries, enters via aarch64 inline asm; runs the official unmodified 100 MB Bun binary on bionic. 79/80 tests pass. - Chrome's Native Messaging protocol re-implemented over WebSocket — 4-byte LE length prefix + JSON, stateful partial-frame decoder, 1 MiB cap, lazy-spawned native host, 18 MCP browser tools. The API simply doesn't exist on Android.
- CDP over an ADB abstract-socket forward — probes both Chrome's PID-suffixed and Edge Android's plain
chrome_devtools_remotenames;Page.bringToFrontbefore capture wakes a backgrounded renderer, so screenshots work with the browser in the background. - Byte-length-preserving binary patching — bun-vfs is offset-keyed but unchecksummed, so a 16-byte-for-16-byte swap (
/etc/resolv.conf→/sdcard/dns.conf) fixes an OAuth-killing c-ares bug thatLD_PRELOADprovably cannot reach. Same patcher later fixed Codex CLI login on Termux. - Config-driven Edge APK surgery — 64 manifest removals, 25 smali stubs, 12 NOPed
loadLibrarysites, 15 native libs dropped (~36 MB), 35 telemetry endpoints to 127.0.0.1; only patched files are replaced, and a stable keystore means in-place updates that keep your data. BuildInfo.isDebugAndroid()→const/4 v0, 0x1— enables Chromium's command-line file on a release build without settingandroid:debuggable, which would open the app to JDWP attach. That one smali edit is what makes--load-extensionpossible.- PNG decoder (all five filters incl. Paeth), RGBA encoder, 216-color quantizer, LZW + GIF89a writer — written from scratch inside the bridge, ~300 lines, zero image dependencies.
- x86_64 on aarch64 in ~14 MB — qemu-user plus a glibc sysroot extracted from Debian
.debs; no chroot, no root, versus 500 MB–1 GB for proot-distro.