@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
@@ -1,267 +0,0 @@
1
- import { readFile, writeFile, readdir } from "node:fs/promises";
2
- import { basename, dirname, join, relative, sep } from "node:path";
3
- import { IndentationText, Project, SyntaxKind, Node, } from "ts-morph";
4
- const SCAN_ROOTS = ["app", "src", "screens"];
5
- const SCAN_EXTENSIONS = new Set([".tsx", ".jsx"]);
6
- const SKIP_DIRS = new Set([
7
- "node_modules",
8
- "dist",
9
- "build",
10
- ".git",
11
- ".ceraph",
12
- ".rn-mcp-cache",
13
- ".expo",
14
- ".next",
15
- "ios",
16
- "android",
17
- ]);
18
- const SHIM_IMPORT_MODULE = "@ceraph/react-native-mcp/shim";
19
- const SHIM_IMPORT_NAME = "CeraphCamera";
20
- export function suggestImageKey(absPath) {
21
- const file = basename(absPath).replace(/\.(tsx|jsx)$/i, "");
22
- const parent = basename(dirname(absPath));
23
- const generic = new Set(["page", "index", "screen", "route", "layout"]);
24
- const stem = generic.has(file.toLowerCase()) ? parent : file;
25
- const kebab = stem
26
- .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
27
- .toLowerCase()
28
- .replace(/[^a-z0-9]+/g, "-")
29
- .replace(/^-+|-+$/g, "");
30
- return kebab.length > 0 ? kebab : "camera";
31
- }
32
- async function listCandidateFiles(projectDir) {
33
- const out = [];
34
- async function walk(dir) {
35
- let entries;
36
- try {
37
- entries = await readdir(dir, { withFileTypes: true });
38
- }
39
- catch {
40
- return;
41
- }
42
- for (const entry of entries) {
43
- const abs = join(dir, entry.name);
44
- if (entry.isDirectory()) {
45
- if (SKIP_DIRS.has(entry.name))
46
- continue;
47
- await walk(abs);
48
- continue;
49
- }
50
- if (!entry.isFile())
51
- continue;
52
- const dot = entry.name.lastIndexOf(".");
53
- if (dot < 0)
54
- continue;
55
- const ext = entry.name.slice(dot).toLowerCase();
56
- if (!SCAN_EXTENSIONS.has(ext))
57
- continue;
58
- out.push(abs);
59
- }
60
- }
61
- for (const sub of SCAN_ROOTS) {
62
- await walk(join(projectDir, sub));
63
- }
64
- for (const candidate of ["App.tsx", "App.jsx"]) {
65
- out.push(join(projectDir, candidate));
66
- }
67
- return out;
68
- }
69
- function buildProject(filePaths) {
70
- const project = new Project({
71
- useInMemoryFileSystem: false,
72
- skipAddingFilesFromTsConfig: true,
73
- skipFileDependencyResolution: true,
74
- skipLoadingLibFiles: true,
75
- compilerOptions: { allowJs: true, jsx: 1 },
76
- manipulationSettings: { indentationText: IndentationText.TwoSpaces },
77
- });
78
- for (const path of filePaths) {
79
- try {
80
- project.addSourceFileAtPath(path);
81
- }
82
- catch {
83
- }
84
- }
85
- return project;
86
- }
87
- function listOpeningElements(source, name) {
88
- const out = [];
89
- source.forEachDescendant((node) => {
90
- if (Node.isJsxOpeningElement(node) || Node.isJsxSelfClosingElement(node)) {
91
- const tag = node.getTagNameNode();
92
- if (tag.getText() === name) {
93
- out.push(node);
94
- }
95
- }
96
- });
97
- return out;
98
- }
99
- function hasImageKeyAttribute(node) {
100
- const attrs = node.getAttributes();
101
- for (const attr of attrs) {
102
- if (Node.isJsxAttribute(attr)) {
103
- const nameNode = attr.getNameNode();
104
- if (nameNode.getText() === "imageKey")
105
- return true;
106
- }
107
- }
108
- return false;
109
- }
110
- export async function scanCameraViews(projectDir) {
111
- const candidates = await listCandidateFiles(projectDir);
112
- const project = buildProject(candidates);
113
- const edits = [];
114
- for (const source of project.getSourceFiles()) {
115
- const tags = listOpeningElements(source, "CameraView");
116
- if (tags.length === 0)
117
- continue;
118
- for (const tag of tags) {
119
- const start = tag.getStart();
120
- const lineCol = source.getLineAndColumnAtPos(start);
121
- edits.push({
122
- filePath: source.getFilePath(),
123
- relPath: relative(projectDir, source.getFilePath())
124
- .split(sep)
125
- .join("/"),
126
- line: lineCol.line,
127
- suggestedKey: suggestImageKey(source.getFilePath()),
128
- alreadyHasImageKey: hasImageKeyAttribute(tag),
129
- });
130
- }
131
- }
132
- edits.sort((a, b) => a.relPath === b.relPath ? a.line - b.line : a.relPath < b.relPath ? -1 : 1);
133
- return { edits, filesScanned: project.getSourceFiles().length };
134
- }
135
- export async function applyCameraEdits(projectDir, edits, options = {}) {
136
- const byFile = new Map();
137
- for (const edit of edits) {
138
- const list = byFile.get(edit.filePath) ?? [];
139
- list.push(edit);
140
- byFile.set(edit.filePath, list);
141
- }
142
- const project = buildProject(Array.from(byFile.keys()));
143
- const filesEdited = [];
144
- let totalReplacements = 0;
145
- for (const [filePath, fileEdits] of byFile) {
146
- const source = project.getSourceFile(filePath);
147
- if (!source)
148
- continue;
149
- let replacementsThisFile = 0;
150
- for (const edit of fileEdits) {
151
- const tags = listOpeningElements(source, "CameraView");
152
- const target = tags.find((t) => {
153
- const pos = source.getLineAndColumnAtPos(t.getStart());
154
- return pos.line === edit.line;
155
- });
156
- if (!target)
157
- continue;
158
- target.getTagNameNode().replaceWithText("CeraphCamera");
159
- if (Node.isJsxOpeningElement(target)) {
160
- const parent = target.getParentIfKind(SyntaxKind.JsxElement);
161
- if (parent) {
162
- const closing = parent.getClosingElement();
163
- closing.getTagNameNode().replaceWithText("CeraphCamera");
164
- }
165
- }
166
- if (!edit.alreadyHasImageKey && !options.omitImageKey) {
167
- target.addAttribute({
168
- name: "imageKey",
169
- initializer: `"${edit.suggestedKey}"`,
170
- });
171
- }
172
- replacementsThisFile++;
173
- totalReplacements++;
174
- }
175
- const existingShimImport = source.getImportDeclaration(SHIM_IMPORT_MODULE);
176
- if (existingShimImport) {
177
- const named = existingShimImport.getNamedImports();
178
- if (!named.some((n) => n.getName() === SHIM_IMPORT_NAME)) {
179
- existingShimImport.addNamedImport(SHIM_IMPORT_NAME);
180
- }
181
- }
182
- else {
183
- source.addImportDeclaration({
184
- moduleSpecifier: SHIM_IMPORT_MODULE,
185
- namedImports: [SHIM_IMPORT_NAME],
186
- });
187
- }
188
- const expoCameraImport = source.getImportDeclaration("expo-camera");
189
- if (expoCameraImport) {
190
- const remainingTags = listOpeningElements(source, "CameraView");
191
- if (remainingTags.length === 0) {
192
- const named = expoCameraImport.getNamedImports();
193
- const cameraViewSpec = named.find((n) => n.getName() === "CameraView");
194
- if (cameraViewSpec) {
195
- cameraViewSpec.remove();
196
- const remaining = expoCameraImport.getNamedImports();
197
- const hasDefault = expoCameraImport.getDefaultImport() != null;
198
- if (remaining.length === 0 && !hasDefault) {
199
- expoCameraImport.remove();
200
- }
201
- }
202
- }
203
- }
204
- if (replacementsThisFile > 0) {
205
- const updated = source.getFullText();
206
- await writeFile(filePath, updated, "utf-8");
207
- filesEdited.push(filePath);
208
- }
209
- }
210
- return { filesEdited, totalReplacements };
211
- }
212
- export async function setImageKeyOnExistingTag(filePath, line, imageKey) {
213
- const project = buildProject([filePath]);
214
- const source = project.getSourceFile(filePath);
215
- if (!source) {
216
- return { applied: false, reason: `File not found: ${filePath}` };
217
- }
218
- const tags = listOpeningElements(source, "CeraphCamera");
219
- const target = tags.find((t) => {
220
- const pos = source.getLineAndColumnAtPos(t.getStart());
221
- return pos.line === line;
222
- });
223
- if (!target) {
224
- return {
225
- applied: false,
226
- reason: `No <CeraphCamera> at ${filePath}:${line}`,
227
- };
228
- }
229
- let previousValue = null;
230
- let existingFound = false;
231
- for (const attr of target.getAttributes()) {
232
- if (Node.isJsxAttribute(attr)) {
233
- const nameNode = attr.getNameNode();
234
- if (nameNode.getText() === "imageKey") {
235
- existingFound = true;
236
- const init = attr.getInitializer();
237
- if (init && Node.isStringLiteral(init)) {
238
- previousValue = init.getLiteralText();
239
- }
240
- attr.setInitializer(`"${imageKey}"`);
241
- break;
242
- }
243
- }
244
- }
245
- if (!existingFound) {
246
- target.addAttribute({
247
- name: "imageKey",
248
- initializer: `"${imageKey}"`,
249
- });
250
- }
251
- if (previousValue === imageKey) {
252
- return { applied: false, previousValue, reason: "imageKey unchanged" };
253
- }
254
- await writeFile(filePath, source.getFullText(), "utf-8");
255
- return { applied: true, previousValue };
256
- }
257
- export async function snapshotFile(filePath) {
258
- try {
259
- return await readFile(filePath, "utf-8");
260
- }
261
- catch {
262
- return null;
263
- }
264
- }
265
- export async function restoreFile(filePath, snapshot) {
266
- await writeFile(filePath, snapshot, "utf-8");
267
- }
@@ -1,15 +0,0 @@
1
- export type RootComponentTarget = {
2
- kind: "expo-router";
3
- filePath: string;
4
- } | {
5
- kind: "bare-rn";
6
- filePath: string;
7
- };
8
- export declare function detectRootComponent(projectDir: string): Promise<RootComponentTarget | null>;
9
- export interface InjectInstallCeraphResult {
10
- applied: boolean;
11
- reason?: string;
12
- }
13
- export declare function injectInstallCeraph(target: RootComponentTarget): Promise<InjectInstallCeraphResult>;
14
- export declare function snapshotLayout(target: RootComponentTarget): Promise<string | null>;
15
- export declare function restoreLayout(target: RootComponentTarget, snapshot: string): Promise<void>;
@@ -1,167 +0,0 @@
1
- import { readFile, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { IndentationText, Project, Node, } from "ts-morph";
4
- const SHIM_MODULE = "@ceraph/react-native-mcp/shim";
5
- const INSTALL_CALL = "installCeraph";
6
- async function fileExists(path) {
7
- try {
8
- await readFile(path, "utf-8");
9
- return true;
10
- }
11
- catch {
12
- return false;
13
- }
14
- }
15
- export async function detectRootComponent(projectDir) {
16
- const expo = join(projectDir, "app", "_layout.tsx");
17
- if (await fileExists(expo)) {
18
- return { kind: "expo-router", filePath: expo };
19
- }
20
- const expoJsx = join(projectDir, "app", "_layout.jsx");
21
- if (await fileExists(expoJsx)) {
22
- return { kind: "expo-router", filePath: expoJsx };
23
- }
24
- for (const name of ["App.tsx", "App.jsx"]) {
25
- const p = join(projectDir, name);
26
- if (await fileExists(p)) {
27
- return { kind: "bare-rn", filePath: p };
28
- }
29
- }
30
- return null;
31
- }
32
- function findDefaultExportFunction(source) {
33
- for (const fn of source.getFunctions()) {
34
- if (fn.isDefaultExport())
35
- return fn;
36
- }
37
- const exportAssignment = source
38
- .getStatements()
39
- .find((s) => Node.isExportAssignment(s));
40
- if (exportAssignment && Node.isExportAssignment(exportAssignment)) {
41
- const expr = exportAssignment.getExpression();
42
- if (Node.isIdentifier(expr)) {
43
- const symbol = expr.getSymbol();
44
- if (symbol) {
45
- const decl = symbol.getDeclarations()[0];
46
- if (decl && Node.isVariableDeclaration(decl)) {
47
- const init = decl.getInitializer();
48
- if (init && Node.isArrowFunction(init))
49
- return init;
50
- if (init && Node.isFunctionExpression(init))
51
- return init;
52
- }
53
- }
54
- }
55
- if (Node.isArrowFunction(expr))
56
- return expr;
57
- if (Node.isFunctionExpression(expr))
58
- return expr;
59
- }
60
- return null;
61
- }
62
- function ensureUseEffectImport(source) {
63
- const reactImport = source.getImportDeclaration("react");
64
- if (!reactImport) {
65
- source.addImportDeclaration({
66
- moduleSpecifier: "react",
67
- namedImports: ["useEffect"],
68
- });
69
- return;
70
- }
71
- const named = reactImport.getNamedImports();
72
- if (!named.some((n) => n.getName() === "useEffect")) {
73
- reactImport.addNamedImport("useEffect");
74
- }
75
- }
76
- function ensureShimImport(source) {
77
- const existing = source.getImportDeclaration(SHIM_MODULE);
78
- if (!existing) {
79
- source.addImportDeclaration({
80
- moduleSpecifier: SHIM_MODULE,
81
- namedImports: [INSTALL_CALL],
82
- });
83
- return;
84
- }
85
- const named = existing.getNamedImports();
86
- if (!named.some((n) => n.getName() === INSTALL_CALL)) {
87
- existing.addNamedImport(INSTALL_CALL);
88
- }
89
- }
90
- function bodyAlreadyHasInstallCeraph(fn) {
91
- const body = fn.getBody();
92
- if (!body)
93
- return false;
94
- if (!Node.isBlock(body))
95
- return false;
96
- const text = body.getText();
97
- return text.includes(`${INSTALL_CALL}(`);
98
- }
99
- function insertInstallCeraphHook(fn) {
100
- if (bodyAlreadyHasInstallCeraph(fn))
101
- return false;
102
- if (Node.isArrowFunction(fn)) {
103
- const body = fn.getBody();
104
- if (!Node.isBlock(body)) {
105
- const expressionText = body.getText();
106
- body.replaceWithText(`{ return ${expressionText}; }`);
107
- }
108
- }
109
- const block = fn.getBody();
110
- if (!block || !Node.isBlock(block))
111
- return false;
112
- block.insertStatements(0, "useEffect(() => { installCeraph(); }, []);");
113
- return true;
114
- }
115
- export async function injectInstallCeraph(target) {
116
- const project = new Project({
117
- useInMemoryFileSystem: false,
118
- skipAddingFilesFromTsConfig: true,
119
- skipFileDependencyResolution: true,
120
- skipLoadingLibFiles: true,
121
- compilerOptions: { allowJs: true, jsx: 1 },
122
- manipulationSettings: { indentationText: IndentationText.TwoSpaces },
123
- });
124
- const source = project.addSourceFileAtPath(target.filePath);
125
- const fn = findDefaultExportFunction(source);
126
- if (!fn) {
127
- return {
128
- applied: false,
129
- reason: "Could not locate the default-exported function component. " +
130
- `Manually add the following near the top of your root component in ${target.filePath}:\n` +
131
- ` import { installCeraph } from "${SHIM_MODULE}";\n` +
132
- ` useEffect(() => { installCeraph(); }, []);`,
133
- };
134
- }
135
- if (bodyAlreadyHasInstallCeraph(fn)) {
136
- ensureShimImport(source);
137
- ensureUseEffectImport(source);
138
- const updated = source.getFullText();
139
- await writeFile(target.filePath, updated, "utf-8");
140
- return { applied: true, reason: "Already wired — imports checked." };
141
- }
142
- ensureShimImport(source);
143
- ensureUseEffectImport(source);
144
- const inserted = insertInstallCeraphHook(fn);
145
- if (!inserted) {
146
- return {
147
- applied: false,
148
- reason: "Default export is not a block-bodied function. " +
149
- "Manually add `useEffect(() => { installCeraph(); }, [])` at the top " +
150
- "of the component body.",
151
- };
152
- }
153
- const updated = source.getFullText();
154
- await writeFile(target.filePath, updated, "utf-8");
155
- return { applied: true };
156
- }
157
- export async function snapshotLayout(target) {
158
- try {
159
- return await readFile(target.filePath, "utf-8");
160
- }
161
- catch {
162
- return null;
163
- }
164
- }
165
- export async function restoreLayout(target, snapshot) {
166
- await writeFile(target.filePath, snapshot, "utf-8");
167
- }
@@ -1,9 +0,0 @@
1
- export declare const HOOK_SCRIPT = "#!/bin/bash\n# rn-error-notify.sh \u2014 Injected by @ceraph/react-native-mcp init\n# Reads .rn-errors.json and injects runtime errors into Claude's context.\n\nERROR_FILE=\"$CLAUDE_PROJECT_DIR/mobile/.rn-errors.json\"\n\n# Also check project root if mobile/ doesn't exist\nif [ ! -f \"$ERROR_FILE\" ]; then\n ERROR_FILE=\"$CLAUDE_PROJECT_DIR/.rn-errors.json\"\nfi\n\nif [ ! -f \"$ERROR_FILE\" ]; then\n exit 0\nfi\n\nERROR_COUNT=$(jq -r '.errors | length' \"$ERROR_FILE\" 2>/dev/null)\n\nif [ \"$ERROR_COUNT\" = \"0\" ] || [ -z \"$ERROR_COUNT\" ]; then\n exit 0\nfi\n\necho \"REACT NATIVE RUNTIME ERROR DETECTED:\"\necho \"\"\njq -r '.errors[] | \"Error: \\(.message)\\nStack: \\(.stack)\\nTime: \\(.timestamp)\\n---\"' \"$ERROR_FILE\" 2>/dev/null\necho \"\"\necho \"Use rn_get_errors for full details. Fix the error and rebuild.\"\n";
2
- export declare const FLOW_PROGRESS_HOOK_SCRIPT = "#!/bin/bash\n# rn-flow-progress-notify.sh \u2014 Injected by @ceraph/react-native-mcp init\n# Reads .rn-flow-progress.json and injects a one-line heartbeat into\n# Claude's context. HEARTBEAT ONLY \u2014 no step-level failure details.\n\nENTRY_FILE=\"$CLAUDE_PROJECT_DIR/mobile/.rn-flow-progress.json\"\nif [ ! -f \"$ENTRY_FILE\" ]; then\n ENTRY_FILE=\"$CLAUDE_PROJECT_DIR/.rn-flow-progress.json\"\nfi\nif [ ! -f \"$ENTRY_FILE\" ]; then\n exit 0\nfi\n\n# If jq isn't installed, exit quietly \u2014 the heartbeat is best-effort.\nif ! command -v jq >/dev/null 2>&1; then\n exit 0\nfi\n\nENTRY=$(jq -r '.entry' \"$ENTRY_FILE\" 2>/dev/null)\nif [ \"$ENTRY\" = \"null\" ] || [ -z \"$ENTRY\" ]; then\n exit 0\nfi\n\n# jq '// \"\"' (works in jq 1.4+; \"// empty\" needed 1.5+ and would have\n# silently aborted older shells) makes jq emit an empty string for\n# missing fields rather than the literal \"null\". Defaults below keep\n# set -e shells happy in the arithmetic + awk expansions even when\n# the entry is partial (transient state if the file is read\n# mid-write).\nFLOW=$(jq -r '.entry.flow // \"\"' \"$ENTRY_FILE\" 2>/dev/null)\nINDEX=$(jq -r '.entry.index // \"\"' \"$ENTRY_FILE\" 2>/dev/null)\nTOTAL=$(jq -r '.entry.total // \"\"' \"$ENTRY_FILE\" 2>/dev/null)\nSUCCESS=$(jq -r '.entry.success // \"\"' \"$ENTRY_FILE\" 2>/dev/null)\nDURATION=$(jq -r '.entry.durationMs // \"\"' \"$ENTRY_FILE\" 2>/dev/null)\n\nINDEX=${INDEX:-0}\nTOTAL=${TOTAL:-0}\nDURATION=${DURATION:-0}\nFLOW=${FLOW:-unknown}\n\nDISPLAY_INDEX=$((INDEX + 1))\nif [ \"$SUCCESS\" = \"true\" ]; then\n STATUS=\"passed\"\nelse\n STATUS=\"failed\"\nfi\n\necho \"Flow $DISPLAY_INDEX/$TOTAL \u2014 $FLOW: $STATUS in $(awk \"BEGIN {printf \\\"%.1f\\\", $DURATION/1000}\")s\"\necho \"(Wait for the end-of-run summary before making any code changes \u2014 the flows in this run reference your pre-edit code.)\"\n";
3
- export declare const ERROR_HOOK_REL_PATH: readonly [".claude", "hooks", "rn-error-notify.sh"];
4
- export declare const FLOW_PROGRESS_HOOK_REL_PATH: readonly [".claude", "hooks", "rn-flow-progress-notify.sh"];
5
- export declare const ERROR_HOOK_MATCHER = ".rn-errors.json";
6
- export declare const FLOW_PROGRESS_HOOK_MATCHER = ".rn-flow-progress.json";
7
- export declare const ERROR_HOOK_COMMAND = "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/rn-error-notify.sh";
8
- export declare const FLOW_PROGRESS_HOOK_COMMAND = "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/rn-flow-progress-notify.sh";
9
- export declare const CLAUDE_SETTINGS_REL_PATHS: readonly [readonly [".claude", "settings.json"], readonly [".claude", "settings.local.json"]];
@@ -1,91 +0,0 @@
1
- export const HOOK_SCRIPT = `#!/bin/bash
2
- # rn-error-notify.sh — Injected by @ceraph/react-native-mcp init
3
- # Reads .rn-errors.json and injects runtime errors into Claude's context.
4
-
5
- ERROR_FILE="\$CLAUDE_PROJECT_DIR/mobile/.rn-errors.json"
6
-
7
- # Also check project root if mobile/ doesn't exist
8
- if [ ! -f "\$ERROR_FILE" ]; then
9
- ERROR_FILE="\$CLAUDE_PROJECT_DIR/.rn-errors.json"
10
- fi
11
-
12
- if [ ! -f "\$ERROR_FILE" ]; then
13
- exit 0
14
- fi
15
-
16
- ERROR_COUNT=\$(jq -r '.errors | length' "\$ERROR_FILE" 2>/dev/null)
17
-
18
- if [ "\$ERROR_COUNT" = "0" ] || [ -z "\$ERROR_COUNT" ]; then
19
- exit 0
20
- fi
21
-
22
- echo "REACT NATIVE RUNTIME ERROR DETECTED:"
23
- echo ""
24
- jq -r '.errors[] | "Error: \\(.message)\\nStack: \\(.stack)\\nTime: \\(.timestamp)\\n---"' "\$ERROR_FILE" 2>/dev/null
25
- echo ""
26
- echo "Use rn_get_errors for full details. Fix the error and rebuild."
27
- `;
28
- export const FLOW_PROGRESS_HOOK_SCRIPT = `#!/bin/bash
29
- # rn-flow-progress-notify.sh — Injected by @ceraph/react-native-mcp init
30
- # Reads .rn-flow-progress.json and injects a one-line heartbeat into
31
- # Claude's context. HEARTBEAT ONLY — no step-level failure details.
32
-
33
- ENTRY_FILE="\$CLAUDE_PROJECT_DIR/mobile/.rn-flow-progress.json"
34
- if [ ! -f "\$ENTRY_FILE" ]; then
35
- ENTRY_FILE="\$CLAUDE_PROJECT_DIR/.rn-flow-progress.json"
36
- fi
37
- if [ ! -f "\$ENTRY_FILE" ]; then
38
- exit 0
39
- fi
40
-
41
- # If jq isn't installed, exit quietly — the heartbeat is best-effort.
42
- if ! command -v jq >/dev/null 2>&1; then
43
- exit 0
44
- fi
45
-
46
- ENTRY=\$(jq -r '.entry' "\$ENTRY_FILE" 2>/dev/null)
47
- if [ "\$ENTRY" = "null" ] || [ -z "\$ENTRY" ]; then
48
- exit 0
49
- fi
50
-
51
- # jq '// ""' (works in jq 1.4+; "// empty" needed 1.5+ and would have
52
- # silently aborted older shells) makes jq emit an empty string for
53
- # missing fields rather than the literal "null". Defaults below keep
54
- # set -e shells happy in the arithmetic + awk expansions even when
55
- # the entry is partial (transient state if the file is read
56
- # mid-write).
57
- FLOW=\$(jq -r '.entry.flow // ""' "\$ENTRY_FILE" 2>/dev/null)
58
- INDEX=\$(jq -r '.entry.index // ""' "\$ENTRY_FILE" 2>/dev/null)
59
- TOTAL=\$(jq -r '.entry.total // ""' "\$ENTRY_FILE" 2>/dev/null)
60
- SUCCESS=\$(jq -r '.entry.success // ""' "\$ENTRY_FILE" 2>/dev/null)
61
- DURATION=\$(jq -r '.entry.durationMs // ""' "\$ENTRY_FILE" 2>/dev/null)
62
-
63
- INDEX=\${INDEX:-0}
64
- TOTAL=\${TOTAL:-0}
65
- DURATION=\${DURATION:-0}
66
- FLOW=\${FLOW:-unknown}
67
-
68
- DISPLAY_INDEX=\$((INDEX + 1))
69
- if [ "\$SUCCESS" = "true" ]; then
70
- STATUS="passed"
71
- else
72
- STATUS="failed"
73
- fi
74
-
75
- echo "Flow \$DISPLAY_INDEX/\$TOTAL — \$FLOW: \$STATUS in \$(awk "BEGIN {printf \\"%.1f\\", \$DURATION/1000}")s"
76
- echo "(Wait for the end-of-run summary before making any code changes — the flows in this run reference your pre-edit code.)"
77
- `;
78
- export const ERROR_HOOK_REL_PATH = [".claude", "hooks", "rn-error-notify.sh"];
79
- export const FLOW_PROGRESS_HOOK_REL_PATH = [
80
- ".claude",
81
- "hooks",
82
- "rn-flow-progress-notify.sh",
83
- ];
84
- export const ERROR_HOOK_MATCHER = ".rn-errors.json";
85
- export const FLOW_PROGRESS_HOOK_MATCHER = ".rn-flow-progress.json";
86
- export const ERROR_HOOK_COMMAND = '"$CLAUDE_PROJECT_DIR"/.claude/hooks/rn-error-notify.sh';
87
- export const FLOW_PROGRESS_HOOK_COMMAND = '"$CLAUDE_PROJECT_DIR"/.claude/hooks/rn-flow-progress-notify.sh';
88
- export const CLAUDE_SETTINGS_REL_PATHS = [
89
- [".claude", "settings.json"],
90
- [".claude", "settings.local.json"],
91
- ];
@@ -1,11 +0,0 @@
1
- import { type NetworkInterfaceInfo } from "node:os";
2
- export interface LanIpCandidate {
3
- interfaceName: string;
4
- address: string;
5
- kind: "wifi" | "usb-ethernet" | "other";
6
- }
7
- export interface DetectMacLanIpDeps {
8
- networkInterfaces?: () => Record<string, NetworkInterfaceInfo[] | undefined>;
9
- }
10
- export declare function detectMacLanIpCandidates(deps?: DetectMacLanIpDeps): LanIpCandidate[];
11
- export declare function detectMacLanIp(deps?: DetectMacLanIpDeps): LanIpCandidate | null;
@@ -1,51 +0,0 @@
1
- import { networkInterfaces } from "node:os";
2
- const WIFI_INTERFACE_NAMES = new Set(["en0"]);
3
- const USB_ETHERNET_INTERFACE_NAMES = new Set(["en5", "en6"]);
4
- function classifyCandidate(ifName, info) {
5
- if (info.family !== "IPv4")
6
- return null;
7
- if (info.internal)
8
- return null;
9
- if (info.address.startsWith("127."))
10
- return null;
11
- if (info.address.startsWith("169.254."))
12
- return null;
13
- let kind;
14
- if (WIFI_INTERFACE_NAMES.has(ifName))
15
- kind = "wifi";
16
- else if (USB_ETHERNET_INTERFACE_NAMES.has(ifName))
17
- kind = "usb-ethernet";
18
- else
19
- return null;
20
- return { interfaceName: ifName, address: info.address, kind };
21
- }
22
- export function detectMacLanIpCandidates(deps = {}) {
23
- const reader = deps.networkInterfaces ?? networkInterfaces;
24
- const ifs = reader();
25
- const seen = new Set();
26
- const wifi = [];
27
- const usb = [];
28
- const names = Object.keys(ifs).sort();
29
- for (const name of names) {
30
- const infos = ifs[name];
31
- if (!infos)
32
- continue;
33
- for (const info of infos) {
34
- const candidate = classifyCandidate(name, info);
35
- if (!candidate)
36
- continue;
37
- if (seen.has(candidate.address))
38
- continue;
39
- seen.add(candidate.address);
40
- if (candidate.kind === "wifi")
41
- wifi.push(candidate);
42
- else
43
- usb.push(candidate);
44
- }
45
- }
46
- return [...wifi, ...usb];
47
- }
48
- export function detectMacLanIp(deps = {}) {
49
- const candidates = detectMacLanIpCandidates(deps);
50
- return candidates[0] ?? null;
51
- }
@@ -1,13 +0,0 @@
1
- export interface MonorepoCandidate {
2
- absPath: string;
3
- relPath: string;
4
- signals: ReadonlyArray<"expo" | "react-native" | "app.json">;
5
- }
6
- export interface MonorepoDetectionResult {
7
- kind: "none" | "single" | "multi";
8
- matches: MonorepoCandidate[];
9
- rootIsRnApp: boolean;
10
- isMonorepo: boolean;
11
- }
12
- export declare function readWorkspaceGlobs(projectDir: string): Promise<string[] | null>;
13
- export declare function detectMonorepoSubpackages(projectDir: string): Promise<MonorepoDetectionResult>;