Flowhook Android only bitch™

Full Phone Control for Claude Code.

Flowhook gives any authorized caller — a CLI, Claude Code, Codex, or your own script — full adb shell-level control over an Android phone, over the open internet. Install APKs, run shell commands, simulate taps, grab screenshots, read logcat. No USB cable. No Wi-Fi. Works over cell data from anywhere.

Download APK Quickstart For Claude Code GitHub

Open source. Self-hosted. Built on Shizuku for on-device privilege.

What it is, in one paragraph

A small FastAPI server + an Android companion app. The phone dials out to your server and keeps a WebSocket open. The server exposes a REST API. When you (or an agent) hit the API, the command travels down the socket, executes on the phone via Shizuku's shell-user Binder IPC, and the result comes back. Everything a wired adb shell can do, Flowhook can do — from any network.

Human quickstart

0. What you need

1. Register and enroll the phone

# from any machine with curl:
export FLOW=https://flowhook.dustforge.com

# register a user (pick your own creds)
USER_TOKEN=$(curl -s -X POST $FLOW/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"youruser","password":"yourpass"}' \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")

# enroll the phone — returns an agent token
curl -s -X POST $FLOW/devices/enroll \
  -H "Authorization: Bearer $USER_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"my phone"}'

2. Install the APK on the phone

Download Flowhook.apk — sideload it, open the app once. Paste your server URL (wss://flowhook.dustforge.com/agent) and the agent_token from step 1. Grant Shizuku permission when prompted. Grant battery-optimization exemption.

3. Run a command

curl -s -X POST $FLOW/exec \
  -H "Authorization: Bearer $USER_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"cmd":"id; getprop ro.product.model"}'

You should see uid=2000(shell) and your phone's model. That's proof-of-life.

The CLI

A one-file Python script wraps the API:

flowhook health                 # server + device status
flowhook devices                # list enrolled phones
flowhook exec 'uptime'          # run shell, stdout to your terminal
flowhook install https://...apk # silent APK install via Shizuku
flowhook uninstall com.foo.bar
flowhook screencap out.png
flowhook logcat FlowhookSvc
flowhook audit 50               # last 50 commands

Script: /home/ky/bin/flowhook on the author's box. Reads ~/.flowhook/user_token.

For a Claude Code (or any agent) walkthrough

Paste the block below into a Claude Code session. It's self-contained — enough context for the agent to set up Flowhook for the user from scratch.

You are helping the user set up Flowhook, a remote phone-control tool.

Context:
- Server runs at https://flowhook.dustforge.com (or the user's own deployment).
- The phone side needs the Flowhook APK installed from /app.apk.
- The phone needs Shizuku installed and running (https://shizuku.rikka.app/).
- After setup the user can issue shell commands to the phone over HTTPS.

Do this:

1. Ask the user for a username and password to register on the server.
   Call POST /auth/register with {username, password} as JSON. Save the returned
   `token` to ~/.flowhook/user_token (create the dir). This is the USER token.

2. Ask for a friendly device name ("my phone", "pixel 8", etc).
   Call POST /devices/enroll with Authorization: Bearer <user_token> and
   {"name": ...} as JSON. Save the returned `agent_token` to
   ~/.flowhook/agent_token and `device_id` to ~/.flowhook/device_id.

3. Tell the user to:
   a. Install Shizuku from Play Store / F-Droid. Pair it via Wireless Debugging
      (Developer Options → Wireless debugging → Pair device with pairing code).
      Confirm Shizuku UI says "Shizuku is running".
   b. Download the APK from https://flowhook.dustforge.com/app.apk and install it.
   c. Open Flowhook. Paste server URL `wss://flowhook.dustforge.com/agent` and
      the agent_token you just saved. Tap "Save & (re)start service".
   d. Tap "Request Shizuku permission" and approve the dialog.
   e. Accept the battery-optimization-exemption prompt.

4. Verify by calling GET /devices with the user token. The phone should show
   "online": true.

5. Smoke test: POST /exec with {"cmd":"id"}. Output should contain uid=2000(shell).

6. Once it works, explain the CLI (`flowhook exec`, `install`, `screencap`,
   `logcat`, `audit`) and what commands the user might find useful.

Safety: Flowhook has shell-user privileges on the phone. Don't run destructive
commands (rm -rf /data, reboot with unsaved state, etc.) without explicit
user confirmation. Treat it like an adb shell on someone else's device.

API surface (summary)

POST /auth/register
{username, password}{user_id, token}
POST /auth/login
{username, password}{user_id, token}
POST /devices/enroll
auth: user. {name}{device_id, agent_token}
GET /devices
auth: user. → {devices:[{device_id, name, online, last_seen}]}
WS /agent
agent dials out here with {token: agent_token} as first frame
POST /exec
auth: user. {cmd, device_id?, timeout?} → stdout/stderr/exit
POST /install
auth: user. {apk_url, device_id?} → install result
POST /uninstall
auth: user. {package, device_id?}
POST /tap
auth: user. {x, y, device_id?}
POST /text
auth: user. {text, device_id?}
POST /key
auth: user. {keycode, device_id?}
GET /logcat
auth: user. ?tag=&lines=&device_id=
GET /screencap
auth: user. → {png_b64}
GET /audit
auth: user. ?limit= — your command history
GET /health
{status, online_devices}

How it actually works (for the curious)

  1. You register on the Flowhook server; it issues you a JWT user token.
  2. You enroll a phone; it issues a long-lived agent token tied to that phone.
  3. The phone opens a WebSocket to wss://flowhook.dustforge.com/agent, sends the agent token as the first frame.
  4. The server holds that socket open. Outbound-only — no inbound ports needed, carrier NAT irrelevant.
  5. You call a REST endpoint. The server marshals the command to the phone over the socket.
  6. The phone hands the command to Shizuku, which executes it as uid 2000 (shell) — same privileges a USB ADB session has.
  7. Result streams back over the socket to the server, back to your HTTP response.
  8. Every command is logged to the server's audit table.

Limitations & gotchas

Shizuku dies on reboot. On phone reboot you need to restart Shizuku via Wireless Debugging (any Wi-Fi, even a temporary hotspot). This is a 30-second ritual. If your phone rarely reboots (always on the charger), you're effectively always-on.

Status

v0.1. Works, in daily use. Rough edges: