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

# Command reference

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

## Authentication

```bash
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:

```bash
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

```bash
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](mailto:support@revyl.ai).
Web setup is manual. Initialization leaves Web recipes out and points to
[Web recipes](/remote-builds/configuration#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.

```bash
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

```bash
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

```bash
--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:

```bash
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:

```bash
--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](/atlas/explore-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.

```bash
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:

```bash
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:

```bash
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:

```bash
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

```bash
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](/dev-loop).

### New Branch Build Flow

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

```bash
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:

```bash
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`.

```bash
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:

```bash
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):

```bash
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](/builds/branches#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:**

```bash
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.

```bash
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:

```bash
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](/builds/branches) and its
[CLI reference](/builds/branches#cli-reference).

## Build Management

<Note>
  Web is in alpha. To add it to your organization, email [support@revyl.ai](mailto:support@revyl.ai).
</Note>

```bash
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](/builds/branches#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](/remote-builds/configuration)
for prerequisites and flags. This does not change `revyl dev` or test/workflow
`--build` defaults.

### Remote Build Caches

```bash
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](/remote-builds/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:

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

See [Use your own CI](/integrations/github#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:

```bash
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.

3. Use the uploaded build ID from the result to run against that exact binary:

```bash
revyl test run login-flow --build-id <uploaded-build-id>
```

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

```bash
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:

```bash
# 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](/builds/branches) of the app's default branch each time, instead of always opening one upload.

```bash
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:

```bash
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](/tests/yaml).

```bash
# 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.

```bash
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](#global-variables) (org-wide `{{global.name}}`) and [Global Launch Configuration](#global-launch-configuration) (org-wide reusable launch env vars).

## Tags

Organize tests with tags for filtering and grouping.

```bash
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](/tests/yaml#reusable-modules).

```bash
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`.

```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.

```bash
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.

```bash
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:

```bash
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>`.

```bash
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

```bash
# 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.

```bash
# 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](/tests/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.

```bash
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`.

```bash
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](https://github.com/RevylAI/revyl-cli/blob/main/docs/github-status.md).
- `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](https://github.com/RevylAI/revyl-cli/blob/main/docs/github-status.md).
- `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](https://docs.revyl.com/integrations/github) for the
`pr_review` schema and the exact server-stored configuration groups.

## GitLab integration

```bash
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](/integrations/gitlab) for the connection tutorial.

## Device Management

```bash
# 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](mailto: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:

```bash
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](/devices/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](https://www.ietf.org/archive/id/draft-murillo-whep-03.html) playback URL you can embed in your own platform or feed into any WHEP-compatible player.

```bash
# 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.

```bash
cat payload.apns
```

```json
{
  "aps": {
    "alert": {
      "title": "Order shipped",
      "body": "Your order is on its way"
    },
    "badge": 1,
    "sound": "default"
  },
  "Simulator Target Bundle": "com.example.myapp"
}
```

```bash
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:

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

### Device Session Flags

```bash
-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:

```bash
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`.

<Note>
  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.
</Note>

#### Parallel batches

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

```bash
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

<Note>
  Computer is in alpha. To add it to your organization, email [support@revyl.ai](mailto:support@revyl.ai).
</Note>

`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.

```bash
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
# 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

```bash
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](/agents/mcp#skill-selection-storage-and-updates) 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:

```bash
-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
```
