Authentication
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.
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.
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
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
- 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
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
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 branch and wantrevyl dev to run that branch’s 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 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.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.Build Management
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.
Remote Build Caches
Uploading a Build
Userevyl 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 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.
- Use the uploaded binary by running tests against the latest upload:
revyl build listto verify uploads and inspect platform/app historyrevyl test run <test> --build-id <id>to pin a specific 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>
resolves the app’s latest build at open time instead of pinning 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.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.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 default-branch config.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 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.
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.
Device Management
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.
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, then the
active local session. Setting --session-id and -s to different sessions is
an error rather than a silent pick.
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 still stops every session revyl device list would show. Use a per-ID
device stop when a batch worker should release only its own device.
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: