@ceraph/react-native-mcp 0.2.2 → 0.3.2

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 (124) hide show
  1. package/LICENSE +116 -15
  2. package/README.md +79 -77
  3. package/assets/default.png +0 -0
  4. package/dist/app-lifecycle.d.ts +50 -0
  5. package/dist/app-lifecycle.js +487 -0
  6. package/dist/camera-image-writer.d.ts +43 -0
  7. package/dist/camera-image-writer.js +280 -0
  8. package/dist/camera-registry-sync.d.ts +18 -0
  9. package/dist/camera-registry-sync.js +117 -0
  10. package/dist/cli.d.ts +0 -7
  11. package/dist/cli.js +41 -9
  12. package/dist/device-autonomy.d.ts +30 -0
  13. package/dist/device-autonomy.js +117 -0
  14. package/dist/error-parser.d.ts +6 -26
  15. package/dist/error-parser.js +4 -74
  16. package/dist/expo-manager.d.ts +2 -74
  17. package/dist/expo-manager.js +11 -125
  18. package/dist/index.d.ts +0 -7
  19. package/dist/index.js +1266 -56
  20. package/dist/init/ast-camera.d.ts +29 -0
  21. package/dist/init/ast-camera.js +267 -0
  22. package/dist/init/ast-layout.d.ts +15 -0
  23. package/dist/init/ast-layout.js +167 -0
  24. package/dist/init/claude-hook-constants.d.ts +9 -0
  25. package/dist/init/claude-hook-constants.js +91 -0
  26. package/dist/init/lan-ip.d.ts +11 -0
  27. package/dist/init/lan-ip.js +51 -0
  28. package/dist/init/monorepo.d.ts +13 -0
  29. package/dist/init/monorepo.js +185 -0
  30. package/dist/init/oauth.d.ts +52 -0
  31. package/dist/init/oauth.js +220 -0
  32. package/dist/init/package-manager.d.ts +11 -0
  33. package/dist/init/package-manager.js +60 -0
  34. package/dist/init/prompt.d.ts +12 -0
  35. package/dist/init/prompt.js +68 -0
  36. package/dist/init/shell-profile.d.ts +22 -0
  37. package/dist/init/shell-profile.js +85 -0
  38. package/dist/init/steps.d.ts +135 -0
  39. package/dist/init/steps.js +399 -0
  40. package/dist/init/url-scheme.d.ts +42 -0
  41. package/dist/init/url-scheme.js +187 -0
  42. package/dist/init/walkthrough.d.ts +76 -0
  43. package/dist/init/walkthrough.js +340 -0
  44. package/dist/init.d.ts +7 -7
  45. package/dist/init.js +280 -120
  46. package/dist/iproxy-manager.d.ts +32 -0
  47. package/dist/iproxy-manager.js +216 -0
  48. package/dist/mac-caffeinate.d.ts +10 -0
  49. package/dist/mac-caffeinate.js +56 -0
  50. package/dist/permission-interceptor.d.ts +29 -0
  51. package/dist/permission-interceptor.js +185 -0
  52. package/dist/prebuild-detector.d.ts +0 -30
  53. package/dist/prebuild-detector.js +1 -42
  54. package/dist/preflight.d.ts +34 -0
  55. package/dist/preflight.js +847 -0
  56. package/dist/screen.d.ts +132 -43
  57. package/dist/screen.js +668 -94
  58. package/dist/shim/boot.d.ts +41 -0
  59. package/dist/shim/boot.js +141 -0
  60. package/dist/shim/camera.d.ts +22 -0
  61. package/dist/shim/camera.js +62 -0
  62. package/dist/shim/config.d.ts +6 -0
  63. package/dist/shim/config.js +56 -0
  64. package/dist/shim/deep-link.d.ts +1 -0
  65. package/dist/shim/deep-link.js +25 -0
  66. package/dist/shim/dev-guard.d.ts +1 -0
  67. package/dist/shim/dev-guard.js +3 -0
  68. package/dist/shim/error-handler.d.ts +20 -0
  69. package/dist/shim/error-handler.js +66 -0
  70. package/dist/shim/fetch-interceptor.d.ts +13 -0
  71. package/dist/shim/fetch-interceptor.js +93 -0
  72. package/dist/shim/index.d.ts +6 -0
  73. package/dist/shim/index.js +6 -0
  74. package/dist/shim/keep-awake.d.ts +13 -0
  75. package/dist/shim/keep-awake.js +118 -0
  76. package/dist/shim/reload.d.ts +23 -0
  77. package/dist/shim/reload.js +76 -0
  78. package/dist/shim/signal-capture.d.ts +11 -0
  79. package/dist/shim/signal-capture.js +15 -0
  80. package/dist/shim/signal-transport.d.ts +17 -0
  81. package/dist/shim/signal-transport.js +43 -0
  82. package/dist/signal-listener.d.ts +27 -0
  83. package/dist/signal-listener.js +135 -0
  84. package/dist/simulator-boot.d.ts +52 -0
  85. package/dist/simulator-boot.js +227 -0
  86. package/dist/target.d.ts +48 -0
  87. package/dist/target.js +267 -0
  88. package/dist/uninstall/cli-runner.d.ts +32 -0
  89. package/dist/uninstall/cli-runner.js +223 -0
  90. package/dist/uninstall/footprint.d.ts +40 -0
  91. package/dist/uninstall/footprint.js +288 -0
  92. package/dist/uninstall/mcp-tools.d.ts +14 -0
  93. package/dist/uninstall/mcp-tools.js +175 -0
  94. package/dist/uninstall/revert-auth.d.ts +22 -0
  95. package/dist/uninstall/revert-auth.js +31 -0
  96. package/dist/uninstall/revert-boot.d.ts +24 -0
  97. package/dist/uninstall/revert-boot.js +242 -0
  98. package/dist/uninstall/revert-camera.d.ts +12 -0
  99. package/dist/uninstall/revert-camera.js +199 -0
  100. package/dist/uninstall/revert-ceraph-dir.d.ts +27 -0
  101. package/dist/uninstall/revert-ceraph-dir.js +38 -0
  102. package/dist/uninstall/revert-claude-hooks.d.ts +19 -0
  103. package/dist/uninstall/revert-claude-hooks.js +191 -0
  104. package/dist/uninstall/revert-gitignore.d.ts +17 -0
  105. package/dist/uninstall/revert-gitignore.js +43 -0
  106. package/dist/uninstall/revert-mcp-clients.d.ts +57 -0
  107. package/dist/uninstall/revert-mcp-clients.js +194 -0
  108. package/dist/uninstall/revert-package.d.ts +34 -0
  109. package/dist/uninstall/revert-package.js +98 -0
  110. package/dist/uninstall/revert-scheme.d.ts +36 -0
  111. package/dist/uninstall/revert-scheme.js +139 -0
  112. package/dist/uninstall/revert-signal-host-env.d.ts +31 -0
  113. package/dist/uninstall/revert-signal-host-env.js +61 -0
  114. package/dist/uninstall/walkthrough.d.ts +80 -0
  115. package/dist/uninstall/walkthrough.js +1244 -0
  116. package/dist/utils/atomic-write.d.ts +1 -0
  117. package/dist/utils/atomic-write.js +30 -0
  118. package/dist/wait-for-device.d.ts +68 -0
  119. package/dist/wait-for-device.js +368 -0
  120. package/dist/wda-manager.d.ts +38 -0
  121. package/dist/wda-manager.js +186 -0
  122. package/dist/wda-simulator.d.ts +28 -0
  123. package/dist/wda-simulator.js +257 -0
  124. package/package.json +59 -5
@@ -1,9 +1,3 @@
1
- /**
2
- * Manages React Native / Expo child processes (build and Metro dev server).
3
- * Auto-detects whether the project uses Expo or bare React Native
4
- * and runs the appropriate commands.
5
- * Captures stdout/stderr into rolling buffers and parses errors.
6
- */
7
1
  import { spawn } from "node:child_process";
8
2
  import { writeFile, access } from "node:fs/promises";
9
3
  import { join } from "node:path";
@@ -19,30 +13,18 @@ export class RNManager {
19
13
  runtimeErrors = [];
20
14
  buildWarnings = [];
21
15
  metroWarnings = [];
22
- // Parser state must persist across `onData` chunks so that an error
23
- // whose message and stack trace arrive in separate chunks is still
24
- // assembled into a single RuntimeError. Resetting on every chunk
25
- // (the previous bug) caused the .rn-errors.json hook to fire with
26
- // the message but no stack, or to drop multi-line errors entirely.
27
16
  metroParser = new MetroErrorParser();
28
17
  MAX_BUILD_LINES = 1000;
29
18
  MAX_METRO_LINES = 500;
30
19
  MAX_ERRORS = 100;
31
- /** The working directory for commands. */
32
20
  cwd;
33
- /** Path to the error file that triggers the Claude Code hook. */
34
21
  errorFilePath;
35
22
  constructor(cwd) {
36
23
  this.cwd = cwd;
37
24
  this.errorFilePath = join(cwd, ".rn-errors.json");
38
25
  }
39
- /**
40
- * Detect whether this is an Expo or bare React Native project.
41
- * Checks for app.json with expo config or expo package in dependencies.
42
- */
43
26
  async detectProjectType() {
44
27
  try {
45
- // Check for app.json with expo key
46
28
  const appJsonPath = join(this.cwd, "app.json");
47
29
  await access(appJsonPath);
48
30
  const appJson = JSON.parse(await (await import("node:fs/promises")).readFile(appJsonPath, "utf-8"));
@@ -51,10 +33,8 @@ export class RNManager {
51
33
  }
52
34
  }
53
35
  catch {
54
- // No app.json or no expo key
55
36
  }
56
37
  try {
57
- // Check for expo in package.json dependencies
58
38
  const pkgPath = join(this.cwd, "package.json");
59
39
  const pkg = JSON.parse(await (await import("node:fs/promises")).readFile(pkgPath, "utf-8"));
60
40
  if (pkg.dependencies?.expo) {
@@ -62,14 +42,9 @@ export class RNManager {
62
42
  }
63
43
  }
64
44
  catch {
65
- // No package.json
66
45
  }
67
46
  return false;
68
47
  }
69
- /**
70
- * Write errors to .rn-errors.json so the Claude Code hook can detect them.
71
- * Overwrites the file each time — the hook fires on any write.
72
- */
73
48
  async writeErrorFile(errors) {
74
49
  try {
75
50
  await writeFile(this.errorFilePath, JSON.stringify({
@@ -78,54 +53,40 @@ export class RNManager {
78
53
  message: e.message,
79
54
  stack: e.stack,
80
55
  timestamp: e.timestamp,
56
+ kind: e.kind ?? "metro-error",
57
+ ...(e.url !== undefined ? { url: e.url } : {}),
58
+ ...(e.status !== undefined ? { status: e.status } : {}),
59
+ ...(e.method !== undefined ? { method: e.method } : {}),
60
+ ...(e.durationMs !== undefined ? { durationMs: e.durationMs } : {}),
81
61
  })),
82
62
  }, null, 2), "utf-8");
83
63
  }
84
64
  catch {
85
- // Non-critical — don't break the process over a file write
86
65
  }
87
66
  }
88
- /**
89
- * Clear the error file (e.g., on successful build or process start).
90
- */
67
+ async appendShimSignal(entry) {
68
+ this.runtimeErrors.push(entry);
69
+ this.capErrors(this.runtimeErrors);
70
+ await this.writeErrorFile(this.runtimeErrors);
71
+ }
91
72
  async clearErrorFile() {
92
73
  try {
93
74
  await writeFile(this.errorFilePath, JSON.stringify({ timestamp: new Date().toISOString(), errors: [] }), "utf-8");
94
75
  }
95
76
  catch {
96
- // Non-critical
97
77
  }
98
78
  }
99
- /**
100
- * Append a line to a rolling buffer, evicting oldest entries when full.
101
- */
102
79
  pushLine(buffer, line, max) {
103
80
  buffer.push(line);
104
81
  if (buffer.length > max) {
105
82
  buffer.splice(0, buffer.length - max);
106
83
  }
107
84
  }
108
- /**
109
- * Cap an error array at MAX_ERRORS, dropping oldest entries.
110
- */
111
85
  capErrors(arr) {
112
86
  if (arr.length > this.MAX_ERRORS) {
113
87
  arr.splice(0, arr.length - this.MAX_ERRORS);
114
88
  }
115
89
  }
116
- /**
117
- * Kill a child process gracefully (SIGTERM, then SIGKILL after timeout).
118
- *
119
- * The promise resolves only when the process actually exits, not when
120
- * SIGKILL is sent. Resolving on the SIGKILL timer was a race: callers
121
- * (e.g. `runBuild`) would spawn a new build while the old Xcode process
122
- * was still releasing DerivedData / build locks, causing cryptic
123
- * SIGABRT or "file locked" errors on the new build.
124
- *
125
- * A hard upper-bound timer (2s after SIGKILL) guards against the
126
- * pathological case where a process refuses to die at all, so callers
127
- * never hang forever.
128
- */
129
90
  async killProcess(proc) {
130
91
  return new Promise((resolve) => {
131
92
  if (!proc.pid || proc.exitCode !== null) {
@@ -138,10 +99,7 @@ export class RNManager {
138
99
  proc.kill("SIGKILL");
139
100
  }
140
101
  catch {
141
- // Process may have already exited between checks.
142
102
  }
143
- // Wait for `exit` to fire after SIGKILL, but bound the wait so
144
- // callers don't hang on a process that refuses to die.
145
103
  hardTimer = setTimeout(() => {
146
104
  proc.off("exit", onExit);
147
105
  resolve();
@@ -158,18 +116,11 @@ export class RNManager {
158
116
  proc.kill("SIGTERM");
159
117
  }
160
118
  catch {
161
- // Process already exited or PID is gone — treat as done. Detach
162
- // the exit listener so it doesn't fire stale on a stop/start cycle.
163
119
  proc.off("exit", onExit);
164
120
  clearTimeout(softTimer);
165
121
  resolve();
166
122
  return;
167
123
  }
168
- // TOCTOU recovery: if the process exited between the entry guard
169
- // and the `once("exit")` registration above, `kill()` returns false
170
- // (no throw) and our listener never fires for the already-emitted
171
- // exit. Re-check `exitCode` here so we don't wait the full 7s on
172
- // the hard timer.
173
124
  if (proc.exitCode !== null) {
174
125
  proc.off("exit", onExit);
175
126
  clearTimeout(softTimer);
@@ -177,9 +128,6 @@ export class RNManager {
177
128
  }
178
129
  });
179
130
  }
180
- /**
181
- * Run `npx expo prebuild --clean` synchronously (waits for exit).
182
- */
183
131
  runPrebuildClean() {
184
132
  return new Promise((resolve) => {
185
133
  const lines = [];
@@ -196,8 +144,6 @@ export class RNManager {
196
144
  };
197
145
  proc.stdout?.on("data", onData);
198
146
  proc.stderr?.on("data", onData);
199
- // Node fires both `error` and `exit` on a failed spawn. Guard so
200
- // the second handler doesn't run a stale resolve / output dump.
201
147
  let resolved = false;
202
148
  proc.on("error", (err) => {
203
149
  if (resolved)
@@ -219,28 +165,17 @@ export class RNManager {
219
165
  });
220
166
  });
221
167
  }
222
- /**
223
- * Build and run the app on an iOS device or simulator.
224
- * Auto-detects Expo vs bare React Native and uses the appropriate command:
225
- * - Expo: `npx expo run:ios`
226
- * - Bare RN: `npx react-native run-ios`
227
- *
228
- * Optionally runs `npx expo prebuild --clean` first (Expo only).
229
- */
230
168
  async runBuild(options = {}) {
231
169
  const isExpo = await this.detectProjectType();
232
- // Kill any existing build process
233
170
  if (this.buildProcess) {
234
171
  this.buildKilled = true;
235
172
  await this.killProcess(this.buildProcess);
236
173
  this.buildProcess = null;
237
174
  }
238
- // Reset state
239
175
  this.buildKilled = false;
240
176
  this.buildOutput = [];
241
177
  this.buildErrors = [];
242
178
  this.buildWarnings = [];
243
- // Run prebuild --clean if requested (Expo only)
244
179
  if (options.clean) {
245
180
  if (!isExpo) {
246
181
  return {
@@ -273,12 +208,10 @@ export class RNManager {
273
208
  output: prebuildResult.output,
274
209
  };
275
210
  }
276
- // Add prebuild output to build output
277
211
  for (const line of prebuildResult.output.split("\n")) {
278
212
  this.pushLine(this.buildOutput, line, this.MAX_BUILD_LINES);
279
213
  }
280
214
  }
281
- // Construct the build command based on project type
282
215
  const args = isExpo ? ["expo", "run:ios"] : ["react-native", "run-ios"];
283
216
  if (options.device) {
284
217
  args.push(isExpo ? "--device" : "--udid", options.device);
@@ -299,10 +232,6 @@ export class RNManager {
299
232
  };
300
233
  proc.stdout?.on("data", onData);
301
234
  proc.stderr?.on("data", onData);
302
- // Node fires both `error` and `exit` on a spawn failure. Guard so
303
- // the `exit` handler doesn't re-parse the (likely empty) output
304
- // and overwrite `this.buildErrors` / `this.buildWarnings` after
305
- // the caller has already received the failure result.
306
235
  let resolved = false;
307
236
  proc.on("error", (err) => {
308
237
  if (resolved)
@@ -337,7 +266,6 @@ export class RNManager {
337
266
  });
338
267
  return;
339
268
  }
340
- // Parse all captured output
341
269
  const parsed = parseBuildOutput(this.buildOutput);
342
270
  this.buildErrors = parsed.errors;
343
271
  this.buildWarnings = parsed.warnings;
@@ -352,29 +280,18 @@ export class RNManager {
352
280
  type: e.type,
353
281
  })),
354
282
  warnings: parsed.warnings,
355
- output: this.buildOutput.slice(-100).join("\n"), // Last 100 lines
283
+ output: this.buildOutput.slice(-100).join("\n"),
356
284
  });
357
285
  });
358
286
  });
359
287
  }
360
- /**
361
- * Start the Metro dev server.
362
- * Auto-detects Expo vs bare React Native:
363
- * - Expo: `npx expo start --dev-client`
364
- * - Bare RN: `npx react-native start`
365
- *
366
- * Spawns Metro in the background and continuously captures output.
367
- * Returns quickly once Metro shows signs of being ready (or after a timeout).
368
- */
369
288
  async startMetro(options = {}) {
370
289
  const isExpo = await this.detectProjectType();
371
- // Kill existing Metro process
372
290
  if (this.metroProcess) {
373
291
  this.metroKilled = true;
374
292
  await this.killProcess(this.metroProcess);
375
293
  this.metroProcess = null;
376
294
  }
377
- // Reset state
378
295
  this.metroKilled = false;
379
296
  this.metroOutput = [];
380
297
  this.runtimeErrors = [];
@@ -399,9 +316,6 @@ export class RNManager {
399
316
  let resolved = false;
400
317
  const onData = (data) => {
401
318
  const text = data.toString();
402
- // Collect lines from this chunk and parse them as a batch.
403
- // The parser is stateful, so an error whose message and stack
404
- // straddle chunk boundaries is still assembled correctly.
405
319
  const chunkLines = [];
406
320
  for (const line of text.split("\n")) {
407
321
  if (line.trim()) {
@@ -420,7 +334,6 @@ export class RNManager {
420
334
  this.metroWarnings.push(...parsed.warnings);
421
335
  }
422
336
  }
423
- // Detect Metro ready
424
337
  if (!resolved && text.includes("Metro waiting on")) {
425
338
  resolved = true;
426
339
  resolve({
@@ -443,8 +356,6 @@ export class RNManager {
443
356
  });
444
357
  proc.on("exit", (code) => {
445
358
  this.metroProcess = null;
446
- // Flush any error still being assembled in the streaming parser
447
- // so it surfaces in `.rn-errors.json` for the Claude Code hook.
448
359
  const flushed = this.metroParser.flush();
449
360
  if (flushed.runtimeErrors.length > 0) {
450
361
  this.runtimeErrors.push(...flushed.runtimeErrors);
@@ -469,8 +380,6 @@ export class RNManager {
469
380
  });
470
381
  }
471
382
  });
472
- // If Metro doesn't show "ready" within 30s, resolve anyway
473
- // -- it may still be starting up (pod install, etc.)
474
383
  setTimeout(() => {
475
384
  if (!resolved) {
476
385
  resolved = true;
@@ -483,17 +392,7 @@ export class RNManager {
483
392
  }, 30_000);
484
393
  });
485
394
  }
486
- /**
487
- * Return all captured errors from both build and runtime contexts.
488
- */
489
395
  getErrors() {
490
- // Only re-parse the buffer when Metro is NOT running. While the
491
- // streaming parser holds in-progress state (header received, stack
492
- // not yet), an independent re-parse would `flush()` a stackless
493
- // copy of that same error and surface it as a duplicate-without-stack.
494
- // Once Metro exits, the streaming parser has already been flushed
495
- // in the `proc.on("exit")` handler, and re-parsing the buffer is a
496
- // safe fallback for anything still missing.
497
396
  const metroParsed = this.metroProcess === null
498
397
  ? parseMetroOutput(this.metroOutput)
499
398
  : { runtimeErrors: [], warnings: [] };
@@ -512,9 +411,6 @@ export class RNManager {
512
411
  warnings: [...this.buildWarnings, ...this.metroWarnings],
513
412
  };
514
413
  }
515
- /**
516
- * Return recent console output from Metro, optionally filtered by level.
517
- */
518
414
  getConsole(options = {}) {
519
415
  const { lines = 50, level = "all" } = options;
520
416
  let output = this.metroOutput;
@@ -524,12 +420,8 @@ export class RNManager {
524
420
  return classified === level;
525
421
  });
526
422
  }
527
- // Return the last N lines
528
423
  return output.slice(-lines);
529
424
  }
530
- /**
531
- * Stop all managed Expo processes.
532
- */
533
425
  async stopAll() {
534
426
  const stopped = [];
535
427
  if (this.buildProcess) {
@@ -546,15 +438,9 @@ export class RNManager {
546
438
  }
547
439
  return stopped;
548
440
  }
549
- /**
550
- * Get the raw build output buffer (for diagnostics).
551
- */
552
441
  getBuildOutput() {
553
442
  return [...this.buildOutput];
554
443
  }
555
- /**
556
- * Get the raw metro output buffer (for diagnostics).
557
- */
558
444
  getMetroOutput() {
559
445
  return [...this.metroOutput];
560
446
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,2 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * @ceraph/react-native-mcp — MCP server for React Native / Expo development workflow.
4
- *
5
- * Auto-detects Expo vs bare React Native projects and uses the appropriate
6
- * commands. Provides tools for building, running, error capture, screen
7
- * interaction, and prebuild detection.
8
- */
9
2
  export {};