Token导航 LogoToken导航

Agent Skill · 研究检索

mobilerun(mobilerun 控制)

mobilerun 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

mobilerun 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为接触敏感数据;许可证信息还不明确,第三方扫描有提醒项,按安装量在研究检索类的 73,675 个 Skill 里排第 44,713。

总安装 897周安装 37GitHub Stars 1下载 293核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:mobilerun(mobilerun 控制)
来源仓库:https://github.com/droidrun/skills
仓库路径:skills/mobilerun
安装命令:
npx skills add https://github.com/droidrun/skills --skill mobilerun
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/droidrun/skills --skill mobilerun

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

mobilerun 提供对 Android 设备的 AI 原生控制能力,支持触控、输入与自动化。适用于移动端测试、表单填写、数据整理和跨平台工作流搭建。可通过自有设备或云端虚拟机接入,即将扩展 iOS 支持。所有 API 请求必须包含 /v1 路径并通过 curl 发送。

需配置 MOBILERUN_API_KEY 并确保基础域名返回有效响应。

来源仓库内路径:skills/mobilerun

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/droidrun/skills --skill mobilerun;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要接触敏感数据,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

mobilerun 怎么安装?

在终端执行 npx skills add https://github.com/droidrun/skills --skill mobilerun。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

mobilerun 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

mobilerun 需要什么权限?

权限级别是接触敏感数据。该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

mobilerun 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

mobilerun 用的人多吗?

公开数据是累计安装 897 次,近一周安装 37 次,下载 293 次,GitHub 1 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

mobilerun 安全吗?

第三方扫描有提醒项,说明存在需要人工确认的行为。安装前请打开源码看清它读了什么、联网到哪里。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。

先想清楚的地方

  • 需要接触敏感数据,装到常用环境前要先评估影响面。
  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。

不太适合

  • 环境里有大量密钥、凭证,不方便给第三方 Skill 读取的人。
  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
mobilerun

mobilerun 控制 · 本页

8971接触敏感数据未明确有提醒

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Mobilerun

The hosted mobile cloud platform. Grant AI native control of Android devices to tap, swipe, type, navigate apps, fill out forms, extract data, automate workflows, and run QA. Connect your own phone, spin up on-demand virtual devices, or use always-on emulated and physical devices in the cloud. iOS support coming soon.

Base URL: https://api.mobilerun.ai/v1 Auth: Authorization: Bearer <MOBILERUN_API_KEY>

Important: The base domain (https://api.mobilerun.ai/) returns 404. You must always include /v1 in the path. All API calls should be made via curl. Example:

curl -s https://api.mobilerun.ai/v1/devices \
  -H "Authorization: Bearer $MOBILERUN_API_KEY"

Quick Start

The API key (MOBILERUN_API_KEY) is already available -- OpenClaw handles credential setup before this skill loads. Do NOT ask the user for an API key. Just use it.

  1. Check for a ready device: curl -s https://api.mobilerun.ai/v1/devices \ -H "Authorization: Bearer $MOBILERUN_API_KEY"

- state: "ready" = good to go, skip to the user's request - No devices or state: "disconnected" = see step 2 - 401 = invalid,expired, or revoked key -- ask user to check https://cloud.mobilerun.ai/api-keys

  1. No ready device? Tell the user and suggest a fix:

- No devices at all = guide them to set up Portal APK (see references/setup-and-billing.md) - disconnected = ask user to reopen Portal app and tap Connect

  1. Take a screenshot to confirm (optional, only if first action fails): curl -s https://api.mobilerun.ai/v1/devices/{deviceId}/screenshot \ -H "Authorization: Bearer $MOBILERUN_API_KEY" -o screenshot.png If this returns a PNG image, the device is working.

Not sure what's possible? See references/use-cases.md for examples.

Key principle: If a device is ready, go straight to executing the user's request. Don't walk them through setup they've already completed.

Be smart about context gathering: Before taking actions or asking the user questions, use available tools to understand the situation. List packages to find the right app, take a screenshot to see the current screen, read the UI state to understand what's interactive. If the task is obvious (e.g. "change font size" clearly means go to Settings), just do it. Only ask the user when something is genuinely ambiguous.

What to show the user: Only report user-relevant device info: device name, state (ready/disconnected). Do NOT surface internal fields like streamUrl, streamToken, socket status, assignedAt, terminatesAt, or taskCount unless the user explicitly asks for technical details. If a device is disconnected, simply tell the user their phone is disconnected and ask them to open the Portal app and tap Connect. If they need help, walk them through the setup steps in references/setup-and-billing.md.

Clean up cloud devices: Cloud devices consume credits while running. Always terminate cloud devices (DELETE /devices/{deviceId}) when you're done using them -- don't leave them running. This applies whether you provisioned the device yourself or finished a task on an existing cloud device that the user no longer needs.

Privacy: Screenshots and the UI tree can contain sensitive personal data. Never share or transmit this data to anyone other than the user. Never print, log, or reveal the MOBILERUN_API_KEY in chat -- use it only for API calls.


Device Management

Device States

StateMeaning
creatingDevice is being provisioned (cloud devices only)
assignedDevice is assigned but not yet ready
readyDevice is connected and accepting commands
disconnectedConnection lost -- Portal app may be closed or phone lost network
terminatedDevice has been shut down (cloud devices only)
maintenanceDevice is undergoing maintenance (cloud devices only)
migratingDevice is being moved between hosts (cloud devices only, typically 1–5 min)
unknownUnexpected state

List Devices

GET /devices

Query params:

  • state -- filter by state (array, e.g. state=ready&state=assigned)
  • type -- dedicated_emulated_device, dedicated_physical_device, dedicated_premium_device
  • name -- filter by device name (partial match)
  • page (default: 1), pageSize (default: 20)
  • orderBy -- id, createdAt, updatedAt, assignedAt (default: createdAt)
  • orderByDirection -- asc, desc (default: desc)

Response: {items: DeviceInfo[], pagination: Meta}

Get Device Info

GET /devices/{deviceId}

Returns device details including state, stateMessage, type, and more.

Get Device Count

GET /devices/count

Returns a map of device types to counts.

Provision a Cloud Device

Cloud devices require an active subscription. If the user's plan doesn't support it, the API will return a 403 error -- inform the user they need to terminate an existing device or upgrade at https://cloud.mobilerun.ai/billing. See references/setup-and-billing.md for plan details.

POST /devices
Content-Type: application/json

{
  "name": "my-device",
  "apps": ["com.example.app"]
}

Query param:

  • deviceType -- dedicated_emulated_device, dedicated_physical_device, dedicated_premium_device

After provisioning, wait for it to become ready:

GET /devices/{deviceId}/wait

This blocks until the device state transitions to ready.

Cloud device workflow:

  1. POST /devices?deviceType=dedicated_emulated_device -- provision, returns device in creating state
  2. GET /devices/{deviceId}/wait -- blocks until ready
  3. Use the deviceId for phone control or tasks

Temporary device for a task: When the user wants to run a task but has no ready device, provision a temporary cloud device, run the task on it, then clean up:

  1. POST /devices?deviceType=dedicated_emulated_device with {"name": "temp-task-device", "apps": [...]} -- include any apps the task needs
  2. GET /devices/{deviceId}/wait -- wait until ready
  3. POST /tasks with the new deviceId -- run the task (or submit multiple tasks -- they will queue and execute in order)
  4. Monitor via GET /tasks/{taskId}/status until all tasks finish
  5. DELETE /devices/{deviceId} -- terminate the device

Important: DELETE /devices/{deviceId} terminates the device immediately. If any tasks are still queued or running on that device, they will be orphaned or interrupted. Always wait for all tasks to finish before terminating.

Terminate a Cloud Device

DELETE /devices/{deviceId}
Content-Type: application/json

{}
Personal devices cannot be terminated via the API. They disconnect when the Portal app is closed.

Get Device Time

GET /devices/{deviceId}/time

Returns the current time on the device as a string.


Screen Observation

Take Screenshot

GET /devices/{deviceId}/screenshot

Query param: hideOverlay (default: false)

Returns a PNG image as binary data. Use this to see what's currently displayed on screen.

Get UI State (Accessibility Tree)

GET /devices/{deviceId}/ui-state

Query param: filter (default: false) -- set to true to filter out non-interactive elements.

Returns an AndroidState object with three sections:

phone_state
{
  "keyboardVisible": false,
  "packageName": "app.lawnchair",
  "currentApp": "Lawnchair",
  "isEditable": false,
  "focusedElement": {
    "className": "string",
    "resourceId": "string",
    "text": "string"
  }
}
  • currentApp -- human-readable name of the foreground app
  • packageName -- Android package name of the foreground app
  • keyboardVisible -- whether the soft keyboard is showing
  • isEditable -- whether the currently focused element accepts text input
  • focusedElement -- details about the focused UI element (if any)
device_context
{
  "screen_bounds": { "width": 720, "height": 1616 },
  "display_metrics": {
    "density": 1.75,
    "densityDpi": 280,
    "scaledDensity": 1.75,
    "widthPixels": 720,
    "heightPixels": 1616
  }
}
  • screen_bounds -- the actual screen resolution in pixels. All tap/swipe coordinates use this coordinate space.
  • display_metrics -- physical display properties (density, DPI)
a11y_tree (Accessibility Tree)

A recursive tree of UI elements. Each node has:

{
  "className": "android.widget.TextView",
  "packageName": "app.lawnchair",
  "resourceId": "app.lawnchair:id/search_container",
  "text": "Search",
  "contentDescription": "",
  "boundsInScreen": { "left": 48, "top": 1420, "right": 671, "bottom": 1532 },
  "isClickable": true,
  "isLongClickable": false,
  "isEditable": false,
  "isScrollable": false,
  "isEnabled": true,
  "isVisibleToUser": true,
  "isCheckable": false,
  "isChecked": false,
  "isFocusable": false,
  "isFocused": false,
  "isSelected": false,
  "isPassword": false,
  "hint": "",
  "childCount": 0,
  "children": []
}

Key node fields:

  • text -- the visible text on the element
  • contentDescription -- accessibility label (useful when text is empty, e.g. icon buttons)
  • resourceId -- Android resource ID (e.g. com.app:id/button_ok) -- useful for identifying elements
  • boundsInScreen -- pixel coordinates as {left, top, right, bottom}. To tap an element, calculate its center: x = (left + right) / 2, y = (top + bottom) / 2
  • isClickable -- whether the element responds to taps
  • isEditable -- whether the element is a text input field
  • isScrollable -- whether the element supports scrolling (swipe gestures)
  • children -- nested child elements (the tree is recursive)

Other boolean fields (isEnabled, isVisibleToUser, isCheckable, isChecked, isFocused, isSelected, isPassword) are available but rarely needed.

Example: reading a home screen

FrameLayout (0,0,720,1616)
  ScrollView (0,0,720,1616) [scrollable]
    FrameLayout (14,113,706,326)
      LinearLayout (42,128,706,310) [clickable]
        TextView (42,156,706,198) "Tap to set up"
  View (0,94,720,1574) "Home"
  TextView (14,1222,187,1422) "Phone" [clickable]
  TextView (187,1222,360,1422) "Contacts" [clickable]
  TextView (360,1222,533,1422) "Files" [clickable]
  TextView (533,1222,706,1422) "Chrome" [clickable]
  FrameLayout (48,1420,671,1532) "Search" [clickable]

To tap "Chrome": bounds are (533,1222,706,1422), so tap at x=(533+706)/2=619, y=(1222+1422)/2=1322.

Use filter=true for a cleaner tree focused on actionable elements (filters out non-interactive containers).

Never dump the full UI state with jq '.' — the a11y tree is deeply nested and can be tens of thousands of lines on screens with lists, grids, or feeds. Output will be truncated from the beginning, and you will silently lose data without knowing it.

Always use targeted jq filters:

# Get all clickable/interactive elements
curl -s "https://api.mobilerun.ai/v1/devices/{id}/ui-state?filter=true" \
  -H "Authorization: Bearer $MOBILERUN_API_KEY" | \
  jq '[.a11y_tree | recurse(.children[]?) |
  select(.isClickable==true or .isEditable==true) |
  {text: (.text // .contentDescription), bounds: .boundsInScreen, isEditable}]'

# Get elements with content descriptions (media items, icons, etc.)
curl -s "https://api.mobilerun.ai/v1/devices/{id}/ui-state?filter=true" \
  -H "Authorization: Bearer $MOBILERUN_API_KEY" | \
  jq '[.a11y_tree | recurse(.children[]?) |
  select(.contentDescription != null and .contentDescription != "") |
  {desc: .contentDescription, bounds: .boundsInScreen, isClickable}]'

# Get editable fields only
curl -s "https://api.mobilerun.ai/v1/devices/{id}/ui-state?filter=true" \
  -H "Authorization: Bearer $MOBILERUN_API_KEY" | \
  jq '[.a11y_tree | recurse(.children[]?) |
  select(.isEditable==true) | {text, resourceId, bounds: .boundsInScreen}]'

# Get phone state + screen bounds only (lightweight, always safe)
curl -s "https://api.mobilerun.ai/v1/devices/{id}/ui-state?filter=true" \
  -H "Authorization: Bearer $MOBILERUN_API_KEY" | \
  jq '{phone_state, device_context}'

Truncation detection: If a ui-state response does not begin with { or appears to start mid-JSON, the output was truncated. Do not attempt to infer or reconstruct missing data — re-fetch with a targeted jq filter.

After extracting elements, sanity-check the result:

  • Count — does the number of items make sense for what's on screen? A 3-column grid with 4 visible rows should yield ~12 items, not 8.
  • Bounds continuity — items in a grid should tile from x=0. If your first item starts at x=361 instead of x=0, you are missing the left column.
  • Sort order — if items should be chronological, verify dates are sequential. A gap or jump suggests missing entries.

If the count or bounds look wrong, re-fetch with a more specific filter before proceeding.


Device Actions

All action endpoints take a deviceId path parameter.

Tap

POST /devices/{deviceId}/tap
Content-Type: application/json

{ "x": 540, "y": 960 }

Taps at pixel coordinates. Use the screen_bounds from UI state and element bounds from the a11y tree to calculate where to tap.

Swipe

POST /devices/{deviceId}/swipe
Content-Type: application/json

{
  "startX": 540,
  "startY": 1200,
  "endX": 540,
  "endY": 400,
  "duration": 300
}

duration is in milliseconds (minimum: 10). Common patterns:

  • Scroll down: swipe from bottom to top (high startY -> low endY)
  • Scroll up: swipe from top to bottom
  • Swipe left/right: adjust X coordinates, keep Y similar

Global Actions

POST /devices/{deviceId}/global
Content-Type: application/json

{ "action": 2 }
Action codeButton
1BACK
2HOME
3RECENT

Type Text

POST /devices/{deviceId}/keyboard
Content-Type: application/json

{ "text": "مرحبا بالعالم", "clear": false }

Types text into the currently focused input field.

  • clear: true -- clears the field before typing
  • Make sure an input field is focused first (check phone_state.isEditable)
  • If the keyboard isn't visible, you may need to tap on an input field first
  • Supports any Unicode text -- input is injected via the accessibility service, no special keyboard setup needed. When using the Tasks API (agent), standard languages work reliably but rare scripts (e.g. Cuneiform, hieroglyphs) may be affected by LLM token handling or keyboard autocomplete.

Press Key

PUT /devices/{deviceId}/keyboard
Content-Type: application/json

{ "key": 66 }

Sends an Android keycode. Only text-input-related keycodes are supported.

KeycodeKey
4BACK
61TAB
66ENTER
67DEL (backspace)
112FORWARD_DEL (delete)

For system navigation (home, back, recent), use POST /devices/{id}/global instead.

Clear Input

DELETE /devices/{deviceId}/keyboard

Clears the currently focused input field.


App Management

List Installed Apps

GET /devices/{deviceId}/apps

Query param: includeSystemApps (default: false)

Returns an array of AppInfo:

{
  "packageName": "com.example.app",
  "label": "Example App",
  "versionName": "1.2.3",
  "versionCode": 123,
  "isSystemApp": false
}

List Package Names

GET /devices/{deviceId}/packages

Query param: includeSystemPackages (default: false)

Returns a string array of package names. Lighter than the full app list.

Install App

POST /devices/{deviceId}/apps
Content-Type: application/json

{ "packageName": "com.example.app" }

Installs an app from the Mobilerun app library (not the Play Store directly). Takes a couple of minutes and there's no status endpoint -- you'd have to poll GET /devices/{id}/apps to confirm.

Prefer manually installing via Play Store instead. Open the Play Store app on the device, search for the app, and tap install -- this is faster and more reliable. Only use this API endpoint if the user explicitly asks for it.

On personal devices, this endpoint may fail because Android blocks app installations from unknown sources by default.

Start App

PUT /devices/{deviceId}/apps/{packageName}
Content-Type: application/json

{}

Optional body: {"activity": "com.example.app.MainActivity"} -- to launch a specific activity. Usually omitting activity is fine; it launches the default/main activity.

Stop App

PATCH /devices/{deviceId}/apps/{packageName}
Content-Type: application/json

{}

Uninstall App

DELETE /devices/{deviceId}/apps/{packageName}
Content-Type: application/json

{}

App Library (Upload & Manage APKs)

The app library stores APKs that can be pre-installed on cloud devices. Only one app per package name is allowed -- to update an app, delete the existing one first, then re-upload.

List Apps in Library

GET /apps

Query params:

  • page (default: 1), pageSize (default: 10)
  • source -- all, uploaded, store, queued (default: all)
  • query -- search by name
  • sortBy -- createdAt, name (default: createdAt)
  • order -- asc, desc (default: desc)

Get App by ID

GET /apps/{id}

Upload an APK

Uploading is a 3-step process:

Step 1: Create signed upload URL

POST /apps/create-signed-upload-url
Content-Type: application/json

{
  "displayName": "My App",
  "packageName": "com.example.myapp",
  "versionName": "1.0.0",
  "versionCode": 1,
  "targetSdk": 34,
  "sizeBytes": 5242880,
  "files": [
    { "fileName": "base.apk", "contentType": "application/vnd.android.package-archive" }
  ],
  "country": "US"
}

Required: displayName, packageName, versionName, versionCode, targetSdk, sizeBytes, files Optional: description, iconURL, developerName, categoryName, ratingScore, ratingCount

Returns the app id and pre-signed R2 upload URLs for each file.

Step 2: Upload the APK file(s)

Upload each file directly to its pre-signed R2 URL using a PUT request.

Step 3: Confirm the upload

POST /apps/{id}/confirm-upload

Verifies the file exists in R2 and sets the app status to available.

If the upload failed, mark it:

POST /apps/{id}/mark-failed

Delete an App

DELETE /apps/{id}

Removes the app from R2 storage and the database. Use this before re-uploading an app with the same package name.

Re-uploading an App

Only one app per package name is allowed. To update:

  1. Find the existing app: GET /apps?query=com.example.myapp
  2. Delete it: DELETE /apps/{id}
  3. Upload the new version using the 3-step upload flow above

Tasks (AI Agent)

Instead of controlling a phone step-by-step, you can submit a natural language goal and let Mobilerun's AI agent execute it autonomously on the device with its own screen analysis, observe-act loop, and error recovery.

Tasks require a paid subscription with credits. If the user doesn't have an active plan, the API will return an error -- let them know they need a subscription at https://cloud.mobilerun.ai/billing. See references/setup-and-billing.md for plan and credit details.

IMPORTANT: Always Break Goals Into Multiple Small Tasks

Never submit a user's goal as one big task. Always break it into multiple small, focused sub-tasks of ~7-15 UI interactions each, and submit them all to the same device. They queue and run back-to-back automatically — there is zero overhead to splitting.

This is critical because:

  • Reliability — smaller tasks succeed far more often. The agent gets lost or confused on long, multi-step tasks. A focused 5-step task almost always works; a 30-step task frequently fails.
  • Checkpoints — you can see the result of each sub-task before the next one starts, and cancel or adjust if something went wrong.
  • Cost — a failed 30-step task wastes all the credits. With small tasks, only the failed step is wasted.

The only exception is when steps are so tightly coupled they cannot be separated — e.g. a multi-field form that must be filled and submitted in one go, or a login flow where each step depends on the previous. Everything else should be split.

How to decide what goes in one task vs. separate tasks:

  • Can this step succeed on its own, without knowing the result of the previous step? → Separate task
  • Does this step require the previous step's output or screen state to even begin? → Same task (or dependent sub-task with continueOnFailure: false)
  • Is this a different app or a different area of the same app? → Separate task
  • Would you describe this as "and then also..." when explaining it? → Separate task

Task Queuing

Only one task runs on a device at a time. When you submit a task while the device is already busy, it enters a queued state and waits. Once the running task finishes and the device is released, the next queued task is automatically promoted and dispatched -- no polling or manual intervention needed.

This means you can submit multiple tasks to the same device in sequence and they will execute one after another in the order they were created.

continueOnFailure: By default, if a task fails, all subsequent queued tasks for the same device are automatically cancelled. Set continueOnFailure: true on a task to keep it in the queue even if the task before it fails. Tasks with continueOnFailure: false (the default) that are ahead in the queue will be cancelled, and the first continueOnFailure: true task will be promoted next. Use continueOnFailure: true on any sub-task that is independent of the ones before it.

Run a Task

POST /tasks
Content-Type: application/json

{
  "task": "Open Chrome and search for weather",
  "deviceId": "uuid-of-device",
  "llmModel": "google/gemini-3.1-flash-lite-preview"
}

Required fields:

  • task -- natural language description of what to do (min 1 char)
  • deviceId -- UUID of the device to run on. Must be a device in ready state.

Optional fields:

  • llmModel -- which model to use (default: google/gemini-3.1-flash-lite-preview, see GET /models for available models)
  • apps -- list of app package names to pre-install
  • credentials -- list of {packageName, credentialNames[]} for app logins
  • maxSteps -- max agent steps (default: 100)
  • reasoning -- enable reasoning/thinking (default: true). Always set to false unless the user explicitly requests it.
  • vision -- enable vision/screenshot analysis (default: false)
  • temperature -- LLM temperature (default: 0.5)
  • executionTimeout -- timeout in seconds (default: 1000)
  • outputSchema -- JSON schema for structured output (nullable). Only use when the user explicitly asks for structured/formatted data. When set, the agent returns its result as a JSON object matching the schema in the task's output field.
  • vpnCountry -- route through VPN in a specific country: US, BR, FR, DE, IN, JP, KR, ZA. Only use if the task specifically requires a certain region. VPN adds latency -- avoid unless needed.
  • continueOnFailure -- if true, this task stays queued even if the previous task on the same device fails (default: false). See Task Queuing.

Returns:

{
  "id": "uuid",
  "status": "queued",
  "streamUrl": "string | null"
}

The response will almost always return status: "queued" -- even if the device is idle -- because promotion happens asynchronously after the response. This is normal. The task will transition to created and then running shortly after. Check GET /tasks/{id}/status to see the current state.

  • streamUrl -- device stream URL, or null when the task is queued (populated once the task is promoted and assigned to the device)

Writing Task Prompts

You don't see the phone screen -- the agent on the device does. Write prompts that describe what to achieve, not how to navigate the UI. The on-device agent will figure out the taps, swipes, and navigation itself.

Don't assume the UI -- describe the goal:

  • Bad: "Tap the three dots menu in the top right, then tap Settings, scroll down and tap the Dark Mode toggle"
  • Good: "Open Settings in the Chrome app and enable Dark Mode"
  • You don't know what the screen looks like. The on-device agent can see it -- let it handle the navigation.

Don't pass your own UI observations into a task instruction. If you've already read the ui-state and identified an exact element, tap it directly — don't describe it to the agent. If you're submitting a task, describe the *goal* and let the agent do its own observation. Mixing the two — observing yourself, then handing off a screen-state description — means the agent acts on your interpretation of the screen rather than its own, and any error in your observation (truncated data, wrong coordinates) gets baked in silently.

Be specific about the important details:

  • Name the exact app (not "the browser" -- say "Chrome")
  • Specify exact text to type or send
  • Say what counts as success
  • Name the person, contact, or item to find

Single-task examples (each of these is small enough to be one task):

"Open the Settings app, go to Display, and enable Dark Mode"
"Open WhatsApp, find the conversation with John Smith, and send: Running 10 minutes late, sorry!"
"Open Chrome, go to amazon.com, search for 'wireless headphones', and report back the name and price of the top 3 results"
"Open Spotify, go to Settings, turn off Autoplay, set Audio Quality to Very High, and disable Canvas"

Multi-task example — how to split a user request into queued sub-tasks:

User asks: *"Check my email, check my calendar, and find me a good Italian restaurant nearby"*

Submit 3 tasks to the same device:

  1. "Open Gmail and tell me the subjects and senders of unread emails from today" (continueOnFailure: true)
  2. "Open Google Calendar and tell me my events for today" (continueOnFailure: true)
  3. "Open Google Maps, search for 'Italian restaurants near me', and tell me the name, rating, and address of the top 3 results" (continueOnFailure: true)

All three are independent — each gets continueOnFailure: true so they all run regardless.

Multi-task example — dependent steps:

User asks: *"Order me an Uber to 123 Main Street"*

Submit 2 tasks:

  1. "Open the Uber app, set the destination to 123 Main Street, select UberX, and stop before confirming -- report back the estimated price and arrival time"
  2. Only submit after reviewing the result of task 1 with the user — don't auto-confirm a purchase.

Include safety conditions when appropriate:

  • "If the app asks for login, stop and tell me"
  • "If the price is over $50, don't purchase -- just report the price"

Check Task Status

GET /tasks/{task_id}/status

Use this to monitor task progress:

{
  "status": "running",
  "succeeded": null,
  "message": null,
  "output": null,
  "steps": 5,
  "lastResponse": { "event": "ManagerPlanEvent", "data": { ... } }
}
  • While queued: status is queued, lastResponse is null. The task is waiting for the device to become free.
  • While running: lastResponse contains the agent's latest thinking, plan, and actions. Check this to understand what the agent is doing and where it's up to.
  • When finished: status is completed or failed, message has the final answer or failure reason, succeeded is true/false, lastResponse is null.
  • Statuses: queued, created, running, paused, completed, failed, cancelled

Monitoring a Task

After creating a task, follow this pattern:

  1. Immediately tell the user the task ID. The initial status is almost always queued — this is normal, not an error. The task will start shortly.
  2. After 5 seconds -- do the first status check. This catches quick tasks and confirms the agent started. If still queued, check every 15-30 seconds until it transitions to running.
  3. After 30 seconds -- check again if still running.
  4. Subsequent checks -- use your judgement on the interval based on:

- Task complexity -- a simple "open Chrome" task finishes fast; a multi-app workflow takes longer, so space out checks accordingly. - Progress -- if steps are increasing and lastResponse is changing, the agent is working well; you can wait longer between checks. If the step count and lastResponse haven't changed, the agent may be stuck; check sooner and consider warning the user. - Time elapsed -- the longer a task has been running successfully, the more you can trust it and wait between checks.

At each check:

  • Report to the user what the agent is doing (from lastResponse -- its current plan, thinking, what step it's on).
  • Optionally take a screenshot (GET /devices/{id}/screenshot) to show the user what's on screen.
  • Optionally read the UI state (GET /devices/{id}/ui-state) for more context.
  • Give the user a meaningful update, not just "still running" -- e.g. "The agent is on step 8, currently in the Settings app looking for display options."

When the task finishes:

  • Report the result (message, succeeded, output).
  • If the task failed unexpectedly, auto-submit feedback (see Feedback section).

If the agent seems stuck:

  • Send a message via POST /tasks/{id}/message to nudge it in the right direction.
  • Let the user know and ask if they want to steer it or cancel.

Send Message to Task

POST /tasks/{task_id}/message
Content-Type: application/json

{ "message": "Actually, search for 'weather in London' instead" }

Send instructions to steer a running agent task. Use this to correct the agent, provide additional context, or change direction mid-task. The message is queued and delivered to the agent at the next step.

Cancel Task

POST /tasks/{task_id}/cancel

Works on both queued and running tasks. Queued tasks are cancelled instantly. Running tasks transition to cancelling and stop at the next step.

Get Task Details

GET /tasks/{task_id}

Returns the full task object including configuration, status, and trajectory.

List Tasks

GET /tasks

Query params:

  • status -- queued, created, running, paused, completed, failed, cancelled
  • orderBy -- id, createdAt, finishedAt, status (default: createdAt)
  • orderByDirection -- asc, desc (default: desc)
  • query -- search in task description (max 128 chars)
  • page (default: 1), pageSize (default: 20, max: 100)

Task Screenshots & UI States

GET /tasks/{task_id}/screenshots         -- list all screenshot URLs
GET /tasks/{task_id}/screenshots/{index}  -- get screenshot at index
GET /tasks/{task_id}/ui_states            -- list all UI state URLs
GET /tasks/{task_id}/ui_states/{index}    -- get UI state at index

Get Task Trajectory

GET /tasks/{task_id}/trajectory

Returns the full history of events from the task execution.

Available LLM Models

GET /models

Returns the list of models available for tasks. Default: google/gemini-3.1-flash-lite-preview.

List Tasks for a Device

GET /devices/{deviceId}/tasks

Query params: page, pageSize, orderBy, orderByDirection


Feedback

Submit feedback to help improve the Mobilerun platform. This is important for identifying bugs and improving agent performance.

When to auto-submit feedback:

  • When a task fails unexpectedly
  • When the agent behaves incorrectly or produces wrong results
  • When API errors occur that seem like platform bugs
  • Include the taskId, error details, and what happened

When the user asks to submit feedback:

  • Ask for a few details (what happened, what they expected) but don't push hard
  • If they don't want to elaborate, just submit with whatever details you have
POST /feedback
Content-Type: application/json

{
  "title": "Task failed unexpectedly",
  "feedback": "The agent got stuck on the login screen and timed out after 50 steps.",
  "rating": 2,
  "taskId": "uuid-of-related-task"
}

Required fields:

  • title -- short summary (3-100 chars)
  • feedback -- detailed description (10-4000 chars)
  • rating -- 1 to 5

Optional fields:

  • taskId -- UUID of a related task
StatusMeaning
201Feedback submitted
400Validation error
401Invalid or missing API key
429Rate limited -- 15/day cap reached

Common Patterns

Data integrity — never fill gaps: If extracted data is incomplete (truncated output, missing items, suspicious count), re-query with a better jq filter. Never infer or fabricate missing entries. If you cannot get a complete result after two attempts, tell the user what's missing and why before proceeding.

Observe-Act Loop: Most phone control tasks follow this cycle:

  1. Take a screenshot and/or read the UI state
  2. Decide what action to perform
  3. Execute the action (tap, type, swipe, etc.)
  4. Observe again to verify the result
  5. Repeat

Finding tap coordinates: Use GET /devices/{id}/ui-state?filter=true to get the accessibility tree with element bounds, then calculate the center of the target element: x = (left + right) / 2, y = (top + bottom) / 2.

When an action doesn't work:

  • Take a screenshot and re-read the UI state -- the screen may have changed or your tap coordinates may have been off.
  • If an element isn't visible, try scrolling (swipe up/down) to reveal it.
  • If a tap didn't register, recalculate coordinates from the latest UI state and try again.
  • If the app is unresponsive, try pressing HOME and reopening the app.
  • If you're stuck after 2-3 attempts, tell the user what's happening and ask how to proceed.

Typing into a field:

  1. Check phone_state.isEditable -- if false, tap the input field first
  2. Optionally clear existing text with clear: true
  3. Send the text via POST /devices/{id}/keyboard

Two Ways to Control a Device

You have two approaches -- choose based on the task:

  1. Direct control -- You drive the device step-by-step: screenshot, tap, swipe, type. Best for simple, quick actions on a single device.
  2. Mobilerun Agent -- Submit a natural language goal via POST /tasks and the agent executes it autonomously. Best for complex or multi-step tasks. Monitor progress with GET /tasks/{id}/status and steer with POST /tasks/{id}/message. Requires credits (paid plan).

When to use the Mobilerun Agent:

  • When the task is complex or spans multiple screens/apps
  • When the user asks about approaches or alternatives
  • When direct control isn't producing good results
  • When managing multiple devices -- always use tasks for multi-device scenarios. Direct control is sequential (one action at a time on one device), so controlling multiple devices by hand is too slow. Submit a task to each device and monitor them in parallel.

Breaking goals into queued sub-tasks (default approach): Always break user goals into smaller sub-tasks when the steps are independent or semi-independent. Each sub-task should be ~7-15 UI interactions. Submit them all at once to the same device -- they queue and execute in order automatically.

  1. Split the goal into clear, self-contained sub-goals
  2. Submit all sub-tasks via POST /tasks to the same device -- they queue automatically
  3. For independent sub-tasks, set continueOnFailure: true so they run even if an earlier one fails
  4. For dependent sub-tasks (step 2 only makes sense if step 1 succeeded), leave continueOnFailure: false (default)
  5. Monitor progress via GET /tasks/{id}/status as each task runs and completes

Example: "Order groceries from the Instacart app":

  1. "Open Instacart and search for 'organic bananas', add the first result to cart"
  2. "Search for 'whole milk', add the first result to cart" (continueOnFailure: true -- independent of step 1)
  3. "Go to cart and report back the total price -- do not checkout" (continueOnFailure: false -- depends on items being in cart)

Example: "Check my email and my calendar":

  1. "Open Gmail and tell me the subjects of unread emails from today"
  2. "Open Google Calendar and tell me my events for today" (continueOnFailure: true -- completely independent)

Only keep steps in a single task when they are tightly coupled (e.g. filling a form where all fields must be submitted together, or a login flow where each step depends on the previous).

You can also submit sub-tasks one at a time if you need to make decisions between steps based on the result.

Combining both approaches: You can mix direct control and tasks in the same workflow:

  • Use direct control to quickly set something up (open the right app, navigate to a screen), then launch a task for the complex part.
  • Let a task do the heavy lifting, then use direct control for a precise final action (e.g. verify a specific element on screen).
  • Use direct control for a quick check (screenshot to see what's on screen), then decide whether to handle it manually or submit a task.

Only suggest tools and approaches available through this skill -- do not recommend external tools like ADB, scrcpy, Appium, Tasker, etc.


Error Handling

All API errors follow this format:

{
  "title": "Unauthorized",
  "status": 401,
  "detail": "Invalid API key.",
  "errors": []
}
ErrorLikely causeWhat to do
401Invalid or expired API keyAsk user to verify key at https://cloud.mobilerun.ai/api-keys
402 on POST /tasksInsufficient creditsUser needs to add credits or upgrade plan
403 with "limit reached"Plan limit hit (max concurrent devices)User needs to terminate a device or upgrade
404 / 500 on device actionDevice not found or invalid IDVerify device ID, re-list devices
Empty device listNo device connectedGuide user to connect via Portal APK (see references/setup-and-billing.md)
Device disconnectedPortal app closed or phone lost networkAsk user to check phone and reopen Portal
Billing/plan error on POST /devicesFree plan, cloud devices need subscriptionTell user to check plans at https://cloud.mobilerun.ai/billing
Action fails on valid deviceDevice may be busy, locked, or unresponsiveTry taking a screenshot first to check state

For detailed troubleshooting of common issues (device disconnects, keyboard failures, screenshot errors, app install problems, etc.), see references/troubleshooting.md.

References

  • references/use-cases.md — real-world examples of what users can do (read this if the user isn't sure what's possible)
  • references/setup-and-billing.md — auth setup, Portal APK installation, plans & credits, webhooks, resource links
  • references/troubleshooting.md — 10 common issues with symptoms, causes, and fixes
  • references/security.md — data handling, credentials, device permissions
  • references/changelog.md — version history and notable changes

平台分布

Codex

33.48%
按下载量换算98

Claude

30.73%
按下载量换算90

Cursor

19.52%
按下载量换算57

Gemini CLI

9.16%
按下载量换算27

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

接触敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills