@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 +53 -0
- package/README.md +294 -2
- package/README.zh-CN.md +297 -0
- package/cordis.patch.yml +23 -0
- package/ios.svg +40 -0
- package/lib/client.js +1053 -0
- package/lib/index.js +308 -0
- package/lib/providers/build.js +214 -0
- package/lib/providers/host.js +97 -0
- package/lib/providers/logs.js +66 -0
- package/lib/providers/preflight.js +260 -0
- package/lib/providers/project.js +390 -0
- package/lib/providers/screenshot.js +56 -0
- package/lib/providers/sim.js +338 -0
- package/lib/providers/ui.js +177 -0
- package/lib/runtime.js +92 -0
- package/lib/settings.js +403 -0
- package/lib/tools/index.js +400 -0
- package/lib/util/content.js +85 -0
- package/lib/util/path.js +89 -0
- package/lib/util/run.js +140 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +82 -4
- package/skills/ios-dev/SKILL.md +65 -0
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
|
-
#
|
|
1
|
+
# dsh-ios-simulator-helper
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+

|
|
132
|
+
|
|
133
|
+
**2. UI automation and builds** — the `idb` backend and its timeout, then the `xcodebuild` configuration, build timeout and extra arguments:
|
|
134
|
+
|
|
135
|
+

|
|
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
|
+

|
|
140
|
+
|
|
141
|
+
**4. Host check and save** — the macOS verdict and the detected platform, the two detection buttons, and the save button:
|
|
142
|
+
|
|
143
|
+

|
|
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).
|