Skip to main content
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. 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

Visor enables mapped execution by default for direct action commands and scenario runs. It 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. Agents should follow the repository-installed visor-discovery skill: read compact agent memory before visual evidence, use route for multi-step navigation, and use discover only to name or repair unknown live states. See Agent memory and deterministic routes. 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:
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:

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
  • map-dir
  • attach
  • no-map
  • crawl
  • crawl-depth
  • crawl-limit
  • crawl-include
  • crawl-allow-risky
  • annotate-current
  • observation-token
Use --crawl only when you explicitly want diagnostic programmatic exploration. Crawling is bounded by --crawl-depth and --crawl-limit; the agent discovery workflow does not run it by default. 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. Use --annotate-current <file|-> to merge semantic screen/action descriptions into an observed variant. Pass a UTF-8 JSON file or - for one JSON value from standard input. Add --observation-token <token> to annotate the exact prior discovery without another source read. Visor validates annotation input before device selection or daemon work.
screen and actions are optional individually, but the document must contain at least one screen or action. Screen annotations require label and purpose; they also accept description and notes. Actions require command, args, label, intent, and safety, where safety is safe, needs-input, risky, or unknown; they also accept description and notes. Unknown fields and unsupported commands fail with INPUT_ERROR. Response data fields:
  • action
  • map
  • map.summary, when the persisted map can be read
  • screen
  • observation_token
  • memory
  • annotation, when --annotate-current is used
  • crawl, when --crawl is used
annotation reports screen_applied plus action counts for actions_inserted, actions_updated, and actions_merged. 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. map.agent_path points to the compact AI-only memory. map.path points to Visor’s internal runtime index.

route

Purpose: execute one deterministic, agent-authored multi-path route through a single daemon request. Required input:
  • plan file path, or - to read JSON from standard input
Runtime inputs:
  • device
  • server-url
  • app-id
  • attach
  • map-dir
The plan requires a semantic goal and at least one ordered path. Every step requires safety: "safe", a supported action command, arguments, and an expected screen plus executable selector. Visor rejects unsafe plans before device work. Visor tries eligible paths in document order. It checkpoints each step and returns after the first completed path. When no path can recover an unexpected state, it returns status: "needs_discovery" with compact rediscovery context.

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 Visor enables mapped execution, 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.