@ceraph/react-native-mcp 0.3.3 → 0.4.6
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/README.md +335 -68
- package/dist/babel-plugin/index.cjs +1 -0
- package/dist/babel-plugin/index.js +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +1 -47
- package/dist/index.d.ts +106 -1
- package/dist/index.js +2 -1651
- package/dist/shim/async-storage-ops.d.ts +26 -0
- package/dist/shim/async-storage-ops.js +1 -0
- package/dist/shim/boot.d.ts +9 -0
- package/dist/shim/boot.js +1 -141
- package/dist/shim/camera.js +1 -62
- package/dist/shim/command-poll.d.ts +18 -0
- package/dist/shim/command-poll.js +1 -0
- package/dist/shim/config.js +1 -56
- package/dist/shim/console-capture.d.ts +16 -0
- package/dist/shim/console-capture.js +1 -0
- package/dist/shim/deep-link.js +1 -25
- package/dist/shim/dev-guard.js +1 -3
- package/dist/shim/dev-host.d.ts +1 -0
- package/dist/shim/dev-host.js +1 -0
- package/dist/shim/error-handler.js +1 -66
- package/dist/shim/fetch-interceptor.js +1 -93
- package/dist/shim/index.d.ts +3 -0
- package/dist/shim/index.js +1 -6
- package/dist/shim/keep-awake.js +1 -118
- package/dist/shim/network-ownership.d.ts +4 -0
- package/dist/shim/network-ownership.js +1 -0
- package/dist/shim/optimistic-observer.d.ts +29 -0
- package/dist/shim/optimistic-observer.js +1 -0
- package/dist/shim/reload.js +1 -76
- package/dist/shim/reset.d.ts +30 -0
- package/dist/shim/reset.js +1 -0
- package/dist/shim/signal-capture.d.ts +8 -0
- package/dist/shim/signal-capture.js +1 -15
- package/dist/shim/signal-transport.d.ts +14 -1
- package/dist/shim/signal-transport.js +1 -43
- package/dist/shim/xhr-interceptor.d.ts +39 -0
- package/dist/shim/xhr-interceptor.js +1 -0
- package/package.json +40 -11
- package/dist/app-lifecycle.d.ts +0 -50
- package/dist/app-lifecycle.js +0 -487
- package/dist/camera-image-writer.d.ts +0 -43
- package/dist/camera-image-writer.js +0 -280
- package/dist/camera-registry-sync.d.ts +0 -18
- package/dist/camera-registry-sync.js +0 -117
- package/dist/device-autonomy.d.ts +0 -30
- package/dist/device-autonomy.js +0 -117
- package/dist/error-parser.d.ts +0 -51
- package/dist/error-parser.js +0 -275
- package/dist/expo-manager.d.ts +0 -62
- package/dist/expo-manager.js +0 -447
- package/dist/init/ast-camera.d.ts +0 -29
- package/dist/init/ast-camera.js +0 -267
- package/dist/init/ast-layout.d.ts +0 -15
- package/dist/init/ast-layout.js +0 -167
- package/dist/init/claude-hook-constants.d.ts +0 -9
- package/dist/init/claude-hook-constants.js +0 -91
- package/dist/init/lan-ip.d.ts +0 -11
- package/dist/init/lan-ip.js +0 -51
- package/dist/init/monorepo.d.ts +0 -13
- package/dist/init/monorepo.js +0 -185
- package/dist/init/oauth.d.ts +0 -52
- package/dist/init/oauth.js +0 -220
- package/dist/init/package-manager.d.ts +0 -11
- package/dist/init/package-manager.js +0 -60
- package/dist/init/prompt.d.ts +0 -12
- package/dist/init/prompt.js +0 -68
- package/dist/init/shell-profile.d.ts +0 -22
- package/dist/init/shell-profile.js +0 -85
- package/dist/init/steps.d.ts +0 -135
- package/dist/init/steps.js +0 -399
- package/dist/init/url-scheme.d.ts +0 -42
- package/dist/init/url-scheme.js +0 -187
- package/dist/init/walkthrough.d.ts +0 -76
- package/dist/init/walkthrough.js +0 -340
- package/dist/init.d.ts +0 -8
- package/dist/init.js +0 -395
- package/dist/iproxy-manager.d.ts +0 -32
- package/dist/iproxy-manager.js +0 -216
- package/dist/mac-caffeinate.d.ts +0 -10
- package/dist/mac-caffeinate.js +0 -56
- package/dist/permission-interceptor.d.ts +0 -29
- package/dist/permission-interceptor.js +0 -185
- package/dist/prebuild-detector.d.ts +0 -19
- package/dist/prebuild-detector.js +0 -174
- package/dist/preflight.d.ts +0 -34
- package/dist/preflight.js +0 -847
- package/dist/screen.d.ts +0 -184
- package/dist/screen.js +0 -931
- package/dist/signal-listener.d.ts +0 -27
- package/dist/signal-listener.js +0 -135
- package/dist/simulator-boot.d.ts +0 -52
- package/dist/simulator-boot.js +0 -227
- package/dist/target.d.ts +0 -48
- package/dist/target.js +0 -267
- package/dist/uninstall/cli-runner.d.ts +0 -32
- package/dist/uninstall/cli-runner.js +0 -223
- package/dist/uninstall/footprint.d.ts +0 -40
- package/dist/uninstall/footprint.js +0 -288
- package/dist/uninstall/mcp-tools.d.ts +0 -14
- package/dist/uninstall/mcp-tools.js +0 -175
- package/dist/uninstall/revert-auth.d.ts +0 -22
- package/dist/uninstall/revert-auth.js +0 -31
- package/dist/uninstall/revert-boot.d.ts +0 -24
- package/dist/uninstall/revert-boot.js +0 -242
- package/dist/uninstall/revert-camera.d.ts +0 -12
- package/dist/uninstall/revert-camera.js +0 -199
- package/dist/uninstall/revert-ceraph-dir.d.ts +0 -27
- package/dist/uninstall/revert-ceraph-dir.js +0 -38
- package/dist/uninstall/revert-claude-hooks.d.ts +0 -19
- package/dist/uninstall/revert-claude-hooks.js +0 -191
- package/dist/uninstall/revert-gitignore.d.ts +0 -17
- package/dist/uninstall/revert-gitignore.js +0 -43
- package/dist/uninstall/revert-mcp-clients.d.ts +0 -57
- package/dist/uninstall/revert-mcp-clients.js +0 -194
- package/dist/uninstall/revert-package.d.ts +0 -34
- package/dist/uninstall/revert-package.js +0 -98
- package/dist/uninstall/revert-scheme.d.ts +0 -36
- package/dist/uninstall/revert-scheme.js +0 -139
- package/dist/uninstall/revert-signal-host-env.d.ts +0 -31
- package/dist/uninstall/revert-signal-host-env.js +0 -61
- package/dist/uninstall/walkthrough.d.ts +0 -80
- package/dist/uninstall/walkthrough.js +0 -1244
- package/dist/utils/atomic-write.d.ts +0 -1
- package/dist/utils/atomic-write.js +0 -30
- package/dist/wait-for-device.d.ts +0 -68
- package/dist/wait-for-device.js +0 -368
- package/dist/wda-manager.d.ts +0 -38
- package/dist/wda-manager.js +0 -186
- package/dist/wda-simulator.d.ts +0 -28
- 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
|
-
}
|
package/dist/iproxy-manager.d.ts
DELETED
|
@@ -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
|
-
}
|
package/dist/iproxy-manager.js
DELETED
|
@@ -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
|
-
}
|
package/dist/mac-caffeinate.d.ts
DELETED
|
@@ -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;
|