agentleFS
CassiiopeiaCassiiopeia/projectops/skills/pro-launch/SKILL.md

pro-launch

Skill6 starsChanged 32 days agoMITView on GitHub

Capability skill that launches and drives apps, web, desktop windows and servers: emulator/simulator screenshots, browser navigation and capture, installer and OS dialogs, API mocking for empty/error states, HTTP/DB/log access. Used by pro-agent-test, pro-ui-design, pro-figma.

The file

Show its rules and text

We split this skill into the rules it gives an agent

34 rules. About 0 are common, 2 are distinctive, and 32 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 32

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every command is {PYTHON} {SCRIPTS}/launch_cli.py <command>.

    line 27
  2. Very specific:

    The Bash tool resets its state on every call: After you find the path once, write the actual path as a literal value in later blocks.

    line 72
  3. Very specific:

    When another skill calls this one, keep SKILL=pro-launch as is.

    line 73
  4. Very specific:

    If a response contains newer_installed, a newer version is installed (it is in the JSON and also comes with success responses).

    line 85
  5. Very specific:

    If docs_dir from doctor differs from the folder of the SKILL.md you are reading, your docs are an old version.

    line 86
  6. Distinctive:

    If it does not exist (first run), find the scripts as below.

    line 88
  7. Very specific:

    Borrow devices; do not pick them by name (#953): Start every app task with device acquire --platform <ios|android> --purpose "#issue", use …

    line 120
  8. Very specific:

    Do not take a running device that has no ledger record (a person may be using it).

    line 127
  9. Very specific:

    When an element is not found, you get candidates (values actually on screen) and locale.

    line 134
  10. Very specific:

    Do not shut down another session's emulators, simulators or processes.

    line 145
  11. Very specific:

    Do not poll doctor --resources waiting for the pressure to drop.

    line 146
  12. Very specific:

    When doctor --resources recommends device benchmark (never measured · older than 30 days · RAM changed), run it once while no other session …

    line 148
  13. Very specific:

    If text is enough to confirm (web assert · app tree · logs), do not read the image.

    line 150
  14. Very specific:

    Never handle credentials as plain text: Instead of --text on the command line, use --cred (saved) · --text-env (one-off), and $CRED_* …

    line 153
  15. Distinctive:

    Do not call AI generation APIs to produce captures.

    line 156
  16. Very specific:

    Do not write new raw Playwright or CDP scripts, direct adb · simctl · maestro calls, or wait loops.

    line 158
  17. Very specific:

    Do not read credentials straight from config files: Use saved values only through --cred.

    line 160
  18. Very specific:

    If you got stuck and solved it with a workaround, record it with learn before you finish, so the next session does not rebuild the same …

    line 163
  19. Very specific:

    Use --at 0.5,0.8 ratio (0–1) coordinates only when there is no element, such as a canvas or map.

    line 183
  20. Very specific:

    If you get not_found right after a screen transition or loading, check the current screen with app shot, then tap again once with --shot.

    line 191
  21. Very specific:

    Do not type xcrun simctl or adb shell am|pm directly to terminate apps or set location, permissions, push or deep links.

    line 193
  22. Very specific:

    Read → pick by number → press with verification: Do not guess selectors. click returns what it pressed (clicked) and whether the URL or …

    line 245
  23. Very specific:

    If a Google or Apple login is blocked, try web open --headed first.

    line 250
  24. Very specific:

    When done, run web close. The session-end hook also closes browsers this session started (it leaves them if other sessions have tabs).

    line 252
  25. Very specific:

    Read forms with web inspect --role input --near "Email" and use the returned ref for type · check · select.

    line 254
  26. Very specific:

    If the login form is inside an iframe (find returns nothing_found + frames), add --frame <name|iframe selector|part of URL> to find · type …

    line 256
  27. Very specific:

    Sessions in the same repo share one browser: Do long, unsaved input in your own tab created with web tab new --name <name> …

    line 257
  28. Very specific:

    If another session moved your tab, the result carries tab_warning (page_changed_by_other_session).

    line 258
  29. Very specific:

    Never conclude "popups are blocked" without testing window.open in web script.

    line 264
  30. Very specific:

    Prefer text (find · tree · wait --text) over shot.

    line 303
  31. Very specific:

    Before touching a server, a DB or a login screen, and before saying "the user has to log in themselves", check cred list first.

    line 324
  32. Very specific:

    Use only entries whose use_when matches.

    line 325
  33. Very specific:

    If learn_hint arrives, record the approach that got you unstuck with learn.

    line 328
  34. Very specific:

    Before site work you have done before, harness match --url <url> (or harness list) even when nothing came in a response.

    line 331

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

pro-launch — launch, drive, capture

When to use

  • "Launch the emulator and take a screenshot", "simulator screenshot", "capture this screen in the browser", "capture it at mobile width" (Korean triggers: '에뮬레이터 띄워서 찍어줘', '시뮬레이터 스크린샷', '브라우저로 이 화면 캡처해줘', '모바일 폭으로 찍어줘')
  • "Capture the empty list screen", "show the screen when it returns 500", "call this API once" (Korean triggers: '빈 목록 화면 찍어줘', '500 났을 때 화면 보여줘', '이 API 한 번 불러줘')
  • This skill does not decide what to capture or why. That is the job of the calling task skill (pro-agent-test · pro-ui-design · pro-figma).
  • QA that walks a flow end to end while hunting bugs is pro-agent-test. On Google and Apple login screens, this skill hides automation markers to reduce blocking.

This is a capability skill. It documents only what each call does and returns. The calling task skill (pro-agent-test · pro-ui-design · pro-figma · pro-report) decides what to capture and why, and calls this as a script, not as a Skill call (capability skills on the same layer: pro-github · pro-ssh). The agent decides; the script only executes and records. It does not scan routes or screen lists to guess the project structure.

The absolute rules in ../references/common-rules.md apply (no Git commits, protect sensitive data). Arguments: $ARGUMENTS

Reply to the user in their language (or the configured language preference).

What you want to do → command → detailed doc

Every command is {PYTHON} {SCRIPTS}/launch_cli.py <command>. When you call it from outside the repo (after the shell's working directory changed), add --root <project>. Without it, the root is resolved in this order: $PROJECT_ROOT > working directory (git) > most recently used root. A failed lookup returns root_candidates and retry (#885).

GoalCommandDetails
Tools (adb · simctl · Chromium · Pillow · cwebp · ffmpeg) · what can be launched (app · web · server, package name · bundle ID) · attached devices · booted simulators · AVDsdoctor · detect --path . · devices—
Host memory pressure (ok · high · critical) · large processes · repos holding running devices — before booting more virtual devicesdoctor --resourcesCommon rules below
Borrow and return a device — the start and end of every app task (get a free device that matches conditions; when done, shut down what you booted)device acquire --platform ios|android [--form tablet] [--purpose "#issue"] [--wait 90] · device release --device $DEV · device shutdownreferences/app.md
Record prepared state (login · test data · permissions) — protected from cleanup and reset; next time --need gets you that device · view device recordsdevice mark|unmark --kind login --name kakao · device adopt · device recordsreferences/app.md
When you really need a new device (two at once · a completely fresh phone · a different form factor) — instead of raw simctl create/avdmanager · clone a logged-in device · erase · deletedevice acquire --fresh|--count 2 · device create|clone --reason "..." · device erase|deletereferences/app.md
Clean up accumulated devices — delete expired temporary devices · shut down abandoned running devices (report only by default)device gc [--apply] [--sizes]references/app.md
Measure how many devices this computer can handle and save it to config — on a new computer · when several sessions stall while bootingdevice benchmark [--platform ios|android] [--full]references/app.md
Bind roles to devices (two or more participants) · show the owner · claim a specific devicedevice list|bind|unbind|show|claimreferences/app.md
Boot an emulator/simulator (no window by default; small window if you need one) · reboot · locale · dark mode — instead of raw simctl/emulatordevice boot|reboot [--cold]|locale|appearance|text-size · app install --pathreferences/app.md
Launch an app · captureapp launch · app shot [--clean-status]references/app.md
App screen structure · tap · swipe · back · wait for the screen to settleapp tree · app tap · app swipe · app back · app wait --settlereferences/app.md
Several app steps at once (one Maestro run on iOS)app run --steps steps.jsonreferences/app.md
Complex gestures (swipe then tap immediately · swipe from an element) — run the project's Maestro flow and captureapp flow --file x.yamlreferences/app.md gesture playbook
When you look and tap on iOS many times — resident session (20 s → 1 s per call)app session start|stop|statusreferences/app.md
Type text into the app (saved login; Maestro on iOS)app type --cred <name>references/app.md
Wait until the screen changes (or something disappears) · screen recording · burst capture right after an actionapp wait [--gone] · app record start|stop · --burstreferences/app.md
Terminate · relaunch · location · permissions · push (iOS) · deep links — instead of raw simctl/adbapp terminate|relaunch|location|permission|push|openurlreferences/app.md
Open a browser · read the screen · pick what to press · click with verification · type · captureweb setup|open|goto|text|find|click|type|shot|assert|console|closereferences/web.md
Reuse a logged-in browser profile across repos · record logins (so you do not log in or pass 2FA again)web open --profile <name>|--need login:google · web profile list|use|mark|unmarkreferences/web.md
Change width · replace responses (empty list · 500 · delay)web viewport · web routereferences/web.md
Read a form (inputs · buttons · checkboxes · selects + values · disabled state · ref) · read only matching lines · select · check · keys · hover · dialogs · downloads — instead of raw CDP scriptsweb inspect · web text --grep · web select|check|press|hover|dialog|downloadreferences/web.md
Wait for a condition (approval · toast · URL) · scroll an inner box and capture a piece · upload files · act inside an iframe · anything the fixed actions do not coverweb wait|scroll|upload · web shot --container · --frame · web scriptreferences/web.md
Confirm a save went through by its request · save a secret from an input or copy button without printing it · type key by key · clean up leftover browsers--capture-requests · web value|clipboard --save-env · web type --keys · web close --stalereferences/web.md
Programs on this computer's screen (installer wizards · OS dialogs · desktop apps — not web pages, not emulators): list windows · read elements as refs · press without moving the mouse · type · keys · capture · waitdesktop setup|doctor|session|windows|tree|find|click|type|press|focus|shot|waitreferences/desktop.md
Render a state in code and capture itrender snapshot → render scaffold (Flutter) → render runreferences/render.md
Single HTTP request · record how to connect · SQL · logshttp · access show|set|unset · db · logsreferences/server.md
Store credentials · remote commands · sudo on this Maccred list|show|set|unset|import-ssh · ssh · local sudoreferences/credentials.md
Policy for interstitial login screens (trust this device · stay signed in · upsells)cred policy get|set|unsetreferences/web.md "Interstitial login screens"
Shrink for issue attachmentsshrinkreferences/memory.md
Hide sensitive content before posting: cover image areas · replace secrets in a logmask · redact../references/evidence-attach.md
Recall · save · delete · tidy what worked on this computerrecall [--match <word>] · learn [--detail] · forget · tidy (scattered memory) · tidy --buckets (size and bogus buckets; deletes only with --apply)references/memory.md
When you finish a task — areas where you got stuck, got unstuck, and did not learnsession-checkreferences/memory.md
Search · show · list official troubleshooting · draft an official entry from your memorykb search|show|list|proposereferences/troubleshooting.md · references/memory.md
Write free-form notes about work you did and read them next time (work journal)journal [--add "..."] [--title] [--site]references/memory.md
Guide for a site or workflow you repeat (README to follow + optional helper scripts, one folder each) — read it before repeating site work · create one when a workflow repeatsharness list|show <name>|match --url <url>|new <name> --description "..." --url "<pattern>" (apps · desktop: --app · --window)references/harness.md
Output location for this run + env.shget-output-pathBelow
Add a new platform · new action (contributors)—references/extending.md

Find the scripts

The Bash tool resets its state on every call. After you find the path once, write the actual path as a literal value in later blocks. When another skill calls this one, keep SKILL=pro-launch as is.

Use the fixed entry point if it exists (#897). Hard-coding a version folder (.../projectops/4.xx.x/...) lets the docs and the scripts drift apart during a session. ~/.projectops/bin/launch_cli.py appears after the first run. On every call it finds the newest install (checkout > highest plugin-cache version > Cursor) and passes the arguments through. To force a specific folder, set PROJECTOPS_LAUNCH_SCRIPTS.

E="${PROJECTOPS_HOME:-$HOME/.projectops}/bin/launch_cli.py"; [ -f "$E" ] && echo "Instead of SCRIPTS: {PYTHON} $E <command>"
  • In zsh, do not store the command in a variable like L="python3 …/launch_cli.py"; $L web …. zsh treats it as one word and fails with no such file. The fixed entry point is executable, so call it directly as "$E" <command>, or wrap it in a function: launch() { python3 "$HOME/.projectops/bin/launch_cli.py" "$@"; }. Do not paste a cache path with a version in it.
  • If a response contains newer_installed, a newer version is installed (it is in the JSON and also comes with success responses). Switch to the use command it suggests. If docs_dir from doctor differs from the folder of the SKILL.md you are reading, your docs are an old version.

If it does not exist (first run), find the scripts as below.

PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
PYTHON=$(for _py in python3 python; do _path=$(command -v "$_py" 2>/dev/null) || continue; "$_path" -c "import sys; sys.exit(0)" 2>/dev/null && echo "$_path" && break; done)
[ -z "$PYTHON" ] && { echo "Python not found"; exit 1; }
SKILL=pro-launch; ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
[ -d "$ROOT/skills/$SKILL/scripts" ] || for B in ~/.claude/plugins/cache ~/.codex/plugins/cache ~/.gemini/extensions ~/.pi/agent/git; do
  H=$(find "$B" -maxdepth 8 -type d -path "*/projectops/*skills/$SKILL/scripts" 2>/dev/null | sort -V | tail -1)
  [ -n "$H" ] && { ROOT="${H%/skills/$SKILL/scripts}"; break; }
done
# Home-copied installs from npx projectops: GitHub Copilot and Cursor (#200)
[ -d "$ROOT/skills/$SKILL/scripts" ] || for B in ~/.copilot ~/.cursor; do [ -d "$B/skills/$SKILL/scripts" ] && { ROOT="$B"; break; }; done
SCRIPTS="$ROOT/skills/$SKILL/scripts"
[ -d "$SCRIPTS" ] || { echo "projectops skill scripts not found. Check the plugin installation."; exit 1; }
echo "PYTHON=$PYTHON SCRIPTS=$SCRIPTS PROJECT_ROOT=$PROJECT_ROOT"

Output location — get it first. Never invent a path ⚠️

PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py get-output-path --title "{what you are capturing}"
#   --skill ui-design   # when the calling task skill wants the output in its own folder (evidence skills only — otherwise not_evidence_skill)
#   --package {package}    # written to env.sh as PKG

Source the returned env_file on the first line of every later block. It provides LAUNCH_RUN (per skill: {SKILL}_RUN) · $RUN_DIR · $SHOT_DIR · $DEV (with bound roles: DEVn · ROLEn; filled only when exactly one device is free of other sessions — if empty, use device acquire) · $PKG. --skill accepts only EVIDENCE_SKILLS from scripts/common/paths.py. Captures stored in a document skill's folder have no git exclusion and get committed.

Common rules

  • Borrow devices; do not pick them by name (#953). Start every app task with device acquire --platform <ios|android> --purpose "#issue", use the returned device as $DEV, and always finish with device release --device "$DEV" (it shuts the device down if this borrow booted it). Several sessions share the devices on one computer. Do not create new simulators or AVDs with simctl create or avdmanager. Action per verdict: reuse · reclaim → proceed · wait (device_busy) → call again with --wait 90 or do other work first · ask (needs_user) → show the reasons to the user as is and ask · no_match → check devices and widen the conditions. Memory risk (is_memory_risk in needs_user · host_memory_pressure · device_budget_exceeded): call again with --user-approved only after the user answers (acquire · boot). --yes does not pass this gate. Never add it without asking the user. Do not take a running device that has no ledger record (a person may be using it). If it must be that device, ask the user and pass --device.
  • As soon as a person finishes login, 2FA or data setup, run device mark --device "$DEV" --kind login --name <provider> (#954). The next session gets that device with device acquire --need login:<provider>. A new device would not have that state. On a marked device, erase and delete refuse without --yes. acquire creates new devices only when needed (recording the reason and expiry), and does not create one when a limit (running device count · disk · memory) is hit. In Claude Code, a hook blocks raw simctl create|delete|erase · avdmanager create|delete · -wipe-data · emu kill in Bash.
  • Pass the device for control commands with --device. Without it: $DEV → the only attached device. With several devices and no choice, the command stops with no_device.
  • Each command returns one JSON object. ok is success or failure, code is the branch (decide by code, not by message text), and next lists candidate next commands. When an element is not found, you get candidates (values actually on screen) and locale. Pick from them; do not keep guessing.
  • If a failure response has known_fix, try that fix first. It summarizes verified official knowledge (up to 2 entries). For the full symptom and cause use kb show <id>; to search when the response has none, use kb search "<error text>" --code <code>.
  • Do not trim results with head, cut or python. You would cut off next, memory and known_fix too. For a short response, add --brief to any command (only ok · code · summary · next · error · known_fix · memory · learn_hint · journal · harness; on failure also reasons · output_tail). For specific keys only, add --fields file,url (path is an alias of file). Both always keep those guidance keys. Key order is always ok · code · summary · next → known_fix · memory · journal · harness → the rest.
  • On bad_args, read unknown · did_you_mean · valid_options · example and fix the call. Do not retry by guessing. empty_values means that option's value was empty ($VAR unset). If you give device <action> an option that action does not read, it is not silently ignored; it comes back as bad_args (unused_options · valid_options).
  • If pressure: critical (from doctor --resources or the resources in devices), ask the user before booting a new virtual device. Do not shut down another session's emulators, simulators or processes. Only look at owners to see who uses them. In this case acquire does not boot anything and returns ask. Do not poll doctor --resources waiting for the pressure to drop. Show non_device_consumers and ask the user what to close.
  • Boot devices within the memory budget, one at a time across this whole computer (#964). device benchmark measures the budget and per-device cost and saves them to config. When doctor --resources recommends device benchmark (never measured · older than 30 days · RAM changed), run it once while no other session is active.
  • Captures default to WebP with a 1200 px long edge. Use --keep-format only when you need the original, such as for pixel comparison. Open what you captured with Read. If text is enough to confirm (web assert · app tree · logs), do not read the image. Images are the most expensive thing to read.
  • Do not click coordinates with raw host-mouse tools (cliclick · AppleScript · CGEvent called directly). They steal the user's cursor and you cannot tell what was pressed. Use app tap for apps, web find → web click --ref for the web, and for other programs on the screen only desktop, inside a desktop session start (it presses through accessibility without moving the mouse whenever it can, and stops if the user moves the mouse or types).
  • Never handle credentials as plain text. Instead of --text on the command line, use --cred (saved) · --text-env (one-off), and $CRED_* environment variables in command strings. Never copy values into results, reports, issues or commits. Do not write secrets into access.
  • Mutating actions (submit · delete · pay · send) run without asking only locally. Anywhere else, stop and ask (references/web.md safety contract).
  • Do not call AI generation APIs to produce captures. Do not guess the state management approach or the project structure.
  • When stuck, go in this order before building something yourself (#934): the action's --help > kb search > web script (web) · app flow (app). Do not write new raw Playwright or CDP scripts, direct adb · simctl · maestro calls, or wait loops. They bypass the shared connection, time limits and secret masking. Ask first before workarounds that write to the user's account (creating script projects such as Apps Script, calling APIs directly).
  • Do not read credentials straight from config files. Use saved values only through --cred. When you delegate work to another agent, do not tell it to use raw Maestro, simctl or adb; tell it to use this skill.
  • While waiting for a one-time code (OTP), do not submit the login form again. A new code is sent and the previous one becomes invalid.
  • --timeout is in seconds (max 300). A value of 3000 is rejected with timeout_too_large.
  • If you got stuck and solved it with a workaround, record it with learn before you finish, so the next session does not rebuild the same workaround.

App — use this when

source "{env_file value}"
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py app launch --device "$DEV" --pkg "$PKG"
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py app shot   --device "$DEV" --out 01_login --clean-status
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py app tree   --device "$DEV"
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py app tap    --device "$DEV" --text "Log in" --shot after_login
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py app swipe  --device "$DEV" --dir up --shot list_scrolled
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py app relaunch   --device "$DEV" --pkg "$PKG"     # terminate takes the same form
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py app location   --device "$DEV" --lat 37.5665 --lon 126.978
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py app permission --device "$DEV" --pkg "$PKG" --service location --grant
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py app push       --device "$DEV" --pkg "$PKG" --payload push.json
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py app openurl    --device "$DEV" --url "myapp://home"
  • Find what to tap by element: --text (exactly as shown, in the screen's language) · --id (resource-id · accessibilityIdentifier) · --desc (accessibility description); if several match, --index N (from 0). If you do not know the text, check elements and locale from app tree first.
  • Use --at 0.5,0.8 ratio (0–1) coordinates only when there is no element, such as a canvas or map. Pixels are rejected (bad_ratio).
  • Swipe with --dir up|down|left|right; for an exact path, --from 0.5,0.8 --to 0.5,0.2 --ms 300.
  • Bundle two or more steps with app run --steps steps.json. On iOS this launches Maestro only once (step format in references/app.md).
  • With --shot <name>, it captures after the screen settles. screen_changed: false means nothing reacted; pick a different element.
  • On iOS, app tree also uses Maestro, not only app tap and app swipe (10–40 s per call). Run long scenarios at once as the project's E2E flow.
  • On iOS, if you look → decide → tap many times, run app session start --device $DEV first (8 s to start, then tree 0.6 s · tap 1 s measured). transport: session in the result means it used the session; session_fallback gives the reason it fell back to launching per call. When done, app session stop (it also ends itself after 20 minutes idle). app type handles secret values, so it does not use the session (session_fallback: secret_input_per_call).
  • If you get not_found right after a screen transition or loading, check the current screen with app shot, then tap again once with --shot. If it fails again, pick from candidates.
  • iOS bundle IDs (including system apps): the iOS section of references/app.md.
  • Do not type xcrun simctl or adb shell am|pm directly to terminate apps or set location, permissions, push or deep links. The app actions above handle device selection and failure codes.
codeNext action
no_deviceCheck devices and pass --device
device_shutdownThe chosen simulator/AVD is off. Boot it with device boot --device <id> from next, then retry (to borrow one, use device acquire)
not_foundPick from candidates
ui_dump_failedCould not read the screen structure (mid-transition · secure screen). If foreground_package in the response is not your app, another app is covering it. Retry shortly; if it persists, use --at ratio coordinates
not_foreground · app_exitedlaunch/relaunch was sent but the app is not in front / it died immediately. Read blocking_activity · crash_report · exception_line. If it is someone else's device, ask first
device_unresponsiveThe device does not respond (resources includes host memory). Ask the user, then run device reboot --cold
typed_text_mismatchThe input text differs from what was typed (composing keyboard). Use app type --ime ascii
device_owned_by_otherAnother session borrowed this device. Mutating actions (tap · type · install, etc.) are refused; reads (shot · tree · wait) only get a warning field. Get another device with device acquire, or, if it must be that device, ask the user and pass --yes
device_busy · needs_user · no_matching_devicedevice acquire's wait · ask · no_match — see the per-verdict actions in the common rules above
device_budget_exceeded · host_memory_pressureBooting another device would exceed this computer's device memory budget / host memory is at a risky level (reasons · non_device_consumers). Return or shut down unused devices, or show this to the user and pass --user-approved after they answer. If the budget was never measured, run device benchmark
devices_in_use · benchmark_no_roomNot started because measuring could stall other sessions / no room to boot even one test device (config unchanged)
ledger_busyCould not get the device ledger lock within 10 s. Call again shortly
device_protected · device_in_use_unrecordedThe device you want to erase or reset holds prepared state / a person may be using it. Show marks to the user and ask; if they agree, pass --yes
no_matching_profileNo browser profile has the --need state recorded. After a person logs in, run web profile mark
emulator_no_accelerationThe emulator stopped because it could not get hardware acceleration (host memory or swap is short). Waiting will not help, and it is already shut down. Ask the user to free memory
source_bootedA running device cannot be cloned. Retry after the source is shut down
resources_low · create_failedNot enough room to create a new device (memory · disk) / creation failed. Use an existing device with device acquire first
no_focused_windowNo focused window (screen off · locked · mid-transition). This comes after waking it and reading up to three times. Look at the screen with app shot
android_system_not_ready · ui_automation_unavailable · ui_dump_timeoutA device-side problem (system restarting · UiAutomation connection failed · timeout). Look with app shot; if it persists, ask the user about an emulator cold boot
session_start_failed · session_tools_missingContinue without a session (Maestro per call). Read reason
tree_unsupportedThis device cannot read the screen structure. Look with app shot and use --text or --at ratio coordinates
ios_tool_missing · android_tool_missing · adb_missingOffer the user the install instructions from the result
non_ascii_unsupported · ios_text_unsupportedapp type cannot enter this text. Use an English substitute or leave it to the user
unsupported_on_platformThe action does not work on this platform or device (Android push · location on a real Android device, etc.). Use the alternative in next
build_mismatchDevices have different builds installed. Stop and make them match

Web — use this when

PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web open --url {url}                 # reattaches if already running (reused)
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web text                           # read the screen text — cheaper than a screenshot
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web find --text "Log in"            # candidates with ref · link URL
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web click --ref 1 --expect-url /home   # only what you picked, verified through to the result
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web viewport --preset mobile
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web route --match "**/api/items*" --status 200 --body "[]"
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web goto --url {that screen}         # rules apply when you navigate
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web shot --out 02_empty_list
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web route --clear
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web wait --url-contains /home --timeout 180   # 2-step approval · save toast — wait, do not navigate
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web scroll --to "Payment history"     # or --ref N · --by 600 [--container "#list"]
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web upload --files a.png,b.png       # hidden input[type=file] · for a drop zone, --click-ref N
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web type --frame login --selector "#account_name_text_field" --cred apple --cred-field account   # inside an iframe
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web script --file snippet.py          # anything the fixed actions do not cover — a Playwright snippet with page · context
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web tab new --name edit --url {url}  # long work goes in your own tab — then prefix LAUNCH_TAB=edit
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py web login --cred {name} --user-selector "#email" --password-selector "#pw" --submit-selector "#go" --until-url /home --handoff-seconds 120   # hands 2FA to the user and waits
  • Call find/inspect before type or click whenever the selector did not come from the latest find or inspect result. A wrong guess fails after 4 s with candidates (including refs).
  • Read → pick by number → press with verification. Do not guess selectors. click returns what it pressed (clicked) and whether the URL or title changed (changed). changed: false means you pressed the wrong thing; pick again with find. Read information with web text; use shot only to see the layout.
  • For admin consoles (Play Console · App Store Connect · billing · admin), use web open --readonly. Buttons such as delete, release or submit are blocked with mutating_blocked (links, tabs and table rows are navigation and stay open). Use --confirm-mutating only when approved.
  • Stage empty lists, failures and loading with route without touching the server (--status 500 · --delay 5000). Run --clear when done.
  • If a Google or Apple login is blocked, try web open --headed first. If a site behaves oddly, compare with --no-stealth.
  • As soon as a person finishes logging in in the browser, run web profile mark --kind login --name <provider> (#956). In other repos, open that profile with web open --need login:<provider>. When done, run web close. The session-end hook also closes browsers this session started (it leaves them if other sessions have tabs). Closing keeps the login (cookies) in the profile.
  • On playwright_missing, ask the user and run web setup. On browser_not_open · browser_gone, run web open again.
  • Read forms with web inspect --role input --near "Email" and use the returned ref for type · check · select. A disabled element returns element_disabled immediately instead of waiting.
  • Frequent, risky work uses fixed actions; the long tail uses web script. Do not write new scripts that attach to CDP directly. script goes through the shared connection, reinstall, time limits and secret masking.
  • If the login form is inside an iframe (find returns nothing_found + frames), add --frame <name|iframe selector|part of URL> to find · type · click · assert · text · inspect.
  • Sessions in the same repo share one browser. Do long, unsaved input in your own tab created with web tab new --name <name> (LAUNCH_TAB=<name>), and save after each step. If another session moved your tab, the result carries tab_warning (page_changed_by_other_session). New tabs open in the background (--front brings one forward). web open with no tab name will not take a tab another session is using: it opens your own background tab and lists the others in other_sessions. tab_used_by_other_session means you are reading someone else's tab.
  • page_reloaded in a result (also added to summary) means the page reloaded between your commands. Refs and typed text are gone. If it keeps happening, do steps that depend on each other in one web script.
  • New windows (popups) show up as new_tabs in the result of the command that opened them — or of the next command, when the site opens the window a few seconds late. To work in it, run web tab use --target <target_id> --name popup as next says, then prefix commands with LAUNCH_TAB=popup. Your own tab stays where it was. URL and title unchanged with no new_tabs means no new window — the page may have opened an in-page layer instead: read it with web text. Never conclude "popups are blocked" without testing window.open in web script; pro-launch starts the browser with popup blocking off. If web open warns the browser was started before that, run web close and web open.
  • web open clears a hung browser and leftover profile locks by itself and reports recovered. Do not delete locks by hand.
  • Options differ per action: web <action> --help. Options the action does not accept are rejected with bad_args (the usable options are in the error).
codeNext action
nothing_found · selector_not_foundPick from candidates in the result and retry with --ref as next says. If there are no candidates, read the screen text with web text and then find
element_not_actionableThe element exists but is hidden or covered, so it could not be pressed within 4 s. If covered_by is present, a fixed header or overlay covers it — use --dom-click as next says. Otherwise pick again from candidates
frame_not_foundPut a name or part of a URL from frames into --frame
wait_timeoutThe condition was not met. Decide from url · title. If you are waiting for approval, do not navigate; wait again
script_failed · script_timeoutRead the snippet's first error line and fix it · raise --timeout or wait less
ref_staleThe screen changed. Run find again. A control that only exists on hover: web hover the row first, then find
typed_value_not_keptThe box was emptied even after typing key by key, or it disappeared. find again, or do typing and the next step in one web script
expect_not_metThe expected result did not come. Look at the current screen with web text and pick another candidate
mutating_blockedThe read-only session blocked a risky button. Tell the user what would change and get approval
needs_userHand the remaining login steps (2FA · device approval) to the user. Ask with ask_user; when they say they are done, run web login --resume ... from next
tab_goneThe LAUNCH_TAB tab was closed. Run web tab new --name <same name>
browser_start_failedStill failing after self-recovery. Read stderr_tail · causes and decide
  • Before you drive the web for the first time, read the safety contract in references/web.md.

Desktop — use this when

A program window that is neither a web page nor an emulator: an installer wizard, an OS dialog, a desktop app's settings, a macOS notification. Windows and macOS.

PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py desktop doctor                      # packages · security prompt · input session
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py desktop session start --purpose "#985 installer"   # tell the user: keyboard and mouse in use
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py desktop windows                     # titles for --window
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py desktop find --window "Setup" --text "Next"   # candidates with ref · mutating
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py desktop click --ref 3 --shot after_next      # transport: invoke (no mouse) or mouse
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py desktop type --ref 2 --cred <name>           # password fields only via --cred / --text-env
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py desktop wait --text "Completed" --timeout 120
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py desktop wait --window overlay --text "Apple" --timeout 60   # macOS notification / system dialog → matches with ref
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py desktop click --ref 1 --action "<name>"      # a banner's hidden button, name from named_actions (UI language)
PYTHONIOENCODING=utf-8 {PYTHON} {SCRIPTS}/launch_cli.py desktop session stop                # always, when done
  • Reads (windows · tree · find · shot · wait) need no session. Anything that changes the screen needs this session's desktop session start. There is one input session per computer; it expires after 10 idle minutes and the session-end hook releases it.
  • Buttons that install, agree, finish, delete or save are blocked (mutating_blocked) — on by default, unlike web. Tell the user what it changes, then --confirm-mutating on that click. --at ratio clicks always need it (nothing tells what they press).
  • Prefer text (find · tree · wait --text) over shot. Read the image only when the window has no readable elements (tree_unavailable).
codeNext action
desktop_deps_missingAsk the user, then desktop setup (Windows: mss · comtypes, macOS: mss · pyobjc — into the shared venv)
permission_missing (macOS)The terminal app needs Accessibility and Screen Recording — relay grant, then reopen that app
desktop_session_required · desktop_busydesktop session start · another session holds the keyboard and mouse — wait or ask
user_interruptedThe user moved the mouse or typed (input_detected). Stop, ask, and desktop session resume only after they agree
needs_user · screen_locked · elevated_windowUAC / sign-in prompt · locked screen · an administrator window. Only a person can answer — relay ask_user
mutating_blockedRelay ask_user; after approval, the next command
not_found · window_not_found · ref_stalePick from candidates · the screen changed since find — find again
secret_requires_credPassword field: --cred <name> or --text-env VAR
tree_unavailabledesktop shot, read it, click --at x,y --confirm-mutating after asking

Details: references/desktop.md.

render · server · credentials · memory — use this when

  • For states that are hard to reach on a real device (empty list · long text · permission denied): render snapshot → temporary render code → render run. On render_failed, read first_error first (or output_tail if absent) and fix it. In residue, delete only what you created (references/render.md).
  • Decide how to connect to the server by reading the code, and write it down with access set. http --url /path · db --profile db --sql … · logs --tail 100.
  • Before touching a server, a DB or a login screen, and before saying "the user has to log in themselves", check cred list first. Use only entries whose use_when matches. For new information, ask whether you may save it, then cred set. Remote: ssh --cred <name> --command '…' (without saving: ssh --host … --user … --password-env|--key-path …). Admin rights on this Mac: local sudo --cred <name> -- <command> (the user does the first registration with cred set --prompt). Details: references/credentials.md.
  • If a response contains memory, read it first and try that approach first (it is attached automatically — no separate recall needed). After using it, run learn --key <that key> --result ok|fail. If learn_hint arrives, record the approach that got you unstuck with learn. For methods unrelated to the repo, use --scope machine. Secrets and host-mouse methods are never stored. Run shrink on captures before attaching them to an issue. Details: references/memory.md.
  • If a response contains harness, open its readme before repeating work on that site, app or window and follow its loop and human-check points (helper scripts are optional; check the screen when it differs). Before site work you have done before, harness match --url <url> (or harness list) even when nothing came in a response. When a workflow repeats, create one with harness new <name> --url "<pattern>" (apps: --app <package/bundle ID>, desktop: --window "<title>") and put helper scripts in that folder, not in a random project folder. Roles: journal = context log of one task · learn = one-line conclusion · harness = the guide + helpers. Details: references/harness.md.

Customize

Your own language, tone and rules apply: see ../references/preferences.md. Read them once at the start: run the snippet in ../references/preferences.md with SKILL_ID=pro-launch. Keys for this skill: the common language, tone, rules only. Preferences change presentation only; approval gates, secret protection and --dry-run stay in force.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.