--- name: ade-app-control description: Use this skill when you need to run or drive a local Electron/desktop app and capture what it does — launch it or attach to a running renderer, read its logs or answer its terminal prompts, click and type in it, record a video or file proof of it, show it to the user, or pull screenshot-backed DOM/source context into the chat — through `ade app-control`. --- # ADE App Control App Control drives a desktop app you are building. Today that means an Electron app. ADE launches it (or attaches to one that is running) through a Chrome DevTools Protocol (CDP) port. Then you observe it, act on it, record it and file proof from it. It works in the background: no cursor, no focus stealing, and it captures the app's own window, not the user's screen. Not sure App Control is the right surface? The **ade-computer-use** skill picks between `ade apple`, `ade mac-desktop`, `ade app-control` and `ade browser`. ## Rules before your first command - **One session per lane.** `--lane` defaults to `ADE_LANE_ID` and the chat to `ADE_CHAT_SESSION_ID`, so an agent never passes them. Every command acts on your lane's session. Another lane's session is not yours to drive. - **`launch` refuses when your lane already has a live session.** Check `status` first. Use the running app, or `stop` it, or pass `--force` to replace it. `--force` only replaces a session in your own lane. - **Check each step before you report it.** An act that returns `ok` only means ADE sent the input. Read the `hit:` and `effect:` lines and the new observation. Report only what you confirmed. - **`ade: Unknown command 'app-control'` means your shell found an older `ade`.** Run the same command as `"$ADE_CLI_PATH" app-control ...` (PowerShell: `& $env:ADE_CLI_PATH app-control ...`) and keep using it for the rest of the task. - **App Control needs the running ADE brain.** A session lives in the brain, so `--headless` is refused. If a command says no brain answered, you are running the wrong `ade`: use `$ADE_CLI_PATH`. Never start, stop or install a brain or service, and never `npm install` in the user's repo to fix ADE. Report the error instead. ## Common tasks Use these directly; you do not need `--help` for them. | Task | Command | |---|---| | What is running in this lane | `ade app-control status --text` | | Launch the app | `ade app-control launch --command "npm run dev" --text` | | Attach to a running app | `ade app-control connect --cdp-port 9222 --text` | | What is on screen | `ade app-control observe --map --text` | | Click a control by its label | `ade app-control click --text-match "Save" --text` | | Right-click a control | `ade app-control right-click --text-match "Row" --text` | | Fill a field | `ade app-control fill --selector "#name" --value "Ada" --text` | | Wait for text to appear | `ade app-control wait --text-match "Saved" --timeout-ms 8000 --text` | | Record a video | `ade app-control record start --caption "" --text`, then `record stop --text` | | File a still as proof | `ade app-control proof --caption "" --text` | | Show the app to the user | `ade app-control show --floating --text` | | Read the app's terminal | `ade app-control logs --text` | | Stop | `ade app-control stop --text` | ## The loop ### 1. Launch or attach ```bash ade app-control status --text ade app-control launch --command "npm run dev" --text ade app-control launch pnpm dev --text # the words after launch are the command ade app-control launch --command "pnpm dev" --cwd apps/desktop --text ade app-control connect --cdp-port 9222 --text # an app that is already running ``` `launch` runs the command in a visible terminal that belongs to your chat. ADE sets `ADE_APP_CONTROL_CDP_PORT` and `ADE_APP_CONTROL_DEBUG_FLAGS` in its environment. It forwards the debug flags on its own for npm, pnpm, yarn and bun scripts and for a direct `electron` command. `--cwd` resolves a relative path from the directory you run `ade` in. An installed Electron app (Slack, a packaged build of the app you work on) is launched the same way: name its binary and add `$ADE_APP_CONTROL_DEBUG_FLAGS`, which ADE fills in itself, so it works in zsh too. The app must not already be running; quit the user's copy only if they say so. ```bash ade app-control launch --command '/Applications/Slack.app/Contents/MacOS/Slack $ADE_APP_CONTROL_DEBUG_FLAGS' --text ``` `connect` attaches to an app you did not launch. The app must already expose a CDP port (`--remote-debugging-port`). When you attach to something that is already running, `ade app-control claim --lane --text` attributes it to your lane. ### 2. Observe before you act ```bash ade app-control observe --map --text # screenshot + numbered element map + handles ade app-control observe --no-dom --text # screenshot only ``` An observation gives you a screenshot and numbered elements with handles (`obs-…:e:7`). A handle is valid only for recent observations. ADE keeps the newest 3 per session, so observe again when a handle is refused as expired. **Act by handle or by label, not by coordinates.** A point is a guess that the layout did not move. Observations also carry console output, failed network requests and the in-flight request count. That is usually the fastest way to see why a click did nothing. ### 3. Act, then read the answer ```bash ade app-control click --handle obs-...:e:7 --text ade app-control click --text-match "Save" --text ade app-control click --button right --text-match "Row" --text ade app-control right-click --text-match "Row" --text # same as click --button right ade app-control hover --test-id row-3 --text ade app-control fill --selector "#name" --value "Ada" --text ade app-control clear --selector "#name" --text ade app-control type "hello" --text # into the focused element ade app-control press --key Enter --text # alias: key; Enter submits a focused form ade app-control press --key "Meta+n" --text # modifiers: Alt, Control/Ctrl, Meta/Cmd, Shift ade app-control scroll --x 120 --y 420 --delta-y 600 --text ade app-control wait --text-match "Saved" --timeout-ms 8000 --text ade app-control wait --load-state network-idle --text ade app-control trace --limit 20 --text # recent actions for this session ``` Targets are `--handle`, `--selector`, `--text-match`, `--test-id`, `--element `, or `--x`/`--y`. A disabled target is refused, not clicked. Every act answers with two lines and a fresh observation: - `hit:` — the element the command actually hit. - `effect:` — `observed` (ADE saw the app change), `unconfirmed` (ADE looked and saw no change), or `not_checked` (ADE did not look). When the effect is `unconfirmed`, observe again before you act again or report the step. Do not repeat the action blindly. If the change takes time, use `wait` instead of acting again. `--fast` skips the settle delay; `--no-observe` skips the observation (then you must check the step yourself). `--session ` makes a command fail if the session has changed under you. ### 4. Record and file proof ```bash ade app-control record start --caption "Settings save the API key" --text # ... drive the app ... ade app-control record status --text ade app-control record stop --text ade app-control proof --caption "Settings shows the saved key" --text ``` Use a **video** when the claim is about behavior over time: a flow, an animation, a retry. Use a **still** (`proof`) when one screen proves the claim. - `record start` captures the app's own window. When it stops, it becomes a demo: still time cut, waits sped up, zoom to each action, a pointer and click rings, under 10 MB. `--plain` keeps it as recorded; use it only when the user asks. - Mark each step while recording with `ade proof step ""`. Each step is a caption in the video and a chapter in ADE's player. - A recording stops itself after 5 minutes of real time, or after 2 minutes with no action. `--max-seconds ` sets a shorter limit. Record the flow that shows the claim, not the whole task. - Always pass `--caption`. `record stop` files a captioned video as proof under your lane, your chat and the lane's PR. A video with no caption stays a scratch file (unless the cap or the app closing ended it). - `record stop` prints `duration` (the demo), `real time` with the time cut, the `file`, and a `proof` line with the proof id. If it prints an `error` line, the recording failed: report that. Never attach an older file in its place. On macOS the first recording can need the Screen Recording grant; the answer says so. - A recording also stops when the app closes (`stopped: the app closed`). - Confirm the final state before `record stop`, so the video ends on it. - `proof` observes the app and files that screenshot with your caption. Write a caption that says what the capture shows, then check that the screenshot really shows it. `screenshot` captures without filing anything. The **ade-proof-artifacts** skill covers `ade proof attach`, allowed roots and how to confirm a filing. ### 5. Show the user When the user asks to see the app, or you want them to watch: ```bash ade app-control show --text # App Control in the tools pane ade app-control show --floating --text # the floating card over the chat ``` `ade ui show app-control` and `ade ui show floating-app-control` do the same. The answer says what really happened: `shown`, `held` (the user is not on your chat yet; it opens when they go there), or `no_desktop` (exit 1; nothing was shown, so tell the user). The user can also watch the session live from ADE on the web or the phone. ### 6. Stop ```bash ade app-control stop --text ``` `stop` quits an app ADE launched, with its whole process tree. It only detaches from an app you attached with `connect`; that app keeps running. Save anything that matters first. ADE also releases the session when your chat ends and when the lane is archived or deleted. ## Optional: keep the app off the user's screen App Control never needs the user's screen. On a Mac host you may still move the app's window onto the lane's private Mac Desktop: ```bash ade mac-desktop start --text ade mac-desktop windows --text ade mac-desktop claim --window --text ``` This is optional. Keep driving it with `ade app-control`. ## Context for the chat ```bash ade app-control snapshot --text # screenshot + DOM element refs ade app-control inspect --x 120 --y 420 # hit-test a point, add nothing to the chat ade app-control select --x 120 --y 420 # add that element to the chat as context ``` `select` returns screenshot-backed DOM, selector and source context, and attaches it to the chat that owns the session. ## Windows, targets and drivers ```bash ade app-control windows --text # debuggable windows ade app-control switch-window --target --text # drive another window ade app-control targets --text # raw CDP targets ade app-control attach-target --target --text ade app-control drivers --text # cdp | computer_use availability ``` Switching windows clears the action trace, and handles from the old window stop resolving. Observe again after a switch. `computer_use` is listed but not implemented; it reports `unavailable`. ## Logs and terminal ```bash ade app-control logs --text --max-bytes 8388608 ade app-control terminal write --data "y\n" --text ade app-control terminal signal --signal SIGINT --text ``` Use `ade terminal list --text` and `ade terminal read ...` only when no App Control terminal is active. ## Troubleshooting - **`Waiting for CDP on 127.0.0.1:` never clears.** The app did not open the debug port. A custom launcher (a shell script, a wrapper, `concurrently`, a dev script that starts Electron itself) must pass `ADE_APP_CONTROL_DEBUG_FLAGS` through to Electron, or read `ADE_APP_CONTROL_CDP_PORT` and pass `--remote-debugging-port`. You can also put the placeholder in the command, and ADE substitutes it: `ade app-control launch --command "/path/script.sh {ADE_APP_CONTROL_DEBUG_FLAGS}" --text`. Read `ade app-control logs --text` to see what the app printed. - **The port is taken.** Another app (or an old run) holds it. `stop` the old session, or pass `--cdp-port `. - **The app restarted and the session looks stale.** Dev servers that rebuild the main process restart Electron. Find the new page and re-attach: ```bash ade app-control targets --text ade app-control attach-target --target --text ade app-control observe --text ``` - **`launch` refused: the lane already has a session.** Run `status`. Reuse it, `stop` it, or pass `--force` to replace it. ## Launching ADE itself from inside ADE If you run inside one ADE (Beta or stable) and need the ADE dev app under App Control, the dev launcher is isolated and safe: ```bash ade app-control launch --command "npm run dev" --text ade app-control launch --command "npm run dev:desktop -- --socket /tmp/ade-runtime-app.sock {ADE_APP_CONTROL_DEBUG_FLAGS}" --text ``` `npm run dev` uses its own runtime socket (`/tmp/ade-runtime-dev.sock`) and its own Electron profile (`ade-desktop-dev`), so it does not collide with the ADE that hosts you. `ade runtime status --text` tells you which socket the CLI uses. The `dev-desktop` launcher accepts App Control's `{ADE_APP_CONTROL_DEBUG_FLAGS}` placeholder (and the `ADE_APP_CONTROL_CDP_PORT` it exports) and passes the CDP port and render flags through to Electron, so `launch` connects on the port App Control expects.