worktree-device
v0.2.1
Published
Assign Android targets and Metro ports per git worktree
Maintainers
Readme
worktree-device
worktree-device is a local CLI for assigning Android targets and Metro ports per workspace.
It is intended for projects that run multiple git worktrees or local checkouts at the same time.
The tool is project-agnostic. Projects opt in with a .worktree-device.toml file that declares
which resources each command needs.
Requirements and support
- macOS or Linux with a POSIX shell
- Python 3 available as
python3 - Android SDK
adbandemulatorcommands for Android target allocation - Xcode Command Line Tools on macOS when using the emulator label
- A project-level
.worktree-device.toml
npm Installation
Install an exact local development version so every checkout uses the same CLI:
npm install --save-dev --save-exact [email protected]
npm exec -- worktree-device doctorOr install it globally:
npm install --global [email protected]
worktree-device doctorLocal standalone layout
Current local layout:
~/.local/bin/worktree-device
~/.config/worktree-device/config.toml
~/.local/state/worktree-device/state.sqlite
~/.local/share/worktree-device/README.md
~/.local/share/worktree-device/macos-emulator-label.swift
~/Applications/Worktree Device Overlay.app~/.local/bin must be on PATH.
Concept
worktree-device does not infer behavior from command names. It only reads the command's declared
resource requirements:
requires = []
Run the command without allocation.
requires = ["metro.port"]
Reserve a Metro port and inject METRO_PORT/RCT_METRO_PORT.
requires = ["android.target"]
Reserve an Android target and inject ANDROID_SERIAL.
requires = ["android.target", "metro.port"]
Reserve both, configure adb reverse, then run the command.The state database stores reservations. It is not treated as absolute truth; each run rechecks adb devices, registered AVDs, active ports, and process state.
Global Config
Global defaults live at:
~/.config/worktree-device/config.tomlExample:
[android]
adb = "adb"
emulator = "emulator"
physical = "exclude"
boot_avd_if_needed = true
boot_timeout_sec = 180
[metro]
port_start = 8081
port_end = 8099
[state]
path = "~/.local/state/worktree-device/state.sqlite"
[ui]
emulator_label = trueOn macOS, emulator_label displays a host-side badge above the emulator window when a command
allocates both an emulator and a Metro port. The badge shows the git branch (or a short commit SHA
for detached HEAD) and the Metro port. Its reload button sends Expo's reload message only to the
badge's allocated Metro server. Set emulator_label to false to disable the badge.
The CLI builds a background-only Worktree Device Overlay.app in ~/Applications. The first run
registers that named app with macOS and asks once for Accessibility permission. It does not appear
in the Dock or menu bar. With permission enabled, the app follows emulator window events. If Accessibility
window attributes are unavailable, it falls back to querying only the target CGWindowID at 10 Hz
instead of scanning all windows. Because local builds use ad-hoc signing, macOS may request approval
again after an update. The app exits when the owning worktree-device run process or emulator ends
and hides the badge when the target window is not visible. Overlay failures never change the
configured child command's exit status.
Project Config
Each participating project or worktree should add .worktree-device.toml at its workspace root:
version = 1
[workspace]
root = "git"
[android]
physical = "exclude"
boot_avd_if_needed = true
[metro]
port_start = 8081
port_end = 8099
[commands.androidDev]
run = "npm run android:dev:inner"
requires = ["android.target", "metro.port"]
adb_reverse = true
[commands.androidInstallDebug]
run = "npm run android:install:debug:inner"
requires = ["android.target"]
[commands.metro]
run = "npm run start:inner"
requires = ["metro.port"]run is executed from the detected workspace root unless the command has its own cwd.
CLI
Run a configured command:
worktree-device run androidDevPreview allocation without modifying state, booting an emulator, or running the child command:
worktree-device run androidDev --dry-run --jsonPass extra arguments to the child command:
worktree-device run androidDev -- --clear-cachePrint allocation environment:
worktree-device env androidDev --format shell
worktree-device env androidDev --format jsonInspect local state and Android tooling:
worktree-device list
worktree-device doctor
worktree-device doctor --fixRelease reservations:
worktree-device release
worktree-device release --allPhysical Devices
Physical devices are excluded by default. Override per run:
worktree-device run androidDev --physical allow
worktree-device run androidDev --physical prefer
worktree-device run androidDev --physical only
worktree-device run androidDev --serial <adb-serial>Policy values:
exclude = default; emulator/AVD only
allow = prefer emulator, fallback to physical
prefer = prefer physical, fallback to emulator
only = physical onlyAn explicit --serial is honored when that adb target is online.
Injected Environment
Child commands receive the relevant values:
ANDROID_SERIAL
AVD_NAME
METRO_PORT
RCT_METRO_PORT
WORKTREE_DEVICE_WORKSPACE_ID
WORKTREE_DEVICE_WORKSPACE_PATH
WORKTREE_DEVICE_ANDROID_SERIAL
WORKTREE_DEVICE_AVD_NAME
WORKTREE_DEVICE_METRO_PORT
WORKTREE_DEVICE_TARGET_TYPEProject-local scripts should use these variables instead of implementing their own target allocator.
License
UNLICENSED. This package is not open source, and no permission is granted to use, copy, modify, or redistribute its source except as authorized by the copyright holder.
