Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
5162e95
feat: add Revyl cloud device integration
anamhira47 Feb 21, 2026
e688865
Replace RevylWorkerClient with CLI-based RevylCliClient
anamhira47 Mar 19, 2026
07a0d14
feat: Revyl integration parity — runYaml, blaze mode, network toggle,…
anamhira47 Mar 23, 2026
676e4a4
fix: resolve compile errors in RevylMcpBridge blaze mode stub
anamhira47 Mar 23, 2026
a7864b9
feat: add RevylBlazeSupport factory for host-level blaze wiring
anamhira47 Mar 23, 2026
2c999b8
feat: surface action coordinates + screen dimensions for Trailblaze i…
anamhira47 Mar 24, 2026
e8feb0c
feat: add Revyl as selectable driver in desktop GUI
anamhira47 Mar 24, 2026
efc17eb
feat: Revyl integration — device model fix, native steps, instruction…
anamhira47 Mar 25, 2026
be9de86
chore: remove unused RevylDevicePreset enum
anamhira47 Mar 25, 2026
a4d912f
feat: add REVYL_API_KEY to settings UI and handle missing key gracefully
anamhira47 Mar 25, 2026
d8bcf34
fix: always pass -s session index to prevent cross-contamination
anamhira47 Mar 25, 2026
5bb8d3d
feat: surface Revyl viewer URL in session report header
anamhira47 Mar 25, 2026
a1b4bbb
Address PR #110 review comments from handstandsam
anamhira47 Mar 25, 2026
97433fc
fix: pass target to type/clear-text CLI commands to prevent rejection
anamhira47 Mar 25, 2026
89c7631
feat: revyl CLI integration improvements + doubleTap tool
anamhira47 Mar 25, 2026
db0db2a
Merge remote-tracking branch 'upstream/main'
anamhira47 Apr 8, 2026
c500c53
docs: polish Revyl review nits
anamhira47 Apr 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,9 @@ However:
- **Not all Maestro features are exposed** - only the subset needed for Trailblaze tools
- **The driver is replaceable** - the architecture supports alternative implementations

**Revyl integration:** A standalone agent, `RevylTrailblazeAgent`, implements `TrailblazeAgent` directly (no Maestro)
and talks to Revyl cloud devices via HTTP. See [Revyl integration](revyl-integration.md) for details.

### Driver Interface

Drivers must implement command execution:
Expand Down
158 changes: 158 additions & 0 deletions docs/revyl-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
title: Revyl Cloud Device Integration
---

# Revyl Cloud Device Integration

Trailblaze can use [Revyl](https://revyl.ai) cloud devices instead of local ADB or Maestro. This lets you run the same AI-powered tests against managed Android and iOS devices without a local device or emulator.

## Overview

The Revyl integration provides:

- **RevylCliClient** (`trailblaze-revyl`) – Shells out to the `revyl` CLI binary for all device interactions.
- **RevylTrailblazeAgent** (`trailblaze-host`) – Maps every Trailblaze tool to a `revyl device` CLI command.
- **RevylNativeToolSet** (`trailblaze-revyl`) – Revyl-specific LLM tools (tap, type, swipe, assert) using natural language targeting and AI-powered visual grounding.

Core data classes and the CLI client live in the `trailblaze-revyl` module. Host-level wiring (agent, blaze support) lives in `trailblaze-host/.../host/revyl/`.

## Prerequisites

1. Install the `revyl` CLI binary on your PATH:

```bash
curl -fsSL https://raw.githubusercontent.com/RevylAI/revyl-cli/main/scripts/install.sh | sh
# Or with Homebrew:
brew install RevylAI/tap/revyl
```

2. Set the `REVYL_API_KEY` environment variable (or configure it in Settings > Environment Variables in the desktop app).

**Optional overrides:**

- `REVYL_BINARY` – Path to a specific `revyl` binary (skips PATH lookup).

## Architecture

```mermaid
flowchart LR
subgraph trailblaze["Trailblaze"]
LLM["LLM Agent"]
AGENT["RevylTrailblazeAgent"]
CLI["RevylCliClient"]
end
subgraph cli["revyl CLI"]
BIN["revyl device *"]
end
subgraph revyl["Revyl Cloud"]
PROXY["Backend Proxy"]
WORKER["Worker"]
end
DEVICE["Cloud Device"]
LLM --> AGENT
AGENT --> CLI
CLI -->|"ProcessBuilder"| BIN
BIN --> PROXY
PROXY --> WORKER
WORKER --> DEVICE
```

1. The LLM calls Trailblaze tools (tap, inputText, swipe, etc.).
2. **RevylTrailblazeAgent** dispatches each tool to **RevylCliClient**.
3. **RevylCliClient** runs the corresponding `revyl device` command via `ProcessBuilder` and parses the JSON output.
4. The `revyl` CLI handles auth, backend proxy routing, and AI-powered target grounding transparently.
5. The cloud device executes the action and returns results.

## Quick start

```kotlin
// Prerequisites: revyl CLI on PATH + REVYL_API_KEY set
val client = RevylCliClient()

// Start a cloud device with an app installed
val session = client.startSession(
platform = "android",
appUrl = "https://example.com/my-app.apk",
)
println("Viewer: ${session.viewerUrl}")

// Interact using natural language targets
client.tapTarget("Sign In button")
client.typeText("user@example.com", target = "email field")
client.tapTarget("Log In")

// Screenshot
client.screenshot("after-login.png")

// Clean up
client.stopSession()
```

## Supported operations

All 12 Trailblaze tools are fully implemented:

| Trailblaze tool | CLI command |
|-----------------|-------------|
| tap (coordinates) | `revyl device tap --x N --y N` |
| tap (grounded) | `revyl device tap --target "..."` |
| inputText | `revyl device type --text "..." [--target "..."]` |
| swipe | `revyl device swipe --direction <dir>` |
| longPress | `revyl device long-press --target "..."` |
| launchApp | `revyl device launch --bundle-id <id>` |
| installApp | `revyl device install --app-url <url>` |
| eraseText | `revyl device clear-text` |
| pressBack | `revyl device back` |
| pressKey | `revyl device key --key ENTER` |
| openUrl | `revyl device navigate --url "..."` |
| screenshot | `revyl device screenshot --out <path>` |

## Native instruction and validation steps

Revyl devices can optionally use Revyl's own agent pipeline for natural-language
prompt steps instead of routing through Trailblaze's LLM agent. This is
controlled by the `useRevylNativeSteps` flag on `TrailblazeConfig` (default
`false` -- LLM pipeline is used until native step reporting with screenshots
is implemented).

| YAML key | PromptStep type | CLI command | What happens |
|----------|-----------------|-------------|--------------|
| `- step:` | `DirectionStep` | `revyl device instruction "..."` | Revyl's worker agent plans and executes the action |
| `- verify:` | `VerificationStep` | `revyl device validation "..."` | Revyl's worker agent asserts against the current screen |

When `useRevylNativeSteps` is `true`:

- Each `- step:` prompt is sent directly to `revyl device instruction` in a
single round-trip. Revyl's agent handles grounding and execution natively.
- Each `- verify:` prompt is sent to `revyl device validation`, which performs
a visual assertion without needing a view hierarchy.
- Explicit tool steps (e.g. `- tapOn:`, `- inputText:`) are **not** affected
and still route through `RevylTrailblazeAgent` -> individual CLI commands.

When `useRevylNativeSteps` is `false`:

- All prompt steps go through Trailblaze's LLM agent pipeline, which decides
which tools to call (tap, type, swipe, etc.) and dispatches them via
`RevylTrailblazeAgent`. This is the original pre-flag behavior.

```yaml
# Example trail using both step types
- prompts:
- step: Open the Search tab
- step: Type "beetle" in the search field
- verify: Search results are visible
- step: Tap the first result
- verify: Product detail page is shown
```

## Limitations

- No local ADB or Maestro; all device interaction goes through Revyl cloud devices.
- View hierarchy from Revyl is minimal (screenshot-based AI grounding is used instead).
- Requires network access to the Revyl backend.

## See also

- [Architecture](architecture.md) – Revyl as an alternative to HostMaestroTrailblazeAgent.
- [Revyl CLI](https://github.com/RevylAI/revyl-cli) – Command-line tool for devices and tests.
- [Revyl docs](https://docs.revyl.ai) – Full CLI and SDK documentation.
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ include(
":trailblaze-models",
":trailblaze-compose",
":trailblaze-playwright",
":trailblaze-revyl",
":trailblaze-ui",
":trailblaze-report",
":trailblaze-server",
Expand Down
1 change: 1 addition & 0 deletions trailblaze-desktop/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
implementation(project(":trailblaze-common"))
implementation(project(":trailblaze-compose"))
implementation(project(":trailblaze-host"))
implementation(project(":trailblaze-revyl"))
implementation(project(":trailblaze-models"))
implementation(project(":trailblaze-report"))
implementation(project(":trailblaze-server"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package xyz.block.trailblaze.desktop

import xyz.block.trailblaze.compose.driver.tools.ComposeToolSet
import xyz.block.trailblaze.revyl.tools.RevylNativeToolSet
import xyz.block.trailblaze.host.rules.TrailblazeHostDynamicLlmClientProvider
import xyz.block.trailblaze.host.rules.TrailblazeHostDynamicLlmTokenProvider
import xyz.block.trailblaze.host.yaml.DesktopYamlRunner
Expand Down Expand Up @@ -31,6 +32,7 @@ class OpenSourceTrailblazeDesktopApp : TrailblazeDesktopApp(
TrailblazeJsonInstance = TrailblazeJson.createTrailblazeJsonInstance(
allToolClasses = TrailblazeToolSet.AllBuiltInTrailblazeToolsForSerializationByToolName
+ ComposeToolSet.toolClassesByToolName
+ RevylNativeToolSet.RevylLlmToolSet.toolClasses.associateBy { it.toolName() }
+ desktopAppConfig.availableAppTargets.flatMap { it.getAllCustomToolClassesForSerialization() }
.associateBy { it.toolName() },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import xyz.block.trailblaze.llm.providers.OpenRouterTrailblazeLlmModelList
import xyz.block.trailblaze.mcp.utils.JvmLLMProvidersUtil
import xyz.block.trailblaze.model.TrailblazeHostAppTarget
import xyz.block.trailblaze.report.utils.LogsRepo
import xyz.block.trailblaze.revyl.RevylCliClient
import xyz.block.trailblaze.ui.TrailblazeDesktopUtil
import xyz.block.trailblaze.ui.TrailblazeSettingsRepo
import xyz.block.trailblaze.ui.models.AppIconProvider
Expand All @@ -35,6 +36,8 @@ class OpenSourceTrailblazeDesktopAppConfig : TrailblazeDesktopAppConfig(
TrailblazeDriverType.IOS_HOST,
TrailblazeDriverType.PLAYWRIGHT_NATIVE,
TrailblazeDriverType.PLAYWRIGHT_ELECTRON,
TrailblazeDriverType.REVYL_ANDROID,
TrailblazeDriverType.REVYL_IOS,
)

// Start with no platforms enabled by default - user must explicitly enable them
Expand Down Expand Up @@ -76,7 +79,7 @@ class OpenSourceTrailblazeDesktopAppConfig : TrailblazeDesktopAppConfig(
ALL_MODEL_LISTS.mapNotNull { trailblazeLlmModelList ->
val trailblazeLlmProvider = trailblazeLlmModelList.provider
JvmLLMProvidersUtil.getEnvironmentVariableKeyForLlmProvider(trailblazeLlmProvider)
}
} + RevylCliClient.REVYL_API_KEY_ENV

override fun getCurrentlyAvailableLlmModelLists(): Set<TrailblazeLlmModelList> {
val modelLists = JvmLLMProvidersUtil.getAvailableTrailblazeLlmProviderModelLists(ALL_MODEL_LISTS)
Expand Down
1 change: 1 addition & 0 deletions trailblaze-host/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ dependencies {
api(libs.slf4j.api)

implementation(project(":trailblaze-common"))
implementation(project(":trailblaze-revyl"))
implementation(project(":trailblaze-compose"))
implementation(libs.compose.ui.test.junit4)
implementation(project(":trailblaze-playwright"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,9 @@ open class RunCommand : Callable<Int> {
BasePlaywrightElectronTest.ELECTRON_BUILT_IN_TOOL_CLASSES
TrailblazeDriverType.COMPOSE ->
ComposeToolSet.LlmToolSet.toolClasses
TrailblazeDriverType.REVYL_ANDROID,
TrailblazeDriverType.REVYL_IOS ->
xyz.block.trailblaze.revyl.tools.RevylNativeToolSet.RevylLlmToolSet.toolClasses
else -> emptySet()
}

Expand Down
Loading