Metadata-Version: 2.5
Name: autosana
Version: 0.10.1
Summary: Local testing infrastructure for Autosana — mobile and web
Project-URL: Homepage, https://autosana.ai
License: Proprietary
Requires-Python: >=3.9
Requires-Dist: filelock>=3.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: typer>=0.9.0
Provides-Extra: local
Requires-Dist: pillow>=10.0.0; extra == 'local'
Description-Content-Type: text/markdown

# Autosana CLI

Connect local simulators, emulators, physical devices, and dev servers to [Autosana](https://autosana.ai) to test locally.

## Install

```
pipx install autosana
```

Or with uv:

```
uv tool install autosana
```

## Quick Start

```bash
# Check prerequisites
autosana doctor

# Auto-install Node.js + Appium + cloudflared
autosana doctor --fix

# Start local session (iOS)
autosana up --platform ios

# Start local session on a physical iPhone by UDID
autosana up --platform ios --device 00008140-00064D5930E3C01C --detach --json

# Diagnose a physical iPhone setup before running flows
autosana doctor --platform ios --device 00008140-00064D5930E3C01C --json

# Start local session (web — point to your dev server, http or https)
# The scheme (http vs https) is auto-detected. HTTPS dev servers like
# `next dev --experimental-https` and `vite --https` work out of the box.
autosana up --platform web --port 3000

# Multi-device — start sessions for all booted simulators
autosana up --platform ios --all-devices --detach --json

# Or start specific devices by name/UDID
autosana up --platform ios --device "iPhone 16 Pro" --detach --json
autosana up --platform ios --device "iPhone 17 Pro" --detach --json

# Check running sessions (includes device and screenshot readiness checks)
autosana status

# Recover a degraded session without a full restart when possible
autosana recover --session-id <SESSION_ID> --json

# Stop all sessions
autosana down --all
```

## Working with flows

Store your end-to-end tests as YAML under a `.autosana/` folder ([code-managed flows](https://docs.autosana.ai/code-managed-flows)), then validate, export, and run them from the terminal.

```bash
# Validate your .autosana/ flow & suite files offline (same rules the sync applies)
autosana flows validate

# Export existing dashboard flows/suites to .autosana/ YAML for review + commit
autosana flows export --all

# Run your branch's flows in the cloud (attributed to the current git ref), no PR needed
autosana flows run --flow login --bundle-id com.acme.app --platform ios

# In a GitLab CI job: sync .autosana/ on the default branch, check it in merge requests
autosana flows sync
```

### Run a flow on your local simulator

Run the flow you're *currently editing* — your uncommitted `.autosana/` YAML — on the simulator you booted with `autosana up`. No commit, no push, no cloud device.

```bash
# 1. Boot a local session (once)
autosana up --platform ios

# 2. Run your working-copy flow on it
autosana run login --local

# ...or a whole suite (its setup_flow runs on the device first)
autosana run --suite checkout --local

# Web: connect your dev server, then target its Autosana app ID
autosana up --platform web --port 3000
autosana run login --local --app-id <APP_ID>
```

`autosana run` is shorthand for `autosana flows run`. Targeting uses sensible defaults, so the everyday command is just `autosana run <flow> --local`:

- **App** — web runs use `--app-id`; mobile runs use `--bundle-id`, the `AUTOSANA_BUNDLE_ID` env var, or your org's single app for the platform.
- **Platform** — web is inferred from `--app-id`; mobile is taken from your `up` session. Pass `--platform` / `--session-id` only to disambiguate mobile sessions.
- **Environment** — `--environment <name>` selects the variable set that `${env:…}` references (and your flows) resolve against; required when the app spans multiple environments.

Notes:

- Runs your **instructions**; hooks are not executed locally (they manipulate the environment, not the device) — a suite's `setup_flow` gives you on-device setup instead.

## Upload a build

Send a build you just made to Autosana so cloud runs test it. It becomes the app's active build for the whole workspace: flow and suite runs that don't pin a build use it, and the app's CI/CD automations start, as for any build upload.

```bash
# iOS Simulator build: pass the .app folder (zipped for you); the bundle id comes from Info.plist
autosana builds upload ~/Library/Developer/Xcode/DerivedData/MyApp-*/Build/Products/Debug-iphonesimulator/MyApp.app --json

# Real-iPhone build (.ipa), or Android (.apk / .aab) with its package name
autosana builds upload MyApp.ipa
autosana builds upload app-debug.apk --bundle-id com.acme.app

# Upload to a specific existing app (its `id` from apps_list), in its own environment
autosana builds upload app-debug.apk --app-id <APP_ID>
```

A device build (an `.app` from an Xcode archive, platform `iphoneos`) can't run on the iOS Simulator, so it is refused; build for a Simulator destination or export an `.ipa`. `--json` prints `app_id` and `app_build_id`.

No login? An agent connected to the Autosana MCP (OAuth) calls the `builds_upload` tool for a signed link, runs `autosana builds upload <path> --link "<url>"` (checks and uploads the build, no login or API key), then calls `builds_upload` again with the printed `upload_id` to make it the active build.

## Sandboxes

Drive a cloud simulator or emulator directly, one command at a time. Built for coding agents that want to check their work on a real device while they build, without writing a flow first. Sandboxes have to be enabled for your company.

```bash
# Lease a device, install your app's active build, launch it, save the first screenshot
autosana sandbox start --platform ios --bundle-id com.example.app

# Tap at X,Y in pixels of the last screenshot; the settled screen is saved as the next one
autosana sandbox tap 603,1311

# Double-tap
autosana sandbox tap 603,1311 --count 2

# Press and hold
autosana sandbox long-press 603,1311 --duration 1.5s

# Set a field's text (replaces what it holds); --submit presses return
autosana sandbox type "jane@example.com" --into 603,880
autosana sandbox type "hunter2" --into 603,1020 --submit

# Scroll (down shows what is further down), or swipe between two points
autosana sandbox scroll down
autosana sandbox swipe 600,1200 100,1200

# A swipe starts exactly where you say, so in from the left edge goes back on Android.
# --flick lifts without pausing first, so what you swiped keeps moving
autosana sandbox swipe 0,700 400,700
autosana sandbox swipe 320,1200 320,400 --flick

# Open a link, go home, then bring the app back: as it was, as a new process, or as a new install
autosana sandbox open myapp://orders/42
autosana sandbox press home
autosana sandbox launch
autosana sandbox launch --cold
autosana sandbox launch --reset

# Send your app a push notification (iOS simulators), from flags or a JSON file
autosana sandbox push --title "Order shipped" --body "Your order is on its way."
autosana sandbox push payload.json

# Change the device, or just wait for the app
autosana sandbox set orientation landscape
autosana sandbox set location 37.77,-122.41
autosana sandbox wait 2s

# Look again without acting
autosana sandbox look

# List live sandboxes (* marks this worktree's current one), then release the device
autosana sandbox list
autosana sandbox end
```

- **Builds:** `start` installs a build already uploaded to Autosana (`--app-build-id`, default: the app's active build) and prints which one. iOS needs a simulator build (`.app` or `.zip`), not an `.ipa`. Apps that ship with the simulator (Safari, Reminders, Photos) need no build: `--bundle-id com.apple.mobilesafari`. The app can be left out when `.autosana/config.yaml` names a default.
- **Device:** `--device "iPhone 17 Pro" --os 26.0` picks a model and OS; the default is the latest.
- **Lifetime:** a sandbox holds its device until `end` or until `--ttl` runs out (default 30m). End sandboxes you are done with.
- **Screenshots:** saved to `.autosana/.sandboxes/<sandbox-id>/step-<n>.png` in your repository (the folder ignores itself in git), or under `~/.autosana/sandboxes/` outside a repository. They stay after `end`, which prints the folder; the 1,000 most recently used ended sandboxes keep theirs.
- **Coordinates:** tap in pixels of the most recent screenshot, exactly as you see it. Screenshots are scaled so their longest side is 1568 px (an iPhone 17 Pro comes back as 721x1568), which image readers show without resizing, so there is nothing to convert. `--max-size <px>` on `start`, `look` and `tap` changes that, for example `--max-size 2622` to read fine print at full resolution; the next tap then uses that screenshot's pixels.
- **Actions:** `tap`, `long-press`, `type`, `scroll`, `swipe`, `press home`, `open`, `launch`, `wait`, `push` and `set` (`location`, `orientation`, `clipboard`, `locale`, `network`). Each one answers like a tap: what it did, whether the screen changed, and the next screenshot. Positions are always pixels of the most recent screenshot.
- **Push notifications:** `push` delivers a notification to your app the way your server would, so you can check the banner, what tapping it opens, and what your app does with the payload. Pass `--title` and `--body`, or a JSON file with an `aps` object for anything more (badges, custom keys). iOS simulators only, and your app must have been allowed to show notifications. The banner stays up for about five seconds: tap it, or `wait 5s` before tapping elsewhere, because a tap elsewhere while it is up may not reach your app.
- **Launching:** `launch` brings your app back to the front exactly as it was. `launch --cold` closes it first, so it starts as a new process with its saved data. `launch --reset` reinstalls it, so it starts as it does after a first install.
- **Typing:** `type` sets the field's whole text, it does not append. `--into X,Y` picks the field; without it the text goes to whichever field has the keyboard. The keyboard stays up afterwards so you can see what you typed; `--submit` presses return. `type "" --into X,Y` empties a field.
- **Speed:** every action waits for the screen to settle and returns it. `--no-settle` captures immediately; `--no-observe` skips the screenshot.
- **What a tap tells you:** whether the screen changed (`screen changed` or `no change on screen`, `changed` in `--json`) and how long the tap and the settle each took, so you can often skip opening the screenshot. Without settling it makes no claim about change.
- **Watching:** `start` prints a `watch` link to the sandbox's page in the Autosana dashboard, where a signed-in teammate sees each step and its screenshot as it happens. The same page is the replay once the sandbox ends; `end` prints it again (`watch_url` in `--json`).
- **Name:** `--name "checkout confirm button"` says what the sandbox is for and is the title of its page. Without it the sandbox is named after the git branch you are on.
- **Several sandboxes:** each git worktree has its own current sandbox. Address another with `--sandbox <id>`, using an id from `autosana sandbox list`.
- **If the app stops running:** when your app crashes or is closed, the sandbox keeps its device. The next `tap` relaunches the app and fails with code `app_relaunched` without tapping, because the screen you aimed at is gone; look at the screenshot it saved, then tap again. `look` shows whatever is on the device, which may be the home screen.
- **Scripting:** every command takes `--json`. Exit codes: `0` ok, `1` the command failed, `2` bad usage, `3` not logged in, not enabled, or at the sandbox limit, `4` the sandbox is gone.

`look` and `tap` go straight to the sandbox's own address, authorized by a short-lived sandbox token that `start` receives. Only `start`, `list` and `end` call the Autosana API.

## Multi-Device Parallel Testing

Test across multiple simulators simultaneously:

1. Boot multiple iOS Simulators or Android Emulators
2. Run `autosana up --platform ios --all-devices --detach --json` to start sessions for all devices
3. Your coding agent dispatches flows to different devices in parallel via `device_tunnel_map`
4. All flows execute simultaneously — 3 devices = 3x faster

Works with both iOS and Android. Each device gets its own Appium server and Cloudflare tunnel.

## Device Health Checks

`autosana up` and `autosana status` verify that devices are actually responsive — not just listed by ADB/simctl:

- **Android**: runs `adb shell echo ok` with a 5s timeout to detect zombie emulators
- **iOS Simulators**: runs `xcrun simctl getenv` with a 5s timeout to detect frozen simulators
- **Physical iOS devices**: uses Xcode CoreDevice (`xcrun devicectl`) to verify the device is paired, connected, awake, and available to Xcode. If the device is locked, unplugged, offline, or missing a Trust This Computer prompt, `autosana up` exits before starting Appium.
- **Web**: probes `http://127.0.0.1:<port>` and `https://127.0.0.1:<port>` in parallel (3 attempts with backoff, to cover dev servers still booting). The responding scheme is used for the Cloudflare tunnel origin, preferring https on tie. HTTPS origins skip TLS verification on loopback — self-signed certs from mkcert, `next dev --experimental-https`, and `vite --https` just work. Verification is intentionally disabled here because the cloudflared origin is `127.0.0.1` (no MITM surface); to lock cloudflared back into strict verification with a trusted CA, you'd run cloudflared yourself instead of via `autosana up`.

If a mobile device is unresponsive, `autosana up` fast-fails before wasting time on Appium/tunnel setup. `autosana status --json` reports `device_responsive: false` with an actionable issue message.

`autosana status --json` also includes `components`, `next_action`, and each session's `session_id`. Agents should read these before every local run. If a Cloudflare tunnel or local proxy process died, run `autosana recover --session-id <SESSION_ID> --json` using the matching `status.sessions[].session_id` first. Recovery restarts the smallest failed scope when possible: tunnel first, local proxy next, and full session restart only when Appium/WebDriverAgent needs it.

The detected scheme is reported in `autosana up --json` as `scheme: "http"` or `scheme: "https"`.

## Physical iPhone Readiness

Physical iPhone local runs use Appium/WebDriverAgent (WDA) for both control and screenshots. WDA is installed on the device by `autosana up` and validated with a real screenshot capture before the session reports ready — no extra brew installs are required. For physical iOS sessions, `autosana up --platform ios --device <UDID> --detach --json` starts a local Autosana proxy in front of Appium and tunnels that proxy. The proxy exposes:

- `/_autosana/health`
- `/_autosana/screenshot`
- `/_autosana/diagnostics`

`autosana up` fails fast if no real screenshot provider is ready. `autosana status --json` includes physical-readiness fields:

- `screenshot_ready`
- `screenshot_provider`
- `screen_non_black`
- `wda_ready`
- `screenshot_diagnostics`
- `local_proxy_token` (physical iPhone only, internal token for Autosana proxy screenshots)
- `components`
- `next_action`

Treat `local_proxy_token` as a secret. Read it from `autosana status --json`, pass it directly to Autosana MCP tools for physical iPhone runs, and do not paste real token values into bug reports, chat logs, or public docs. Re-read status after recovery or restart because the token can change. If recovery rotates the token during an in-flight run, start a fresh `flows_run` with the new session values.

Run `autosana doctor --platform ios --device <UDID> --json` when physical setup fails. It checks CoreDevice connectivity, pairing/trust, Developer Mode, DDI services, WebDriverAgent signing, and the optional iPhone Mirroring host capture fallback. WDA screenshots are validated by `autosana up` itself at session start, so doctor warnings on the optional fallback do not block `up` — the doctor only surfaces the concrete blocker when the WDA path itself fails.

Keep the iPhone awake, unlocked, trusted, in Developer Mode, and plugged into power during physical local runs. If the display turns off, WDA may still return an accessibility tree, but Autosana treats black screenshots as a hard error and will not reconstruct a fake screenshot from accessibility data.

## Requirements

- **iOS Simulator**: macOS with one or more booted iOS Simulators
- **Physical iOS**: macOS with Xcode, a paired/unlocked iPhone in Developer Mode, and an Apple Development signing identity (with private key) available to Xcode. After WebDriverAgent first installs on the device, you must trust the developer certificate: **Settings → General → VPN & Device Management → tap your "Apple Development" entry → Trust**. For team-specific WebDriverAgent signing, set `AUTOSANA_IOS_XCODE_ORG_ID` and optionally `AUTOSANA_IOS_UPDATED_WDA_BUNDLE_ID`.
- **Android**: macOS, Linux, or Windows with one or more booted Android Emulators
- **Web**: a local dev server running on any OS
- An [Autosana](https://autosana.ai) account
