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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,14 @@ routes with the same service token.
Normal service restarts preserve that token so paired clients stay connected.
Use `simdeck service reset` only when you want to rotate the service token and
restart the LaunchAgent.
The service uses port 4310 unless you pass `-p` or `--port`.
The service uses port 4310 unless you pass `-p` or `--port`, or set a default
in `~/.simdeck/config.json`.
SimDeck-owned Android emulator boots use host GPU rendering by default; use
`simdeck service restart --android-gpu auto` or
`--android-gpu swiftshader_indirect` only as a machine-specific fallback.
Managed Android boots also add `-no-audio` by default. Set
`android.disableAudio` to `false` in `~/.simdeck/config.json` when you need
emulator audio.
Use `simdeck service kill` when you want to stop every SimDeck service process,
including services started from another checkout or installed binary.

Expand All @@ -114,6 +118,7 @@ simdeck --device <other-udid> describe --format agent --max-depth 2
simdeck list
simdeck use <udid>
simdeck boot <udid>
simdeck boot android:<avd-name> --android-emulator-arg=-no-snapshot
simdeck shutdown
simdeck erase
simdeck install /path/to/App.app
Expand Down
16 changes: 15 additions & 1 deletion actions/run-android-comment-session/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ inputs:
description: Android SDK build-tools version used to inspect APK metadata.
required: false
default: "36.0.0"
android_emulator_args:
description: Extra Android emulator startup arguments, one argument per line.
required: false
default: ""
public_health_check:
description: Verify the public Cloudflare Tunnel health endpoint before continuing.
required: false
Expand Down Expand Up @@ -123,6 +127,7 @@ runs:
INPUT_ANDROID_TARGET_VALUE: ${{ inputs.android_target }}
INPUT_ANDROID_ARCH_VALUE: ${{ inputs.android_arch }}
INPUT_ANDROID_BUILD_TOOLS_VALUE: ${{ inputs.android_build_tools }}
INPUT_ANDROID_EMULATOR_ARGS_VALUE: ${{ inputs.android_emulator_args }}
INPUT_PUBLIC_HEALTH_CHECK_VALUE: ${{ inputs.public_health_check }}
INPUT_CI_PROXY_URL_VALUE: ${{ inputs.ci_proxy_url }}
INPUT_PROXY_LINKS_VALUE: ${{ inputs.proxy_links }}
Expand Down Expand Up @@ -163,6 +168,7 @@ runs:
write_env "SIMDECK_ANDROID_TARGET" "${INPUT_ANDROID_TARGET_VALUE}"
write_env "SIMDECK_ANDROID_ARCH" "${INPUT_ANDROID_ARCH_VALUE}"
write_env "SIMDECK_ANDROID_BUILD_TOOLS" "${INPUT_ANDROID_BUILD_TOOLS_VALUE}"
write_env "SIMDECK_ANDROID_EMULATOR_ARGS" "${INPUT_ANDROID_EMULATOR_ARGS_VALUE}"
write_env "SIMDECK_CI_PROXY_URL" "${INPUT_CI_PROXY_URL_VALUE}"
write_env "SIMDECK_PROXY_LINKS" "${INPUT_PROXY_LINKS_VALUE}"
write_env "SIMDECK_SESSION_PASSWORD" "${INPUT_SESSION_PASSWORD_VALUE}"
Expand Down Expand Up @@ -656,7 +662,15 @@ runs:
echo "ANDROID_UDID=${udid}" >> "${GITHUB_ENV}"
echo "udid=${udid}" >> "${GITHUB_OUTPUT}"
echo "Booting ${udid}"
simdeck --server-url "http://127.0.0.1:${SIMDECK_PORT}" boot "${udid}"
boot_args=()
if [[ -n "${SIMDECK_ANDROID_EMULATOR_ARGS:-}" ]]; then
while IFS= read -r arg; do
if [[ -n "${arg//[[:space:]]/}" ]]; then
boot_args+=(--android-emulator-arg="${arg}")
fi
done <<< "${SIMDECK_ANDROID_EMULATOR_ARGS}"
fi
simdeck --server-url "http://127.0.0.1:${SIMDECK_PORT}" boot "${udid}" "${boot_args[@]}"
date +%s > /tmp/sim-boot-end

- name: Update status comment with booted emulator URL
Expand Down
23 changes: 22 additions & 1 deletion docs/api/rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@ Device IDs come from `/api/simulators`. Android IDs use the `android:` prefix.
Booted devices are listed first. Paired iPhone and Apple Watch entries include
`pairedWatchUDID` or `pairedPhoneUDID` when CoreSimulator reports a pairing.

Android emulator boot accepts optional startup arguments:

```json
{
"androidEmulatorArgs": ["-no-snapshot"],
"androidDisableAudio": true
}
```

`androidDisableAudio` inherits the configured `android.disableAudio` value,
which defaults to `true`. SimDeck also reads global Android defaults from
`~/.simdeck/config.json` before applying request arguments. Request arguments
are appended after config arguments, so one-off boots can override safe defaults
such as `-gpu`.

SimDeck appends its own selected AVD, emulator console/ADB ports, and
shared-video flags around those arguments; `-avd`, `@AVD`, `-ports`, and
`-share-vid` are reserved.

Create requests use identifiers from `/api/simulators/create-options`. New
devices are booted before the response is returned. If an iOS simulator is
created with `pairedWatch`, the watch is created, paired, and booted too.
Expand Down Expand Up @@ -97,7 +116,9 @@ Android:
"platform": "android",
"name": "Pixel_8_API_36",
"deviceTypeIdentifier": "pixel_8",
"runtimeIdentifier": "system-images;android-36;google_apis;arm64-v8a"
"runtimeIdentifier": "system-images;android-36;google_apis;arm64-v8a",
"androidEmulatorArgs": ["-no-snapshot"],
"androidDisableAudio": true
}
```

Expand Down
5 changes: 5 additions & 0 deletions docs/cli/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ simdeck list
simdeck list --format json
simdeck use <udid>
simdeck boot <udid>
simdeck boot android:<avd-name> --android-emulator-arg=-no-snapshot
simdeck shutdown
simdeck erase
```
Expand All @@ -68,6 +69,10 @@ inventory, including paths and display metadata.
directory. After that, most device commands can omit `<udid>`; explicit UDIDs
still override the default.

For Android emulator startup flags, repeat `--android-emulator-arg=<arg>` on
`simdeck boot`. SimDeck still owns the AVD selector, emulator ports, and
shared-video flag used for the browser stream.

## Apps and URLs

```sh
Expand Down
33 changes: 32 additions & 1 deletion docs/cli/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ project default from `simdeck use <udid>`, then auto-inference from the service.
Used by `simdeck`, `service start`, `service restart`, `service on`, and `service reset`.
When `service restart` is run without `--port`, it preserves the installed
LaunchAgent port or the current singleton service port before falling back to
`4310`.
the configured `~/.simdeck/config.json` service port and then `4310`.

| Flag | Default | Notes |
| ---------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- |
Expand All @@ -40,6 +40,27 @@ LaunchAgent port or the current singleton service port before falling back to
| `--open` | off | Open the browser after starting the service |
| `--autostart` / `-a` | off | Register the service as a macOS LaunchAgent |

## Global config

SimDeck reads optional user defaults from `~/.simdeck/config.json`:

```json
{
"service": {
"port": 4311
},
"android": {
"emulatorArgs": ["-no-snapshot"],
"disableAudio": true
}
}
```

`service.port` applies when a service command omits `--port`. Command-line
flags still win. `android.emulatorArgs` are prepended to Android boot request
arguments, and `android.disableAudio` controls whether SimDeck adds
`-no-audio` to managed Android emulator boots.

## `describe`

Alias: `snapshot`.
Expand All @@ -54,6 +75,16 @@ Alias: `snapshot`.
| `--point <x>,<y>` | Describe the element at a screen point |
| `--direct` | Skip service and use native accessibility directly |

## Device lifecycle

| Command | Useful flags |
| ------- | ----------------------------------------------------------------------------------------------------------------------- |
| `boot` | `--android-emulator-arg=<arg>` for Android emulator startup flags; repeat once per argument, for example `-no-snapshot` |

SimDeck reserves Android emulator target and stream flags such as `-avd`,
`-ports`, and `-share-vid` so browser streaming remains attached to the
selected emulator.

## Input

| Command | Useful flags |
Expand Down
31 changes: 16 additions & 15 deletions docs/guide/github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,21 +188,22 @@ Supported quality values include `tiny`, `low`, `economy`, `fast`, `smooth`, `ba

## Common inputs

| Input | Default | Purpose |
| ------------------- | ----------------------------------- | ------------------------------------------- |
| `bundle_id` | empty | Bundle ID to launch |
| `package_name` | empty | Android package name to launch |
| `build_workflow` | `build-ios-simulator.yml` | Workflow file that uploads the app artifact |
| `artifact_prefix` | `ios-simulator-app` / `android-apk` | Artifact prefix |
| `simdeck_version` | `latest` | npm version or dist-tag |
| `stream_profile` | `tiny` | Default stream quality |
| `simulator_name` | `iPhone 17 Pro` | Preferred simulator |
| `avd_name` | `SimDeck_Pixel_CI` | Preferred Android emulator |
| `keepalive_seconds` | `1800` | Session lifetime after launch |
| `simulator_cache` | `true` | Restore and save simulator cache |
| `proxy_links` | `true` | Post SimDeck CI proxy links |
| `ci_proxy_url` | `https://ci.simdeck.sh` | Optional SimDeck CI proxy URL |
| `session_password` | empty | Optional password for proxy-gated sessions |
| Input | Default | Purpose |
| ----------------------- | ----------------------------------- | ---------------------------------------------- |
| `bundle_id` | empty | Bundle ID to launch |
| `package_name` | empty | Android package name to launch |
| `build_workflow` | `build-ios-simulator.yml` | Workflow file that uploads the app artifact |
| `artifact_prefix` | `ios-simulator-app` / `android-apk` | Artifact prefix |
| `simdeck_version` | `latest` | npm version or dist-tag |
| `stream_profile` | `tiny` | Default stream quality |
| `simulator_name` | `iPhone 17 Pro` | Preferred simulator |
| `avd_name` | `SimDeck_Pixel_CI` | Preferred Android emulator |
| `android_emulator_args` | empty | Extra emulator startup arguments, one per line |
| `keepalive_seconds` | `1800` | Session lifetime after launch |
| `simulator_cache` | `true` | Restore and save simulator cache |
| `proxy_links` | `true` | Post SimDeck CI proxy links |
| `ci_proxy_url` | `https://ci.simdeck.sh` | Optional SimDeck CI proxy URL |
| `session_password` | empty | Optional password for proxy-gated sessions |

## Password-protected links

Expand Down
14 changes: 13 additions & 1 deletion docs/guide/service.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ simdeck -p 4311
`--open` opens the local browser URL. `-p` or `--port` selects a non-default
port; the default is `4310`.

You can set a user default in `~/.simdeck/config.json`:

```json
{
"service": {
"port": 4311
}
}
```

Explicit `--port` flags still win over the config file.

When that port is already used by a SimDeck service from another binary,
`simdeck` leaves it running and uses the next available port. This keeps source
checkout builds fast without touching your installed service.
Expand Down Expand Up @@ -56,7 +68,7 @@ pairing code. `service off` removes the LaunchAgent. `service kill` and
services started by another SimDeck binary.
When `service restart` is run without `--port`, it keeps the installed
LaunchAgent port or the current singleton service port before falling back to
`4310`.
the configured service port and then `4310`.

## Options

Expand Down
35 changes: 35 additions & 0 deletions packages/client/src/api/controls.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { afterEach, describe, expect, it, vi } from "vitest";

import { bootSimulator } from "./controls";

describe("controls", () => {
afterEach(() => {
vi.unstubAllGlobals();
});

it("posts Android emulator startup args when booting", async () => {
const fetchMock = vi.fn(async () => {
return new Response(JSON.stringify({ ok: true, simulator: null }), {
headers: { "content-type": "application/json" },
status: 200,
});
});
vi.stubGlobal("fetch", fetchMock);

await bootSimulator("android:Pixel_8_API_36", {
androidEmulatorArgs: ["-no-snapshot"],
androidDisableAudio: false,
});

expect(fetchMock).toHaveBeenCalledWith(
"/api/simulators/android:Pixel_8_API_36/boot",
expect.objectContaining({
body: JSON.stringify({
androidEmulatorArgs: ["-no-snapshot"],
androidDisableAudio: false,
}),
method: "POST",
}),
);
});
});
7 changes: 4 additions & 3 deletions packages/client/src/api/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { accessTokenFromLocation, apiHeaders, apiRequest } from "./client";
import { apiUrl } from "./config";
import type {
ButtonPayload,
BootPayload,
CrownPayload,
EdgeTouchPayload,
InstallUploadResponse,
Expand Down Expand Up @@ -39,7 +40,7 @@ export interface ScreenRecordingStartResponse {
async function postSimulatorAction(
udid: string,
action: string,
payload?: LaunchPayload | OpenUrlPayload,
payload?: BootPayload | LaunchPayload | OpenUrlPayload,
): Promise<SimulatorMetadata | null> {
if (action === "launch" || action === "open-url") {
const response = await apiRequest<{
Expand All @@ -65,8 +66,8 @@ async function postSimulatorAction(
return "simulator" in response ? response.simulator : null;
}

export function bootSimulator(udid: string) {
return postSimulatorAction(udid, "boot");
export function bootSimulator(udid: string, payload?: BootPayload) {
return postSimulatorAction(udid, "boot", payload);
}

export function shutdownSimulator(udid: string) {
Expand Down
5 changes: 5 additions & 0 deletions packages/client/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ export interface SimulatorResponse {
simulator: SimulatorMetadata;
}

export interface BootPayload {
androidEmulatorArgs?: string[];
androidDisableAudio?: boolean;
}

export interface InstallUploadResponse {
action: "install";
fileName: string;
Expand Down
Loading
Loading