@zhz8888/dsh-ios-simulator-helper 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,53 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zhz8888
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ Upstream attribution
26
+ --------------------
27
+
28
+ This package is a DeepSeek Harness port of the ZCode official `ios-simulator`
29
+ plugin (`@zcode/ios-simulator-plugin`, version 0.1.0), which Z.ai distributes
30
+ under the MIT License.
31
+
32
+ Original work: Copyright (c) Z.ai
33
+ Plugin: ios-simulator 0.1.0 (ZCode official plugin, Z.ai)
34
+ Licence: MIT
35
+
36
+ The MIT terms reproduced above apply to the upstream work as well, and the
37
+ upstream copyright notice applies to it in addition to the notice at the top of
38
+ this file. Material ported from that plugin:
39
+
40
+ - the 20 tool definitions with their names, descriptions and argument
41
+ schemas, and the `ios_preflight`, simulator (`simctl`), project discovery
42
+ and scaffolding, `xcodebuild` orchestration, screenshot, log and `idb` UI
43
+ providers derived from it;
44
+ - the generated SwiftUI starter project — `App.swift`, `ContentView.swift`
45
+ and the Xcode 14-compatible `project.pbxproj` — kept verbatim;
46
+ - the `ios-dev` skill text, adapted for DSH;
47
+ - the plugin icon (`ios.svg`), which is the iOS wordmark supplied for this
48
+ project, refitted to DSH's 36x36 icon frame and made light/dark adaptive
49
+ (the glyph's original colour, its exact inverse under a dark scheme).
50
+
51
+ Everything else — the DSH host half, the browser half and its settings page,
52
+ the macOS-only host gate, the settings schema, the test suite and the scripts —
53
+ is original to this package and covered by the notice at the top of this file.
package/README.md CHANGED
@@ -1,3 +1,295 @@
1
- # Temporary Holding Version
1
+ # dsh-ios-simulator-helper
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
+
5
+ The package is published as `@zhz8888/dsh-ios-simulator-helper`, and the plugin page shows its localized name — **iOS Simulator** in English, **iOS 模拟器** in Chinese — read from `locale/en.json` and `locale/zh.json`, which carry the one-line description too.
6
+
7
+ A DeepSeek Harness (DSH) native port of the official ZCode `ios-simulator@0.1.0` plugin: all 20 original MCP tools preserved as native DSH tools, plus a full **settings page** (Settings → iOS Simulator) that edits the simulator parameters in-app instead of through environment variables.
8
+
9
+ - Tool and argument names are identical to the original, so workflows written for the `ios_simulator` MCP server keep working; one tool is added — `ios_shutdown_simulator`, because the original could boot a simulator but never stop one.
10
+ - 17 settings fields in 5 groups, all live-editable; an empty field falls back to the original plugin's `IOS_SIM_*` variable, then to the built-in default.
11
+ - The settings page detects the host system and, **on a non-macOS host, states that this plugin only supports macOS**; every `ios_*` tool refuses in one clear sentence instead of failing with `spawn xcrun ENOENT`.
12
+
13
+ ## Contents
14
+
15
+ - [Requirements](#requirements)
16
+ - [Install](#install)
17
+ - [Settings page](#settings-page)
18
+ - [Screenshots](#screenshots)
19
+ - [Tools](#tools)
20
+ - [Skill and slash command](#skill-and-slash-command)
21
+ - [Differences from the original](#differences-from-the-original)
22
+ - [Architecture](#architecture)
23
+ - [Development and verification](#development-and-verification)
24
+ - [Marketplace listing](#marketplace-listing)
25
+ - [Known limitations](#known-limitations)
26
+ - [License](#license)
27
+
28
+ ## Requirements
29
+
30
+ | Dependency | Notes |
31
+ |---|---|
32
+ | macOS | The only supported host. `xcrun simctl`, `xcodebuild` and Simulator.app exist nowhere else. |
33
+ | Full Xcode | `xcode-select -p` must point at `Xcode.app/Contents/Developer`; Command Line Tools alone are not enough. |
34
+ | An iOS Simulator runtime | At least one iOS/iPadOS runtime installed from Xcode → Settings → Platforms. |
35
+ | Node.js ≥ 22 | The DSH host runtime. |
36
+ | `idb` (optional) | Needed only by the `ios_ui_*` tools. Without it, build/run/screenshot/logs are unaffected. |
37
+
38
+ `ios_preflight` checks every item above and prints a fix for each failure; the settings page's "Run diagnostic" button runs the same checks.
39
+
40
+ ## Install
41
+
42
+ ### 1. Install dependencies
43
+
44
+ The package ships source plus a hand-written client bundle; the runtime dependencies come from the DSH profile.
45
+
46
+ ```bash
47
+ cd /path/to/dsh-ios-simulator-helper
48
+ pnpm install
49
+ ```
50
+
51
+ `@deepseek-ai/schemastery`, `@deepseek-ai/dsh-tools` and `@deepseek-ai/cordis` are **peer dependencies**: the harness ships all three, so the plugin declares the versions it is built against instead of pulling in a second copy. DSH's compatibility gate compares the `@deepseek-ai/dsh*` ranges in `peerDependencies` against the running version and refuses to install on a mismatch. The package has no runtime dependencies of its own.
52
+
53
+ ### 2. Install into a profile
54
+
55
+ **DSH Desktop (local GUI)**: the `desktop` profile is owned by the Electron app. Add this directory through the app's **plugin manager** (install from a local path).
56
+
57
+ **CLI / any other profile**:
58
+
59
+ ```bash
60
+ dsh plugin --profile <profile> add /absolute/path/to/dsh-ios-simulator-helper
61
+ ```
62
+
63
+ `package.json` declares `dsh.bundle.patch`, so DSH's reconcile step appends this package to `dsh.profile.bundles`, profile boot merges `cordis.patch.yml`, and exactly one host row is mounted.
64
+
65
+ **Manual install** (no CLI): add the package as a `file:` dependency in the profile and append its name to `dsh.profile.bundles`:
66
+
67
+ ```jsonc
68
+ // ~/.dsh/profiles/<profile>/package.json
69
+ {
70
+ "dependencies": {
71
+ "@zhz8888/dsh-ios-simulator-helper": "file:/absolute/path/to/dsh-ios-simulator-helper"
72
+ },
73
+ "dsh": {
74
+ "profile": {
75
+ "bundles": ["@deepseek-ai/dsh-base", "…", "@zhz8888/dsh-ios-simulator-helper"]
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ```bash
82
+ cd ~/.dsh/profiles/<profile> && pnpm install
83
+ ```
84
+
85
+ Do **not** also add the row to the profile's own `cordis.patch.yml`: mounting the package twice registers the same tool names twice and fails at the loader. Use one channel only.
86
+
87
+ ### 3. Open the settings page
88
+
89
+ After a reload or restart, open **Settings → iOS Simulator**. The page only appears while the host half actually serves this plugin's settings namespace; otherwise it leaves no trace.
90
+
91
+ ## Settings page
92
+
93
+ The settings namespace equals the profile patch row id equals `ios-simulator-helper` (also `dsh.name`). The page reads and writes through `ctx.configForms`, so staged edits, dirty detection, inline validation, optimistic concurrency (revisions) and "overridden / reset to default" all come from the harness.
94
+
95
+ | Group | Field | Type | Default | Legacy env fallback | Notes |
96
+ |---|---|---|---|---|---|
97
+ | Simulator and device | `defaultDevice` | text (suggestions from this machine) | `iPhone 16` | `IOS_SIM_DEFAULT_DEVICE` | Device-name fragment used when a tool names no `udid`/`device`; falls back to the first available iPhone on a miss. |
98
+ | | `defaultRuntime` | text (suggestions from installed runtimes) | empty | `IOS_SIM_DEFAULT_RUNTIME` | Runtime fragment such as `iOS-27`; empty matches any. A **preference, not a requirement**: a stale value widens the pool instead of erroring. |
99
+ | | `autoBootSimulator` | switch | on | `IOS_SIM_AUTO_BOOT` | When off, tools fail instead of running `simctl boot` for a stopped target. |
100
+ | | `autoOpenSimulator` | switch | on | `IOS_SIM_AUTO_OPEN` | Matches the original by opening the Simulator window (boot / show / open URL / build and run); off keeps everything headless. |
101
+ | | `bootTimeoutMs` | integer | `120000` | `IOS_SIM_BOOT_TIMEOUT_MS` | Longest wait for one `simctl boot` / `bootstatus`. |
102
+ | UI automation | `uiBackend` | select | `auto` | `IOS_SIM_UI_BACKEND` | `auto` probes for `idb`; `idb` requires it; `none` disables the UI tools; `xcodebuildmcp` is reserved. |
103
+ | | `idbBinary` | text | `idb` | `IOS_SIM_IDB_BINARY` | Command name or absolute path of `idb`. |
104
+ | | `uiTimeoutMs` | integer | `60000` | `IOS_SIM_UI_TIMEOUT_MS` | Longest runtime of one `idb` UI command. |
105
+ | Build and DerivedData | `defaultConfiguration` | text | `Debug` | `IOS_SIM_CONFIGURATION` | Used when a tool names no `configuration`. |
106
+ | | `buildTimeoutMs` | integer | `600000` | `IOS_SIM_BUILD_TIMEOUT_MS` | Longest runtime of one `xcodebuild build`. |
107
+ | | `extraBuildArgs` | multiline | empty | `IOS_SIM_EXTRA_BUILD_ARGS` | One argument per line, `#` starts a comment; inserted after `-derivedDataPath` and before `build`. |
108
+ | | `derivedDataDir` | text | empty | `IOS_SIM_DERIVED_DATA_DIR` | Empty uses `<data dir>/DerivedData/<project fingerprint>`. |
109
+ | | `disableCodeSigning` | switch | on | `IOS_SIM_DISABLE_CODE_SIGNING` | The original always passed `CODE_SIGNING_ALLOWED=NO`; off lets Xcode decide. |
110
+ | Logs | `logSeconds` | integer | `120` | `IOS_SIM_LOG_SECONDS` | Recent seconds `ios_logs` reads when the call names no window; capped at 3600. |
111
+ | | `logLimit` | integer | `20000` | `IOS_SIM_LOG_LIMIT` | Largest rendered log `ios_logs` returns when the call names no limit. |
112
+ | Output directories | `dataDir` | text | empty | `IOS_SIM_PLUGIN_DATA` | Root for screenshots, build logs and DerivedData; empty uses `$DSH_HOME/ios-simulator-helper`, then `~/.dsh/ios-simulator-helper`. |
113
+ | | `screenshotDir` | text | empty | `IOS_SIM_SCREENSHOT_DIR` | Empty uses `<data dir>/screenshots`. |
114
+
115
+ Below the form sit the host check and three buttons:
116
+
117
+ - **Host system** — decided twice: synchronously from the browser's own platform string (so a non-macOS browser sees the notice on first paint, before any request), then authoritatively from the host half's `GET /api/ios-simulator-helper/host`. The host wins on a conflict. When the host is unsupported (or the browser is, and the host is unknown), a red notice titled **"This plugin only supports macOS"** appears above the form and names the detected platform.
118
+ - **Run diagnostic** → `GET /api/ios-simulator-helper/status`, the same checks `ios_preflight` runs, listed one by one with status, detail and fix.
119
+ - **Refresh simulators and runtimes** → `GET /api/ios-simulator-helper/inventory`, which discovers the Xcode path and version, the macOS version, simulators, runtimes and the resolved default target to fill the fields' suggestion lists.
120
+
121
+ ### Legacy configuration
122
+
123
+ The original plugin's `userConfig` (`default_device`, `ui_backend`) and its environment variables `IOS_SIM_DEFAULT_DEVICE`, `IOS_SIM_UI_BACKEND` and `IOS_SIM_PLUGIN_DATA` still work as fallbacks (last column above), so an existing ZCode or CI setup needs no change. The original's `IOS_SIM_PACKAGE_ROOT` only located the plugin for its own MCP launcher; DSH resolves the package path directly and does not need it.
124
+
125
+ ## Screenshots
126
+
127
+ All four are the live page in DSH Desktop, in the order you scroll it — nothing is mocked up.
128
+
129
+ **1. Simulator and device** — the fields every `ios_*` call reads:
130
+
131
+ ![The iOS Simulator settings page in DSH Desktop: the simulator-and-device group with default device, default runtime, auto boot, auto open and the boot timeout](assets/settings-page-1-simulator-and-device.png)
132
+
133
+ **2. UI automation and builds** — the `idb` backend and its timeout, then the `xcodebuild` configuration, build timeout and extra arguments:
134
+
135
+ ![The page scrolled to UI automation and build settings: UI backend, idb binary, UI timeout, default configuration, build timeout and the extra xcodebuild arguments](assets/settings-page-2-ui-automation-and-build.png)
136
+
137
+ **3. Logs and output** — the DerivedData directory and code-signing switch, then the `ios_logs` window and character limit, then where screenshots, logs and DerivedData land:
138
+
139
+ ![The page scrolled further: DerivedData directory, code signing during builds, the default log window and character limit, and the data and screenshot directories](assets/settings-page-3-logs-and-output.png)
140
+
141
+ **4. Host check and save** — the macOS verdict and the detected platform, the two detection buttons, and the save button:
142
+
143
+ ![The bottom of the page: the host check reading "macOS — supported (darwin/arm64)", the run-diagnostic and refresh buttons, and the save button](assets/settings-page-4-host-check.png)
144
+
145
+ `screenshots.json` lists these four files in this order next to `package.json`, which is how a plugin market finds them. `assets/` holds only these curated shots; run output and verification captures stay out of the repository (see `.gitignore`).
146
+
147
+ ## Tools
148
+
149
+ 21 tools: the original 20 with identical names and arguments, plus one addition.
150
+
151
+ | Tool | Purpose |
152
+ |---|---|
153
+ | `ios_preflight` | Check the host (macOS), Xcode, `xcodebuild`, `simctl`, runtimes, simulators and the UI backend, and echo the current settings. |
154
+ | `ios_list_simulators` | List iOS/iPadOS simulators and installed runtimes. |
155
+ | `ios_boot_simulator` | Boot the selected simulator, optionally opening the Simulator window. |
156
+ | `ios_show_simulator` | Open Simulator.app, optionally focused on one UDID. |
157
+ | `ios_shutdown_simulator` | **Added**: shut the selected simulator down (`simctl shutdown`); already-stopped is a success, not an error. |
158
+ | `ios_discover_project` | Find `.xcodeproj`/`.xcworkspace`, schemes and bundle ids. |
159
+ | `ios_create_app` | Generate a minimal SwiftUI project (refuses to overwrite generated files by default). |
160
+ | `ios_build_app` | Build the iOS Simulator product with `xcodebuild`. |
161
+ | `ios_build_and_run` | Build → install → launch → show. |
162
+ | `ios_install_app` | Install a `.app` on the selected simulator (absolute paths allowed). |
163
+ | `ios_launch_app` | Launch by bundle id. |
164
+ | `ios_terminate_app` | Terminate by bundle id. |
165
+ | `ios_open_url` | Open a URL in the simulator. |
166
+ | `ios_screenshot` | Capture a PNG to disk, and return it as an image when the model accepts image input. |
167
+ | `ios_logs` | Read `log show` output, optionally filtered by bundle id. |
168
+ | `ios_ui_status` | Report UI-automation availability and the backend the settings selected. |
169
+ | `ios_ui_tap` / `ios_ui_swipe` / `ios_ui_type_text` / `ios_ui_button` / `ios_ui_describe` | idb-backed tap / swipe / typing / hardware button / accessibility tree. |
170
+
171
+ Two behaviours deserve their own note:
172
+
173
+ - **`ios_screenshot`** always writes the PNG to disk (under `<data dir>/screenshots/` by default) and additionally submits it to the host's attachment service for a canonical image block. When the current model declares no image input, or the deployment stores no attachments, the result degrades to text plus the file path instead of failing the call.
174
+ - **Bundle-id resolution in `ios_build_and_run`** (a fix over the original): the original asked `xcodebuild -showBuildSettings` and nothing else. Without `-derivedDataPath` that command prepares the *default* DerivedData directory, fails in a restricted environment, and the original silently returned `undefined` — so a successful build was followed by "bundle id could not be resolved". This plugin resolves in the order *explicit `bundleId` argument → `CFBundleIdentifier` of the `.app` that was just built (read with `plutil`, i.e. the value simctl installs) → `-showBuildSettings` with the same `-derivedDataPath` → identifiers found in `project.pbxproj`*, and reports the winner as `bundleIdSource`.
175
+
176
+ ## Skill and slash command
177
+
178
+ - **`ios-dev` skill** — shipped in `skills/ios-dev/`, registered by the host half through `ctx.skills.register()` as an embedded skill with the skill directory as its `resourceBase`. Rewritten for DSH: no `mcp__…` prefix, environment configuration points at the settings page, and the macOS precondition plus the "shut the simulator down when you are done" step are spelled out.
179
+ - **`/ios-dev [goal]` command** — registered through `ctx.commands.register()`. DSH user commands deliberately never create a model message, so this command does the one thing that is useful without the model: it runs the environment diagnostic and prints the verdict, the failing checks and their fixes. On a non-macOS host the verdict is the "only supports macOS" sentence.
180
+
181
+ ## Differences from the original
182
+
183
+ | Aspect | Original (ZCode) | This plugin (DSH) |
184
+ |---|---|---|
185
+ | Tool exposure | stdio MCP server `ios-simulator`; the model sees `mcp__ios_simulator__*` | Native `ctx.tools.register()`; the model sees `ios_*` |
186
+ | User configuration | `plugin.json` `userConfig` (2 items), injected as environment variables | Settings page with 17 fields on a schemastery `Config`, all `.volatile()`; the old environment variables remain as fallbacks |
187
+ | Host check | `process.platform` read once, inside `ios_preflight` | Centralised `requireMacOS()`: one sentence per tool, plus the settings page's synchronous and authoritative verdicts |
188
+ | Working directory | `process.cwd()` | `exec.agent.session.header.cwd` (the session workspace) |
189
+ | Data directory | `IOS_SIM_PLUGIN_DATA` / temp | `$DSH_HOME/ios-simulator-helper`, configurable on the settings page |
190
+ | Path safety | `inside(cwd, path)` refuses escapes | Same semantics, split into read-only paths (an absolute `.app` is fine) and write targets (must stay inside the session workspace) |
191
+ | Screenshot result | MCP image content (base64) | An image block built from a host attachment reference, degraded to text plus a disk path on refusal |
192
+ | Result normalisation | Providers return objects containing `undefined` | Every tool result passes through `toLosslessJson()` (the registry rejects `undefined`) |
193
+ | Slash command | Injects a prompt into the model's conversation | Runs the diagnostic and prints the verdict |
194
+ | Process invocation | `node:child_process` spawn | Unchanged |
195
+
196
+ Keeping direct spawns is a deliberate trade-off: `xcodebuild`, `simctl` and `idb` then behave byte-for-byte as they did in the original. The cost is bypassing DSH's subprocess seam — no unified process management, environment scrubbing or shutdown-time termination. Every external call here has an explicit timeout and a `SIGTERM`, and no credential environment variable is read or forwarded.
197
+
198
+ ## Architecture
199
+
200
+ ```
201
+ dsh-ios-simulator-helper/
202
+ ├── package.json # dsh.bundle.patch, dsh.client, peer dependencies (compatibility gate)
203
+ ├── cordis.patch.yml # - insert: [{ id: ios-simulator-helper, name: '@zhz8888/dsh-ios-simulator-helper' }]
204
+ ├── ios.svg # plugin icon: iOS wordmark in its original colour, inverted in dark mode
205
+ ├── locale/en.json # plugin-page name and description (English; its presence is what enables every other language)
206
+ ├── locale/zh.json # the Simplified Chinese pair
207
+ ├── README.md # this file (English, primary)
208
+ ├── README.zh-CN.md # Simplified Chinese edition
209
+ ├── lib/
210
+ │ ├── index.js # host half: tools, skill, command, three Fetch routes, settings policy
211
+ │ ├── client.js # browser half: hand-written __ModuleLoader__ bundle, settings page + host check
212
+ │ ├── settings.js # field declarations, schemastery Config (all .volatile()), live reads
213
+ │ ├── runtime.js # per-call runtime (session cwd + live settings + platform → data dirs)
214
+ │ ├── tools/index.js # 21 defineTool definitions, argument/result schemas, image admission
215
+ │ ├── providers/ # ported file by file from the original, now taking the runtime explicitly
216
+ │ └── util/ # process execution, paths, content blocks
217
+ ├── skills/ios-dev/ # SKILL.md
218
+ ├── scripts/ # check-bundle.mjs (consistency gate), verify-mount.mjs, smoke.mjs, link-dsh-deps.mjs
219
+ └── test/ # node:test unit and integration tests
220
+ ```
221
+
222
+ Four constraints shaped the layout:
223
+
224
+ 1. **The settings namespace has exactly one source of truth**: the profile patch row id. The `id` in `cordis.patch.yml`, `SETTINGS_NAMESPACE` in `lib/settings.js` and the literal in `lib/client.js` must agree; `scripts/check-bundle.mjs` verifies them, the field-table drift between the two halves, the three routes, and that the macOS logic is still in place.
225
+ 2. **A browser bundle cannot import a host module** (the client purity gate forbids cross-plugin value imports), so the field table is written twice and kept honest by that gate; the bundle may only `require` the shell's nine static modules (React, Cordis, `dsh-client-store`, `dsh-client-ui-slots`, `dsh-client-ui-primitives`, `dsh-client-ui-dockkit`, …).
226
+ 3. **The platform check is centralised**: `hostInfo()` / `requireMacOS()` in `lib/providers/host.js` are the only places that read platform semantics, and the platform is an injectable parameter, so the non-macOS branch is testable on a Mac.
227
+ 4. **The plugin-page name is a package resource, not a manifest field**: DSH resolves `<package>/locale/en.json` through the `exports` map, so `./locale/*.json` has to stay exported and `locale` has to stay in `files`; either omission degrades silently to showing the raw package name. `en.json` is also the switch that enables every other language file, so it cannot be dropped in favour of `zh.json`. `test/manifest.test.js` pins all three conditions.
228
+
229
+ ## Development and verification
230
+
231
+ ```bash
232
+ pnpm install # schemastery + react/react-dom (tests only)
233
+ pnpm test # 80 tests, no Xcode and no simulator required
234
+ pnpm run check # tests + host/client consistency gate + real Cordis mount
235
+ pnpm run verify:mount # real Cordis Context + real ToolRuntime registry
236
+ pnpm run smoke # read-only probes on the real machine
237
+ pnpm run smoke:simulator # generated project → build → install/launch → screenshot → logs → terminate → shutdown
238
+ pnpm run link:dsh-deps # offline development: symlink the local DSH @deepseek-ai packages
239
+ ```
240
+
241
+ `smoke:simulator` accepts `--device <fragment>`, `--data-dir <path>`, `--keep` and `--no-macros`:
242
+
243
+ - The data directory defaults to the generated project, so the run also completes from a restricted (sandboxed) shell, and the cleanup shuts down **only a simulator this run booted** — never one you were already using.
244
+ - `--no-macros` replaces the generated `ContentView.swift` with a macro-free variant. The original template's `@State` / `#Preview` need `swift-plugin-server`, whose macro expansion runs under `sandbox-exec`; **a caller that is itself sandboxed cannot start that nested sandbox** and the compile fails with `sandbox-exec: sandbox_apply: Operation not permitted` — which has nothing to do with this plugin (the same command line fails for the original). The flag exists so the rest of the chain — build orchestration, product discovery, install, launch, screenshot, logs, terminate, shutdown — stays verifiable from such a shell.
245
+
246
+ ### Four verification layers
247
+
248
+ | Layer | Command | What it proves |
249
+ |---|---|---|
250
+ | Unit / parsers | `pnpm test` | Client-bundle loading contract and static-module limits, manifest and patch contract, settings projection (live volatile reads, environment fallbacks, out-of-range fallbacks, boolean spellings), and the ported parsers (`simctl list` devices and runtimes, UDID validation, selection order, bundle-id sources, `plutil`, path boundaries, log clamping, lossless-JSON normalisation) |
251
+ | Settings-page render | `pnpm test` (`client-render.test.js`) | The whole page rendered with real React and `react-dom/server` in four host scenarios (macOS, Linux, Windows, undetectable): every group, field and dictionary key present, controlled inputs carrying real values, switches and selects in the right state, **the "only supports macOS" notice shown on non-macOS and absent on macOS**, the notice naming the detected platform, and no unresolved dictionary key |
252
+ | Consistency gate | `scripts/check-bundle.mjs` | Bundle id = package name, namespace = patch row id, the two field tables agreeing key by key and option by option, the three routes present on both sides, the macOS verdict and notice keys still in place, and the bundle still a classic script |
253
+ | Real host mount | `scripts/verify-mount.mjs` | Mounting on a **real** Cordis `Context` and the **real** `ToolRuntime`: 21 tools registered, read-only tools dispatched through the registry and validated against their schemas, malformed arguments refused by the registry, unknown tool names unresolvable, all 17 settings fields volatile, and every tool unregistered after dispose |
254
+
255
+ `smoke` covers what only a real machine can prove: Xcode discovery, actual `simctl` calls, whether the generated project compiles, product discovery and bundle-id resolution, and install / launch / screenshot / logs / terminate / shutdown.
256
+
257
+ ## Marketplace listing
258
+
259
+ This repository is shaped for the [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) list and its [contributing guide](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/blob/main/contributing.md). What that guide asks for, and where it lives here:
260
+
261
+ | Requirement | Where it is satisfied |
262
+ |---|---|
263
+ | A `dsh.bundle` manifest in `package.json` — the thing that makes `dsh plugin add` work | `dsh.bundle.patch` → `./cordis.patch.yml`; asserted by `test/manifest.test.js` |
264
+ | A `cordis.patch.yml` next to `package.json` that mounts the plugin | `cordis.patch.yml`, one `insert` row whose id is the settings namespace |
265
+ | Real, working code rather than a placeholder | `lib/` (host half, client half, 21 tools), `test/`, `scripts/` |
266
+ | Official `@deepseek-ai/*` packages declared as `peerDependencies`, not `dependencies` | `package.json` — the package has **no runtime dependencies** |
267
+ | A peer range that does not silently exclude prerelease harness builds | every range that names a prerelease also names its next tuple: `^0.2.0-rc.2 \|\| ^0.2.1-alpha.1` |
268
+ | 1-8 screenshots declared in this repository | `screenshots.json` → `assets/`, with the `1-8`, in-repo, no-`..` rules asserted by `test/manifest.test.js` |
269
+ | `repository`, `homepage` and `bugs` pointing at the listed repository | `package.json`, asserted by `test/manifest.test.js` |
270
+
271
+ The submission itself is a single file in the list repository — `data/plugins/zhz8888__dsh-ios-simulator-helper.yml`, with `url: https://github.com/zhz8888/dsh-ios-simulator-helper`, `name: zhz8888/dsh-ios-simulator-helper` and a `description.en` that states only what the code does. Category `dev`: the plugin builds, runs and inspects iOS apps from the harness.
272
+
273
+ Three things no repository can do for itself, and the only ones left:
274
+
275
+ 1. **Push this repository to `https://github.com/zhz8888/dsh-ios-simulator-helper`.** The list's CI fetches `package.json` from that exact URL, which is why `repository`, `homepage` and `bugs` here already name it. If the repository ends up under another owner or name, change those three fields (and the test that pins them) first.
276
+ 2. **Add the `dsh-plugin` topic** to the GitHub repository.
277
+ 3. **Wait a day.** The CI rejects repositories younger than 24 hours, so the pull request cannot be opened the same day the repository is created.
278
+
279
+ Publishing to npm is optional and changes nothing about listing; if you do publish, the package's `repository` field already points back here, which is what links the two.
280
+
281
+ ## Known limitations
282
+
283
+ - **macOS only.** On any other host `ios_preflight` fails the `Host OS (macOS)` check, skips the remaining probes instead of producing meaningless spawn failures, and every `ios_*` tool refuses; the settings page keeps the notice at the top.
284
+ - `ios_ui_*` needs `idb`; `ios_ui_status` explains what is missing. `xcodebuildmcp` is only a reserved value (matching the original's `reservedBackends`) and is not implemented.
285
+ - The original's `ios_build_and_run` always opened the Simulator window; here that is the `autoOpenSimulator` setting (on by default, so behaviour matches). `ios_build_app` and `ios_screenshot` still do not open windows, as in the original.
286
+ - `ios_logs` reads `log show` (no live stream), with a configurable and clamped window and length.
287
+ - Swift-macro projects cannot be compiled when the caller is itself sandboxed (see `--no-macros`); DSH host-half code runs outside the bash sandbox, so normal use is unaffected.
288
+ - The generated project template is byte-for-byte the original's (an Xcode 14-compatible `project.pbxproj`, `objectVersion = 56`) and does not track newer Xcode templates.
289
+ - **The settings page has been eyeballed once, in DSH Desktop**: the package was installed into the `desktop` profile from a local path, the page opened at Settings → iOS Simulator, and the four shots in `assets/` are that session, top to bottom. Dark theme, narrow-width wrapping and the non-macOS notice are covered by the render tests, not by eye.
290
+ - `@deepseek-ai/dsh-client-ui-primitives` is a browser bundler input (`*.module.css`, `simple-icons`, `zustand/vanilla`) that plain Node cannot import, so the render tests double that package; React itself and `react-dom/server` are real.
291
+ - No credential handling: the plugin never reads or forwards key/token/secret environment variables.
292
+
293
+ ## License
294
+
295
+ [MIT](LICENSE), copyright (c) 2026 zhz8888. Tool definitions, provider logic and the generated SwiftUI project template are ported from the ZCode official `ios-simulator` plugin, distributed under the MIT License by Z.ai; the template sources are kept verbatim. See the attribution at the end of [LICENSE](LICENSE).