@ceraph/react-native-mcp 0.3.2 → 0.4.5

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.
Files changed (132) hide show
  1. package/README.md +335 -68
  2. package/dist/babel-plugin/index.cjs +1 -0
  3. package/dist/babel-plugin/index.js +1 -0
  4. package/dist/cli.d.ts +3 -1
  5. package/dist/cli.js +1 -47
  6. package/dist/index.d.ts +106 -1
  7. package/dist/index.js +2 -1651
  8. package/dist/shim/async-storage-ops.d.ts +26 -0
  9. package/dist/shim/async-storage-ops.js +1 -0
  10. package/dist/shim/boot.d.ts +9 -0
  11. package/dist/shim/boot.js +1 -141
  12. package/dist/shim/camera.js +1 -62
  13. package/dist/shim/command-poll.d.ts +18 -0
  14. package/dist/shim/command-poll.js +1 -0
  15. package/dist/shim/config.js +1 -56
  16. package/dist/shim/console-capture.d.ts +16 -0
  17. package/dist/shim/console-capture.js +1 -0
  18. package/dist/shim/deep-link.js +1 -25
  19. package/dist/shim/dev-guard.js +1 -3
  20. package/dist/shim/dev-host.d.ts +1 -0
  21. package/dist/shim/dev-host.js +1 -0
  22. package/dist/shim/error-handler.js +1 -66
  23. package/dist/shim/fetch-interceptor.js +1 -93
  24. package/dist/shim/index.d.ts +3 -0
  25. package/dist/shim/index.js +1 -6
  26. package/dist/shim/keep-awake.js +1 -118
  27. package/dist/shim/network-ownership.d.ts +4 -0
  28. package/dist/shim/network-ownership.js +1 -0
  29. package/dist/shim/optimistic-observer.d.ts +29 -0
  30. package/dist/shim/optimistic-observer.js +1 -0
  31. package/dist/shim/reload.js +1 -76
  32. package/dist/shim/reset.d.ts +30 -0
  33. package/dist/shim/reset.js +1 -0
  34. package/dist/shim/signal-capture.d.ts +8 -0
  35. package/dist/shim/signal-capture.js +1 -15
  36. package/dist/shim/signal-transport.d.ts +14 -1
  37. package/dist/shim/signal-transport.js +1 -43
  38. package/dist/shim/xhr-interceptor.d.ts +39 -0
  39. package/dist/shim/xhr-interceptor.js +1 -0
  40. package/package.json +41 -15
  41. package/dist/app-lifecycle.d.ts +0 -50
  42. package/dist/app-lifecycle.js +0 -487
  43. package/dist/camera-image-writer.d.ts +0 -43
  44. package/dist/camera-image-writer.js +0 -280
  45. package/dist/camera-registry-sync.d.ts +0 -18
  46. package/dist/camera-registry-sync.js +0 -117
  47. package/dist/device-autonomy.d.ts +0 -30
  48. package/dist/device-autonomy.js +0 -117
  49. package/dist/error-parser.d.ts +0 -51
  50. package/dist/error-parser.js +0 -275
  51. package/dist/expo-manager.d.ts +0 -62
  52. package/dist/expo-manager.js +0 -447
  53. package/dist/init/ast-camera.d.ts +0 -29
  54. package/dist/init/ast-camera.js +0 -267
  55. package/dist/init/ast-layout.d.ts +0 -15
  56. package/dist/init/ast-layout.js +0 -167
  57. package/dist/init/claude-hook-constants.d.ts +0 -9
  58. package/dist/init/claude-hook-constants.js +0 -91
  59. package/dist/init/lan-ip.d.ts +0 -11
  60. package/dist/init/lan-ip.js +0 -51
  61. package/dist/init/monorepo.d.ts +0 -13
  62. package/dist/init/monorepo.js +0 -185
  63. package/dist/init/oauth.d.ts +0 -52
  64. package/dist/init/oauth.js +0 -220
  65. package/dist/init/package-manager.d.ts +0 -11
  66. package/dist/init/package-manager.js +0 -60
  67. package/dist/init/prompt.d.ts +0 -12
  68. package/dist/init/prompt.js +0 -68
  69. package/dist/init/shell-profile.d.ts +0 -22
  70. package/dist/init/shell-profile.js +0 -85
  71. package/dist/init/steps.d.ts +0 -135
  72. package/dist/init/steps.js +0 -399
  73. package/dist/init/url-scheme.d.ts +0 -42
  74. package/dist/init/url-scheme.js +0 -187
  75. package/dist/init/walkthrough.d.ts +0 -76
  76. package/dist/init/walkthrough.js +0 -340
  77. package/dist/init.d.ts +0 -8
  78. package/dist/init.js +0 -395
  79. package/dist/iproxy-manager.d.ts +0 -32
  80. package/dist/iproxy-manager.js +0 -216
  81. package/dist/mac-caffeinate.d.ts +0 -10
  82. package/dist/mac-caffeinate.js +0 -56
  83. package/dist/permission-interceptor.d.ts +0 -29
  84. package/dist/permission-interceptor.js +0 -185
  85. package/dist/prebuild-detector.d.ts +0 -19
  86. package/dist/prebuild-detector.js +0 -174
  87. package/dist/preflight.d.ts +0 -34
  88. package/dist/preflight.js +0 -847
  89. package/dist/screen.d.ts +0 -184
  90. package/dist/screen.js +0 -931
  91. package/dist/signal-listener.d.ts +0 -27
  92. package/dist/signal-listener.js +0 -135
  93. package/dist/simulator-boot.d.ts +0 -52
  94. package/dist/simulator-boot.js +0 -227
  95. package/dist/target.d.ts +0 -48
  96. package/dist/target.js +0 -267
  97. package/dist/uninstall/cli-runner.d.ts +0 -32
  98. package/dist/uninstall/cli-runner.js +0 -223
  99. package/dist/uninstall/footprint.d.ts +0 -40
  100. package/dist/uninstall/footprint.js +0 -288
  101. package/dist/uninstall/mcp-tools.d.ts +0 -14
  102. package/dist/uninstall/mcp-tools.js +0 -175
  103. package/dist/uninstall/revert-auth.d.ts +0 -22
  104. package/dist/uninstall/revert-auth.js +0 -31
  105. package/dist/uninstall/revert-boot.d.ts +0 -24
  106. package/dist/uninstall/revert-boot.js +0 -242
  107. package/dist/uninstall/revert-camera.d.ts +0 -12
  108. package/dist/uninstall/revert-camera.js +0 -199
  109. package/dist/uninstall/revert-ceraph-dir.d.ts +0 -27
  110. package/dist/uninstall/revert-ceraph-dir.js +0 -38
  111. package/dist/uninstall/revert-claude-hooks.d.ts +0 -19
  112. package/dist/uninstall/revert-claude-hooks.js +0 -191
  113. package/dist/uninstall/revert-gitignore.d.ts +0 -17
  114. package/dist/uninstall/revert-gitignore.js +0 -43
  115. package/dist/uninstall/revert-mcp-clients.d.ts +0 -57
  116. package/dist/uninstall/revert-mcp-clients.js +0 -194
  117. package/dist/uninstall/revert-package.d.ts +0 -34
  118. package/dist/uninstall/revert-package.js +0 -98
  119. package/dist/uninstall/revert-scheme.d.ts +0 -36
  120. package/dist/uninstall/revert-scheme.js +0 -139
  121. package/dist/uninstall/revert-signal-host-env.d.ts +0 -31
  122. package/dist/uninstall/revert-signal-host-env.js +0 -61
  123. package/dist/uninstall/walkthrough.d.ts +0 -80
  124. package/dist/uninstall/walkthrough.js +0 -1244
  125. package/dist/utils/atomic-write.d.ts +0 -1
  126. package/dist/utils/atomic-write.js +0 -30
  127. package/dist/wait-for-device.d.ts +0 -68
  128. package/dist/wait-for-device.js +0 -368
  129. package/dist/wda-manager.d.ts +0 -38
  130. package/dist/wda-manager.js +0 -186
  131. package/dist/wda-simulator.d.ts +0 -28
  132. package/dist/wda-simulator.js +0 -257
package/dist/init.js DELETED
@@ -1,395 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readFile, writeFile, mkdir, access, chmod } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { runWalkthrough } from "./init/walkthrough.js";
5
- import { AppLifecycle } from "./app-lifecycle.js";
6
- import { ScreenManager } from "./screen.js";
7
- import { DeviceAutonomy } from "./device-autonomy.js";
8
- import { runPreflight } from "./preflight.js";
9
- import { HOOK_SCRIPT, FLOW_PROGRESS_HOOK_SCRIPT, ERROR_HOOK_COMMAND, ERROR_HOOK_MATCHER, FLOW_PROGRESS_HOOK_COMMAND, FLOW_PROGRESS_HOOK_MATCHER, } from "./init/claude-hook-constants.js";
10
- import { writeFileAtomic } from "./utils/atomic-write.js";
11
- const PROJECT_DIR = process.cwd();
12
- const MCP_ENTRY = {
13
- "mobile-mcp": {
14
- command: "npx",
15
- args: ["-y", "@mobilenext/mobile-mcp@latest"],
16
- },
17
- "react-native-mcp": {
18
- command: "npx",
19
- args: ["-y", "@ceraph/react-native-mcp@latest"],
20
- },
21
- };
22
- async function fileExists(path) {
23
- try {
24
- await access(path);
25
- return true;
26
- }
27
- catch {
28
- return false;
29
- }
30
- }
31
- async function readJson(path) {
32
- let raw;
33
- try {
34
- raw = await readFile(path, "utf-8");
35
- }
36
- catch (err) {
37
- const e = err;
38
- if (e && e.code === "ENOENT")
39
- return { kind: "missing" };
40
- throw err;
41
- }
42
- try {
43
- return { kind: "ok", value: JSON.parse(raw) };
44
- }
45
- catch (err) {
46
- return { kind: "malformed", raw, error: err };
47
- }
48
- }
49
- function isPlainObject(v) {
50
- return v != null && typeof v === "object" && !Array.isArray(v);
51
- }
52
- async function writeJson(path, data) {
53
- await mkdir(join(path, ".."), { recursive: true });
54
- await writeFileAtomic(path, JSON.stringify(data, null, 2) + "\n");
55
- }
56
- function log(msg) {
57
- console.log(` ${msg}`);
58
- }
59
- export async function setupJsonMcp(configPath, label) {
60
- const readResult = await readJson(configPath);
61
- if (readResult.kind === "malformed") {
62
- log(`${label}: existing config at ${configPath} is malformed JSON — preserving it. Fix the file manually then re-run init.`);
63
- return false;
64
- }
65
- const baseExisting = readResult.kind === "ok" ? readResult.value : {};
66
- if (readResult.kind === "ok" && !isPlainObject(baseExisting)) {
67
- log(`${label}: existing config at ${configPath} is not a JSON object (got ${Array.isArray(baseExisting) ? "array" : typeof baseExisting}) — preserving it.`);
68
- return false;
69
- }
70
- const existing = isPlainObject(baseExisting)
71
- ? baseExisting
72
- : {};
73
- const rawServers = existing.mcpServers;
74
- if (rawServers !== undefined && !isPlainObject(rawServers)) {
75
- log(`${label}: existing mcpServers in ${configPath} is not a JSON object (got ${Array.isArray(rawServers) ? "array" : typeof rawServers}) — preserving the file.`);
76
- return false;
77
- }
78
- const servers = isPlainObject(rawServers)
79
- ? rawServers
80
- : {};
81
- if (servers["react-native-mcp"]) {
82
- log(`${label}: already configured`);
83
- return false;
84
- }
85
- servers["mobile-mcp"] = MCP_ENTRY["mobile-mcp"];
86
- servers["react-native-mcp"] = MCP_ENTRY["react-native-mcp"];
87
- existing.mcpServers = servers;
88
- await writeJson(configPath, existing);
89
- log(`${label}: configured ✓`);
90
- return true;
91
- }
92
- async function setupCodexToml(configPath) {
93
- let content = "";
94
- try {
95
- content = await readFile(configPath, "utf-8");
96
- }
97
- catch {
98
- }
99
- if (content.includes("react-native-mcp")) {
100
- log("Codex: already configured");
101
- return false;
102
- }
103
- const tomlBlock = `
104
- [mcp_servers.mobile-mcp]
105
- command = "npx"
106
- args = ["-y", "@mobilenext/mobile-mcp@latest"]
107
-
108
- [mcp_servers.react-native-mcp]
109
- command = "npx"
110
- args = ["-y", "@ceraph/react-native-mcp@latest"]
111
- `;
112
- await mkdir(join(configPath, ".."), { recursive: true });
113
- await writeFileAtomic(configPath, content + tomlBlock);
114
- log("Codex: configured ✓");
115
- return true;
116
- }
117
- export async function setupClaudeHook(projectDir = PROJECT_DIR) {
118
- const hooksDir = join(projectDir, ".claude", "hooks");
119
- const hookPath = join(hooksDir, "rn-error-notify.sh");
120
- const progressHookPath = join(hooksDir, "rn-flow-progress-notify.sh");
121
- await mkdir(hooksDir, { recursive: true });
122
- await writeFileAtomic(hookPath, HOOK_SCRIPT);
123
- await chmod(hookPath, 0o755);
124
- log("Hook script: .claude/hooks/rn-error-notify.sh ✓");
125
- await writeFileAtomic(progressHookPath, FLOW_PROGRESS_HOOK_SCRIPT);
126
- await chmod(progressHookPath, 0o755);
127
- log("Hook script: .claude/hooks/rn-flow-progress-notify.sh ✓");
128
- const sharedSettingsPath = join(projectDir, ".claude", "settings.json");
129
- const localSettingsPath = join(projectDir, ".claude", "settings.local.json");
130
- const hasCeraphEntry = (s, matcher, command) => {
131
- const settingsObj = isPlainObject(s) ? s : {};
132
- const hooksVal = settingsObj.hooks;
133
- const hooks = isPlainObject(hooksVal)
134
- ? hooksVal
135
- : {};
136
- const fc = hooks.FileChanged;
137
- if (!Array.isArray(fc)) {
138
- return fc !== undefined;
139
- }
140
- return fc.some((entry) => {
141
- if (!isPlainObject(entry))
142
- return false;
143
- if (entry.matcher !== matcher)
144
- return false;
145
- const inner = entry.hooks;
146
- if (!Array.isArray(inner))
147
- return false;
148
- return inner.some((h) => {
149
- if (!isPlainObject(h))
150
- return false;
151
- const cmd = h.command;
152
- return typeof cmd === "string" && cmd === command;
153
- });
154
- });
155
- };
156
- const sharedRead = await readJson(sharedSettingsPath);
157
- const localRead = await readJson(localSettingsPath);
158
- if (sharedRead.kind === "malformed") {
159
- log(`Claude Code hook: ${sharedSettingsPath} is malformed JSON — preserving it. Fix the file manually then re-run init.`);
160
- return;
161
- }
162
- if (localRead.kind === "malformed") {
163
- log(`Claude Code hook: ${localSettingsPath} is malformed JSON — preserving it. Fix the file manually then re-run init.`);
164
- return;
165
- }
166
- const sharedSettings = sharedRead.kind === "ok" ? sharedRead.value : null;
167
- const localSettings = localRead.kind === "ok" ? localRead.value : null;
168
- if (sharedSettings !== null && !isPlainObject(sharedSettings)) {
169
- log(`Claude Code hook: ${sharedSettingsPath} top-level is not a JSON object — preserving it.`);
170
- return;
171
- }
172
- if (localSettings !== null && !isPlainObject(localSettings)) {
173
- log(`Claude Code hook: ${localSettingsPath} top-level is not a JSON object — preserving it.`);
174
- return;
175
- }
176
- const settings = sharedSettings ?? {};
177
- const hooksVal = settings.hooks;
178
- if (hooksVal !== undefined && !isPlainObject(hooksVal)) {
179
- log(`Claude Code hook: ${sharedSettingsPath} \`hooks\` is not a JSON object — preserving it.`);
180
- return;
181
- }
182
- const hooks = isPlainObject(hooksVal)
183
- ? hooksVal
184
- : {};
185
- const fcVal = hooks.FileChanged;
186
- const fileChangedHooks = Array.isArray(fcVal)
187
- ? fcVal
188
- : [];
189
- let mutated = false;
190
- if (!hasCeraphEntry(sharedSettings, ERROR_HOOK_MATCHER, ERROR_HOOK_COMMAND) &&
191
- !hasCeraphEntry(localSettings, ERROR_HOOK_MATCHER, ERROR_HOOK_COMMAND)) {
192
- fileChangedHooks.push({
193
- matcher: ERROR_HOOK_MATCHER,
194
- hooks: [
195
- {
196
- type: "command",
197
- command: ERROR_HOOK_COMMAND,
198
- },
199
- ],
200
- });
201
- mutated = true;
202
- log("Claude Code hook: FileChanged → .rn-errors.json ✓");
203
- }
204
- else {
205
- log("Claude Code hook (.rn-errors.json): already configured");
206
- }
207
- if (!hasCeraphEntry(sharedSettings, FLOW_PROGRESS_HOOK_MATCHER, FLOW_PROGRESS_HOOK_COMMAND) &&
208
- !hasCeraphEntry(localSettings, FLOW_PROGRESS_HOOK_MATCHER, FLOW_PROGRESS_HOOK_COMMAND)) {
209
- fileChangedHooks.push({
210
- matcher: FLOW_PROGRESS_HOOK_MATCHER,
211
- hooks: [
212
- {
213
- type: "command",
214
- command: FLOW_PROGRESS_HOOK_COMMAND,
215
- },
216
- ],
217
- });
218
- mutated = true;
219
- log("Claude Code hook: FileChanged → .rn-flow-progress.json ✓");
220
- }
221
- else {
222
- log("Claude Code hook (.rn-flow-progress.json): already configured");
223
- }
224
- if (mutated) {
225
- hooks.FileChanged = fileChangedHooks;
226
- settings.hooks = hooks;
227
- await writeJson(sharedSettingsPath, settings);
228
- }
229
- }
230
- const CAMERA_IMAGES_README = `# Ceraph camera test images
231
-
232
- Per-screen test images for \`<CeraphCamera />\` from
233
- \`@ceraph/react-native-mcp/shim\`. The filename (without extension) is
234
- the imageKey you pass on the JSX tag.
235
-
236
- ## Three ways to wire a camera
237
-
238
- \`<CeraphCamera />\` (no \`imageKey\`) — uninitialized. Falls back to a
239
- bundled 1024x1024 black PNG that ships with the package, so the screen
240
- renders. The MCP's \`rn_preflight\` lists these as info findings so an
241
- AI assistant can prompt you to make a deliberate choice. Pick from:
242
-
243
- 1. \`<CeraphCamera imageKey="default" />\` — explicit acknowledgment of
244
- the black PNG. Right answer for most form / upload / permission
245
- flows where the image content doesn't matter.
246
- 2. \`<CeraphCamera imageKey="profile" />\` — a specific test image you
247
- provide (this folder). Drop \`profile.jpg\` (or \`.png\` / \`.webp\` /
248
- \`.heic\`) here. The agent can write the file for you via
249
- \`ceraph_add_camera_image\`.
250
- 3. \`<CeraphCamera imageKey="@runtime" />\` — runtime control. The flow
251
- planner picks the image per-step at test time. Use when one camera
252
- screen feeds different scenarios across multiple flows.
253
-
254
- ## Naming
255
-
256
- Lowercase + hyphen-separated, descriptive of what the image represents:
257
-
258
- - \`profile.jpg\` — face photo for a profile picture screen
259
- - \`id-card.png\` — government-issued ID for a KYC scan screen
260
- - \`product-photo.jpg\` — a product image for an upload screen
261
-
262
- Supported extensions: \`.jpg\`, \`.jpeg\`, \`.png\`, \`.webp\`, \`.heic\`.
263
- Extension priority on collision: jpg > jpeg > png > webp > heic.
264
-
265
- ## Workflow
266
-
267
- 1. Drop the image into this folder with the right filename.
268
- 2. Reference it on the tag:
269
- \`\`\`tsx
270
- import { CeraphCamera } from "@ceraph/react-native-mcp/shim";
271
- <CeraphCamera imageKey="profile" />
272
- \`\`\`
273
- 3. Regenerate the registry: call the MCP tool
274
- \`rn_sync_camera_registry\`, or just call \`rn_preflight\` — it
275
- syncs the registry automatically.
276
- 4. In \`App.tsx\` or your root layout (once):
277
- \`\`\`tsx
278
- import { installCeraph } from "@ceraph/react-native-mcp/shim";
279
- useEffect(() => { installCeraph(); }, []);
280
- \`\`\`
281
- \`installCeraph()\` loads the generated registry from
282
- \`./.ceraph/camera-images/_registry\` and wires the deep-link
283
- handlers the flow planner uses.
284
-
285
- Commit these images so flows are reproducible across machines and CI.
286
- `;
287
- async function setupCameraImagesDir() {
288
- const dir = join(PROJECT_DIR, ".ceraph", "camera-images");
289
- await mkdir(dir, { recursive: true });
290
- const readmePath = join(dir, "README.md");
291
- if (!(await fileExists(readmePath))) {
292
- await writeFile(readmePath, CAMERA_IMAGES_README, "utf-8");
293
- log("Camera images: .ceraph/camera-images/ + README.md ✓");
294
- }
295
- else {
296
- log("Camera images: .ceraph/camera-images/ already exists");
297
- }
298
- }
299
- async function setupGitignore() {
300
- const gitignorePath = join(PROJECT_DIR, ".gitignore");
301
- let content = "";
302
- try {
303
- content = await readFile(gitignorePath, "utf-8");
304
- }
305
- catch {
306
- }
307
- const entries = [".rn-errors.json", ".rn-flow-progress.json", ".rn-mcp-cache/"];
308
- const missing = entries.filter((e) => !content.includes(e));
309
- if (missing.length === 0) {
310
- return;
311
- }
312
- const prefix = content === "" || content.endsWith("\n") ? "" : "\n";
313
- const addition = prefix + missing.join("\n") + "\n";
314
- await writeFileAtomic(gitignorePath, content + addition);
315
- log(`.gitignore: added ${missing.join(", ")} ✓`);
316
- }
317
- export async function setupMcpClients() {
318
- console.log("MCP configuration:");
319
- await setupJsonMcp(join(PROJECT_DIR, ".mcp.json"), "Claude Code");
320
- await setupJsonMcp(join(PROJECT_DIR, ".cursor", "mcp.json"), "Cursor");
321
- await setupCodexToml(join(PROJECT_DIR, ".codex", "config.toml"));
322
- await setupJsonMcp(join(PROJECT_DIR, ".vscode", "mcp.json"), "VS Code");
323
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
324
- if (home) {
325
- const windsurfPath = join(home, ".codeium", "windsurf", "mcp_config.json");
326
- if (await fileExists(join(home, ".codeium", "windsurf"))) {
327
- await setupJsonMcp(windsurfPath, "Windsurf");
328
- }
329
- const antigravityPath = join(home, ".gemini", "antigravity", "mcp_config.json");
330
- if (await fileExists(join(home, ".gemini", "antigravity"))) {
331
- await setupJsonMcp(antigravityPath, "Antigravity");
332
- }
333
- }
334
- }
335
- function summarisePreflight(checks) {
336
- const blockers = checks.filter((c) => c.severity === "error" && !c.ok);
337
- const warnings = checks.filter((c) => c.severity === "warning" && !c.ok);
338
- if (blockers.length === 0 && warnings.length === 0)
339
- return "";
340
- const lines = [];
341
- for (const c of blockers) {
342
- lines.push(` [BLOCKER] ${c.name}: ${c.message}`);
343
- if (c.remediation)
344
- lines.push(` → ${c.remediation}`);
345
- }
346
- for (const c of warnings) {
347
- lines.push(` [WARN] ${c.name}: ${c.message}`);
348
- if (c.remediation)
349
- lines.push(` → ${c.remediation}`);
350
- }
351
- return lines.join("\n") + "\n";
352
- }
353
- export async function runInit(opts = {}) {
354
- console.log("\n@ceraph/react-native-mcp init\n");
355
- await setupMcpClients();
356
- console.log("\nRuntime error hook:");
357
- await setupClaudeHook();
358
- console.log("\nCamera test images:");
359
- await setupCameraImagesDir();
360
- console.log("\nGitignore:");
361
- await setupGitignore();
362
- console.log("");
363
- await runWalkthrough(PROJECT_DIR, {
364
- agentMode: opts.agentMode === true,
365
- nonInteractive: opts.agentMode === true ? true : undefined,
366
- runPreflight: async () => {
367
- const screen = new ScreenManager();
368
- const apps = new AppLifecycle(screen);
369
- const autonomy = new DeviceAutonomy(screen);
370
- const res = await runPreflight({
371
- screen,
372
- apps,
373
- autonomy,
374
- projectDir: PROJECT_DIR,
375
- excludeRuntimeChecks: true,
376
- });
377
- const passedCount = res.checks.filter((c) => c.ok).length;
378
- return {
379
- ok: res.ok,
380
- summary: summarisePreflight(res.checks),
381
- passedCount,
382
- totalCount: res.checks.length,
383
- };
384
- },
385
- });
386
- }
387
- const argv1 = process.argv[1] ?? "";
388
- if (argv1.endsWith("init.js") || argv1.endsWith("init.ts")) {
389
- const agentMode = process.env.CERAPH_AGENT_MODE === "1" ||
390
- process.argv.includes("--agent");
391
- runInit({ agentMode }).catch((err) => {
392
- console.error("Init failed:", err);
393
- process.exit(1);
394
- });
395
- }
@@ -1,32 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- export interface IproxyManagerOptions {
3
- localPort?: number;
4
- devicePort?: number;
5
- spawnFn?: typeof spawn;
6
- whichFn?: (bin: string) => Promise<string | null>;
7
- }
8
- export interface IproxyStartResult {
9
- ok: boolean;
10
- reason?: "not-installed" | "spawn-failed" | "already-running";
11
- pid?: number;
12
- stderr?: string[];
13
- }
14
- export declare class IproxyManager {
15
- private child;
16
- private udid;
17
- private localPort;
18
- private devicePort;
19
- private stderrBuffer;
20
- private spawnFn;
21
- private whichFn;
22
- private startInFlight;
23
- constructor(opts?: IproxyManagerOptions);
24
- isRunning(): boolean;
25
- start(udid: string): Promise<IproxyStartResult>;
26
- private startInner;
27
- stop(): Promise<void>;
28
- restart(udid?: string): Promise<IproxyStartResult>;
29
- getStderr(): string[];
30
- private appendStderr;
31
- private detectEarlyExit;
32
- }
@@ -1,216 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- const DEFAULT_LOCAL = 8100;
3
- const DEFAULT_DEVICE = 8100;
4
- const MAX_LOG_LINES = 50;
5
- function defaultWhich(bin) {
6
- return new Promise((resolve) => {
7
- let stdout = "";
8
- let settled = false;
9
- const child = spawn("which", [bin], {
10
- stdio: ["ignore", "pipe", "ignore"],
11
- });
12
- child.stdout.on("data", (chunk) => {
13
- stdout += chunk.toString();
14
- });
15
- child.on("error", () => {
16
- if (!settled) {
17
- settled = true;
18
- resolve(null);
19
- }
20
- });
21
- child.on("exit", (code) => {
22
- if (settled)
23
- return;
24
- settled = true;
25
- if (code !== 0)
26
- resolve(null);
27
- else {
28
- const trimmed = stdout.trim();
29
- resolve(trimmed.length > 0 ? trimmed : null);
30
- }
31
- });
32
- });
33
- }
34
- export class IproxyManager {
35
- child = null;
36
- udid = null;
37
- localPort;
38
- devicePort;
39
- stderrBuffer = [];
40
- spawnFn;
41
- whichFn;
42
- startInFlight = null;
43
- constructor(opts = {}) {
44
- this.localPort = opts.localPort ?? DEFAULT_LOCAL;
45
- this.devicePort = opts.devicePort ?? DEFAULT_DEVICE;
46
- this.spawnFn = opts.spawnFn ?? spawn;
47
- this.whichFn = opts.whichFn ?? defaultWhich;
48
- }
49
- isRunning() {
50
- return this.child !== null && this.child.exitCode === null;
51
- }
52
- async start(udid) {
53
- while (true) {
54
- if (this.isRunning()) {
55
- return {
56
- ok: false,
57
- reason: "already-running",
58
- pid: this.child?.pid,
59
- };
60
- }
61
- if (!this.startInFlight)
62
- break;
63
- await this.startInFlight.catch(() => undefined);
64
- }
65
- const myInFlight = this.startInner(udid);
66
- this.startInFlight = myInFlight;
67
- try {
68
- return await myInFlight;
69
- }
70
- finally {
71
- if (this.startInFlight === myInFlight) {
72
- this.startInFlight = null;
73
- }
74
- }
75
- }
76
- async startInner(udid) {
77
- if (this.isRunning()) {
78
- return {
79
- ok: false,
80
- reason: "already-running",
81
- pid: this.child?.pid,
82
- };
83
- }
84
- const binPath = await this.whichFn("iproxy");
85
- if (!binPath) {
86
- return { ok: false, reason: "not-installed" };
87
- }
88
- this.udid = udid;
89
- this.stderrBuffer = [];
90
- let child;
91
- try {
92
- child = this.spawnFn(binPath, [
93
- String(this.localPort),
94
- String(this.devicePort),
95
- "-u",
96
- udid,
97
- ], { stdio: ["ignore", "pipe", "pipe"] });
98
- }
99
- catch (err) {
100
- this.appendStderr(`iproxy spawn threw: ${err instanceof Error ? err.message : String(err)}`);
101
- return { ok: false, reason: "spawn-failed", stderr: [...this.stderrBuffer] };
102
- }
103
- child.stderr?.on("data", (chunk) => {
104
- const text = chunk.toString();
105
- for (const line of text.split(/\r?\n/)) {
106
- if (line.trim().length === 0)
107
- continue;
108
- this.appendStderr(line);
109
- }
110
- });
111
- child.stdout?.on("data", (chunk) => {
112
- const text = chunk.toString();
113
- for (const line of text.split(/\r?\n/)) {
114
- if (line.trim().length === 0)
115
- continue;
116
- this.appendStderr(`[stdout] ${line}`);
117
- }
118
- });
119
- const earlyExit = await this.detectEarlyExit(child, 50);
120
- if (earlyExit) {
121
- this.child = null;
122
- return {
123
- ok: false,
124
- reason: "spawn-failed",
125
- stderr: [...this.stderrBuffer],
126
- };
127
- }
128
- this.child = child;
129
- child.on("exit", () => {
130
- if (this.child === child) {
131
- this.child = null;
132
- }
133
- });
134
- return {
135
- ok: true,
136
- pid: child.pid,
137
- stderr: [...this.stderrBuffer],
138
- };
139
- }
140
- async stop() {
141
- if (this.startInFlight) {
142
- await this.startInFlight.catch(() => undefined);
143
- }
144
- const child = this.child;
145
- if (!child)
146
- return;
147
- this.child = null;
148
- if (child.exitCode !== null)
149
- return;
150
- return new Promise((resolve) => {
151
- let settled = false;
152
- let killTimer = null;
153
- let fallbackTimer = null;
154
- const finish = () => {
155
- if (settled)
156
- return;
157
- settled = true;
158
- if (killTimer)
159
- clearTimeout(killTimer);
160
- if (fallbackTimer)
161
- clearTimeout(fallbackTimer);
162
- resolve();
163
- };
164
- child.once("exit", finish);
165
- try {
166
- child.kill("SIGTERM");
167
- }
168
- catch {
169
- finish();
170
- return;
171
- }
172
- killTimer = setTimeout(() => {
173
- if (settled)
174
- return;
175
- try {
176
- child.kill("SIGKILL");
177
- }
178
- catch {
179
- }
180
- fallbackTimer = setTimeout(finish, 100);
181
- fallbackTimer.unref();
182
- }, 1000);
183
- killTimer.unref();
184
- });
185
- }
186
- async restart(udid) {
187
- await this.stop();
188
- return this.start(udid ?? this.udid ?? "");
189
- }
190
- getStderr() {
191
- return [...this.stderrBuffer];
192
- }
193
- appendStderr(line) {
194
- this.stderrBuffer.push(line);
195
- if (this.stderrBuffer.length > MAX_LOG_LINES) {
196
- this.stderrBuffer.shift();
197
- }
198
- }
199
- detectEarlyExit(child, withinMs) {
200
- return new Promise((resolve) => {
201
- let settled = false;
202
- const finish = (value) => {
203
- if (settled)
204
- return;
205
- settled = true;
206
- resolve(value);
207
- };
208
- const onExit = () => finish(true);
209
- child.once("exit", onExit);
210
- setTimeout(() => {
211
- child.off("exit", onExit);
212
- finish(false);
213
- }, withinMs).unref();
214
- });
215
- }
216
- }
@@ -1,10 +0,0 @@
1
- import { spawn as nodeSpawn } from "node:child_process";
2
- export interface CaffeinateResult {
3
- applied: boolean;
4
- reason: "started" | "already-active" | "not-darwin" | "spawn-failed";
5
- }
6
- export type Spawner = typeof nodeSpawn;
7
- export declare function _resetForTesting(): void;
8
- export declare function _setSpawnerForTesting(fn: Spawner | null): void;
9
- export declare function enableCaffeinate(): CaffeinateResult;
10
- export declare function disableCaffeinate(): void;