Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ node_modules/
# files are unaffected by these ignore rules. The framework regenerates it
# idempotently for the same toolset.
**/trails/config/trailmaps/*/tools/trailblaze-client.d.ts
/trails/.trailblaze/
**/trails/.trailblaze/

# Daemon-emitted compiled-target output (`trailblaze check` writes per-target YAML
# bundles here from the workspace's `trailmaps/` definitions). Generated and lazy-rebundled
# at daemon init, never edited by hand.
/trails/config/dist/
**/trails/config/dist/

# Daemon local data — settings JSON, cache, in-progress logs. Created in the working
# tree when the daemon's `appDataDirectory` points at the repo (developer convenience
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@ curl -fsSL https://raw.githubusercontent.com/block/trailblaze/main/install.sh |
# List connected devices (Android emulator, iOS simulator, or web browser)
trailblaze device list

# Pin this shell to a device + target so subsequent calls inherit both from the env
eval $(trailblaze device connect android --target default)
# Pin this terminal to a device + target so subsequent calls inherit both.
# Trailblaze remembers per-terminal — other terminals stay independent.
trailblaze device connect android --target default

# Read the screen — returns a view hierarchy with refs (e.g. ab42) the agent can target
trailblaze snapshot
Expand All @@ -139,8 +140,8 @@ trailblaze snapshot
# platform-specific selector strategy. Every action takes a --step for self-heal.
trailblaze tool tap ref=ab42 -s "Tap sign in"

# Done — release the device + clear TRAILBLAZE_DEVICE from this shell
eval $(trailblaze device disconnect)
# Done — release the device and clear this terminal's pin
trailblaze device disconnect
```

Paste those into Claude Code, Codex, Cursor, Goose, or anything that can run bash and
Expand Down
44 changes: 22 additions & 22 deletions docs/CLI.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/generated/external-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Toolsets are declared in `trailmaps/<id>/toolsets/*.yaml`. They are pure YAML gr
| `android_primitives` | Yes | `android-ondevice-accessibility`, `android-ondevice-instrumentation` | 5 |
| `compose_core` | No | `compose` | 6 |
| `compose_verification` | No | `compose` | 2 |
| `core_interaction` | Yes | `android-ondevice-accessibility`, `android-ondevice-instrumentation`, `ios-host` | 15 |
| `core_interaction` | Yes | `android-ondevice-accessibility`, `android-ondevice-instrumentation`, `ios-host` | 16 |
| `memory` | No | `all drivers` | 8 |
| `meta` | Yes | `all drivers` | 2 |
| `mobile_primitives` | Yes | `android-ondevice-accessibility`, `android-ondevice-instrumentation`, `ios-host` | 4 |
Expand Down
40 changes: 40 additions & 0 deletions docs/generated/functions/custom/waitForChange.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!-- GENERATED BY trailblaze check. DO NOT EDIT. -->
<!-- Trailblaze framework tool reference -->
<!-- Regenerate with: ./gradlew :docs:generator:run -->

# `waitForChange`

Wait until the UI has settled after your action. Use this instead of a fixed-duration wait when
you've triggered an action (a new screen loads, content updates, a list scrolls) and want to block
until the UI is quiet again. Returns immediately if the UI is already settled when this runs.
Known limit: it cannot wait for a delayed async change that hasn't started yet — for that, use a
specific-element wait (e.g. assertVisible on the element you expect to appear).

## Source

- Kind: class-backed
- Class: `xyz.block.trailblaze.toolcalls.commands.WaitForChangeTrailblazeTool`

## Contract

- Visible to LLM: yes (`surface_to_llm: true`)
- Recordable: yes (`is_recordable: true`)
- Host-only: no (`requires_host: false`)

## Input schema

### Optional parameters

- `timeoutMs` — `Integer`
Maximum time to wait for the screen to change, in milliseconds. Default 8000.
- `quietWindowMs` — `Integer`
Time with no further UI events required to consider the screen settled, in milliseconds. Default 300.
- `requireChange` — `Boolean`
When true, timing out with no change is an error. When false, a timeout is treated as a generous settle and succeeds. Default true.

## Output

Returns: `string` (opaque text content)

Typed result schemas (`kind: query | action`, MCP `structuredContent`) are not yet carried by the resolved manifest — this section will gain detail when that lands.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import xyz.block.trailblaze.llm.config.LlmModelCostConfig
import xyz.block.trailblaze.llm.config.LlmProviderConfig
import xyz.block.trailblaze.llm.config.LlmProviderType
import java.io.File
import java.util.Locale

/**
* Generates documentation for all built-in LLM models that ship with Trailblaze.
Expand Down Expand Up @@ -143,6 +144,6 @@ class LlmConfigDocsGenerator(
}

private fun formatCost(cost: Double): String {
return if (cost == 0.0) "free" else "\$${String.format("%.2f", cost)}"
return if (cost == 0.0) "free" else "\$${String.format(Locale.US, "%.2f", cost)}"
}
}
66 changes: 51 additions & 15 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,32 +66,68 @@ cd trailblaze

## Connect a Device

List what's connected, then pin this shell to one of them:
List what's connected, then pin this terminal to one of them:

```bash
trailblaze device list

# Pin: exports TRAILBLAZE_DEVICE into the current shell so every follow-up call
# inherits the device + target without repeating the flags.
eval $(trailblaze device connect android --target default)
# Pin: remembers this terminal's device + target so every follow-up call
# inherits them without repeating the flags. Other terminals stay independent.
trailblaze device connect android --target default
```

The short form `android` works when only one Android device is connected; with
two or more, use the fully-qualified `android/<id>` form shown by `device list`
(same for `ios/<udid>`). `web` is always unambiguous.

You'll see Android emulators (`android/emulator-5554`), iOS simulators
(`ios/<simulator-id>`), and any web targets. After the pin, device-acting CLI
calls that take a `-d/--device` flag (`snapshot`, `tool`, `blaze`, `ask`, `verify`,
`session start`, `session stop`, `run`) read `TRAILBLAZE_DEVICE` from the env — no
`session start`, `session stop`, `run`) inherit the pinned device automatically — no
`-d <device>` flag needed. `session save` is implicit (saves the current session)
and doesn't take `-d`. For CI / scripts that prefer determinism, pass `-d <platform>`
(or `-d <platform>/<id>`) on each call as an override; explicit flags win over the
env. `mcp` accepts `--device` / `--target` at startup to pre-bind the MCP session
pin. `mcp` accepts `--device` / `--target` at startup to pre-bind the MCP session
(so the agent's first tool call already has a device); workspace and setup commands
(`config`, `app`, `device list`) don't take `-d`. `run` reads `TRAILBLAZE_DEVICE`
just like the action commands, but each replay spawns a fresh session rather than
reattaching to the pinned interactive one.
(`config`, `app`, `device list`) don't take `-d`. `run` inherits the pin just like
the action commands, but each replay spawns a fresh session rather than reattaching
to the pinned interactive one.

How the pin works under the hood: `trailblaze device connect` records this terminal's
shell PID alongside the bound device in `~/.trailblaze/shell-device-pins-<port>.json`.
Subsequent CLI calls from the same terminal look up that entry and use it as the
default. The pin survives daemon restarts. Open a second terminal and it gets its
own slot — pinning device A in one terminal doesn't leak into another. For scripts
and CI where each call is a fresh shell, pass `--device <id>` on every command
instead — the pin file is per-shell-PID and won't carry. (`TRAILBLAZE_DEVICE` is
also honored as a manual override, but `--device` is the recommended form.)

If your pinned device goes away (emulator killed, simulator shut down, USB cable
unplugged), the next CLI call fails with the standard `Device bind failed`
envelope and self-evicts the pin in the background, so the call *after* that
falls through to autodetect — if exactly one device is connected, it'll be used
silently; if zero or multiple, you get the appropriate error. Autodetect (the
single-device convenience) doesn't write a pin either: it just uses the one
connected device for that one call. Pinning is always an explicit
`device connect` action.

### Precedence between TRAILBLAZE_DEVICE and the file-pin

Resolution order, highest priority first:

1. Explicit `--device <id>` flag on the command.
2. `TRAILBLAZE_DEVICE` environment variable (manual override; mostly relevant
for CI scripts that explicitly export it).
3. This terminal's file-pin (written by `trailblaze device connect`).
4. Autodetect when exactly one device is connected.

So `export TRAILBLAZE_DEVICE=ios/<udid>` in your shell shadows the file-pin —
useful if you want a per-shell override without disturbing the pinned device for
the rest of your work. Unset the env var to fall back to the pin.

To swap target without disconnecting, use `trailblaze device rebind --target <new>`. To
release, `eval $(trailblaze device disconnect)` — the leading `eval $(...)` also unsets
`TRAILBLAZE_DEVICE` in the parent shell so the next session starts clean.
release, `trailblaze device disconnect` — clears this terminal's pin so the next
session starts clean.

## Drive the Device

Expand Down Expand Up @@ -131,16 +167,16 @@ this into the agent's session:

```
You have access to the `trailblaze` CLI. Use it to drive the connected device. First
pin the shell to a device + target so subsequent calls don't have to repeat the flags:
- `eval $(trailblaze device connect <platform> --target <app>)` — pin once at start
pin this terminal to a device + target so subsequent calls don't have to repeat the flags:
- `trailblaze device connect <platform> --target <app>` — pin once at start
- `trailblaze session start --title "<short_name>"` — start a tracked session
(captures video/logs, groups the steps for later save)
- `trailblaze snapshot` — see what's on screen (UI tree with refs)
- `trailblaze tool <name> <args> -s "<why>"` — take an action
- `trailblaze toolbox -d <platform>` — list available tools (toolbox still wants -d)
- `trailblaze toolbox` — list available tools (uses the pinned device automatically)
- When done, `trailblaze session save` to write the recording out as a `.trail.yaml`,
then `trailblaze session stop` to end the session. Optionally
`eval $(trailblaze device disconnect)` to release the device.
`trailblaze device disconnect` to release the device.
```

If your agent can run a shell command, it can drive a device. No SDK to install, no
Expand Down
8 changes: 6 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ this repo — no mockup. Click through for the full interactive report, or brows

## Quickstart

Install Trailblaze first ([Getting Started](getting_started.md) walks through it),
then:

```bash
trailblaze device list

# Pin this shell to a device + target so subsequent calls inherit both from the env
eval $(trailblaze device connect android --target default)
# Pin this terminal to a device — subsequent calls inherit it.
# Trailblaze remembers per-terminal, so other terminals stay independent.
trailblaze device connect android

# Read the screen — returns a UI tree with refs (e.g. ab42) your agent can target
trailblaze snapshot
Expand Down
2 changes: 1 addition & 1 deletion docs/mcp/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ When you want the MCP session to land on a specific device + target without the
}
```

Alternatively, drop the flags and inherit `TRAILBLAZE_DEVICE` from the shell that launches the MCP server (helpful when a CLI session pinned via `eval $(trailblaze device connect …)` is already in scope). Explicit `--device` / `--target` win over the env var.
Alternatively, drop the flags and inherit the device pin from the terminal that launches the MCP server (helpful when you've already run `trailblaze device connect …` in that terminal). Explicit `--device` / `--target` win over the pin. `TRAILBLAZE_DEVICE` is also honored as a manual override — useful for CI agents that set the env var explicitly.

### Connecting from Cursor

Expand Down
Loading
Loading