Skip to content

Writing a Skill

A skill is a set of automation scripts that drive one app (or a system feature) on a real Android device. When your skill lands in phonebase-skill-hub, any PhoneBase user can pb skills install <your-skill> and run it on their own cloud phone.

This tutorial walks you through writing your first skill in about 10 minutes.


0. Prerequisites

You'll need:

  • pb CLI installed locally (see Quick Start)
  • A reachable cloud phone or real Android device
  • Node.js 20+
  • A GitHub account (to submit the PR)

Fork the repo:

bash
gh repo fork phonebase-cloud/phonebase-skill-hub --clone
cd phonebase-skill-hub

1. Directory Layout

Each skill lives under skills/<id>/ and needs at minimum:

skills/
└── your-skill/
    ├── SKILL.md           ← metadata + description (required)
    ├── scripts/           ← command scripts (required)
    │   ├── open.js
    │   ├── close.js
    │   └── state.js
    └── resources/         ← optional assets
        └── ic_launcher.webp

Rules:

  • <id> is the unique skill identifier, becoming the command prefix (pb your-skill open). Use lowercase, hyphens, no spaces.
  • Each .js file is a subcommand, filename = command name (open.jspb your-skill open).
  • Files starting with _ (e.g. _lib.js, _utils.js) are not registered as commands — use them for shared helpers.

2. Write SKILL.md

SKILL.md is a YAML frontmatter + markdown body. The frontmatter is structured metadata (the docs site reads it to generate the listing page and detail page); the body is human-readable context.

Minimum example:

markdown
---
name: your-skill
app_name: Your App
description: One-liner describing what this skill does
platform: android
package: com.example.yourapp
---

# Your Skill

Longer description — what it does, dependencies, caveats.

Available Fields

FieldTypeRequiredDescription
namestringSkill name, usually matches the directory name
descriptionstringOne-liner shown on the listing page
app_namestringFriendly display name (e.g. Google Play Store). Used for card titles and detail page headings
display_namestringAlias for app_name, higher priority. Only set one
versionstringSemver, e.g. 1.0.0
authorstringAuthor or organization
platformandroid | ios | webTarget platform. Auto-inferred from package / bundle_id if omitted
categorystringFree-form category tag (app-store / social / e-commerce / ...)
packagestringAndroid package name
bundle_idstringiOS bundle id
tagsstring[]Arbitrary tags
requiresstring[]Dependent skills / capabilities, e.g. ["googleservices"]

app_name vs display_name

Both do exactly the same thing — provide a friendly display name. The canonical name in this project is app_name; display_name is kept as a compatible alias. If both are set, display_name wins.

Icon

Drop the app icon at resources/ic_launcher.<ext>. The docs site will pick it up automatically:

skills/your-skill/
└── resources/
    └── ic_launcher.webp    ← webp preferred, png / jpg / svg also supported

3. Write Scripts with JSDoc

Every script file is an ES module exporting a default async function. The top of the file should be a standard /** */ JSDoc block describing the command metadata — the docs site parses it to render the command table and argument list.

Tags

TagExamplePurpose
@command <name>@command searchCommand name (defaults to filename)
@description <text>@description Search the storeDefault-language description
@description:<lang> <text>@description:en Search the storeLocalized description
@arg <name>:<type>[!][=<default>] <desc>@arg keyword:string! Search keywordArgument declaration
@arg:<lang> <name> <desc>@arg:en keyword Search keywordLocalized argument description

Where <type> is string / int / bool / etc., ! marks required, and =<value> sets a default.

Full Example

js
/**
 * @command search
 * @description Search the Play Store via market://search deeplink
 * @description:zh 用 market://search deeplink 在 Play Store 里搜索 App
 * @arg keyword:string! Search keyword
 * @arg:zh keyword 搜索关键词
 * @arg limit:int=20 Max number of results
 */

'use strict';

const pb = require('@phonebase-cloud/pb');
const { parseArgs } = require('node:util');

async function main() {
  const { values } = parseArgs({
    options: {
      keyword: { type: 'string' },
      limit: { type: 'string', default: '20' },
    },
  });

  await pb.startActivity({
    action: 'android.intent.action.VIEW',
    data: `market://search?q=${encodeURIComponent(values.keyword)}&c=apps`,
  });

  await pb.sleep(1500);

  return { ok: true };
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});

Writing Guidelines

  1. Prefer deeplinks over simulated UI tapsmarket://, mailto:, custom app schemes are 10× more reliable than clicking through a fragile UI tree.
  2. Be idempotent — scripts should handle "already open", "not installed", "already in background" gracefully.
  3. Return structured data, don't printreturn { ok: true, ... } instead of console.log(...). The CLI formats it for the user; HTTP/TCP callers get the object too.
  4. Errors need contextthrow new Error(\Launching ${pkg} failed: foreground is still ${currentPkg}`)notthrow new Error('failed')`.
  5. Never hard-code secrets — pass via args or use pb.secret.get(key).

4. Local Testing

Link your skill into the local pb directory and test against a real device:

bash
# In the phonebase-skill-hub repo root
pb skills link skills/your-skill
pb device list
pb your-skill open --device <your-device-id>
pb your-skill state --device <your-device-id>
pb your-skill close --device <your-device-id>

Checklist:

  • [ ] Every command returns successfully
  • [ ] Error cases have clear messages
  • [ ] Different devices and app versions all work

5. Submit a PR

bash
git checkout -b add-your-skill
git add skills/your-skill
git commit -m "feat: add your-skill"
git push origin add-your-skill
gh pr create --title "Add your-skill" --fill

CI on PR will check:

  • SKILL.md frontmatter schema
  • Default export on every scripts/*.js
  • JSDoc @command / @description / @arg syntax
  • Basic lint rules

After merge, the docs site auto-rebuilds and your skill appears in the Skill Hub listing.


6. Checklist

Before opening a PR:

  • [ ] SKILL.md has name / description / package (and app_name if the display name differs from name)
  • [ ] Every script has a JSDoc header with @command / @description / @arg
  • [ ] Every script's default export is an async function returning a structured object
  • [ ] Error messages include context
  • [ ] Scripts are idempotent
  • [ ] Tested against a real device (not only against mocks)
  • [ ] No hard-coded secrets

Done? Submit a PR → phonebase-skill-hub