# `mix mob.connect`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mix/tasks/mob.connect.ex#L1)

Discovers connected Android and iOS devices, sets up USB tunnels,
restarts the app on each device, waits for Erlang nodes to come online,
then drops into an IEx session connected to all of them.

    mix mob.connect

The restart is what makes a fresh session reliable: the app registers in the
Mac's EPMD through the tunnels just set up, under the node name and port
mob_dev expects. To inspect a session that is already running, attach
without restarting it:

    mix mob.connect --no-restart

## Options

  * `--no-iex`   — set up connections but don't start IEx (print node names instead)
  * `--no-restart` — attach to the app that is already running instead of
    restarting it, keeping its state. On Android the node is looked up in EPMD
    under the deploy-time name or the bare `<app>_android` a launcher start
    registers. The app must have started while the tunnels were up (it
    registers in the Mac's EPMD through `adb reverse`); if it didn't, restart it.
  * `--name`     — local node name for this session (default: `mob_dev@127.0.0.1`,
    or `mob_dev_<os pid>@127.0.0.1` when another process on this Mac holds it)
  * `--cookie`   — use this Erlang cookie instead of the app's private one. Only
    needed for an app that sets a custom `cookie:` in `Mob.Dist.ensure_started/1`
  * `--ios-only` / `--android-only` — restrict discovery to one platform. iOS-only
    development on a Mac with no Android platform-tools installed works without
    this flag (adb's absence is handled gracefully), but `--ios-only` skips the
    Android scan entirely — useful when a phone for another project is plugged in.
    To make it the default for a project, set it once in `mob.exs`:

        config :mob_dev, platforms: [:ios]
  * `--only` / `--device` (`-d`) — restrict to devices whose serial/udid contains
    the given substring. Repeatable. Without it, connect attaches to *every*
    running device, so a single slow or locked device (e.g. a plugged-in
    physical iPhone) can stall the whole run. Target one phone with:

        mix mob.connect --only ZY22CRLMWK

    Without it, a device another `agent-device` session has claimed is
    skipped (and named), since connecting restarts the app; a claimed device
    you name is used after a warning. See `MobDev.DeviceLeases`.

## Multiple simultaneous sessions

Because Erlang distribution allows many nodes to connect to the same device,
you can run multiple independent sessions at the same time. Use `--name` to
give each one a unique identity:

    # Terminal 1 — interactive developer session
    mix mob.connect --name mob_dev_1@127.0.0.1

    # Terminal 2 — agent or second developer
    mix mob.connect --name mob_dev_2@127.0.0.1

Both see the same live device state and can call `Mob.Test.*` and `nl/1`
independently without interfering with each other.

## IEx + dashboard in one process

For an interactive session alongside the dev dashboard, use:

    iex -S mix mob.server

This starts the dashboard at `localhost:4040` and gives you an IEx prompt in
the same process. Run `mix mob.connect` (or let the dashboard auto-connect)
to attach to device nodes, then call `Mob.Test.*` directly from the IEx prompt.
This is the recommended setup when working alongside an agent — the agent uses
Tidewave to execute `Mob.Test.*` calls in the same running session.

## iOS physical device connectivity

Physical iPhones support three connection modes. The BEAM picks the right one
automatically at startup based on which network interfaces are present:

| Priority | Connection | Node name | When |
|----------|------------|-----------|------|
| 1 | WiFi / LAN | `<app>_ios@10.0.0.x` | On the same network as the Mac |
| 1 | Tailscale  | `<app>_ios@100.x.x.x` | Any network — see below |
| 2 | USB only   | `<app>_ios@169.254.x.x` | Cable plugged in, no WiFi |
| 3 | None       | `<app>_ios@127.0.0.1` | No network |

WiFi is preferred over USB so the node IP stays stable across cable plug/unplug.
Plugging or unplugging the USB cable does not change the node name as long as
WiFi is available. The node only falls back to the USB link-local address when
there is no WiFi at all.

`mix mob.connect` does not guess which of these the BEAM chose; it asks the
phone's EPMD for this project's `<app>_ios` node and connects to the name EPMD
reports, ignoring other Mob apps' nodes on the same phone. For a phone found
over USB it queries the USB link-local address, then the phone's other
addresses (its `.local` mDNS name's IPv4s). One of those is used only when
its EPMD lists the same node at the same dist port, i.e. the same BEAM. The
`<app>_ios@169.254.x.x` guess is used only when the link-local EPMD lists
nothing yet.

**The node name is still fixed at app launch.** If distribution isn't working,
force-quit the app on the iPhone and relaunch it so it picks up the current
network state.

**USB** is the default and works with no setup. Plug in the cable and run
`mix mob.connect`.

**WiFi** works automatically when the Mac and iPhone are on the same network. If
it doesn't connect, check: was the app last launched with USB plugged in? If so,
force-quit and relaunch the app (without USB), then run `mix mob.connect` again.
Public WiFi and corporate networks often block device-to-device traffic (client
isolation) — use Tailscale in those environments.

**Tailscale** lets you connect over any network including cellular. It is a free
mesh VPN (free for personal use at tailscale.com). Install it on both the Mac
and iPhone, sign in to the same account, and `mix mob.connect` works the same
way regardless of what network either device is on. Tailscale must be active on
the iPhone before the app launches — the node name is fixed at BEAM startup.

**Personal Hotspot** (iPhone sharing its cellular connection as WiFi) also works
automatically — the Mac connects to the hotspot and the LAN detection picks up
the `172.20.10.x` address.

Distribution is authenticated with a random 256-bit cookie per app, kept in an
owner-only file under `~/.mob/dist_cookies/`.
`mob.connect` hands it to the app when it restarts it: Android reads it from a
file in the app's private storage, iOS from the launch environment. An iOS app
started from Xcode or the home screen has an ephemeral random cookie until
`mob.connect` restarts it. Apps built against a mob from before MOB-49 still
use the public `mob_secret`; `mob.connect` falls back to it with a warning.

## Under the hood

`mix mob.connect` is a convenience wrapper around standard Erlang distribution setup:

    # Android: set up adb port tunnels so the device BEAM registers in the Mac's EPMD
    adb reverse tcp:4369 tcp:4369   # EPMD: device → Mac
    adb forward tcp:9100 tcp:9100   # dist port: Mac → device

    # iOS simulator shares the Mac's network stack — no tunnelling needed

    # iOS physical: BEAM registers its own in-process EPMD on the device;
    # Mac connects directly to the device IP (USB link-local, WiFi, or Tailscale)

    # Then, in Elixir:
    Node.start(:"mob_dev@127.0.0.1", :longnames)
    Node.set_cookie(MobDev.DistCookie.for_project!())
    Node.connect(:"my_app_android@127.0.0.1")
    Node.connect(:"my_app_ios@127.0.0.1")
    IEx.start([])

You can do all of this by hand in any IEx session — `mix mob.connect` just
discovers the device node names and wires up the tunnels automatically.

# `resolve_platforms`

```elixir
@spec resolve_platforms(
  keyword(),
  [:android | :ios]
) :: {:ok, [:android | :ios]} | {:error, String.t()}
```

Resolves which platforms to discover from the parsed options and the
`mob.exs` default.

`--ios-only` / `--android-only` win over the default; passing both is a
contradiction and returns `{:error, _}`. With neither flag, the `mob.exs`
default (`config :mob_dev, platforms: [...]`, both platforms when unset) is
used. Pure — exposed for testing.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
