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

# Configuration Files

The CLI uses a `.revyl/` directory for project configuration:

```
your-app/
├── .revyl/
│   ├── config.yaml       # Project configuration
│   ├── tests/            # Local test definitions
│   │   └── login-flow.yaml
│   └── .gitignore        # Allowlist: only config.yaml and tests/ are committed
└── ...
```

## config.yaml

`revyl init` writes the local project config. A minimal project has a stable
project ID and may add session behavior, named build profiles, and PR review
policy.

Device commands do not require this file. `revyl device start` also accepts
legacy session settings without migration and does not validate unrelated
build or PR-review settings. See [Auth And Session Prep](/cli/device/auth-and-session-prep)
for discovery and session behavior. `revyl build`, `revyl config validate`,
and `revyl config push` still require the canonical format shown below.

```yaml theme={null}
project:
  id: "00000000-0000-4000-8000-000000000000"

session:
  idle_timeout_seconds: 1800
  before_script:
    script_path: "scripts/prepare-test-session.sh"
    timeout_seconds: 120
  auth_bypass:
    launch_vars: [REVYL_AUTH_BYPASS_ENABLED, REVYL_AUTH_BYPASS_TOKEN]
    deep_link: "myapp://revyl-auth?token=${REVYL_AUTH_BYPASS_TOKEN}"

build:
  framework: expo
  env:
    NODE_ENV: development
  secrets: [EXPO_TOKEN]
  caches:
    - key: node-modules
      paths: [node_modules]
  profiles:
    development:
      ios:
        app_id: "00000000-0000-4000-8000-000000000001"
        setup_commands: ["npm ci"]
        build_commands:
          - "npx eas build --platform ios --profile development --local --output build/app.tar.gz"
        output_path: "build/app.tar.gz"
      android:
        app_id: "00000000-0000-4000-8000-000000000002"
        setup_commands: ["npm ci"]
        build_commands:
          - "npx eas build --platform android --profile development --local --output build/app.apk"
        output_path: "build/app.apk"
```

See [Mint The Token Per Session](/builds/auth-bypass-deeplinks#mint-the-token-per-session)
for the full before-script contract, and
[Auth And Session Prep](/cli/device/auth-and-session-prep) for how those blocks
attach on `revyl device start`.

This example deliberately uses EAS.
Expo recipes that run `expo prebuild` followed by Xcode or Gradle do not need
Expo authentication solely because their framework is Expo.

### Section Reference

| Option | Type | Description |
| - | - | - |
| `project.id` | `string` | Required stable project UUID. New local projects generate one; `revyl init --project` can supply one. Possessing an ID is not authorization and does not prove that a server project exists. |
| `session.idle_timeout_seconds` | `int` | Optional idle timeout for sessions started from this project. |
| `session.before_script.script_path` | `string` | Optional executable path relative to the project root. It may use `../` only while remaining inside the Git worktree. |
| `session.before_script.timeout_seconds` | `int` | Optional bound for the before-script process. |
| `session.auth_bypass.launch_vars` | `string[]` | Names of launch variables attached at session start. Secret values are not stored in config. |
| `session.auth_bypass.deep_link` | `string` | Optional deep link opened after launch. Its `${VAR}` placeholders must resolve from one supported launch-variable source. |
| `build.framework` | `string` | Required when `build` is present: `ios`, `android`, `react_native`, `expo`, or `flutter`. |
| `build.env` | `object` | Non-secret environment defaults inherited by every recipe. Recipe values override matching keys. |
| `build.secrets` | `string[]` | Secret reference names inherited by every recipe. Values never belong in config. |
| `build.caches` | `object[]` | Cache defaults, each with a stable `key` and project-relative `paths`. |
| `build.profiles.<name>` | `object` | Customer-named profile containing an optional `ios` recipe, an optional `android` recipe, or both. No profile is stored as the active/default profile. |
| `build.profiles.<name>.<platform>.app_id` | `string` | Optional authored Revyl app association. It may remain absent until an execution or server-policy boundary requires and resolves an active organization-owned app. |
| `build.profiles.<name>.<platform>.setup_commands` | `string[]` | Optional ordered setup commands. |
| `build.profiles.<name>.<platform>.build_commands` | `string[]` | Required ordered build commands for a declared recipe. |
| `build.profiles.<name>.<platform>.output_path` | `string` | Optional project-relative artifact file, glob, or app-bundle path produced by the recipe. |
| `build.profiles.<name>.<platform>.image` | `string` | Optional remote build image. |
| `build.profiles.<name>.<platform>.timeout_seconds` | `int` | Optional bounded recipe timeout. |
| `build.profiles.<name>.<platform>.env` | `object` | Non-secret recipe environment merged over `build.env`. |
| `build.profiles.<name>.<platform>.secrets` | `string[]` | Recipe secret references merged with `build.secrets`. |
| `build.profiles.<name>.<platform>.caches` | `object[]` | Recipe caches with project-relative paths, merged with matching build-level cache keys. |
| `pr_review` | `object` | Optional PR review policy containing triggers, build selection, proof policy, workflow IDs, and strict build-check behavior. |
| `pr_review.review_triggers.paths` | `string[]` | Optional repository-relative globs. Empty defaults a nested project to `<project-root>/**`; the repository-root project remains repo-wide. Explicit globs replace that default. |
| `pr_review.review_triggers.labels` | `string[]` | Optional trimmed, non-empty, unique label names. Positive names are OR filters; prefix a name with `!` to exclude matching pull requests. |

Path globs are trimmed, use forward slashes, contain 1-1,024
characters, and have no leading or trailing slash, repeated slash, NUL, or
`.`/`..` path segment. Invalid values are rejected when the configuration is
validated or saved.

Label filters must already be trimmed and cannot be empty, `!` by
itself, or duplicated.

## Build Configuration

Profiles name complete build intents such as `development`, `pull-request`, or
`release`. Each profile owns its iOS and/or Android recipe. There is no authored
active profile: commands accept an explicit profile or resolve one only when
the choice is deterministic.

```yaml theme={null}
build:
  framework: react_native
  profiles:
    development:
      ios:
        setup_commands:
          - "bundle exec pod install --project-directory=ios"
        build_commands:
          - "xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyAppDev -sdk iphonesimulator -derivedDataPath build"
        output_path: "build/Build/Products/Debug-iphonesimulator/*.app"
      android:
        build_commands:
          - "cd android && ./gradlew assembleDebug"
        output_path: "android/app/build/outputs/apk/debug/*.apk"
```

Every declared recipe has `build_commands`. `setup_commands` is optional.
Initialization materializes detected framework-specific command, output, and
image defaults. An Xcode scheme selected during initialization is embedded
in the generated commands instead of being stored as a separate field.

`revyl build` runs in the cloud by default and requires `app_id` before
submission; it can discover the artifact when `output_path` is absent. Managed
PR recipes likewise require an active app. Local execution requires
`output_path`, authenticates, and uploads the result. Interactive local builds
can select or create an app after building; non-interactive builds require
`app_id` in advance. See [Build Configuration](/remote-builds/configuration).

Build-level `env`, `secrets`, and `caches` are defaults. Recipe environment
values override matching build-level keys; secret names are de-duplicated; and
caches with the same key merge their paths. A name cannot be both an effective
environment key and a secret reference.

### Artifact-First CI

If CI already produced the artifact, skip the config-driven build step and
upload it directly:

```bash theme={null}
revyl build upload --file build/App.app.zip --platform ios --app "$REVYL_IOS_APP_ID" --json
revyl build upload --url "$ARTIFACT_URL" --header "Authorization: Bearer $ARTIFACT_TOKEN" --app "$REVYL_IOS_APP_ID" --json
```

## Development Runtime

The project config has no `hotreload` block and stores no active profile or
platform. A `revyl dev` start applies explicit `--profile` and `--platform`
values first. Without `--profile`, it prefers one unique development-like
profile, where `dev` or `development` must be a complete token, and then infers
only a sole eligible profile or platform. Remaining ambiguity prompts in an
interactive terminal and fails with the valid choices in a non-interactive
session.

The framework identifies the runtime family. One resolved profile/platform
recipe supplies the app and build definition for the dev context, including
its initial local or remote build and subsequent rebuilds. Expo dev-client
scheme and prefix information belongs to the selected build artifact metadata
rather than the current checkout.

## Project Settings

```bash theme={null}
revyl config path                    # Show the selected config file location
revyl config show                    # Display the local project configuration
revyl config validate                # Validate locally; attempt connected validation with authentication and a supported GitHub origin
revyl config migrate --check         # Summarize a legacy conversion without writing
revyl config migrate                 # Confirm, back up, and atomically replace a legacy file
revyl config migrate --write         # Back up and replace without confirmation
revyl config push                    # Publish a manual-authority project configuration
revyl config push --force            # Publish once while retaining default-branch authority
revyl config pull                    # Pull server configuration; back up a divergent existing config
revyl config authorize-cursor-proof  # Authorize Cursor proof runs for this project
```

Project commands select the nearest `.revyl/config.yaml` between the current
directory and the active Git worktree root. Use `-C <directory>` to select from
another directory. `config show` expects the project config format and directs legacy
files to migration.

Migration only mutates local project files. A successfully prepared legacy
migration uses one concise clean-or-lossy human summary; a write creates an
exact-byte backup. JSON `--check` contains the complete migration proposal and
migration ledger.
The default mode asks for confirmation; `--check` creates no backup and writes
nothing; and `--write` skips confirmation. A write creates an exact-byte
config backup, creates
only missing conflict-free `.revyl/tests/<alias>.yaml` files, and then
atomically replaces the config. An existing alias file is preserved
byte-for-byte only when it points to the same remote test; any other conflict
stops migration because the destination is unsafe to write.

When authenticated, migration may reuse or interactively select a project from
the verified repository, resolve enabled PR-build app names to exact platform
app IDs, and resolve legacy PR workflow names through exact, case-sensitive
read-only lookup. Missing, duplicate, or inaccessible workflow names are
omitted and reported, including the implicit `smoke` workflow used by the
legacy `smoke_every_pr` preset. The retired top-level workflow alias cache is
reported and removed. An enabled PR build whose app or framework cannot be resolved
is omitted and prominently reported as lossy rather than silently removed. Inspect
JSON `--check` before writing; afterward, compare the reported backup or ask a coding
agent to reconcile omissions. Migration never
creates or attaches a server project, pulls or publishes configuration, or otherwise
mutates server state.

Edit `.revyl/config.yaml` directly for ordinary changes and run
`revyl config validate`; the config commands have no separate
command for mutating individual YAML keys.

### Configuration publication and retrieval

`revyl config push` publishes typed configuration meaning, not raw YAML, and
`revyl config pull` retrieves the server configuration. Neither operation
is part of test sync. Push requires a readable canonical project config,
authentication, a stable `project.id`, a supported GitHub origin, and verified
access to the exact repository. A failed prerequisite
names 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 to
recover. Pull accepts the same project config, or—when no config exists—uses
the verified GitHub repository and nearest configured project root to create
the complete config. Pull never converts or overwrites a legacy or
invalid file. When an existing config differs from the server, pull
creates an exact-byte backup, atomically replaces the file, and reports the
backup path without an interactive diff or confirmation. If the local project
was deleted and exactly one active replacement owns that same root, pull backs
up and replaces the stale file without trusting its retired ID. Revyl stores these
top-level groups from the project configuration:

* `project.id`
* `session`, including its timeout, before-script declaration, and auth-bypass references
* `build.framework` and every named build profile, including each iOS or Android recipe, app association when present, commands, output, caches, non-secret environment values, secret references, image, and timeout
* optional `pr_review`, including triggers, managed-profile or external-CI build selection, proof policy, workflow references, and strict build-check behavior

Comments, YAML formatting, local tests, the repository remote URL, invocation
directory, and any transient profile/platform selection are not stored as part
of the project configuration aggregate. Secret references are stored by name;
secret values are not sent in this contract.

The server independently verifies the authenticated GitHub repository and
repository-relative project root, recompiles the supplied typed contract, and
checks referenced resources. Manual publication atomically replaces the whole
aggregate using the state observed immediately before the write. Under
default-branch authority, ordinary manual publication is rejected: use
`revyl config validate`, commit the designated file, and let reconciliation
publish it. `revyl config push --force` deliberately replaces the aggregate
once while preserving default-branch authority and its reconciliation
position. The next newer accepted default-branch configuration can replace
that override. `--force` is a no-op when authority is already manual.

`revyl config authorize-cursor-proof` is separate from configuration publication. It
records server-owned human authority only after live Cursor repository access
is confirmed; it does not change authored configuration, hashes, or authority,
and works for both manual and default-branch projects.

### Project roots and deletion

Project roots and config paths are immutable identity. To correct a root:

1. Delete the manual project from **Settings → Integrations → GitHub**.
2. Choose **Add project** and enter the correct root.
3. Run `revyl -C <replacement-root> config pull` to create or safely replace its local config.

A Git-managed project cannot be deleted directly. Remove its designated config
file from the default branch and wait for successful reconciliation to return it
to manual authority, then delete it explicitly. Removing the file alone preserves
the project, its configuration, and its automation.

Deletion archives only that project. It preserves the repository connection,
GitHub installation, repository report destination, frozen and historical results,
and already-frozen review and build work, which may finish. Pending Cursor proof
that has not launched is the exception: deletion removes its live authorization,
so it fails closed. Deleting the last project shows **No projects configured**
and **Add project**. After navigation, the repository leaves the configured list
but remains selectable through **Add Repo**; no root project is created
automatically.
There is no CLI project-delete command. A stale local file remains usable for
local-only commands. Connected project-configuration surfaces—`config validate`,
`config push`, `config pull`, GitHub setup/status, Cursor authorization, and TUI
publication—identify the retired project and explain how to create or pull the
replacement instead of retrying its ID. Automatic pull recovery is limited to one
exact same-root replacement; otherwise Revyl does not assume the old root.

## Environment Variable Overrides

These environment variables configure CLI defaults, credentials, and integration behavior:

| Variable | Description |
| - | - |
| `REVYL_API_KEY` | API key for authentication; normally takes precedence over stored credentials, except after an explicit local `revyl auth login` |
| `REVYL_BINARY` | Executable path or command name used by SDK and MCP subprocess delegation; resolved again for each delegated command |
| `REVYL_PROJECT_DIR` | Override the project directory for MCP server |

## .gitignore Defaults

The `.revyl/.gitignore` generated by `revyl init` uses an allowlist approach:
everything inside `.revyl/` is ignored by default except for the shared project
files listed below.

**Committed (shared with your team):**

* `.revyl/config.yaml` — project configuration
* `.revyl/tests/**` — local test definitions
* `.revyl/.gitignore` — the ignore rules themselves

Everything else under `.revyl/` (device sessions, MCP artifacts, PID files, etc.)
is local runtime state and stays out of version control automatically.

## Test Aliases

Test aliases are managed as files in `.revyl/tests/`. Each file maps to a
remote test via `_meta.remote_id`. Explicit `revyl config migrate` externalizes
conflict-free legacy `tests:` entries as stub files; ordinary config and test
commands never migrate them implicitly.


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