[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-95154":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":17,"stars90d":15,"forks30d":15,"starsTrendScore":18,"compositeScore":19,"rankGlobal":9,"rankLanguage":9,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":21,"hasPages":21,"topics":23,"createdAt":9,"pushedAt":9,"updatedAt":40,"readmeContent":41,"aiSummary":42,"trendingCount":15,"starSnapshotCount":15,"syncStatus":43,"lastSyncTime":44,"discoverSource":45},95154,"dsh-ios","ZSeven-W\u002Fdsh-ios","ZSeven-W","DeepSeek Harness (DSH) plugin: a live iOS Simulator — and a USB-connected iPhone — inside the conversation. 22 agent tools for booting, building, driving the UI by accessibility identity, OCR text or list rows, plus a streaming sidebar panel you can tap and drag on.",null,"TypeScript",218,22,144,1,0,12,52,76,89.29,"MIT License",false,"main",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39],"accessibility","ai-agents","coding-agent","deepseek-harness","dsh","dsh-plugin","ios","ios-simulator","iphone","mcp","mobile-automation","plugin","typescript","ui-automation","webdriveragent","xcode","2026-08-24 04:01:23","\u003Cp align=\"center\">\n  \u003Cimg src=\".\u002Fdocs\u002Fimages\u002Fdsh-ios-logo.png\" alt=\"DSH iOS\" width=\"120\" \u002F>\n\u003C\u002Fp>\n\n\u003Ch1 align=\"center\">DSH iOS Simulator\u003C\u002Fh1>\n\n\u003Cp align=\"center\">\n  \u003Cstrong>A live, interactive iOS Simulator inside a \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fdeepseek-ai\u002Fdeepseek-harness\">DeepSeek Harness\u003C\u002Fa> conversation — plus your real iPhone over USB.\u003C\u002Fstrong>\u003Cbr \u002F>\n  \u003Csub>22 agent tools &bull; live MJPEG sidebar panel &bull; simulator &amp; real iPhone over USB &bull; list\u002Ffeed row actions &bull; SwiftUI preview hot reload\u003C\u002Fsub>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Csub>npm: \u003Ccode>@zseven-w\u002Fdsh-ios\u003C\u002Fcode> &middot; Current plugin release: \u003Ccode>0.1.0-rc.3\u003C\u002Fcode> &middot; Tested with DSH \u003Ccode>0.1.1-rc.1\u003C\u002Fcode>\u003C\u002Fsub>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cb>English\u003C\u002Fb> &middot; \u003Ca href=\".\u002FREADME.zh.md\">简体中文\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.zh-TW.md\">繁體中文\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.ja.md\">日本語\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.ko.md\">한국어\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.fr.md\">Français\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.es.md\">Español\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.de.md\">Deutsch\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.pt.md\">Português\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.ru.md\">Русский\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.hi.md\">हिन्दी\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.tr.md\">Türkçe\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.th.md\">ไทย\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.vi.md\">Tiếng Việt\u003C\u002Fa> &middot; \u003Ca href=\".\u002FREADME.id.md\">Bahasa Indonesia\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Csub>npm: \u003Ccode>@zseven-w\u002Fdsh-ios\u003C\u002Fcode> &middot; Current plugin release: \u003Ccode>0.1.0-rc.3\u003C\u002Fcode> &middot; Tested with DSH \u003Ccode>0.1.1-rc.1\u003C\u002Fcode>\u003C\u002Fsub>\n\u003C\u002Fp>\n\n\u003Cbr \u002F>\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\".\u002Fdocs\u002Fimages\u002Fdsh-ios-overview.png\" alt=\"DSH iOS Simulator — a real iPhone inside the conversation\" width=\"100%\" \u002F>\n\u003C\u002Fp>\n\u003Cp align=\"center\">\u003Csub>A real iPhone driven from inside a DSH conversation — the agent's tool calls on the left, the live device panel on the right\u003C\u002Fsub>\u003C\u002Fp>\n\n## Why DSH iOS Simulator\n\nDSH iOS Simulator gives the agent a real iOS Simulator inside the conversation — and gives you the pixels. The agent can boot a device, build and run an Xcode project or Swift package, drive the UI by accessibility identity or by OCR text, read unified logs, and inspect processes, backtraces, and leaks, while a live stream of the device renders in a persistent sidebar panel where you can tap, drag, rotate, and press Home directly on the video. The same verbs also work on a real iPhone connected over USB: the plugin builds and launches WebDriverAgent on the phone, tunnels its control and screen ports over loopback, and streams the device into the same panel, cards, and tools. No image blocks, no screen-recording files: visual bytes reach the UI only through signed, expiring URLs served by the DSH webserver.\n\n| | |\n| --- | --- |\n| 🖥️ **Live simulator in the conversation** | A serve-sim MJPEG stream of the booted device, proxied through signed `\u002F_dsh\u002Fdsh-ios\u002F*` routes into a persistent right-side panel — the browser never touches serve-sim's port. |\n| 📱 **Real iPhone over USB** | `ios_real_start_wda` builds and launches WebDriverAgent on a connected phone and tunnels its control (REST) and screen (MJPEG) ports over loopback; the same panel, tools, cards, and status capsule then drive the phone. The device must be unlocked, and every real-account tap is gated by the plugin's identify-before-tap rules. |\n| 🛠️ **22 agent tools** | Devices, boot\u002Fshutdown, screenshot, interact, build &amp; run, unified logs, AXe-backed UI tree + tap-by-element, list\u002Ffeed row actions, Vision OCR find\u002Ftap, SwiftUI preview hot reload, processes, backtrace, leaks, app info. |\n| 👆 **Interactive panel** | Tap and drag on the live video; Home \u002F rotate \u002F screenshot \u002F refresh icon toolbar with hover tooltips; size modes (适应 · 50–125% · S\u002FM\u002FL); frame styles (无框 \u002F 边框 \u002F 真机框); drag-resize up to 960 px with double-click reset; landscape auto-widen. |\n| 🧾 **List &amp; feed rows** | `ios_sim_ui_rows` turns deep accessibility snapshots into indexed rows with labels and generically parsed counters; `ios_sim_tap_row` taps inside a row at relative coordinates and verifies the action by the counter's expected ±1 change — the only reliable confirmation a list app offers. |\n| 🔐 **Loopback-only transport** | serve-sim binds 127.0.0.1 in a dedicated port range; every route requires a loopback peer, a loopback `Host`, and Fetch-Metadata\u002FOrigin checks; HMAC capabilities expire within 10 minutes. The WebDriverAgent control\u002FMJPEG tunnels on a real device are loopback usbmux forwards under the same fence. |\n| ⚡ **SwiftUI preview hot reload** | `ios_sim_preview` generates a disposable host app outside your package, builds your previews as a dylib, and hot-swaps edits into the running simulator without relaunching (~2–5 s). |\n| 🧭 **Semantic UI automation** | `ios_sim_ui_tree` dumps the accessibility tree (AXe-backed) and `ios_sim_tap_element` taps by label or identifier; `ios_sim_find_text` OCRs the screen when the tree is empty or degenerate, and `ios_sim_tap_text` taps the matched text — identity- and text-based taps instead of guessed coordinates. |\n\n## Tools\n\nAll 22 tools are registered on every host and return plain JSON — visual bytes reach the UI only through `presentationMeta` + signed routes, never as image blocks. Simulator udids route through simctl\u002Fserve-sim; physical-device udids route through WebDriverAgent automatically. On non-macOS hosts (or when serve-sim is unresolvable) the tools stay registered but fail with an explanatory error; the one exception is `ios_sim_preview` `status`, which truthfully reports `{ running: false }` on any host.\n\n### Core simulator tools\n\n| Tool | What it does | Key parameters |\n| --- | --- | --- |\n| `ios_sim_devices` | List the iOS Simulator devices available on this Mac (udid, name, runtime, state) and which are booted, plus any USB-connected physical iPhones under `realDevices` (udid, name, osVersion, model, state, developerMode). Use it to discover the udid or name to pass to the other tools. | — |\n| `ios_sim_boot` | Boot a device and start its live serve-sim stream; the stream stays alive for the conversation so the panel can show the simulator live. | `udid` (required — udid or device name) |\n| `ios_sim_shutdown` | Shut a device down; stops the stream when it targets that device. | `udid` (required) |\n| `ios_sim_screenshot` | Capture a PNG and return a small JSON summary (path, bytes, dimensions, device); the image renders in the card\u002Fpanel, never as an image block. Works on the streamed simulator and on a USB-connected phone via WebDriverAgent. | `udid` (optional — streamed device, else first booted) |\n| `ios_sim_interact` | Interact with the streamed device — simulator or USB-connected phone: tap at normalized 0..1 coordinates, type text (US keyboard on a simulator), press a hardware button (`home`, `lock`, `volumeUp`…), scroll, or send a touch gesture; after the action settles (~300 ms) a fresh screenshot shows the effect. | `action` (required — `tap`\u002F`type`\u002F`button`\u002F`gesture`\u002F`scroll`), `x`\u002F`y`, `text`, `name`, `json` |\n| `ios_sim_list_apps` | List the apps INSTALLED on a booted simulator or a connected phone (bundle id, display name, version, system flag) — a third-party bundle id cannot be guessed, so list it or pass `name` to `ios_sim_launch_app`. A FAILED listing throws (e.g. \"the device is not reachable by CoreDevice\") instead of returning an empty list, so `count: 0` always means the device really has no matching app. | `udid` (optional), `query` (case-insensitive substring over display name AND bundle id, CJK included), `include_system` (default false) |\n| `ios_sim_launch_app` | Launch an installed app on a booted simulator or a connected phone — by `bundleId`, or by `name` (a case-insensitive display-name substring resolved through the same listing, CJK included). Exactly one of the two; a launch failure and an ambiguous name both come back with what to do next (`ios_sim_build_run` is for building one from source). | `bundleId` or `name` (exactly one), `udid`, `relaunch` |\n| `ios_sim_build_run` | Build an `.xcodeproj`, `.xcworkspace`, or Swift package for the simulator, install the built `.app`, and launch it; pass a physical-device udid to build, install, and launch on the phone instead (requires Apple Development signing). On failure the result carries the filtered `xcodebuild` error tail. Takes minutes for a full build. | `projectPath` (required), `scheme`, `udid` (streamed → booted → newest-runtime iPhone, which is booted), `configuration` (default `Debug`) |\n| `ios_real_start_wda` | Start WebDriverAgent (WDA) on a USB-connected physical iPhone — real devices only, never a simulator. Adopts an already-running WDA when one answers; otherwise runs the `xcodebuild` build\u002Flaunch (a cold build takes minutes), then waits until WDA reports ready and returns the control\u002FMJPEG ports the live panel streams through. Run this first when `ios_sim_screenshot` \u002F `ios_sim_interact` \u002F `ios_sim_ui_tree` \u002F `ios_sim_tap_element` report WDA is not running for the device. | `udid` (required — physical-device udid from `ios_sim_devices.realDevices`) |\n\n### UI-tree tools (AXe-backed)\n\n| Tool | What it does | Key parameters |\n| --- | --- | --- |\n| `ios_sim_ui_tree` | Dump the frontmost app's accessibility element tree (labels, identifiers, values, frames in device points) plus the screen size in points — AXe on a simulator, WebDriverAgent on a USB-connected phone (depth-capped by default there: an uncapped snapshot of a busy app measures ~32 s \u002F 751 KB, capped ~2 s); output is capped at ~40 KB (deepest levels pruned, `truncated` + hint set). | `udid` (optional), `max_depth`, `filter` (case-insensitive substring over label\u002Fidentifier\u002Ftype) |\n| `ios_sim_tap_element` | Tap an element by identity — exact match first, then case-insensitive substring over `identifier`\u002F`label`; nested duplicates collapse to one target, ambiguous matches list every candidate. The tap lands on the element center (AXe HID on a simulator, WebDriverAgent on a phone), then a ~300 ms screenshot shows the effect; pass `expect_text` \u002F `expect_gone` and the tap plus its verification become one round trip (`expected.matched`). | `udid` (optional), `identifier`, `label`, `expect_text`, `expect_gone` |\n\n### List &amp; feed rows\n\nList\u002Ffeed apps aggregate each item into one accessibility cell whose label carries the whole summary and all its counters (\"57 回复。18 喜欢。592 次查看\") — there are no per-control child buttons to match, and the row cells only surface at a deep snapshot. These two tools expose that structure as rows and act inside a row.\n\n| Tool | What it does | Key parameters |\n| --- | --- | --- |\n| `ios_sim_ui_rows` | Read the visible list\u002Ffeed rows of the frontmost app as rows instead of a raw tree: each row reports its index, frame in points, the aggregated label, and the counters parsed out of that label (number + classifier token, e.g. `57 回复` → 回复=57, 中文 or English — no app vocabulary hardcoded). Rows only surface at a deep snapshot: on a phone the default `max_depth` is 60, costing ~15–25 s \u002F ~0.5 MB per call (WDA serves requests serially) — keep the cheap observers (`ios_sim_find_text` \u002F `ios_sim_ui_tree`) first. Counters are parsed heuristically and keys round-trip: pass a key exactly as listed to `ios_sim_tap_row.expect_count`. When no rows are found the result says why (depth too shallow \u002F not a list screen \u002F genuinely no accessibility information after a deep read) — a shallow read is never reported as \"the app has no accessibility information\"; off-screen rows are excluded and counted as `omittedOffscreen`. | `udid` (optional), `max_depth` (phone-only; default 60) |\n| `ios_sim_tap_row` | Tap at a relative position inside one visible list row (reported by `ios_sim_ui_rows`: 0-based index; x\u002Fy as fractions of that row's frame — 0 = left\u002Ftop edge, 1 = right\u002Fbottom, default 0.5 = center) on a simulator (AXe) or a USB-connected phone (WebDriverAgent). The row frame comes from a FRESH tree read, so no absolute screen coordinates are guessed; an out-of-range index FAILS (never clamps). Safety gate: with `expect_count={key,delta}` the tool verifies the action by re-reading the row label and checking the counter moved exactly +1\u002F−1 (`countCheck.verified`); if the key is not among the row's parsed counters the tap is REFUSED before it happens — a real-device tap is never a probe. Without `expect_count` the tap still happens (an explicit row-relative position IS the identification) but nothing is verified. | `udid` (optional), `index` (required), `x`, `y` (fractions 0..1), `max_depth`, `expect_count` (`{key, delta}`) |\n\n### OCR tools (Vision)\n\n| Tool | What it does | Key parameters |\n| --- | --- | --- |\n| `ios_sim_find_text` | OCR the CURRENT screen of a booted simulator or a USB-connected phone with the plugin-compiled Vision helper (accurate recognition, zh-Hans + en-US, compiled with `swiftc` on first use into `~\u002FLibrary\u002FCaches\u002Fdsh-ios\u002Fbin\u002Focr`). Use it when the accessibility tree is empty or degenerate, for text rendered as graphics (badge counts, prices baked into images), or to independently verify what is on screen. Captures a fresh screenshot and returns `{device, size, items:[{text, confidence, rect}]}` — rects are device-point boxes (origin top-left), confidence-sorted, capped at ~40 KB (`truncated` drops the lowest-confidence tail; narrow with `query` or raise `min_confidence`). | `udid` (optional), `query` (case-insensitive substring), `min_confidence` (default 0.3) |\n| `ios_sim_tap_text` | OCR the CURRENT screen and tap the center of the best text match — the same exact → case-insensitive-contains → candidate-list ambiguity rules as `ios_sim_tap_element`, for text the accessibility tree cannot see (no-a11y apps, badge counts, text baked into images). On a phone the tap lands at absolute device points through WebDriverAgent; on the streamed simulator it is sent normalized through the serve-sim control (run `ios_sim_boot` first). After ~300 ms a fresh screenshot shows the effect; pass `expect_text` \u002F `expect_gone` and the tap plus its verification become one round trip (`expected.matched`). On a REAL device every tap has real consequences — never tap an unidentified control to find out what it does. | `udid` (optional), `query` (required), `min_confidence`, `expect_text`, `expect_gone` |\n| `ios_sim_wait_for` | Wait until text appears or disappears on the screen, polling the same capture+OCR pipeline as `ios_sim_find_text` until the condition holds or the timeout expires (default 8 s, max 60 s). A timeout is a normal `matched:false` answer, never an error — one call instead of a find_text loop that costs ~1.2 s per round trip on a phone. On a match, `item` carries the OCR text, confidence, and rect in device points. | `udid` (optional), `text` (required), `mode` (`appear`\u002F`disappear`), `timeout_ms`, `min_confidence` |\n\n### Logs tool\n\n| Tool | What it does | Key parameters |\n| --- | --- | --- |\n| `ios_sim_logs` | Read what a simulator app prints, from the device unified log: `snapshot` (`log show --last \u003Cduration>`, default 2m) or `follow` (bounded live capture for `duration_seconds`, default 10, max 60 — never a hanging stream). Output is capped at ~300 lines \u002F 30 KB with a narrowing hint. | `udid` (optional), `mode` (`snapshot`\u002F`follow`), `duration`, `duration_seconds`, `bundle_id`, `predicate` (raw NSPredicate, overrides `bundle_id`), `level` (`default`\u002F`info`\u002F`debug`), `grep` |\n\n### Preview tool\n\n| Tool | What it does | Key parameters |\n| --- | --- | --- |\n| `ios_sim_preview` | SwiftUI preview hot reload, live in the simulator: `start` (default) validates the package, generates a disposable host app in the plugin cache (never inside your package), builds the package as a dylib for the simulator, installs + launches the host, and watches the sources — every edit rebuilds and hot-swaps without relaunching (~2–5 s). Compiler errors keep the last good preview and surface through `status`; one session at a time. | `packagePath` (required for `start`), `udid`, `action` (`start`\u002F`status`\u002F`stop`), `previewFilter` (case-insensitive substring over preview names) |\n\n### Debug tools\n\n| Tool | What it does | Key parameters |\n| --- | --- | --- |\n| `ios_sim_processes` | List the running app processes of one simulator from its own launchd (host-visible pid, name, bundle id) — the pid source for backtrace\u002Fleaks; a physical-device udid lists the phone's processes through devicectl instead. | `udid` (optional), `filter` (case-insensitive substring over name\u002Fbundle id) |\n| `ios_sim_backtrace` | One-shot batch LLDB (attach → thread backtrace → detach, never interactive); output capped at ~200 lines, main thread first, target always verified resumed. When macOS denies the attach (Developer Mode off), degrades to Xcode's non-suspending `sample` engine and reports the enable hint. Simulators only — physical devices are rejected with the reason. | `udid` (optional), `pid` \u002F `bundle_id`, `all_threads` (default true) |\n| `ios_sim_leaks` | Analyze leaks with Xcode's `leaks` tool: `summary` (leak count, total leaked bytes, top ~30 types) or `memgraph` (a `.memgraph` artifact to open in Xcode Instruments, never parsed here). The app is suspended while scanning and always resumed. Simulators only. | `udid` (optional), `pid` \u002F `bundle_id`, `mode` (`summary`\u002F`memgraph`) |\n| `ios_sim_app_info` | Installed-app facts: app bundle path, writable data container, and Info.plist values — via `simctl appinfo` (with a `get_app_container` fallback) on a simulator, via `devicectl` on a USB-connected phone; `installed: false` plus a `note` naming `ios_sim_list_apps` for missing apps. | `udid` (optional), `bundle_id` (required) |\n\n## Display surfaces\n\n- **Sidebar panel — “iOS 模拟器”.** The live view lives in a persistent right-hand panel (a fixed dock that pushes the conversation aside, or a centered overlay on narrow viewports). It renders the live MJPEG stream and accepts click-to-tap and drag-to-gesture directly on the video, with an icon toolbar (Home, screenshot, rotate, refresh) whose buttons carry hover tooltips. Size controls offer **适应** (fit to panel width), **50–125%** zoom of the device's logical width, and **S \u002F M \u002F L** presets that size the device's short side (portrait width; landscape scales so the device keeps its physical size). Frame styles are **无框 \u002F 边框 \u002F 真机框** (frameless \u002F bezel \u002F realistic device shell) with a proportional corner radius. When the device rotates to landscape the panel auto-widens to a comfortable size and restores your width when it rotates back — a manual drag during the stint always wins. The left-edge handle drags the panel wider\u002Fnarrower (max 960 px; double-click resets to the default width). When a USB-connected iPhone is the stream target, the same panel shows the phone's WebDriverAgent MJPEG stream with the same controls.\n- **Compact conversation cards.** Tool results render as one-line cards with no inline imagery: the unified **“iOS 模拟器”** title, an action sub-label (Boot \u002F Screenshot \u002F Interact \u002F Build &amp; Run \u002F Start WebDriverAgent), the device name, a status badge, and an “open in sidebar” cue. Clicking the row opens the panel; clicks on buttons, links, or the live frame itself never trigger it.\n- **Status capsule above the input.** While the panel is closed and a stream is online, a small green-dot pill (`\u003Cdevice> · 实时`) appears above the composer and opens the panel when clicked. It is session-gated: it renders and polls only while the current conversation has mounted simulator results, and stops when you switch to a session without them.\n- **Standard mode and Code Mode.** Standard sessions use the host-projected `presentationMeta`. Nested Code Mode (PTC) dispatches never carry meta, so the client reconstructs the identical meta from the durable result JSON — the panel, the cards, and the capsule work in both modes.\n\n## Security\n\n- The browser never talks to serve-sim's port. Every byte crosses the DSH webserver origin through plugin-owned `\u002F_dsh\u002Fdsh-ios\u002F*` routes: `\u002Fstream\u002F\u003Ctoken>` (MJPEG proxy), `\u002Fscreenshot\u002F\u003Ctoken>` (cached PNG), `\u002Fws?token=…` (HID control relay), plus `\u002Fgrant`, `\u002Fcapture`, and `\u002Fstatus` endpoints.\n- Tokens are HMAC-SHA256 capabilities (`base64url(payload).base64url(mac)`) expiring within 10 minutes, signed with a per-DSH-home key (`\u003CDSH_HOME>\u002Fcache\u002Fdsh-ios\u002Fstream-access.key`, 0600, created atomically).\n- Every route applies a loopback\u002Ftrusted transport fence before any capability is consulted: loopback peer address, loopback `Host` (DNS-rebinding rejected), and Fetch-Metadata\u002FOrigin checks. The screenshot route serves only files inside the plugin cache directory (symbolic links refused, `realpath` containment).\n- serve-sim runs as a foreground child on loopback only, in a dedicated port range (3181–3244), so a user's own serve-sim on port 3100 is never touched; `--host` is never used.\n- **Real-device transport** — the WebDriverAgent control (REST, device port 8100) and screen (MJPEG, port 9100) tunnels are loopback usbmux forwards over the USB link; they sit behind the same signed-route fence, and the browser still only ever talks to the DSH webserver origin.\n- **Orphan adoption\u002Freclaim** — if a previous DSH host was killed ungracefully and its serve-sim helper survived, the same device is adopted (the orphan's handshake is authoritative); a stale helper squatting on a slot for a different device is reclaimed via `serve-sim -k` and relaunched once.\n- **Keep-alive + idle stop** — a crashed stream restarts in the background (~5 s delay); with zero consumers the stream stops automatically after 5 minutes. Intentional stops are never fought. (The real-device runner is exempt from the idle reaping on purpose: restarting it costs a multi-minute `xcodebuild` rebuild.)\n\n## Requirements\n\n- **macOS with full Xcode** — not just Command Line Tools. `xcodebuild`, `xcrun simctl`, and the simulator runtimes all ship with Xcode.\n- **At least one iOS Simulator runtime** installed in Xcode.\n- **DSH ≥ 0.1.0-rc.6 with the web bundle** for the panel. Headless profiles work too: all 22 tools function normally, just without the live view.\n- **Non-macOS hosts**: the plugin loads and all 22 tools register, but every call returns an explanatory error (`iOS Simulator requires macOS with Xcode …`).\n- **serve-sim** ships as an npm dependency of this plugin, so it resolves locally on real installs; the `npx -y serve-sim` fallback covers development trees (first use needs network).\n- **AXe** (optional — only the AXe-backed tools need it: `ios_sim_ui_tree` \u002F `ios_sim_tap_element`, plus `ios_sim_ui_rows` \u002F `ios_sim_tap_row` on a simulator): `brew install cameroncooke\u002Faxe\u002Faxe`, or let the plugin auto-download the pinned release (v1.8.0, SHA-256 verified) into `~\u002FLibrary\u002FCaches\u002Fdsh-ios\u002Fbin`. `DSH_IOS_AXE_BIN` overrides resolution; `DSH_IOS_AXE_OFFLINE=1` disables the download.\n- **Vision OCR** (optional — only `ios_sim_find_text` \u002F `ios_sim_tap_text` need it): the plugin compiles its bundled `assets\u002Focr.swift` with `swiftc` on first use into `~\u002FLibrary\u002FCaches\u002Fdsh-ios\u002Fbin\u002Focr` (zh-Hans + en-US recognition).\n- **lldb attach** needs macOS Developer Mode: run `sudo DevToolsSecurity -enable` once. Until then `ios_sim_backtrace` uses Xcode's `sample` engine (non-suspending) and `ios_sim_leaks` degrades with the enable hint.\n- **Real iPhone** — a USB-connected iPhone with the screen unlocked (WebDriverAgent cannot start on a locked screen; consider Auto-Lock: Never), a data-capable USB cable (a Wi-Fi-only pairing cannot carry the port forward), Developer Mode enabled on the device, a WebDriverAgent checkout at `~\u002FLibrary\u002FCaches\u002Fdsh-ios\u002Fwda\u002Fsrc` (the plugin builds its `WebDriverAgentRunner` scheme from there — it never downloads or clones anything). The first WDA build installs a signed WebDriverAgentRunner: trust its certificate on the device when prompted, and re-run `ios_real_start_wda` when the free-team signing profile expires (7-day lifetime).\n\n## Install into DSH\n\n```sh\ndsh plugin --profile web add @zseven-w\u002Fdsh-ios@latest\ndsh web\n```\n\n## Quick start\n\nA typical first conversation:\n\n1. **Discover devices** — “List the available simulators.” → `ios_sim_devices`.\n2. **Boot** — “Boot the iPhone 17 Pro.” → `ios_sim_boot`. The stream starts and the **“iOS 模拟器” panel** opens: the device is live in the sidebar. (Click any simulator card row, or the status pill above the input, to reopen it.)\n3. **Tap on the video** — tap or drag directly on the panel; or let the agent drive the UI: “Open Settings, then tap General.” → `ios_sim_interact` (or `ios_sim_ui_tree` + `ios_sim_tap_element` for identity-based taps; `ios_sim_find_text` + `ios_sim_tap_text` for text-based taps; `ios_sim_ui_rows` + `ios_sim_tap_row` for list\u002Ffeed apps).\n4. **Build &amp; run your app** — “Build and run \u002Fpath\u002Fto\u002FMyApp.xcodeproj.” → `ios_sim_build_run`. A full build takes minutes; when it lands, the app launches on the simulator and you watch it live in the panel.\n5. **Preview hot reload** — “Show the SwiftUI previews of \u002Fpath\u002Fto\u002FMyPackage.” → `ios_sim_preview start`. Edit a source file and the preview hot-swaps in the running simulator within ~2–5 s — no relaunch.\n6. **Drive a real iPhone** — plug the phone in over USB (data cable), unlock it, then “Start WebDriverAgent on the phone.” → `ios_real_start_wda`. The panel switches to the phone's live stream and every tool accepts its `realDevices` udid; when a call fails, read the coded reason from the panel's status (`device-locked`, `cert-untrusted`, `profile-expired`, `tunnel-failed`, `device-unplugged`).\n\n## Troubleshooting\n\n- **Backtrace uses `sample` instead of lldb, or leaks complains about restricted inspection** — macOS Developer Mode is off. Run `sudo DevToolsSecurity -enable` once and retry. The tools degrade cleanly until then: `ios_sim_backtrace` falls back to Xcode's `sample` (symbolized, non-suspending) and `ios_sim_leaks` reports the enable hint.\n- **`ios_sim_ui_tree` \u002F `ios_sim_tap_element` need AXe** — install it with `brew install cameroncooke\u002Faxe\u002Faxe`, or let the plugin download the pinned release on first use (needs network to github.com). The error message always carries the full install hint; `DSH_IOS_AXE_BIN=\u002Fpath\u002Fto\u002Faxe` overrides resolution. The row tools (`ios_sim_ui_rows` \u002F `ios_sim_tap_row`) need AXe on a simulator too.\n- **`ios_sim_find_text` \u002F `ios_sim_tap_text` report the OCR helper is missing** — first use compiles the bundled `assets\u002Focr.swift` with `swiftc` (needs Xcode) into `~\u002FLibrary\u002FCaches\u002Fdsh-ios\u002Fbin\u002Focr`; the error carries the exact path and hint.\n- **`ios_sim_ui_rows` finds no rows** — the result says why: depth too shallow (raise `max_depth`; on a phone each deeper snapshot costs ~15–25 s), not a list screen, or genuinely no accessibility information after a deep read. A shallow read is never misreported as missing accessibility.\n- **`ios_sim_leaks` on iOS 26.2 simulators** — on iOS 26.2 runtimes, Xcode's `leaks` can fail to inspect simulator processes with fatal diagnostics such as `Failed to get DYLD info` or minimal-corpse errors, even with Developer Mode enabled. The tool degrades cleanly: you get the raw diagnostic, the target is always verified resumed, and nothing hangs. There is no plugin-side fix — when it bites, try `mode: \"memgraph\"` or a different runtime.\n- **Real-device calls fail with a coded status** — the panel's status names the cause instead of guessing: `device-locked` (unlock the phone; it recovers by itself), `cert-untrusted` (trust the WebDriverAgent certificate on the device), `profile-expired` (free-team signing lasts 7 days — re-run `ios_real_start_wda` to rebuild), `tunnel-failed` (check the USB link\u002Fusbmuxd), `device-unplugged` (use a data-capable USB cable — Wi-Fi-only pairing cannot carry the port forward).\n- **The stream stops by itself** — that is the idle policy, not a crash: with zero consumers (panel closed, no cards mounted, no route active) the stream stops after 5 minutes and restarts on the next tool call or panel open. A crashed stream restarts in the background within ~5 seconds.\n\n## Development\n\n```sh\npnpm install\npnpm run build      # host tsc + client bundle → lib\u002F\npnpm run typecheck\n```\n\nThe `scripts\u002F` smoke tests exercise the built `lib\u002F` (macOS only for the parts that boot a simulator or talk to a USB-connected phone; set `DSH_IOS_SMOKE_SKIP_SIM=1` to skip those parts):\n\n| Script | What it covers |\n| --- | --- |\n| `node scripts\u002Fdev-smoke.mjs` | Sim host: binary resolution, stream launch, control, keep-alive, dispose. |\n| `node scripts\u002Fdev-tools-smoke.mjs [--full-build]` | The core tools against a real simulator (plus a real build with `--full-build`). |\n| `node scripts\u002Fdev-routes-smoke.mjs` | Signed web routes: grant, stream proxy, screenshot, ws relay, fences, expiry. |\n| `node scripts\u002Fdev-card-smoke.mjs` | Client cards: static SSR (no `\u003Cimg>`), status\u002Fcapture contract, live-ish network part. |\n| `node scripts\u002Fdev-panel-smoke.mjs` | Panel components, size modes, frame styles, dock\u002Ftrigger\u002Fcapsule logic (static only). |\n| `node scripts\u002Fdev-logs-smoke.mjs` | `ios_sim_logs` snapshot\u002Ffollow, filters, caps, process reaping. |\n| `node scripts\u002Fdev-uitree-smoke.mjs` | UI-tree tools: AXe resolution\u002Fdownload pipeline, selectors, real-simulator tree + tap. |\n| `node scripts\u002Fdev-debug-smoke.mjs` | Debug tools: processes, backtrace (lldb + sample), leaks, app info. |\n| `node scripts\u002Fdev-preview-smoke.mjs` | Preview hot reload: start, edit → hot-swap without relaunch, error recovery, stop. |\n| `node scripts\u002Fdev-orphan-smoke.mjs` | Orphaned serve-sim adoption\u002Freclaim after an ungraceful host kill. |\n| `node scripts\u002Fdev-ocr-smoke.mjs` | Vision-OCR tools: helper resolution, swiftc compile cache, recognition pipeline, tap-text routing. |\n| `node scripts\u002Fdev-wda-smoke.mjs` | WebDriverAgent host: `ServerURLHere` parsing, failure classification, tunnels, keep-alive (mocked; optional live pass). |\n| `node scripts\u002Fdev-realdevice-smoke.mjs` | `xcrun devicectl` against a USB-connected iPhone — the exact code paths the tools use. |\n| `node scripts\u002Fdev-realstart-smoke.mjs` | The `\u002Freal-start` route: fence, coded refusals, build\u002Flaunch gating (static). |\n| `node scripts\u002Fdev-realtools-smoke.mjs` | Real-device backends of `ios_sim_screenshot` \u002F `ios_sim_interact` \u002F `ios_sim_ui_tree` \u002F `ios_sim_tap_element` plus `ios_real_start_wda`. |\n\n## Ecosystem\n\n- [DSH Android](https:\u002F\u002Fgithub.com\u002FZSeven-W\u002Fdsh-android) — a live Android emulator or USB device inside the conversation, driven entirely through adb\n- [DSH Crew](https:\u002F\u002Fgithub.com\u002FZSeven-W\u002Fdsh-crew) — dispatch work to DSH agents from Claude Code \u002F Codex\n- [DSH Noema](https:\u002F\u002Fgithub.com\u002FZSeven-W\u002Fdsh-noema) — long-term memory for DSH\n- [DSH OpenPencil](https:\u002F\u002Fgithub.com\u002FZSeven-W\u002Fdsh-openpencil) — inspect and edit `.op` design documents inside a conversation\n\n## Credits &amp; License\n\n- [serve-sim](https:\u002F\u002Fgithub.com\u002FEvanBacon\u002Fserve-sim) — Evan Bacon — the simulator streaming engine (Apache-2.0; bundled runtime dependency).\n- [AXe](https:\u002F\u002Fgithub.com\u002Fcameroncooke\u002FAXe) — Cameron Cooke — the accessibility CLI behind the UI-tree tools (MIT).\n- [WebDriverAgent](https:\u002F\u002Fgithub.com\u002Fappium\u002FWebDriverAgent) — the WebDriver server the plugin builds and launches on real devices (BSD-licensed).\n- Architecture inspired by Codex's “Build iOS Apps” plugin; the SwiftUI preview engine is a clean-room reimplementation of the publicly documented approach — no Codex code is copied.\n- See [THIRD_PARTY_NOTICES.md](.\u002FTHIRD_PARTY_NOTICES.md) for the full notices.\n\n**License**: MIT\n","DSH iOS 是一个为 DeepSeek Harness（DSH）设计的插件，支持在 AI 对话环境中实时接入 iOS 模拟器及 USB 连接的真实 iPhone 设备。它提供 22 个自动化工具，涵盖设备启动、Xcode 项目构建、基于可访问性标识或 OCR 的 UI 驱动、列表行交互、日志与进程分析等，并通过流式 MJPEG 侧边栏面板实现可视化操作（如点击、拖拽、旋转）。技术上依托 WebDriverAgent、Xcode 构建链与安全签名 URL 流媒体，无需本地端口暴露。适用于 iOS 应用开发调试、AI 编程代理的移动端自动化测试与交互验证等场景。",2,"2026-08-22 02:30:05","CREATED_QUERY"]