-
Notifications
You must be signed in to change notification settings - Fork 0
device-use v2: standalone iOS workbench + MCP server (AAP-ready) #249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
5d1ec3e
feat(device-use): Phase 1 — scaffold v2 server + frontend
zvadaadam 63b66ad
feat(device-use): Phase 2 — expand engine with xcodebuild, build wrap…
zvadaadam ac09d6d
feat(device-use): Phase 3 — REST API, MCP HTTP endpoint, WS event bus
zvadaadam c704fea
feat(device-use): Phase 4 — React viewer (TopBar, DeviceFrame, Sideba…
zvadaadam 29a71d8
feat(device-use): Phase 5 — CLI cleanup (drop SDK, drop stream CLI, a…
zvadaadam 28aabd2
feat(device-use): Phase 6 — docs, AAP manifest, composite run tool
zvadaadam c79baaa
test(device-use): add end-to-end server harness against real simulator
zvadaadam dd2bce5
fix(device-use): bind server to 0.0.0.0 so IPv6 orphans can't shadow us
zvadaadam 740b865
fix(device-use): boot + set_active_simulator now start the MJPEG stream
zvadaadam 4c8999e
feat(device-use): interactive tap + swipe on the stream
zvadaadam 20582a3
fix(device-use): use canvas + binary WS for interactive stream
zvadaadam 678a5ac
feat(device-use): auto-refresh the elements sidebar after each canvas…
zvadaadam 43b0924
fix(device-use): ref-based tap uses tapEntry (id → label → coords fal…
zvadaadam 5324c77
feat(device-use): hardware-button row + context-menu suppression
zvadaadam fd1bd9a
refactor(device-use): post-cleanup — remove dead code + tighten server
zvadaadam 1f9c00e
refactor(device-use): drop Vol+/Vol− buttons — simbridge doesn't map …
zvadaadam ef6e7ca
fix(device-use): stop the 10s blink — use primitive deps in DeviceFrame
zvadaadam 8f0fb70
fix(device-use): stop the blink at the source — sim-store preserves i…
zvadaadam 01cc5bd
feat(device-use): keyboard input + CI workflow + long-press verified
zvadaadam 1d9ed9e
docs(device-use): handoff section for the next PR (Deus AAP host)
zvadaadam f14d701
fix(device-use): address CodeRabbit review on PR #249
zvadaadam 52307fa
chore(device-use): sync bun.lock with zustand workspace dep
zvadaadam 248d281
fix(device-use): address CodeRabbit round 2 (3/4)
zvadaadam 92c6ff1
docs(device-use): clarify launch.ready manifest path in handoff section
zvadaadam fa9fe35
docs(device-use): address CodeRabbit round 4 (5 doc nits)
zvadaadam 7349537
docs(device-use): align manifest example with shipped agentic-app.json
zvadaadam 0727f9f
style(device-use): adopt Deus design tokens (Jony-Ive dark theme)
zvadaadam da10f8b
feat(device-use): collapsible logs drawer (collapsed by default)
zvadaadam 1209382
feat(device-use): themed Select (Radix UI) — replaces native <select>
zvadaadam f07de8f
fix(device-use): correct cache-control for prod static serving
zvadaadam 84f06e5
feat(device-use): proper iPhone/iPad device frame (ported from apps/web)
zvadaadam 5ff20ec
fix(device-use): drop overlay dynamic island/camera dot — stream alre…
zvadaadam ae01a44
fix(device-use): shell aspect ratio derived from stream so the screen…
zvadaadam a501569
refactor(device-use): collapsible sidebar with Panel wrapper
zvadaadam f07bf09
docs: video-use + AAP v1.1 design draft
zvadaadam File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| name: device-use e2e | ||
|
|
||
| # Exercises the full device-use v2 stack against a real iOS simulator: | ||
| # server spawn → /health → WS subscribe → REST + MCP tool-call → | ||
| # xcodebuild → install → launch → snapshot → tap → state persistence. | ||
| # | ||
| # Schedule only — macOS minutes are 10× Linux, so we don't run per-PR. | ||
| # Manual dispatch is available if you want to verify a branch before merge. | ||
| on: | ||
| schedule: | ||
| - cron: "0 7 * * *" # 07:00 UTC nightly | ||
| workflow_dispatch: | ||
|
|
||
| jobs: | ||
| e2e: | ||
| runs-on: macos-15 | ||
| timeout-minutes: 15 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Bun | ||
| uses: oven-sh/setup-bun@v2 | ||
| with: | ||
| bun-version: latest | ||
|
|
||
| - name: Install deps (also builds simbridge via prepare-device-use) | ||
| run: bun install --frozen-lockfile | ||
|
|
||
| - name: Pick + boot a simulator | ||
| id: sim | ||
| run: | | ||
| UDID=$(xcrun simctl list devices available --json \ | ||
| | jq -r '.devices | to_entries[] | .value[] | select(.name | startswith("iPhone")) | select(.isAvailable == true) | .udid' \ | ||
| | head -1) | ||
| if [ -z "$UDID" ]; then | ||
| echo "::error::No iPhone simulator available on this runner" | ||
| xcrun simctl list devices available | ||
| exit 1 | ||
| fi | ||
| echo "Picked simulator: $UDID" | ||
| echo "udid=$UDID" >> "$GITHUB_OUTPUT" | ||
| xcrun simctl boot "$UDID" || true | ||
| # Wait until the sim actually reports Booted, max ~30s. | ||
| for _ in $(seq 1 30); do | ||
| STATE=$(xcrun simctl list devices | grep "$UDID" | grep -oE '\((Booted|Booting|Shutdown)\)' | tr -d '()') | ||
| [ "$STATE" = "Booted" ] && break | ||
| sleep 1 | ||
| done | ||
| xcrun simctl list devices | grep "$UDID" | ||
|
|
||
| - name: Run e2e against the real sim | ||
| working-directory: packages/device-use | ||
| env: | ||
| E2E_SIM_UDID: ${{ steps.sim.outputs.udid }} | ||
| run: bun scripts/e2e-server.ts | ||
|
|
||
| - name: Shutdown simulator (cleanup) | ||
| if: always() | ||
| run: xcrun simctl shutdown "${{ steps.sim.outputs.udid }}" || true |
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ node_modules/ | |
| dist/ | ||
| bin/ | ||
| .context/ | ||
| .device-use/ | ||
| native/.build/ | ||
| native/.swiftpm/ | ||
| test-apps/*/build/ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,82 +1,109 @@ | ||
| # device-use | ||
|
|
||
| iOS Simulator automation for AI agents — CLI, SDK, and engine. | ||
| Standalone iOS Simulator workbench for humans and agents. | ||
|
|
||
| Ported from [`expo/agent-simulator`](https://github.com/expo/agent-simulator), refactored | ||
| for [Bun](https://bun.sh) with a single-file compiled binary. | ||
| - **Viewer** — open `localhost:3100` to see a live phone screen, boot sims, build + run your Xcode project, inspect the a11y tree. | ||
| - **MCP server** — `/mcp` exposes 23 tools (build, install, launch, tap, type, snapshot, …). Any MCP-speaking client (Claude Code, Claude Desktop, Cursor, …) can drive the simulator. | ||
| - **CLI** — `device-use list`, `device-use tap @e1`, `device-use serve`, etc. Works standalone, no server required. | ||
|
|
||
| ## Install (from source) | ||
| Under the hood: a Bun server hosting a React viewer, an HTTP MCP transport, a WebSocket event bus, and a Swift `simbridge` binary that talks to private CoreSimulator + AccessibilityPlatform frameworks. | ||
|
|
||
| ## Install from source | ||
|
|
||
| ```bash | ||
| bun install | ||
| bun run build:native # Builds simbridge Swift binary (requires Xcode) | ||
| bun run compile # Produces ./bin/device-use + ./bin/simbridge | ||
| ./bin/device-use install # Installs the Claude skill | ||
| bun run build:native # Builds the Swift simbridge binary (requires Xcode) | ||
| ``` | ||
|
|
||
| ## Run the server | ||
|
|
||
| ```bash | ||
| bun run dev # Hono server on 3100 (proxies to Vite for HMR) | ||
| bun run dev:frontend # Vite dev server on 5173 (second terminal) | ||
| # or, from the CLI: | ||
| bunx device-use serve --port 3100 --open | ||
| ``` | ||
|
|
||
| Open [http://localhost:3100](http://localhost:3100). Pick a simulator, paste a `.xcodeproj`/`.xcworkspace` path, click **▶ Run**. | ||
|
|
||
| For production: `bun run build && bun run start`. | ||
|
|
||
| ## MCP endpoint | ||
|
|
||
| Point any MCP client at `http://localhost:3100/mcp`. Example — Claude Desktop's `.claude/mcp.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "mcpServers": { | ||
| "device-use": { | ||
| "type": "http", | ||
| "url": "http://localhost:3100/mcp" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Quick start | ||
| Tools available: `list_devices`, `boot`, `set_active_simulator`, `set_active_project`, `get_project_info`, `build`, `install`, `launch_app`, `terminate_app`, `list_apps`, `app_state`, `snapshot`, `tap`, `type_text`, `swipe`, `press_button`, `screenshot`, `wait_for`, `open_url`, `grant_permission`, `stream_logs`, `stop_logs`, `get_state`. | ||
|
|
||
| ## CLI | ||
|
|
||
| ```bash | ||
| device-use list # List simulators | ||
| device-use boot "iPhone 17 Pro" # Boot by name | ||
| device-use snapshot -i # Dump interactive UI with @refs | ||
| device-use snapshot -i # Accessibility tree with @refs | ||
| device-use tap @e1 # Tap by ref | ||
| device-use type "hello@example.com" # Type into focused field | ||
| device-use screenshot result.png # Capture screen | ||
| device-use serve --open # Start server + open viewer | ||
| device-use doctor # Verify environment | ||
| ``` | ||
|
|
||
| Full command list: `device-use help`. | ||
|
|
||
| ## Architecture | ||
|
|
||
| - **`native/`** — Swift package (`simbridge`) that talks to private CoreSimulator | ||
| and AccessibilityPlatform frameworks. Handles HID injection, accessibility | ||
| queries, and MJPEG streaming. | ||
| - **`src/engine/`** — TypeScript primitives wrapping `xcrun simctl` and | ||
| `simbridge` IPC. No CLI or SDK imports. | ||
| - **`src/cli/`** — Hand-rolled CLI with flat commands and JSON-when-piped. | ||
| - **`src/sdk/`** — Fluent `session()` builder for programmatic automation. | ||
| - **`skills/device-use/SKILL.md`** — Claude Code skill definition. | ||
|
|
||
| ## Commands | ||
|
|
||
| | Command | Purpose | | ||
| | ---------------------------- | --------------------------------------------------------------------------------- | | ||
| | `list` | List available simulators | | ||
| | `boot` / `shutdown` / `open` | Simulator lifecycle | | ||
| | `snapshot` | Accessibility tree with `@refs` (`-i` for interactive only, `--diff` for changes) | | ||
| | `screenshot` | PNG/JPEG capture, optionally base64 | | ||
| | `tap` | By `@ref`, `--id`, `--label`, or `-x -y` | | ||
| | `type` | Into focused field, optional `--submit` | | ||
| | `wait-for` | Poll until element appears/disappears | | ||
| | `stream` | MJPEG screen server (`enable`/`disable`/`status`) | | ||
| | `open-url` | Deep link / URL | | ||
| | `session` | Manage default simulator + ref state | | ||
| | `doctor` | Environment check | | ||
| | `install` | Verify setup + install Claude skill | | ||
|
|
||
| ## SDK | ||
|
|
||
| ```ts | ||
| import { session } from "device-use"; | ||
|
|
||
| await session("iPhone 17 Pro").app("Maps").snapshot().tapOn("@e1").inputText("Coffee").run(); | ||
| ``` | ||
| ┌──────────────────────────────────────────────────────────────────┐ | ||
| │ packages/device-use/ │ | ||
| │ │ | ||
| │ native/ — Swift simbridge binary (unchanged) │ | ||
| │ │ | ||
| │ src/engine/ — TS primitives: simctl + simbridge IPC + │ | ||
| │ xcodebuild + logs. Pure, injectable executors. │ | ||
| │ ▲ │ | ||
| │ │ imported by │ | ||
| │ │ │ | ||
| │ ┌───┴────────────┐ ┌────────────────────────────┐ │ | ||
| │ │ src/cli/ │ │ src/server/ │ │ | ||
| │ │ per-command, │ │ long-lived Bun.serve │ │ | ||
| │ │ stateless │ │ / /mcp /ws /health │ │ | ||
| │ └────────────────┘ │ /stream.mjpeg /api/* │ │ | ||
| │ └──────────┬─────────────────┘ │ | ||
| │ │ serves │ | ||
| │ ▼ │ | ||
| │ src/frontend/ (Vite + React) │ | ||
| │ TopBar, DeviceFrame, Sidebar, │ | ||
| │ LogsDrawer — WS client of server │ | ||
| └──────────────────────────────────────────────────────────────────┘ | ||
| ``` | ||
|
|
||
| ## Distribution | ||
|
|
||
| The compiled `./bin/device-use` is a single ~58 MB Bun executable. Ship | ||
| it alongside `./bin/simbridge` (~1.6 MB) — the CLI looks for `simbridge` as a | ||
| sibling of its own binary (or via `$DEVICE_USE_SIMBRIDGE` override). | ||
| CLI and server share the engine but are independent peers — neither needs the other to work. | ||
|
|
||
| ## Requirements | ||
| ## Agentic Apps Protocol | ||
|
|
||
| - macOS 14+ with Xcode installed | ||
| - Bun 1.1+ (dev only — compiled binary has no runtime dep) | ||
| device-use is the reference implementation of an AAP app. The `agentic-app.json` at the package root declares how a host IDE (Deus, any MCP-speaking IDE) should launch and embed it. | ||
|
|
||
| ## Testing | ||
| ## Develop | ||
|
|
||
| ```bash | ||
| bun test # Unit tests | ||
| bun run typecheck # TS check | ||
| ./bin/device-use doctor # End-to-end env check | ||
| bun test # 86 unit + integration tests, no real sim needed | ||
| bun run typecheck # tsc --noEmit | ||
| bun run build # simbridge + ts bundles + frontend bundle | ||
| bun run compile # single-file bin/device-use + bin/simbridge | ||
| ``` | ||
|
|
||
| Tests live in `test/` — never colocated with `src/`. `packages/device-use/scripts/ws-smoke.ts` is a manual WS sanity check (server must be running). | ||
|
|
||
| ## License | ||
|
|
||
| MIT | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.