Authentication
Feedback
Send feedback to the Revyl team from an authenticated CLI session: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
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: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, 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 vianpx 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 latestor--build-version-idpicks 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
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:
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: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 viaapp_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 wantrevyl dev to run that branch’s build:
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.- Build the artifact with your normal local or CI build command.
- Upload it with
revyl build upload --file. - Run
revyl dev.
revyl dev builds on):
Builds and Dev Mode
Allrevyl 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.jsonnative 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.
App Management
An app is a named container for your uploaded builds (e.g. “My App Android”). Tests run against an app.--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
Uploading a Build
For GitHub PR matching from any CI runner, supply the base repository and full PR head SHA. These flags work with both--file and --url and override
detected SCM identity:
revyl build any time you want to refresh the binary on Revyl without re-running tests.
Common flow:
- Make sure your app is configured and credentials are available.
- Run:
--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.
- Use the uploaded build ID from the result to run against that exact binary:
revyl build listto verify uploads and inspect platform/app historyrevyl test run <test> --build-id <id>to run one exact build
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:--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:
~/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.
{{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 withmodule_import blocks. For examples, see Creating Tests.
Scripts
Manage code-execution scripts used bycode_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 viarevyl-file:// URIs.
.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: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>.
launch-var, launch-vars, launch-variable.
Workflow Management
--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.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 connectonly installs or verifies repository access. It never edits or publishes project configuration. From a coding agent,--no-wait --jsonprints the install link asinstall_urland returns at once instead of waiting;--no-openalso skips opening a browser. Confirm the install withgithub status. See the CLI’s GitHub status guide.github statusalso reports whether the current Git repository (from its GitHub origin) is granted to the installation, even before a.revyl/config.yamlexists, so a fresh checkout can tell whether it still has to be added to the GitHub App.--jsonprints one stable object on stdout; the keys and status values are documented in the CLI’s GitHub status guide.github setupguides the selected project through a managed-profile or finished-artifact CI-upload policy, writespr_review, and validates the project config. Under manual authority, it confirms publication and publishes the same complete aggregate asrevyl 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 pushuses an absent-or-hash precondition and is rejected when the project is managed from its designated config on the repository’s default branch.github setupprepares the designated file for commit in that mode. Runrevyl config validateand commit the file, or userevyl config push --forcefor 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.
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
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 Nbuilds a simple APNs payload from the flags and delivers it. At least one of--title,--body, or--badgeis 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).
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:
- A
Simulator Target Bundlekey inside the payload file, if present. - The
--bundle-idflag. - The session’s app under test.
Simulator Target Bundle key, pass --bundle-id to target an app other than the session’s app under test:
Device Session Flags
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:
--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:
- The session last started or selected in this project directory by
revyl device start,revyl dev,revyl device use, orrevyl device attach, while it is live. - Otherwise the most recently started live session started from this directory.
- 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.
--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 itssession_id, and drive every later command by ID:
Revyl Computer
Computer discovery and shell access use the separaterevyl-computer
executable. On macOS or Linux, install it with:
~/.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 usingREVYL_API_KEY from your environment, or reuse the saved
credentials from revyl auth login if you already have the Revyl CLI installed:
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:
{"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 usingREVYL_API_KEY from your environment, or reuse the saved
credentials from revyl auth login if you already have the Revyl CLI installed.
Then run:
Choosing a computer
The command accepts no target, a name, a serial, or an instance ID. Pick by what you already know: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:
/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 withrevyl-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 forlist, 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
&, andnohupfails in these shells with “can’t detach from console”. Run long jobs insidescreen, which is preinstalled, and reattach withscreen -rfrom a new shell, or start them withyour-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 withexit 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.