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

## Authentication

```bash theme={null}
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 theme={null}
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 theme={null}
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.

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
the same GitHub PR-automation flow as `revyl github 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 theme={null}
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 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 without leaving default-branch 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 origin is available, it also verifies
GitHub 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.

`config push` requires a readable local project config, authentication, and
verified access to its GitHub 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 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 keeps default-branch management and its
reconciliation position intact, so a later default-branch configuration change
can replace the forced aggregate. Under manual authority, `--force` does not
change behavior.

When no local config exists, `config pull` verifies the GitHub 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 and
default-branch configuration authority.

## 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 theme={null}
revyl test run login-flow                 # Run against last uploaded build
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 theme={null}
--retries 3       # Retry on failure (1-5, default 1)
--build-id <id>   # Run against a specific build version
--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 theme={null}
revyl explore run "My App"                # Explore the latest build and 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 theme={null}
--build-id <id>                # Pin an uploaded build
--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](/cli/explore) 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 theme={null}
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
* resolves the latest build for your current git branch from the selected recipe's app, then installs it
  * if no branch-matching build exists, it falls back to the latest available build and prints a warning
* uses the same resolved recipe for a forced local build, remote build, 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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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](/develop/dev-loop-overview).

### New Branch Build Flow

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

```bash theme={null}
git checkout -b feature/new-login
revyl build --profile development --platform ios
revyl dev --profile development --platform ios
```

If you need to pin exactly one build:

```bash theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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 includes its branch and commit.

When you run `revyl dev`, the CLI scans the selected recipe's app for a build
matching your current git branch. If found, it uses that build. If not, it
falls back to the latest available build and prints a warning.

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** (coming soon): Every code change requires a new build.

**Team workflow commands:**

```bash theme={null}
revyl build list --branch HEAD               # Does my branch have a build?
revyl build --profile development --platform ios  # Build/upload tagged with current branch
revyl dev --profile development --platform ios    # Use that recipe and branch build
revyl dev --build-version-id <id>            # Pin a specific build
```

**Tip**: For Expo/React Native, multiple developers can use the same 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 theme={null}
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
```

## Build Management

```bash theme={null}
revyl build                              # Build from config in the cloud
revyl build --profile development --platform android  # Build a selected recipe in the cloud
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 --file ./app.apk --app <id> --preview --json  # Also print a click-to-launch preview URL
revyl build list                         # List uploaded builds
revyl build list --app <app-id>          # List builds for a specific app
```

Direct builds select one customer-named profile and its `ios` or `android`
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 theme={null}
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 theme={null}
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 theme={null}
cd your-app
revyl build --profile development --platform ios
```

When `--version` is omitted, the CLI defaults to a branch-aware version label:
`<branch-slug>-<timestamp>` (for example `feature-new-login-20260227-153000`).
In detached-head/non-git contexts it falls back to timestamp-only.

3. Use the uploaded binary by running tests against the latest upload:

```bash theme={null}
revyl test run login-flow
```

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

```bash theme={null}
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 pin a specific 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 theme={null}
# 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>`
resolves the app's latest build at open time instead of pinning one upload.

```bash theme={null}
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 theme={null}
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>  # Pin a specific 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](/cli/tests/creating-tests).

```bash theme={null}
# 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                      # 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 theme={null}
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 Variables](#global-launch-variables) (org-wide reusable launch env vars).

## Tags

Organize tests with tags for filtering and grouping.

```bash theme={null}
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](/cli/tests/creating-tests#reusable-modules).

```bash theme={null}
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 theme={null}
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 theme={null}
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

Shared variables referenced via `{{global.name}}`. Local test variables with the same name take precedence. Use `--secret` for credentials or tokens; secret values are encrypted and shown as `********` in list/get output.

```bash theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
# 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 --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 theme={null}
# 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 clear my-workflow

# Run config (parallelism, retries)
revyl workflow config show my-workflow
revyl workflow config set my-workflow --parallel 3 --retries 2
```

### 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 theme={null}
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 theme={null}
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 default-branch config.
  `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 default-branch management
  enabled.
* A successful default-branch reconciliation 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 shows **No
projects configured** and **Add project**. 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.ai/integrations/github) for the
`pr_review` schema and the exact server-stored configuration groups.

## Device Management

```bash theme={null}
# 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 all active sessions
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 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                   # ENTER or BACKSPACE
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.

See [Live Observability](/cli/device/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 theme={null}
# 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 N` 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 theme={null}
cat payload.apns
```

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

```bash theme={null}
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 theme={null}
revyl device push --title "Order shipped" --bundle-id com.example.myapp
```

### Device Session Flags

```bash theme={null}
-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 theme={null}
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 theme={null}
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
```

## Revyl Computer

Computer discovery and shell access use the separate `revyl-computer`
executable. On macOS or Linux, install it with:

```bash theme={null}
curl -fsSL https://github.com/RevylAI/revyl-cli/releases/latest/download/install-computer.sh | sh
```

The installer detects your platform, verifies the release's SHA-256 checksum,
and installs to `~/.revyl/bin` without sudo. Restart your shell or run the PATH
command it prints. Use `REVYL_COMPUTER_INSTALL_DIR` to choose another absolute
directory, `REVYL_COMPUTER_VERSION` to pin a release tag, or
`REVYL_COMPUTER_NO_MODIFY_PATH=1` to leave your shell profile unchanged.

On Windows, download `revyl-computer-windows-<arch>.exe` from the
[CLI releases](https://github.com/RevylAI/revyl-cli/releases), verify it against
the release's `checksums.txt`, and rename it to `revyl-computer.exe` in a
directory on your `PATH`. The existing `revyl` package-manager installs do not
install this separate binary.

### List assigned computers

Authenticate using `REVYL_API_KEY` from your environment, or reuse the saved
credentials from `revyl auth login` if you already have the Revyl CLI installed:

```bash theme={null}
revyl-computer list
revyl-computer list --status online
revyl-computer list --name Revyl-Mac-Studio
revyl-computer list --json
```

Lists the computers assigned to the organization in your current credentials.
The human table is sorted by name and shows NAME, STATUS, LAST SEEN, and
INSTANCE ID. `LAST SEEN` is relative, such as `3m ago`, so offline or stale
machines are visible without opening a shell. A summary shows total, online,
and offline counts.

Use `--status online|offline` to select a status and `--name <prefix>` for a
case-insensitive name prefix. The CLI fetches the full organization inventory
once and applies these filters locally; the filters can be combined. The
command takes no organization selector and does not open a shell or change a
computer. An online status does not guarantee shell access; the checks
described below still apply.

The human-readable table and summary are written to stderr. `--json` writes
only the complete typed response to stdout, with no banners. Filters apply in
JSON mode, but every matching computer retains all schema fields:

```json theme={null}
{
  "computers": [
    {
      "instance_id": "mi-0123456789abcdef0",
      "name": "Revyl-Mac-Studio-T65T47F91M",
      "status": "online",
      "last_seen_at": "2026-09-23T18:42:00Z"
    }
  ]
}
```

When no computers match, the command succeeds with an informative message, or
`{"computers":[]}` in JSON mode. `--quiet` suppresses the human-readable output
but leaves JSON output intact. The API returns computers in stable instance ID
order; the CLI table sorts them by name. An unavailable inventory is an error,
not an empty list. A computer whose assignment Revyl is still updating can be
missing from the list briefly and reappears once the update completes. The list
can be up to about 30 seconds old; see
[Using computers at scale](#using-computers-at-scale).

### How access works

```mermaid theme={null}
flowchart LR
  CLI["revyl-computer CLI"] -->|"list or name/serial lookup"| LIST["GET /computers"]
  CLI -->|"ssh without a target"| DEFAULT["POST /mac/shell-sessions"]
  CLI -->|"ssh with an instance ID"| TARGET["POST /computers/{instance_id}/shell-sessions"]
  LIST --> AUTH["Authenticate and check organization membership"]
  DEFAULT --> AUTH
  TARGET --> AUTH
  AUTH -->|"list or default"| INVENTORY["Org-scoped SSM inventory + paged tagging API"]
  INVENTORY -->|"name, status, last ping"| CLI
  INVENTORY -->|"lowest eligible online ID"| RECHECK["Recheck ownership, provisioning, and online status"]
  AUTH -->|"explicit ID"| RECHECK
  RECHECK -->|"eligible"| SESSION["SSM StartSession"]
  SESSION -->|"short-lived session details"| CLI
```

Name and serial targets resolve against the full organization inventory in the
CLI and then use the same instance-ID endpoint as a direct target. The backend
rechecks admission immediately before opening the shell; inventory status is
only a hint, not a guarantee of access.

### Open a shell

Authenticate using `REVYL_API_KEY` from your environment, or reuse the saved
credentials from `revyl auth login` if you already have the Revyl CLI installed.
Then run:

```bash theme={null}
revyl-computer --version
revyl-computer ssh
revyl-computer ssh Revyl-Mac-Studio-T65T47F91M
revyl-computer ssh T65T47F91M
revyl-computer ssh mi-build-host
revyl-computer ssh name:mi-0123456789abcdef1
revyl-computer ssh mi-0123456789abcdef0
```

Revyl brokers the connection, so no SSH client, AWS Session Manager plugin,
SSH key, or cloud credentials are required. The organization comes only from
your Revyl credentials.

#### Choosing a computer

The command accepts no target, a name, a serial, or an instance ID. Pick by what
you already know:

```bash theme={null}
# No target: take the eligible online computer with the lowest instance ID.
revyl-computer ssh
```

```bash theme={null}
# Name: copy the NAME column from 'revyl-computer list'. Matching ignores case,
# so 'revyl-mac-studio-t65t47f91m' resolves the same computer.
revyl-computer ssh Revyl-Mac-Studio-T65T47F91M
```

```bash theme={null}
# Serial: the trailing 10-12 character uppercase letter-or-digit segment of the
# name. Use it when the short hardware serial is what you have written down.
revyl-computer ssh T65T47F91M
```

```bash theme={null}
# Names beginning with 'mi-' work normally; use 'name:' if the entire name
# looks like an instance ID, so the CLI resolves it as a name instead.
revyl-computer ssh mi-build-host
revyl-computer ssh name:mi-0123456789abcdef1
```

```bash theme={null}
# Instance ID: required when two computers share a name, and the only form that
# skips the inventory fetch.
revyl-computer ssh mi-0123456789abcdef0
```

Resolution fetches the full organization inventory once, then opens the resolved
instance ID. A name matches the validated name exactly, without case
sensitivity. A serial is accepted when it is that name's final hyphen segment
and is 10-12 uppercase letters or digits. Managed-instance IDs must match `mi-`
followed by 17 lowercase hexadecimal characters and go directly to the open
request without an inventory fetch.
Names that merely start with `mi-` are resolved from inventory. An exact
instance-ID-shaped target is treated as an ID unless prefixed with `name:`;
that prefix forces an exact name lookup, including when the name resembles an
ID belonging to another computer.

Each case fails loudly instead of connecting somewhere else:

| Target | What happens |
| - | - |
| No target | Fails if no eligible computer is online. |
| Unknown name or serial | Reports that the computer was not found in your organization. |
| Ambiguous name or serial | Reports the ambiguity and asks for the instance ID from `revyl-computer list`. |

Automatic selection and a direct instance-ID target show the selected instance
ID in the connection banner; a name or serial target also shows the locally
resolved name.

Shell access requires a human user with a current Owner, Admin, Member, or
Internal role in that organization. Revyl verifies current membership when
opening or explicitly terminating a session, including when an API-key
response omits the role. Viewers, removed members, disabled or locked users,
and service identities cannot use these operations. If membership cannot be
verified, the request fails rather than granting access. This check does not
revoke a shell that is already connected.

While Revyl is busy, the CLI retries opening the session automatically for up
to about a minute; see [Pace shell openings](#pace-shell-openings). Once Revyl
opens the session, connection setup, including the shell handshake, has a
30-second deadline. The welcome message appears only after the shell is ready;
this deadline does not limit an established interactive shell. Failed or canceled startup closes
the connection and requests session termination through Revyl, with a separate
10-second cleanup deadline. If cleanup cannot be confirmed, the command reports
that the remote session may remain until its idle timeout.

Use it to prepare the machine for your builds and tests: install a toolchain,
add a certificate, inspect a build that failed, or check what a test left
behind.

The machine is shared by your whole organization and its disk persists between
sessions, so anything you install or change stays there for your teammates and
for later runs. Treat it like a shared build machine rather than a scratch
container.

You have passwordless `sudo`. Homebrew lives under `/opt/homebrew`, owned by
the `revyl-admin` account, and isn't on your `PATH`, so install packages such
as `tmux` through that account:

```bash theme={null}
sudo -u revyl-admin -H /opt/homebrew/bin/brew install <package>
```

You can also create your own admin user for your tools. Don't change the
ownership of `/opt/homebrew`, and don't modify or delete the `revyl-admin`
account: Revyl's maintenance of the computer depends on both.

End the session with `exit` or Ctrl-D. Sessions also close on their own after
an hour of inactivity, and after 24 hours in any case. Closing your terminal
disconnects the client; the idle timeout remains the fallback when explicit
termination cannot be confirmed.

An online machine is not sufficient for access: Revyl must also verify its
customer assignment, approved configuration, and completed provisioning.
If the command reports that no machine is available, the machine may be
unassigned, offline, still being provisioned, or unable to pass these checks.
Contact support for help.

### Using computers at scale

These practices keep automation reliable when your organization runs many
computers and many shells at once.

#### Target computers by instance ID

Fetch the inventory once with `revyl-computer list --json`, keep it in your
automation, and pass instance IDs to `revyl-computer ssh`. An instance ID opens
the shell directly, while a name or serial first fetches the whole inventory, so
resolving names for every operation adds a list request to each one. Refresh
your copy when computers are added or removed, or when opening a shell reports
that a computer is unavailable.

Don't use `revyl-computer ssh` without a target to spread work across
computers. Every shell opened without a target lands on the same computer: the
online one with the lowest instance ID.

#### Pace shell openings

Opening shells is rate-limited across Revyl. Open shells at a steady pace with
random jitter rather than starting many at the same moment.

When Revyl is busy, it asks the client to retry after a short delay. The CLI
handles this automatically for `list`, name or serial lookup, and `ssh`: it
waits for the delay Revyl suggests plus up to one second of random jitter,
prints one `Revyl is busy ..., retrying` line to stderr (suppressed by
`--quiet`), and keeps retrying for up to about a minute. If Revyl is still
busy after that, the command fails with an error saying so; retry later. Other
failures, such as an unavailable computer or a denied request, are not retried.
If you call the API directly, treat HTTP 429 the same way and wait for its
`Retry-After` header before retrying.

#### Keep shells long-lived, but expect them to end

Reusing a shell for many commands is cheaper than opening a new shell for each
one, but every shell eventually ends:

* A shell with no activity closes after 60 minutes, and every shell closes
  after 24 hours.
* A dropped network connection, a computer restart, or maintenance also ends
  it.
* Processes started in a shell stop when the shell ends, including ones
  backgrounded with a plain `&`, and `nohup` fails in these shells with "can't
  detach from console". Run long jobs inside `screen`, which is preinstalled,
  and reattach with `screen -r` from a new shell, or start them with
  `your-command & disown`. Nothing keeps running across a computer restart.
* Reconnect with exponential backoff and random jitter when a shell ends, and
  stagger when your sessions start, so they don't all reach the 24-hour limit
  and reconnect at the same moment.

#### Check status before targeting a computer

`list` includes offline computers. Check STATUS and LAST SEEN (`status` and
`last_seen_at` in JSON) before targeting a computer. The list can be up to about
30 seconds old, so a computer removed from your organization can still appear
for that long, but it can't be opened: Revyl rechecks access every time a shell
opens.

#### Expect brief maintenance

A computer can briefly refuse new shells while Revyl maintains it. Opening a
shell on it then reports that the computer is unavailable. Retry later with
backoff, or use another computer.

#### Exit shells when you're done

End each shell with `exit` or Ctrl-D so the session closes cleanly. Killing the
client process instead can leave the session open on the computer until its
60-minute idle timeout.

## Shell Completion

```bash theme={null}
# 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 theme={null}
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 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
```

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](/cli/mcp-setup#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 theme={null}
--debug       # Enable debug logging
--json        # Output as JSON (where supported)
--version     # Show CLI version and exit
--quiet / -q  # Suppress non-essential output
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.