{"id":49560714,"url":"https://github.com/tddworks/baguette","last_synced_at":"2026-05-11T07:14:48.497Z","repository":{"id":354982525,"uuid":"1226304875","full_name":"tddworks/baguette","owner":"tddworks","description":"Headless iOS Simulator manager/farm + host-side input injection for iOS 26 — taps, swipes, multi-finger gestures, and 60 fps streaming","archived":false,"fork":false,"pushed_at":"2026-05-07T05:41:11.000Z","size":24558,"stargazers_count":717,"open_issues_count":1,"forks_count":27,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-05-07T12:41:37.827Z","etag":null,"topics":["agent","cli","devicefarm","ios","simulator","simulatorkit","streaming"],"latest_commit_sha":null,"homepage":"https://tddworks.github.io/baguette/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tddworks.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["hanrw"]}},"created_at":"2026-05-01T08:14:13.000Z","updated_at":"2026-05-07T12:38:35.000Z","dependencies_parsed_at":"2026-05-04T09:02:27.623Z","dependency_job_id":"abaa38cd-b854-4bf6-8699-ed3a5d0f5b38","html_url":"https://github.com/tddworks/baguette","commit_stats":null,"previous_names":["tddworks/baguette"],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/tddworks/baguette","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tddworks%2Fbaguette","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tddworks%2Fbaguette/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tddworks%2Fbaguette/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tddworks%2Fbaguette/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tddworks","download_url":"https://codeload.github.com/tddworks/baguette/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tddworks%2Fbaguette/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32885070,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-10T13:40:02.631Z","status":"online","status_checked_at":"2026-05-11T02:00:05.975Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent","cli","devicefarm","ios","simulator","simulatorkit","streaming"],"created_at":"2026-05-03T08:14:37.585Z","updated_at":"2026-05-11T07:14:48.485Z","avatar_url":"https://github.com/tddworks.png","language":"Swift","funding_links":["https://github.com/sponsors/hanrw"],"categories":["Development"],"sub_categories":["iOS / macOS"],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/logo.png\" alt=\"Baguette\" width=\"240\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eBaguette\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\u003cem\u003eBon appétit.\u003c/em\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  Headless iOS Simulator manager + host-side input injection for iOS 26.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/tddworks/baguette/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/tddworks/baguette/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/tddworks/baguette\"\u003e\u003cimg src=\"https://codecov.io/gh/tddworks/baguette/branch/main/graph/badge.svg\" alt=\"Coverage\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/tddworks/baguette/releases/latest\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/tddworks/baguette?sort=semver\" alt=\"Latest release\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/tddworks/baguette\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Swift-6.1-orange?logo=swift\" alt=\"Swift 6.2\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/macOS-15%2B-blue?logo=apple\" alt=\"macOS 15+\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Xcode-26-1575F9?logo=xcode\" alt=\"Xcode 26\"\u003e\n\u003c/p\u003e\n\nA single Swift CLI — **`baguette`** — that creates / boots / shuts down\nsimulator devices, streams their screens at 60 fps, and injects taps\n/ swipes / multi-finger touches without booting the Simulator.app GUI.\nOptionally serves a self-contained web UI on `localhost` so you can\ncontrol any booted simulator from a browser.\n\n## Demo\n\nhttps://github.com/user-attachments/assets/e904413f-16bb-4b3d-86d5-162333403cee\n\nhttps://github.com/user-attachments/assets/c49c9f4b-0e4b-47ea-9272-3223b1ac7739\n\nhttps://github.com/user-attachments/assets/65dc62ee-f0c7-48fb-9c57-5bd267c8c02f\n\n\u003e The raw clip lives at [`assets/demo.mp4`](assets/demo.mp4) — drag\n\u003e it into a GitHub web edit of this README to upload as a CDN-hosted\n\u003e video and replace the line above with the auto-generated URL.\n\n- **Frame streaming** — MJPEG or H.264 / AVCC over stdout or WebSocket.\n  Runtime-tunable bitrate / fps / scale.\n- **Host-HID input** — taps / swipes / streaming 1- and 2-finger gestures /\n  home, lock, power, action, volume buttons / Mac keyboard / scroll wheel,\n  all through SimulatorKit's 9-argument\n  `IndigoHIDMessageForMouseNSEvent` from Xcode 26's preview-kit. No dylib\n  injection, no `DYLD_INSERT_LIBRARIES`, no per-app priming.\n- **Accessibility tree** — `baguette describe-ui` returns the on-screen\n  AX tree as JSON: per-node `role`, `label`, `value`, `identifier`, and\n  `frame` in the same device-point coordinates as `tap` / `swipe`. Hit-test\n  mode (`--x --y`) returns the topmost node under a coordinate. Powered by\n  the private `AccessibilityPlatformTranslation` framework with a\n  `bridgeTokenDelegate` we install ourselves to make the dispatcher work\n  out of Simulator.app.\n- **Live unified-log stream** — `baguette logs --udid \u003cX\u003e` streams the\n  booted simulator's `os_log` output line-by-line to stdout; `WS\n  /simulators/:udid/logs` does the same to a browser. Predicate /\n  bundle-id filters supported.\n- **Standalone web UI** — `baguette serve` opens `http://localhost:8421/simulators`\n  with a list page, live stream, gesture input, and DeviceKit-sourced\n  bezels for every simulator family.\n- **Device farm** — `http://localhost:8421/farm` is an interactive\n  multi-device dashboard. Every booted simulator streams in a wall / grid\n  / list, with filtering and sorting; click a tile to focus it for\n  full-quality streaming + gesture and hardware-button input through the\n  same `GestureDispatcher` → `IndigoHIDInput` pipeline as the CLI.\n- **TDD non-negotiable, layered, mock-injected** — bounded-context\n  Domain / Infrastructure / App split; ~290 Swift Testing cases backed\n  by auto-generated `MockXxx` fakes for every external port (`Input`,\n  `Screen`, `Accessibility`, `LogStream`, `Chromes`, `DeviceHost`).\n  Adapters take `any DeviceHost` rather than the concrete\n  `CoreSimulators` so error-path branches are unit-tested without a\n  booted sim. `swift test` requires no simulator at all.\n\n## Install\n\n```bash\nbrew install tddworks/tap/baguette\n```\n\nApple Silicon only. Requires Xcode 26 — `baguette` links against private\nSimulatorKit / CoreSimulator frameworks shipped with Xcode.\n\n## Quickstart\n\n```bash\n# Start the web UI\nbaguette serve\n\n# Single-device dashboard — list, boot/shutdown, per-device stream pages\nopen http://localhost:8421/simulators\n\n# Device farm — every booted simulator side-by-side, click to focus\nopen http://localhost:8421/farm\n```\n\n`/simulators` lists every simulator on the machine with Boot / Shutdown\nbuttons; click any booted device to open its Stream page — live frames,\nmouse/touch input, and the DeviceKit-sourced bezel.\n\n`/farm` is the multi-device control surface. See\n[Device farm](#device-farm) below.\n\nHeadless from the terminal works too:\n\n```bash\nbaguette list\nbaguette boot --udid \u003cUDID\u003e\nbaguette tap --udid \u003cUDID\u003e --x 219 --y 478 --width 438 --height 954\n```\n\n## Build from source\n\n```bash\nmake           # release build via ./build.sh\nswift test     # run the test suite\n```\n\nHybrid build: SPM fetches dependencies (`ArgumentParser`, `Mockable`,\n`Hummingbird`, `HummingbirdWebSocket`); `swiftc` compiles everything\nwith an Objective-C bridging header targeting `arm64e-apple-macos26.0`,\nlinking `CoreSimulator`, `SimulatorKit`, `IOSurface`, `VideoToolbox`,\n`CoreGraphics`, `ImageIO` from Xcode's private frameworks.\n\n## CLI\n\n```\nbaguette \u003ccommand\u003e [options]\n\n  list [--json]                              List devices (default + custom sets;\n                                             --json emits {\"running\":[…],\"available\":[…]})\n  boot     --udid \u003cUDID\u003e                     Boot headlessly\n  shutdown --udid \u003cUDID\u003e                     Shutdown\n  stream   --udid \u003cUDID\u003e [--fps 60] [--format mjpeg|avcc]\n                                             Stream frames on stdout\n  screenshot --udid \u003cUDID\u003e [--output \u003cpath\u003e] [--quality 0.85] [--scale 1]\n                                             Capture one JPEG frame\n                                             (defaults to stdout)\n  describe-ui --udid \u003cUDID\u003e [--x \u003cpx\u003e --y \u003cpx\u003e] [--output \u003cpath\u003e]\n                                             Dump on-screen accessibility tree\n                                             as JSON (full tree or hit-test);\n                                             frames are in DEVICE POINTS so\n                                             they pipe straight back into a tap.\n  logs --udid \u003cUDID\u003e [--level info|debug|default]\n                     [--style default|compact|json|ndjson|syslog]\n                     [--predicate \u003cNSPredicate\u003e] [--bundle-id \u003cid\u003e]\n                                             Stream the booted simulator's\n                                             unified log to stdout, line by line\n                                             (SIGINT to stop). Levels are the\n                                             three the iOS-runtime `log stream`\n                                             accepts — not host-`log`'s five.\n  input    --udid \u003cUDID\u003e                     Read JSON gestures from stdin\n\n  # Standalone web UI on localhost. Serves /simulators (single-device\n  # dashboard) and /farm (multi-device dashboard) — both backed by the\n  # same WS endpoint and HID pipeline.\n  serve    [--port 8421] [--host 127.0.0.1] [--device-set \u003cpath\u003e]\n\n  # DeviceKit chrome / bezel data.\n  chrome layout    --udid \u003cUDID\u003e             Print bezel layout JSON\n  chrome composite --udid \u003cUDID\u003e             Write composite PNG to stdout\n  chrome layout    --device-name \"iPhone 17 Pro\"\n  chrome composite --device-name \"iPhone 17 Pro\"\n\n  # One-shot gestures — same HID path as `input`, one gesture per\n  # invocation. Coordinates are in DEVICE POINTS; `width` / `height`\n  # are the simulator's screen size in points.\n  tap     --udid … --x … --y … --width … --height … [--duration 0.05]\n  swipe   --udid … --startX … --startY … --endX … --endY … --width … --height …\n  pinch   --udid … --cx … --cy … --startSpread … --endSpread … --width … --height …\n  pan     --udid … --x1 … --y1 … --x2 … --y2 … --dx … --dy … --width … --height …\n  press   --udid … --button home|lock\n```\n\n## `baguette serve` — the web UI\n\n```bash\nbaguette serve --port 8421\n# [baguette] listening on http://127.0.0.1:8421/simulators\n```\n\nOpen `http://localhost:8421/simulators` in any browser. You get the\ndevice list (RUNNING / AVAILABLE sections), Boot / Shutdown buttons,\nand a Stream page per device with live frames + gesture input + the\nDeviceKit-sourced bezel.\n\nThe HTML is editable on disk — `Sources/Baguette/Resources/Web/sim.html`\nopens directly in any browser via `file://` (preview mode), and points\nto its sibling `.js` files. Set `BAGUETTE_WEB_DIR` to override the\nserved root for live-iteration without rebuilding.\n\n### Routes (single resource tree, no `/api/` prefix)\n\n| Method | Path                                       | Backed by                    |\n|--------|--------------------------------------------|------------------------------|\n| `GET`  | `/`                                        | 302 → `/simulators`          |\n| `GET`  | `/simulators`                              | list HTML                    |\n| `GET`  | `/simulators.json`                         | list JSON `{running, available}` |\n| `GET`  | `/simulators/:udid`                        | stream HTML                  |\n| `POST` | `/simulators/:udid/boot`                   | `simulator.boot()`           |\n| `POST` | `/simulators/:udid/shutdown`               | `simulator.shutdown()`       |\n| `GET`  | `/simulators/:udid/chrome.json`            | DeviceKit bezel layout       |\n| `GET`  | `/simulators/:udid/bezel.png`              | rasterized bezel PNG         |\n| `GET`  | `/simulators/:udid/screenshot.jpg`         | one-shot JPEG of the framebuffer (`?quality=\u0026scale=`) |\n| `WS`   | `/simulators/:udid/stream?format=mjpeg\\|avcc` | live frames + control + input + `describe_ui` |\n| `WS`   | `/simulators/:udid/logs?level=\u0026style=\u0026predicate=\u0026bundleId=` | live unified-log stream (one `{\"type\":\"log\",\"line\":…}` text frame per entry) |\n| `GET`  | `/farm`                                    | device-farm HTML             |\n| `GET`  | `/farm/:file`                              | farm UI asset (`farm.css`, `farm-*.js`, …) |\n| `GET`  | `/\u003cfile\u003e.{html,js,css}`                    | static UI asset              |\n\n### One bidirectional WebSocket per stream\n\nThe same WS carries everything for a viewing session:\n\n- **Server → Browser** — encoded binary frames (one per WS message).\n  - MJPEG: raw JPEG bytes per frame.\n  - AVCC: 1-byte tag + payload — `0x01` avcC description, `0x02` keyframe,\n    `0x03` delta, `0x04` JPEG seed (renders before H.264 IDR lands).\n- **Browser → Server** — text JSON, one line per message:\n  - Stream control: `{\"type\":\"set_bitrate\",\"bps\":N}` /\n    `{\"type\":\"set_fps\",\"fps\":N}` / `{\"type\":\"set_scale\",\"scale\":N}` /\n    `{\"type\":\"force_idr\"}` / `{\"type\":\"snapshot\"}`.\n  - Gesture input: same wire format as `baguette input` (see below).\n\nNo `/event` POST, no UDID-keyed registry — the WS handler closure owns\nthe live stream + simulator handle for the duration.\n\n## Device farm\n\n```bash\nbaguette serve\nopen http://localhost:8421/farm\n```\n\nA multi-device dashboard for the booted simulators on the host. Every\ndevice renders in a single page; the same WebSocket pipeline that powers\n`/simulators/:udid` drives every tile.\n\n**What it does**\n\n- **Three view modes** — Grid (compact thumbnails), Wall (large tiles\n  with bezels), and List (one-row-per-device with metadata). Toggle from\n  the header.\n- **Filter and sort** — by device family, OS version, run state. The\n  rail on the left holds filter state across view changes.\n- **Click to focus** — clicking any tile re-parents its `\u003ccanvas\u003e` into\n  a full-quality focused pane on the right. The thumbnail keeps streaming\n  at low bitrate; only the focused tile pays for full-rate frames. No\n  separate mirror video element — the same canvas appears in two places.\n- **Input on the focused tile** — gestures, hardware buttons (home /\n  lock), and the pinch overlay all round-trip through `SimInputBridge`\n  → `GestureDispatcher` → `IndigoHIDInput`. Anything the CLI can drive,\n  the focused tile can drive.\n- **Bezels** — each tile renders with its DeviceKit bezel by default,\n  with a **9-slice composition fallback** for devices without a packaged\n  asset. Toggle to a raw (no-bezel) display mode from the tile menu.\n\n**What's served**\n\n`/farm` is a thin HTML shell at `Resources/Web/farm/farm.html` that\nloads five IIFE component scripts from `/farm/\u003cname\u003e.js`:\n\n| Script           | Job                                             |\n|------------------|-------------------------------------------------|\n| `farm-views.js`  | Grid / Wall / List renderers (pure DOM)         |\n| `farm-tile.js`   | `FarmTile` — per-device thumbnail StreamSession |\n| `farm-focus.js`  | `FarmFocus` — focused-device pane               |\n| `farm-filter.js` | `FarmFilter` — filter state + sidebar wiring    |\n| `farm-app.js`    | `FarmApp` — orchestrator (boot, fetch, dispatch)|\n\n`BAGUETTE_WEB_DIR` overrides the served root, so you can iterate on the\nfarm UI without rebuilding — point it at `Sources/Baguette/Resources/Web`\non disk and reload the browser.\n\n## Wire protocol — `baguette input`\n\nNewline-delimited JSON on stdin → `{\"ok\":true}` / `{\"ok\":false,\"error\":…}`\non stdout, one ack per line.\n\n```json\n{\"type\":\"tap\",   \"x\":219, \"y\":478, \"width\":438, \"height\":954, \"duration\":0.05}\n{\"type\":\"swipe\", \"startX\":219,\"startY\":760, \"endX\":219,\"endY\":190,\n                 \"width\":438,\"height\":954, \"duration\":0.3}\n\n// 1-finger streaming (phase-driven)\n{\"type\":\"touch1-down\", \"x\":219, \"y\":478, \"width\":438,\"height\":954}\n{\"type\":\"touch1-move\", \"x\":225, \"y\":485, \"width\":438,\"height\":954}\n{\"type\":\"touch1-up\",   \"x\":225, \"y\":485, \"width\":438,\"height\":954}\n\n// 2-finger streaming (the primary pinch / pan path for real-time gestures)\n{\"type\":\"touch2-down\", \"x1\":175,\"y1\":478, \"x2\":263,\"y2\":478, \"width\":438,\"height\":954}\n{\"type\":\"touch2-move\", \"x1\":150,\"y1\":478, \"x2\":288,\"y2\":478, \"width\":438,\"height\":954}\n{\"type\":\"touch2-up\",   \"x1\":150,\"y1\":478, \"x2\":288,\"y2\":478, \"width\":438,\"height\":954}\n\n// Buttons (only home / lock reach a working target on iOS 26.4)\n{\"type\":\"button\", \"button\":\"home\"}\n{\"type\":\"button\", \"button\":\"lock\"}\n\n// Scroll\n{\"type\":\"scroll\", \"deltaX\":0, \"deltaY\":-50}\n\n// One-shot pinch (server interpolates 10 steps)\n{\"type\":\"pinch\", \"cx\":219,\"cy\":478, \"startSpread\":60,\"endSpread\":240,\n                 \"width\":438,\"height\":954, \"duration\":0.6}\n\n// One-shot parallel pan of two fingers\n{\"type\":\"pan\", \"x1\":175,\"y1\":478, \"x2\":263,\"y2\":478,\n               \"dx\":0,\"dy\":200, \"width\":438,\"height\":954, \"duration\":0.5}\n```\n\n**Coordinate convention.** All `x` / `y` / `startX` / `endX` / `x1` / `x2`\nare in **device points** — same units as `width` and `height`. The HID\nadapter normalises internally before handing them to the C function.\nA \"tap at the centre of an iPhone 17 Pro Max\" is `x:219, y:478` (half of\n438×954), not `x:0.5, y:0.5`. The browser UI multiplies its normalized\ncoordinates by `width` / `height` before serialising.\n\n### Not yet wired\n\n- `key` / `type` — keyboard isn't on the host-HID path yet (preview-kit\n  recipe still WIP). Routes through external tools today.\n- `siri` button — crashes `backboardd` via every known Indigo path.\n\n## `baguette stream` — frame streaming\n\n```bash\nbaguette stream --udid \u003cUDID\u003e --format avcc --fps 60 | ffplay -\n```\n\nOutputs length-prefixed binary frames on stdout. AVCC carries a 1-byte\ntype prefix per chunk:\n\n| Prefix | Meaning |\n|--------|---------|\n| `0x01` | avcC description — feed to `VideoDecoder.configure` |\n| `0x02` | Keyframe (IDR) AVCC payload |\n| `0x03` | Delta frame |\n| `0x04` | JPEG seed — paints before H.264 IDR lands |\n\nRuntime control: while streaming, write one JSON line per command to\nstdin to retune without restarting.\n\n```json\n{\"type\":\"set_bitrate\",\"bps\":4000000}\n{\"type\":\"set_fps\",\"fps\":30}\n{\"type\":\"set_scale\",\"scale\":2}\n{\"type\":\"force_idr\"}\n{\"type\":\"snapshot\"}\n```\n\n## `baguette chrome` — DeviceKit bezel data\n\n```bash\nbaguette chrome layout --device-name \"iPhone 17 Pro\" | jq .\nbaguette chrome composite --device-name \"iPhone 17 Pro\" \u003e iphone17pro.png\n```\n\nReads Apple's own DeviceKit chrome bundles\n(`/Library/Developer/DeviceKit/Chrome/`) and emits the bezel layout\nJSON or rasterizes the composite PDF to PNG. The `serve` page uses\nthis for every simulator family — no hand-curated bezel table to keep\nin sync.\n\n## Source layout\n\nBounded contexts mirror across `Domain/` and `Infrastructure/` so a\nfeature lives in one place across both layers.\n\n```\n.\n├── Makefile                          wraps build.sh\n├── build.sh                          hybrid SPM + swiftc, arm64e-apple-macos26.0\n├── Package.swift                     SPM manifest\n│\n├── Sources/Baguette/\n│   ├── App/                          CLI dispatch + use-case orchestration\n│   │   ├── RootCommand.swift\n│   │   ├── GestureDispatcher.swift   JSON line → Gesture → Input\n│   │   ├── ReconfigParser.swift      runtime stream-control parser\n│   │   ├── Logger.swift\n│   │   └── Commands/                 one file per CLI subcommand\n│   │       (list / boot / shutdown / stream / input / serve / chrome /\n│   │        screenshot / describe-ui / logs / gesture one-shots /\n│   │        keyboard / press)\n│   │\n│   ├── Domain/                       pure Swift, no Apple private APIs\n│   │   ├── Common/                   CoordinateTypes (Point, Size, Rect, Insets,\n│   │   │                             HIDUsage, DeviceButton)\n│   │   ├── Simulator/                Simulator value type + Simulators aggregate +\n│   │   │                             DeviceHost port (the seam adapters depend on)\n│   │   ├── Input/                    Input port + Gesture / GestureRegistry +\n│   │   │                             Tap / Swipe / Touch1 / Touch2 / Press /\n│   │   │                             Scroll / Pinch / Pan / Key / TypeText /\n│   │   │                             Keyboard\n│   │   ├── Screen/                   Screen port (frame source)\n│   │   ├── Stream/                   Stream port + StreamConfig / StreamFormat\n│   │   │                             + Envelope (MJPEG / AVCC framing)\n│   │   ├── Chrome/                   Chromes aggregate + DeviceChrome /\n│   │   │                             DeviceProfile (bezel layout)\n│   │   ├── Accessibility/            AXNode value type + Accessibility port\n│   │   │                             (on-screen UI tree)\n│   │   └── Logs/                     LogFilter value type + LogStream port\n│   │                                 (live unified-log feed)\n│   │\n│   ├── Infrastructure/               concrete @Mockable port impls (private-API\n│   │                                 code lives ONLY here)\n│   │   ├── Simulator/                CoreSimulators (CoreSimulator + SimulatorKit\n│   │   │                             ObjC bridge); conforms to Simulators +\n│   │   │                             DeviceHost\n│   │   ├── Input/                    IndigoHIDInput (9-arg\n│   │   │                             IndigoHIDMessageForMouseNSEvent + button +\n│   │   │                             HIDArbitrary + keyboard paths)\n│   │   ├── Screen/                   SimulatorKitScreen (framebuffer callbacks),\n│   │   │                             ScreenSnapshot (one-shot JPEG capture)\n│   │   ├── Stream/                   MJPEG / AVCC encoders, JPEG / H.264\n│   │   │                             encoders, Scaler, SeedFilter, Stdout /\n│   │   │                             WebSocket FrameSinks, ControlChannel\n│   │   ├── Chrome/                   LiveChromes + ChromeStore /\n│   │   │                             FileSystemChromeStore + PDFRasterizer\n│   │   ├── Accessibility/            AXPTranslatorAccessibility (AXPTranslator +\n│   │   │                             TokenDispatcher bridge for the iOS-26\n│   │   │                             out-of-Simulator.app accessibility path)\n│   │   ├── Logs/                     SimDeviceLogStream (shells out to\n│   │   │                             `xcrun simctl spawn` for the in-sim\n│   │   │                             `/usr/bin/log stream` child)\n│   │   └── Server/                   Server (Hummingbird HTTP + WS) + WebRoot\n│   │\n│   └── Resources/Web/                static UI for `serve`\n│       ├── sim.html                  list + stream entry, opens via file://\n│       ├── sim-list.js               list page renderer\n│       ├── sim-stream.js             stream-page orchestrator\n│       ├── sim-stream.html           stream view markup\n│       ├── sim-input.js              SimInput / MouseGestureSource / PinchOverlay\n│       ├── sim-input-bridge.js       SimInput → baguette wire-format mapper\n│       ├── sim-native.js             focus-mode (single-sim fullscreen) view\n│       ├── frame-decoder.js          MJPEG / AVCC strategy\n│       ├── device-frame.js           bezel + screen DOM\n│       ├── stream-session.js         WebSocket + paint loop\n│       ├── capture-gallery.js        screenshot fetch + thumbs\n│       └── farm/                     multi-device dashboard (farm.html, farm.css,\n│                                     farm-tile.js, farm-grid.js, …)\n│\n└── Tests/BaguetteTests/              mirrors Sources/ contexts\n    ├── App/                          GestureDispatcher / ReconfigParser /\n    │                                 Logger / Commands (CommandParsing,\n    │                                 ChromeCommand) tests\n    ├── Simulator/                    Simulator / Simulators / DeviceHost tests\n    ├── Input/                        Gesture / GestureRegistry / Keyboard /\n    │                                 IndigoHIDInput error-path tests\n    ├── Screen/                       (none yet — Screen port covered via\n    │                                 mocks in Server tests)\n    ├── Stream/                       Envelope / StreamConfig / StreamFormat tests\n    ├── Server/                       BezelRoutes / WebRootSubdir tests\n    ├── Chrome/                       DeviceChrome / DeviceProfile / LiveChromes /\n    │                                 CoreGraphicsPDFRasterizer / integration tests\n    ├── Accessibility/                AXNode / Accessibility port /\n    │                                 AXPTranslatorAccessibility error-path tests\n    └── Logs/                         LogFilter / LogStream port /\n                                      SimDeviceLogStream error-path tests\n```\n\n## Testing\n\n**TDD is non-negotiable** — every behaviour change to a Domain or\nInfrastructure type lands in a failing `@Test` under `Tests/` first,\nthen the smallest implementation that turns it green, then refactor.\nRead `CLAUDE.md`'s \"TDD is non-negotiable\" pre-implementation gate\nbefore contributing — that's the project's primary rule and it\noverrides \"the change is small\" / \"I'll add the test after\".\n\n~290 tests using **Swift Testing** (`@Suite`, `@Test`, `#expect`),\nnot XCTest. Chicago-school state-based: every external boundary is\nan `@Mockable` protocol (`Input`, `Screen`, `Accessibility`,\n`LogStream`, `Chromes`, `DeviceHost`); tests substitute\nauto-generated `MockXxx` fakes, and assert on returned values rather\nthan recorded calls.\n\nAdapters that talk to private SimulatorKit / CoreSimulator /\nAccessibilityPlatformTranslation symbols (`IndigoHIDInput`,\n`AXPTranslatorAccessibility`, `SimDeviceLogStream`,\n`SimulatorKitScreen`) take `any DeviceHost` rather than the concrete\n`CoreSimulators` aggregate, so their error-path branches —\n`simulatorNotBooted`, idempotent `stop`, host-deallocated, etc. —\nare unit-tested via `MockDeviceHost` without needing a real booted\nsimulator. The successful private-API call path stays\nintegration-only — manually smoke-tested through the CLI and serve\nUI against a booted iOS sim.\n\n```bash\nswift test                                              # all tests\nswift test --filter Simulators                          # one suite\nswift test --filter \"GestureRegistry/parses tap\"        # one test\n```\n\nThe `MOCKING` compilation flag is set under `.debug` only, so release\nbuilds (via `./build.sh`) carry no mock code.\n\n## Why this works on iOS 26.4 when older tools don't\n\niOS 26 changed `SimulatorHID`'s wire format. Public tools like `idb` and\n`AXe` call `IndigoHIDMessageForMouseNSEvent` with the old 5-argument\nsignature; those messages now route to a pointer-service target that\nsilently drops or crashes `backboardd`. Baguette uses the **9-argument\nsignature from Xcode 26's preview-kit**, which routes through digitizer\ntarget `0x32` — the target iOS 26 still honours.\n\nThat single calling-convention change is the entire difference. The\nrecipe is heavily commented in `Sources/Baguette/Infrastructure/Input/IndigoHIDInput.swift`,\nand the layered design is documented in\n[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).\n\n## License\n\nApache License 2.0 — see [`LICENSE`](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftddworks%2Fbaguette","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftddworks%2Fbaguette","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftddworks%2Fbaguette/lists"}