Vibeview
Pricing

AI Agent Control

Let your AI coding agent verify a change by driving a real, running copy of your app on a VibeView cloud device — installing the build, tapping through the UI, reading back what’s on screen, and reporting whether it actually worked. This is the same live-development loop described in Live Development, extended so an agent (not just you) can drive the session from the command line.

Install and authenticate

npm install -g vibeview
vibeview login

vibeview login opens your browser to complete authentication and saves a token locally. In CI or any non-interactive environment, set VIBEVIEW_API_TOKEN instead — every command accepts it as an alternative to being logged in.

Making it available to your agent

Installing the CLI doesn’t by itself tell your coding agent that VibeView exists. One command sets that up:

vibeview agent-setup

It does three things:

  1. Installs the agent skill into ~/.claude/skills/vibeview-agent/, where Claude Code discovers it automatically in every project. The skill describes the whole workflow — build requirements, the verify loop, cleanup — so the agent knows how to use VibeView, not just that it exists. Pass --project to install into the current project’s .claude/skills/ instead (useful when teammates should get it too, since that directory can be committed).
  2. Offers to register the MCP server with Claude Code (claude mcp add vibeview -- vibeview mcp), so the tools are visible in every session. Pass --mcp to register without asking, or --no-mcp to skip. Other MCP clients are configured per client — see Using it with an MCP client below.
  3. Suggests a line for your project’s CLAUDE.md (or AGENTS.md if you use other agents) telling the agent to verify UI changes on a live device. This is the strongest per-project nudge: the skill teaches the agent how, the instruction line tells it when.

Do you need both the skill and MCP? Usually not. If your agent can run shell commands (Claude Code in a terminal, for example), the skill alone is enough — it drives the CLI directly, and skipping MCP keeps tool definitions out of your agent’s context. Register the MCP server when the agent can’t shell out: Claude Desktop, Copilot agent mode, restricted environments where named tools are allowed but arbitrary commands aren’t. For those clients MCP isn’t an extra — it’s the only way in.

Agents other than Claude Code can be pointed at the skill file directly — it’s plain instructions any coding agent can follow — or given the MCP server via their own client configuration.

One honest note: even fully set up, an agent asked only to “implement X” won’t always verify on a device unprompted. The setup above raises the odds considerably, but saying “implement X and verify it on the device” is what makes it reliable.

The dev loop

  1. Upload a debug build of your app (only needed again after a native change — see Preparing Your Build):
    vibeview upload-app ./path/to/app-debug.apk
  2. Start Metro in your React Native project (yarn start or equivalent).
  3. Start a session in the background:
    vibeview dev --detach --json
    This prints a session_ready event with a session_id your agent uses to target every command that follows (--session <id>). session_ready fires once the session actually accepts commands — the device is up and the app is installed — so the first ui-tree can follow it immediately. The event also includes the URL of a page where you can watch and interact with the session — detached sessions never open a browser themselves, so open that URL yourself if you want to watch along.
  4. Drive the session with the command verbs below — always start with ui-tree to see what’s on screen and get element references (@e5, etc.) to act on. If the app is still on its splash or loading screen, the tree can come back empty or near-empty — that’s the app booting, not a broken session. Wait a few seconds and fetch it again.
  5. When you’re done, stop the session:
    vibeview dev-stop

Every command also works against a session you already started from the VibeView dashboard — pass its session ID with --session <id> instead of starting a new one with dev --detach.

Watching along — and taking over

Every session has a live, fully interactive page (the url in session_ready, or page_url from the MCP dev_start tool). The bundled skill instructs agents to share it with you the moment a session starts, so you can watch the agent work in real time — and drive the device yourself whenever you want. The agent’s commands and your input coexist; keep it to a single browser tab per session.

That takeover is also how agents get unstuck. The skill tells them to stop and ask you whenever a screen needs something only a human should provide — signing in with real credentials, a 2FA code, a CAPTCHA, or a risky confirmation — rather than guessing. Do your part on the session page, tell the agent you’re done, and it re-reads the screen (verifying the blocker is actually gone) before continuing.

A detached session records its state in a .vibeview/ directory in your project root (that’s how later commands find it without --session). Add .vibeview/ to your project’s .gitignore — it’s local machine state, not something to commit.

Commands

Every command below accepts --session <id> (targets a specific session; falls back to the current project’s dev --detach session if omitted) and --json (prints the raw result as one JSON line instead of human-readable text).

CommandPurpose
ui-treeFetch the current screen’s UI elements, each with a reference (@e5) to act on.
logsRead recent app logs from the device — JS console output, native errors, and crash messages.
screenshotCapture a screenshot of the current screen and save it to disk.
tap <target>Tap an element by reference (e.g. @e5) or raw coordinates (e.g. 100,200).
long-press <ref>Long-press (touch and hold) an element.
swipe <direction>Swipe up, down, left, or right.
scroll <direction>Scroll the current view up, down, left, or right.
scroll-to <text>Scroll until an element matching that text is visible, stopping at the end of the list.
drag <from> <to>Drag between two points, each an element reference or raw coordinates.
alert <get|accept|dismiss>Inspect or respond to a visible iOS system alert.
type <text>Type text into the currently focused input field.
clear-textClear the currently focused input field.
press <button>Press a device button or system gesture (home, back, d-pad, etc).
open-url <url>Open a deep link or URL inside the app.
waitWait a moment before the next action.
find <text>Find an element by its text, optionally constrained to be near/above/below another element.
tap-focused <ref>TV only: move focus to an element and select it in one step.
focus <ref>TV only: move focus to an element without selecting it.

After every action, the response tells you what changed on screen — a full element list, the specific differences, or a note that nothing changed — so an agent always knows the current state before deciding what to do next. That means a normal loop is ui-tree once at the start, then act, act, act: you don’t need to re-fetch the tree between steps.

Use the tree and the screenshot for different things

The UI tree describes structure: which elements exist, what text they carry, and where they sit. It carries no colour, contrast, or stacking information, so a screen can pass every check the tree can express and still be visibly broken — text rendered in a colour that vanishes into the background, or an element drawn underneath another one.

Use the tree to find elements and confirm structure. Take a screenshot whenever a change affects how something looks, and inspect the image itself.

Over MCP, screenshot writes the file and returns its path; pass inline: true to receive the image itself. An agent that can’t open files on the machine running the server needs inline: true to see anything at all — it costs context, so it’s worth reserving for the checks where appearance actually matters.

A worked example

Adding a heading to a screen and confirming it renders, with hot reload already running:

vibeview ui-tree                      # what's on screen now?
# ...edit your component in your editor; Fast Refresh pushes it in ~1s...
vibeview ui-tree                      # the new heading appears in the tree
vibeview screenshot --out ./check.png # ...and actually looks right
vibeview tap @e7                      # keep going: tap through the flow

No rebuild and no re-upload — a JavaScript or asset change reaches the device through Metro. You only rebuild when native dependencies change.

Element references go stale as soon as the screen changes. Using a stale one is refused and the action does not run; because nothing happened, the response reports the screen as unchanged rather than returning a fresh element list, so run ui-tree again to pick up live references before retrying.

A few commands are worth knowing about specifically:

  • scroll-to is the right way to reach something off-screen — it keeps scrolling until an element whose text or accessibility label matches is visible, and gives up when the content stops moving, instead of you guessing how many scroll calls are needed. It works out which way to scroll on its own for ordinary vertical lists; pass --direction for horizontal rows of items.
  • drag is the only command for a precise two-point gesture — dragging a slider to a value, reordering a list by dragging an item, or panning a map. swipe and scroll only take a direction and can’t express those. Each end can be an element reference or coordinates, so you can mix them. Options let you slow the drag down for precision, press and hold before the drag starts (for reorder gestures), and hold at the destination before releasing.
  • alert handles iOS system alerts such as permission prompts, which sit above your app and block it. Use alert get to read the message and the exact button labels, then alert accept or alert dismiss — optionally with --button to choose a specific label.
  • logs is how an agent finds out why something broke. It returns the app’s own output — JS console lines, native errors, crash messages — scoped to the app under test, not the whole device. When the app crashes or freezes, the screen alone can’t explain it; the log usually can. Each response ends with a cursor value: pass it back as --since to receive only lines that arrived after your previous read, so acting and then checking logs --since <cursor> shows exactly what that action logged. --tail caps how many lines come back (default 100, newest kept).

For Apple TV and Android TV apps, start the session with --platform tvos or --platform androidtv and navigate with the d-pad via press (dpad_up, dpad_down, dpad_left, dpad_right, dpad_center).

Using it with an MCP client

The same commands are also available as MCP tools, so any MCP-compatible agent can call them directly instead of shelling out to the CLI. MCP is an open standard, so this works with any model and any client that speaks it — not just one vendor.

The server runs over stdio: the command is vibeview and the argument is mcp. Every client expresses that slightly differently.

Claude Code (command line):

claude mcp add vibeview -- vibeview mcp

Cursor.cursor/mcp.json in your project (or the global one in ~/.cursor/):

{
  "mcpServers": {
    "vibeview": {
      "command": "vibeview",
      "args": ["mcp"]
    }
  }
}

Claude Desktopclaude_desktop_config.json, same mcpServers shape as above.

VS Code (GitHub Copilot agent mode).vscode/mcp.json:

{
  "servers": {
    "vibeview": {
      "command": "vibeview",
      "args": ["mcp"]
    }
  }
}

Anything else — point the client at command vibeview with args ["mcp"]. If the client can’t find the binary, use the absolute path from which vibeview (a global npm install usually resolves without it).

Authentication comes from the same place as the CLI: run vibeview login once, or set VIBEVIEW_API_TOKEN in the environment your client launches the server with. If your client supports per-server environment variables, add it there:

{
  "mcpServers": {
    "vibeview": {
      "command": "vibeview",
      "args": ["mcp"],
      "env": { "VIBEVIEW_API_TOKEN": "your-token" }
    }
  }
}

Tool names are the command names above with hyphens replaced by underscores (ui_tree, tap, scroll_to, long_press, and so on), and each takes its arguments by name plus an optional session_id.

Three tools exist only over MCP, covering the steps a CLI user would do with ordinary shell commands, so an agent with no shell access can still run the whole loop:

ToolPurpose
upload_appUpload a debug build by path and get back the app ID to start a session with — the equivalent of vibeview upload-app.
dev_startStart a session that stays open for the lifetime of the connection — the equivalent of vibeview dev --detach.
dev_stopStop the session dev_start started.

Note that dev_start is stateful: once it succeeds, every later tool call that doesn’t name a session_id targets that session. Only one can run at a time, and it keeps billing streaming minutes until dev_stop is called or the connection ends.

Roku channels are the exception: their reload loop is driven from the terminal, so dev_start does not accept roku — start those sessions with vibeview dev --platform roku and point your agent at the running session instead.

Driving an embed session

A session someone started from an embedded demo can be driven with the same commands. You target the session that visitor is already using: pass its id with --session <id> (or session_id over MCP) and every command lands on the device in front of them. Agent Control never starts a second session for this, and no additional device is used.

Getting the session id

The embedded page announces it to your page. Listen for the session:started event — it carries the id — and hand that to your agent:

window.addEventListener('message', (event) => {
  if (event.data?.source !== 'vibeview-embed') return;
  if (event.data.type === 'session:started') {
    const sessionId = event.data.sessionId; // pass this to Agent Control
  }
});

If the visitor lands in a queue you’ll get session:queued first — that one carries only their queue position, not an id, because no device has been handed out yet. Wait for session:started. See Page events for the full list.

Keep your credential on your own server. The embed page never receives one, and your page shouldn’t hold one either — it only needs the session id.

Two things have to be true first:

  1. Agent Control is allowed on that embed key. It’s a per-key setting under Settings → Embeds, off by default, and only an organization admin can turn it on — see Allow Agent Control.
  2. You’re calling as a Developer or Admin in the organization that owns the key. Viewer credentials are refused, and a token never carries more authority than the member it belongs to.

People viewing your embed in a browser never gain any of this. There’s no command surface on the embedded page — driving the session is something your organization does with its own credentials, from your own tooling — and turning the setting on changes nothing about what a visitor can do.

If either condition isn’t met the command is refused outright and nothing runs on the device. The same applies the moment the key stops being active — turning the setting off, disabling the key, or revoking it all end agent access at the very next command, even mid-session.

What’s available on an embed session

An embed deliberately keeps visitors inside your app, and Agent Control holds that same line. Everything you need to read and drive a screen is there; the two things that would take a visitor out of your app are not.

CommandOn an embed session
ui-tree, screenshot, logsRead the screen and the app’s own output, exactly as on any other session.
tap, long-press, drag, swipe, scroll, scroll-toAvailable, by element reference or by coordinates.
type, clear-textAvailable.
alertAvailable — respond to a system alert sitting over your app.
find, waitAvailable.
tap-focused, focusAvailable on TV sessions.
press <button>Limited to back and the d-pad (dpad_up, dpad_down, dpad_left, dpad_right, dpad_center). home, lock, and siri are refused: they would drop the visitor out of your app, blank their screen, or hand them to a system assistant.
open-urlNot available. Opening a deep link or URL is the one command whose whole purpose is leaving the app.

A refused command says what isn’t allowed and what is, so an agent can correct itself instead of retrying blindly — and the refusal happens before anything reaches the device.

The visitor is using the screen too

This is the one place where you aren’t alone on the device. A visitor can tap between two of your commands, so the screen you read a moment ago may not be the screen you’re acting on, and an element reference you were holding can be stale by the time you use it.

When that happens the command comes back unsuccessful without acting, and the response carries a fresh reading of the current screen — including whatever the visitor just changed. Treat that as ordinary rather than as a failure to retry: re-read the screen and pick the element again from what’s there now. Repeating the same reference will keep failing, because it no longer points at anything.

Sharing the screen is the point of this, not a flaw to work around. The visitor keeps tapping while you work, and they watch your actions happen live on their own device.

The skill

The npm package also ships a ready-made skill at skills/vibeview-agent/SKILL.md describing this entire workflow (build requirements, the loop, verifying, cleanup, and the full command reference) in a form coding agents with filesystem access can read and follow directly. It covers the same ground as this page.

Billing

A session started this way is an ordinary VibeView session — it bills streaming minutes the same as any session started from the dashboard. Remember to run vibeview dev-stop (or stop the session your agent started) when you’re done so it doesn’t keep running unattended.