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

# Command reference

> See every visor command, the inputs it accepts, and the shape of the data it returns.

This page documents the current command surface exactly as implemented.

If you run `visor --help`, `visor -h`, or invoke the CLI with no command, visor prints a usage summary instead of a JSON response envelope. `visor --version` and `visor -v` print the package version instead of a JSON response envelope.

## Global runtime inputs

These inputs are accepted globally and, where relevant, by individual commands.

| Input        | Meaning                                                                                                    |
| ------------ | ---------------------------------------------------------------------------------------------------------- |
| `device`     | Device or simulator identifier                                                                             |
| `timeout`    | Per-step timeout budget in milliseconds for scenario runs                                                  |
| `output`     | Base output directory for run artifacts and reports                                                        |
| `format`     | `text` or `json`                                                                                           |
| `server-url` | Appium server URL                                                                                          |
| `app-id`     | Android app package or iOS bundle identifier; on iOS it must match an installed app on the selected target |
| `attach`     | Attach to an already running app process when possible                                                     |
| `no-map`     | Disable app-map reads and writes for this command                                                          |
| `repair`     | Allow opt-in exploratory app-map repair after a missing or stale route                                     |

Important notes:

* `format` is currently informational only for normal commands; the implementation still returns JSON envelopes
* `visor --help`, `visor -h`, and no-command invocation print plain-text usage instead of JSON
* `visor --version` and `visor -v` print the package version instead of JSON
* `platform` is inferred from the selected device; `--platform` is not accepted

## Mapped execution

Mapped execution is enabled by default for direct action commands and scenario runs. Visor observes lightweight UI source around target actions, stores a schema-versioned host-machine app map under `.visor/maps`, and can reuse confirmed navigation edges when a later command targets a known screen from a different current screen.

Use `--no-map` or `VISOR_NO_MAP=true` when you need raw Appium behavior. No-map mode disables both map reads and map writes.

Exploratory repair is off by default. Use `--repair` when you want Visor to try safe controls after a missing or stale route instead of failing fast.

Every mapped direct action returns `data.map`. Mapped target, coordinate, and scroll actions also return `data.observation` with before/after fingerprints, `screen_changed`, previous/current screen ids, and `visible_text_count`. Scenario runs return `data.run.map` and per-step map details under `data.run.steps[].details.map` when a step used routing, repair, or map observation.

## Direct action commands

### `tap`

Purpose: click an element or coordinate.

Inputs:

* target mode: `target`
* coordinate mode: `x`, `y`
* optional: `normalized`
* optional post-action wait: `--wait-for`, `--timeout`, and `--poll-ms` to wait for a selector after the tap

Returns action payload fields:

* `action`
* `platform`
* `args`

`args` contains either:

* `target`, or
* `x`, `y`, and `normalized`

Coordinate `x` and `y` values are screen points by default. With `normalized`, they are fractions of the current screen width and height.
Coordinate taps work as direct commands and as scenario steps. Target taps report `args.tap_method`; when Visor can resolve an element rectangle, it taps the center coordinate and returns the resolved `x` and `y`.

Examples:

```bash theme={null}
visor tap --target accessibility=Continue
visor tap --target text=Continue
visor tap --target text~=Settings
visor tap --target "first-in-section=Featured products"
visor tap --x 120 --y 640
visor tap --x 0.5 --y 0.92 --normalized
```

### `navigate`

Purpose: move to a destination.

Required input:

* `to`

Returns action payload fields:

* `action`
* `platform`
* `args.to`

Current behavior:

* the destination is passed to WebdriverIO through `url(to)`

### `act`

Purpose: perform a helper action.

Supported variants:

* `name=type` with `target` and `value`
* `name=type` with `value` only, which types into the active field
* `name=back`
* `name=home`
* `name=reset`
* `name=drag` with `start-x`, `start-y`, `end-x`, and `end-y`
* `name=slider` with `target`, `value` from `0` to `1`, and optional `start-value`

`drag` also accepts `normalized` so coordinates can be fractions of the viewport. `reset` requires `app-id` because Visor restarts the target app.

Optional post-action wait inputs:

* `--wait-for`
* `--timeout`
* `--poll-ms`

`--wait-for` polls for a selector after the helper action completes. `--timeout` and `--poll-ms` control that post-action wait.

Returns action payload fields:

* `action`
* `platform`
* `args`

### `scroll`

Purpose: perform a page-style vertical scroll.

Required input:

* `direction` with value `up` or `down`

Optional input:

* `percent`, default `70`
* post-action wait: `--wait-for`, `--timeout`, and `--poll-ms`

`--wait-for` polls for a selector after the scroll completes. `--timeout` and `--poll-ms` control that post-action wait.

Returns action payload fields:

* `action`
* `platform`
* `args.direction`
* `args.percent`

Current behavior:

* Android uses Appium scroll gestures within the current viewport
* iOS attempts the same gesture path and falls back to a touch swipe when needed

### `screenshot`

Purpose: capture a PNG image.

Optional input:

* `label`
* `path`
* `settle` or `settle-ms`

Returns action payload fields:

* `action`
* `platform`
* `args.label`
* `args.file`
* `args.path`
* `args.width`
* `args.height`

If `label` is omitted, the runtime defaults it to `capture`.
If `path` is provided, Visor writes the PNG to that path. Otherwise it writes `<label>.png` in the current working directory for direct Appium actions, or under `--output` when that directory is supplied. Direct command responses include the screenshot path in both `data.args.path` and the top-level `artifacts` array. `settle` waits briefly before capture; `settle-ms` controls the wait duration.

Examples:

```bash theme={null}
visor screenshot --label checkout
visor screenshot --path /tmp/checkout.png
```

### `wait`

Purpose: pause, wait for a selector, or wait for source stability.

Inputs:

* `ms`
* `for` with optional `timeout` and `poll-ms`
* `stable` with optional `timeout` and `poll-ms`

Returns action payload fields:

* `action`
* `platform`
* `args.ms`, or selector/stability wait result fields

`wait --for <selector>` polls until the selector exists. `wait --stable` polls UI source until two consecutive captures match. If either predicate times out, the command or scenario step fails and preserves the wait payload with `matched: false`.

### `source`

Purpose: capture current UI source as XML.

Optional inputs:

* `label`
* `path`
* `settle` or `settle-ms`

Returns action payload fields:

* `action`
* `platform`
* `args.label`
* `args.file`
* `args.path`
* `args.format`
* `args.bytes`

For direct action commands, run `visor start` first.
For direct `screenshot` and `source`, a returned `args.path` is also surfaced as a top-level response artifact. `--output <dir>` writes the capture under that directory when `path` is not provided.

### `discover`

Purpose: observe the current screen and update the shared app map without running a scenario action.

Optional runtime inputs:

* `device`
* `server-url`
* `app-id`
* `attach`
* `no-map`
* `crawl`
* `crawl-depth`
* `crawl-limit`
* `crawl-include`
* `crawl-allow-risky`

Use `--crawl` to explore safe controls from the current screen and record app-map edges. Crawling is bounded by `--crawl-depth` and `--crawl-limit`.
Use `--crawl-include <text>` and `--crawl-allow-risky` on sandbox/dev builds when you intentionally want the crawler to include otherwise risky controls such as delete, logout, purchase, or confirm surfaces.

Response data fields:

* `action`
* `map`
* `map.summary`, when the persisted map can be read
* `screen`
* `crawl`, when `--crawl` is used

`map.summary` is a path-free inventory of the persisted app map for review: schema version, identity, app id, platform, screen count, variant count, edge count, authentication-required variant count, and last update time.

Persisted variants include source-derived `items` for review and replay tooling, plus `exit_recipes` for visible back/close/dismiss/done controls when coordinates are available.

### `record`

Purpose: start or stop a named replay flow.

Required input:

* `name`

Optional input:

* `stop`
* `force`
* `record-values`

`visor record <name>` creates `.visor/flows/<name>.json` and marks it active. Successful direct action commands append ordered steps to every active flow. `visor record <name> --stop` closes the flow so it can be replayed.

Recording is privacy-preserving by default: typed `act --name type` values are redacted from the flow, and map, observation, and screen metadata are not persisted into `.visor/flows/*.json`. Use `--record-values` only when you intentionally want typed values saved for replay. Starting a recording fails if a flow with the same name already exists; pass `--force` to overwrite it.

### `replay`

Purpose: run a recorded flow through the normal scenario runner.

Required input:

* `name`

Optional runtime inputs:

* `device`
* `timeout`
* `output`
* `server-url`
* `app-id`
* `attach`
* `no-map`
* `repair`
* `param` as `key=value`

`replay` converts recorded steps into a generated scenario and runs it through the Appium-backed daemon path. `--param query=shoes` substitutes `{{query}}` placeholders in recorded args before execution. Replay responses list substituted names in `data.param_keys` and do not echo parameter values.

## Scenario commands

### `validate`

Purpose: parse a scenario and return validation issues.

Required input:

* `scenario`

Response data fields:

* `valid`
* `issues`

Exit behavior:

* returns success when the scenario is valid
* returns a non-zero exit code with `status: ok` when the scenario is syntactically valid JSON but fails schema validation
* returns a failure envelope when parsing fails or the file cannot be read

### `run`

Purpose: execute one scenario, evaluate assertions, and write reports.

Required input:

* `scenario`

Optional runtime inputs:

* `device`
* `timeout`
* `output`
* `server-url`
* `app-id`
* `attach`
* `runtime`, with values `appium` or `local`
* `no-map`

Response data fields:

* `run`
* `warnings`

The `run` object contains the full run result, including step results, assertion results, artifacts, determinism signature, and run-level error data.

For Appium-backed scenario runs, run `visor start` first.

For deterministic local development checks, pass `--runtime local`. This runs the scenario against Visor's local fixture runtime, writes normal reports and artifacts, and does not require Appium, a daemon, an emulator, a simulator, or a real app.

When mapped execution is enabled, the run also includes map metadata and any hidden route or repair decisions in step details.

### `benchmark`

Purpose: run one scenario repeatedly and compute a determinism score.

Required input:

* `scenario`

Optional inputs:

* `runs` with default `20`
* `threshold` with default `95.0`
* `device`
* `timeout`
* `output`
* `server-url`
* `app-id`
* `attach`
* `compare-map`

Response data fields:

* `runs`
* `threshold`
* `determinismScore`
* `pass`
* `failures`
* `runIds`
* `warnings`
* `variants` when `compare-map` is enabled

Benchmark runs are Appium-backed. They do not accept `--runtime local`; use `run` with `--runtime local` for deterministic local harness checks.

Use `--compare-map` to run the same scenario twice per iteration group: once with app-map reads and writes disabled, and once with app-map enabled. Each entry in `variants` includes `name`, `mapEnabled`, `runs`, `threshold`, `determinismScore`, `pass`, `failures`, and `runIds`.

For benchmark runs, run `visor start` first.

### `report`

Purpose: return guidance about where run reports are stored.

Optional input:

* `path`, which defaults to `artifacts`

Response data fields:

* `message`
* `path`
* `format`

## Runtime lifecycle commands

### `start`

Purpose: start the Visor daemon and ensure Appium is reachable.

Optional inputs:

* `server-url`
* `appium-cmd`

Response data fields:

* `serverUrl`
* `daemon`
* `appium`

`daemon` includes process and diagnostic metadata such as `running`, `pid`, `socketPath`, `metadataPath`, `logPath`, `packageVersion`, `runtimeVersion`, `currentPackageVersion`, and `stale`. When `visor start` finds an already-running daemon whose package version differs from the current CLI package version, or whose package version is `unknown`, `daemon.stale` is `true` and the response includes `daemon.warning` and `daemon.nextAction`.

### `status`

Purpose: report Visor daemon state, Appium reachability, and warm sessions.

Optional inputs:

* `server-url`

Response data fields:

* `serverUrl`
* `daemon`
* `appium`

`daemon.packageVersion` and `daemon.runtimeVersion` describe the running daemon process. `daemon.currentPackageVersion` describes the CLI process that produced the status response. If those package versions differ, or the daemon does not report a package version and is shown as `unknown`, `daemon.stale` is `true`, `daemon.warning` explains the mismatch, and `daemon.nextAction` is `restart`. Visor reports this state but does not automatically restart the daemon.

### `stop`

Purpose: close warm sessions and stop the Visor daemon.

Optional inputs:

* `server-url`
* `force`

Response data fields:

* `serverUrl`
* `stopped`
* `daemon`
* `appium`

## Response envelope behavior

Every command returns the same top-level envelope fields:

* `status`
* `command_id`
* `started_at`
* `ended_at`
* `artifacts`
* `next_action`
* `error`
* `data`

`next_action` is a hint, not an execution chain. Examples include `run`, `report`, and `none`.

Help and version output are the exceptions: `visor --help`, `visor -h`, and no-command invocation print usage text instead of an envelope, while `visor --version` and `visor -v` print the package version.
