# tap-agent command reference

!!! warning "Experimental"
    `tap-agent` and its export format may change in any release until this notice goes away.

Every `tap-agent` verb, its arguments and the MCP tool that does the same. The page is
generated from the CLI parser and the MCP server, so it matches `tap-agent <verb> --help`.
The [tap-agent](index.md) pages explain the flow; this page is for looking
things up.

## Conventions

| Term | Meaning |
|---|---|
| Target | A ref from `snapshot` (`@e12`), or `key=value` terms joined by commas, all of which must match. Keys: id, text, text~, desc, desc~, class, hint, pkg, index. `~=` means contains. A target matches anywhere on the screen; `pkg=` keeps it to one app's nodes. |
| `-s`, `--session NAME` | The session to use (default `$TAP_AGENT_SESSION`, else `agent`). Every verb but `mcp` and `skill` takes it. |
| `-d`, `--device SERIAL` | Needed only when the session holds several devices. Every verb that runs on a device takes it. |
| `--settle` | After the step, wait for the screen to stop changing and print the difference (`+` added, `-` removed). |
| Durations | `--timeout`, `--idle`, `--wait-for-device`: `500ms`, `10s`, `2m`, or a number of seconds. |
| Files | `screenshot`, `capture` and `pull` write under `.tap/agent/` unless `-o` says otherwise, and print the path. |
| Exit codes | `0` ok, `1` the step failed (the reason is on stderr), `2` usage, `3` no server running (`tap start`). |
| MCP | Every tool also takes `session`; tools that run on a device take `device`. Actions take `settle`. |

## At a glance

| Command | What it does | MCP tool |
|---|---|---|
| [`devices`](#devices) | List the devices ADB sees and who holds them | `devices` |
| [`attach`](#attach) | Attach a device to the session (created if needed) | `attach` |
| [`sessions`](#sessions) | List sessions with their devices | `sessions` |
| [`release`](#release) | End the session and detach its devices | `release` |
| [`snapshot`](#snapshot) | Print the screen, one node per line with its ref | `snapshot` |
| [`settle`](#settle) | Wait for the screen to stop changing, then print what changed | `settle` |
| [`wait`](#wait) | Wait until a target is visible (or gone, or exactly one node) | `wait_for` |
| [`activity`](#activity) | Print the activity on top of the screen (package/class) | `foreground_activity` |
| [`screenshot`](#screenshot) | Save a PNG and print its path | `screenshot` |
| [`capture`](#capture) | Save screenshot, hierarchy, device info and driver log | `capture` |
| [`tap`](#tap) | Tap a node | `tap` |
| [`fill`](#fill) | Replace a field's text (no key events) | `fill` |
| [`type`](#type) | Type key events into the focused field | `type_text` |
| [`clear`](#clear) | Clear a field's text | `clear` |
| [`submit`](#submit) | Run a focused field's keyboard action (Search, Go, Send, Done, …); API 30+ | `submit` |
| [`action`](#action) | List a node's accessibility actions, or perform one as a screen reader does (no touch) | `accessibility_action` |
| [`progress`](#progress) | Set a slider (SeekBar, Slider, RatingBar) to a value in its own units | `set_progress` |
| [`keyboard`](#keyboard) | Whether a soft keyboard shows, or hide it | `keyboard` |
| [`scroll`](#scroll) | Scroll a node towards a content edge (down reveals content below) | `scroll` |
| [`swipe`](#swipe) | Swipe across a node, the finger moving in the direction | `swipe` |
| [`fling`](#fling) | Fling a node towards a content edge, as for scroll; content may keep moving | `fling` |
| [`drag`](#drag) | Long-press a node, move it onto another node and drop it | `drag` |
| [`pinch`](#pinch) | Pinch a node open (fingers apart, zoom in) or closed | `pinch` |
| [`key`](#key) | Press a key: back, home, recents, enter, tab, delete, … or a key code | `press_key` |
| [`panel`](#panel) | Open the notification shade or quick settings (key back closes it) | `open_panel` |
| [`rotate`](#rotate) | Rotate the display and keep it there (auto: back to the sensor) | `rotate` |
| [`screen`](#screen) | Screen state, or turn it on/off, or wake it and dismiss a keyguard without a PIN | `screen` |
| [`app`](#app) | App lifecycle: launch, cold-launch, foreground, background, open-link, stop, clear, install, uninstall, grant, revoke, granted, running, locale | `app` |
| [`permission`](#permission) | List the permission dialog's buttons, or press one | `permission` |
| [`condition`](#condition) | Device conditions (animations, dark mode, font scale, density, network switches, languages, stay awake, accessibility display): print or change until release | `condition` |
| [`location`](#location) | Mock the device location until release | `set_location` |
| [`notification`](#notification) | List the device's notifications, or wait for, open or dismiss one (read as data; the shade stays closed) | `notification` |
| [`toast`](#toast) | Wait for a toast (shown in the last 3.5s or coming) and print it | `await_toast` |
| [`clipboard`](#clipboard) | Print the device clipboard, or set it | `clipboard` |
| [`push`](#push) | Copy a local file to the device until release | `push_file` |
| [`pull`](#pull) | Copy a device file here and print the local path | `pull_file` |
| [`media`](#media) | Add a photo or video to the device gallery until release | `add_media` |
| [`export`](#export) | Print the session's event log as JSON (every device call, in order) | `export` |
| [`mcp`](#mcp) | Run the MCP server on stdio (same sessions as the CLI) | — |
| [`skill`](#skill) | Print the agent skill (SKILL.md) | — |

## Sessions and devices

A session is a held connection on the server: it outlives each CLI call, is shared by the CLI and the MCP server, and ends on `release` or after 15 idle minutes. `--session NAME` (or `TAP_AGENT_SESSION`) keeps several apart. A device is held by one session at a time, exactly as a test holds it.

### `devices`

List the devices ADB sees and who holds them.

```text
tap-agent devices [-s SESSION]
```

MCP: `devices()`: List the devices ADB sees, their state and the session holding each.

### `attach`

Attach a device to the session (created if needed).

```text
tap-agent attach [-s SESSION] [--idle IDLE] [--wait-for-device DURATION] serial
```

| Argument | Meaning |
|---|---|
| `serial` | — |
| `--idle IDLE` | end the session after this long unused (default 15m) |
| `--wait-for-device DURATION` | wait this long for a device another session holds |

MCP: `attach(serial, idle_minutes, wait_for_device_seconds)`: Attach a device to the session, creating the session if needed. Call this first.

### `sessions`

List sessions with their devices.

```text
tap-agent sessions [-s SESSION]
```

MCP: `sessions()`: List sessions, how long each has been idle, and their devices.

### `release`

End the session and detach its devices.

```text
tap-agent release [-s SESSION]
```

MCP: `release()`: End the session: detach its devices so others can use them.

## Reading the screen

`snapshot` is the main way to see the screen: text, one node per line, grouped by window, each with a ref (`@e12`) to act on. A ref names a selector the server found to match exactly that node, and keeps naming it while it stays on screen. Prefer it to `screenshot`, which an agent has to look at.

### `snapshot`

Print the screen, one node per line with its ref.

```text
tap-agent snapshot [-s SESSION] [-d DEVICE] [-i | --all]
```

| Argument | Meaning |
|---|---|
| `-i, --interactive` | only nodes an action can target |
| `--all` | every node, layout containers too |

MCP: `snapshot(view, device)`: The current screen, one node per line with its ref (`@eN`).

### `settle`

Wait for the screen to stop changing, then print what changed.

```text
tap-agent settle [-s SESSION] [-d DEVICE] [--full] [--timeout TIMEOUT]
```

| Argument | Meaning |
|---|---|
| `--full` | also print unchanged nodes (=) |
| `--timeout TIMEOUT` | default 10s |

MCP: `settle(full, device)`: Wait for the screen to stop changing, then list what changed since the last snapshot.

### `wait`

Wait until a target is visible (or gone, or exactly one node).

```text
tap-agent wait [-s SESSION] [-d DEVICE] [--gone | --one] [--timeout TIMEOUT]
                      target
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `--gone` | — |
| `--one` | — |
| `--timeout TIMEOUT` | default 10s |

MCP: `wait_for(target, state, timeout_seconds, device)`: Wait, polling on the device, until target is visible, gone, or exactly one node.

### `activity`

Print the activity on top of the screen (package/class).

```text
tap-agent activity [-s SESSION] [-d DEVICE]
```

MCP: `foreground_activity(device)`: The activity on top of the screen as package/class, or that none is resumed (the keyguard shows, or one is starting). Use it to check that a link or a notification opened the right screen.

### `screenshot`

Save a PNG and print its path.

```text
tap-agent screenshot [-s SESSION] [-d DEVICE] [-o OUT]
```

| Argument | Meaning |
|---|---|
| `-o, --out OUT` | file (default .tap/agent/<serial>-<time>.png) |

MCP: `screenshot(device)`: A PNG of the screen, also saved to disk. Prefer snapshot: it is text and has refs.

### `capture`

Save screenshot, hierarchy, device info and driver log.

```text
tap-agent capture [-s SESSION] [-d DEVICE] [-o OUT]
```

| Argument | Meaning |
|---|---|
| `-o, --out OUT` | directory (default .tap/agent/capture-<serial>-<time>) |

MCP: `capture(device)`: Save a screenshot, the hierarchy, device info and the driver log; returns the paths.

## Acting on elements

Every action takes a target and needs exactly one matching node: zero is `NOT_FOUND`, several is `AMBIGUOUS`, and both fail before anything touches the device. A tap whose point another window covers (a dialog, the shade) fails as `NOT_INTERACTABLE` / `OBSCURED`. With `--settle`, the command then waits for the screen to stop changing and prints what changed, which is usually enough to pick the next step.

### `tap`

Tap a node.

```text
tap-agent tap [-s SESSION] [-d DEVICE] [--settle] [--long | --double] target
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `--settle` | wait for the screen to settle, then print what changed |
| `--long` | long-press instead |
| `--double` | double-tap instead |

MCP: `tap(target, long, double, settle, device)`: Tap the one node matching target.

### `fill`

Replace a field's text (no key events).

```text
tap-agent fill [-s SESSION] [-d DEVICE] [--settle] target text
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `text` | — |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `fill(target, text, settle, device)`: Replace a field's text (accessibility set-text; no key events).

### `type`

Type key events into the focused field.

```text
tap-agent type [-s SESSION] [-d DEVICE] [--settle] text
```

| Argument | Meaning |
|---|---|
| `text` | — |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `type_text(text, settle, device)`: Type key events into whatever has input focus (tap a field first).

### `clear`

Clear a field's text.

```text
tap-agent clear [-s SESSION] [-d DEVICE] [--settle] target
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `clear(target, settle, device)`: Clear a field's text.

### `submit`

Run a focused field's keyboard action (Search, Go, Send, Done, …); API 30+.

```text
tap-agent submit [-s SESSION] [-d DEVICE] [--settle] target
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `submit(target, settle, device)`: Run a text field's keyboard action (Search, Go, Send, Done, ... as the app set it up), exactly as the keyboard's action key does; press_key enter is not the same. The field must have input focus (tap it first). Android 11 (API 30)+.

### `action`

List a node's accessibility actions, or perform one as a screen reader does (no touch).

```text
tap-agent action [-s SESSION] [-d DEVICE] [--settle] [--custom] target [name]
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `name` | a standard action (expand, collapse, dismiss, scroll-forward, ...) or, with --custom, a label; optional |
| `--settle` | wait for the screen to settle, then print what changed |
| `--custom` | NAME is a custom action's label (e.g. 'Archive') |

MCP: `accessibility_action(target, name, custom, settle, device)`: Perform an accessibility action on a node as a screen reader does (no touch), or list the actions it offers and its range. Use it for what a node offers instead of a gesture: expand/collapse a section, a list item's custom "Archive"/"Delete". An action the node does not offer fails before anything happens.

### `progress`

Set a slider (SeekBar, Slider, RatingBar) to a value in its own units.

```text
tap-agent progress [-s SESSION] [-d DEVICE] [--settle] target value
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `value` | — |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `set_progress(target, value, settle, device)`: Set a slider (SeekBar, Slider, RatingBar) to an exact value through accessibility ACTION_SET_PROGRESS. A value outside the node's range fails before anything happens.

### `keyboard`

Whether a soft keyboard shows, or hide it.

```text
tap-agent keyboard [-s SESSION] [-d DEVICE] [--settle] [{state,hide}]
```

| Argument | Meaning |
|---|---|
| `action` | one of `state`, `hide`; optional; default `state` |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `keyboard(action, settle, device)`: The soft keyboard: is it showing, or hide it.

## Gestures

Gestures run on one node, sized relative to it, never at screen coordinates.

### `scroll`

Scroll a node towards a content edge (down reveals content below).

```text
tap-agent scroll [-s SESSION] [-d DEVICE] [--settle] target {up,down,left,right}
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `direction` | one of `up`, `down`, `left`, `right` |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `scroll(target, direction, settle, device)`: One scroll gesture on a scrollable node; `down` reveals content below.

### `swipe`

Swipe across a node, the finger moving in the direction.

```text
tap-agent swipe [-s SESSION] [-d DEVICE] [--settle] target {up,down,left,right}
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `direction` | one of `up`, `down`, `left`, `right` |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `swipe(target, direction, settle, device)`: One swipe across a node, the finger moving in `direction`.

### `fling`

Fling a node towards a content edge, as for scroll; content may keep moving.

```text
tap-agent fling [-s SESSION] [-d DEVICE] [--settle] target {up,down,left,right}
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `direction` | one of `up`, `down`, `left`, `right` |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `fling(target, direction, settle, device)`: One fast swipe on a scrollable node towards a content edge, as for scroll; the content may keep moving afterwards (use settle).

### `drag`

Long-press a node, move it onto another node and drop it.

```text
tap-agent drag [-s SESSION] [-d DEVICE] [--settle] target destination
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `destination` | where to drop it: a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `drag(target, destination, settle, device)`: Long-press the node matching target, move it onto the node matching destination and drop it.

### `pinch`

Pinch a node open (fingers apart, zoom in) or closed.

```text
tap-agent pinch [-s SESSION] [-d DEVICE] [--settle] [--percent PERCENT]
                       target {open,close}
```

| Argument | Meaning |
|---|---|
| `target` | a ref (@e3) or selector terms: id=login, text=Log in, desc=Close, class=Button, … |
| `how` | one of `open`, `close` |
| `--settle` | wait for the screen to settle, then print what changed |
| `--percent PERCENT` | how far across the node, 1..100 (default 80) |

MCP: `pinch(target, how, percent, settle, device)`: Two-finger pinch on a node (a map, an image).

## Keys, system panels and the display

Target the shade's and quick settings' nodes with `pkg=com.android.systemui`; `key back` closes them (twice from quick settings on newer Android). A rotation is kept until release, which restores the device's own setting.

### `key`

Press a key: back, home, recents, enter, tab, delete, … or a key code.

```text
tap-agent key [-s SESSION] [-d DEVICE] [--settle] name
```

| Argument | Meaning |
|---|---|
| `name` | — |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `press_key(key, settle, device)`: Press a key.

### `panel`

Open the notification shade or quick settings (key back closes it).

```text
tap-agent panel [-s SESSION] [-d DEVICE] [--settle] {notifications,quick-settings}
```

| Argument | Meaning |
|---|---|
| `name` | one of `notifications`, `quick-settings` |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `open_panel(panel, settle, device)`: Open a system panel, as a swipe down from the status bar would. Its nodes belong to com.android.systemui (target them with pkg=com.android.systemui); press_key back closes it.

### `rotate`

Rotate the display and keep it there (auto: back to the sensor).

```text
tap-agent rotate [-s SESSION] [-d DEVICE] [--settle]
                        {portrait,landscape,natural,left,upside-down,right,auto}
```

| Argument | Meaning |
|---|---|
| `how` | one of `portrait`, `landscape`, `natural`, `left`, `upside-down`, `right`, `auto` |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `rotate(how, settle, device)`: Rotate the display and keep it there. Returns where the display is afterwards: the app may pin its own orientation. The device's rotation settings are restored on release.

### `screen`

Screen state, or turn it on/off, or wake it and dismiss a keyguard without a PIN.

```text
tap-agent screen [-s SESSION] [-d DEVICE] [--settle] [{state,on,off,unlock}]
```

| Argument | Meaning |
|---|---|
| `action` | one of `state`, `on`, `off`, `unlock`; optional; default `state` |
| `--settle` | wait for the screen to settle, then print what changed |

MCP: `screen(action, settle, device)`: Screen power and lock screen. A keyguard with a PIN, pattern or password is never unlocked.

## Apps and permissions

`app <action> <package> [argument]` covers the app lifecycle. `permission` handles the runtime-permission dialog an app shows, whatever app shows it.

### `app`

App lifecycle: launch, cold-launch, foreground, background, open-link, stop, clear, install, uninstall, grant, revoke, granted, running, locale.

```text
tap-agent app [-s SESSION] [-d DEVICE] [--any-app] [--extra KEY[:TYPE]=VALUE]
                     {launch,cold-launch,foreground,background,open-link,stop,clear,install,uninstall,grant,revoke,granted,running,locale}
                     package [argument]
```

| Argument | Meaning |
|---|---|
| `action` | one of `launch`, `cold-launch`, `foreground`, `background`, `open-link`, `stop`, `clear`, `install`, `uninstall`, `grant`, `revoke`, `granted`, `running`, `locale` |
| `package` | the app's package name, e.g. com.example.app |
| `argument` | activity (launch), URI (open-link), APK path (install), permission (grant, revoke, granted) or languages (locale: fr-FR,en or system); optional |
| `--any-app` | open-link: let any app handle the link, not only this one |
| `--extra KEY[:TYPE]=VALUE` | launch/cold-launch: an intent extra; TYPE is string (default), int, long, float or bool; repeat for more |

MCP: `app(action, package, argument, any_app, extras, device)`: App lifecycle: launch, cold-launch (force-stop first), foreground (as the launcher would: back where it was), background (home key), open-link (a deep link, restricted to the app unless any_app), stop, clear data, install, uninstall, grant or revoke a runtime permission (revoking stops the app's process), ask whether a permission is granted, whether the app is running, or read or set the app's own languages (locale, API 33+, restored on release).

### `permission`

List the permission dialog's buttons, or press one.

```text
tap-agent permission [-s SESSION] [-d DEVICE] [--settle] [--timeout TIMEOUT]
                            [--accuracy {precise,approximate}]
                            [{allow,allow-foreground-only,allow-one-time,allow-always,allow-selected,allow-all,deny,deny-and-dont-ask-again,keep-foreground-only,keep-one-time}]
```

| Argument | Meaning |
|---|---|
| `choice` | the button to press (omit to list them); one of `allow`, `allow-foreground-only`, `allow-one-time`, `allow-always`, `allow-selected`, `allow-all`, `deny`, `deny-and-dont-ask-again`, `keep-foreground-only`, `keep-one-time`; optional |
| `--settle` | wait for the screen to settle, then print what changed |
| `--timeout TIMEOUT` | how long to wait for the dialog (default 10s) |
| `--accuracy ACCURACY` | pick Precise or Approximate first (location dialog, Android 12+); one of `precise`, `approximate` |

MCP: `permission(choice, timeout_seconds, accuracy, settle, device)`: Android's runtime-permission dialog: without a choice, wait for it and list the buttons (and location accuracies) it offers; with one, press that button.

## Device conditions and location

Everything changed here is restored on release: animations, dark mode, font scale, density, the network switches, the languages and the accessibility display settings, and the mocked location.

### `condition`

Device conditions (animations, dark mode, font scale, density, network switches, languages, stay awake, accessibility display): print or change until release.

```text
tap-agent condition [-s SESSION] [-d DEVICE]
                           [{animations,dark-mode,font-scale,density,airplane-mode,wifi,mobile-data,locale,stay-awake,high-contrast-text,color-inversion,bold-text}]
                           [value]
```

| Argument | Meaning |
|---|---|
| `name` | omit to print all; one of `animations`, `dark-mode`, `font-scale`, `density`, `airplane-mode`, `wifi`, `mobile-data`, `locale`, `stay-awake`, `high-contrast-text`, `color-inversion`, `bold-text`; optional |
| `value` | animations/dark-mode/airplane-mode/wifi/mobile-data/stay-awake/high-contrast-text/color-inversion/bold-text: on\|off; font-scale: 0.5..2.0; density: dpi or reset; locale: comma-separated BCP-47 tags, e.g. fr-FR,en (omit to print); optional |

MCP: `condition(name, value, device)`: Read the device conditions (animations, dark mode, font scale, display density, airplane mode, Wi-Fi, mobile data, the device languages, stay awake while plugged in, high-contrast text, colour inversion, bold text) or change one. A change lasts until release, which restores what the device had, and the result is the value read back. Dark mode and the network switches need API 29+, bold text API 31+; the network switches are real (nothing is mocked).

### `location`

Mock the device location until release.

```text
tap-agent location [-s SESSION] [-d DEVICE] [--accuracy ACCURACY]
                          [--altitude ALTITUDE]
                          latitude longitude
```

| Argument | Meaning |
|---|---|
| `latitude` | -90..90 |
| `longitude` | -180..180 |
| `--accuracy ACCURACY` | in meters (default 5) |
| `--altitude ALTITUDE` | in meters |

MCP: `set_location(latitude, longitude, accuracy_m, altitude_m, device)`: Mock the device location until release (the device's location providers report this fix, and location is turned on if it was off). Call again to move it; release restores the device.

## Notifications, toasts and the clipboard

Notifications are read as data: the shade stays closed. Toasts are caught when shown in the last 3.5 s or while waiting.

### `notification`

List the device's notifications, or wait for, open or dismiss one (read as data; the shade stays closed).

```text
tap-agent notification [-s SESSION] [-d DEVICE] [--settle] [--title TITLE]
                              [--text TEXT] [--contains] [--package PACKAGE] [--button BUTTON]
                              [--timeout TIMEOUT]
                              [{list,await,open,dismiss}]
```

| Argument | Meaning |
|---|---|
| `action` | one of `list`, `await`, `open`, `dismiss`; optional; default `list` |
| `--settle` | wait for the screen to settle, then print what changed |
| `--title TITLE` | the notification's title |
| `--text TEXT` | the notification's text |
| `--contains` | title/text are only part of it |
| `--package PACKAGE` | only this package's notifications |
| `--button BUTTON` | open: press the action button with this title instead |
| `--timeout TIMEOUT` | await: default 10s |

MCP: `notification(action, title, text, contains, package, button, timeout_seconds, settle, device)`: The device's notifications as data (the shade stays closed; they never appear in snapshot). open and dismiss need exactly one match. The driver gets notification access for the session.

### `toast`

Wait for a toast (shown in the last 3.5s or coming) and print it.

```text
tap-agent toast [-s SESSION] [-d DEVICE] [--contains] [--package PACKAGE]
                       [--timeout TIMEOUT]
                       [text]
```

| Argument | Meaning |
|---|---|
| `text` | the toast's text (omit for any toast); optional |
| `--contains` | text is only part of the toast |
| `--package PACKAGE` | only this package's toasts (default: any app's) |
| `--timeout TIMEOUT` | default 10s |

MCP: `await_toast(text, contains, package, timeout_seconds, device)`: Wait for a toast (one shown in the last 3.5 s counts) and return its text and app. Toasts never appear in snapshot: use this to check one.

### `clipboard`

Print the device clipboard, or set it.

```text
tap-agent clipboard [-s SESSION] [-d DEVICE] [text]
```

| Argument | Meaning |
|---|---|
| `text` | put this on the clipboard (omit to print it); optional |

MCP: `clipboard(text, device)`: Read the device clipboard as text, or set it.

## Files and the gallery

The bytes stream through the server. What Tap created on the device is removed on release, and an existing file is never overwritten.

### `push`

Copy a local file to the device until release.

```text
tap-agent push [-s SESSION] [-d DEVICE] local device_path
```

| Argument | Meaning |
|---|---|
| `local` | the file on this machine |
| `device_path` | absolute path on the device; its directory must exist (e.g. /sdcard/Download/a.pdf) |

MCP: `push_file(local_path, device_path, device)`: Copy a local file to the device until release. A device file Tap did not push is never overwritten.

### `pull`

Copy a device file here and print the local path.

```text
tap-agent pull [-s SESSION] [-d DEVICE] [-o OUT] device_path
```

| Argument | Meaning |
|---|---|
| `device_path` | absolute path of a regular file on the device |
| `-o, --out OUT` | file (default .tap/agent/<serial>-<time>-<name>) |

MCP: `pull_file(device_path, out, device)`: Copy a device file to this machine; returns the local path.

### `media`

Add a photo or video to the device gallery until release.

```text
tap-agent media [-s SESSION] [-d DEVICE] [--name NAME] local
```

| Argument | Meaning |
|---|---|
| `local` | the image or video on this machine (jpg, png, gif, webp, heic, bmp, mp4, 3gp, webm, mkv, mov) |
| `--name NAME` | file name on the device (default the local name) |

MCP: `add_media(local_path, name, device)`: Add a photo or video to the device gallery (Pictures/Tap or Movies/Tap, indexed so gallery apps and photo pickers list it) until release.

## Turning a session into a test

The event log lists every device call the session made, in order, with its outcome; refs appear as the selectors they stood for. See [Sessions to tests](export.md).

### `export`

Print the session's event log as JSON (every device call, in order).

```text
tap-agent export [-s SESSION] [-o OUT]
```

| Argument | Meaning |
|---|---|
| `-o, --out OUT` | write it to this file instead |

MCP: `export(path)`: The session's event log as JSON: every device call it made (commands and app changes), in order, with outcomes and the selectors refs stood for. Export before `release`: the log ends with the session. Converting it into a test is up to the user.

## Agent setup

Register the MCP server with your agent, or give it the CLI with the skill text.

### `mcp`

Run the MCP server on stdio (same sessions as the CLI).

```text
tap-agent mcp
```

MCP: none (CLI only).

### `skill`

Print the agent skill (SKILL.md).

```text
tap-agent skill
```

MCP: none (CLI only).
