# quest-dev

CLI tools for Meta Quest development — stay awake, screenshots, URL opening, logcat, and battery via ADB.

## Installation

```bash
npm install -g @myerscarpenter/quest-dev
```

To install a prerelease build (newer features, may have bugs):

```bash
npm install -g @myerscarpenter/quest-dev@next
```

## Prerequisites

- **ADB** - Android Debug Bridge must be installed and in your PATH
  - macOS: `brew install android-platform-tools`
  - Linux: `sudo apt install adb`
  - Windows: [Download Platform Tools](https://developer.android.com/tools/releases/platform-tools)

- **Quest Device** - Connected via USB with [Developer Mode and USB Debugging enabled](https://developers.meta.com/horizon/documentation/native/android/mobile-device-setup/)

- **cdp-cli** (optional) - For smart tab reuse in the `open` command

  ```bash
  npm install -g @myerscarpenter/cdp-cli
  ```

## Casting Setup

quest-dev can cast your Quest's screen to your computer. This requires a casting APK that ships inside Meta Quest Developer Hub (MQDH).

**One-time setup:**

```bash
quest-dev setup-cast
```

On macOS, this auto-detects MQDH if it's installed in `/Applications`. Otherwise, download MQDH first and point to it:

```bash
# macOS — .app bundle (easiest)
quest-dev setup-cast "/Applications/Meta Quest Developer Hub.app"

# macOS — .dmg download
quest-dev setup-cast ~/Downloads/MetaQuestDeveloperHub.dmg

# Windows/Linux — .exe.zip download (requires 7z)
quest-dev setup-cast ~/Downloads/Meta-Quest-Developer-Hub.exe.zip
```

Download MQDH from: https://developer.oculus.com/meta-quest-developer-hub

The APK is extracted once and cached at `~/.local/share/quest-dev/`. When you start casting, it's auto-installed on your Quest if needed.

## Usage

### Screenshot

Take a screenshot from Quest and save it locally:

```bash
quest-dev screenshot ./screenshot.jpg
quest-dev screenshot ~/Pictures/quest-capture.jpg
```

This uses Quest's native screenshot service which captures the full VR view, including immersive content that CDP screenshots can't capture.

### Open URL

Open a URL in Quest Browser with automatic ADB port forwarding:

```bash
quest-dev open http://localhost:3000/
quest-dev open http://localhost:9004/my-xr-app/
```

This command:

1. Sets up ADB reverse port forwarding (Quest → Host) so the Quest can reach your dev server
2. Sets up ADB forward port forwarding (Host → Quest) for CDP communication
3. If Quest Browser is already running with the URL, reloads the tab
4. If a blank tab exists, navigates it to the URL
5. Otherwise, launches Quest Browser with the URL

Port forwarding is idempotent - safe to run multiple times without issues.

### Stay Awake

Keep your Quest awake during development by disabling autosleep, guardian, and system dialogs using the Meta Scriptable Testing API.

```bash
quest-dev stay-awake --pin 1234
```

**Config file** — avoid passing `--pin` every time by creating `.quest-dev.json` in your project or `~/.config/quest-dev/config.json` globally:

```json
{
  "pin": "1234",
  "idleTimeout": 300000,
  "lowBattery": 10
}
```

**Flags:**

- `--status` — show current Scriptable Testing properties without changing anything
- `--disable` — manually disable test mode (restore all properties)
- `--idle-timeout <ms>` — auto-exit after inactivity (default: 300000ms / 5min)
- `--low-battery <percent>` — auto-exit when battery drops below threshold (default: 10%)
- `--verbose` — log battery status every 60s instead of only on 5% boundary crossings

**Activity signaling** — reset the idle timer from another process:

```bash
kill -USR1 $(cat ~/.quest-dev-stay-awake.pid)
```

**Watchdog** — a child process monitors the parent PID and automatically restores Quest settings if the parent dies (terminal close, `kill`, etc.), preventing a drained battery.

Requires Quest OS v44+ and your Meta Store PIN.

### Logcat

Capture Android logcat output for Quest debugging. Quest's ring buffer fills fast under VR load, so always start capture before testing.

```bash
quest-dev logcat start              # start capturing (clears ring buffer first)
quest-dev logcat start --filter "Unity:V"  # capture with filter
quest-dev logcat status             # check if capturing
quest-dev logcat tail               # tail(1) the current log file (defaults to last 10 lines)
quest-dev logcat tail -f            # stream until Ctrl-C
quest-dev logcat tail -n 500        # last 500 lines and exit (great for scripts)
quest-dev logcat stop               # stop capturing, show file info
```

Log files are saved to `./logs/logcat/` with a `latest.txt` symlink.

### Battery

Show Quest battery level and charging status:

```bash
quest-dev battery
```

## How It Works

- **screenshot**: Triggers `com.oculus.metacam/.capture.CaptureService` via ADB, waits for the JPEG to be fully written (by checking for the EOI marker), pulls the file, then deletes it from the Quest

- **open**: Uses ADB for port forwarding and browser launching. If `cdp-cli` is installed, it uses CDP to intelligently reuse existing tabs instead of opening new ones.

- **stay-awake**: Uses the Meta Scriptable Testing API (`content://com.oculus.rc`) to disable autosleep, guardian, and dialogs. A watchdog child process ensures cleanup on exit.

- **logcat**: Spawns a background `adb logcat` process writing to timestamped files. Clears the ring buffer on start to avoid stale data.

- **battery**: Reads battery level and charging state via `adb shell dumpsys battery`.

## Multiple devices

quest-dev supports controlling several Quests from one machine at the same time —
each command targets a device, and all per-device state (daemon, ports, logs,
stay-awake) is keyed on the device's stable **hardware serial**, so two agents never
collide.

### Selecting a device

Pass `--device <ref>` to any command, where `<ref>` is an **alias**, a raw **address**
(`127.0.0.1:5555`, `quest3.home.arap:5555`), or a **serial**. You can also set
`QUEST_DEVICE` once in your shell instead of repeating the flag:

```bash
export QUEST_DEVICE=quest3
quest-dev stay-awake
quest-dev open http://localhost:3000/
```

Resolution order: `--device` → `$QUEST_DEVICE` → saved `config.device` → the single
connected device (if exactly one). With one device connected, no flag is needed.

### Aliases (for devices that move)

A device's address changes as you move it (SSH tunnel at the office, `*.home.arap` on
the LAN). Register a stable alias once, update its address whenever it moves, and refer
to it by name thereafter:

```bash
quest-dev device set quest3 127.0.0.1:5555     # connects, records the serial
quest-dev device set quest3 quest3.home.arap:5555   # later, after it moved
quest-dev device list                          # show aliases (alias, address, serial, daemon state)
quest-dev device rm quest3
```

The alias maps to a current address; the underlying hardware serial is what keeps each
device's daemon, logs, and ports consistent across moves.

### Inspecting a device's ports

```bash
quest-dev device info quest3            # human-readable
quest-dev device info quest3 --json     # agent-friendly: serial, address, daemon/CDP/cast ports, stay-awake, battery
```

Each device gets its own daemon on an OS-assigned HTTP port and a deterministic
per-serial CDP port — `device info` reports the live values.

### Where state lives (XDG)

- Daemon registry + PID files: `$XDG_RUNTIME_DIR/quest-dev/` (falls back to
  `~/.local/state/quest-dev/run/` when `XDG_RUNTIME_DIR` is unset, e.g. on macOS).
- Logcat output: `$XDG_STATE_HOME/quest-dev/logcat/<serial>/` (default
  `~/.local/state/quest-dev/logcat/<serial>/`).
- Aliases + config: `$XDG_CONFIG_HOME/quest-dev/` (default `~/.config/quest-dev/`).

## Development

```bash
# Install dependencies
pnpm install

# Build
pnpm run build

# Run locally
node build/index.js screenshot ./test.jpg
node build/index.js open http://localhost:3000/
```

## License

MIT
