Revyl Documentation

CLI

Command reference

Every revyl command and its common flags, grouped by what it does.

Authentication

revyl auth login     # Authenticate with Revyl
revyl auth logout    # Remove stored credentials
revyl auth status    # Show authentication status

Feedback

Send feedback to the Revyl team from an authenticated CLI session:

revyl feedback --type bug --body "Rebuild hangs after restarting Metro" --json
revyl feedback --type feature --body-file request.md --json
revyl feedback --type other --body-file - --json < feedback.txt

Choose bug, feature, or other and provide exactly one body source. The body must be nonempty and no longer than 4,000 characters. Do not include secrets, API keys, or customer content. Requests include CLI version, OS, architecture, and available agent context. Submissions are not automatically retried.

Human mode prints Feedback submitted. to stderr; --json returns {"sent": true, "attachments_uploaded": true} on stdout. This command sends text only; the attachment field belongs to the shared support response.

Project Setup

revyl init                                    # Detect and write the local project config
revyl init -y                                 # Write it without interactive follow-up
revyl init --project <uuid>                   # Store this project ID locally
revyl init --detect                           # Re-run framework and build detection
revyl init --force                            # Replace an existing canonical local config
revyl init --xcode-scheme ios=MyAppDev        # Embed an Xcode scheme in generated commands (repeatable)

Initialization always writes a stable local project.id. When detection succeeds, it also writes the detected build.framework and named profiles for the detected iOS and/or Android platforms. Supplying --project writes that ID into the local file; it does not validate, register, attach, or publish a server project. An Xcode scheme supplied during setup is materialized into the generated build commands, not stored as separate runtime state.

Web is in alpha. To add it to your organization, email support@revyl.ai. Web setup is manual. Initialization leaves Web recipes out and points to Web recipes; it does not detect a Web framework, package manager, or preview host, or print host-specific CI workflows. Existing hand-written Web profiles are preserved by --detect.

Use -y to accept only deterministic local choices without prompts and stop after config creation. Interactive initialization may continue through the existing optional authentication, app, skill, and first-build onboarding after the local file is written. If authentication is needed, the CLI prints an approval URL and short code that may be approved in any signed-in browser. After it writes the project config, authenticated setup can also continue into revyl github setup. Configure GitLab automation separately with revyl gitlab setup. The locally written project.id remains unverified and unpublished unless that separate server-backed flow succeeds.

Project Config

Inspect, validate, and migrate the nearest local .revyl/config.yaml. Use the global -C <directory> option to select another project root in the same worktree.

revyl config path                                 # Print the selected config path
revyl config show                                 # Show the local project config
revyl config show --json                          # JSON for scripting
revyl config validate                             # Validate locally and, with authentication and a supported GitHub or GitLab origin, against Revyl
revyl config migrate                              # Summarize, confirm, back up, and replace a legacy file
revyl config migrate --check                      # Summarize a legacy conversion without writing
revyl config migrate --write                      # Back up and replace without confirmation
revyl config migrate --project <uuid>             # Use this project ID in the local conversion
revyl config push                                 # Publish the complete config when authority is manual
revyl config push --force                         # Publish once; the repository's default branch keeps management
revyl config pull                                 # Restore or replace from server state, backing up divergence
revyl config pull --project <uuid>                # Assert the expected configured project ID before pulling
revyl config authorize-cursor-proof               # Authorize Cursor proof runs for the current project

config path and config show use the same nearest-config selection as other project commands. config show expects the canonical contract; migrate a legacy file first. To change settings, edit .revyl/config.yaml directly and validate it; the config commands have no separate command for mutating individual YAML keys.

config migrate only mutates local project files. A successfully prepared legacy migration uses the same concise clean-or-lossy human summary; a write creates an exact-byte backup. JSON --check contains the complete migration proposal and migration ledger. Legacy top-level test aliases become conflict-checked .revyl/tests/<alias>.yaml files. A matching file for the same remote test is preserved byte-for-byte; any other destination conflict fails migration. The retired top-level workflow alias cache is reported and removed.

The default mode asks for confirmation, creates an exact-byte config backup, creates only missing alias files, and atomically replaces the config. --check creates no backup and writes no file; --write performs the same local changes without confirmation. When authenticated, migration may make read-only lookups to reuse or interactively select a project from the verified repository, resolve enabled legacy PR-build app names to exact platform app IDs, and resolve legacy PR workflow names—including the implicit smoke workflow from smoke_every_pr—through authenticated exact-name lookup. Missing, duplicate, or inaccessible workflow matches are omitted and reported instead of stopping the remaining conversion. An enabled PR build is never silently removed: unresolved app or framework meaning is reported as lossy and omitted from the best-effort proposal. Inspect JSON --check before writing; afterward, compare the reported backup or ask a coding agent to reconcile omissions. Use --project when you need to supply the local project identity explicitly. Migration never creates or attaches a server project, pulls or publishes configuration, or otherwise mutates server state.

config validate always validates the discovered project config locally. When authenticated and a supported GitHub or GitLab origin is available, it also verifies repository access and checks the project identity, repository root, configuration path, and referenced Revyl resources without writing anything. Validation requires a stable project.id; it does not convert or scaffold a legacy config. A valid file does not change pull request automation until it is published, so after connected validation succeeds the command also reports whether Revyl runs this exact configuration. The JSON publication.status is published, unpublished, drifted (the local file differs from the published one), or unknown when connected validation did not succeed, and publication.next_action names the step that publishes it: revyl config push, or for a project managed from the repository's default branch, committing the designated .revyl/config.yaml. If the local file does not enable both PR review and Proof of Changes, unpublished or drifted guidance first asks you to configure an enabled pr_review block with its build settings and proof_of_changes.enabled: true, then validate again.

config push requires a readable local project config, authentication, and verified access to its connected repository. Recovery errors name the exact config migrate, config pull or init, auth login or auth status, github status or github connect, Git-origin or repository-access repair, config validate, or retry command needed for the failed prerequisite. Push then reads the current server state and publishes with an exact absent-or-hash precondition. It makes a single publish attempt so an ambiguous network result cannot be retried as a new write. Projects managed from the repository's default branch reject ordinary manual publication; validate the file and commit the designated .revyl/config.yaml instead. config push --force is an explicit one-publication override. It doesn't change who manages the config or its reconciliation position, so a later configuration change on the repository's default branch can replace the forced aggregate. Under manual authority, --force does not change behavior.

When no local config exists, config pull verifies the connected repository, selects the nearest configured project root containing the current directory, verifies its configuration hash, and atomically creates .revyl/config.yaml at that root. --project may select an explicitly identified, more-specific registered project beneath an ancestor local config, but it never rebinds an applicable active local project with a different ID. It does not replace a legacy or invalid file. With an existing valid file, semantic equality remains a no-op. Divergence creates an exact-byte backup and atomically replaces the file. When the local project was deleted and exactly one active replacement owns the same root, pull performs that backup/replacement without trusting the retired ID. Pull reports the backup path and does not require an interactive confirmation, including in JSON and non-interactive invocations.

config authorize-cursor-proof records server-owned human authority for unattended Cursor proof runs after Revyl confirms the caller can launch cloud agents and the caller's live Cursor repository catalog includes the project repository. It does not edit or publish .revyl/config.yaml, and works for both manual authority and authority from the repository's default branch.

Inherited Launch Configuration

REVYL_INHERITED_LAUNCH_ENV_VAR_IDS is a comma-separated list of stored launch-configuration UUIDs applied automatically by revyl dev, revyl test run, revyl workflow run, and raw revyl device start commands. Cloud Agent Conversations sets it from the launch selection. Explicit --launch-var and --launch-arg-set values remain additive; --no-inherited-launch-vars ignores it entirely. Restart an existing device or dev session before changing launch configuration.

Running Tests

revyl test run login-flow                 # Saved branch and build (Default branch tries your Git branch)
revyl test run login-flow --build         # Build, upload, then run

revyl workflow run smoke-tests            # Run a workflow
revyl workflow run smoke-tests --build    # Build then run workflow

Advanced Run Flags

--retries 3       # Retry on failure (1-5, default 1)
--build-id <id>   # Run one exact build
--branch <name>   # Run-only Revyl branch override
--build-selection latest  # Newest build on the branch, ignoring its pin
--all-branches    # Newest build across all of the app's branches
--no-wait         # Queue and exit without waiting for results
--verbose / -v    # Show step-by-step execution progress
--timeout 3600    # Max execution time in seconds

Explore

Build or refresh an Atlas map from the terminal:

revyl explore run "My App"                # Your Git branch when it has a usable build, else the default branch; wait
revyl explore run --platform android       # Use the configured Android app
revyl explore run "My App" --no-wait --json
revyl explore status <run-id>
revyl explore cancel <run-id>

Common run flags:

--build-id <id>                # Explore an exact uploaded build
--branch <name>                # Use this branch instead of your Git branch or the default branch
--build-selection latest       # Newest build on the branch, ignoring its pin
--all-branches                 # Newest build across all of the app's branches
--explorers 4                  # Parallel explorers (default 3)
--strategy surface-sweep       # balanced, surface-sweep, journey-focus, hard-edges
--instructions "Cover checkout"
--auth-instructions "Use the test account"
--launch-var <key-or-id>       # Stored launch variable (repeatable)
--device-model "iPhone 16"
--os-version "iOS 18.5"
--max-duration 20m
--idle-timeout 10m
--timeout 30m                  # Local wait limit; does not cancel the run
--open

See Explore from the CLI for output, outcome, and CI semantics.

Dev Loop

revyl dev starts a local development loop backed by a cloud device session. Both Expo and bare React Native projects are supported, plus native Swift/iOS and Android projects via rebuild-based loops.

Each revyl dev start creates a dev context -- a named, worktree-local dev loop bound to one resolved profile/platform recipe. Use --context for same-repo concurrency; separate worktrees use the default context automatically. The context keeps that recipe for subsequent rebuilds.

revyl dev --profile development --platform ios  # Explicit profile and platform
revyl dev --platform android           # Infer the profile when only one remains eligible
revyl dev --context ios-main           # Named context for parallel loops
revyl dev --no-open                    # SSH/headless: keep device running but don't open browser
revyl dev --profile development --platform ios --build  # Force a fresh build
revyl dev --app-id <app-id>            # Use explicit app override
revyl dev --build-version-id <id>      # Use explicit build override
revyl dev --launch-var API_URL         # Add a stored variable by key (UUID also accepted)
revyl dev --no-inherited-launch-vars   # Ignore inherited launch-variable IDs
revyl dev --force-hot-reload           # Diagnostic launch after Expo relay transport
revyl dev --no-build --tunnel "<expo-dev-client-link>"  # Use an existing Expo tunnel/deep link

Profile and platform selection applies explicit flags first. Without --profile, revyl dev prefers one uniquely named development-like profile, then falls back to a sole eligible choice. Development-like names contain a dev or development token, so dev, development, and ios-dev match but device does not. Any remaining interactive ambiguity prompts; a non-interactive run fails with the choices and the required flag. There is no active/default profile or default platform.

revyl dev:

  • starts your local dev server (Expo via npx expo start --dev-client, or Metro via npx react-native start)
  • creates a Revyl relay to expose it to cloud devices
  • runs on --branch, otherwise the branch named after your Git branch, creating it if needed, and uploads its rebuilds there
  • installs that branch's default build, unless --build-selection latest or --build-version-id picks another
  • on a new or empty branch, builds a dev build first
  • outside a Git checkout, with no branch or build chosen, uses the newest usable build on the app's default branch, even when that branch is pinned
  • with --remote --seed-latest, installs the branch's newest build at once and swaps in the fresh build when it lands
  • with --no-build, reuses an existing build and turns off rebuilds; when the branch has no usable build, your Git branch falls back to the app's default branch, while a branch you name fails
  • doesn't accept All branches, since a dev loop follows one branch
  • keeps the same resolved recipe and branch for detached startup and every rebuild in the context
  • opens a cloud device session wired to the deep link

Normal revyl dev runs suppress advisory HMR diagnostics. Use revyl dev --debug when you specifically need relay/HMR troubleshooting output. In cloud-agent environments, keep the Revyl relay running after Dev loop ready and use device screenshots or revyl device report --session-id <id> --json as source of truth before switching to an external Expo tunnel. For Expo manifest readiness timeouts, use --force-hot-reload as the first diagnostic path. Revyl still starts Expo and verifies relay transport; the flag skips only the manifest and bundle proof so the device can be the source of truth. If the app loads, keep working. If the dev client shows a project load error, restart Expo/Metro or capture a session report with revyl device report --session-id <id> --json.

For externally managed Expo tunnels, start Expo yourself and pass either the full dev-client link Expo prints or the raw https://... tunnel URL:

npx expo start --tunnel --dev-client
revyl dev --no-build --app-id <app-id> --tunnel "<deep-link-from-expo>"

When you pass the full Expo dev-client link, revyl dev uses its scheme and prefix directly, so selected-build metadata is not required. For a raw tunnel URL or normal startup, Expo scheme and prefix information comes from the selected build's metadata.

After the dev loop is running, use revyl device commands to interact:

revyl device screenshot
revyl device tap --target "Login button"
revyl device instruction "verify the dashboard loads"

Device-first flow

Start a plain device session first, then attach and run the dev loop on it:

revyl device start --platform ios
revyl dev attach active                # or: revyl dev attach <session-id>
revyl dev                              # reuses the attached session

When a context has an attached session, revyl dev --context <name> reuses it instead of provisioning a new device. Attached sessions are left running when the dev loop exits -- use revyl device stop to end them.

Context management

revyl dev list                         # List dev contexts in the current worktree
revyl dev use ios-main                 # Switch the current context
revyl dev status                       # Show context status (JSON)
revyl dev rebuild                      # Trigger a rebuild
revyl dev stop                         # Stop the current context (name one when several are live)
revyl dev stop --all                   # Stop all contexts

revyl dev stop exits zero when the named context doesn't exist in this worktree, printing that there is nothing to stop; --json adds already_stopped: true. Without a context name or --context, it refuses to choose when more than one context in the worktree has a running loop or its own device session, and lists each one with its revyl dev stop <context> command. When exactly one context is live and the current context is a loop that has already exited, it stops the live one instead and says so on stderr; --json output then adds defaults_applied. If there is no current context and several contexts exist, it asks you to name one, as before.

Hot reload providers

Expo deep-links into a dev client via app_scheme; bare React Native loads the JS bundle directly over the Revyl→Metro relay. For provider config schema and the full per-framework setup walkthrough, see Dev Loop Overview.

New Branch Build Flow

Use this when you create a new Git branch and want revyl dev to run that branch's build:

git checkout -b feature/new-login
revyl build --profile development --platform ios
revyl dev --profile development --platform ios

Both use the Revyl branch feature/new-login, which the first upload creates.

To use one exact build:

revyl dev --build-version-id <build-id>

New Branch Direct File Flow (No Build Step)

Use this when you already have a local artifact and want to upload it without running the build command.

  1. Build the artifact with your normal local or CI build command.
  2. Upload it with revyl build upload --file.
  3. Run revyl dev.
git checkout -b feature/new-login
revyl build upload --file build/dev-ios.tar.gz --platform ios
revyl dev --profile development --platform ios

Optional explicit version label:

revyl build upload --file build/dev-ios.tar.gz --platform ios --version feature-new-login-20260227-153000

Plain device sessions (no hot reload -- these are the base layer revyl dev builds on):

revyl device start --platform ios
# Want hot reload? Run: revyl dev attach active

Builds and Dev Mode

All revyl build and revyl build upload commands push to an app container. For config-driven builds, that is the app_id on the resolved recipe. When Git metadata is available, each upload records its Git branch and commit.

--branch <name> chooses the branch to upload to or run on. Without it, uploads go to your Git branch, and runs on Default branch try it first. See How a run picks its build.

Each developer gets their own cloud device session, relay, and local dev server -- builds are the only shared resource.

When you need a new build (by project type):

  • Expo / React Native: Dev mode serves your JS/TS live from your local Metro via a Revyl relay. The binary is just a "dev client shell." You only need a new build when native dependencies change (new native modules, Podfile changes, Gradle dependency changes, app.json native config).
  • Swift: Every code change requires a new build. The binary is the app.
  • Kotlin/Android: hot reload isn't supported, so every code change requires a new build.

Team workflow commands:

revyl build list --app "My App" --branch feature/new-login
revyl build --profile development --platform ios      # Uploads to your Git branch
revyl dev --profile development --platform ios        # Runs on your Git branch
revyl dev --profile development --platform ios --branch main  # Reuse another branch's dev build
revyl dev --build-version-id <id>            # Use one exact build

Tip: For Expo/React Native, developers can share one dev build and still see their own code changes, since JS is served locally. For native projects, each developer should upload their own branch build.

App Management

An app is a named container for your uploaded builds (e.g. "My App Android"). Tests run against an app.

revyl app create --name "My App" --platform android   # Create an app
revyl app list                                         # List all apps
revyl app list --platform ios                          # Filter by platform
revyl app delete "My App"                              # Delete an app

Manage the default branch and each branch's default build without changing Git:

revyl app branch list --app "My App"
revyl app branch create staging --app "My App"
revyl app branch default staging --app "My App"
revyl app branch pin staging <build-id> --app "My App"
revyl app branch unpin staging --app "My App"
revyl app branch rename staging release --app "My App"

Add --pin to revyl build or revyl build upload to pin the branch to the new build; uploads don't pin otherwise. Saved IDs survive renames; name-based scripts must use the new name. See Build branches and its CLI reference.

Build Management

Web is in alpha. To add it to your organization, email support@revyl.ai.

revyl build                              # Build from config in the cloud
revyl build --profile development --platform android  # Build a selected recipe in the cloud
revyl build --profile preview --platform web --local  # Build a Web bundle locally and upload it
revyl build upload --file ./app.apk --app <id>  # Upload a local artifact directly
revyl build upload --url <artifact-url> --app <id>  # Ingest from a remote URL
revyl build upload --platform web --url <https-url>  # Register a Web preview deploy as a build
revyl build upload --file ./app.apk --app <id> --preview --json  # Also print a click-to-launch preview URL
revyl build which --app <app-id>          # Show the branch and build a run would use, and whether the branch is pinned
revyl build which --app <app-id> --branch staging --json
revyl build list                         # List your organization's apps
revyl build list --app <app-id>          # List builds across all app branches
revyl build list --app <app-id> --branch staging --page 1 --page-size 20
revyl build list --app <app-id> --branch HEAD   # Builds for your checked-out Git branch
revyl build list --app <app-id> --all-branches  # All branches (the default)

revyl build which shows the branch and build a run would use without starting one. It doesn't read a test or workflow's saved choice. See Check which build a run uses.

Direct builds select one customer-named profile and its ios, android, or web recipe. There is no active/default profile. If an omitted value has one eligible choice, the CLI resolves it; otherwise interactive use prompts and non-interactive use fails with the available choices. Local and remote builds execute the same inherited recipe from the selected project root.

revyl build runs in the cloud by default. See Build Configuration for prerequisites and flags. This does not change revyl dev or test/workflow --build defaults.

Remote Build Caches

revyl cache list                         # List configured caches
revyl cache delete <key>                 # Delete a configured cache

These commands manage configured path caches. Automatic compilation caches are controlled through the remote build configuration. See Caching.

Uploading a Build

For GitHub PR matching from any CI runner, supply the base repository and full PR head SHA. These flags work with both --file and --url and override detected SCM identity:

revyl build upload --file ./app.apk --app <id> \
  --repo acme/mobile --commit "$PR_HEAD_SHA" --yes --json

See Use your own CI for review policy and runner setup.

Use revyl build any time you want to refresh the binary on Revyl without re-running tests.

Common flow:

  1. Make sure your app is configured and credentials are available.
  2. Run:
cd your-app
revyl build --profile development --platform ios

When --version is omitted, the CLI uses the version label <git-branch-slug>-YYYYMMDD-HHMMSS (for example feature-new-login-20260227-153000), from the branch checked out locally. On a detached checkout, as in most CI jobs, the label is the timestamp alone. The label doesn't choose the Revyl branch.

  1. Use the uploaded build ID from the result to run against that exact binary:
revyl test run login-flow --build-id <uploaded-build-id>

Or let Revyl build locally and upload before running the test:

revyl test run login-flow --build --profile development --platform ios

Useful companion commands:

  • revyl build list to verify uploads and inspect platform/app history
  • revyl test run <test> --build-id <id> to run one exact build

For Expo projects, authenticate any tools named by the selected recipe before running the build. Revyl executes the authored command arrays without rewriting eas.json or switching EAS profiles.

Uploading from a URL

For teams that store builds in internal artifact storage (Artifactory, S3, GCS, GitHub Actions), the CLI can ingest an artifact directly from a URL without downloading it locally first:

# Public or pre-signed URL
revyl build upload --url https://artifacts.internal.company.com/builds/app-latest.ipa --app <id>

# Authenticated URL with custom headers
revyl build upload --url https://example.com/builds/app.apk \
  --header "Authorization: Bearer <token>" \
  --app <id>

# Multiple headers
revyl build upload --url https://example.com/builds/app.ipa \
  --header "Authorization: Bearer <token>" \
  --header "X-Custom-Header: value" \
  --app <id>

The backend downloads, validates, and stores the artifact server-side. The --url flag is mutually exclusive with --file.

Preview a just-uploaded build

--preview prints a URL that auto-starts a simulator with that exact build when a signed-in org member opens it. Upload itself does not start a device. Opening the link lands in the normal session viewer. The launch page reads platform from the uploaded build's app. --json stays the same envelope and adds optional preview_url on the build object (/sessions/launch?buildId=<id>). Refreshing /sessions/launch while a start is in flight opens the same session; after that start settles, opening the durable URL again starts a new device. /sessions/launch?appId=<app_id> opens the default build of the app's default branch each time, instead of always opening one upload.

revyl build upload --file ./app.apk --app <id> --yes --preview --json

Native iOS: Build in Xcode, then revyl dev

For native iOS developers, revyl dev automatically detects the most recent simulator .app from Xcode DerivedData. Build your app in Xcode as you normally would, then run:

revyl dev                     # Auto-finds and uploads the local simulator build
revyl dev --build             # Force a rebuild from the configured build command
revyl dev --build-version-id <id>  # Use one exact uploaded build

The discovery scans ~/Library/Developer/Xcode/DerivedData/<Project>-*/Build/Products/Debug-iphonesimulator/*.app for the most recently modified non-test .app that matches the project in the current directory. Test runner bundles (*Tests.app, *UITests.app) are automatically excluded.

Test Management

For the end-to-end CLI authoring workflow, see Test Configuration.

# Test lifecycle
revyl test create login-flow --platform android   # Create + auto-sync YAML to .revyl/tests/
revyl test create --from-session <session-id> login-flow --app <app-id>
revyl test run login-flow                          # Run a test
revyl test run login-flow --no-open                # Keep a blocking terminal run from opening its completed report
revyl test run login-flow --no-inherited-launch-vars # Ignore inherited launch-variable IDs
revyl test open login-flow                         # Open test in browser editor
revyl test rename login-flow new-login-flow        # Rename while preserving history
revyl test duplicate login-flow                    # Clone an existing test
revyl test delete login-flow                       # Delete a test
revyl test cancel <task-id>                        # Cancel a running test

# Sync & inspect
revyl test sync                   # Reconcile local and remote tests
revyl test sync --dry-run         # Preview test reconciliation changes
revyl test sync --prune           # Reconcile tests and prune stale mappings
revyl test list                   # Show local tests with sync status
revyl test remote                 # List all tests in your organization
revyl test push                   # Push local changes to remote
revyl test pull                   # Pull remote changes to local
revyl test diff login-flow        # Show diff between local and remote

# Execution status & reports
revyl test status login-flow                       # Latest execution status
revyl test history login-flow                      # Execution history (table)
revyl test report login-flow                       # Detailed report (latest execution)
revyl test report <task-uuid> --json               # Report by task ID, JSON output
revyl test report login-flow --no-steps            # Summary only, no step breakdown
revyl test share login-flow                        # Public report link (no sign-in needed)
revyl test share login-flow --private              # Organization-only link; requires Revyl sign-in
revyl test share login-flow --open                 # Share link and open in browser

# Versioning
revyl test versions login-flow                     # Show saved versions for a test
revyl test restore login-flow --version 3          # Restore to a previous version

# YAML-first creation (requires .revyl/config.yaml)
revyl test create login-flow --from-file ./login-flow.yaml
revyl test create --from-session <session-id> login-flow --app <app-id>
revyl test push login-flow --force

# Per-command flags
#   --dry-run    Available on: test create, test push, test pull

# Live verify during a dev session, then promote to a regression
revyl dev
revyl device validation -s 0 "Home screen is visible"
revyl test create login-flow --from-session <session-id>
revyl test run login-flow

revyl sync is a deprecated compatibility alias for revyl test sync. Both forms reconcile tests only. Project configuration remains explicit through revyl init, revyl config pull, and revyl config push.

Test Variables

Per-test variables referenced in step descriptions via {{name}} syntax and substituted at runtime. Names may contain letters, numbers, hyphens, or underscores.

revyl test var list login-flow                              # List variables on a test
revyl test var get login-flow username                      # Read a single value
revyl test var set login-flow username=testuser@example.com # Add or update (upsert)
revyl test var set login-flow "password=my secret"          # Values may contain spaces
revyl test var set login-flow otp-code                      # Name-only (runtime-filled)
revyl test var rename login-flow old-name new-name          # Preserve value, change name
revyl test var delete login-flow username                   # Delete one
revyl test var clear login-flow                             # Delete ALL (needs --force in non-TTY)

Test variables are distinct from Global Variables (org-wide {{global.name}}) and Global Launch Configuration (org-wide reusable launch env vars).

Tags

Organize tests with tags for filtering and grouping.

revyl tag list                                # List all tags with test counts
revyl tag list --search regression            # Filter by name
revyl tag create regression --color "#22C55E" # Create (color optional, hex)
revyl tag update regression --name regression-v2 --description "…"
revyl tag delete regression --force           # Delete (removes from all tests)

# Tag assignments on a test
revyl tag get my-test                         # Show tags on a test
revyl tag set my-test regression,smoke        # Replace all tags (auto-creates missing)
revyl tag add my-test login                   # Add tags (keep existing)
revyl tag remove my-test smoke                # Remove specific tags

Module Management

Reusable modules can be imported into tests with module_import blocks. For examples, see Creating Tests.

revyl module list                                  # List modules
revyl module list --search login                   # Filter modules by name/description
revyl module get login                             # Show module blocks and metadata
revyl module create login-flow --from-file blocks.yaml
revyl module update login --from-file new-blocks.yaml
revyl module insert login                          # Print a module_import YAML snippet
revyl module usage login                           # Show tests that import this module
revyl module versions login                        # List saved versions (who changed what and when)
revyl module restore login --version 2             # Restore blocks + metadata to a prior version
revyl module delete login                          # Delete a module

Scripts

Manage code-execution scripts used by code_execution blocks in tests. Supported runtimes: python, javascript, typescript, bash.

revyl script list                                             # List all scripts
revyl script list --runtime python                            # Filter by runtime
revyl script list --json                                      # JSON output
revyl script get my-validator                                 # Show metadata + source
revyl script create my-validator --runtime python --file validate.py
revyl script create price-check --runtime javascript --file check.js --description "Validate prices"
revyl script update my-validator --file validate.py           # Replace source
revyl script update my-validator --name new-name              # Rename
revyl script update my-validator --description "Updated"      # Change description
revyl script delete my-validator --force                      # Delete
revyl script usage my-validator                               # Show tests that use this script
revyl script insert my-validator                              # Print a code_execution YAML snippet

File Management

Manage files (certificates, configs, images, media) in your organization's file library. Tests reference uploaded files via revyl-file:// URIs.

revyl file list                                               # List files
revyl file list --limit 10 --offset 20                        # Paginate
revyl file upload ./certs/staging.pem                         # Upload a file
revyl file upload ./config.json --name "App Config" --description "Feature flags"
revyl file download staging.pem                               # Download to ./staging.pem
revyl file download staging.pem ./certs/                      # Download into a directory
revyl file download staging.pem ./renamed.pem                 # Download to exact path
revyl file edit staging.pem --name "prod-cert.pem"            # Rename
revyl file edit staging.pem --file ./new-cert.pem             # Replace content (preserves ID)
revyl file delete staging.pem --force                         # Delete

Supported types (with size limits): certificates .pem .cer .crt .key .p12 .pfx .der (50 MB), config .json .xml .yaml .yml .toml .csv .txt .conf .cfg .ini .properties (50 MB), images .png .jpg .jpeg .gif .pdf (250 MB), media .mp4 .mp3 (500 MB).

Global Resources

Org-wide resources available across all tests.

Global Variables

Reference a global as {{global.name}}, which reads only global variables, or as {{name}}, where a local test variable with the same name takes precedence. Use --secret for credentials or tokens; secret values are encrypted and shown as ******** in list/get output.

revyl global var list                                         # List all
revyl global var get login-email                              # Read one (secrets are masked)
revyl global var set login-email=testuser@example.com         # Add or update (upsert)
revyl global var set "password=my secret" --secret            # Store as encrypted secret
revyl global var set "password=new value" --no-secret         # Convert/update as plaintext
revyl global var set otp-code                                 # Name-only (runtime-filled)
revyl global var delete login-email                           # Delete

Global Launch Configuration

Reusable launch configurations are stored at the org level and attached to tests from the web UI. Environment configurations hold key-value startup settings. iOS argument sets hold exact ordered argv tokens and are created and edited in Library > Launch Configurations. Either type can be secret.

Getting started with raw device sessions:

revyl global launch-var create API_URL=https://staging.example.com
revyl device start --platform ios --launch-var API_URL

Create an environment configuration with a KEY=VALUE pair, then pass its key or UUID to --launch-var. Select a stored iOS argument set by name or UUID with repeatable --launch-arg-set. Append non-secret inline tokens with repeatable --launch-arg=<token>.

revyl global launch-var list                                  # Show non-secret values; secrets stay masked
revyl global launch-var get API_URL                           # Show one
revyl global launch-var create API_URL=https://staging.example.com
revyl global launch-var create API_TOKEN=token --secret       # Store encrypted; never readable
revyl global launch-var create DEBUG=true --description "Enable debug startup"
revyl global launch-var update API_URL --value https://prod.example.com
revyl global launch-var update API_URL --secret               # Encrypt the current plaintext value
revyl global launch-var update API_TOKEN --secret=false --value replacement
revyl global launch-var update API_URL --key API_BASE_URL --description "Shared API endpoint"
revyl global launch-var delete API_URL --force                # Also detaches from any tests

Aliases: launch-var, launch-vars, launch-variable.

Workflow Management

# Lifecycle
revyl workflow list                                              # List all workflows
revyl workflow list --json                                       # JSON output
revyl workflow info smoke-tests                                  # Tests, overrides, run config, last run
revyl workflow create smoke-tests --tests login-flow,checkout    # Create workflow
revyl workflow add-tests smoke-tests payment                     # Add test(s) to workflow
revyl workflow remove-tests smoke-tests checkout                 # Remove test(s) from workflow
revyl workflow rename smoke-tests regression-smoke               # Rename (preserves history)
revyl workflow run smoke-tests                                   # Run workflow
revyl workflow run smoke-tests --concurrency 4                    # Override the cap for this run
revyl workflow run smoke-tests --concurrency 0                    # Use organization capacity only for this run
revyl workflow run smoke-tests --no-open                         # Keep a blocking terminal run from opening its completed report
revyl workflow run smoke-tests --build                           # Build + upload before running
revyl workflow run smoke-tests --build --profile development --platform ios
revyl workflow run smoke-tests --ios-app <uuid> --android-app <uuid>  # Override apps for this run
revyl workflow run smoke-tests --location "37.7749,-122.4194"    # Override GPS for this run
revyl workflow run smoke-tests --launch-var API_URL              # Stored variable key (UUID also accepted)
revyl workflow run smoke-tests --launch-env FEATURE_PREVIEW=true # Inline launch var for every test
revyl workflow run smoke-tests --launch-arg-set AuthTestArgs      # Stored iOS argument set
revyl workflow run smoke-tests --launch-arg=--route --launch-arg="sign in" # Exact inline tokens
revyl workflow run smoke-tests --no-inherited-launch-vars        # Ignore inherited launch-variable IDs
revyl workflow open smoke-tests                                  # Open in browser
revyl workflow delete smoke-tests                                # Delete workflow
revyl workflow cancel <task-id>                                  # Cancel running workflow

# Execution status & reports
revyl workflow status smoke-tests                                # Latest execution status
revyl workflow history smoke-tests                               # Execution history (table)
revyl workflow report smoke-tests                                # Detailed report (latest)
revyl workflow report <task-uuid> --json                         # Report by task ID, JSON
revyl workflow share smoke-tests                                 # Public report link (no sign-in needed)
revyl workflow share smoke-tests --private                       # Organization-only link; requires Revyl sign-in
revyl workflow share smoke-tests --open                          # Share and open in browser

Blocking human-terminal test and workflow runs open their completed report by default. Use --no-open to suppress it. No-wait, JSON, GitHub Actions, CI, SSH, and other headless executions never open a browser, even when --open is present.

All four launch flags are repeatable. For duplicate keys, inline --launch-env values override --launch-var, and --launch-var overrides values attached to an individual test. Prefer stored --launch-var values for secrets because inline values are non-secret and may be visible in shell history, process arguments, and execution reports. Stored argument sets are flattened in attachment/flag order, then inline tokens are appended in flag order. Mixed workflows skip argument sets for Android children; Android-only runs reject them.

Workflow Settings

Stored overrides and run configuration that apply to every execution of a workflow.

# Location override (all tests in workflow use this GPS)
revyl workflow location set my-workflow --lat 37.7749 --lng -122.4194
revyl workflow location show my-workflow
revyl workflow location clear my-workflow

# App override (per platform)
revyl workflow app set my-workflow --ios <app-uuid>
revyl workflow app set my-workflow --android <app-uuid>
revyl workflow app set my-workflow --ios <ios-uuid> --android <android-uuid>
revyl workflow app show my-workflow
revyl workflow app disable my-workflow    # Turn the override off and keep its apps
revyl workflow app enable my-workflow     # Turn the saved override back on
revyl workflow app clear my-workflow      # Remove the override, like turning it off in the dashboard

# Run config (attempts per test, including the first)
revyl workflow config show my-workflow
revyl workflow config set my-workflow --retries 2
revyl workflow config set my-workflow --concurrency 12
revyl workflow config set my-workflow --concurrency 0  # Remove the workflow cap

Stored retries apply while the workflow's run config override is enabled; otherwise each test uses its own run config. config show reports retries, whether the override is enabled, and the saved concurrency cap; use workflow info for the full stored configuration. When retries are unset, config show labels the default of one attempt. Explicit values outside 1–5 are rejected without changing anything. Transient read and save failures are retried; if current settings cannot be read, nothing is changed so other stored settings are not overwritten. On workflow config set, --concurrency saves a cap for future runs across iOS and Android independently of the run-config override. Zero removes the cap; organization capacity always applies. Initialized runs retain their snapshot. JSON output includes nullable default_concurrency_limit. The CLI reads the saved value back before confirming a concurrency change (see Workflows).

On workflow run, --concurrency overrides the saved default for that run only; omitting it inherits the default, and 0 removes the workflow cap for that run. Organization capacity still applies. This works with both waiting and --no-wait runs. The execution API accepts concurrency_limit_override with the same semantics; omission or null inherits the saved default.

Workflow Quarantine

Quarantined tests still run but their failures are ignored when computing overall workflow pass/fail — useful for unblocking CI while flaky tests are investigated.

revyl workflow quarantine list smoke-tests                             # Show tests and status
revyl workflow quarantine add smoke-tests login-flow                   # Quarantine one
revyl workflow quarantine add smoke-tests login-flow checkout payment  # Quarantine several
revyl workflow quarantine remove smoke-tests login-flow                # Unquarantine

GitHub PR Automation

Connect the Revyl GitHub App and configure PR automation for the project root selected by the nearest .revyl/config.yaml.

revyl github connect                     # Install or verify GitHub repository access
revyl github connect --no-open --no-wait --json  # Print the install link and return (agents, remote shells)
revyl github status                      # Show the GitHub App connection status
revyl github status --json               # Same facts as one JSON object on stdout
revyl github setup                       # Configure the selected project root
revyl -C apps/mobile github setup        # Configure a nested monorepo project
  • github connect only installs or verifies repository access. It never edits or publishes project configuration. From a coding agent, --no-wait --json prints the install link as install_url and returns at once instead of waiting; --no-open also skips opening a browser. Confirm the install with github status. See the CLI's GitHub status guide.
  • github status also reports whether the current Git repository (from its GitHub origin) is granted to the installation, even before a .revyl/config.yaml exists, so a fresh checkout can tell whether it still has to be added to the GitHub App. --json prints one stable object on stdout; the keys and status values are documented in the CLI's GitHub status guide.
  • github setup guides the selected project through a managed-profile or finished-artifact CI-upload policy, writes pr_review, and validates the project config. Under manual authority, it confirms publication and publishes the same complete aggregate as revyl config push. Under Git authority, it prepares the designated file for commit without publishing.
  • GitHub installation is repository-wide, while setup, publication, authority, and reconciliation are project-root-specific. Run setup separately from each intended root in a monorepo.
  • Ordinary config push uses an absent-or-hash precondition and is rejected when the project is managed from its designated config on the repository's default branch. github setup prepares the designated file for commit in that mode. Run revyl config validate and commit the file, or use revyl config push --force for a deliberate one-time publication that keeps management from the repository's default branch enabled.
  • A successful reconciliation from the repository's default branch makes that project Git-owned. Fetch or validation failures preserve its current state. Removing the designated file returns only that project to manual authority while retaining its last accepted aggregate and automation; it does not delete the project.

Project roots are immutable. Manual projects can be deleted from the GitHub integration in the web app and recreated at the correct root. Git-owned projects must first return to manual authority through the designated-file flow above. Deletion preserves the repository connection, GitHub installation, repository report destination, historical results, and already-frozen review and build work, which may finish. Pending Cursor proof that has not launched fails closed because deletion removes its live authorization. Deleting the last project opens the dashboard's form for a new project root. There is no CLI project-delete command; after creating a same-root replacement, use revyl -C <replacement-root> config pull to back up and replace a stale local file. Pull adopts a replacement automatically only when exactly one active project owns the old root; otherwise Revyl does not assume where the replacement belongs.

The standalone github init and github push commands no longer exist; config push is the direct publication command and never scaffolds. See the GitHub integration for the pr_review schema and the exact server-stored configuration groups.

GitLab integration

revyl gitlab connect                     # Enter an automation token at a hidden prompt
revyl gitlab connect --instance https://gitlab.example.com
revyl gitlab connect --token-stdin       # Read a token from an existing secure stdin source
revyl gitlab status --json               # API, repository, project, and webhook state
revyl gitlab setup                       # Interactive project configuration and webhook check
revyl -C apps/mobile gitlab setup        # Target a nested project

GitLab requires an automation token with Developer access and api scope. Connection management uses the active Revyl organization. Token replacement uses the same connect command. Setup preserves Git-managed authority and checks authenticated webhook delivery independently of API access. A GitLab Maintainer or Owner installs the project webhook manually.

Setup checks live repository access before prompting to select or create apps.

Status preserves API, repository, and webhook facts when project configuration cannot be read, and reports the project-specific failure in project_error.

See GitLab integration for the connection tutorial.

Device Management

# Session lifecycle
revyl device start                             # Start a cloud device session (defaults to iOS)
revyl device start --platform android --open   # Start and open viewer in browser
revyl device start --platform ios --app-url https://example.com/app.ipa # Start a raw session with a preinstalled app
revyl device start --platform ios --launch-var API_URL --launch-var DEBUG # Stored variable keys (UUIDs also accepted)
revyl device start --platform ios --launch-arg-set AuthTestArgs --launch-arg=--debug # Stored set, then inline token
revyl device start --platform ios --no-inherited-launch-vars # Ignore inherited launch-variable IDs
revyl device start --device-name revyl-ios-iphone  # Use a named device preset
revyl device start --device-model "iPhone 16" --os-version "iOS 18.5"  # Specific model + runtime
revyl device start --device-model "iPhone 16"  # Newest runtime that model supports (announced on stderr)
revyl device stop                              # Stop your only live session (name one with -s when several are live)
revyl device stop --all                        # Stop all sessions
revyl device list                              # List active sessions, including ones still starting
revyl device use <index>                       # Use this session for commands in this directory that name none
revyl device attach <session-id>               # Attach to an existing session by ID
revyl device info                              # Show session details (includes `whep_url` in JSON when available)
revyl device history                           # Show recent device session history
revyl device doctor                            # Run session diagnostics
revyl device targets                           # List available device models and OS versions
revyl device targets --platform android --json # Filter / JSON output

# Interaction (use --target for AI grounding, or --x/--y for coordinates)
revyl device tap --target "Login button"                         # AI-grounded tap
revyl device tap --x 200 --y 400                                 # Coordinate tap
revyl device double-tap --target "item"                          # Double-tap
revyl device long-press --target "icon" --duration 1500          # Long press (ms)
revyl device type --target "Email field" --text "user@test.com"  # Type text
revyl device swipe --target "list" --direction down              # Swipe gesture
revyl device swipe up                                            # Swipe from the screen center
revyl device scroll down --amount 600                            # Web: scroll toward later content
revyl device scroll up --amount 300 --target "Results list"       # Web: scroll a nested region
revyl device select "Canada" --target "Country"                   # Web: exact native select option label
revyl device drag --start-x 100 --start-y 200 --end-x 300 --end-y 400  # Drag

# Utility
revyl device screenshot                        # Capture screenshot
revyl device screenshot --out screen.png       # Save to file
revyl device wait --duration-ms 1000           # Fixed wait on the session
revyl device pinch --x 200 --y 400 --scale 1.5 # Pinch / zoom gesture
revyl device clear-text --target "Search"      # Clear text in a field
revyl device back                              # Android back / provider back action
revyl device key --key ENTER                   # Phones: ENTER or BACKSPACE
revyl device key Escape                        # Web: dismiss a dialog
revyl device key "Shift+Tab"                    # Web: keyboard reachability
revyl device key "Meta+K"                       # Web: shortcut
revyl device shake                             # Trigger shake gesture
revyl device home                              # Return to home screen
revyl device open-app --app settings           # Open a system app
revyl device navigate --url https://example.com # Open URL or deep link
revyl device set-location --lat 37.77 --lon -122.42 # Set GPS location
revyl device download-file --url https://example.com # Download file to device
revyl device download-file --url https://example.com --filename report.pdf # Override destination filename
revyl device install --app-url <url>           # Install app from URL
revyl device launch                            # Launch the app this session installed
revyl device launch --bundle-id com.app.id     # Launch a specific installed app
revyl device kill-app                          # Kill the current installed app
revyl device push --title "Order shipped" --body "Track it now" # Build and deliver a simple notification
revyl device push apns payload.apns            # Deliver a raw APNs payload file

# Live step execution on an active session
revyl device instruction "Open Settings and tap Wi-Fi"           # Execute one instruction step
revyl device validation "Verify the Settings title is visible"   # Execute one validation step
# A false/missing validation verdict exits non-zero; --json stdout remains parseable with success=false
revyl device extract "Extract the visible account email" --variable-name account_email # Execute one extract step
revyl device code-execution script_123                          # Execute one code-execution step

# UI inspection
revyl device hierarchy                         # Dump UI hierarchy (Android XML / iOS JSON)

# Live observability (perf, network, logs) and post-session artifacts
revyl device perf                              # Stream CPU% / memory / FPS from active session
revyl device requests                          # Stream live network requests
revyl device logs                              # Stream captured device logs (logcat / OSLog)
revyl device logs --no-follow                  # Single snapshot of recent log lines
revyl device logs --json | jq -r '.items[]'    # JSON per poll; pipe to jq for scripting
revyl device report                            # View the report for the active session
revyl device report --session-id <uuid>        # View report by session ID
revyl device report --artifact perf --download # Download a session artifact (perf|network|trace)

revyl device start accepts --app-id together with --build-version-id when that build belongs to the app, and names the owning app when it doesn't. --app-url installs the file at that URL, so it can't be combined with either. revyl device type needs --target or --x/--y because the device focuses a field by tapping it; revyl device tap needs one as well.

Web is in alpha. To add it to your organization, email support@revyl.ai. Start a Web session with --platform web and the Web app's ID. Without --build-version-id or --url, it opens the newest Web build on --branch, or on the app's default branch:

revyl device start --platform web --app <app-id>                    # Newest Web build, desktop viewport
revyl device start --platform web --app <app-id> --url <https-url> --commit <sha>  # Create or reuse a URL build first
revyl device start --platform web --app <app-id> --viewport 390x844 # WIDTHxHEIGHT; default 1280x800

On Web, device key accepts Enter (Return), Backspace, Tab, Escape (Esc), Delete, Space, the four Arrow keys, Home, End, PageUp, and PageDown. Chords use distinct Ctrl, Alt, Meta, or Shift modifiers joined with +, followed by a named key or one ASCII letter/digit. Bare letters/digits and function keys are rejected. Phones retain Enter/Backspace and their Return/Delete aliases.

Web-only device scroll accepts up/down/left/right and --amount 1–16384 pixels (default 600), optionally at --target or paired --x/--y. Down/right move toward later content, unlike finger-motion swipe directions. With no target it uses the viewport center. Web-only device select requires a target or paired coordinates and matches the exact visible label of one enabled native HTML select option, not its value attribute. All accept -s/--session-id and --json; unsupported phone actions fail without dispatch.

See Live Observability for the full reference on perf, requests, logs, report --artifact, network, and --device-name presets.

For raw device sessions, URL-based app flows work in two modes:

  • revyl device start --app-url ... preinstalls the app before the session is ready.
  • To inject launch config at boot, use --launch-var <key-or-id> for stored environment values, --launch-arg-set <name-or-id> for stored iOS argument sets, and --launch-arg=<token> for non-secret inline iOS tokens.
  • revyl device install --app-url ... installs into an already running raw session.
  • revyl device download-file --url ... only downloads the file to device storage; it does not install the app.

Live Stream URL

Every active session streams the device screen over WebRTC. The --json output from device info and device list includes a whep_url field — a standard WHEP playback URL you can embed in your own platform or feed into any WHEP-compatible player.

# Get the raw stream URL for the active session
revyl device info --json | jq -r '.whep_url'

# List all sessions with their stream URLs
revyl device list --json | jq '.[].whep_url'

Push Notifications

revyl device push delivers a notification to the app running in a session's iOS simulator. It has two forms:

  • revyl device push --title "..." --body "..." --badge 1 builds a simple APNs payload from the flags and delivers it. At least one of --title, --body, or --badge is required.
  • revyl device push apns <file-path> delivers a raw APNs payload file, preserving its structure, custom keys, and values, for testing payload shapes the flag form cannot express (custom keys, mutable-content, content-available, etc).

The notification is delivered straight to the simulator running your app. It does not travel through Apple's push service, so there is no push token, no signed build, and no aps-environment entitlement required. It exercises your app's notification-handling code, the same path a real push triggers, rather than push delivery itself. iOS only; there is no Android equivalent.

A payload file must be a JSON object with a top-level aps key, and at most 4096 bytes total (Apple's own limit for a regular notification).

No notification will appear on screen until the app has been granted notification permission. Revyl does not grant it for you, so that your tests can cover the permission prompt and the denied case as a real user would meet them. Launch the app so it asks, accept the prompt, then send the notification.

Delivery and display are separate, which is why a push can report delivered while the screen stays empty: the payload reached the app, and iOS declined to draw anything because permission was missing. The app's own handlers still run, so userNotificationCenter(_:willPresent:) and the rest fire either way. Silent pushes (content-available with no alert) are unaffected, since they display nothing by design.

The target bundle ID is resolved in this order:

  1. A Simulator Target Bundle key inside the payload file, if present.
  2. The --bundle-id flag.
  3. The session's app under test.
cat payload.apns
{
  "aps": {
    "alert": {
      "title": "Order shipped",
      "body": "Your order is on its way"
    },
    "badge": 1,
    "sound": "default"
  },
  "Simulator Target Bundle": "com.example.myapp"
}
revyl device push apns payload.apns

When the payload has no Simulator Target Bundle key, pass --bundle-id to target an app other than the session's app under test:

revyl device push --title "Order shipped" --bundle-id com.example.myapp

Device Session Flags

-s <index|id>       # Target a session by local index or server-issued session ID
--session-id <uuid> # Target a session by server-issued ID (explicit spelling)
--json              # Output as JSON (useful for scripting)
--timeout <secs>    # Idle timeout for start (default: 300)

Every revyl device command that acts on a session accepts both spellings. -s takes either form: -s 2 is a local index from revyl device list, while -s 9f3c1a2b-... is the server-issued session_id returned by revyl device start --json.

REVYL_SESSION_ID scopes an entire shell to one session, so batch workers do not have to repeat the flag:

export REVYL_SESSION_ID=9f3c1a2b-4d5e-4f60-8a71-b2c3d4e5f607
revyl device screenshot --out screen.png

Precedence is --session-id, then -s, then REVYL_SESSION_ID. Setting --session-id and -s to different sessions is an error rather than a silent pick. A command that names no session uses, in order:

  1. The session last started or selected in this project directory by revyl device start, revyl dev, revyl device use, or revyl device attach, while it is live.
  2. Otherwise the most recently started live session started from this directory.
  3. Otherwise the account's only live session. When several are live and none was started or selected here, the command stops and lists each session with its -s <session-id> command; revyl device use <index> picks one for this directory.

The CLI prints one stderr line when this choice changes from the previous command in the directory, and --json output then adds defaults_applied.

When -s <index> no longer matches a session and exactly one session is live, device commands use that session and print Using session 1, ios 9f3c1a2b (index 0 is stale and this is the only active session). on stderr; --json output adds defaults_applied. The CLI refuses this fallback when it knows the stale index's own session ended while the live one was already running, since that session likely belongs to another caller, and while another session is still starting, since then no session is the only one. revyl device stop never falls back: a stale index is nothing to stop.

When a session has ended, the next "no session" error says which session ended, how long ago, and why when Revyl knows (for example an idle timeout and its length), then names the revyl device start command for the same platform.

REVYL_SESSION_ID applies only to commands that accept -s. The revyl dev commands pick their session from the dev context instead, so exporting it for a batch leaves them alone.

revyl device stop --all ignores REVYL_SESSION_ID, -s, and --session-id and requests a stop for every session revyl device list would show. It returns stopped_all: false with stop_requested: true and exit zero when all requests were accepted but cleanup is pending. A nonzero exit means at least one request failed. The batch session_settled and device_released fields are true only when confirmed for every targeted session. Per-session results preserve each identity, acceptance, settlement, release, and request error for mixed outcomes. Use a per-ID device stop when a batch worker should release only its own device.

revyl device stop is idempotent. With no running session, or with a -s <index> whose session already ended, it exits zero, prints one stderr line such as No active device session to stop; nothing to do., and --json adds already_stopped: true. A session ID your organization can't access still fails. Without -s, --session-id, REVYL_SESSION_ID, or --all, it stops a session only when exactly one is live; with several, it stops nothing and lists each one with its revyl device stop -s <session-id> command, because another agent using the same account may still be using the others. Sessions still starting (no reachable worker yet) are never reported as nothing to stop: a single stop names them with a stop-by-ID command, and --all lists them under unreachable_session_ids with stopped_all: false.

Local indexes live in a per-project file that every CLI process in the directory shares. Writes to it are serialized, so concurrent device start runs no longer drop each other's sessions, but an index still only means anything relative to that file. Session IDs are resolved straight from the backend in a single call and never touch the file, so prefer them for anything running more than one session at a time.

Parallel batches

Start each session, keep its session_id, and drive every later command by ID:

SESSION_A=$(revyl device start --platform ios --json | jq -r '.session_id')
SESSION_B=$(revyl device start --platform ios --json | jq -r '.session_id')

revyl device instruction "Sign in as the test user" -s "$SESSION_A" --json &
revyl device instruction "Open the settings tab" -s "$SESSION_B" --json &
wait

revyl device stop -s "$SESSION_A"
revyl device stop -s "$SESSION_B"

# Or stop every session. --all ignores REVYL_SESSION_ID.
# revyl device stop --all

Computer runs

Computer is in alpha. To add it to your organization, email support@revyl.ai.

revyl computer and revyl device start --computer are not listed in revyl --help or revyl device start --help. Computers must be enabled for your organization; otherwise revyl computer create is refused.

revyl computer targets an explicit computer ID ($COMPUTER_ID), independently of device sessions. A create whose computer was recorded but could not be started returns start_failed, and that computer reads as failed. Create also needs a free device slot (a computer takes one slot of the budget your iOS sessions use, and the app sessions inside it take none), your organization's compute credits (HTTP 402 when they are exhausted or unpaid), and a role other than Viewer. A computer keeps that slot until its cleanup is confirmed, so a computer that reads failed, owner_lost or stopped with release unknown still holds it. revyl computer stop on that computer frees the slot once its cleanup can be confirmed.

revyl computer create --os macos --json
revyl computer list
revyl computer get "$COMPUTER_ID"
revyl computer get "$COMPUTER_ID" --json
revyl computer exec "$COMPUTER_ID" --request-id "$REQUEST_ID" --timeout 30s -- git status
revyl computer files write "$COMPUTER_ID" workspace/file --from local.bin --json
revyl computer files read "$COMPUTER_ID" workspace/file > downloaded.bin
revyl computer files stat "$COMPUTER_ID" workspace/file --json
revyl computer process list "$COMPUTER_ID" --json
revyl computer process inspect "$COMPUTER_ID" "$HANDLE" --json
revyl computer process terminate "$COMPUTER_ID" "$HANDLE" --json
revyl computer operation get "$COMPUTER_ID" "$OPERATION_ID" --json
revyl computer report "$COMPUTER_ID" --format json
revyl computer report "$COMPUTER_ID" --format atif
revyl computer stop "$COMPUTER_ID" --json

list prints the organization's newest computers with their state (--limit 1..100, default 25), and get prints a computer's state and steps as a table unless --json is set. There is no image, lifetime, or working-directory option, and no active-computer setting. create --id supplies a proposed ID; otherwise it is generated once. Use exec --stdin FILE|- or files write --from FILE|- for bounded binary input. Process handles belong to the computer; they are not process IDs.

A computer runs for up to an hour, counted from create. Operation timeouts are 1ms–5m (default 30s). Exec accepts up to 64 arguments and 16,384 combined argv/stdin bytes. Exec keeps the first 1,048,576 bytes (1 MiB) of combined stdout/stderr; the command still runs to completion and reports its real exit code, and later output is dropped. Reads accept --max-bytes 1..1048576, writes accept at most 65,536 bytes, and process lists accept --max-items 1..64. Guest paths are relative and cannot contain traversal, empty/dot components, controls, backslashes or colons. Local inputs must be regular files or stdin; symlinks and special files are rejected. Local input acquisition and reads honor --timeout and command cancellation. Oversize input fails without truncation.

JSON preserves typed server results with padded URL-safe base64 binary fields. Without JSON, exec and file read emit exact bytes; metadata goes to stderr, including a note when exec output reached the 1 MiB limit. Reports and ATIF are summary-only server projections. get additionally shows each recorded exec step's command_display: the shell-quoted argv with the credential forms the backend recognizes replaced by <redacted> (anything else is shown as typed), at most 200 characters, never output or stdin. If the display cannot be read, get still succeeds without it. A timed-out or disconnected mutation is never automatically retried or redirected: use the printed operation ID with operation get, or inspect the computer with get/report. Reusing a request UUID with changed input conflicts. There is no cross-restart raw-result recovery.

revyl device start --platform ios --app "$APP" --computer "$COMPUTER_ID" requires computers to be enabled for your organization. It starts a Web app or iOS simulator session inside the running computer when its hosting capability lists that platform. It needs an uploaded build; the computer chooses the device and the app launches with its defaults, so --app-url, device selectors, launch variables, arguments and environment, run options and test-backed starts are refused. The app session has its own session ID, stream, report and revyl device stop. Stopping it leaves the computer running; stopping the computer ends it. Its report attributes only the actions Revyl's driver took, and commands run on the computer can also change the app.

Execution, evidence-write health and release certainty are separate. Exit 0 means a successful recorded operation/read or confirmed recorded stop. A command that exits non-zero prints Command exited with code N and exits with the guest's code (1 when that code is outside 1–255); 1 also means another failed operation or an input/API/transport error; 74 means successful execution with unrecorded evidence; 75 means pending; 124 means timed out; 125 means the outcome or cleanup is unknown, or create returned a failed computer. Reading a report can succeed even when historical execution failed. Stop retains evidence and never reports cleanup as confirmed when it isn't.

Shell Completion

# Bash (add to ~/.bashrc)
source <(revyl completion bash)

# Zsh (add to ~/.zshrc)
source <(revyl completion zsh)

# Fish
revyl completion fish | source

# PowerShell
revyl completion powershell | Out-String | Invoke-Expression

Diagnostics & Utilities

revyl doctor     # Check CLI health, connectivity, auth, sync status
revyl ping       # Test API connectivity and latency
revyl upgrade    # Check for and install CLI updates; leave agent skills unchanged
revyl --version  # Show CLI version (short format)
revyl version    # Show version, commit, and build date (--json for CI)
revyl docs       # Open Revyl documentation in browser
revyl schema     # Display CLI command schema (for integrations)
revyl mcp serve                         # Start MCP server for AI agent integration (legacy flat mode)
revyl mcp serve --profile dev           # 11 focused setup, dev-loop, and device tools
revyl mcp serve --profile core          # Broad device + test/build compatibility surface
revyl mcp serve --profile full          # Core plus workflow/module/script/tag/file/variable management
revyl mcp serve --profile core --experimental-workspace # Opt into the embedded Atlas and device workspace
revyl skill list                                      # List first-class skills
revyl skill list --all                                # Include optional and compatibility skills
revyl skill list --installed --json                   # Inspect project and global installations
revyl skill install                                  # Recommended skills; a terminal picker preselects them
revyl skill install --name revyl-cli-dev-loop --name revyl-cli-create --agent codex --yes
revyl skill install --name revyl-cli-atlas --cursor
revyl skill install --name revyl-cli-atlas-review --claude
revyl skill install --name revyl-cli-figma-review --agent claude-code --global --yes
revyl skill install --name revyl-cli-cross-platform-review --agent claude-code --global --yes
revyl skill install --name revyl-cli-auth-bypass --codex --global
revyl skill install --name revyl-cli-create --claude --copy
revyl skill update                                   # Update installed, unmodified project packages
revyl skill update revyl-cli-atlas                    # Update one installed package
revyl skill update --global --json                    # Update global packages; return JSON
revyl skill show --name revyl-cli-dev-loop             # Print only the root SKILL.md
revyl skill export --name revyl-cli-create -o FILE     # Export only the root SKILL.md
make device-prod-smoke-ios    # Local iOS branch smoke against production device relay
make device-prod-smoke-android # Local Android branch smoke against production device relay
make device-prod-sdk-smoke-ios # Local iOS SDK smoke against production
make device-prod-sdk-smoke-android # Local Android SDK smoke against production

mcp serve --experimental-workspace enables the experimental embedded Atlas and device workspace in compatible MCP Apps hosts. It is opt-in; omitting the flag preserves the existing profile behavior. Browser authentication remains separate from CLI authentication, and opening the workspace does not provision a device.

Without selectors, skill install uses the recommended skills that skill list shows. An interactive picker preselects them; an empty selection changes nothing. Noninteractive runs, --yes, and --json install them directly and name them on stderr; --json output adds "selection": "default". Repeated --name flags choose specific skills. --skill aliases --name; --all, --cli, and --mcp select the full catalog or a family.

Shared packages live in .agents/skills/ for direct Cursor and Codex discovery, with per-skill relative links in .claude/skills/. --global selects home directory scope; --copy uses independent legacy agent directories instead. Other agents reading the shared directory can discover the same skills. Conflicting legacy packages in any agent directory in the selected scope block shared storage, including retired auth-bypass platform aliases, even if that agent was not selected. These packages are preserved, not pruned. Symlinked shared directories must resolve inside the selected project or home directory. The installer copies complete packages and does not write AGENTS.md or inject Cursor rules. See MCP Setup for client setup and package details.

skill update refreshes only already-installed, unmodified Revyl-managed packages from this CLI version. Local edits and unmanaged packages are preserved and reported; no new skills are installed. Normal installation also leaves existing packages unchanged. skill install --force is an explicit destructive override that replaces the selected packages, including local edits. Binary upgrades leave skill packages unchanged.

skill list --installed scans both project and global locations; only skill update --global restricts an update to global scope. With --json, installation, installed listing, and updates return a skills array. Each entry includes name, the package directory as path, and status; failed or preserved entries can also include a reason.

Global Flags

These flags are available on all commands:

-C, --chdir <dir>  # Run as if started in this directory (the project dir containing .revyl/)
--debug            # Enable debug logging
--json             # Output as JSON (where supported)
--version          # Show CLI version and exit
--quiet / -q       # Suppress non-essential output

On this page