MCPcopy Index your code
hub / github.com/AppiumTestDistribution/AppClaw

github.com/AppiumTestDistribution/AppClaw @v1.9.3

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.9.3 ↗ · + Follow
1,425 symbols 3,520 edges 207 files 247 documented · 17% updated 1d agov1.9.3 · 2026-07-06★ 893 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

AppClaw logo

AppClaw

AI-powered mobile automation agent for Android and iOS. Tell it what to do in plain English — it figures out what to tap, type, and swipe.

AppClaw demo
You: "Send a WhatsApp message to Mom
      saying good morning"

AppClaw:
  Step 1: Open WhatsApp
  Step 2: Search for Mom
  Step 3: Open chat with Mom
  Step 4: Type "good morning"
  Step 5: Tap Send
  Step 6: Done

  ✅ Goal completed in 6 steps.

Prerequisites

  1. Node.js 18+
  2. Device connected — USB, emulator, or simulator
  3. LLM API key from any supported provider (Anthropic, OpenAI, Google, Groq, or local Ollama)

Installation

From npm

npm install -g appclaw

Create a .env file in your working directory:

cp .env.example .env

Local development

git clone https://github.com/AppiumTestDistribution/appclaw.git
cd appclaw
npm install
cp .env.example .env

Edit .env based on your preferred mode:

Vision mode (recommended)

Screenshot-first mode using Stark (df-vision + Gemini) for element location. Requires a Gemini API key.

LLM_PROVIDER=gemini
LLM_API_KEY=your-gemini-api-key
LLM_MODEL=gemini-3.1-flash-lite
AGENT_MODE=vision

DOM mode

Uses XML page source to find elements by accessibility ID, xpath, etc. No vision needed — works with any LLM provider.

LLM_PROVIDER=gemini            # or anthropic, openai, groq, ollama
LLM_API_KEY=your-api-key
AGENT_MODE=dom

Usage

Platform & device selection

AppClaw supports both Android and iOS (simulators + real devices). On macOS, you'll get an interactive prompt to choose. For CI or to skip prompts, use flags:

# Android (default — no flags needed)
appclaw "Open Settings"

# iOS Simulator (auto-selects the booted simulator)
appclaw --platform ios --device-type simulator "Open Settings"

# iOS Simulator — pick by name
appclaw --platform ios --device-type simulator --device "iPhone 17 Pro" "Open Settings"

# iOS Real Device — pick by UDID
appclaw --platform ios --device-type real --udid 00008120-XXXX "Open Settings"

# Env vars work too (great for .env or CI)
PLATFORM=ios DEVICE_TYPE=simulator appclaw "Open Settings"

Tip: If only one simulator is booted, it's auto-selected — no --udid needed.

Cloud devices

Run against a remote Appium grid — BrowserStack, Sauce Labs, LambdaTest, or any self-hosted grid — instead of a local device. Set CLOUD_PROVIDER and the CLOUD_* creds; AppClaw builds the hub URL, skips local device discovery, and routes the session to the grid. appclaw init can prompt for these and write them to .env.example.

# .env — BrowserStack
CLOUD_PROVIDER=browserstack
CLOUD_USERNAME=your-user
CLOUD_ACCESS_KEY=your-key
CLOUD_DEVICE_NAME=Google Pixel 8
CLOUD_OS_VERSION=14
CLOUD_APP=bs://<hashed-app-id>        # optional; app to install

# Sauce Labs (region defaults to us-west-1; override with CLOUD_REGION)
CLOUD_PROVIDER=saucelabs
CLOUD_USERNAME=your-user
CLOUD_ACCESS_KEY=your-key
CLOUD_DEVICE_NAME=iPhone 14
CLOUD_OS_VERSION=16

# Self-hosted grid — bring your own hub URL (auth may be embedded)
CLOUD_PROVIDER=custom
CLOUD_SERVER_URL=https://user:key@grid.internal:4444/wd/hub
CLOUD_DEVICE_NAME=Pixel_7
CLOUD_OS_VERSION=14

AppClaw sets only the essentials (platformName, appium:automationName, device, OS, app). Provider-specific optionsbstack:options, sauce:options, lt:options, real-vs-emulator flags, video, network logs — go in a capabilities file passed via --caps (or CAPABILITIES_FILE / the SDK capabilitiesFile option), which is merged on top and wins:

// caps.json  →  appclaw --flow login.yaml --caps caps.json
{
  "bstack:options": { "projectName": "AppClaw", "buildName": "smoke", "networkLogs": true }
}

CLOUD_BUILD_NAME / CLOUD_PROJECT_NAME are convenience shortcuts mapped into the active provider's namespace, so common dashboard labels don't need a caps file.

Agent mode (LLM-driven)

# Interactive mode (prompts for platform + goal)
appclaw

# Pass goal directly
appclaw "Open Settings"
appclaw "Search for cats on YouTube"
appclaw "Turn on WiFi"
appclaw "Send hello on WhatsApp to Mom"

# Or with npx (no global install)
npx appclaw "Open Settings"

When running from a local clone, use npm start instead:

npm start
npm start "Open Settings"

Export a replayable test — pass --export and the agent's trajectory is written as a runnable vitest spec when the goal completes:

# Default path: $EXPORT_DIR/<goal-slug>.test.ts (defaults to .appclaw/exports/)
appclaw --export "Open YouTube and search for Appium 3.0"

# Bare filename → EXPORT_DIR/<name>
appclaw --export youtube.test.ts "Open YouTube and search for Appium 3.0"

# Path with directory hint → used verbatim
appclaw --export tests/e2e/youtube.test.ts "Open YouTube"

# Override the directory for one run
appclaw --export-dir tests/recorded --export youtube.test.ts "Open YouTube"

# Or set it persistently in .env
echo 'EXPORT_DIR=tests/recorded' >> .env

The export drops the wrong-direction branch when verification rejected a done, preserves the auto-launched app as the first step, and translates internal agent tools back to natural language so the generated test reads like English. See the SDK section below for what the test file looks like.

YAML flows (no LLM needed)

Run declarative automation steps from a YAML file — fast, repeatable, zero LLM cost:

appclaw --flow examples/flows/google-search.yaml

Flows support both structured and natural language syntax:

Structured:

appId: com.android.settings
name: Turn on WiFi
---
- launchApp
- wait: 2
- tap: 'Connections'
- tap: 'Wi-Fi'
- done: 'Wi-Fi turned on'

Natural language:

name: YouTube search
---
- open YouTube app
- click on search icon
- type "Appium 3.0" in the search bar
- perform search
- scroll down until "TestMu AI" is visible
- verify video from TestMu AI is visible
- done

Supported natural language patterns include: open <app>, click/tap <element>, type "text", scroll up/down, swipe left/right, scroll down until "X" is visible, wait N seconds, go back, press home, verify/assert <element> is visible, press enter, and done. Questions like "whats on the screen?" or "how many items are there?" are answered via vision without executing any action.

Parallel & suite runs

Run the same flow on N devices simultaneously, or distribute a suite of flows across N workers:

Same flow, N devices — add parallel: N to the flow's metadata:

name: youtube_parallel
platform: android
parallel: 2
---
- open YouTube app
- search for "Appium 3.0"
- assert "TestMu AI" is visible
- done
appclaw --flow youtube.yaml   # spins up 2 devices, runs flow on both concurrently

Suite: different flows, N workers — a suite YAML lists flows and a worker count:

name: youtube_suite
platform: android
parallel: 2
flows:
  - flows/login.yaml
  - flows/search.yaml
  - flows/playback.yaml
appclaw --flow youtube-suite.yaml   # 2 devices pull from queue until all 3 flows finish

The VS Code extension shows a live multi-device grid — each device card updates in real time with a per-device step log, progress bar, and pass/fail result. Failed flows can be re-run with Re-run Failed from the summary notification.

Playground (interactive REPL)

Build YAML flows interactively on a real device — type commands and watch them execute live:

appclaw --playground

# iOS simulator
appclaw --playground --platform ios --device-type simulator

# Specific device
appclaw --playground --platform ios --device-type simulator --device "iPhone 17 Pro"

Features:

  • Type natural-language commands that execute immediately on the device
  • Steps accumulate as you go
  • Export to a YAML flow file or a runnable SDK test (vitest spec) — format picked from the file extension
  • Slash commands: /help, /steps, /export, /clear, /device, /disconnect

Export formats/export dispatches by extension:

> /export my-flow.yaml              # YAML flow (default behaviour)
> /export tests/youtube.test.ts     # SDK vitest spec
> /export youtube.test.ts           # bare filename → EXPORT_DIR/youtube.test.ts

Bare filenames for SDK tests land in EXPORT_DIR (default .appclaw/exports); paths with a directory hint are used verbatim. YAML files stay in the current directory regardless.

SDK (TypeScript / JavaScript)

Drive a device programmatically from a vitest / jest / mocha test. The SDK exposes the same natural-language layer as the playground, so steps you build interactively can be lifted into a test file as-is.

import { AppClaw, AppClawAssertionError } from 'appclaw';
import { describe, it } from 'vitest';
import 'dotenv/config';

describe('YouTube smoke', () => {
  it('searches and verifies a result', async () => {
    const app = new AppClaw({
      provider: 'gemini',
      apiKey: process.env.LLM_API_KEY,
      platform: 'android',
      agentMode: 'vision',
      video: true, // record screen, embed in report
      mcpDebug: false, // silence appium-mcp stderr noise
    });

    await app.run('open YouTube app');
    await app.run('tap search icon');
    await app.run('type "Appium 3.0"');
    await app.run('tap first result');

    // Throws AppClawAssertionError on failure — vitest marks the test red.
    // The error message includes the original claim, the LLM's reason (in
    // vision mode), and a snapshot of visible texts (in DOM mode).
    await app.verify('the video by TestMu AI is visible');

    await app.teardown(); // closes the appium session, writes the report
  }, 120_000);
});

Three execution surfaces:

Method Use when
app.run(instruction, options?) One natural-language step. Returns { success, action, message } — does NOT throw on failure. options overrides wait/scroll config for this call only.
app.verify(claim) Assertion. Throws AppClawAssertionError so the test framework records a failure.
app.runFlow(path) Execute a YAML flow file end-to-end.
app.runGoal(goal, opts?) Hand the goal to the LLM agent (same as CLI agent mode). Supports exportPath (see below).

Recording goal runs as replayable tests — pass exportPath to runGoal() and the agent's trajectory is written as a runnable vitest spec when it finishes:

await app.runGoal('open YouTube, search for Appium 3.0, play first result', {
  exportPath: 'tests/e2e/youtube.test.ts',
});

The exported file replays via app.run(...) calls — no LLM cost, no agent loop. The exporter:

  • Prepends the synthetic launch_app step the preprocessor handled (so the launch isn't missing from the replay)
  • Drops the entire branch before a rejected done decision — the recovery path the agent took after a verification failure is the only one preserved
  • Translates internal agent tools (find_and_click, find_and_type, etc.) back into natural language so the test reads like English

Read the four caveats embedded in the generated file header before running it in CI — replays don't inherit the agent's safety net.

Per-command overridesapp.run() takes an optional second argument that wins over the instance defaults for that one step. Useful for a slow screen that needs a longer wait, or a tight list that needs a shorter scroll:

// Wait up to 20s for this specific (slow-loading) screen
await app.run('click on Dashboard', { waitTimeout: 20000 });

// Scroll a short distance, up to 5 times, to find an item
await app.run('scroll down until Karma is visible', { scrollMode: 'short', scrollTimes: 5 });

// A single full-screen swipe
await app.run('swipe up', { scrollMode: 'full' });
RunOptions field Purpose
waitTimeout Implicit-wait timeout (ms) for this command's target element. Overrides instance value.
waitInterval Poll cadence (ms) for this command's implicit wait.
scrollMode Scroll/swipe distance: short (~30%) / medium (~60%) / full (~90%) of the screen.
scrollTimes Repeat count (plain swipe) or max scroll attempts (scroll … until …).

scrollMode / scrollTimes can also be set on the constructor as instance-wide defaults.

Constructor options (all optional — env vars fall through):

Option Default Purpose

Extension points exported contracts — how you extend this code

PreCheckResult (Interface)
Non-visual instructions handled without screenshot.
src/flow/vision-execute.ts
RunnerReporter (Interface)
(no doc) [2 implementers]
src/runner/reporter.ts
RecordedStep (Interface)
A step captured during the run
src/memory/recorder.ts
CloudChoices (Interface)
Cloud creds collected interactively (only when a provider other than 'none' is chosen).
src/cli/init.ts
MCPClient (Interface)
(no doc) [2 implementers]
src/mcp/types.ts
ProviderSpec (Interface)
Per-provider connection details.
src/device/cloud.ts
DaemonMetadata (Interface)
(no doc)
packages/appclaw-agent/src/paths.ts
ConnectedEvent (Interface)
(no doc)
vscode-extension/src/bridge.ts

Core symbols most depended-on inside this repo

callTool
called by 127
src/mcp/types.ts
parseFlowYamlString
called by 65
src/flow/parse-yaml-flow.ts
run
called by 54
src/sdk/index.ts
emitJson
called by 51
src/json-emitter.ts
stopSpinner
called by 45
src/ui/renderer.ts
tryParseNaturalFlowLine
called by 42
src/flow/natural-line.ts
esc
called by 36
src/report/renderer.ts
getRenderer
called by 34
src/ui/renderer.ts

Shape

Function 906
Interface 229
Method 228
Class 62

Languages

TypeScript100%

Modules by API surface

src/ui/terminal.ts77 symbols
src/flow/run-yaml-flow.ts53 symbols
src/ui/ink/InkRenderer.tsx43 symbols
src/ui/ink/store.ts41 symbols
src/agent-runtime/index.ts41 symbols
src/ui/renderer.ts39 symbols
src/playground/index.ts37 symbols
src/report/renderer.ts34 symbols
src/cli/init.ts30 symbols
vscode-extension/src/bridge.ts29 symbols
src/runner/suite-report.ts24 symbols
src/report/writer.ts23 symbols

For agents

$ claude mcp add AppClaw \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact