Skip to content

Commit 877dfc9

Browse files
authored
refactor: simplify SimDeck service lifecycle
Simplifies the SimDeck service lifecycle, moves the package layout forward, hardens long-running stream/AX behavior, refreshes docs, and adds a CI WebRTC software stream benchmark.
1 parent 1827060 commit 877dfc9

39 files changed

Lines changed: 1685 additions & 654 deletions

.github/workflows/ci.yml

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
name: Rust lint and unit tests
1919
runs-on: macos-15
2020
env:
21-
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
21+
DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer
2222

2323
steps:
2424
- uses: actions/checkout@v4
@@ -124,7 +124,7 @@ jobs:
124124
name: Build integration artifacts
125125
runs-on: macos-15
126126
env:
127-
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
127+
DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer
128128

129129
steps:
130130
- uses: actions/checkout@v4
@@ -184,7 +184,7 @@ jobs:
184184
name: CLI simulator integration
185185
runs-on: macos-15
186186
env:
187-
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
187+
DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer
188188
needs:
189189
- rust
190190
- client
@@ -216,7 +216,7 @@ jobs:
216216
name: JS API simulator integration
217217
runs-on: macos-15
218218
env:
219-
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
219+
DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer
220220
needs:
221221
- rust
222222
- client
@@ -242,6 +242,47 @@ jobs:
242242
- name: JS API simulator integration tests
243243
run: npm run test:integration:js-api
244244

245+
integration-webrtc:
246+
name: WebRTC stream benchmark
247+
runs-on: macos-15
248+
timeout-minutes: 20
249+
env:
250+
DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer
251+
needs:
252+
- rust
253+
- client
254+
- packages
255+
- build-artifacts
256+
257+
steps:
258+
- uses: actions/checkout@v4
259+
260+
- uses: actions/setup-node@v4
261+
with:
262+
node-version: 20
263+
264+
- name: Download integration artifacts
265+
uses: actions/download-artifact@v4
266+
with:
267+
name: simdeck-integration-artifacts
268+
path: .
269+
270+
- name: Make CLI executable
271+
run: chmod +x build/simdeck build/simdeck-bin
272+
273+
- name: WebRTC software stream benchmark
274+
run: npm run test:integration:webrtc
275+
env:
276+
# The service still targets 60 fps at full device resolution. Hosted
277+
# macOS software encode currently sustains roughly 26-30 decoded fps,
278+
# so CI gates the stable hosted-runner floor while local runs can keep
279+
# using the stricter 55+ fps thresholds.
280+
SIMDECK_E2E_MIN_DECODED_FPS: "24"
281+
SIMDECK_E2E_MIN_PRESENTED_FPS: "24"
282+
SIMDECK_E2E_MIN_RECEIVED_FPS: "55"
283+
SIMDECK_E2E_WEBRTC_MS: "20000"
284+
SIMDECK_INTEGRATION_DEVICE_TYPE: iPhone SE (3rd generation)
285+
245286
integration-android:
246287
name: Android emulator integration
247288
runs-on: ubuntu-latest

AGENTS.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,27 +111,31 @@ hydrates root and `packages/client` `node_modules` plus
111111
falling back to `npm ci` for missing package installs and ensuring Homebrew
112112
`pkgconf`/`x264` are available for native builds. Its Run action executes
113113
`npm run codex:run`, which builds the CLI and client, saves fresh caches, and
114-
restarts the workspace-local daemon.
114+
restarts the local service.
115115

116-
Run the local daemon:
116+
Run the local service:
117117

118118
```sh
119119
./build/simdeck
120-
./build/simdeck daemon start --port 4311
120+
./build/simdeck -p 4311
121121
```
122122

123-
Running without a subcommand starts a foreground workspace daemon, prints local and LAN HTTP URLs, prints a six-digit pairing code for LAN browsers, and stops when the command exits, when you press `q`, or when you press Ctrl-C. If the always-on service is active on 4310, running without a subcommand or running `simdeck ui` prints the existing service endpoints instead of starting a project daemon. Pass a simulator name or UDID as the only argument to select it by default in the UI. Use `./build/simdeck -d`, `./build/simdeck -k`, and `./build/simdeck -r` as detached start, kill, and restart shortcuts.
123+
Running without a subcommand starts or reuses the background service, prints
124+
local and LAN HTTP URLs, and prints a six-digit pairing code for LAN browsers.
125+
Pass a simulator name or UDID as the only argument to select it by default in
126+
the UI. Use `./build/simdeck -a` or `./build/simdeck pair` when the service
127+
should be registered as a LaunchAgent.
124128

125129
Use software H.264 when macOS screen recording starves the hardware encoder:
126130

127131
```sh
128-
./build/simdeck daemon start --port 4311 --video-codec h264-software
132+
./build/simdeck daemon restart --video-codec h264-software
129133
```
130134

131135
For LAN access:
132136

133137
```sh
134-
./build/simdeck daemon start --port 4311 --bind 0.0.0.0 --advertise-host 192.168.1.50
138+
./build/simdeck -p 4311 --bind 0.0.0.0 --advertise-host 192.168.1.50
135139
```
136140

137141
Useful direct commands:

README.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ To focus a specific simulator by name or UDID, pass it as the only argument:
6666
simdeck "iPhone 17 Pro Max"
6767
```
6868

69-
`simdeck -d` for detached start, `simdeck -k` to kill the background daemon, and `simdeck -r` to restart it.
69+
Use `simdeck --open` to open the browser automatically, `simdeck -p 4311` to
70+
use a non-default port, and `simdeck -a` to register the service for login
71+
autostart.
7072

7173
The served loopback browser UI receives the generated API access token automatically.
7274
LAN clients should pair with the printed code before receiving the API cookie.
@@ -77,20 +79,17 @@ For pairing with SimDeck iOS app:
7779
simdeck pair
7880
```
7981

80-
This starts or refreshes the global LaunchAgent-backed SimDeck service, prints
82+
This starts or refreshes the LaunchAgent-backed SimDeck service, prints
8183
local, LAN, and Tailscale URLs when available, and shows a QR code with a
8284
`simdeck://pair` link. The QR contains the pairing code plus all detected
8385
non-loopback addresses, so pairing once can save both the LAN and Tailscale
8486
routes with the same service token.
8587
Normal service restarts preserve that token so paired clients stay connected.
8688
Use `simdeck service reset` only when you want to rotate the service token and
8789
restart the LaunchAgent.
88-
The LaunchAgent service uses port 4310. Project daemons start at port 4311 and
89-
probe upward when that port is busy. When the service is active, `simdeck` and
90-
`simdeck ui` print the existing service endpoints instead of starting a project
91-
daemon; use the `daemon` subcommand when you explicitly want a workspace daemon.
90+
The service uses port 4310 unless you pass `-p` or `--port`.
9291

93-
CLI commands automatically use the same warm daemon:
92+
CLI commands automatically use the same warm service:
9493

9594
```sh
9695
simdeck list
@@ -195,8 +194,8 @@ try {
195194
}
196195
```
197196

198-
`connect()` starts the project daemon when needed, reuses it when it is already
199-
healthy, and only stops daemons it started itself. Pass `udid` to `connect()`
197+
`connect()` starts the SimDeck service when needed and reuses it when it is
198+
already healthy. Pass `udid` to `connect()`
200199
to make it the default for session methods; each method still accepts an
201200
explicit UDID as the first argument when needed. Query helpers such as
202201
`tree()`, `query()`, `waitFor()`, `assert()`, and selector `tapElement()`
@@ -236,7 +235,7 @@ import "expo-router/entry";
236235
Import it before `expo-router/entry` or `AppRegistry.registerComponent(...)`
237236
so the package can capture React Fiber commits. The auto entrypoint no-ops
238237
outside development, reads `EXPO_PUBLIC_SIMDECK_PORT` when present, and
239-
otherwise scans common SimDeck daemon ports.
238+
otherwise scans common SimDeck service ports.
240239

241240
## Flutter Inspector
242241

actions/run-android-comment-session/action.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,7 @@ runs:
338338
SIMDECK_REALTIME_MIN_BITRATE="${stream_min_bitrate}" \
339339
SIMDECK_REALTIME_BITS_PER_PIXEL="${stream_bits_per_pixel}" \
340340
SIMDECK_LOCAL_STREAM_FPS="${stream_fps}" \
341-
simdeck daemon run \
342-
--project-root "${GITHUB_WORKSPACE}" \
341+
simdeck service run \
343342
--metadata-path "${metadata_path}" \
344343
--port "${SIMDECK_PORT}" \
345344
--bind 127.0.0.1 \

actions/run-ios-comment-session/action.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,7 @@ runs:
302302
SIMDECK_REALTIME_MIN_BITRATE="${stream_min_bitrate}" \
303303
SIMDECK_REALTIME_BITS_PER_PIXEL="${stream_bits_per_pixel}" \
304304
SIMDECK_LOCAL_STREAM_FPS="${stream_fps}" \
305-
simdeck daemon run \
306-
--project-root "${GITHUB_WORKSPACE}" \
305+
simdeck service run \
307306
--metadata-path "${metadata_path}" \
308307
--port "${SIMDECK_PORT}" \
309308
--bind 127.0.0.1 \

docs/api/health.md

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Health and metrics
22

3-
Use these endpoints to check whether a daemon is reachable and to diagnose stream performance.
3+
Use these endpoints to check whether the service is reachable and to diagnose stream performance.
44

55
## Health
66

@@ -36,18 +36,18 @@ Example:
3636

3737
Important fields:
3838

39-
| Field | Meaning |
40-
| --------------- | --------------------------------------------------------- |
41-
| `ok` | Server is alive |
42-
| `serverId` | Stable identity for the current daemon token |
43-
| `advertiseHost` | Host/IP the daemon advertises for non-local clients |
44-
| `hostId` | Stable hashed identity for the Mac hardware host |
45-
| `hostName` | Local host name for grouping LAN/Tailscale/Bonjour URLs |
46-
| `httpPort` | Port serving UI and API |
47-
| `serverKind` | `launchAgent`, `workspace`, `foreground`, or `standalone` |
48-
| `videoCodec` | Requested codec mode: `auto`, `hardware`, or `software` |
49-
| `streamQuality` | Active stream profile and limits |
50-
| `webRtc` | ICE settings the browser should use |
39+
| Field | Meaning |
40+
| --------------- | ------------------------------------------------------- |
41+
| `ok` | Server is alive |
42+
| `serverId` | Stable identity for the current service token |
43+
| `advertiseHost` | Host/IP the service advertises for non-local clients |
44+
| `hostId` | Stable hashed identity for the Mac hardware host |
45+
| `hostName` | Local host name for grouping LAN/Tailscale/Bonjour URLs |
46+
| `httpPort` | Port serving UI and API |
47+
| `serverKind` | `launchAgent` or `standalone` |
48+
| `videoCodec` | Requested codec mode: `auto`, `hardware`, or `software` |
49+
| `streamQuality` | Active stream profile and limits |
50+
| `webRtc` | ICE settings the browser should use |
5151

5252
When auth is required, the `401` JSON body still includes `serverId`, `advertiseHost`, `hostId`, `hostName`, `httpPort`, and `serverKind` so native clients can group endpoints before pairing.
5353

@@ -64,6 +64,10 @@ Useful fields:
6464
| `latest_first_frame_ms` | First-frame startup time |
6565
| `frames_dropped_server` | Server dropping stale frames to stay current |
6666
| `keyframe_requests` | Stream refresh or recovery activity |
67+
| `stream_pipeline_resets` | Encoder resets after all viewers disconnect |
68+
| `latest_accessibility_snapshot_ms` | Most recent native accessibility duration |
69+
| `max_accessibility_snapshot_ms` | Slowest native accessibility duration |
70+
| `accessibility_snapshot_timeouts` | Native accessibility calls that timed out |
6771
| `active_streams` | Open browser streams |
6872
| `encoders[].encoder.overloadState` | `nominal`, `strained`, or `overloaded` |
6973
| `client_streams` | Recent browser decoder and render reports |

docs/api/rest.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Use the CLI for most automation. Use the API when you are building a custom clie
66

77
## Authentication
88

9-
Browser sessions loaded from the SimDeck server receive auth automatically. Direct callers should send the daemon token from `simdeck daemon status`:
9+
Browser sessions loaded from the SimDeck server receive auth automatically. Direct callers should send the service token from `simdeck daemon status`:
1010

1111
```text
1212
X-SimDeck-Token: <token>

docs/cli/commands.md

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,37 @@
22

33
Replace `simdeck` with `./build/simdeck` when running from a source checkout.
44

5-
## UI and daemon
5+
## UI and service
66

77
| Command | Purpose |
88
| -------------------------------- | ------------------------------------------- |
9-
| `simdeck` | Start a foreground browser session |
9+
| `simdeck` | Start or reuse the service and print URLs |
1010
| `simdeck <name-or-udid>` | Start and select a device |
11-
| `simdeck -d` | Start or reuse the detached project daemon |
12-
| `simdeck -k` | Stop the detached project daemon |
13-
| `simdeck -r` | Restart the detached project daemon |
14-
| `simdeck ui --open` | Open the browser UI from a daemon |
11+
| `simdeck --open` | Open the browser UI |
12+
| `simdeck -p 4311` | Use a non-default service port |
13+
| `simdeck -a` | Register the service for login autostart |
1514
| `simdeck pair` | Show native iOS pairing code and QR |
16-
| `simdeck daemon status` | Show daemon URL, PID, token, and log path |
17-
| `simdeck daemon stop` | Stop the current project daemon |
18-
| `simdeck daemon killall` | Stop all project daemons |
15+
| `simdeck daemon status` | Show service URL, PID, token, and log path |
16+
| `simdeck daemon stop` | Stop the current background service |
17+
| `simdeck daemon restart` | Restart the current background service |
1918
| `simdeck service on/off/restart` | Manage the optional always-on macOS service |
2019

2120
Examples:
2221

2322
```sh
24-
simdeck ui --port 4320 --open
25-
simdeck ui --open
23+
simdeck -p 4320 --open
24+
simdeck --open
2625
simdeck pair
2726
simdeck daemon restart --video-codec software --stream-quality low
2827
```
2928

30-
`simdeck pair` uses the global LaunchAgent-backed service instead of a
31-
project-local daemon. It binds the service for LAN access, preserves an existing
32-
service token and pairing code when present, detects LAN and Tailscale IPv4
33-
addresses, and prints a `simdeck://pair` QR for the native iOS app. The service
34-
uses port 4310; workspace daemons start at 4311 and probe upward.
29+
`simdeck` starts or reuses one local service. It uses port 4310 unless you pass
30+
`-p` or `--port`. Normal commands reuse that same warm service.
3531

36-
When the service is active, `simdeck` and `simdeck ui` print the existing
37-
service endpoints instead of launching a project daemon. Use `simdeck daemon
38-
start` or `simdeck daemon restart` when you explicitly want a workspace daemon.
32+
`simdeck pair` installs or refreshes the LaunchAgent-backed service. It binds
33+
the service for LAN access, preserves an existing service token and pairing code
34+
when present, detects LAN and Tailscale IPv4 addresses, and prints a
35+
`simdeck://pair` QR for the native iOS app.
3936

4037
`simdeck service restart` also preserves the installed service token so native
4138
clients remain paired across service restarts. Use `simdeck service reset` to
@@ -158,7 +155,7 @@ Use `wait-for` or `assert` steps instead of fixed sleeps when possible.
158155

159156
## Maestro YAML
160157

161-
Run common Maestro flows through SimDeck's daemon-backed iOS Simulator API:
158+
Run common Maestro flows through SimDeck's service-backed iOS Simulator API:
162159

163160
```sh
164161
simdeck maestro test flow.yaml --artifacts-dir artifacts/maestro

docs/cli/flags.md

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,36 @@ Pass `--help` to any command for the generated flag list:
55
```sh
66
simdeck --help
77
simdeck tap --help
8-
simdeck daemon start --help
8+
simdeck daemon restart --help
99
```
1010

1111
## Global
1212

13-
| Flag | Env | Purpose |
14-
| --------------------- | -------------------- | -------------------------------- |
15-
| `--server-url <url>` | `SIMDECK_SERVER_URL` | Target a specific running daemon |
16-
| `--device <selector>` | `SIMDECK_DEVICE` | One-off simulator override |
13+
| Flag | Env | Purpose |
14+
| --------------------- | -------------------- | --------------------------------- |
15+
| `--server-url <url>` | `SIMDECK_SERVER_URL` | Target a specific running service |
16+
| `--device <selector>` | `SIMDECK_DEVICE` | One-off simulator override |
1717

1818
`SIMDECK_UDID` is also accepted for compatibility. Device commands resolve in
1919
this order: positional UDID, `--device`, `SIMDECK_DEVICE`, `SIMDECK_UDID`, the
20-
project default from `simdeck use <udid>`, then auto-inference from the daemon.
20+
project default from `simdeck use <udid>`, then auto-inference from the service.
2121

2222
## Server options
2323

24-
Used by `simdeck ui`, `daemon start`, `daemon restart`, `service on`, and `service restart`.
25-
26-
| Flag | Default | Notes |
27-
| ---------------------------- | -------------------------------------- | --------------------------------------------------------------------------------- |
28-
| `--port <port>` | `4311` for daemons, `4310` for service | HTTP port. Daemons probe upward when busy |
29-
| `--bind <ip>` | `127.0.0.1` | Use `0.0.0.0` or `::` for LAN access |
30-
| `--advertise-host <host>` | detected | Host printed for remote browsers |
31-
| `--client-root <path>` | bundled client | Static client directory |
32-
| `--video-codec <mode>` | `auto` | `auto`, `hardware`, or `software` |
33-
| `--stream-quality <profile>` | `full` | `full`, `balanced`, `economy`, `low`, `tiny`, `ci-software`, and related profiles |
34-
| `--local-stream-fps <fps>` | `60` | Local stream frame target |
35-
| `--low-latency` | off | Conservative software H.264 profile |
36-
| `--open` | off | `ui` only |
24+
Used by `simdeck`, `daemon start`, `daemon restart`, `service on`, and `service restart`.
25+
26+
| Flag | Default | Notes |
27+
| ---------------------------- | -------------- | --------------------------------------------------------------------------------- |
28+
| `--port <port>` / `-p` | `4310` | HTTP port |
29+
| `--bind <ip>` | `127.0.0.1` | Use `0.0.0.0` or `::` for LAN access |
30+
| `--advertise-host <host>` | detected | Host printed for remote browsers |
31+
| `--client-root <path>` | bundled client | Static client directory |
32+
| `--video-codec <mode>` | `auto` | `auto`, `hardware`, or `software` |
33+
| `--stream-quality <profile>` | `full` | `full`, `balanced`, `economy`, `low`, `tiny`, `ci-software`, and related profiles |
34+
| `--local-stream-fps <fps>` | `60` | Local stream frame target |
35+
| `--low-latency` | off | Conservative software H.264 profile |
36+
| `--open` | off | Open the browser after starting the service |
37+
| `--autostart` / `-a` | off | Register the service as a macOS LaunchAgent |
3738

3839
## `describe`
3940

0 commit comments

Comments
 (0)