DSH iOS 模擬器
在 DeepSeek Harness 對話裡嵌入一台即時、可互動的 iOS 模擬器——USB 連接的真機 iPhone 同樣支援。
22 個智慧代理工具 • 側邊欄即時 MJPEG 面板 • 模擬器與 USB 真機 • 清單/資訊流列級操作 • SwiftUI 預覽熱重載
npm: @zseven-w/dsh-ios · 目前外掛程式版本:0.1.0-rc.3 · 已在 DSH 0.1.1-rc.1 驗證
English · 简体中文 · 繁體中文 · 日本語 · 한국어 · Français · Español · Deutsch · Português · Русский · हिन्दी · Türkçe · ไทย · Tiếng Việt · Bahasa Indonesia
npm: @zseven-w/dsh-ios · 目前外掛版本: 0.1.0-rc.3 · 已在 DSH 0.1.1-rc.1 驗證
在 DSH 對話中直接操作實機 —— 左側是 Agent 的工具呼叫,右側是即時裝置面板
## 為什麼選擇 DSH iOS 模擬器
DSH iOS 模擬器讓智慧代理在對話裡擁有一台真正的 iOS 模擬器,也讓你親眼看到畫面。智慧代理可以啟動裝置、用 Xcode 專案或 Swift 套件建置並執行 App、按無障礙身份或 OCR 文字驅動介面、讀取統一記錄檔,還能檢查處理程序、呼叫堆疊與記憶體洩漏;與此同時,裝置的即時畫面會渲染在常駐的側邊欄面板裡,你可以在影片上直接點按、拖曳、旋轉、按 Home 鍵。同樣的操作也能作用於 USB 連接的真機 iPhone:外掛程式會在手機上建置並啟動 WebDriverAgent,把控制與畫面連接埠經回送通道轉送,把裝置畫面投進同一套面板、卡片與工具。整個過程不用圖片內容塊,也沒有錄影檔案——視覺資料只會透過 DSH webserver 簽章的限時 URL 進入介面。
| | |
| --- | --- |
| 🖥️ **對話裡的即時模擬器** | 已啟動裝置的 serve-sim MJPEG 畫面,經簽章後的 `/_dsh/dsh-ios/*` 路由代理進常駐的右側面板——瀏覽器永遠不會接觸 serve-sim 的連接埠。 |
| 📱 **USB 真機 iPhone** | `ios_real_start_wda` 在已連接的手機上建置並啟動 WebDriverAgent,把控制(REST)與畫面(MJPEG)連接埠經回送通道轉送;同一套面板、工具、卡片與狀態膠囊即可驅動真機。裝置必須處於解鎖狀態,真機帳號上的每一次點按都受外掛程式的「先識別、再點按」規則約束。 |
| 🛠️ **22 個智慧代理工具** | 裝置清單、啟動/關閉、截圖、互動、建置執行、統一記錄檔、基於 AXe 的 UI 樹與按元素點擊、清單/資訊流列級操作、Vision OCR 找字/點字、SwiftUI 預覽熱重載、處理程序清單、呼叫堆疊、洩漏分析、App 資訊。 |
| 👆 **可互動面板** | 在即時畫面上點按、拖曳;Home / 旋轉 / 截圖 / 重新整理圖示工具列(懸停提示);尺寸模式(适应 · 50–125% · S/M/L);外框樣式(无框 / 边框 / 真机框);拖曳調寬上限 960px、雙擊重設;橫向畫面自動加寬。 |
| 🧾 **清單與資訊流列** | `ios_sim_ui_rows` 把深層無障礙快照轉成帶索引、標籤與通用解析計數器的列;`ios_sim_tap_row` 在列內按相對座標點按,並用計數器符合預期的 ±1 變化驗證操作是否生效——這是清單類 App 唯一可靠的確認方式。 |
| 🔐 **僅回送的傳輸** | serve-sim 只綁定 127.0.0.1 的專屬連接埠段;每條路由都要求回送對端、回送 `Host` 與 Fetch-Metadata/Origin 驗證;HMAC 能力權杖 10 分鐘內過期。。 |
| ⚡ **SwiftUI 預覽熱重載** | `ios_sim_preview` 在套件之外產生一次性宿主 App,把你的預覽編譯成 dylib,編輯後無需重啟即可熱替換進正在執行的模擬器(約 2–5 秒)。 |
| 🧭 **語意化 UI 自動化** | `ios_sim_ui_tree` 匯出無障礙元素樹(基於 AXe),`ios_sim_tap_element` 按標籤或識別碼點擊;當元素樹為空或退化時,`ios_sim_find_text` 直接對螢幕做 OCR,`ios_sim_tap_text` 點擊命中的文字——按身份或按文字點擊,而不是猜座標。 |
## 工具
全部 22 個工具在任何主機上都會註冊,且只回傳純 JSON——視覺資料只透過 `presentationMeta` + 簽章路由進入介面,絕不以圖片塊形式回傳。模擬器 udid 自動走 simctl/serve-sim,真機 udid 自動走 WebDriverAgent。非 macOS 主機(或 serve-sim 無法解析)上工具仍然註冊,但呼叫時會回傳明確的錯誤;唯一的例外是 `ios_sim_preview` 的 `status`,它在任何主機上都會如實回傳 `{ running: false }`。
### 核心模擬器工具
| 工具 | 作用 | 關鍵參數 |
| --- | --- | --- |
| `ios_sim_devices` | 列出這台 Mac 上可用的 iOS 模擬器裝置(udid、名稱、執行時期、狀態)以及哪些已啟動,另外在 `realDevices` 裡列出 USB 連接的真機 iPhone(udid、名稱、osVersion、model、state、developerMode)。先用它發現要傳給其他工具的 udid 或名稱。 | — |
| `ios_sim_boot` | 啟動指定裝置並開始其 serve-sim 即時推流;推流在對話期間保持存活,面板可以即時顯示模擬器。 | `udid`(必填——udid 或裝置名稱) |
| `ios_sim_shutdown` | 關閉指定裝置;若推流目標正是該裝置,則同時停止推流。 | `udid`(必填) |
| `ios_sim_screenshot` | 擷取一張 PNG,回傳簡短的 JSON 摘要(路徑、位元組數、尺寸、裝置);圖片在卡片/面板中渲染,絕不會以圖片塊形式回傳。正在推流的模擬器與 USB 連接的真機(經 WebDriverAgent)都可以截。 | `udid`(可選——預設取正在推流的裝置,其次取第一個已啟動的模擬器) |
| `ios_sim_interact` | 與正在推流的裝置互動——模擬器或 USB 真機均可:在 0..1 歸一化座標上點按、輸入文字(模擬器為美式鍵盤)、按下硬體按鍵(`home`、`lock`、`volumeUp`…)、捲動或傳送觸控手勢;操作穩定後(約 300 毫秒)附帶一張新截圖展示效果。 | `action`(必填——`tap`/`type`/`button`/`gesture`/`scroll`),`x`/`y`、`text`、`name`、`json` |
| `ios_sim_list_apps` | 列出模擬器或已連接真機上**已安裝**的 App(bundle id、顯示名稱、版本、是否系統 App)——第三方 App 的 bundle id 無法猜測,先列出它,或給 `ios_sim_launch_app` 傳 `name`。列舉失敗會拋錯(例如「裝置目前無法透過 CoreDevice 存取」)而不是回傳空清單,所以 `count: 0` 一定意味著裝置上確實沒有匹配的 App。 | `udid`(可選)、`query`(對顯示名稱與 bundle id 同時做不區分大小寫的子字串匹配,支援中文)、`include_system`(預設 false) |
| `ios_sim_launch_app` | 啟動已安裝的 App(模擬器或已連接真機均可):既可以傳 `bundleId`,也可以傳 `name`(對顯示名稱做不區分大小寫的子字串匹配,走同一套列舉邏輯,支援中文)。兩者只能給其一;啟動失敗或名稱有歧義時,錯誤裡會直接給出下一步該怎麼做(從原始碼建置請用 `ios_sim_build_run`)。 | `bundleId` 或 `name`(二選一)、`udid`、`relaunch` |
| `ios_sim_build_run` | 為模擬器建置 `.xcodeproj`、`.xcworkspace` 或 Swift 套件,安裝產生的 `.app` 並啟動;真機 udid 則改為在手機上建置、安裝並啟動(需要 Apple Development 簽章)。建置失敗時回傳過濾後的 `xcodebuild` 報錯尾部。完整建置通常需要幾分鐘。 | `projectPath`(必填)、`scheme`、`udid`(推流裝置 → 已啟動裝置 → 最新執行時期 iPhone,會自動啟動)、`configuration`(預設 `Debug`) |
| `ios_real_start_wda` | 在 USB 連接的真機 iPhone 上啟動 WebDriverAgent(WDA)——僅限真機,絕不用於模擬器。若已有 WDA 在回應則直接接管,否則執行 `xcodebuild` 建置/啟動(冷建置可能耗時數分鐘),然後等待 WDA 就緒並回傳即時面板所用的控制/MJPEG 連接埠。當 `ios_sim_screenshot` / `ios_sim_interact` / `ios_sim_ui_tree` / `ios_sim_tap_element` 報告該裝置 WDA 未執行時,先呼叫本工具。 | `udid`(必填——來自 `ios_sim_devices.realDevices` 的真機 udid) |
### UI 樹工具(基於 AXe)
| 工具 | 作用 | 關鍵參數 |
| --- | --- | --- |
| `ios_sim_ui_tree` | 匯出最前方 App 的無障礙元素樹(標籤、識別碼、取值、以點為單位的 frame)以及螢幕尺寸(點)——模擬器走 AXe,USB 真機走 WebDriverAgent(真機預設限制快照深度:繁忙 App 的不限深快照實測約 32 秒 / 751 KB,限深後約 2 秒);輸出上限約 40 KB(超出時裁掉最深層級,並置 `truncated` + 提示)。 | `udid`(可選)、`max_depth`、`filter`(對標籤/識別碼/類型做不區分大小寫的子字串匹配) |
| `ios_sim_tap_element` | 按身份點擊元素——先精確匹配,再做不區分大小寫的子字串匹配(`identifier`/`label`);巢狀重複元素摺疊為同一個目標,若有多個不同元素匹配則逐一列出候選。點擊落在元素中心(模擬器走 AXe HID,真機走 WebDriverAgent),隨後約 300 毫秒截一張效果圖;傳 `expect_text` / `expect_gone` 則點擊與驗證合併為一次往返(`expected.matched`)。 | `udid`(可選)、`identifier`、`label`、`expect_text`、`expect_gone` |
### 清單與資訊流列
清單/資訊流類 App 把每條內容聚合進一個無障礙 Cell——標籤裡包含整條摘要與全部計數器(「57 回复。18 喜欢。592 次查看」),沒有可以匹配的逐控制項子按鈕,而且這些列只有在深層快照裡才會出現。下面兩個工具把這種結構暴露為「列」,並在列內操作。
| 工具 | 作用 | 關鍵參數 |
| --- | --- | --- |
| `ios_sim_ui_rows` | 把最前方 App 可見的清單/資訊流列讀成「列」而不是原始樹:每一列包含索引、以點為單位的 frame、聚合標籤,以及從標籤裡通用解析出的計數器(數字 + 分類詞,如 `57 回复` → 回复=57,中文或英文——不內建任何 App 詞彙)。列只有在深層快照裡才出現:真機上預設 `max_depth` 為 60,每次呼叫約 15–25 秒 / ~0.5 MB(WDA 序列處理請求)——先考慮廉價的觀察手段(`ios_sim_find_text` / `ios_sim_ui_tree`)。計數器按啟發式解析、鍵值可原樣回傳:給 `ios_sim_tap_row.expect_count` 傳鍵時務必與清單完全一致。找不到列時結果會說明原因(深度太淺 / 不是清單頁 / 深度讀取後確實沒有無障礙資訊)——淺讀絕不會被報告成「該 App 沒有無障礙資訊」;螢幕外的列會被排除並計入 `omittedOffscreen`。 | `udid`(可選)、`max_depth`(僅真機生效;預設 60) |
| `ios_sim_tap_row` | 在一條可見清單列內按相對位置點按(列由 `ios_sim_ui_rows` 報告:0 基索引;x/y 為該列 frame 的比例——0 = 左/上邊緣,1 = 右/下邊緣,預設 0.5 = 中心),模擬器走 AXe、USB 真機走 WebDriverAgent。列的 frame 來自一次全新的樹讀取,絕不猜測絕對螢幕座標;索引越界直接失敗(絕不截斷收攏)。安全閘:傳 `expect_count={key,delta}` 時工具會重新讀取列標籤,校驗計數器恰好變化 +1/−1(`countCheck.verified`);若鍵不在該列解析出的計數器裡,點按會在執行前被拒絕——真機上的點按絕不是試探。不傳 `expect_count` 時點按仍會執行(明確的相對位置本身就是定位),但不會有任何驗證。 | `udid`(可選)、`index`(必填)、`x`、`y`(0..1 比例)、`max_depth`、`expect_count`(`{key, delta}`) |
### OCR 工具(Vision)
| 工具 | 作用 | 關鍵參數 |
| --- | --- | --- |
| `ios_sim_find_text` | 用外掛程式編譯的 Vision 助手對已啟動模擬器或 USB 真機的目前螢幕做 OCR(識別準確,zh-Hans + en-US,首次使用由 `swiftc` 編譯進 `~/Library/Caches/dsh-ios/bin/ocr`)。適用於無障礙樹為空或退化、文字以圖形渲染(角標數字、嵌進圖片的價格)或需要獨立核對螢幕內容的場景。先擷取一張新截圖,再回傳 `{device, size, items:[{text, confidence, rect}]}`——rect 是以裝置點為單位的框(原點在左上),按信心度排序,輸出上限約 40 KB(`truncated` 表示丟掉了信心度最低的尾部;可用 `query` 收窄或調高 `min_confidence`)。 | `udid`(可選)、`query`(不區分大小寫的子字串)、`min_confidence`(預設 0.3) |
| `ios_sim_tap_text` | 對目前螢幕做 OCR 並點按最佳文字匹配的中心——沿用與 `ios_sim_tap_element` 相同的「先精確、再忽略大小寫包含、多候選報歧義」規則,適用於無障礙樹看不到的文字(無 a11y 的 App、角標數字、嵌進圖片的文字)。真機上透過 WebDriverAgent 落在裝置絕對座標;模擬器上經 serve-sim 控制以歸一化座標下發(先呼叫 `ios_sim_boot`)。約 300 毫秒後附一張新截圖展示效果;傳 `expect_text` / `expect_gone` 則點擊與驗證合併為一次往返(`expected.matched`)。在真機上每一次點按都有真實後果——絕不靠點按來試探一個未識別的控制項。 | `udid`(可選)、`query`(必填)、`min_confidence`、`expect_text`、`expect_gone` |
| `ios_sim_wait_for` | 等待某段文字在螢幕上出現或消失:重用 `ios_sim_find_text` 的截圖+OCR 流程輪詢,直到條件成立或逾時(預設 8 秒,上限 60 秒)。逾時是正常的 `matched:false` 結果,絕不擲錯——一次呼叫取代手動循環 find_text(實機上每輪約 1.2 秒)。命中時 `item` 帶回 OCR 文字、信心值與裝置點座標框。 | `udid`(選填)、`text`(必填)、`mode`(`appear`/`disappear`)、`timeout_ms`、`min_confidence` |
### 記錄檔工具
| 工具 | 作用 | 關鍵參數 |
| --- | --- | --- |
| `ios_sim_logs` | 從裝置統一記錄檔讀取 App 執行時期的輸出:`snapshot`(`log show --last `,預設 2m)或 `follow`(有界即時擷取 `duration_seconds`,預設 10 秒、上限 60——絕不會掛起不回傳)。輸出上限約 300 行 / 30 KB,並附收窄提示。 | `udid`(可選)、`mode`(`snapshot`/`follow`)、`duration`、`duration_seconds`、`bundle_id`、`predicate`(原始 NSPredicate,優先於 `bundle_id`)、`level`(`default`/`info`/`debug`)、`grep` |
### 預覽工具
| 工具 | 作用 | 關鍵參數 |
| --- | --- | --- |
| `ios_sim_preview` | 在模擬器裡即時熱重載 SwiftUI 預覽:`start`(預設)驗證套件、在外掛程式快取裡產生一次性宿主 App(絕不會寫進你的套件)、把套件編譯為模擬器 dylib、安裝並啟動宿主、然後監聽原始碼——每次編輯都會重新建置並熱替換,無需重啟(約 2–5 秒)。編譯錯誤不會殺死工作階段:宿主保留最後一次成功的預覽,錯誤尾部透過 `status` 回傳;同一時間只能執行一個預覽工作階段。 | `packagePath`(`start` 時必填)、`udid`、`action`(`start`/`status`/`stop`)、`previewFilter`(對預覽名稱做不區分大小寫的子字串匹配) |
### 偵錯工具
| 工具 | 作用 | 關鍵參數 |
| --- | --- | --- |
| `ios_sim_processes` | 從模擬器自身的 launchd 列出其執行中的 App 處理程序(主機可見的 pid、處理程序名稱、bundle id)——backtrace/leaks 的 pid 來源;真機 udid 則改經 devicectl 列出手機上的處理程序。 | `udid`(可選)、`filter`(對處理程序名稱/bundle id 做不區分大小寫的子字串匹配) |
| `ios_sim_backtrace` | 一次性批次 LLDB(attach → thread backtrace → detach,絕非常駐工作階段);輸出上限約 200 行、主執行緒在前,目標處理程序必定被驗證已恢復執行。當 macOS 拒絕 attach(開發者模式未開啟)時,回退到 Xcode 的 `sample` 引擎(不掛起處理程序)並給出開啟提示。僅支援模擬器——真機會被明確拒絕並說明原因。 | `udid`(可選)、`pid` / `bundle_id`、`all_threads`(預設 true) |
| `ios_sim_leaks` | 用 Xcode 的 `leaks` 工具分析洩漏:`summary`(洩漏數、洩漏總位元組、前約 30 種洩漏類型)或 `memgraph`(產生 `.memgraph` 工件,用 Xcode Instruments 開啟,外掛程式絕不解析)。掃描期間 App 會被掛起,但之後必定恢復。僅支援模擬器。 | `udid`(可選)、`pid` / `bundle_id`、`mode`(`summary`/`memgraph`) |
| `ios_sim_app_info` | 讀取已安裝 App 的資訊:App 套件路徑、可寫資料容器、Info.plist 關鍵欄位——模擬器走 `simctl appinfo`(附 `get_app_container` 回退),USB 真機走 `devicectl`;未安裝時回傳 `installed: false`,並在 `note` 中提示改用 `ios_sim_list_apps`。 | `udid`(可選)、`bundle_id`(必填) |
## 顯示面
- **側邊欄面板——「iOS 模拟器」。** 即時畫面位於常駐的右側面板(固定停靠、把對話區讓開;窄視窗下退化為居中浮層)。面板渲染即時 MJPEG 畫面,支援在影片上直接點按、拖曳手勢,並有圖示工具列(Home、截圖、旋轉、重新整理),按鈕帶懸停提示。尺寸控制提供**适应**(鋪滿面板寬度)、**50–125%**(按裝置邏輯寬度縮放)以及 **S / M / L** 預設(按裝置短邊定尺寸;橫向時按顯示比例縮放,保持裝置實體大小)。外框樣式為**无框 / 边框 / 真机框**(frameless / bezel / 逼真的裝置外殼),圓角按比例計算。裝置旋轉為橫向時面板自動加寬到舒適尺寸,轉回直向時恢復你原來的寬度——橫向期間你手動拖過寬度則以你的選擇為準。左側邊緣的把手可以拖寬/收窄面板(上限 960px;雙擊恢復預設寬度)。當推流目標是 USB 連接的 iPhone 時,同一個面板顯示手機經 WebDriverAgent 的 MJPEG 畫面,操作方式完全一致。
- **緊湊對話卡片。** 工具結果渲染為單列卡片,不含任何內聯圖片:統一的**「iOS 模拟器」**標題 + 操作副標籤(啟動 / 截圖 / 互動 / 建置執行 / 啟動 WebDriverAgent)+ 裝置名稱 + 狀態徽章 + 「在側邊欄開啟」提示。點擊卡片所在列即可開啟面板;點擊按鈕、連結或即時畫面本身不會觸發。
- **輸入框上方的狀態膠囊。** 面板關閉且推流線上時,輸入框上方會出現一個綠點小膠囊(`<裝置名稱> · 实时`),點擊即可開啟面板。它受工作階段門控:只有當目前工作階段裡掛載著模擬器結果時才渲染並輪詢,切換到沒有結果的工作階段即自動隱藏。
- **標準模式與 Code 模式。** 標準工作階段使用主機下發的 `presentationMeta`;Code 模式(PTC)的巢狀呼叫不會攜帶 meta,用戶端會從結果中的完整 JSON 重建出完全一致的 meta——面板、卡片和狀態膠囊在兩種模式下都能工作。
## 安全
- 瀏覽器永遠不會接觸 serve-sim 的連接埠。所有流量都經由 DSH webserver 源站上的 `/_dsh/dsh-ios/*` 路由:`/stream/`(MJPEG 代理)、`/screenshot/`(快取 PNG)、`/ws?token=…`(HID 控制轉送),以及 `/grant`、`/capture`、`/status` 端點。
- 權杖是 HMAC-SHA256 能力憑證(`base64url(payload).base64url(mac)`),10 分鐘內過期,用每個 DSH 主目錄私有的金鑰簽章(`/cache/dsh-ios/stream-access.key`,0600,原子建立)。
- 每條路由在檢查任何能力之前先套用回送/可信傳輸圍欄:回送對端位址、回送 `Host`(拒絕 DNS 重綁定)、Fetch-Metadata/Origin 驗證。截圖路由只提供外掛程式快取目錄內的檔案(拒絕符號連結,並做 `realpath` 包含性驗證)。
- serve-sim 以前景子處理程序方式執行,僅綁定回送位址的專屬連接埠段(3181–3244),絕不會動使用者自己在 3100 連接埠上的 serve-sim;從不使用 `--host`。。
- **孤兒處理程序收養/回收**——若上一個 DSH 主機被異常殺死、其 serve-sim 子處理程序存活了下來:同一裝置會被直接收養(孤兒處理程序的握手資訊視為權威);若殘留處理程序占用槽位卻服務著別的裝置,則透過 `serve-sim -k` 回收並重試一次。
- **保活與閒置停止**——推流崩潰後約 5 秒會在背景自動重啟;當沒有消費者時,閒置 5 分鐘自動停止。主動停止絕不會被保活邏輯對抗。(真機 runner 有意豁免閒置回收:重啟它意味著一次數分鐘的 `xcodebuild` 重新建置。)
## 環境需求
- **macOS + 完整版 Xcode**——僅裝 Command Line Tools 不夠。`xcodebuild`、`xcrun simctl` 和模擬器執行時期都隨 Xcode 提供。
- **Xcode 中至少安裝一個 iOS 模擬器執行時期**。
- **DSH ≥ 0.1.0-rc.6 且使用 Web 版**,才能顯示面板。無頭(headless)設定下外掛程式同樣可用:22 個工具照常工作,只是沒有即時畫面。
- **非 macOS 主機**:外掛程式依然能載入,22 個工具也會註冊,但每次呼叫都會回傳明確的錯誤訊息(`iOS Simulator requires macOS with Xcode …`)。
- **serve-sim** 作為本外掛程式的 npm 依賴隨套件安裝,正式安裝時會從本地解析;開發目錄則回退到 `npx -y serve-sim`(首次使用需要連網)。
- **AXe**(可選——只有基於 AXe 的工具需要:`ios_sim_ui_tree` / `ios_sim_tap_element`,以及模擬器上的 `ios_sim_ui_rows` / `ios_sim_tap_row`):`brew install cameroncooke/axe/axe`,或讓外掛程式自動下載固定版本(v1.8.0,驗證 SHA-256)到 `~/Library/Caches/dsh-ios/bin`。`DSH_IOS_AXE_BIN` 可覆蓋解析結果;`DSH_IOS_AXE_OFFLINE=1` 可停用下載。
- **Vision OCR**(可選——只有 `ios_sim_find_text` / `ios_sim_tap_text` 需要):外掛程式首次使用時用 `swiftc` 把內建的 `assets/ocr.swift` 編譯到 `~/Library/Caches/dsh-ios/bin/ocr`(識別 zh-Hans + en-US)。
- **lldb attach 需要 macOS 開發者模式**:執行一次 `sudo DevToolsSecurity -enable`。在此之前 `ios_sim_backtrace` 會改用 Xcode 的 `sample` 引擎(不掛起處理程序),`ios_sim_leaks` 會帶著開啟提示降級執行。。首次 WDA 建置會安裝簽章的 WebDriverAgentRunner:按提示在裝置上信任其憑證;免費團隊簽章描述檔 7 天過期後需重新執行 `ios_real_start_wda`。
## 安裝到 DSH
```sh
dsh plugin --profile web add @zseven-w/dsh-ios@latest
dsh web
```
## 快速開始
一次典型的對話流程:
1. **發現裝置**——「列出可用的模擬器。」 → `ios_sim_devices`。
2. **啟動**——「啟動 iPhone 17 Pro。」 → `ios_sim_boot`。推流開始,**「iOS 模拟器」面板**隨之開啟:裝置在側邊欄裡即時顯示。(點擊任意模擬器卡片所在列,或輸入框上方的狀態膠囊,即可重新開啟。)
3. **在畫面上點按**——直接在面板上點按、拖曳;或讓智慧代理驅動介面:「開啟設定,然後點 General。」 → `ios_sim_interact`(按元素身份點擊用 `ios_sim_ui_tree` + `ios_sim_tap_element`;按文字點擊用 `ios_sim_find_text` + `ios_sim_tap_text`;清單/資訊流 App 用 `ios_sim_ui_rows` + `ios_sim_tap_row`)。
4. **建置並執行你的 App**——「建置並執行 /path/to/MyApp.xcodeproj。」 → `ios_sim_build_run`。完整建置需要幾分鐘;完成後 App 會在模擬器裡啟動,你可以在面板裡即時觀看。
5. **預覽熱重載**——「顯示 /path/to/MyPackage 的 SwiftUI 預覽。」 → `ios_sim_preview start`。修改原始碼後,預覽會在約 2–5 秒內熱替換進正在執行的模擬器——無需重啟。
6. **驅動真機 iPhone**——用 USB 資料傳輸線連接手機並解鎖,然後說「在手機上啟動 WebDriverAgent。」 → `ios_real_start_wda`。面板切到手機的即時畫面,所有工具都接受其 `realDevices` 裡的 udid;呼叫失敗時讀面板狀態裡的編碼原因(`device-locked`、`cert-untrusted`、`profile-expired`、`tunnel-failed`、`device-unplugged`)。
## 疑難排解
- **backtrace 用的是 `sample` 而不是 lldb,或 leaks 提示受限檢查**——macOS 開發者模式未開啟。執行一次 `sudo DevToolsSecurity -enable` 後重試。在此之前工具會平滑降級:`ios_sim_backtrace` 回退到 Xcode 的 `sample`(已符號化、不掛起處理程序),`ios_sim_leaks` 會給出開啟提示。
- **`ios_sim_ui_tree` / `ios_sim_tap_element` 需要 AXe**——用 `brew install cameroncooke/axe/axe` 安裝,或讓外掛程式在首次使用時自動下載固定版本(需要能存取 github.com)。錯誤訊息裡始終附帶完整的安裝提示;`DSH_IOS_AXE_BIN=/path/to/axe` 可覆蓋解析結果。列工具(`ios_sim_ui_rows` / `ios_sim_tap_row`)在模擬器上同樣需要 AXe。
- **`ios_sim_find_text` / `ios_sim_tap_text` 報告缺少 OCR 助手**——首次使用會用 `swiftc`(需要 Xcode)把內建的 `assets/ocr.swift` 編譯到 `~/Library/Caches/dsh-ios/bin/ocr`;錯誤訊息裡帶具體路徑與提示。
- **`ios_sim_ui_rows` 找不到列**——結果會說明原因:深度太淺(調大 `max_depth`;真機上每次更深快照約 15–25 秒)、不是清單頁,或深度讀取後確實沒有無障礙資訊。淺讀絕不會被誤報為「缺少無障礙支援」。
- **iOS 26.2 模擬器上的 `ios_sim_leaks` 怪癖**——在 iOS 26.2 執行時期上,即使開發者模式已開啟,Xcode 的 `leaks` 也可能無法分析模擬器處理程序,報出 `Failed to get DYLD info` 或 minimal-corpse 之類的致命診斷。工具會平滑降級:你能看到原始診斷,目標處理程序必定被驗證恢復,不會卡住。外掛程式側沒有修復辦法——遇到時試試 `mode: "memgraph"` 或換一個執行時期。。
- **推流自己停了**——這是閒置策略,不是崩潰:沒有消費者(面板關閉、沒有掛載的卡片、沒有活躍路由)時,推流會在 5 分鐘後停止,並在下一次工具呼叫或開啟面板時重啟。崩潰的推流則會在約 5 秒內於背景自動重啟。
## 開發
```sh
pnpm install
pnpm run build # 主機 tsc + 用戶端打包 → lib/
pnpm run typecheck
```
`scripts/` 下的煙霧測試會驗證編譯產物 `lib/`(需要啟動模擬器或連接真機 USB 的部分僅限 macOS;設定 `DSH_IOS_SMOKE_SKIP_SIM=1` 可跳過這些部分):
| 腳本 | 覆蓋內容 |
| --- | --- |
| `node scripts/dev-smoke.mjs` | 模擬器主機:二進位解析、推流啟動、控制、保活、dispose。 |
| `node scripts/dev-tools-smoke.mjs [--full-build]` | 在真實模擬器上驗證核心工具(加 `--full-build` 還會執行一次真實建置)。 |
| `node scripts/dev-routes-smoke.mjs` | 簽章 Web 路由:grant、推流代理、截圖、ws 轉送、圍欄、過期。 |
| `node scripts/dev-card-smoke.mjs` | 用戶端卡片:靜態 SSR(斷言無 `
`)、status/capture 契約、近即時的網路部分。 |
| `node scripts/dev-panel-smoke.mjs` | 面板元件、尺寸模式、外框樣式、停靠/觸發/膠囊邏輯(純靜態)。 |
| `node scripts/dev-logs-smoke.mjs` | `ios_sim_logs` 的 snapshot/follow、篩選器、上限、處理程序回收。 |
| `node scripts/dev-uitree-smoke.mjs` | UI 樹工具:AXe 解析/下載管線、選擇器、真實模擬器上的樹與點擊。 |
| `node scripts/dev-debug-smoke.mjs` | 偵錯工具:處理程序、呼叫堆疊(lldb + sample)、洩漏、App 資訊。 |
| `node scripts/dev-preview-smoke.mjs` | 預覽熱重載:啟動、編輯 → 不重啟的熱替換、錯誤恢復、停止。 |
| `node scripts/dev-orphan-smoke.mjs` | 主機被異常殺死後孤兒 serve-sim 的收養/回收。 |
| `node scripts/dev-ocr-smoke.mjs` | Vision-OCR 工具:助手解析、swiftc 編譯快取、識別管線、tap-text 路由。 |
| `node scripts/dev-wda-smoke.mjs` | WebDriverAgent 主機:`ServerURLHere` 解析、失敗分類、通道、保活(mock;可選實測)。 |
| `node scripts/dev-realdevice-smoke.mjs` | 對 USB 連接的 iPhone 執行 `xcrun devicectl`——工具所用的真實程式碼路徑。 |
| `node scripts/dev-realstart-smoke.mjs` | `/real-start` 路由:圍欄、編碼拒絕、建置/啟動門控(純靜態)。 |
| `node scripts/dev-realtools-smoke.mjs` | `ios_sim_screenshot` / `ios_sim_interact` / `ios_sim_ui_tree` / `ios_sim_tap_element` 的真機後端,以及 `ios_real_start_wda`。 |
## 生態
- [DSH Android](https://github.com/ZSeven-W/dsh-android) — 在對話中執行 Android 模擬器或 USB 實機,全部由 adb 驅動
- [DSH Crew](https://github.com/ZSeven-W/dsh-crew) — 從 Claude Code / Codex 把任務派給 DSH agent
- [DSH Noema](https://github.com/ZSeven-W/dsh-noema) — DSH 的長期記憶
- [DSH OpenPencil](https://github.com/ZSeven-W/dsh-openpencil) — 在對話中檢視與編輯 `.op` 設計文件
## 致謝與授權
- [serve-sim](https://github.com/EvanBacon/serve-sim) —— Evan Bacon —— 模擬器推流引擎(Apache-2.0;隨套件安裝的執行時期依賴)。
- [AXe](https://github.com/cameroncooke/AXe) —— Cameron Cooke —— UI 樹工具所依賴的無障礙 CLI(MIT)。
- [WebDriverAgent](https://github.com/appium/WebDriverAgent) —— 外掛程式在真機上建置並啟動的 WebDriver 伺服器(BSD 授權)。
- 架構受 Codex 的「Build iOS Apps」外掛啟發;SwiftUI 預覽引擎是對其公開文件所述方案的潔淨室(clean-room)重實作,未複製任何 Codex 程式碼。
- 完整聲明見 [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md)。
**授權條款**:MIT