portholejs 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -46
- package/client-dist/assets/index-cy7uJgaT.js +49 -0
- package/client-dist/index.html +1 -1
- package/dist/cli.js +351 -83
- package/dist/cli.js.map +1 -1
- package/dist/control-client.d.ts +1 -0
- package/dist/control-client.d.ts.map +1 -1
- package/dist/control-client.js +10 -2
- package/dist/control-client.js.map +1 -1
- package/dist/engine/scrcpy-engine.d.ts +2 -2
- package/dist/engine/scrcpy-engine.d.ts.map +1 -1
- package/dist/engine/scrcpy-engine.js.map +1 -1
- package/dist/engine/types.d.ts +3 -8
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/focus-navigation.d.ts +27 -0
- package/dist/focus-navigation.d.ts.map +1 -0
- package/dist/focus-navigation.js +118 -0
- package/dist/focus-navigation.js.map +1 -0
- package/dist/gesture.d.ts +14 -0
- package/dist/gesture.d.ts.map +1 -0
- package/dist/gesture.js +110 -0
- package/dist/gesture.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/input-validation.d.ts.map +1 -1
- package/dist/input-validation.js +4 -56
- package/dist/input-validation.js.map +1 -1
- package/dist/input.d.ts +1 -21
- package/dist/input.d.ts.map +1 -1
- package/dist/keycodes.d.ts +2 -1
- package/dist/keycodes.d.ts.map +1 -1
- package/dist/keycodes.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +185 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mp4-writer.d.ts +18 -0
- package/dist/mp4-writer.d.ts.map +1 -0
- package/dist/mp4-writer.js +195 -0
- package/dist/mp4-writer.js.map +1 -0
- package/dist/protocol.d.ts +74 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +154 -7
- package/dist/protocol.js.map +1 -1
- package/dist/recording.d.ts +22 -0
- package/dist/recording.d.ts.map +1 -0
- package/dist/recording.js +132 -0
- package/dist/recording.js.map +1 -0
- package/dist/screen-diff.d.ts +21 -0
- package/dist/screen-diff.d.ts.map +1 -0
- package/dist/screen-diff.js +70 -0
- package/dist/screen-diff.js.map +1 -0
- package/dist/server/http.d.ts +5 -4
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +69 -31
- package/dist/server/http.js.map +1 -1
- package/dist/server/ws.d.ts +4 -3
- package/dist/server/ws.d.ts.map +1 -1
- package/dist/server/ws.js +32 -18
- package/dist/server/ws.js.map +1 -1
- package/dist/session.d.ts +25 -10
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +117 -67
- package/dist/session.js.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +1 -3
- package/dist/state.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +18 -3
- package/skills/porthole/SKILL.md +130 -0
- package/client-dist/assets/index-C_qSS_Gy.js +0 -49
package/README.md
CHANGED
|
@@ -65,6 +65,7 @@ in sync (see `src/engine/scrcpy-engine.ts`).
|
|
|
65
65
|
|
|
66
66
|
```sh
|
|
67
67
|
npx portholejs start Pixel_8_Pro_API_34
|
|
68
|
+
npx portholejs start Pixel_8_Pro_API_34 Television_1080p_API_34
|
|
68
69
|
```
|
|
69
70
|
|
|
70
71
|
For a headless agent workflow:
|
|
@@ -76,54 +77,108 @@ npx portholejs screenshot -q
|
|
|
76
77
|
npx portholejs kill -q
|
|
77
78
|
```
|
|
78
79
|
|
|
80
|
+
## Things to try
|
|
81
|
+
|
|
82
|
+
- **Test a TV app without touching a remote.** Walk the D-pad focus to any
|
|
83
|
+
element by its text and press OK, then read where leanback focus landed as
|
|
84
|
+
JSON:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
porthole focus-on "Library" --select
|
|
88
|
+
porthole focused -q
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- **Record a bug repro as a real video** while you drive the phone from the
|
|
92
|
+
CLI. The swipe below opens the notification shade:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
porthole record repro.mp4 --duration 30s
|
|
96
|
+
porthole scroll down
|
|
97
|
+
porthole swipe 0.5 0.05 0.5 0.7
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- **Catch visual regressions in one line.** Save a baseline, then the assert
|
|
101
|
+
exits non-zero and writes a diff image the moment the screen drifts:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
porthole screenshot -o good.png
|
|
105
|
+
porthole assert-screen good.png --threshold 0.02
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- **Stream a phone and a TV side by side** from one command. Both devices
|
|
109
|
+
share the same browser tab with a device picker:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
porthole start Pixel_8 Tv_1080p
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- **Let an agent run the whole loop.** Add the MCP server to Claude Code
|
|
116
|
+
(below) and ask it to "open my app on the TV emulator, navigate to Settings,
|
|
117
|
+
and check nothing crashed". It will drive the D-pad, read the UI as JSON
|
|
118
|
+
with `dump_ui`, and pull `get_crashes` on its own.
|
|
119
|
+
|
|
120
|
+
- **If anything misbehaves**, diagnose your setup in seconds:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
porthole doctor
|
|
124
|
+
```
|
|
125
|
+
|
|
79
126
|
## CLI Reference
|
|
80
127
|
|
|
81
|
-
| Command
|
|
82
|
-
|
|
|
83
|
-
| `porthole start [avd]`
|
|
84
|
-
| `porthole list`
|
|
85
|
-
| `porthole kill [avd]`
|
|
86
|
-
| `porthole tap <x> <y>`
|
|
87
|
-
| `porthole
|
|
88
|
-
| `porthole
|
|
89
|
-
| `porthole
|
|
90
|
-
| `porthole
|
|
91
|
-
| `porthole
|
|
92
|
-
| `porthole
|
|
93
|
-
| `porthole
|
|
94
|
-
| `porthole
|
|
95
|
-
| `porthole
|
|
96
|
-
| `porthole
|
|
97
|
-
| `porthole
|
|
98
|
-
| `porthole
|
|
99
|
-
| `porthole
|
|
100
|
-
| `porthole
|
|
128
|
+
| Command | Purpose |
|
|
129
|
+
| ------------------------------------ | --------------------------------------------------- |
|
|
130
|
+
| `porthole start [avd...]` | Boot or attach one or more emulators and serve them |
|
|
131
|
+
| `porthole list` | List AVDs, running emulators, and known sessions |
|
|
132
|
+
| `porthole kill [avd]` | Stop emulators Porthole booted and detached servers |
|
|
133
|
+
| `porthole tap <x> <y>` | Touch normalized phone coordinates from `0..1` |
|
|
134
|
+
| `porthole swipe <x1> <y1> <x2> <y2>` | Swipe between normalized phone coordinates |
|
|
135
|
+
| `porthole longpress <x> <y>` | Long-press normalized phone coordinates |
|
|
136
|
+
| `porthole scroll <direction>` | Scroll phone content up, down, left, or right |
|
|
137
|
+
| `porthole key <keycode>` | Send an Android keycode down/up pair |
|
|
138
|
+
| `porthole remote <button>` | Press a TV D-pad/media button |
|
|
139
|
+
| `porthole text "<string>"` | Type text into the active session |
|
|
140
|
+
| `porthole screenshot [-o file.png]` | Save a PNG screenshot |
|
|
141
|
+
| `porthole assert-screen <png>` | Compare the current screen with a PNG baseline |
|
|
142
|
+
| `porthole record <out.mp4>` | Record the current H.264 stream to MP4 |
|
|
143
|
+
| `porthole focused` | Print the currently focused UI node |
|
|
144
|
+
| `porthole focus-on <text>` | Move TV D-pad focus to matching text |
|
|
145
|
+
| `porthole dump-ui [--filter text]` | Dump the Android UI hierarchy |
|
|
146
|
+
| `porthole wait-for <text>` | Wait until UI text appears |
|
|
147
|
+
| `porthole open-url <url>` | Open a URL or Android deep link |
|
|
148
|
+
| `porthole stop-app <package>` | Force-stop an app |
|
|
149
|
+
| `porthole clear-app <package>` | Clear app data |
|
|
150
|
+
| `porthole rotate <orientation>` | Rotate a phone emulator |
|
|
151
|
+
| `porthole emu <args...>` | Pass through to `adb emu` |
|
|
152
|
+
| `porthole doctor` | Diagnose Node/SDK/adb/AVD/session problems |
|
|
153
|
+
| `porthole mcp` | Run the MCP server over stdio |
|
|
101
154
|
|
|
102
155
|
Common options:
|
|
103
156
|
|
|
104
|
-
| Option | Meaning
|
|
105
|
-
| ----------------------- |
|
|
106
|
-
| `-p, --port <port>` | Preview/control port, default `3200`
|
|
107
|
-
| `-d, --device <serial>` |
|
|
108
|
-
| `--host <host>` | Bind address, default `127.0.0.1`
|
|
109
|
-
| `--no-preview` | Do not open the browser automatically
|
|
110
|
-
| `--detach` | Start the preview server in the background
|
|
111
|
-
| `--mjpeg` | Force MJPEG screenshot polling
|
|
112
|
-
| `-q, --quiet` | Emit one JSON object/array on stdout
|
|
113
|
-
| `--max-size <px>` | Maximum scrcpy stream dimension
|
|
114
|
-
| `--max-fps <fps>` | Maximum scrcpy FPS
|
|
115
|
-
| `--bitrate <bps>` | Scrcpy video bitrate
|
|
116
|
-
| `--wipe-data` | Wipe emulator data before boot
|
|
117
|
-
| `--no-snapshot` | Disable loading/saving emulator snapshots
|
|
118
|
-
| `--cold-boot` | Alias for `--no-snapshot`
|
|
119
|
-
| `--gpu <mode>` | Pass emulator GPU mode
|
|
120
|
-
| `--keep-alive` | Leave a Porthole-booted emulator running on exit
|
|
157
|
+
| Option | Meaning |
|
|
158
|
+
| ----------------------- | ------------------------------------------------------- |
|
|
159
|
+
| `-p, --port <port>` | Preview/control port, default `3200` |
|
|
160
|
+
| `-d, --device <serial>` | Target a running emulator serial; comma-list on `start` |
|
|
161
|
+
| `--host <host>` | Bind address, default `127.0.0.1` |
|
|
162
|
+
| `--no-preview` | Do not open the browser automatically |
|
|
163
|
+
| `--detach` | Start the preview server in the background |
|
|
164
|
+
| `--mjpeg` | Force MJPEG screenshot polling |
|
|
165
|
+
| `-q, --quiet` | Emit one JSON object/array on stdout |
|
|
166
|
+
| `--max-size <px>` | Maximum scrcpy stream dimension |
|
|
167
|
+
| `--max-fps <fps>` | Maximum scrcpy FPS |
|
|
168
|
+
| `--bitrate <bps>` | Scrcpy video bitrate |
|
|
169
|
+
| `--wipe-data` | Wipe emulator data before boot |
|
|
170
|
+
| `--no-snapshot` | Disable loading/saving emulator snapshots |
|
|
171
|
+
| `--cold-boot` | Alias for `--no-snapshot` |
|
|
172
|
+
| `--gpu <mode>` | Pass emulator GPU mode |
|
|
173
|
+
| `--keep-alive` | Leave a Porthole-booted emulator running on exit |
|
|
121
174
|
|
|
122
175
|
Quiet schemas are intentionally simple: `list -q` returns
|
|
123
|
-
`{ "devices": [...], "sessions": [...] }`; `start --detach -q`
|
|
124
|
-
session record with `url`, `pid`, `serial`, `avdName`, `port`, and
|
|
125
|
-
|
|
126
|
-
`{ "ok": true, "
|
|
176
|
+
`{ "devices": [...], "sessions": [...] }`; single-device `start --detach -q`
|
|
177
|
+
returns the session record with `url`, `pid`, `serial`, `avdName`, `port`, and
|
|
178
|
+
`profile`; multi-device detached starts return `{ "sessions": [...] }`; input
|
|
179
|
+
commands return `{ "ok": true, "session": ... }`; `screenshot -q` returns
|
|
180
|
+
`{ "ok": true, "path": "...", "session": ... }`. Pass `-d <serial>` to
|
|
181
|
+
port-scoped commands when a server is streaming more than one emulator.
|
|
127
182
|
|
|
128
183
|
## MCP Setup
|
|
129
184
|
|
|
@@ -147,15 +202,70 @@ claude mcp add porthole -- npx portholejs mcp
|
|
|
147
202
|
```
|
|
148
203
|
|
|
149
204
|
Useful MCP tools: `list_devices`, `boot_device`, `wait_for_boot`,
|
|
150
|
-
`attach_device`, `tap`, `
|
|
151
|
-
`
|
|
152
|
-
`
|
|
205
|
+
`attach_device`, `tap`, `swipe`, `long_press`, `scroll`, `key`, `remote`,
|
|
206
|
+
`type_text`, `screenshot`, `assert_screen`, `start_recording`, `stop_recording`,
|
|
207
|
+
`dump_ui`, `get_focused`, `focus_on`, `find_element`, `wait_for`, `open_url`,
|
|
208
|
+
`stop_app`, `clear_app`, `get_crashes`, `read_logcat`, and `install_apk`.
|
|
209
|
+
|
|
210
|
+
## Agent Skill
|
|
211
|
+
|
|
212
|
+
Porthole ships an agent skill (`skills/porthole/`) that teaches agents the full workflow: doctor triage,
|
|
213
|
+
boot/attach, semantic UI assertions, TV navigation, and cleanup.
|
|
214
|
+
|
|
215
|
+
Install it by copying the skill directory into your skills folder — for the
|
|
216
|
+
current project:
|
|
217
|
+
|
|
218
|
+
```sh
|
|
219
|
+
mkdir -p .claude/skills
|
|
220
|
+
cp -r node_modules/portholejs/skills/porthole .claude/skills/
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
or for all your projects:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
mkdir -p ~/.claude/skills
|
|
227
|
+
cp -r node_modules/portholejs/skills/porthole ~/.claude/skills/
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
From a clone of this repository, copy `skills/porthole` instead. Claude Code
|
|
231
|
+
picks the skill up automatically; ask it to "test my app on the Android
|
|
232
|
+
emulator" and it will follow the Porthole workflow.
|
|
233
|
+
|
|
234
|
+
## GitHub Action
|
|
235
|
+
|
|
236
|
+
Porthole ships an in-repo GitHub Action for CI smoke tests. It wraps
|
|
237
|
+
`reactivecircus/android-emulator-runner`, starts `npx --yes portholejs` against
|
|
238
|
+
the booted emulator, and runs your script while the emulator is alive.
|
|
239
|
+
|
|
240
|
+
```yaml
|
|
241
|
+
jobs:
|
|
242
|
+
tv-smoke:
|
|
243
|
+
runs-on: ubuntu-latest
|
|
244
|
+
steps:
|
|
245
|
+
- uses: actions/checkout@v4
|
|
246
|
+
- id: porthole
|
|
247
|
+
uses: giolaq/porthole@main
|
|
248
|
+
with:
|
|
249
|
+
avd-name: porthole-tv
|
|
250
|
+
api-level: "35"
|
|
251
|
+
profile: tv
|
|
252
|
+
script: |
|
|
253
|
+
mkdir -p artifacts
|
|
254
|
+
"$PORTHOLE_CLI" focus-on "Library" --select -q -p "$PORTHOLE_PORT"
|
|
255
|
+
"$PORTHOLE_CLI" screenshot -o artifacts/library.png -q -p "$PORTHOLE_PORT"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The action exposes `url`, `serial`, and `port` outputs, and also sets
|
|
259
|
+
`PORTHOLE_URL`, `PORTHOLE_SERIAL`, `PORTHOLE_PORT`, and `PORTHOLE_CLI` for the
|
|
260
|
+
smoke script. See `docs/github-action-example.yml` for a fuller `focus-on` /
|
|
261
|
+
`assert-screen` workflow that uploads screenshot artifacts on failure.
|
|
153
262
|
|
|
154
263
|
## Browser UI
|
|
155
264
|
|
|
156
|
-
The preview shows
|
|
265
|
+
The preview shows device tabs, stream status, screenshots, copy-to-clipboard,
|
|
157
266
|
stream stats, logcat, drag-and-drop APK install/file push, phone hardware keys,
|
|
158
|
-
and a TV D-pad remote.
|
|
267
|
+
and a TV D-pad remote. Switching tabs re-subscribes to that device's stream
|
|
268
|
+
without reloading the page. TV sessions reject touch input server-side.
|
|
159
269
|
|
|
160
270
|
Video uses WebCodecs by default. Browsers without `VideoDecoder` automatically
|
|
161
271
|
fall back to `/stream.mjpeg`, and `--mjpeg` or `?video=mjpeg` forces that mode.
|