npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

serve-android-skill

v0.2.2

Published

Claude AI Skill for Android device control via Serve Android

Readme

Serve Android AI Skill

Control Android devices programmatically with Claude AI. This TypeScript SDK enables AI agents to take screenshots, interact with device UI, and automate testing flows.

Installation

npm install serve-android-skill

📋 Choose Your Installation Method

  • Local Installation (Recommended): Install in your project for version control

    npm install serve-android-skill
  • Global Installation: Install once, use in all projects

    npm install -g @serve-android/skill

👉 Read Installation Options Guide to choose the best method for your use case.

Quick Start

import { ServeDeviceClient } from '@serve-android/skill'

// Create client pointing to your Serve Device server
const client = new ServeDeviceClient('http://localhost:3000')

// Get connected devices
const devices = await client.getDevices()
console.log(devices)
// [
//   {
//     id: 'emulator-5554',
//     model: 'Android SDK built for x86',
//     state: 'device',
//     resolution: { width: 1080, height: 1920 }
//   }
// ]

// Control the device
const deviceId = devices[0].id
await client.tap(deviceId, 540, 960)        // Tap at coordinates
await client.type(deviceId, 'Hello, World') // Type text
await client.pressBack(deviceId)            // Press Back button

// Take a screenshot
const screenshot = await client.screenshot(deviceId)
console.log(screenshot.data) // base64 PNG data

Usage with Claude

This skill is designed for use with Claude AI agents. Register it as a tool in your agent:

const tools = [
  {
    name: 'control_android_device',
    description: 'Control a connected Android device',
    input_schema: {
      type: 'object',
      properties: {
        action: {
          type: 'string',
          enum: ['tap', 'swipe', 'type', 'screenshot', 'press_back'],
          description: 'The action to perform',
        },
        deviceId: {
          type: 'string',
          description: 'ID of the device to control',
        },
        x: { type: 'number', description: 'X coordinate' },
        y: { type: 'number', description: 'Y coordinate' },
        text: { type: 'string', description: 'Text to type' },
      },
    },
  },
]

// In your agent's tool handler:
const client = new ServeDeviceClient('http://localhost:3000')

async function handleTool(toolName: string, toolInput: Record<string, any>) {
  if (toolName === 'control_android_device') {
    const { action, deviceId, x, y, text } = toolInput
    switch (action) {
      case 'tap':
        return await client.tap(deviceId, x, y)
      case 'screenshot':
        return await client.screenshot(deviceId)
      case 'type':
        return await client.type(deviceId, text)
      case 'press_back':
        return await client.pressBack(deviceId)
    }
  }
}

API Reference

Basic Operations

getDevices(): Promise<Device[]>

List all connected Android devices.

const devices = await client.getDevices()

screenshot(deviceId: string): Promise<Screenshot>

Take a screenshot from the device.

const screenshot = await client.screenshot('emulator-5554')
// screenshot.data: base64 PNG
// screenshot.width: number
// screenshot.height: number
// screenshot.capturedAt: Date

tap(deviceId: string, x: number, y: number): Promise<void>

Tap at specific coordinates.

await client.tap(deviceId, 540, 960) // Center of 1080x1920 screen

swipe(deviceId: string, x1: number, y1: number, x2: number, y2: number, duration?: number): Promise<void>

Swipe from one point to another (duration in milliseconds).

await client.swipe(deviceId, 540, 1600, 540, 400, 500) // Scroll up

type(deviceId: string, text: string): Promise<void>

Type text on the device.

await client.type(deviceId, 'Hello, World!')

key(deviceId: string, keyCode: string): Promise<void>

Press a key or key code (e.g., 'KEYCODE_BACK', 'KEYCODE_ENTER').

await client.key(deviceId, 'KEYCODE_BACK')

Convenience Methods

pressBack(deviceId: string): Promise<void>

Press the Back button.

await client.pressBack(deviceId)

pressHome(deviceId: string): Promise<void>

Press the Home button.

await client.pressHome(deviceId)

pressRecent(deviceId: string): Promise<void>

Press the Recent Apps button.

await client.pressRecent(deviceId)

pressPower(deviceId: string): Promise<void>

Press the Power button.

await client.pressPower(deviceId)

pressVolumeUp(deviceId: string): Promise<void>

Press Volume Up.

await client.pressVolumeUp(deviceId)

pressVolumeDown(deviceId: string): Promise<void>

Press Volume Down.

await client.pressVolumeDown(deviceId)

Percentage-Based Operations

These methods use screen percentages (0-100) instead of absolute coordinates, making them more robust to different device resolutions.

tapPercent(deviceId: string, percentX: number, percentY: number): Promise<void>

Tap at screen percentages.

await client.tapPercent(deviceId, 50, 50) // Center of screen

swipePercent(deviceId: string, percentX1: number, percentY1: number, percentX2: number, percentY2: number, duration?: number): Promise<void>

Swipe using screen percentages.

await client.swipePercent(deviceId, 50, 80, 50, 20, 500) // Scroll up

Scrolling

scrollDown(deviceId: string, distance?: number, duration?: number): Promise<void>

Scroll down (swipe up from middle of screen).

await client.scrollDown(deviceId, 500, 300) // Scroll down 500px in 300ms

scrollUp(deviceId: string, distance?: number, duration?: number): Promise<void>

Scroll up (swipe down from middle of screen).

await client.scrollUp(deviceId, 500, 300)

Advanced Interactions

interact(deviceId: string, action: () => Promise<void>, delayMs?: number): Promise<{ before: Screenshot; after: Screenshot }>

Perform an action and capture before/after screenshots for vision-based automation.

const { before, after } = await client.interact(deviceId, async () => {
  await client.tap(deviceId, 540, 960)
}, 500)

// Use before/after for visual comparison or UI analysis

chain(deviceId: string, ...commands: Array<{ action: () => Promise<void>; delay?: number }>): Promise<void>

Execute multiple commands in sequence with optional delays.

await client.chain(
  deviceId,
  { action: () => client.tapPercent(deviceId, 50, 50), delay: 200 },
  { action: () => client.type(deviceId, 'Search query') },
  { action: () => client.key(deviceId, 'KEYCODE_ENTER'), delay: 500 },
)

doubleTap(deviceId: string, x: number, y: number, delayMs?: number): Promise<void>

Double-tap at coordinates.

await client.doubleTap(deviceId, 540, 960, 100)

longPress(deviceId: string, x: number, y: number, duration?: number): Promise<void>

Long-press (hold for duration milliseconds).

await client.longPress(deviceId, 540, 960, 1000) // Hold for 1 second

Utilities

waitForDevice(deviceId: string, maxWaitTime?: number): Promise<boolean>

Wait for a device to be connected and ready.

const ready = await client.waitForDevice('emulator-5554', 30000)
if (!ready) {
  throw new Error('Device not ready')
}

getDevice(deviceId: string): Promise<Device | null>

Get information about a specific device.

const device = await client.getDevice('emulator-5554')
if (device?.state === 'device') {
  console.log(`Device ready: ${device.model}`)
}

saveScreenshot(deviceId: string, filepath: string): Promise<void>

Save screenshot to a file (Node.js only).

await client.saveScreenshot('emulator-5554', './screenshot.png')

AI Agent Integration Examples

Vision-Based Testing

// Take screenshot, analyze with Claude vision, decide next action
const screenshot = await client.screenshot(deviceId)
const base64Image = screenshot.data

// Send to Claude for analysis
const analysis = await claude.messages.create({
  model: 'claude-3-5-sonnet-20241022',
  max_tokens: 1024,
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'image',
          source: { type: 'base64', media_type: 'image/png', data: base64Image },
        },
        { type: 'text', text: 'What buttons are visible on this screen?' },
      ],
    },
  ],
})

Form Filling Automation

await client.chain(
  deviceId,
  { action: () => client.tapPercent(deviceId, 50, 30) },  // Focus name field
  { action: () => client.type(deviceId, 'John Doe'), delay: 200 },
  { action: () => client.key(deviceId, 'KEYCODE_TAB') },  // Move to email
  { action: () => client.type(deviceId, '[email protected]'), delay: 200 },
  { action: () => client.key(deviceId, 'KEYCODE_TAB') },  // Move to submit
  { action: () => client.key(deviceId, 'KEYCODE_ENTER') }, // Submit form
)

Navigation Testing

const pages: string[] = []

for (let i = 0; i < 5; i++) {
  const screenshot = await client.screenshot(deviceId)
  pages.push(screenshot.data)

  // Scroll to next page
  await client.scrollDown(deviceId)
  await new Promise((r) => setTimeout(r, 500)) // Wait for animation
}

// pages now contains screenshots of each screen

Configuration

Timeout Control

Set custom request timeout (default: 30s):

const client = new ServeDeviceClient(
  'http://localhost:3000',
  60000, // 60 second timeout
)

Error Handling

try {
  await client.tap(deviceId, 100, 100)
} catch (error) {
  if (error.message.includes('HTTP 404')) {
    console.error('Device not found')
  } else if (error.name === 'AbortError') {
    console.error('Request timeout')
  } else {
    console.error('Unexpected error:', error.message)
  }
}

Key Codes

Common Android key codes for use with key():

| Key | Code | |-----|------| | Back | KEYCODE_BACK | | Home | KEYCODE_HOME | | Recent Apps | KEYCODE_APP_SWITCH | | Power | KEYCODE_POWER | | Volume Up | KEYCODE_VOLUME_UP | | Volume Down | KEYCODE_VOLUME_DOWN | | Enter | KEYCODE_ENTER | | Delete | KEYCODE_DEL | | Space | KEYCODE_SPACE | | Tab | KEYCODE_TAB | | D-pad Up | KEYCODE_DPAD_UP | | D-pad Down | KEYCODE_DPAD_DOWN | | D-pad Left | KEYCODE_DPAD_LEFT | | D-pad Right | KEYCODE_DPAD_RIGHT |

See Android KeyEvent documentation for complete list.

Performance Tips

  1. Batch operations - Use chain() to reduce latency between commands
  2. Use percentages - tapPercent() and swipePercent() work across device resolutions
  3. Cache device info - Call getDevice() once and reuse resolution data
  4. Screenshot timing - Add delays after UI animations before taking screenshots
  5. Connection pooling - Reuse the same ServeDeviceClient instance

Troubleshooting

"Device not found"

const devices = await client.getDevices()
console.log(devices) // Check if your device is listed

// Wait for device to be ready
const ready = await client.waitForDevice(deviceId, 10000)

"Command timeout"

// Increase timeout for slow operations
const client = new ServeDeviceClient('http://localhost:3000', 60000)

"Cannot connect to server"

// Verify server is running
// Default URL: http://localhost:3000
// Check CORS settings if accessing from different domain

Screenshot is blank or black

  • Ensure device screen is not locked
  • Check device state with getDevice()
  • Try pressHome() to wake the device

Contributing

Issues and pull requests welcome! Please report bugs at the project repository.

License

MIT