Skip to main content

Authentication

Feedback

Send feedback to the Revyl team from an authenticated CLI session:
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

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 setup is manual. Initialization leaves Web recipes out and points to Web recipes; it does not detect a Web framework, package manager, or preview host, or print host-specific CI workflows. Existing hand-written Web profiles are preserved by --detect. Use -y to accept only deterministic local choices without prompts and stop after config creation. Interactive initialization may continue through the existing optional authentication, app, skill, and first-build onboarding after the local file is written. If authentication is needed, the CLI prints an approval URL and short code that may be approved in any signed-in browser. After it writes the project config, authenticated setup can also continue into revyl github setup. Configure GitLab automation separately with revyl gitlab setup. The locally written project.id remains unverified and unpublished unless that separate server-backed flow succeeds.

Project Config

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

Advanced Run Flags

Explore

Build or refresh an Atlas map from the terminal:
Common run flags:
See Explore from the CLI for output, outcome, and CI semantics.

Dev Loop

revyl dev starts a local development loop backed by a cloud device session. Both Expo and bare React Native projects are supported, plus native Swift/iOS and Android projects via rebuild-based loops. Each revyl dev start creates a dev context — a named, worktree-local dev loop bound to one resolved profile/platform recipe. Use --context for same-repo concurrency; separate worktrees use the default context automatically. The context keeps that recipe for subsequent rebuilds.
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:
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:

Device-first flow

Start a plain device session first, then attach and run the dev loop on it:
When a context has an attached session, revyl dev --context <name> reuses it instead of provisioning a new device. Attached sessions are left running when the dev loop exits — use revyl device stop to end them.

Context management

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

Hot reload providers

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

New Branch Build Flow

Use this when you create a new Git branch and want revyl dev to run that branch’s build:
Both use the Revyl branch feature/new-login, which the first upload creates. To use one exact build:

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.
Optional explicit version label:
Plain device sessions (no hot reload — these are the base layer revyl dev builds on):

Builds and Dev Mode

All revyl build and revyl build upload commands push to an app container. For config-driven builds, that is the app_id on the resolved recipe. When Git metadata is available, each upload records its Git branch and commit. --branch <name> chooses the branch to upload to or run on. Without it, uploads go to your Git branch, and runs on Default branch try it first. See How a run picks its build. Each developer gets their own cloud device session, relay, and local dev server — builds are the only shared resource. When you need a new build (by project type):
  • Expo / React Native: Dev mode serves your JS/TS live from your local Metro via a Revyl relay. The binary is just a “dev client shell.” You only need a new build when native dependencies change (new native modules, Podfile changes, Gradle dependency changes, app.json native config).
  • Swift: Every code change requires a new build. The binary is the app.
  • Kotlin/Android (coming soon): Every code change requires a new build.
Team workflow commands:
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.
Manage the default branch and each branch’s default build without changing Git:
Add --pin to revyl build or revyl build upload to pin the branch to the new build; uploads don’t pin otherwise. Saved IDs survive renames; name-based scripts must use the new name. See Build branches and Build branches from the CLI.

Build Management

revyl build which shows the branch and build a run would use without starting one. It doesn’t read a test or workflow’s saved choice. See Check which build a run uses. Direct builds select one customer-named profile and its ios, android, or web recipe. There is no active/default profile. If an omitted value has one eligible choice, the CLI resolves it; otherwise interactive use prompts and non-interactive use fails with the available choices. Local and remote builds execute the same inherited recipe from the selected project root. revyl build runs in the cloud by default. See Build Configuration for prerequisites and flags. This does not change revyl dev or test/workflow --build defaults.

Remote Build Caches

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

Uploading a Build

For GitHub PR matching from any CI runner, supply the base repository and full PR head SHA. These flags work with both --file and --url and override detected SCM identity:
See Use your own CI for review policy and runner setup. Use revyl build any time you want to refresh the binary on Revyl without re-running tests. Common flow:
  1. Make sure your app is configured and credentials are available.
  2. Run:
When --version is omitted, the CLI uses the version label <git-branch-slug>-YYYYMMDD-HHMMSS (for example feature-new-login-20260227-153000), from the branch checked out locally. On a detached checkout, as in most CI jobs, the label is the timestamp alone. The label doesn’t choose the Revyl branch.
  1. Use the uploaded build ID from the result to run against that exact binary:
Or let Revyl build locally and upload before running the test:
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:
The backend downloads, validates, and stores the artifact server-side. The --url flag is mutually exclusive with --file.

Preview a just-uploaded build

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

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:
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.
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.
Test variables are distinct from Global Variables (org-wide {{global.name}}) and Global Launch Variables (org-wide reusable launch env vars).

Tags

Organize tests with tags for filtering and grouping.

Module Management

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

Scripts

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

File Management

Manage files (certificates, configs, images, media) in your organization’s file library. Tests reference uploaded files via revyl-file:// URIs.
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.

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:
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>.
Aliases: launch-var, launch-vars, launch-variable.

Workflow Management

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.
Stored retries apply while the workflow’s run config override is enabled; otherwise each test uses its own run config. config show reports retries, whether the override is enabled, and the saved concurrency cap; use workflow info for the full stored configuration. When retries are unset, config show labels the default of one attempt. Explicit values outside 1–5 are rejected without changing anything. Transient read and save failures are retried; if current settings cannot be read, nothing is changed so other stored settings are not overwritten. On workflow config set, --concurrency saves a cap for future runs across iOS and Android independently of the run-config override. Zero removes the cap; organization capacity always applies. Initialized runs retain their snapshot. JSON output includes nullable default_concurrency_limit. The CLI reads the saved value back before confirming a concurrency change (see Workflows). On workflow run, --concurrency overrides the saved default for that run only; omitting it inherits the default, and 0 removes the workflow cap for that run. Organization capacity still applies. This works with both waiting and --no-wait runs. The execution API accepts concurrency_limit_override with the same semantics; omission or null inherits the saved default.

Workflow Quarantine

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

GitHub PR Automation

Connect the Revyl GitHub App and configure PR automation for the project root selected by the nearest .revyl/config.yaml.
  • github connect only installs or verifies repository access. It never edits or publishes project configuration. From a coding agent, --no-wait --json prints the install link as install_url and returns at once instead of waiting; --no-open also skips opening a browser. Confirm the install with github status. See the CLI’s GitHub status guide.
  • github status also reports whether the current Git repository (from its GitHub origin) is granted to the installation, even before a .revyl/config.yaml exists, so a fresh checkout can tell whether it still has to be added to the GitHub App. --json prints one stable object on stdout; the keys and status values are documented in the CLI’s GitHub status guide.
  • github setup guides the selected project through a managed-profile or finished-artifact CI-upload policy, writes pr_review, and validates the project config. Under manual authority, it confirms publication and publishes the same complete aggregate as revyl config push. Under Git authority, it prepares the designated file for commit without publishing.
  • GitHub installation is repository-wide, while setup, publication, authority, and reconciliation are project-root-specific. Run setup separately from each intended root in a monorepo.
  • Ordinary config push uses an absent-or-hash precondition and is rejected when the project is managed from its designated config on the repository’s default branch. github setup prepares the designated file for commit in that mode. Run revyl config validate and commit the file, or use revyl config push --force for a deliberate one-time publication that keeps management from the repository’s default branch enabled.
  • A successful reconciliation from the repository’s default branch makes that project Git-owned. Fetch or validation failures preserve its current state. Removing the designated file returns only that project to manual authority while retaining its last accepted aggregate and automation; it does not delete the project.
Project roots are immutable. Manual projects can be deleted from the GitHub integration in the web app and recreated at the correct root. Git-owned projects must first return to manual authority through the designated-file flow above. Deletion preserves the repository connection, GitHub installation, repository report destination, historical results, and already-frozen review and build work, which may finish. Pending Cursor proof that has not launched fails closed because deletion removes its live authorization. Deleting the last project opens the dashboard’s form for a new project root. There is no CLI project-delete command; after creating a same-root replacement, use revyl -C <replacement-root> config pull to back up and replace a stale local file. Pull adopts a replacement automatically only when exactly one active project owns the old root; otherwise Revyl does not assume where the replacement belongs. The standalone github init and github push commands no longer exist; config push is the direct publication command and never scaffolds. See the GitHub integration for the pr_review schema and the exact server-stored configuration groups.

GitLab integration

GitLab requires an automation token with Developer access and api scope. Connection management uses the active Revyl organization. Token replacement uses the same connect command. Setup preserves Git-managed authority and checks authenticated webhook delivery independently of API access. A GitLab Maintainer or Owner installs the project webhook manually. Setup checks live repository access before prompting to select or create apps. Status preserves API, repository, and webhook facts when project configuration cannot be read, and reports the project-specific failure in project_error. See GitLab integration for the connection tutorial.

Device Management

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. On Web, device key accepts Enter (Return), Backspace, Tab, Escape (Esc), Delete, Space, the four Arrow keys, Home, End, PageUp, and PageDown. Chords use distinct Ctrl, Alt, Meta, or Shift modifiers joined with +, followed by a named key or one ASCII letter/digit. Bare letters/digits and function keys are rejected. Phones retain Enter/Backspace and their Return/Delete aliases. Web-only device scroll accepts up/down/left/right and --amount 1–16384 pixels (default 600), optionally at --target or paired --x/--y. Down/right move toward later content, unlike finger-motion swipe directions. With no target it uses the viewport center. Web-only device select requires a target or paired coordinates and matches the exact visible label of one enabled native HTML select option, not its value attribute. All accept -s/--session-id and --json; unsupported phone actions fail without dispatch. See Live Observability for the full reference on perf, requests, logs, report --artifact, network, and --device-name presets. For raw device sessions, URL-based app flows work in two modes:
  • revyl device start --app-url ... preinstalls the app before the session is ready.
  • To inject launch config at boot, use --launch-var <key-or-id> for stored environment values, --launch-arg-set <name-or-id> for stored iOS argument sets, and --launch-arg=<token> for non-secret inline iOS tokens.
  • revyl device install --app-url ... installs into an already running raw session.
  • revyl device download-file --url ... only downloads the file to device storage; it does not install the app.

Live Stream URL

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

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.
When the payload has no Simulator Target Bundle key, pass --bundle-id to target an app other than the session’s app under test:

Device Session Flags

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:
Precedence is --session-id, then -s, then REVYL_SESSION_ID. Setting --session-id and -s to different sessions is an error rather than a silent pick. A command that names no session uses, in order:
  1. The session last started or selected in this project directory by revyl device start, revyl dev, revyl device use, or revyl device attach, while it is live.
  2. Otherwise the most recently started live session started from this directory.
  3. Otherwise the account’s only live session. When several are live and none was started or selected here, the command stops and lists each session with its -s <session-id> command; revyl device use <index> picks one for this directory.
The CLI prints one stderr line when this choice changes from the previous command in the directory, and --json output then adds defaults_applied. When -s <index> no longer matches a session and exactly one session is live, device commands use that session and print Using session 1, ios 9f3c1a2b (index 0 is stale and this is the only active session). on stderr; --json output adds defaults_applied. The CLI refuses this fallback when it knows the stale index’s own session ended while the live one was already running, since that session likely belongs to another caller, and while another session is still starting, since then no session is the only one. revyl device stop never falls back: a stale index is nothing to stop. When a session has ended, the next “no session” error says which session ended, how long ago, and why when Revyl knows (for example an idle timeout and its length), then names the revyl device start command for the same platform. REVYL_SESSION_ID applies only to commands that accept -s. The revyl dev commands pick their session from the dev context instead, so exporting it for a batch leaves them alone. revyl device stop --all ignores REVYL_SESSION_ID, -s, and --session-id and requests a stop for every session revyl device list would show. It returns stopped_all: false with stop_requested: true and exit zero when all requests were accepted but cleanup is pending. A nonzero exit means at least one request failed. The batch session_settled and device_released fields are true only when confirmed for every targeted session. Per-session results preserve each identity, acceptance, settlement, release, and request error for mixed outcomes. Use a per-ID device stop when a batch worker should release only its own device. revyl device stop is idempotent. With no running session, or with a -s <index> whose session already ended, it exits zero, prints one stderr line such as No active device session to stop; nothing to do., and --json adds already_stopped: true. A session ID your organization can’t access still fails. Without -s, --session-id, REVYL_SESSION_ID, or --all, it stops a session only when exactly one is live; with several, it stops nothing and lists each one with its revyl device stop -s <session-id> command, because another agent using the same account may still be using the others. Sessions still starting (no reachable worker yet) are never reported as nothing to stop: a single stop names them with a stop-by-ID command, and --all lists them under unreachable_session_ids with stopped_all: false.
Local indexes live in a per-project file that every CLI process in the directory shares. Writes to it are serialized, so concurrent device start runs no longer drop each other’s sessions, but an index still only means anything relative to that file. Session IDs are resolved straight from the backend in a single call and never touch the file, so prefer them for anything running more than one session at a time.

Parallel batches

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

Revyl Computer

Computer discovery and shell access use the separate revyl-computer executable. On macOS or Linux, install it with:
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, 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:
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:
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.

How access works

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

Diagnostics & Utilities

mcp serve --experimental-workspace enables the experimental embedded Atlas and device workspace in compatible MCP Apps hosts. It is opt-in; omitting the flag preserves the existing profile behavior. Browser authentication remains separate from CLI authentication, and opening the workspace does not provision a device. Without selectors, skill install uses the recommended skills that skill list shows. An interactive picker preselects them; an empty selection changes nothing. Noninteractive runs, --yes, and --json install them directly and name them on stderr; --json output adds "selection": "default". Repeated --name flags choose specific skills. --skill aliases --name; --all, --cli, and --mcp select the full catalog or a family. Shared packages live in .agents/skills/ for direct Cursor and Codex discovery, with per-skill relative links in .claude/skills/. --global selects home directory scope; --copy uses independent legacy agent directories instead. Other agents reading the shared directory can discover the same skills. Conflicting legacy packages in any agent directory in the selected scope block shared storage, including retired auth-bypass platform aliases, even if that agent was not selected. These packages are preserved, not pruned. Symlinked shared directories must resolve inside the selected project or home directory. The installer copies complete packages and does not write AGENTS.md or inject Cursor rules. See MCP Setup for client setup and package details. skill update refreshes only already-installed, unmodified Revyl-managed packages from this CLI version. Local edits and unmanaged packages are preserved and reported; no new skills are installed. Normal installation also leaves existing packages unchanged. skill install --force is an explicit destructive override that replaces the selected packages, including local edits. Binary upgrades leave skill packages unchanged. skill list --installed scans both project and global locations; only skill update --global restricts an update to global scope. With --json, installation, installed listing, and updates return a skills array. Each entry includes name, the package directory as path, and status; failed or preserved entries can also include a reason.

Global Flags

These flags are available on all commands: