Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 15 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

<img width="900" height="567" alt="demo" src="https://github.com/user-attachments/assets/ea5c5be1-c66d-4adc-a0d4-cc3878660152" />

View and control Android emulators from OpenCode Desktop on Windows, Linux, and
macOS. Includes a Device panel and agent tools.
<br>

View and control Android (for now) emulators from OpenCode Desktop on Windows, Linux, and
macOS.

## Requirements

- OpenCode Desktop compatible with plugin SDK 2.2.2, using a local server.
- Node 22.19+ on the 22.x line, or Node 24+, with npm.
- Android SDK Platform-Tools and Emulator, a configured Android Virtual Device,
and hardware virtualization enabled.
- Android SDK Platform-Tools and Emulator, a configured Android Virtual Device, and hardware virtualization enabled.
- ffmpeg with libx264 on the desktop process's PATH.

Set `ANDROID_HOME` or `ANDROID_SDK_ROOT` if your SDK is outside the standard
Android Studio location. Set `OPENCODE_DEVICE_NODE` to your Node executable if
the desktop cannot find it.
Set `ANDROID_HOME` or `ANDROID_SDK_ROOT` if your SDK is outside the standard Android Studio location.
Set `OPENCODE_DEVICE_NODE` to your Node executable if the desktop cannot find it.

## Install

Expand All @@ -28,27 +28,25 @@ Add this entry to your OpenCode configuration.
```

Restart OpenCode Desktop, then open **Add tab > Device** in a session.
First use downloads the pinned device helpers from npm. Android SDK tools,
Node and ffmpeg must already be installed.
First use downloads the pinned device helpers from npm.
Android SDK tools, Node and ffmpeg must already be installed.

## Use

The panel opens the first configured emulator and starts it if needed. Use the
picker to switch devices, refresh to rescan, and detach to disconnect without
shutting down the emulator. Click or drag the screen to control it.
picker to switch devices and refresh to rescan.

Agent access starts enabled. Use the switch to revoke access for the current
session. Agents can only use a device you have opened. Installing an APK or
shutting down an emulator requires a separate permission prompt.
session. Agents can only use a device you have opened.
Installing an APK or shutting down an emulator requires a separate permission prompt.

Tools use `device_*` names, or `device.*` in Code Mode. They can inspect the
screen, launch apps, enter text, interact with elements and take screenshots.

Android emulators only (for now).
[iOS, physical devices, remote servers and direct keyboard input into the panel coming soon.]

Prebuilt plugin files are included. No build step or Bun installation is needed
to install the plugin.
Prebuilt plugin files are included.

## Development

Expand All @@ -61,5 +59,5 @@ bun run bundle
bun run verify:package
```

MIT license. Dependency licenses are listed in
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
MIT license.
Dependency licenses are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
21 changes: 0 additions & 21 deletions src/gui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,6 @@ export default Plugin.define({
if (!value) setSelectedDevice(attachment() ? previous : "");
};

const detach = async () => {
const current = attachment();
if (!current) return;
const closed = await outcome("close", () =>
bridge.manual({ scope: scope(), operation: { type: "close", handle: current.handle } }),
);
if (!closed) return;
setSelectedDevice("");
};

createEffect(() => {
scope();
void refresh();
Expand Down Expand Up @@ -384,17 +374,6 @@ export default Plugin.define({
{(device) => <option value={device.id}>{device.name}</option>}
</For>
</select>
<ctx.ui.Tooltip value="Detach device">
<button
type="button"
class="device-panel__action"
aria-label="Detach device"
disabled={!attachment() || !!busy()}
onClick={() => void detach()}
>
<ctx.ui.Icon name="close" size="small" />
</button>
</ctx.ui.Tooltip>
<ctx.ui.Tooltip
value={
attachment()?.agentAccess
Expand Down
Loading