> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mobster.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Every command and option

> Every mobster command, subcommand, argument and option with its default, generated from the command's own parser.

This page is generated from the parsers `mobster` itself runs, so it lists exactly what this version accepts. `mobster <command> --help` prints the same text in the terminal. The [Errors and exit codes](/reference/errors) lists the exit codes.

Options must be spelled in full: `--exec` is not `--execute`. A command's options can go before or after its name.

| Command | What it does |
| - | - |
| [`mobster doctor`](#mobster-doctor) | check your iPhone, key and tools; `--fix` sets up what is missing |
| [`mobster devices`](#mobster-devices) | list the iPhones and simulators Mobster can use |
| [`mobster test`](#mobster-test) | run your app's saved checks on simulators and iPhones, with reports |
| [`mobster mcp`](#mobster-mcp) | let your coding agent use the phone and check your app |
| [`mobster login`](#mobster-login) | save your Claude or OpenAI key, or a plan's or provider's |
| [`mobster setup`](#mobster-setup) | set up your iPhone for Mobster, one step at a time |
| [`mobster chat`](#mobster-chat) | talk to Mobster's agent; follow-ups know what earlier tasks found |
| [`mobster workflow`](#mobster-workflow) | print a saved workflow's steps in the order they run |
| [`mobster run`](#mobster-run) | run one task and print each step (JSON lines when piped) |
| [`mobster memory`](#mobster-memory) | see and edit what Mobster remembers |
| [`mobster history`](#mobster-history) | list past tasks |
| [`mobster export`](#mobster-export) | save a past task as a captioned GIF or MP4 |
| [`mobster screen`](#mobster-screen) | show your iPhone's screen in the terminal |
| [`mobster xray`](#mobster-xray) | map an app's screens and paywalls, or see who your apps talk to |
| [`mobster verify`](#mobster-verify) | run one check once, from flags or a file, and print passed or failed |
| [`mobster sim`](#mobster-sim) | create and clean up Mobster's simulators |
| [`mobster ci`](#mobster-ci) | run your checks on every pull request, on a Mac of yours |
| [`mobster expo`](#mobster-expo) | open your Expo app on a simulator or your iPhone, and build it |
| [`mobster asc`](#mobster-asc) | read App Store Connect feedback and builds, make screenshots |
| [`mobster testflight`](#mobster-testflight) | list your TestFlight builds and install one on your iPhone |
| [`mobster xcode`](#mobster-xcode) | run your checks after every simulator build in Xcode |
| [`mobster fixtures`](#mobster-fixtures) | list the app states your checks start from, and put a simulator in one |
| [`mobster links`](#mobster-links) | list your app’s deep links and check that each lands on its screen |
| [`mobster serve`](#mobster-serve) | start the local API that Mobster for Mac and other clients use |
| [`mobster api`](#mobster-api) | serve every app on your iPhone as a local, read-only API |
| [`mobster phone`](#mobster-phone) | set your iPhone's clipboard, put files on it, or install a build |
| [`mobster alerts`](#mobster-alerts) | test the webhook that hears about scheduled tasks |
| [`mobster audit`](#mobster-audit) | check a Team plan audit export offline |
| [`mobster env`](#mobster-env) | keep a project's test credentials in your Keychain, for its checks |
| [`mobster demo`](#mobster-demo) | run a scripted task: no phone, no keys |
| [`mobster completion`](#mobster-completion) | print a shell completion script (zsh, bash or fish) |
| [`mobster update`](#mobster-update) | say whether a newer Mobster is out, and where to get it |
| [`mobster version`](#mobster-version) | print the version |
| [`mobster logout`](#mobster-logout) | remove a saved key, or forget a plan's or provider's, on this Mac |
| [`mobster wifi`](#mobster-wifi) | use an iPhone without the cable, over the encrypted Wi-Fi link |
| [`mobster tui`](#mobster-tui) | open the terminal UI (what `mobster` does on its own) |
| [`mobster help`](#mobster-help) | show the help for a command or a topic |
| [`mobster build-ocr`](#mobster-build-ocr) | compile the optional Apple Vision OCR helper (needs Xcode) |

## `mobster`

`mobster` with no command opens the terminal UI. These options belong to it (and to `mobster tui`).

**Options**

<ResponseField name={"--wda-url"} type={"URL"} default={"http://127.0.0.1:8100"}>
  the phone's address, for a phone you set up by hand (default: [http://127.0.0.1:8100](http://127.0.0.1:8100)) \[env: MOBSTER\_WDA\_URL]
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the iPhone or simulator to use: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--env-file"} type={"PATH"}>
  load KEY=VALUE lines (ANTHROPIC\_API\_KEY, OPENAI\_API\_KEY, ...); variables already set win \[env: MOBSTER\_ENV\_FILE]
</ResponseField>

<ResponseField name={"--demo"} type={"flag"} default={"off"}>
  use a scripted phone and policy: no iPhone, no keys, no model calls
</ResponseField>

<ResponseField name={"--resume"} type={"ID, optional"}>
  open a past task and put it back in the prompt (the latest without an ID)
</ResponseField>

<ResponseField name={"-c, --continue"} type={"flag"} default={"off"}>
  continue your last conversation
</ResponseField>

<ResponseField name={"--app"} type={"NAME"}>
  start with this app chosen (a name or bundle ID)
</ResponseField>

<ResponseField name={"--no-bell"} type={"flag"} default={"off"}>
  don't ring the terminal bell when Mobster needs you
</ResponseField>

<ResponseField name={"-V, --version"} type={"flag"}>
  print the version and exit
</ResponseField>

## `mobster doctor`

Check everything a task needs and print how to fix what is missing. Without `--fix` it changes nothing. Keys are shown masked: their first characters and last four.

`--fix` runs the guided steps for what is missing: `mobster login` for a model key and `mobster setup` for an iPhone that isn't ready, then checks again.

**Options**

<ResponseField name={"--wda-url"} type={"URL"} default={"http://127.0.0.1:8100"}>
  the phone's address, for a phone you set up by hand (default: [http://127.0.0.1:8100](http://127.0.0.1:8100)) \[env: MOBSTER\_WDA\_URL]
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the iPhone or simulator to use: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--env-file"} type={"PATH"}>
  load KEY=VALUE lines (ANTHROPIC\_API\_KEY, OPENAI\_API\_KEY, ...); variables already set win \[env: MOBSTER\_ENV\_FILE]
</ResponseField>

<ResponseField name={"--fix"} type={"flag"} default={"off"}>
  run the guided key and iPhone setup for what is missing, then check again
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the checks as JSON
</ResponseField>

<ResponseField name={"--simulator"} type={"flag"} default={"off"}>
  skip the USB iPhone and Xcode checks (a simulator's phone)
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 ready for tasks (warnings allowed), 1 at least one problem, 2 --fix without a
            terminal

examples:
  mobster doctor
  mobster doctor --fix
```

## `mobster devices`

List every device Mobster can drive: USB iPhones (set up or newly plugged in), Mobster's simulators, and phone addresses from MOBSTER\_WDA\_DEVICES. Each shows its state: Ready, In use, Connected, Needs setup or Unplugged. Name one with `--device` on run, screen, doctor, serve, mcp and the terminal UI. It changes nothing.

`mobster devices lock-probe` reads whether an iPhone is locked or showing a system sheet (Face ID, a locked app, the App Store's side-button confirmation) the way a task's check does, and lists what the lock screen shows. It taps and types nothing.

`mobster devices status` reads a device's battery and thermal state, and whether this Mac is on battery. A value the device doesn't report is left out. Without `--device` it reads the only device, else the first iPhone set up.

**Arguments**

<ResponseField name={"lock-probe|status"} type={"lock-probe | status, optional"}>
  lock-probe: the lock state and system sheets; status: battery and heat. Neither taps anything
</ResponseField>

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the devices (or the probe, or the status) as JSON
</ResponseField>

<ResponseField name={"--wda-url"} type={"URL"} default={"$MOBSTER_WDA_URL, else http://127.0.0.1:8100"}>
  lock-probe, status: the phone's address (default: `$MOBSTER_WDA_URL`, else [http://127.0.0.1:8100](http://127.0.0.1:8100))
</ResponseField>

<ResponseField name={"--device"} type={"NAME"}>
  lock-probe, status: the device to read, by id, UDID or name
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster devices
  mobster devices --json
  mobster devices lock-probe --device "Sam's iPhone"
  mobster devices status --device "Sam's iPhone"
  mobster run "Turn on Dark Mode" --execute --device "Sam's iPhone"
```

## `mobster test`

Run every check in .mobster/checks (or the paths given) on one or more devices, in parallel, with retries, flake detection, and JUnit and HTML reports with the frames. A check is a saved YAML file; `mobster verify` runs one without saving it.

**Arguments**

<ResponseField name={"PATH"} type={"PATH, zero or more"}>
  check files or folders of them (default: .mobster/checks); `record` makes a check from a task instead, and `import maestro` from Maestro flows
</ResponseField>

**Options**

<ResponseField name={"--sim"} type={"TYPE[@RUNTIME]"} default={"each check's own, or .mobster/matrix.yaml"} post={["repeatable"]}>
  a simulator device type, with an optional runtime, such as "iPhone 17 Pro\@iOS 26.4"; repeatable (default: each check's own, or .mobster/matrix.yaml)
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} post={["repeatable"]}>
  a device from `mobster devices`, or all: every iPhone ready on this Mac, side by side in one report; repeatable. On a real iPhone, checks drive only builds you installed, never App Store apps
</ResponseField>

<ResponseField name={"--testflight"} type={"BUNDLE"} post={["repeatable"]}>
  with `--device`: your app's bundle ID, installed from TestFlight, which checks may drive as your own build; repeatable
</ResponseField>

<ResponseField name={"--matrix"} type={"FILE"}>
  a YAML file of simulators to run every check on (simulators only)
</ResponseField>

<ResponseField name={"--locale"} type={"LANG[,LANG]"} post={["repeatable"]}>
  run every check once in each language, such as de,ja,ar: the app launches in that language and region, and the screens are read for truncated text, untranslated strings and, in a right-to-left language, a layout that didn't mirror
</ResponseField>

<ResponseField name={"--spread"} type={"flag"} default={"off"}>
  with two or more devices: run each check once, on whichever device is free, instead of on every one (the report groups checks by device)
</ResponseField>

<ResponseField name={"--parallel"} type={"N"} default={"1"}>
  simulators running checks at once (default: 1); each iPhone runs one check at a time
</ResponseField>

<ResponseField name={"--retries"} type={"N"} default={"1, or 0 with --repeat"}>
  attempts after a failure, from a fresh app (default: 1, or 0 with `--repeat`)
</ResponseField>

<ResponseField name={"--repeat"} type={"K"} default={"1"}>
  run each check K times and report its pass rate (flake hunting)
</ResponseField>

<ResponseField name={"--strict"} type={"flag"} default={"off"}>
  a flaky check fails the run
</ResponseField>

<ResponseField name={"--quarantine"} type={"FILE"} default={".mobster/quarantine.yaml"}>
  checks that run and are reported but never fail the run (default: .mobster/quarantine.yaml)
</ResponseField>

<ResponseField name={"--auto-quarantine"} type={"flag"} default={"off"}>
  quarantine for this run the checks whose last 20 results say flaky; without it Mobster only suggests them
</ResponseField>

<ResponseField name={"--tag"} type={"TAG"} post={["repeatable"]}>
  only checks with this tag; repeatable
</ResponseField>

<ResponseField name={"--audit"} type={"a11y"} post={["repeatable"]}>
  also audit what each check reads: a11y finds controls with no label, tap targets under 44 pt, images with no label and text clipped at a large text size; no model is called
</ResponseField>

<ResponseField name={"--a11y"} type={"warn | strict, optional"}>
  audit the screens each check reads for accessibility: warn (the default) reports findings and passes, strict fails on a new one; findings in .mobster/a11y-baseline.json never fail
</ResponseField>

<ResponseField name={"--update-a11y-baseline"} type={"flag"} default={"off"}>
  write this run's accessibility findings to .mobster/a11y-baseline.json, so later runs fail only on new ones
</ResponseField>

<ResponseField name={"--shard"} type={"I/N"}>
  run the I-th of N even shards, such as 2/4
</ResponseField>

<ResponseField name={"--keyless"} type={"flag"} default={"off"}>
  never call a model: checks with steps couldn't run
</ResponseField>

<ResponseField name={"--replay"} type={"flag"} default={"off"}>
  replay each check's recorded path with no model, and heal a step that changed (the default)
</ResponseField>

<ResponseField name={"--fresh"} type={"flag"} default={"off"}>
  run every step with Smart and record the path again
</ResponseField>

<ResponseField name={"--no-heal"} type={"flag"} default={"off"}>
  replay, and fail a check whose screen changed instead of healing it (strict CI)
</ResponseField>

<ResponseField name={"--junit"} type={"FILE"}>
  also write the JUnit XML here
</ResponseField>

<ResponseField name={"--html"} type={"DIR"}>
  also write the HTML report (with each run's report) here
</ResponseField>

<ResponseField name={"--video"} type={"flag"} default={"off"}>
  record each run's screen, on a simulator or an iPhone, into the report
</ResponseField>

<ResponseField name={"--history"} type={"flag"} default={"off"}>
  print each check's last 20 results (pass rate, duration trend, flake score) and run nothing
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print results.json on stdout instead of the summary
</ResponseField>

<ResponseField name={"--share"} type={"flag"} default={"off"}>
  also upload the report, sealed, to your team's hosted reports and print its link (Team plan: one of the team's Macs, or MOBSTER\_TEAM\_KEY in CI)
</ResponseField>

<ResponseField name={"--share-days"} type={"7 | 30 | 90"} default={"30"}>
  how long a shared report is kept: 7, 30 or 90 (default: 30)
</ResponseField>

<ResponseField name={"--assert-timeout"} type={"S"} default={"5"}>
  how long expectations may wait for the screen to settle (default: 5)
</ResponseField>

<ResponseField name={"--run"} type={"RUN_ID"}>
  the finished task to make a check from
</ResponseField>

<ResponseField name={"--name"} type={"TEXT"} default={"the task"}>
  the check's name (default: the task)
</ResponseField>

<ResponseField name={"-o, --output"} type={"PATH"} default={"print it"}>
  write the check here (default: print it); with import, the folder to write the checks to (default: .mobster/checks)
</ResponseField>

<ResponseField name={"--bundle"} type={"ID"}>
  the app's bundle ID for flows with no appId, and over the appId of the others
</ResponseField>

<ResponseField name={"--app"} type={"PATH"}>
  write this .app (built for the iOS Simulator) into each check
</ResponseField>

<ResponseField name={"--skip-unsupported"} type={"flag"} default={"off"}>
  drop commands Mobster can't run instead of turning each into a step marked TODO, and list each one; an assertion is never dropped
</ResponseField>

<ResponseField name={"--dry-run"} type={"flag"} default={"off"}>
  say what would be written, and write nothing
</ResponseField>

<ResponseField name={"--force"} type={"flag"} default={"off"}>
  replace checks that already exist
</ResponseField>

<ResponseField name={"--list"} type={"flag"} default={"off"}>
  list the recipes, one line each
</ResponseField>

<ResponseField name={"--set"} type={"KEY=VALUE"} post={["repeatable"]}>
  a recipe param; repeatable (`mobster test add --list` shows each recipe's)
</ResponseField>

<ResponseField name={"--base"} type={"REF"} default={"$GITHUB_BASE_REF, else origin/main"}>
  the branch the PR merges into (default: `$GITHUB_BASE_REF`, else origin/main)
</ResponseField>

<ResponseField name={"--no-build"} type={"flag"} default={"off"}>
  don't build the app: test the builds the checks name
</ResponseField>

<ResponseField name={"--scheme"} type={"NAME"} default={"the only one"}>
  the Xcode scheme to build (default: the only one)
</ResponseField>

<ResponseField name={"--project"} type={"PATH"} default={"the one in the project"}>
  the .xcodeproj or .xcworkspace to build (default: the one in the project)
</ResponseField>

<ResponseField name={"--drafts"} type={"run | write | off"} default={"run"}>
  checks for changed screens no check covers: write and run them (default), only write them, or neither. Drafts never fail the PR
</ResponseField>

<ResponseField name={"--no-gifs"} type={"flag"} default={"off"}>
  don't make a GIF of each failed check
</ResponseField>

<ResponseField name={"--no-made-with"} type={"flag"} default={"off"}>
  leave the "Made with Mobster" line out of the PR comment
</ResponseField>

<ResponseField name={"--comment"} type={"FILE"}>
  also write the PR comment's Markdown here
</ResponseField>

<ResponseField name={"--github"} type={"check | comment | off"} default={"check"}>
  put the verdict on the pull request with Mobster Checks, the GitHub App: a check run (check, the default), the check run and one comment kept up to date (comment), or nothing (off)
</ResponseField>

<ResponseField name={"--at"} type={"HH:MM"}>
  with nightly on: the time to run every night, on a 24-hour clock, such as 02:00
</ResponseField>

<ResponseField name={"--accept"} type={"flag"} default={"all"}>
  keep the healed steps of these checks (default: all)
</ResponseField>

<ResponseField name={"--reject"} type={"flag"} default={"off"}>
  keep failing: put the recorded steps back and fail when they change again
</ResponseField>

<ResponseField name={"--env-file"} type={"PATH"} default={"$MOBSTER_ENV_FILE"}>
  load KEY=VALUE lines, such as OPENAI\_API\_KEY or ANTHROPIC\_API\_KEY for Smart steps; variables already set win; default: `$MOBSTER_ENV_FILE`
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 passed, 1 failed, 2 needs review, 3 couldn't run, 130 ctrl+c

examples:
  mobster test
  mobster test --sim "iPhone 17 Pro" --sim "iPhone SE (3rd generation)" --parallel 2
  mobster test --junit build/junit.xml --html build/mobster-report
  mobster test record --run 3f9c2a1b7d0e --name paywall
```

## `mobster mcp`

Serve Mobster as an MCP server on stdio, so your coding agent can launch and check an iOS app on a simulator: it starts a check with verify\_start, drives the app with screen, tap, type\_text and swipe, and gets the verdict from verify\_finish.

**Options**

<ResponseField name={"--env-file"} type={"PATH"} default={"$MOBSTER_ENV_FILE"}>
  load KEY=VALUE lines; OPENAI\_API\_KEY or ANTHROPIC\_API\_KEY turns on smart in verify\_start; variables already set win; default: `$MOBSTER_ENV_FILE`
</ResponseField>

<ResponseField name={"--keyless"} type={"flag"} default={"off"}>
  never offer smart in verify\_start, even with an OpenAI or Anthropic key: the coding agent drives
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"iPhone 17 Pro"}>
  the device for runs and phone tools that don't name one: an id, UDID or name from `mobster devices`, or a simulator device type (default: iPhone 17 Pro)
</ResponseField>

<ResponseField name={"--allow-device"} type={"NAME"} post={["repeatable"]}>
  drive only this real iPhone (id, UDID or name), never another; repeatable. Without it every set-up device is allowed; simulators always are
</ResponseField>

<ResponseField name={"--allow-testflight"} type={"BUNDLE"} post={["repeatable"]}>
  let run\_tests with pr drive this app's TestFlight build on an `--allow-device` iPhone as your own app; repeatable
</ResponseField>

<ResponseField name={"--allow-files"} type={"flag"} default={"off"}>
  offer the file tools (put\_file, get\_file, list\_files, and send\_media, which adds to Photos) when this build has them; off by default
</ResponseField>

<ResponseField name={"--allow-photos"} type={"flag"} default={"off"}>
  offer pull\_media, which copies the newest photos and recordings from an iPhone's camera roll to this Mac; off by default
</ResponseField>

<ResponseField name={"--runtime"} type={"NAME"} default={"the newest installed"}>
  the iOS runtime for runs that don't name one, such as "iOS 26.4" (default: the newest installed)
</ResponseField>

<ResponseField name={"--out"} type={"DIR"} default={"$MOBSTER_RUNS_DIR, else ./.mobster/runs"}>
  where runs go (default: `$MOBSTER_RUNS_DIR`, else ./.mobster/runs)
</ResponseField>

<ResponseField name={"--log"} type={"FILE"}>
  also append the server's log to FILE (it never holds keys or long typed text)
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster mcp install --all      add Mobster to Claude Code, Codex, Cursor and the others found here
  mobster mcp clients            which agent clients are here, and which run Mobster
  mobster mcp doctor             start the server as a client would and call status
```

| Command | What it does |
| - | - |
| [`mobster mcp install`](#mobster-mcp-install) | add Mobster's server to Claude Code, Codex, Cursor and others |
| [`mobster mcp clients`](#mobster-mcp-clients) | list the agent clients found here and whether Mobster is set up |
| [`mobster mcp doctor`](#mobster-mcp-doctor) | start the server as a client would, list its tools and call status |

### `mobster mcp install`

Add Mobster's MCP server to your agents' configs: Claude Code, Codex, Cursor, VS Code, Claude Desktop, opencode, Gemini CLI, Windsurf, Zed, Goose, Amp, Cline, oh-my-pi (omp) and the agents inside Xcode. It writes the absolute path of this mobster, because apps opened from the Dock don't see your shell's PATH. Claude Code and Codex are set up with their own `mcp add` commands; the rest by editing the file, keeping its comments and formatting, with a copy of the old file beside it (.mobster-backup). Running it again changes nothing. It never writes a key: Smart's key stays in `--env-file`.

**Arguments**

<ResponseField name={"CLIENT"} type={"CLIENT, zero or more"}>
  the clients to set up, by name (see `mobster mcp clients`)
</ResponseField>

**Options**

<ResponseField name={"--all"} type={"flag"} default={"off"}>
  every client found on this Mac
</ResponseField>

<ResponseField name={"--project"} type={"DIR"}>
  write the project's config in DIR (such as .cursor/mcp.json) instead of yours
</ResponseField>

<ResponseField name={"--allow-device"} type={"NAME"} post={["repeatable"]}>
  the server drives only this real iPhone; repeatable
</ResponseField>

<ResponseField name={"--allow-testflight"} type={"BUNDLE"} post={["repeatable"]}>
  run\_tests with pr may drive this app's TestFlight build on an allowed iPhone; repeatable
</ResponseField>

<ResponseField name={"--env-file"} type={"PATH"}>
  the server loads this file (OPENAI\_API\_KEY or ANTHROPIC\_API\_KEY for Smart); it must be readable by you only (chmod 600)
</ResponseField>

<ResponseField name={"--with-skill"} type={"flag"} default={"off"}>
  also copy the mobster skill into each client's skills folder
</ResponseField>

<ResponseField name={"--with-xcode-tools"} type={"flag"} default={"off"}>
  also add Xcode's own tools (xcrun mcpbridge, Xcode 26.3 or later) to Claude Code and Codex
</ResponseField>

<ResponseField name={"--dry-run"} type={"flag"} default={"off"}>
  show what would change and write nothing
</ResponseField>

<ResponseField name={"--remove"} type={"flag"} default={"off"}>
  take Mobster out of the clients' configs instead
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the result as JSON
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster mcp install --all                      every client found on this Mac
  mobster mcp install claude-code cursor --with-skill
  mobster mcp install codex --env-file ~/.config/mobster/agent.env
  mobster mcp install --all --allow-device "Test iPhone"
  mobster mcp install cursor --project .         this repository's .cursor/mcp.json
  mobster mcp install xcode                      the agents inside Xcode
  mobster mcp install claude-code codex --with-xcode-tools
  mobster mcp install --all --remove --with-skill

clients: claude-code, codex, cursor, vscode, claude-desktop, opencode, gemini, windsurf, zed, goose, amp, cline, omp, xcode
```

### `mobster mcp clients`

List the agent clients Mobster can set up, whether each is installed on this Mac, and whether its config runs Mobster's server.

**Options**

<ResponseField name={"--project"} type={"DIR"}>
  look at the project's configs in DIR
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the list as JSON
</ResponseField>

### `mobster mcp doctor`

Start Mobster's MCP server the way an app opened from the Dock would (its absolute path, launchd's PATH, the home folder), send initialize, list the tools and call status, with the time each step took. Then check that every client's entry runs a mobster that exists.

**Options**

<ResponseField name={"--env-file"} type={"PATH"}>
  start the server with this env file, as install would
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the report as JSON
</ResponseField>

## `mobster login`

Save your Claude or OpenAI key for Mobster's agent, on this Mac. Mobster tests the key with one tiny request first, then saves it in Mobster's settings file, which only you can read. Mobster for Mac and every `mobster` command use it. The key is never shown: only its first characters and last four.

`mobster login <plan or provider>` saves a plan's key or sign-in (Kimi Code, Grok ...) or another provider's key. A ChatGPT, Claude or Google plan runs through your own Codex, Claude Code or Gemini CLI, which signs in itself: `mobster login codex` says how.

`mobster login status` shows which keys, plans and providers are saved.

**Arguments**

<ResponseField name={"status|PLAN|PROVIDER"} type={"status|PLAN|PROVIDER, optional"}>
  status: show what is saved, masked; a plan or provider: kimi, zai, grok, openrouter, xai, custom ...; codex, claude or gemini: how that CLI signs in
</ResponseField>

**Options**

<ResponseField name={"--provider"} type={"anthropic | openai"}>
  anthropic (Claude, the default) or openai
</ResponseField>

<ResponseField name={"--with-key"} type={"flag"} default={"off"}>
  read the key from standard input (a pipe or a file, never a terminal)
</ResponseField>

<ResponseField name={"--skip-check"} type={"flag"} default={"off"}>
  save it without testing it (no network)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the result as JSON
</ResponseField>

<ResponseField name={"--model"} type={"ID"}>
  a plan or provider: the model Mobster's agent uses there
</ResponseField>

<ResponseField name={"--base-url"} type={"URL"}>
  custom, ollama or lmstudio: the endpoint's base URL, such as [http://127.0.0.1:11434/v1](http://127.0.0.1:11434/v1)
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 saved (or status shown), 1 the key or the sign-in didn't work, 2 usage,
            130 ctrl+c

examples:
  mobster login
  mobster login --provider openai
  mobster login --provider anthropic --with-key < key.txt
  mobster login kimi
  mobster login status
```

## `mobster setup`

Get your iPhone ready for Mobster: plug it in, trust this Mac, turn on Developer Mode and put Mobster's helper on it. Each step ticks itself off as it happens. The same guided setup as Mobster for Mac's.

**Options**

<ResponseField name={"--device"} type={"NAME"}>
  the iPhone to set up, when several are plugged in
</ResponseField>

<ResponseField name={"--team"} type={"ID"}>
  a paid Apple developer team ID to sign Mobster's helper with
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print each change as a JSON line
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 your iPhone is ready, 1 a step failed, 3 couldn't run (no Xcode, no tools),
            130 ctrl+c

examples:
  mobster setup
  mobster setup --device "Sam's iPhone"
```

## `mobster chat`

Start or continue a conversation with Mobster's agent on your iPhone. Follow-ups use what earlier tasks found; type while it works to steer it. Uses Mobster for Mac or `mobster serve` when one is open, so approvals show there too. With neither open, a message runs here in the terminal, and `mobster chat` alone opens the terminal UI.

**Arguments**

<ResponseField name={"MESSAGE"} type={"MESSAGE, zero or more"}>
  what to ask Mobster's agent; leave it out for a conversation in this terminal
</ResponseField>

**Options**

<ResponseField name={"--thread"} type={"ID"} default={"the one this terminal used last"}>
  continue this conversation (default: the one this terminal used last)
</ResponseField>

<ResponseField name={"--new"} type={"flag"} default={"off"}>
  start a new conversation
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"the conversation's"}>
  the iPhone or simulator: an id, UDID or name (default: the conversation's)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object when the task finishes or needs you; never answers an approval
</ResponseField>

<ResponseField name={"--keep-waiting"} type={"flag"} default={"off"}>
  with `--json` and the app or mobster serve running: wait while the task waits for you, until it ends or `--wait` runs out
</ResponseField>

<ResponseField name={"--wait"} type={"SECONDS"} default={"300"}>
  with the app or mobster serve running: how long to follow a task (default 300); it goes on after
</ResponseField>

<ResponseField name={"--url"} type={"URL"} default={"$MOBSTER_URL, else the running Mac app's, else http://127.0.0.1:8765"}>
  Mobster's address (default: `$MOBSTER_URL`, else the running Mac app's, else [http://127.0.0.1:8765](http://127.0.0.1:8765))
</ResponseField>

<ResponseField name={"--workflow"} type={"NAME"}>
  run a saved workflow (its name or id) instead of a message: in a new conversation, its own one, or `--thread` ID
</ResponseField>

<ResponseField name={"--set"} type={"BLANK=VALUE"} post={["repeatable"]}>
  with `--workflow`: a value for one of its blanks; repeat it for each
</ResponseField>

`--thread` and `--new` can't be used together.

```text theme={"theme":"css-variables"}
examples:
  mobster chat
  mobster chat "What's on my calendar tomorrow?"
  mobster chat --thread 3f9c2a1b7d0e "Now text Sam the first one"
  mobster chat --workflow "Reels digest" --set topic=basketball
```

## `mobster workflow`

Print a saved workflow's steps from Mobster for Mac or `mobster serve`, in the order they run: each one's type, title and the steps it waits for. `mobster chat --workflow` runs one.

```text theme={"theme":"css-variables"}
examples:
  mobster workflow show "Answer DMs"
  mobster workflow show 3f9c2a1b7d0e --draft --json
```

| Command | What it does |
| - | - |
| [`mobster workflow show`](#mobster-workflow-show) | print a workflow's steps in the order they run |

### `mobster workflow show`

Print a saved workflow's steps in the order they run: each one's id, type, title and the steps it waits for, with a loop's steps under it. It shows the published version unless you add `--draft`, and changes nothing.

**Arguments**

<ResponseField name={"NAME"} type={"NAME"} required>
  the workflow's name or id
</ResponseField>

**Options**

<ResponseField name={"--draft"} type={"flag"} default={"off"}>
  the draft you're editing, not the published version
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the steps as JSON
</ResponseField>

<ResponseField name={"--url"} type={"URL"} default={"$MOBSTER_URL, else the running Mac app's, else http://127.0.0.1:8765"}>
  Mobster's address (default: `$MOBSTER_URL`, else the running Mac app's, else [http://127.0.0.1:8765](http://127.0.0.1:8765))
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster workflow show "Answer DMs"
  mobster workflow show 3f9c2a1b7d0e --draft --json
```

## `mobster run`

Run one task on your iPhone and print each step. On a terminal each step prints as a line; piped, or with `--json`, every event prints as one JSON object per line, and the last is the result.

Mobster's agent runs it on your Claude or OpenAI key. It asks before it sends, buys, posts or deletes: on a terminal you answer y or n; with `--json` or a pipe the answer is no, and the run ends as declined.

Long tasks ("scroll basketball Reels for 10 minutes and tell me what you learned", "go through my unread emails"): Mobster prints a plan with its estimate and asks Start? \[Y/n] on a terminal (`--yes` starts at once). It covers the budget in as little phone time as it can, hands the phone back, then prints the report as Markdown.

ctrl+c stops at the next safe point (twice to force).

**Arguments**

<ResponseField name={"TASK"} type={"TASK"} required>
  what to do, in plain words
</ResponseField>

**Options**

<ResponseField name={"--wda-url"} type={"URL"} default={"http://127.0.0.1:8100"}>
  the phone's address, for a phone you set up by hand (default: [http://127.0.0.1:8100](http://127.0.0.1:8100)) \[env: MOBSTER\_WDA\_URL]
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the iPhone or simulator to use: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--env-file"} type={"PATH"}>
  load KEY=VALUE lines (ANTHROPIC\_API\_KEY, OPENAI\_API\_KEY, ...); variables already set win \[env: MOBSTER\_ENV\_FILE]
</ResponseField>

<ResponseField name={"--execute"} type={"flag"} default={"off"}>
  act on the phone (a task needs it)
</ResponseField>

<ResponseField name={"--demo"} type={"flag"} default={"off"}>
  a scripted phone and policy: no iPhone, no keys, no model calls
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print raw JSON events, even on a terminal
</ResponseField>

<ResponseField name={"--spend-cap-usd"} type={"USD"}>
  stop before further model calls once this task has cost USD
</ResponseField>

<ResponseField name={"--long"} type={"flag"} default={"off"}>
  run it as a long task, with a plan first and a report at the end (most long tasks are found on their own)
</ResponseField>

<ResponseField name={"--budget"} type={"VALUE"}>
  a long task's budget: a time (10m, 90s, 1h), a number of items (30) or all
</ResponseField>

<ResponseField name={"--yes"} type={"flag"} default={"off"}>
  start a long task without asking (its plan still prints)
</ResponseField>

`--wda-url` and `--device` can't be used together.

```text theme={"theme":"css-variables"}
exit codes: 0 done, 1 the task didn't finish, 2 usage error,
            3 couldn't run (no phone answered, no key), 130 stopped with ctrl+c

examples:
  mobster run "Turn on Dark Mode" --execute
  mobster run "What's my battery level?" --execute --json
  mobster run "Turn on Dark Mode" --demo          a scripted phone: no iPhone, no keys
  mobster run "Scroll basketball Reels for 10 minutes and tell me what you learned" --execute
  mobster run "Go through my unread emails" --execute --long --budget all
```

## `mobster memory`

List, add and delete the things Mobster remembers, in your words. Mobster's agent reads the ones that fit each task. Everything stays on this Mac.

```text theme={"theme":"css-variables"}
examples:
  mobster memory list
  mobster memory add "My gym is the one on 5th Street"
  mobster memory pin 3f9c2a1b7d0e
```

| Command | What it does |
| - | - |
| [`mobster memory list`](#mobster-memory-list) | list what Mobster remembers (the default) |
| [`mobster memory add`](#mobster-memory-add) | tell Mobster something to remember |
| [`mobster memory rm`](#mobster-memory-rm) | forget one thing |
| [`mobster memory pin`](#mobster-memory-pin) | send one thing with every task |
| [`mobster memory unpin`](#mobster-memory-unpin) | send one thing only with tasks that mention it |
| [`mobster memory clear`](#mobster-memory-clear) | delete everything Mobster remembers |
| [`mobster memory export`](#mobster-memory-export) | print everything Mobster remembers as Markdown |

### `mobster memory list`

List what Mobster remembers: pinned things first, then the newest.

**Options**

<ResponseField name={"--app"} type={"BUNDLE"}>
  only what Mobster remembers for this app (its bundle ID)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print JSON
</ResponseField>

### `mobster memory add`

Tell Mobster something to remember, in your words (up to 300 characters). Pinned things go with every task; the rest go with tasks that mention them. Mobster never remembers passwords, codes or card numbers.

**Arguments**

<ResponseField name={"TEXT"} type={"TEXT"} required>
  what to remember
</ResponseField>

**Options**

<ResponseField name={"--app"} type={"BUNDLE"}>
  only for tasks in this app (its bundle ID)
</ResponseField>

<ResponseField name={"--pin"} type={"flag"} default={"off"}>
  send it with every task (up to 10 pinned)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print JSON
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster memory add "My gym is the one on 5th Street"
  mobster memory add "Sign my texts with – Sam" --app com.apple.MobileSMS --pin
```

### `mobster memory rm`

Forget one thing, by the ID `list` shows.

**Arguments**

<ResponseField name={"ID"} type={"ID"} required>
  the 12-character ID from `mobster memory list`
</ResponseField>

### `mobster memory pin`

Pin one thing, so it goes with every task (up to 10 pinned).

**Arguments**

<ResponseField name={"ID"} type={"ID"} required>
  the 12-character ID from `mobster memory list`
</ResponseField>

### `mobster memory unpin`

Unpin one thing: it goes only with tasks that mention it.

**Arguments**

<ResponseField name={"ID"} type={"ID"} required>
  the 12-character ID from `mobster memory list`
</ResponseField>

### `mobster memory clear`

Delete everything Mobster remembers, and its suggestions. Asks you to type "delete everything" first, unless you add `--yes`.

**Options**

<ResponseField name={"--yes"} type={"flag"} default={"off"}>
  don't ask
</ResponseField>

### `mobster memory export`

Print everything Mobster remembers as Markdown, or save it with `--output`.

**Options**

<ResponseField name={"--output"} type={"FILE"}>
  save it to this file instead
</ResponseField>

## `mobster history`

List the tasks you ran in this terminal, newest first. Open one with `mobster --resume ID`, or save one as a GIF with `mobster export ID --gif`. The history holds task text and screen text: keep it private.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object per task
</ResponseField>

<ResponseField name={"-n, --limit"} type={"N"} default={"20"}>
  show N tasks (default: 20)
</ResponseField>

## `mobster export`

Save a finished task as a captioned GIF, one screen per step with what Mobster did, ready to post: under X's 15 MB limit, on the Paper or Night ground. `--mp4` saves an MP4 too (it needs ffmpeg). The file goes to \~/Downloads and never replaces one there. It shows every screen of the task, so check it before you post it: `--redact` blurs text fields and messages and leaves the answer out. Nothing is uploaded.

**Arguments**

<ResponseField name={"ID"} type={"ID"} required>
  a task's ID from `mobster history` (its first characters do), or latest
</ResponseField>

**Options**

<ResponseField name={"--gif"} type={"flag"} default={"off"}>
  save a GIF
</ResponseField>

<ResponseField name={"--mp4"} type={"flag"} default={"off"}>
  save an MP4 (needs ffmpeg)
</ResponseField>

<ResponseField name={"--redact"} type={"flag"} default={"off"}>
  blur text fields and messages, and leave out the answer
</ResponseField>

<ResponseField name={"--theme"} type={"paper | night"} default={"paper"}>
  the ground: paper (light, the default) or night (dark)
</ResponseField>

<ResponseField name={"-o, --output"} type={"PATH"} default={"~/Downloads"}>
  a file or folder to save to (default: \~/Downloads)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print where it saved as JSON
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 saved, 1 couldn't (no such task, no screens, no ffmpeg for --mp4), 2 usage

examples:
  mobster export latest --gif
  mobster export 3f9c2a1b7d10 --gif --mp4 --theme night
  mobster export 3f9c --gif --redact -o ~/Desktop/clip.gif
```

## `mobster screen`

Print your iPhone's current screen. kitty, Ghostty, iTerm2 and WezTerm show the real image; other terminals get a half-block drawing. Nothing is tapped.

**Options**

<ResponseField name={"--wda-url"} type={"URL"} default={"http://127.0.0.1:8100"}>
  the phone's address, for a phone you set up by hand (default: [http://127.0.0.1:8100](http://127.0.0.1:8100)) \[env: MOBSTER\_WDA\_URL]
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the iPhone or simulator to use: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--out"} type={"FILE"}>
  save the PNG to FILE instead of printing it
</ResponseField>

<ResponseField name={"--width"} type={"COLUMNS"}>
  draw it this many columns wide
</ResponseField>

## `mobster xray`

X-ray an app on your iPhone. `app` maps an app with App X-ray: Mobster taps through every control that opens a screen and writes a map of the screens, how they connect and the paywalls it found, with no model and no tap on anything that could act. `network` records the phone's traffic with Network X-ray and says which domains and companies each app talked to, reading hostnames, sizes and times only. `diff` compares two maps to see what an update changed. Everything stays on this Mac.

```text theme={"theme":"css-variables"}
examples:
  mobster xray app com.apple.Preferences
  mobster xray app Weather --screens 60 --minutes 10
  mobster xray network --device "Test iPhone" --seconds 30
  mobster xray diff before/map.json after/map.json
```

| Command | What it does |
| - | - |
| [`mobster xray app`](#mobster-xray-app) | map an app's screens, links and paywalls with no model |
| [`mobster xray network`](#mobster-xray-network) | record your iPhone's network traffic and say who each app talked to |
| [`mobster xray diff`](#mobster-xray-diff) | compare two maps of one app |

### `mobster xray app`

Map an app on your iPhone: Mobster taps through every control that opens a screen, within the limits you set, and writes the map (map.json), a report (report.html) and a thumbnail of each screen to a folder on this Mac. It reads paywalls and taps nothing on them. It never taps a control that could act (delete, send, buy, a switch, a field), never types, and calls no model, so it needs no key. It runs like any task: your plan, the phone's queue and Stop apply.

**Arguments**

<ResponseField name={"APP"} type={"APP"} required>
  the app's bundle ID, or its name on the phone
</ResponseField>

**Options**

<ResponseField name={"--device"} type={"NAME"}>
  the iPhone to use (an id, UDID or name from `mobster devices`)
</ResponseField>

<ResponseField name={"--screens"} type={"N"} default={"30"}>
  stop after this many screens, 1 to 120 (default 30)
</ResponseField>

<ResponseField name={"--minutes"} type={"N"} default={"5"}>
  stop after this many minutes, 1 to 30 (default 5)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print each event as one JSON line; the last is the result
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 mapped, 1 stopped or mapped nothing, 2 usage, 3 couldn't run (no phone, no plan, app not found,
            an app it doesn't map), 130 ctrl+c

examples:
  mobster xray app com.apple.Preferences
  mobster xray app Weather --screens 60 --minutes 10 --device "Test iPhone"
  mobster xray app com.example.yourapp --json
```

### `mobster xray network`

Record an iPhone's network traffic for a while and say who each app talked to: the domains, the companies behind them, and which do ads, analytics or attribution. Mobster reads hostnames, sizes and times. It decrypts nothing and keeps no page, no URL path and no address. The capture file is deleted after the analysis unless you pass `--keep-capture`.

**Options**

<ResponseField name={"--device"} type={"NAME"} default={"the one connected"}>
  the iPhone: an id, UDID or name from `mobster devices` (default: the one connected)
</ResponseField>

<ResponseField name={"--seconds"} type={"N"} default={"60"}>
  how long to record, 5 to 3600 (default 60)
</ResponseField>

<ResponseField name={"--wifi"} type={"flag"} default={"off"}>
  record over Wi-Fi even with the cable in (refused unless the link is encrypted)
</ResponseField>

<ResponseField name={"--app"} type={"NAME"}>
  report only this app, by its name on the iPhone
</ResponseField>

<ResponseField name={"--keep-capture"} type={"flag"} default={"off"}>
  keep the .pcapng beside the report (readable only by you)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

<ResponseField name={"--from"} type={"FILE.pcapng"}>
  read a capture you already have, with no iPhone
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 done, 1 the capture failed, 2 usage, 3 couldn't run (no such iPhone, a simulator, no
            capture tool)

examples:
  mobster xray network --device "Test iPhone" --seconds 30
  mobster xray network --device "Test iPhone" --app Weather
  mobster xray network --from capture.pcapng
```

### `mobster xray diff`

Compare two maps `mobster xray app` wrote: the screens added, removed and changed, and the paywall prices that moved. Each argument is a map.json file or the folder that holds it. It reads two files on this Mac and needs no phone and no plan.

**Arguments**

<ResponseField name={"BEFORE"} type={"BEFORE"} required>
  the older map: a map.json file or its folder
</ResponseField>

<ResponseField name={"AFTER"} type={"AFTER"} required>
  the newer map: a map.json file or its folder
</ResponseField>

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the comparison as JSON
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 compared (changes or none), 1 a map couldn't be read, 2 usage

examples:
  mobster xray diff before/map.json after/map.json
  mobster xray diff before after --json
```

## `mobster verify`

Check an iOS app on a headless simulator that Mobster manages. Mobster installs and launches the app, runs the steps (Smart, on your OpenAI or Anthropic key) or none (a launch-only check), and decides the verdict from your expectations on the accessibility tree, never from a model. Each run writes a report with the frames that prove it.

A check is a saved YAML file. verify is the one-off form of `mobster test`: it runs one check, from flags or `--check` FILE, and `--save` NAME keeps it for `mobster test`.

**Arguments**

<ResponseField name={"STEP"} type={"STEP, zero or more"}>
  one step of the flow in plain English; none means a launch-only check
</ResponseField>

**Options**

<ResponseField name={"--check"} type={"FILE"}>
  run a saved check (.yaml or .json); not combined with steps or expectations
</ResponseField>

<ResponseField name={"--app"} type={"PATH"}>
  the .app built for the iOS Simulator; it is installed before the run
</ResponseField>

<ResponseField name={"--bundle"} type={"ID"} default={"read from --app"}>
  the app's bundle ID (default: read from `--app`)
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"iPhone 17 Pro, else the newest iPhone"}>
  a device from `mobster devices` (its id, UDID or name), or the simulator device type (default: iPhone 17 Pro, else the newest iPhone)
</ResponseField>

<ResponseField name={"--runtime"} type={"NAME"} default={"the newest installed"}>
  the iOS runtime, such as "iOS 26.4" (default: the newest installed)
</ResponseField>

<ResponseField name={"--reset"} type={"none | data | reinstall"} default={"reinstall with --app, else data; com.apple.* apps are never reset"}>
  none, data or reinstall (default: reinstall with `--app`, else data; com.apple.\* apps are never reset)
</ResponseField>

<ResponseField name={"--launch-arg"} type={"ARG"} post={["repeatable"]}>
  a launch argument; repeatable (write `--launch-arg=-Flag` for one that starts with -)
</ResponseField>

<ResponseField name={"--launch-env"} type={"KEY=VALUE"} post={["repeatable"]}>
  a launch environment variable; repeatable
</ResponseField>

<ResponseField name={"--open-url"} type={"URL"}>
  a deep link opened after launch
</ResponseField>

<ResponseField name={"--text"} type={"TEXT"} post={["repeatable"]}>
  expect TEXT on screen; repeatable
</ResponseField>

<ResponseField name={"--no-text"} type={"TEXT"} post={["repeatable"]}>
  expect TEXT not on screen; repeatable
</ResponseField>

<ResponseField name={"--visible"} type={"SEL"} post={["repeatable"]}>
  expect an element on screen: key=value\[,key=value] with keys id, label, value, role, enabled and selected; repeatable
</ResponseField>

<ResponseField name={"--absent"} type={"SEL"} post={["repeatable"]}>
  expect no element to match SEL; repeatable
</ResponseField>

<ResponseField name={"--expect"} type={"JSON"} post={["repeatable"]}>
  any expectation as JSON, such as '\{"count": \{"id": "/^plan\_/"}, "equals": 3}'; repeatable
</ResponseField>

<ResponseField name={"--name"} type={"TEXT"} default={"the first step, or \"Launch check\""}>
  the check's name in reports (default: the first step, or "Launch check")
</ResponseField>

<ResponseField name={"--keyless"} type={"flag"} default={"off"}>
  never call a model; a check with steps then can't run
</ResponseField>

<ResponseField name={"--max-usd"} type={"USD"} default={"0.25, at most 1.00"}>
  Smart's spend cap for the run (default: 0.25, at most 1.00)
</ResponseField>

<ResponseField name={"--max-seconds"} type={"S"} default={"180, at most 600"}>
  Smart's time limit for the flow (default: 180, at most 600)
</ResponseField>

<ResponseField name={"--assert-timeout"} type={"S"} default={"5"}>
  how long expectations may wait for the screen to settle (default: 5)
</ResponseField>

<ResponseField name={"--out"} type={"DIR"} default={"./.mobster/runs"}>
  where runs go (default: ./.mobster/runs)
</ResponseField>

<ResponseField name={"--save"} type={"NAME"}>
  also save the check as .mobster/checks/NAME.yaml (a-z, 0-9 and -)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the result as JSON (the default when stdout isn't a terminal)
</ResponseField>

<ResponseField name={"--env-file"} type={"PATH"} default={"$MOBSTER_ENV_FILE"}>
  load KEY=VALUE lines, such as OPENAI\_API\_KEY or ANTHROPIC\_API\_KEY for Smart; variables already set win; default: `$MOBSTER_ENV_FILE`
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 passed, 1 failed, 2 needs review, 3 couldn't run (usage errors too),
            130 ctrl+c

examples:
  mobster verify --bundle com.apple.Preferences --text General
  mobster verify --app build/YourApp.app --open-url yourapp://lesson --text "Fill in the blank"
  mobster verify "Open General, then About" --bundle com.apple.Preferences --text "iOS Version"
  mobster verify --check .mobster/checks/lesson-complete.yaml --json
```

## `mobster sim`

Manage the headless simulators that `mobster verify` and `mobster mcp` run on: list them, prepare one (create, boot, build and start WebDriverAgent), shut them down, erase or delete them. It deletes only simulators Mobster created.

| Command | What it does |
| - | - |
| [`mobster sim list`](#mobster-sim-list) | list Mobster's simulators |
| [`mobster sim prepare`](#mobster-sim-prepare) | create, boot and start WebDriverAgent, timing each phase |
| [`mobster sim biometric`](#mobster-sim-biometric) | test biometric enrollment and authentication on a simulator |
| [`mobster sim shutdown`](#mobster-sim-shutdown) | stop WebDriverAgent and shut down Mobster's simulators |
| [`mobster sim erase`](#mobster-sim-erase) | erase one Mobster simulator |
| [`mobster sim delete`](#mobster-sim-delete) | delete Mobster's simulators |
| [`mobster sim prune`](#mobster-sim-prune) | remove unused simulators and stale WebDriverAgent builds |
| [`mobster sim doctor`](#mobster-sim-doctor) | check what `mobster verify` needs |

### `mobster sim list`

List the simulators Mobster created, with their state and WebDriverAgent address.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print JSON instead of a table
</ResponseField>

### `mobster sim prepare`

Create the simulator if needed, boot it headless, build WebDriverAgent the first time, start it on 127.0.0.1, and print how long each phase took. It stays up afterwards.

**Options**

<ResponseField name={"--device"} type={"NAME"} default={"iPhone 17 Pro, else the newest iPhone the runtime supports"}>
  the simulator device type (default: iPhone 17 Pro, else the newest iPhone the runtime supports)
</ResponseField>

<ResponseField name={"--runtime"} type={"NAME"} default={"the newest installed"}>
  the iOS runtime, such as "iOS 26.4" (default: the newest installed)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the simulator and the phase times as JSON
</ResponseField>

### `mobster sim biometric`

Read or change biometric enrollment, or send a Face ID or Touch ID test event to an idle, booted Mobster simulator. Real devices are never supported. A sent event does not confirm that the app authenticated.

**Arguments**

<ResponseField name={"UDID"} type={"UDID"} required>
  the booted Mobster simulator
</ResponseField>

<ResponseField name={"ACTION"} type={"status | enroll | unenroll | match | nonmatch"} required>
  read enrollment, enable or disable it, or send a match or nonmatch event
</ResponseField>

**Options**

<ResponseField name={"--type"} type={"face-id | touch-id"} default={"face-id"}>
  the biometric event type (default: face-id; enrollment is shared)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print enrollment and event delivery as JSON
</ResponseField>

### `mobster sim shutdown`

Stop WebDriverAgent and shut down every Mobster simulator that isn't running a check, or the one named.

**Arguments**

<ResponseField name={"UDID"} type={"UDID, optional"}>
  only this simulator
</ResponseField>

### `mobster sim erase`

Shut down and erase one Mobster simulator: its apps and data go, and the next run boots it fresh.

**Arguments**

<ResponseField name={"UDID"} type={"UDID"} required>
  the simulator to erase
</ResponseField>

### `mobster sim delete`

Shut down and delete Mobster's simulators. Only UDIDs in Mobster's registry whose names start with “Mobster · ” are deleted; any other UDID is refused.

**Arguments**

<ResponseField name={"UDID"} type={"UDID, optional"}>
  the simulator to delete
</ResponseField>

**Options**

<ResponseField name={"--all"} type={"flag"} default={"off"}>
  delete every Mobster simulator
</ResponseField>

`UDID` and `--all` can't be used together; one is required.

### `mobster sim prune`

Delete Mobster simulators unused for 14 days, WebDriverAgent builds for Xcode versions no longer installed, and registry entries for simulators that are gone.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print what was removed as JSON
</ResponseField>

### `mobster sim doctor`

Check Xcode, the iOS runtime, the device type, git, disk space, WebDriverAgent, Mobster's simulators, ports, and your OpenAI or Anthropic key for Smart, and print the fix for each problem. It never runs sudo and never downloads a runtime.

**Options**

<ResponseField name={"--fix"} type={"flag"} default={"off"}>
  prepare the simulator: create, boot and build WebDriverAgent once
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print \{"ok", "checks": \[\{"key", "label", "state", "detail", "fix"}]}
</ResponseField>

## `mobster ci`

Run your app's checks on every pull request or merge request, with a Mac of yours as the GitHub Actions or GitLab CI runner: its simulators, and the iPhones plugged into it. One comment on the PR keeps the verdict, the repro steps, frames, GIFs and videos.

```text theme={"theme":"css-variables"}
examples:
  mobster ci init
  mobster ci doctor
  mobster ci run --device "Test iPhone"
  mobster ci comment --dry-run
```

| Command | What it does |
| - | - |
| [`mobster ci init`](#mobster-ci-init) | write the CI workflow for this repository (GitHub Actions or GitLab CI) |
| [`mobster ci doctor`](#mobster-ci-doctor) | check that this Mac is ready to be a CI runner |
| [`mobster ci run`](#mobster-ci-run) | test the PR or MR this job builds; the report goes to .mobster/ci |
| [`mobster ci comment`](#mobster-ci-comment) | keep one comment on the PR or MR with the verdict and repro steps |

### `mobster ci init`

Write the workflow that runs `mobster ci run` on every pull request or merge request, on a Mac of yours as a self-hosted runner.

**Options**

<ResponseField name={"--host"} type={"github | gitlab"} default={"from the origin remote"}>
  where the repository lives (default: from the origin remote)
</ResponseField>

<ResponseField name={"--device"} type={"NAME"}>
  also test on this iPhone, plugged into the runner (the runner must list it in MOBSTER\_RUNNER\_DEVICES)
</ResponseField>

<ResponseField name={"--testflight"} type={"BUNDLE"} post={["repeatable"]}>
  with `--device`: your app's TestFlight build on the phone; repeatable
</ResponseField>

<ResponseField name={"--force"} type={"flag"} default={"off"}>
  replace the workflow file if it exists
</ResponseField>

<ResponseField name={"--print"} type={"flag"} default={"off"}>
  print the workflow instead of writing it
</ResponseField>

### `mobster ci doctor`

Check this Mac as a CI runner: Xcode, a simulator runtime, Mobster for Mac and its plan, system sleep, automatic login, the runner service, free disk and the phones the runner allows. Reads only.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the rows as JSON
</ResponseField>

### `mobster ci run`

Run `mobster test pr` for the pull request or merge request this CI job builds, with a screen recording of each simulator run. The suite's report, JUnit file, frames, GIFs and videos go to .mobster/ci for the job's artifacts. Works the same in a terminal.

**Arguments**

<ResponseField name={"TEST_FLAG"} type={"TEST_FLAG, zero or more"}>
  more `mobster test pr` flags, after a --: mobster ci run -- `--retries` 2
</ResponseField>

**Options**

<ResponseField name={"--base"} type={"REF"} default={"the job's target branch, else origin/main"}>
  the branch the PR merges into (default: the job's target branch, else origin/main)
</ResponseField>

<ResponseField name={"--sim"} type={"TYPE[@RUNTIME]"} default={"each check's own"} post={["repeatable"]}>
  a simulator device type to run on; repeatable (default: each check's own)
</ResponseField>

<ResponseField name={"--device"} type={"NAME"}>
  an iPhone plugged into this Mac; in a CI job the runner must list it in MOBSTER\_RUNNER\_DEVICES
</ResponseField>

<ResponseField name={"--testflight"} type={"BUNDLE"} post={["repeatable"]}>
  with `--device`: your app's TestFlight build on the phone; repeatable
</ResponseField>

<ResponseField name={"--no-video"} type={"flag"} default={"off"}>
  don't record the simulator's screen
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 passed, 1 failed, 2 needs review, 3 couldn't run

examples:
  mobster ci run
  mobster ci run --device "Test iPhone"
  mobster ci run -- --retries 2 --parallel 2
```

### `mobster ci comment`

Post the verdict in .mobster/ci as one comment on the pull request (GITHUB\_TOKEN) or merge request (MOBSTER\_GITLAB\_TOKEN), updated in place on every push: each failure's repro steps, frame, GIF, video and the command that runs it again. Never fails the job.

**Options**

<ResponseField name={"--artifact-url"} type={"URL"} default={"$MOBSTER_ARTIFACT_URL, else the job's page"}>
  the uploaded report's link (default: `$MOBSTER_ARTIFACT_URL`, else the job's page)
</ResponseField>

<ResponseField name={"--dry-run"} type={"flag"} default={"off"}>
  print the comment and post nothing
</ResponseField>

## `mobster expo`

Open your Expo app on a simulator or your iPhone from the JavaScript Metro serves, in your dev client or Expo Go, and check a screen. Build it for checks and CI: Release for the simulator, with the JavaScript inside, or your dev client on your iPhone.

```text theme={"theme":"css-variables"}
examples:
  mobster expo preview --device "Test iPhone" --expect "Welcome back"
  mobster expo reload
  mobster expo build
  mobster expo build --device "Test iPhone"
```

| Command | What it does |
| - | - |
| [`mobster expo doctor`](#mobster-expo-doctor) | say what Mobster found in this Expo or React Native project |
| [`mobster expo preview`](#mobster-expo-preview) | open the app on a simulator or your iPhone from Metro, and check a screen |
| [`mobster expo dev`](#mobster-expo-dev) | run the dev loop: Metro, the dev client, the app open, a reload on every save |
| [`mobster expo reload`](#mobster-expo-reload) | reload the JavaScript in every app connected to Metro |
| [`mobster expo build`](#mobster-expo-build) | build the app for a simulator or your iPhone |
| [`mobster expo stop`](#mobster-expo-stop) | stop the Metro that `mobster expo preview` started |

### `mobster expo doctor`

Read package.json and the Expo config, and say what Mobster found: the app's name, bundle ID and schemes, whether it opens in your dev client or Expo Go, whether the ios folder exists, and whether Metro is running. Reads only.

**Options**

<ResponseField name={"--port"} type={"N"} default={"8081"}>
  Metro's port (default: 8081)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print it as JSON
</ResponseField>

### `mobster expo preview`

Open your app on a simulator or your iPhone from the JavaScript Metro serves from your working tree: in your dev client when the app has expo-dev-client, else in Expo Go. Starts Metro in the background when nothing answers on the port. With `--expect`, waits for the text and says whether it's on screen, with a screenshot.

**Options**

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone, else the only device"}>
  a device from `mobster devices` (default: your iPhone, else the only device)
</ResponseField>

<ResponseField name={"--port"} type={"N"} default={"8081"}>
  Metro's port (default: 8081)
</ResponseField>

<ResponseField name={"--path"} type={"PATH"}>
  then open this screen by the app's own deep link, such as settings/profile
</ResponseField>

<ResponseField name={"--expect"} type={"TEXT"} post={["repeatable"]}>
  text that must be on screen once the app loads; repeatable
</ResponseField>

<ResponseField name={"--wait"} type={"S"} default={"45"}>
  how long `--expect` waits for the bundle to load (default: 45)
</ResponseField>

<ResponseField name={"--no-start"} type={"flag"} default={"off"}>
  don't start Metro; fail when it isn't running
</ResponseField>

<ResponseField name={"--screenshot"} type={"FILE"} default={"with --expect: .mobster/expo/preview.png"}>
  save the screen here (default with `--expect`: .mobster/expo/preview\.png)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the result as JSON
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 opened (and every expectation held), 1 an expectation didn't hold, 3 couldn't

examples:
  mobster expo preview
  mobster expo preview --device "Test iPhone" --expect "Welcome back"
  mobster expo preview --path settings/profile --expect Profile
```

### `mobster expo dev`

Start this project's Metro (or reuse it), install your dev client on the device when it isn't there, open the app, and reload it each time you save a source file. When Metro can't bundle the app, it says so with Metro's error and keeps watching: the next save tries again. ctrl+c stops the Metro this command started. `--check` runs saved checks on the device once the app is up.

**Options**

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone, else the only device"}>
  a device from `mobster devices` (default: your iPhone, else the only device)
</ResponseField>

<ResponseField name={"--port"} type={"N"} default={"8081, else the next free one"}>
  Metro's port (default: 8081, else the next free one)
</ResponseField>

<ResponseField name={"--check"} type={"PATH"} post={["repeatable"]}>
  a check file or folder to run on the device once the app is up; repeatable
</ResponseField>

<ResponseField name={"--no-install"} type={"flag"} default={"off"}>
  don't build and install the dev client when it is missing
</ResponseField>

<ResponseField name={"--once"} type={"flag"} default={"off"}>
  open the app, run the checks, and stop; no watching
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON line per event
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 stopped with ctrl+c (or --once: opened and every check held), 1 Metro can't bundle the app or a check didn't hold, 3 couldn't

examples:
  mobster expo dev --device "Test iPhone"
  mobster expo dev --once --check .mobster/checks/home.yaml
```

### `mobster expo reload`

Send Metro's reload to every app connected to it, as pressing r in `npx expo start` does.

**Options**

<ResponseField name={"--port"} type={"N"} default={"8081"}>
  Metro's port (default: 8081)
</ResponseField>

### `mobster expo build`

Build the app. For a simulator: a Release build into .mobster/build with the JavaScript inside, so it runs with no Metro (what checks and CI use), after `npx expo prebuild` when there's no ios folder. For an iPhone: your dev client, signed with your team and installed by `npx expo run:ios --device`. `--eas` builds an EAS profile on this Mac with `eas build --local`.

**Options**

<ResponseField name={"--device"} type={"NAME"}>
  build for this device: an iPhone (installs your dev client) or a Mobster simulator
</ResponseField>

<ResponseField name={"--install"} type={"flag"} default={"off"}>
  install the build on `--device`
</ResponseField>

<ResponseField name={"--debug"} type={"flag"} default={"off"}>
  a Debug build that loads its JavaScript from Metro (the dev client), not Release
</ResponseField>

<ResponseField name={"--eas"} type={"PROFILE"}>
  build this EAS profile on this Mac (eas build `--local`)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the build as JSON
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster expo build
  mobster expo build --device "iPhone 17 Pro" --install
  mobster expo build --device "Test iPhone"
  mobster expo build --eas development --device "Test iPhone" --install
```

### `mobster expo stop`

Stop the Metro server `mobster expo preview` started in the background for this project.

## `mobster asc`

Use your own App Store Connect API key, kept in your Keychain, to list builds and read your testers' TestFlight feedback and crashes, and make App Store screenshots from your checks in every size and language. Read-only: Mobster submits nothing to Apple.

```text theme={"theme":"css-variables"}
examples:
  mobster asc key add --key-id 2X9R4HXF34 --issuer-id <issuer id> --p8 AuthKey_2X9R4HXF34.p8
  mobster asc builds --app com.example.yourapp
  mobster asc feedback --app com.example.yourapp --crashes
  mobster asc screenshots --size 6.9 --size 13
```

| Command | What it does |
| - | - |
| [`mobster asc key`](#mobster-asc-key) | add, show or remove your App Store Connect API key (kept in the Keychain) |
| [`mobster asc apps`](#mobster-asc-apps) | list the apps your key can see |
| [`mobster asc builds`](#mobster-asc-builds) | list an app's builds and their TestFlight state, newest first |
| [`mobster asc feedback`](#mobster-asc-feedback) | read your testers' TestFlight feedback and crashes |
| [`mobster asc screenshots`](#mobster-asc-screenshots) | make App Store screenshots from your checks, in every size and language |

### `mobster asc key`

Keep your App Store Connect API key in your login Keychain, readable only by Mobster's own programs. Make one in App Store Connect › Users and Access › Integrations. A CI job can pass MOBSTER\_ASC\_KEY\_ID, MOBSTER\_ASC\_ISSUER\_ID and MOBSTER\_ASC\_KEY\_PATH instead.

**Arguments**

<ResponseField name={"ACTION"} type={"add | show | remove"} required>
  add, show or remove
</ResponseField>

**Options**

<ResponseField name={"--key-id"} type={"ID"}>
  with add: the key's Key ID
</ResponseField>

<ResponseField name={"--issuer-id"} type={"UUID"}>
  with add: the Issuer ID (a team key; none for an individual key)
</ResponseField>

<ResponseField name={"--p8"} type={"FILE"}>
  with add: the AuthKey\_\<Key ID>.p8 file Apple gave you
</ResponseField>

### `mobster asc apps`

List the apps your key can see.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print JSON
</ResponseField>

### `mobster asc builds`

List an app's builds and their TestFlight state, newest first.

**Options**

<ResponseField name={"--app"} type={"BUNDLE"} required>
  the app's bundle ID
</ResponseField>

<ResponseField name={"--limit"} type={"N"} default={"10"}>
  how many (default: 10)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print JSON
</ResponseField>

### `mobster asc feedback`

List the screenshot feedback and crash reports your testers sent from TestFlight, newest first. `--save` downloads each crash log and screenshot.

**Options**

<ResponseField name={"--app"} type={"BUNDLE"} required>
  the app's bundle ID
</ResponseField>

<ResponseField name={"--crashes"} type={"flag"} default={"off"}>
  only crashes
</ResponseField>

<ResponseField name={"--screenshots"} type={"flag"} default={"off"}>
  only screenshot feedback
</ResponseField>

<ResponseField name={"--limit"} type={"N"} default={"20"}>
  how many (default: 20)
</ResponseField>

<ResponseField name={"--save"} type={"DIR"}>
  download the crash logs and screenshots here
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print JSON
</ResponseField>

### `mobster asc screenshots`

Run each check on a simulator of each App Store display size, in each language, with the status bar at 9:41, and save its last screen as a full-resolution PNG in the folders fastlane deliver reads. Uploads nothing.

**Arguments**

<ResponseField name={"PATH"} type={"PATH, zero or more"}>
  check files or folders (default: the checks tagged screenshot, else every check)
</ResponseField>

**Options**

<ResponseField name={"--size"} type={"6.9 | 6.5 | 6.3 | 13"} default={"6.9, and 13 for an iPad app"} post={["repeatable"]}>
  a display size; repeatable (default: 6.9, and 13 for an iPad app)
</ResponseField>

<ResponseField name={"--locale"} type={"CODE"} default={"every language the app has"} post={["repeatable"]}>
  an App Store locale such as de-DE; repeatable (default: every language the app has)
</ResponseField>

<ResponseField name={"-o, --output"} type={"DIR"} default={"fastlane/screenshots"}>
  where the PNGs go (default: fastlane/screenshots)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print screenshots.json
</ResponseField>

<ResponseField name={"--env-file"} type={"PATH"}>
  load KEY=VALUE lines (ANTHROPIC\_API\_KEY, OPENAI\_API\_KEY, ...); variables already set win \[env: MOBSTER\_ENV\_FILE]
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster asc screenshots
  mobster asc screenshots .mobster/checks/store --size 6.9 --size 13 --locale en-US --locale de-DE
```

## `mobster testflight`

List your app's TestFlight builds, pick one and install it on your iPhone through the TestFlight app, with an App Store Connect API key kept per project in your Keychain. The build and version go on the next `mobster test` report. Read-only: Mobster submits nothing to Apple.

```text theme={"theme":"css-variables"}
examples:
  mobster testflight key add --key-id 2X9R4HXF34 --issuer-id <issuer id> --p8 AuthKey_2X9R4HXF34.p8
  mobster testflight list --app com.example.yourapp
  mobster testflight install --app com.example.yourapp --build 87 --device "Test iPhone"
```

| Command | What it does |
| - | - |
| [`mobster testflight key`](#mobster-testflight-key) | add, show or remove this project's App Store Connect API key (kept in the Keychain) |
| [`mobster testflight list`](#mobster-testflight-list) | list the app's builds and whether TestFlight can install each, newest first |
| [`mobster testflight install`](#mobster-testflight-install) | install a TestFlight build on your iPhone through the TestFlight app |

### `mobster testflight key`

Keep an App Store Connect API key for this project in your login Keychain, readable only by Mobster's own programs, and never printed. Make one in App Store Connect › Users and Access › Integrations. The key is this project's: a folder with .mobster/ above the current one, or the current folder. `--shared` keeps it for every project instead.

**Arguments**

<ResponseField name={"ACTION"} type={"add | show | remove"} required>
  add, show or remove
</ResponseField>

**Options**

<ResponseField name={"--key-id"} type={"ID"}>
  with add: the key's Key ID
</ResponseField>

<ResponseField name={"--issuer-id"} type={"UUID"}>
  with add: the Issuer ID (a team key; none for an individual key)
</ResponseField>

<ResponseField name={"--p8"} type={"FILE"}>
  with add: the AuthKey\_\<Key ID>.p8 file Apple gave you
</ResponseField>

<ResponseField name={"--shared"} type={"flag"} default={"off"}>
  the key every project uses, not this project's
</ResponseField>

### `mobster testflight list`

List the app's builds and whether TestFlight can install each, newest first.

**Options**

<ResponseField name={"--app"} type={"BUNDLE"} required>
  the app's bundle ID
</ResponseField>

<ResponseField name={"--limit"} type={"N"} default={"10"}>
  how many (default: 10)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print JSON
</ResponseField>

### `mobster testflight install`

Install the newest installable build, or the one `--build` names, on your iPhone: Mobster opens the app's page in TestFlight and taps Install or Update, then waits for Open. You must be a tester of the app. Mobster never accepts TestFlight's terms for you, never picks from Previous Builds (it prints the taps instead), and after a tap it can't confirm it never taps again.

**Options**

<ResponseField name={"--app"} type={"BUNDLE"} required>
  the app's bundle ID
</ResponseField>

<ResponseField name={"--build"} type={"N"} default={"the newest installable"}>
  the build number (default: the newest installable)
</ResponseField>

<ResponseField name={"--version"} type={"V"}>
  the version the build belongs to, such as 1.4.2
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the iPhone (default: your iPhone)
</ResponseField>

<ResponseField name={"--timeout"} type={"SECONDS"} default={"600"}>
  how long to wait for TestFlight (default: 600)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print JSON
</ResponseField>

```text theme={"theme":"css-variables"}
after it: mobster test --device NAME --testflight BUNDLE, whose report names the build
```

## `mobster xcode`

Add a post-action to your Xcode scheme: after each simulator build, Mobster runs the checks of the app you built, on that build, in the background, and the verdict comes as a notification.

```text theme={"theme":"css-variables"}
examples:
  mobster xcode hook --scheme YourApp
  mobster xcode hook --scheme YourApp --tag smoke
  mobster xcode unhook --scheme YourApp
```

| Command | What it does |
| - | - |
| [`mobster xcode hook`](#mobster-xcode-hook) | run your checks after every simulator build in Xcode |
| [`mobster xcode unhook`](#mobster-xcode-unhook) | stop running your checks after Xcode builds |
| [`mobster xcode after-build`](#mobster-xcode-after-build) | run the checks of the app Xcode just built (the post-action runs it) |

### `mobster xcode hook`

Add Mobster's post-action to a shared scheme's build: after each simulator build, the checks of the app Xcode built run in the background on that build, and the verdict comes as a notification.

**Options**

<ResponseField name={"--scheme"} type={"NAME"} default={"the only one, or the one named like the project"}>
  the shared scheme (default: the only one, or the one named like the project)
</ResponseField>

<ResponseField name={"--project"} type={"PATH"} default={"the one here"}>
  the .xcodeproj or .xcworkspace (default: the one here)
</ResponseField>

<ResponseField name={"--sim"} type={"TYPE[@RUNTIME]"} default={"each check's own"} post={["repeatable"]}>
  run the checks on this simulator; repeatable (default: each check's own)
</ResponseField>

<ResponseField name={"--tag"} type={"TAG"} post={["repeatable"]}>
  only the checks with this tag, such as smoke; repeatable
</ResponseField>

### `mobster xcode unhook`

Take Mobster's post-action out of the scheme. Nothing else in it changes.

**Options**

<ResponseField name={"--scheme"} type={"NAME"} default={"the only one, or the one named like the project"}>
  the shared scheme (default: the only one, or the one named like the project)
</ResponseField>

<ResponseField name={"--project"} type={"PATH"} default={"the one here"}>
  the .xcodeproj or .xcworkspace (default: the one here)
</ResponseField>

### `mobster xcode after-build`

Run the checks whose app is the one Xcode just built, on that build. It reads BUILT\_PRODUCTS\_DIR, FULL\_PRODUCT\_NAME, PLATFORM\_NAME and SRCROOT from Xcode. A build for a device is left alone.

**Options**

<ResponseField name={"--background"} type={"flag"} default={"off"}>
  return at once and run in the background, stopping an older build's run
</ResponseField>

<ResponseField name={"--sim"} type={"TYPE[@RUNTIME]"} post={["repeatable"]}>
  a simulator to run on; repeatable
</ResponseField>

<ResponseField name={"--tag"} type={"TAG"} post={["repeatable"]}>
  only the checks with this tag; repeatable
</ResponseField>

## `mobster fixtures`

List the app states your checks start from (.mobster/fixtures/: a reset, launch args, seed files, the keychain, the pasteboard, sign-in steps) and put a simulator's app in one now.

```text theme={"theme":"css-variables"}
examples:
  mobster fixtures list
  mobster fixtures apply logged-in-cart --bundle com.example.shop
```

| Command | What it does |
| - | - |
| [`mobster fixtures list`](#mobster-fixtures-list) | list the project's fixtures and what each one sets up |
| [`mobster fixtures apply`](#mobster-fixtures-apply) | put a simulator's app in a fixture's state now |

### `mobster fixtures list`

List the fixtures in .mobster/fixtures/, flattened with what they extend: the reset, the launch args and env, the seed files, the keychain, the pasteboard and the steps. Reads files only.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print them as JSON
</ResponseField>

<ResponseField name={"--project"} type={"DIR"} default={"the one you are in"}>
  the project folder (default: the one you are in)
</ResponseField>

### `mobster fixtures apply`

Reset the app on a simulator and seed it as the fixtures say: the keychain, the files in its data container and the pasteboard. Several names compose in order. The app is not launched.

**Arguments**

<ResponseField name={"NAME"} type={"NAME, one or more"} required>
  fixtures to apply, in order
</ResponseField>

**Options**

<ResponseField name={"--bundle"} type={"ID"} required>
  the app's bundle ID
</ResponseField>

<ResponseField name={"--app"} type={"PATH"}>
  a built .app to install first (needed for a reinstall)
</ResponseField>

<ResponseField name={"--sim"} type={"TYPE"} default={"Mobster's own"}>
  a simulator, such as "iPhone 17 Pro" (default: Mobster's own)
</ResponseField>

<ResponseField name={"--device"} type={"NAME"}>
  an iPhone: refused when the fixtures change its state
</ResponseField>

<ResponseField name={"--project"} type={"DIR"} default={"the one you are in"}>
  the project folder (default: the one you are in)
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 applied, 3 couldn't

examples:
  mobster fixtures apply logged-in-cart --bundle com.example.shop
  mobster fixtures apply clean empty-cart --bundle com.example.shop --sim "iPhone 17 Pro"
```

## `mobster links`

List the deep links your project has, from .mobster/links.yaml, the Expo config, the Xcode project and links a check opened, and open each on a simulator to see which screen it lands on. A step can then say "open cart" instead of the address.

```text theme={"theme":"css-variables"}
examples:
  mobster links list
  mobster links check
  mobster links check cart --sim "iPhone 17 Pro"
```

| Command | What it does |
| - | - |
| [`mobster links list`](#mobster-links-list) | list the project's deep links and where each came from |
| [`mobster links check`](#mobster-links-check) | open each link on a simulator and record the screen it lands on |

### `mobster links list`

List the project's deep links: the ones in .mobster/links.yaml, the Expo config's scheme and associated domains, the Xcode project's URL types and associated domains, and links a check opened successfully. Dynamic Expo config runs `npx expo config`.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print them as JSON
</ResponseField>

<ResponseField name={"--project"} type={"DIR"} default={"the one you are in"}>
  the project folder (default: the one you are in)
</ResponseField>

### `mobster links check`

Open each link on a simulator, in the app, and say what screen it landed on: passed when the screen shows the link's title, failed with a screenshot when it doesn't. Name links to check some; with none, every link is checked.

**Arguments**

<ResponseField name={"NAME"} type={"NAME, zero or more"}>
  links to check, by name or address (default: all)
</ResponseField>

**Options**

<ResponseField name={"--sim"} type={"TYPE"} post={["repeatable"]}>
  a simulator, such as "iPhone 17 Pro"; repeatable
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} post={["repeatable"]}>
  an iPhone from `mobster devices`; repeatable
</ResponseField>

<ResponseField name={"--bundle"} type={"ID"} default={"read from the project"}>
  the app's bundle ID (default: read from the project)
</ResponseField>

<ResponseField name={"--app"} type={"PATH"} default={"the app already on the simulator"}>
  a built .app to install (default: the app already on the simulator)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print results.json's document
</ResponseField>

<ResponseField name={"--project"} type={"DIR"} default={"the one you are in"}>
  the project folder (default: the one you are in)
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 every link landed where it should, 1 one didn't, 3 couldn't

examples:
  mobster links check
  mobster links check cart profile --sim "iPhone 17 Pro"
```

## `mobster serve`

Start the loopback HTTP API (127.0.0.1 only) that Mobster for Mac launches as its sidecar. Every request needs the launch's API token. Scripting with it: [https://docs.mobster.dev/scripting](https://docs.mobster.dev/scripting)

**Options**

<ResponseField name={"--port"} type={"PORT"} default={"8765"}>
  port on 127.0.0.1 (default: 8765; 0 picks a free one)
</ResponseField>

<ResponseField name={"--wda-url"} type={"URL"} default={"none, or http://127.0.0.1:8100 with --manage-device"}>
  the WebDriverAgent to drive (default: none, or [http://127.0.0.1:8100](http://127.0.0.1:8100) with `--manage-device`); a simulator's runner listens on its own port
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"the only device, else the one last used"}>
  the device a task that names none runs on: an id, UDID or name from `mobster devices` (default: the only device, else the one last used)
</ResponseField>

<ResponseField name={"--manage-device"} type={"flag"} default={"off"}>
  own the USB iPhone setup: build, run and supervise Mobster's helper (the Mac app does this)
</ResponseField>

<ResponseField name={"--data-dir"} type={"PATH"}>
  where the managed device keeps its files
</ResponseField>

<ResponseField name={"--exit-with-parent"} type={"flag"} default={"off"}>
  shut down when the launching process exits (the Mac app's safety net)
</ResponseField>

<ResponseField name={"--keep-runner"} type={"flag"} default={"off"}>
  leave the iPhone runner running on exit, for development reloads; the next server adopts it
</ResponseField>

<ResponseField name={"--env-file"} type={"PATH"}>
  load KEY=VALUE lines (ANTHROPIC\_API\_KEY, OPENAI\_API\_KEY, ...); variables already set win
</ResponseField>

<ResponseField name={"--session"} type={"ID"}>
  preferred WDA session; the one WDA is serving always wins
</ResponseField>

<ResponseField name={"--enable-live"} type={"flag"} default={"off"}>
  allow tasks to act on the phone
</ResponseField>

<ResponseField name={"--spend-cap-usd"} type={"USD"}>
  stop a run before further model calls once its observed inference spend reaches USD
</ResponseField>

<ResponseField name={"--state-db"} type={"PATH"} default={".state/ beside the source in a checkout, else <data dir>/state/"}>
  the private task journal (never automatically replays interrupted tasks); default: .state/ beside the source in a checkout, else \<data dir>/state/
</ResponseField>

## `mobster api`

Serve every app Mobster has an app pack for as an API on this Mac: GET /v1/\<app>/\<thing> reads that app on your iPhone into rows (JSON or CSV), and GET /v1/openapi.json describes every endpoint. Each one is also an MCP tool in `mobster mcp`. Every endpoint only reads, and each client calls it with its own token.

```text theme={"theme":"css-variables"}
examples:
  mobster api tokens create n8n
  mobster api serve --phone "Test iPhone"
  mobster api tokens revoke n8n
```

| Command | What it does |
| - | - |
| [`mobster api serve`](#mobster-api-serve) | serve the App API on 127.0.0.1 until ctrl+c |
| [`mobster api tokens`](#mobster-api-tokens) | make, list and revoke the tokens clients call the App API with |

### `mobster api serve`

Serve every app Mobster has an app pack for as GET /v1/\<app>/\<thing> on 127.0.0.1. Each request needs a token from `mobster api tokens create`. Every endpoint only reads.

**Options**

<ResponseField name={"--port"} type={"PORT"} default={"8780"}>
  port on 127.0.0.1 (default: 8780; 0 picks a free one)
</ResponseField>

<ResponseField name={"--phone"} type={"NAME"}>
  the iPhone a read uses when it names none: an id, UDID or name from `mobster devices`
</ResponseField>

<ResponseField name={"--allow-device"} type={"NAME"} post={["repeatable"]}>
  read only this real iPhone, never another; repeatable
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster api serve --phone "Test iPhone" --allow-device "Test iPhone"
  curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8780/v1/apps
```

### `mobster api tokens`

Make one token for each client you let call the App API, list them, and revoke one. Mobster keeps each token's hash, never the token.

| Command | What it does |
| - | - |
| [`mobster api tokens create`](#mobster-api-tokens-create) | make a token for one client and print it once |
| [`mobster api tokens list`](#mobster-api-tokens-list) | list the tokens: name, id, first characters, last use |
| [`mobster api tokens revoke`](#mobster-api-tokens-revoke) | revoke a token, by its id or name |

#### `mobster api tokens create`

Make a token for one client, such as a script or n8n, and print it once. Mobster can't show it again.

**Arguments**

<ResponseField name={"NAME"} type={"NAME"} required>
  the client's name, 1 to 40 characters
</ResponseField>

#### `mobster api tokens list`

List the tokens, never the tokens themselves.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print JSON
</ResponseField>

#### `mobster api tokens revoke`

Revoke a token: the next request with it is refused.

**Arguments**

<ResponseField name={"ID|NAME"} type={"ID|NAME"} required>
  the token's id or name from `mobster api tokens list`
</ResponseField>

## `mobster phone`

Phone utilities that aren't tasks: put text on the phone's clipboard (or print what it holds), and install a signed .app or .ipa on a USB iPhone or a Mobster simulator.

```text theme={"theme":"css-variables"}
examples:
  mobster phone clipboard set "https://example.com/invite/42"
  mobster phone clipboard get
  mobster phone file put menu.pdf --app com.apple.Pages
  mobster phone install build/YourApp.ipa --device "Work iPhone"
```

| Command | What it does |
| - | - |
| [`mobster phone clipboard`](#mobster-phone-clipboard) | set or read the phone's clipboard (text, at most 64 KB) |
| [`mobster phone file`](#mobster-phone-file) | move files between this Mac and an app's folder on the iPhone (USB) |
| [`mobster phone photos`](#mobster-phone-photos) | add photos and videos to the iPhone's Photos, or copy the newest to this Mac |
| [`mobster phone open`](#mobster-phone-open) | open a URL or deep link on the phone |
| [`mobster phone button`](#mobster-phone-button) | press a hardware button, or lock the screen |
| [`mobster phone install`](#mobster-phone-install) | install a signed .app or .ipa on the device |
| [`mobster phone record`](#mobster-phone-record) | record a USB iPhone's screen to a movie on this Mac |
| [`mobster phone network`](#mobster-phone-network) | create network profiles, diagnose this Mac, or capture USB traffic |
| [`mobster phone password`](#mobster-phone-password) | enter a password privately into a focused secure app field |

### `mobster phone clipboard`

Set the phone's clipboard from the Mac, or print what it holds.

| Command | What it does |
| - | - |
| [`mobster phone clipboard set`](#mobster-phone-clipboard-set) | put text on the phone's clipboard |
| [`mobster phone clipboard get`](#mobster-phone-clipboard-get) | print the phone's clipboard |

#### `mobster phone clipboard set`

Put text on the phone's clipboard: TEXT, a file's text with `--file`, or standard input with -. At most 64 KB.

**Arguments**

<ResponseField name={"TEXT"} type={"TEXT, optional"}>
  the text, or - for standard input
</ResponseField>

**Options**

<ResponseField name={"--file"} type={"FILE"}>
  put this UTF-8 file's text on the clipboard
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

#### `mobster phone clipboard get`

Print the phone's clipboard. WebDriverAgent's runner comes to the front for a moment to read it (iOS lets only the front app read the clipboard). Only the CLI reads it: no MCP tool does, because it may hold a password.

**Options**

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

### `mobster phone file`

Copy files between this Mac and the Documents folder of an iPhone app that shares files (the folder Files shows under On My iPhone). Over the cable only. Nothing else on the phone is reachable, the camera roll included.

```text theme={"theme":"css-variables"}
exit codes: 0 done, 1 it failed, 2 usage, 3 no iPhone on a cable or no iPhone tools
```

| Command | What it does |
| - | - |
| [`mobster phone file put`](#mobster-phone-file-put) | copy a file from this Mac into an app's folder |
| [`mobster phone file get`](#mobster-phone-file-get) | copy a file from an app's folder to this Mac |
| [`mobster phone file ls`](#mobster-phone-file-ls) | list the apps that share files, or the files in one |

#### `mobster phone file put`

Copy a file (up to 100 MB) into an app's folder on the iPhone. A file with the same name is kept: the copy gets a new name (menu 2.pdf) unless you pass `--replace`.

**Arguments**

<ResponseField name={"PATH"} type={"PATH"} required>
  the file on this Mac
</ResponseField>

**Options**

<ResponseField name={"--app"} type={"BUNDLE"} required>
  the app's bundle ID, such as com.apple.Pages (`mobster phone file ls` lists them)
</ResponseField>

<ResponseField name={"--name"} type={"NAME"} default={"its own"}>
  the name it gets on the iPhone (default: its own)
</ResponseField>

<ResponseField name={"--replace"} type={"flag"} default={"off"}>
  overwrite a file with the same name
</ResponseField>

<ResponseField name={"--container"} type={"flag"} default={"off"}>
  the app's own Documents folder, for a development build of yours that doesn't share files
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

#### `mobster phone file get`

Copy a file from an app's folder on the iPhone to this Mac.

**Arguments**

<ResponseField name={"NAME"} type={"NAME"} required>
  the file's name in the app's folder
</ResponseField>

**Options**

<ResponseField name={"--app"} type={"BUNDLE"} required>
  the app's bundle ID
</ResponseField>

<ResponseField name={"-o, --output"} type={"PATH"} default={"the current folder"}>
  where to save it: a folder, or a file name (default: the current folder)
</ResponseField>

<ResponseField name={"--container"} type={"flag"} default={"off"}>
  the app's own Documents folder, for a development build of yours
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

#### `mobster phone file ls`

Without `--app`, list the iPhone's apps whose folders show in Files. With `--app`, list the files in that app's folder.

**Options**

<ResponseField name={"--app"} type={"BUNDLE"}>
  the app's bundle ID
</ResponseField>

<ResponseField name={"--container"} type={"flag"} default={"off"}>
  the app's own Documents folder, for a development build of yours
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

### `mobster phone photos`

Add a photo or video from this Mac to the iPhone's Photos library (Mobster's runner saves it; the first time, iOS asks on the iPhone), or copy the newest photos, screenshots and screen recordings from its camera roll to this Mac (over the cable).

```text theme={"theme":"css-variables"}
exit codes: 0 done, 1 it failed, 2 usage, 3 no iPhone on a cable or no iPhone tools
```

| Command | What it does |
| - | - |
| [`mobster phone photos add`](#mobster-phone-photos-add) | add photos and videos to Photos |
| [`mobster phone photos pull`](#mobster-phone-photos-pull) | copy the newest photos and recordings to this Mac |

#### `mobster phone photos add`

Add JPEGs, HEICs, PNGs, GIFs, WebPs, MOVs or MP4s (up to 4 GB each, 50 at a time) to the iPhone's Photos library, into the album Mobster unless you name another. Each one is checked by SHA-256 on the iPhone, and the library's count is read before and after.

**Arguments**

<ResponseField name={"PATH"} type={"PATH, one or more"} required>
  the photos or videos on this Mac
</ResponseField>

**Options**

<ResponseField name={"--album"} type={"NAME"} default={"Mobster"}>
  the album they go into, made if it isn't there (default Mobster); `--album` '' for none
</ResponseField>

<ResponseField name={"--via"} type={"auto | cable | wifi | airdrop | mirroring | icloud"} default={"auto"}>
  auto (default), cable, wifi, airdrop, mirroring or icloud
</ResponseField>

<ResponseField name={"--profile"} type={"PROFILE"}>
  send to this profile's iPhone (Settings › Profiles)
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

#### `mobster phone photos pull`

Copy the newest photos, screenshots and screen recordings from the iPhone, newest first, each checked by SHA-256 or size. Over the cable by default; `--via` wifi reads them through Mobster's runner, `--via` tunnel through Apple's own tunnel (needs pymobiledevice3), `--via` icloud from this Mac's Photos library.

**Options**

<ResponseField name={"-n, --count"} type={"N"} default={"1"}>
  how many, 1 to 20 (default 1)
</ResponseField>

<ResponseField name={"--since"} type={"WHEN"}>
  only newer ones: 10m, 2h, 1d or an ISO time
</ResponseField>

<ResponseField name={"--jpeg"} type={"flag"} default={"off"}>
  convert HEIC photos to JPEG (over the cable)
</ResponseField>

<ResponseField name={"-o, --output"} type={"DIR"} default={"the current folder"}>
  the folder to save into (default: the current folder)
</ResponseField>

<ResponseField name={"--via"} type={"auto | cable | wifi | tunnel | icloud"} default={"auto"}>
  auto (default), cable, wifi, tunnel or icloud
</ResponseField>

<ResponseField name={"--profile"} type={"PROFILE"}>
  copy from this profile's iPhone (Settings › Profiles)
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

### `mobster phone open`

Open a URL or deep link on the phone, such as maps\://?q=coffee or App-prefs:WIFI. Links that act as they open (tel:, facetime:, running a shortcut) ask for `--yes`.

**Arguments**

<ResponseField name={"URL"} type={"URL"} required>
  the link
</ResponseField>

**Options**

<ResponseField name={"--yes"} type={"flag"} default={"off"}>
  open a link that asks first: a call, a message, a shortcut run, or a new app's link
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

### `mobster phone button`

Press Home, Volume Up or Volume Down, or lock the screen. Mobster can't unlock an iPhone: after lock, unlock it yourself.

**Arguments**

<ResponseField name={"BUTTON"} type={"home | volume_up | volume_down | lock"} required>
  home, volume\_up, volume\_down or lock
</ResponseField>

**Options**

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

### `mobster phone install`

Install a build on a USB iPhone (xcrun devicectl) or a Mobster simulator (xcrun simctl). The build must be signed for that iPhone, and the iPhone needs Developer Mode on.

**Arguments**

<ResponseField name={"PATH"} type={"PATH"} required>
  the .app folder or .ipa file
</ResponseField>

**Options**

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 installed, 1 the install failed, 2 usage, 3 no device or no Xcode
```

### `mobster phone record`

Record a paired iPhone's screen, and the audio it offers, over USB into a new .mov file, without a WebDriverAgent session. It uses the capture device QuickTime Player records from, so macOS asks once whether Mobster may use the camera and microphone. iOS shows its demonstration status bar while it records, and protected content may not be recorded. It never overwrites a file.

**Options**

<ResponseField name={"--output"} type={"FILE"} required>
  write a new .mov file
</ResponseField>

<ResponseField name={"--seconds"} type={"N"} default={"10"}>
  how long to record, 1 to 300 (default 10)
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the device: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 saved, 1 it failed, 2 usage, 3 no iPhone on a cable or no native helper
```

### `mobster phone network`

Create removable iPhone network profiles, inspect this Mac's connectivity, or capture a USB iPhone's packets. Profiles need installation in Settings.

| Command | What it does |
| - | - |
| [`mobster phone network proxy-profile`](#mobster-phone-network-proxy-profile) | write a Wi-Fi HTTP proxy or PAC profile |
| [`mobster phone network vpn-profile`](#mobster-phone-network-vpn-profile) | write an IKEv2 VPN profile with username authentication |
| [`mobster phone network relay-profile`](#mobster-phone-network-relay-profile) | write an Apple HTTP/2 network relay profile |
| [`mobster phone network diagnose`](#mobster-phone-network-diagnose) | inspect this Mac's route and optional TCP endpoint reachability |
| [`mobster phone network capture`](#mobster-phone-network-capture) | save a bounded USB iPhone packet trace |

#### `mobster phone network proxy-profile`

Write a Wi-Fi profile for one SSID. Apps may ignore HTTP proxies; this does not route cellular traffic or decrypt TLS.

**Options**

<ResponseField name={"--ssid"} type={"SSID"} required>
  the Wi-Fi network name
</ResponseField>

<ResponseField name={"--server"} type={"SERVER"}>
  the HTTP proxy hostname or IP address
</ResponseField>

<ResponseField name={"--pac"} type={"URL"}>
  the HTTP or HTTPS proxy auto-configuration URL
</ResponseField>

<ResponseField name={"--port"} type={"PORT"} default={"8080"}>
  the HTTP proxy port (default: 8080)
</ResponseField>

<ResponseField name={"--username"} type={"USERNAME"}>
  the HTTP proxy username
</ResponseField>

<ResponseField name={"--password-file"} type={"FILE"}>
  read the proxy password from this UTF-8 file
</ResponseField>

<ResponseField name={"--wifi-password-file"} type={"FILE"}>
  read the Wi-Fi password from this UTF-8 file
</ResponseField>

<ResponseField name={"--encryption"} type={"Any | WPA2 | WPA3 | None"} default={"Any"}>
  the Wi-Fi encryption type (default: Any)
</ResponseField>

<ResponseField name={"--output"} type={"FILE"} required>
  write a new private .mobileconfig file; existing files are kept
</ResponseField>

<ResponseField name={"--name"} type={"NAME"} default={"Mobster proxy"}>
  the profile's display name (default: Mobster proxy)
</ResponseField>

<ResponseField name={"--identifier"} type={"IDENTIFIER"} default={"app.mobster.network.proxy"}>
  the stable profile identifier (default: app.mobster.network.proxy)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

`--server` and `--pac` can't be used together; one is required.

#### `mobster phone network vpn-profile`

Write an IKEv2 EAP VPN profile for your provider. This does not create a VPN server. Install the profile and connect in Settings.

**Options**

<ResponseField name={"--server"} type={"SERVER"} required>
  the VPN server hostname or IP address
</ResponseField>

<ResponseField name={"--remote-id"} type={"REMOTE_ID"} required>
  the server identifier from your VPN provider
</ResponseField>

<ResponseField name={"--local-id"} type={"LOCAL_ID"} required>
  the client identifier from your VPN provider
</ResponseField>

<ResponseField name={"--username"} type={"USERNAME"} required>
  the VPN account username
</ResponseField>

<ResponseField name={"--password-file"} type={"FILE"}>
  read the VPN password from this UTF-8 file
</ResponseField>

<ResponseField name={"--output"} type={"FILE"} required>
  write a new private .mobileconfig file; existing files are kept
</ResponseField>

<ResponseField name={"--name"} type={"NAME"} default={"Mobster VPN"}>
  the profile's display name (default: Mobster VPN)
</ResponseField>

<ResponseField name={"--identifier"} type={"IDENTIFIER"} default={"app.mobster.network.vpn"}>
  the stable profile identifier (default: app.mobster.network.vpn)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

#### `mobster phone network relay-profile`

Write an Apple network relay profile for an existing compatible HTTP/2 relay service. This is not an ordinary HTTP proxy or a new relay server.

**Options**

<ResponseField name={"--url"} type={"URL"} required>
  the HTTPS relay endpoint
</ResponseField>

<ResponseField name={"--token-file"} type={"FILE"}>
  read the bearer token from this UTF-8 file
</ResponseField>

<ResponseField name={"--output"} type={"FILE"} required>
  write a new private .mobileconfig file; existing files are kept
</ResponseField>

<ResponseField name={"--name"} type={"NAME"} default={"Mobster relay"}>
  the profile's display name (default: Mobster relay)
</ResponseField>

<ResponseField name={"--identifier"} type={"IDENTIFIER"} default={"app.mobster.network.relay"}>
  the stable profile identifier (default: app.mobster.network.relay)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

#### `mobster phone network diagnose`

Check this Mac's default route and optional TCP endpoint. With `--device`, also report USB attachment. This does not test the iPhone's internet egress, proxy authentication, or installed profiles.

**Options**

<ResponseField name={"--device"} type={"NAME"}>
  also inspect USB attachment for this device
</ResponseField>

<ResponseField name={"--host"} type={"HOST"}>
  the hostname or IP address to test from this Mac
</ResponseField>

<ResponseField name={"--port"} type={"PORT"} default={"8080"}>
  the TCP endpoint port (default: 8080)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

#### `mobster phone network capture`

Capture packets through Apple's Remote Virtual Interface. Requires a paired USB iPhone, Xcode tools and BPF permissions on this Mac. TLS remains encrypted; packet data may contain private information.

**Options**

<ResponseField name={"--device"} type={"NAME"} required>
  the paired USB iPhone's id, UDID or name
</ResponseField>

<ResponseField name={"--output"} type={"FILE"} required>
  write a new private pcap file
</ResponseField>

<ResponseField name={"--seconds"} type={"SECONDS"} default={"30"}>
  stop after 1 to 300 seconds (default: 30)
</ResponseField>

<ResponseField name={"--packets"} type={"PACKETS"} default={"10000"}>
  stop after 1 to 100000 packets (default: 10000)
</ResponseField>

<ResponseField name={"--snaplen"} type={"SNAPLEN"} default={"128"}>
  capture 64 to 65535 bytes per packet (default: 128)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

### `mobster phone password`

Enter one password from a hidden prompt or a private UTF-8 file. Requires a rebuilt Mobster runner and an empty, focused secure field. Does not submit, retry, or enter a phone passcode.

**Options**

<ResponseField name={"--app"} type={"BUNDLE"} required>
  the foreground app's bundle ID
</ResponseField>

<ResponseField name={"--field"} type={"NAME"} required>
  the secure field's accessibility identifier or label
</ResponseField>

<ResponseField name={"--file"} type={"FILE"}>
  read the password from a private UTF-8 file
</ResponseField>

<ResponseField name={"--device"} type={"NAME"}>
  the device's id, UDID or name
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object without the password
</ResponseField>

## `mobster alerts`

Mobster posts to MOBSTER\_ALERT\_WEBHOOK\_URL (https: Slack, Discord, ntfy or any JSON webhook) when a scheduled task fails, stops or waits a minute for your approval. The post never holds the task's answer or anything it typed.

```text theme={"theme":"css-variables"}
example:
  mobster alerts test
```

| Command | What it does |
| - | - |
| [`mobster alerts test`](#mobster-alerts-test) | send a test alert to your webhook |

### `mobster alerts test`

Send a test alert to MOBSTER\_ALERT\_WEBHOOK\_URL (or `--url`) and say whether the service took it.

**Options**

<ResponseField name={"--url"} type={"URL"}>
  send to this https address instead of the saved one
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 sent, 1 the service didn't take it, 2 no usable address
```

## `mobster audit`

Check a Team plan audit export offline: the hash chain, the signed head of the API's export, and with `--keys` every Mac's signature. Nothing leaves this Mac.

```text theme={"theme":"css-variables"}
examples:
  mobster audit verify audit.jsonl
  mobster audit verify audit.jsonl --keys keys.json
  mobster audit verify export.json
```

| Command | What it does |
| - | - |
| [`mobster audit verify`](#mobster-audit-verify) | check an audit export's hash chain and signatures |

### `mobster audit verify`

Check an audit export, offline. It follows the hash chain entry by entry and says which entry first fails: a gap, an entry out of order, or one that was changed. For the JSON Lines export from the API it also checks the signed head against Mobster's license key, which pins where the export starts, where it ends and how many entries it holds, so a cut-off start or end is caught too. With `--keys` it checks each Mac entry's signature against that Mac's key. The relay's own entries are unsigned.

Get the Macs' keys from the Macs themselves, not from the export, so the relay can't vouch for itself. Nothing leaves this Mac.

**Arguments**

<ResponseField name={"FILE"} type={"FILE"} required>
  the audit export: JSON Lines from the API, or JSON from the console
</ResponseField>

**Options**

<ResponseField name={"--key"} type={"FILE"} default={"the key built into Mobster"}>
  the license public key, as a PEM, for a JSON Lines export's signed head (default: the key built into Mobster)
</ResponseField>

<ResponseField name={"--keys"} type={"FILE"}>
  a JSON file of \{"\<mac id>": "\<base64url Ed25519 public key>"}; checks every Mac entry's signature
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the result as one JSON object
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster audit verify audit.jsonl
  mobster audit verify audit.jsonl --keys keys.json
  mobster audit verify export.json

exit codes: 0 every entry checked out, 1 something didn't check out, 2 a file couldn't be read
```

## `mobster env`

Keep a project's test credentials and other secrets in your Keychain, named in .mobster/env.example (names only, committed). A check writes \$\{NAME} in its launch settings and steps; the run fills it in and masks it everywhere. A name with no value stops the run before it starts. Values are never printed or written to the project.

```text theme={"theme":"css-variables"}
examples:
  mobster env set TEST_PASSWORD
  printf '%s' "$PW" | mobster env set TEST_PASSWORD --stdin
  mobster env list
  mobster env unset TEST_PASSWORD
```

| Command | What it does |
| - | - |
| [`mobster env set`](#mobster-env-set) | save a value for a name, in your Keychain |
| [`mobster env list`](#mobster-env-list) | show the names and which have a value |
| [`mobster env unset`](#mobster-env-unset) | remove a name's saved value |

### `mobster env set`

Save a value for a name, in your Keychain.

**Arguments**

<ResponseField name={"NAME"} type={"NAME"} required>
  the variable's name, such as TEST\_PASSWORD
</ResponseField>

**Options**

<ResponseField name={"--project"} type={"DIR"} default={"the folder with .mobster/ above this one"}>
  the project folder (default: the folder with .mobster/ above this one)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the result as JSON
</ResponseField>

<ResponseField name={"--stdin"} type={"flag"} default={"off"}>
  read the value from standard input instead of asking for it
</ResponseField>

### `mobster env list`

Show the names and which have a value.

**Options**

<ResponseField name={"--project"} type={"DIR"} default={"the folder with .mobster/ above this one"}>
  the project folder (default: the folder with .mobster/ above this one)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the result as JSON
</ResponseField>

### `mobster env unset`

Remove a name's saved value.

**Arguments**

<ResponseField name={"NAME"} type={"NAME"} required>
  the variable's name, such as TEST\_PASSWORD
</ResponseField>

**Options**

<ResponseField name={"--project"} type={"DIR"} default={"the folder with .mobster/ above this one"}>
  the project folder (default: the folder with .mobster/ above this one)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the result as JSON
</ResponseField>

## `mobster demo`

Run one task offline, "Search for coffee", on a scripted phone and model: the real runtime and agent loop, no iPhone, no keys and no model calls. The same as `mobster run "Search for coffee" --demo`. For the terminal UI on it, run `mobster --demo`.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print raw JSON events, even on a terminal
</ResponseField>

## `mobster completion`

Print a completion script for your shell, so tab completes commands, options, your devices and past task IDs. Homebrew installs the zsh and fish ones for you.

**Arguments**

<ResponseField name={"SHELL"} type={"zsh | bash | fish, optional"}>
  zsh, bash or fish
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster completion zsh > "${fpath[1]}/_mobster"
  mobster completion bash > ~/.local/share/bash-completion/completions/mobster
  mobster completion fish > ~/.config/fish/completions/mobster.fish
```

## `mobster update`

Say whether a newer Mobster is out. New versions come in Mobster for Mac, which updates itself: [https://mobster.dev/download](https://mobster.dev/download). It installs nothing, and the mobster you have keeps working. Mobster checks once a day and says so after a command; MOBSTER\_NO\_UPDATE\_CHECK=1 turns that off.

**Options**

<ResponseField name={"--check"} type={"flag"} default={"off"}>
  only say whether a new release is out
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster update
  mobster update --check
```

## `mobster version`

Print the version of Mobster.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print the version, Python and platform as JSON
</ResponseField>

## `mobster logout`

Remove a saved key from this Mac: from Mobster's settings file, which Mobster for Mac uses too. A key set in your shell or an `--env-file` stays where it is.

`mobster logout <plan or provider>` forgets that plan's or provider's key or sign-in.

**Arguments**

<ResponseField name={"PLAN|PROVIDER"} type={"PLAN|PROVIDER, optional"}>
  forget a plan's or provider's key or sign-in, instead of removing a key
</ResponseField>

**Options**

<ResponseField name={"--provider"} type={"anthropic | openai"} default={"the one Mobster's agent uses"}>
  which key to remove (default: the one Mobster's agent uses)
</ResponseField>

```text theme={"theme":"css-variables"}
examples:
  mobster logout
  mobster logout --provider openai
  mobster logout kimi
```

## `mobster wifi`

Turn on Wi-Fi use for an iPhone you set up with a cable once, see how each phone is connected, and check the encrypted link between this Mac and the phone.

```text theme={"theme":"css-variables"}
examples:
  mobster wifi status
  mobster wifi on --device "Sam's iPhone"
  mobster wifi probe --device "Sam's iPhone"
```

| Command | What it does |
| - | - |
| [`mobster wifi status`](#mobster-wifi-status) | show how each iPhone is connected, and whether Wi-Fi is on for it |
| [`mobster wifi on`](#mobster-wifi-on) | let an iPhone be used over Wi-Fi (plug it in first) |
| [`mobster wifi off`](#mobster-wifi-off) | use an iPhone only with the cable again |
| [`mobster wifi probe`](#mobster-wifi-probe) | check the encrypted Wi-Fi link to an unplugged iPhone, step by step |

### `mobster wifi status`

Show each iPhone set up here: on the cable, on Wi-Fi, or not reachable, and whether Wi-Fi is on for it. Reads only; changes nothing.

**Options**

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

### `mobster wifi on`

Let Mobster reach an iPhone over Wi-Fi when it's unplugged, through the encrypted link between this Mac and the phone. The iPhone must be plugged in and trust this Mac; this is the same switch as Connect via network in Xcode.

**Options**

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the iPhone: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 on, 1 it couldn't be turned on, 2 usage, 3 no such iPhone
```

### `mobster wifi off`

Stop using an iPhone over Wi-Fi. When Mobster turned it on, and the iPhone is plugged in, its Wi-Fi switch is turned off too.

**Options**

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the iPhone: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 off, 1 it failed, 2 usage, 3 no such iPhone
```

### `mobster wifi probe`

Check an unplugged iPhone over Wi-Fi: what Xcode's device service reports, the encrypted tunnel, WebDriverAgent on the tunnel through this Mac's relay, and that the phone's Wi-Fi address does not answer on 8100 or 9100. It binds only 127.0.0.1 and changes nothing on the phone (with `--start`, it launches Mobster's helper on it for the probe, then stops it).

**Options**

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the iPhone: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--start"} type={"flag"} default={"off"}>
  launch Mobster's helper over Wi-Fi for the probe when it isn't running (quit the Mac app first)
</ResponseField>

<ResponseField name={"--json"} type={"flag"} default={"off"}>
  print one JSON object
</ResponseField>

```text theme={"theme":"css-variables"}
exit codes: 0 every check passed, 1 a check failed, 2 usage, 3 couldn't run
```

## `mobster tui`

Open the terminal UI. The same as running `mobster` with no command.

**Options**

<ResponseField name={"--wda-url"} type={"URL"} default={"http://127.0.0.1:8100"}>
  the phone's address, for a phone you set up by hand (default: [http://127.0.0.1:8100](http://127.0.0.1:8100)) \[env: MOBSTER\_WDA\_URL]
</ResponseField>

<ResponseField name={"--device"} type={"NAME"} default={"your iPhone"}>
  the iPhone or simulator to use: an id, UDID or name from `mobster devices` (default: your iPhone)
</ResponseField>

<ResponseField name={"--env-file"} type={"PATH"}>
  load KEY=VALUE lines (ANTHROPIC\_API\_KEY, OPENAI\_API\_KEY, ...); variables already set win \[env: MOBSTER\_ENV\_FILE]
</ResponseField>

<ResponseField name={"--demo"} type={"flag"} default={"off"}>
  use a scripted phone and policy: no iPhone, no keys, no model calls
</ResponseField>

<ResponseField name={"--resume"} type={"ID, optional"}>
  open a past task and put it back in the prompt (the latest without an ID)
</ResponseField>

<ResponseField name={"-c, --continue"} type={"flag"} default={"off"}>
  continue your last conversation
</ResponseField>

<ResponseField name={"--app"} type={"NAME"}>
  start with this app chosen (a name or bundle ID)
</ResponseField>

<ResponseField name={"--no-bell"} type={"flag"} default={"off"}>
  don't ring the terminal bell when Mobster needs you
</ResponseField>

## `mobster help`

Show the help for a command, or a topic: environment, exit-codes, tui. `mobster help --all` lists every command.

**Arguments**

<ResponseField name={"COMMAND"} type={"COMMAND, zero or more"}>
  a command (and a subcommand), or a topic
</ResponseField>

**Options**

<ResponseField name={"--all"} type={"flag"} default={"off"}>
  list every command, the hidden ones too
</ResponseField>

## `mobster build-ocr`

Compile the standalone Apple Vision OCR helper into the build folder.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.