Skip to main content

baguette-ios-simulator

Headless iOS Simulator manager with host-side HID input injection, 60fps streaming, and device farm web UI for iOS 26

Zur Installation springen

Quellinformationen

Repository
reason-machines/trending-skills
Letzte Quellaktivität
3. Mai 2026 um 09:56
Erkannte Sprache von SKILL.md
Englisch
Sterne
82
Forks
15

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
baguette-ios-simulator
description
Headless iOS Simulator manager with host-side HID input injection, 60fps streaming, and device farm web UI for iOS 26
triggers
["control iOS simulator headlessly","inject touch input into simulator","stream simulator screen","manage simulator device farm","tap swipe gesture simulator programmatically","boot simulator without GUI","simulator web UI dashboard","simulate multi-finger pinch zoom on iOS"]
# Baguette iOS Simulator Manager > Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection. Baguette is a Swift CLI tool that creates, boots, and shuts down iOS Simulator devices, streams their screens at 60fps, and injects taps/swipes/multi-finger gestures entirely headlessly — no Simulator.app GUI required. It also serves a self-contained web UI for single-device and multi-device (farm) control. ## Requirements - Apple Silicon Mac only - macOS 15+ - Xcode 26 (links against private SimulatorKit/CoreSimulator frameworks) ## Install ```bash brew install tddworks/tap/baguette ``` ### Build from Source ```bash git clone https://github.com/tddworks/baguette cd baguette make # release build via ./build.sh swift test # run the test suite ``` ## Key CLI Commands ### Device Management ```bash # List all simulators (default + custom sets) baguette list # Boot a simulator headlessly (no Simulator.app window) baguette boot --udid <UDID> # Shutdown a simulator baguette shutdown --udid <UDID> ``` ### Screen Streaming ```bash # Stream frames to stdout as MJPEG (default) baguette stream --udid <UDID> --fps 60 --format mjpeg # Stream as H.264/AVCC baguette stream --udid <UDID> --fps 60 --format avcc # Pipe MJPEG to ffplay for local preview baguette stream --udid <UDID> --fps 30 --format mjpeg | ffplay -i - ``` ### One-Shot Gesture Input Coordinates are in **device points**; `--width`/`--height` are the simulator screen size in points. ```bash # Tap at a point baguette tap --udid <UDID> --x 219 --y 478 --width 438 --height 954 # Tap with custom duration baguette tap --udid <UDID> --x 219 --y 478 --width 438 --height 954 --duration 0.1 # Swipe from top to bottom (scroll down) baguette swipe --udid <UDID> \ --startX 219 --startY 190 \ --endX 219 --endY 760 \ --width 438 --height 954 # Pinch to zoom in (startSpread < endSpread) baguette pinch --udid <UDID> \ --cx 219 --cy 478 \ --startSpread 60 --endSpread 200 \ --width 438 --height 954 # Two-finger pan baguette pan --udid <UDID> \ --x1 175 --y1 478 \ --x2 263 --y2 478 \ --dx 0 --dy -100 \ --width 438 --height 954 # Hardware buttons baguette press --udid <UDID> --button home baguette press --udid <UDID> --button lock ``` ### Streaming Gesture Input (stdin JSON) For real-time or scripted gesture sequences, pipe newline-delimited JSON to `baguette input`: ```bash baguette input --udid <UDID> ``` Each line gets an ack: `{"ok":true}` or `{"ok":false,"error":"..."}`. ### Web UI Server ```bash # Start the web server (default port 8421) baguette serve # Custom port and host baguette serve --port 9000 --host 0.0.0.0 # Custom device set baguette serve --port 8421 --device-set /path/to/device-set # Open single-device dashboard open http://localhost:8421/simulators # Open multi-device farm dashboard open http://localhost:8421/farm ``` ### DeviceKit Chrome/Bezel Data ```bash # Print bezel layout JSON for a booted device baguette chrome layout --udid <UDID> # Write composite PNG (device screenshot + bezel) to stdout baguette chrome composite --udid <UDID> > screenshot.png # By device name (no UDID needed) baguette chrome layout --device-name "iPhone 17 Pro" baguette chrome composite --device-name "iPhone 17 Pro" > iphone17pro_bezel.png ``` ## Wire Protocol — Streaming Input via stdin Send newline-delimited JSON to `baguette input --udid <UDID>`: ```json {"type":"tap", "x":219, "y":478, "width":438, "height":954, "duration":0.05} {"type":"swipe", "startX":219, "startY":760, "endX":219, "endY":190, "width":438, "height":954, "duration":0.3} {"type":"touch1-down", "x":219, "y":478, "width":438, "height":954} {"type":"touch1-move", "x":225, "y":485, "width":438, "height":954} {"type":"touch1-up", "x":225, "y":485, "width":438, "height":954} {"type":"touch2-down", "x1":175, "y1":478, "x2":263, "y2":478, "width":438, "height":954} {"type":"touch2-move", "x1":150, "y1":478, "x2":288, "y2":478, "width":438, "height":954} {"type":"touch2-up", "x1":150, "y1":478, "x2":288, "y2":478, "width":438, "height":954} {"type":"pinch", "cx":219, "cy":478, "startSpread":60, "endSpread":200, "width":438, "height":954} {"type":"button", "button":"home"} {"type":"button", "button":"lock"} {"type":"scroll", "deltaX":0, "deltaY":-50} ``` ## WebSocket Protocol (for Web UI / Custom Clients) Connect to `ws://localhost:8421/simulators/<UDID>/stream?format=mjpeg` (or `avcc`). ### Server → Client (binary frames) - **MJPEG**: raw JPEG bytes per message - **AVCC**: 1-byte tag prefix: - `0x01` — avcC description - `0x02` — keyframe - `0x03` — delta frame - `0x04` — JPEG seed frame (renders before H.264 IDR) ### Client → Server (text JSON) ```json {"type":"set_bitrate", "bps": 2000000} {"type":"set_fps", "fps": 30} {"type":"set_scale", "scale": 0.5} {"type":"force_idr"} {"type":"snapshot"} ``` Gesture input messages (same format as stdin wire protocol above) are also accepted over the WebSocket. ## Web UI Routes | Method | Path | Description | |--------|------|-------------| | `GET` | `/` | Redirects → `/simulators` | | `GET` | `/simulators` | Device list HTML | | `GET` | `/simulators.json` | `{running: [...], available: [...]}` | | `GET` | `/simulators/:udid` | Stream page HTML | | `POST` | `/simulators/:udid/boot` | Boot device | | `POST` | `/simulators/:udid/shutdown` | Shutdown device | | `GET` | `/simulators/:udid/chrome.json` | Bezel layout JSON | | `GET` | `/simulators/:udid/bezel.png` | Rasterized bezel PNG | | `WS` | `/simulators/:udid/stream` | Live stream + input | | `GET` | `/farm` | Multi-device farm HTML | ## Code Examples ### Scripting Gestures from Swift ```swift import Foundation // Build a gesture sequence as newline-delimited JSON func makeGestureScript() -> String { let gestures: [[String: Any]] = [ // Boot sequence: tap the app icon ["type": "tap", "x": 100, "y": 200, "width": 390, "height": 844, "duration": 0.05], // Scroll down in a list ["type": "swipe", "startX": 195, "startY": 600, "endX": 195, "endY": 200, "width": 390, "height": 844, "duration": 0.4], // Pinch to zoom ["type": "pinch", "cx": 195, "cy": 422, "startSpread": 50, "endSpread": 180, "width": 390, "height": 844], // Press home ["type": "button", "button": "home"] ] return gestures.compactMap { dict -> String? in guard let data = try? JSONSerialization.data(withJSONObject: dict) else { return nil } return String(data: data, encoding: .utf8) }.joined(separator: "\n") } // Run baguette input with the script func runGestureScript(udid: String, script: String) async throws { let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/local/bin/baguette") process.arguments = ["input", "--udid", udid] let inputPipe = Pipe() let outputPipe = Pipe() process.standardInput = inputPipe process.standardOutput = outputPipe try process.run() let inputData = (script + "\n").data(using: .utf8)! inputPipe.fileHandleForWriting.write(inputData) inputPipe.fileHandleForWriting.closeFile() process.waitUntilExit() let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() let acks = String(data: outputData, encoding: .utf8) ?? "" print("Acks:\n\(acks)") } ``` ### Listing and Booting Simulators ```swift import Foundation struct SimulatorInfo: Codable { let running: [Device] let available: [Device] struct Device: Codable { let udid: String let name: String let os: String let state: String } } func fetchSimulators(port: Int = 8421) async throws -> SimulatorInfo { let url = URL(string: "http://localhost:\(port)/simulators.json")! let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(SimulatorInfo.self, from: data) } func bootSimulator(udid: String, port: Int = 8421) async throws { let url = URL(string: "http://localhost:\(port)/simulators/\(udid)/boot")! var request = URLRequest(url: url) request.httpMethod = "POST" let (_, response) = try await URLSession.shared.data(for: request) guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw URLError(.badServerResponse) } } // Usage Task { let sims = try await fetchSimulators() print("Running: \(sims.running.map(\.name))") if let first = sims.available.first { try await bootSimulator(udid: first.udid) print("Booted \(first.name)") } } ``` ### Connecting to a Live Stream WebSocket ```swift import Foundation func connectToSimulatorStream(udid: String, port: Int = 8421) { let url = URL(string: "ws://localhost:\(port)/simulators/\(udid)/stream?format=mjpeg")! let session = URLSession(configuration: .default) let task = session.webSocketTask(with: url) task.resume() // Reduce bitrate and fps for a thumbnail let setFps = URLSessionWebSocketTask.Message.string( #"{"type":"set_fps","fps":15}"# ) let setBitrate = URLSessionWebSocketTask.Message.string( #"{"type":"set_bitrate","bps":500000}"# ) task.send(setFps) { _ in } task.send(setBitrate) { _ in } // Receive MJPEG frames func receiveFrame() { task.receive { result in switch result { case .success(.data(let jpegData)): // jpegData is a raw JPEG — display in NSImageView / UIImageView print("Received frame: \(jpegData.count) bytes") receiveFrame() case .success(.string(let text)): print("Control message: \(text)") receiveFrame() case .failure(let error): print("Stream ended: \(error)") } } } receiveFrame() } ``` ### Sending Gestures over WebSocket ```swift func sendTapOverWebSocket(task: URLSessionWebSocketTask, x: Double, y: Double, width: Double, height: Double) { let gesture: [String: Any] = [ "type": "tap",
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen