@ceraph/react-native-mcp 0.2.2 → 0.3.1

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 +38 -5
@@ -0,0 +1,199 @@
1
+ import { writeFile, readdir } from "node:fs/promises";
2
+ import { join, relative, sep } from "node:path";
3
+ import { IndentationText, Node, Project, SyntaxKind, } from "ts-morph";
4
+ const SHIM_MODULE = "@ceraph/react-native-mcp/shim";
5
+ const SHIM_NAMED = "CeraphCamera";
6
+ const EXPO_MODULE = "expo-camera";
7
+ const EXPO_NAMED = "CameraView";
8
+ const SCAN_ROOTS = ["app", "src", "screens"];
9
+ const SCAN_EXTENSIONS = new Set([".tsx", ".jsx"]);
10
+ const SKIP_DIRS = new Set([
11
+ "node_modules",
12
+ "dist",
13
+ "build",
14
+ ".git",
15
+ ".ceraph",
16
+ ".rn-mcp-cache",
17
+ ".expo",
18
+ ".next",
19
+ "ios",
20
+ "android",
21
+ ]);
22
+ async function listCandidateFiles(projectDir) {
23
+ const out = [];
24
+ async function walk(dir) {
25
+ let entries;
26
+ try {
27
+ entries = await readdir(dir, { withFileTypes: true });
28
+ }
29
+ catch {
30
+ return;
31
+ }
32
+ for (const entry of entries) {
33
+ const abs = join(dir, entry.name);
34
+ if (entry.isDirectory()) {
35
+ if (SKIP_DIRS.has(entry.name))
36
+ continue;
37
+ await walk(abs);
38
+ continue;
39
+ }
40
+ if (!entry.isFile())
41
+ continue;
42
+ const dot = entry.name.lastIndexOf(".");
43
+ if (dot < 0)
44
+ continue;
45
+ const ext = entry.name.slice(dot).toLowerCase();
46
+ if (!SCAN_EXTENSIONS.has(ext))
47
+ continue;
48
+ out.push(abs);
49
+ }
50
+ }
51
+ for (const sub of SCAN_ROOTS) {
52
+ await walk(join(projectDir, sub));
53
+ }
54
+ for (const candidate of ["App.tsx", "App.jsx"]) {
55
+ out.push(join(projectDir, candidate));
56
+ }
57
+ return out;
58
+ }
59
+ function buildProject(filePaths) {
60
+ const project = new Project({
61
+ useInMemoryFileSystem: false,
62
+ skipAddingFilesFromTsConfig: true,
63
+ skipFileDependencyResolution: true,
64
+ skipLoadingLibFiles: true,
65
+ compilerOptions: { allowJs: true, jsx: 1 },
66
+ manipulationSettings: { indentationText: IndentationText.TwoSpaces },
67
+ });
68
+ for (const path of filePaths) {
69
+ try {
70
+ project.addSourceFileAtPath(path);
71
+ }
72
+ catch {
73
+ }
74
+ }
75
+ return project;
76
+ }
77
+ function listJsxByName(source, name) {
78
+ const out = [];
79
+ source.forEachDescendant((node) => {
80
+ if (Node.isJsxOpeningElement(node) || Node.isJsxSelfClosingElement(node)) {
81
+ const tag = node.getTagNameNode();
82
+ if (tag.getText() === name) {
83
+ out.push(node);
84
+ }
85
+ }
86
+ });
87
+ return out;
88
+ }
89
+ function removeImageKeyAttribute(node) {
90
+ const attrs = node.getAttributes();
91
+ for (const attr of attrs) {
92
+ if (Node.isJsxAttribute(attr)) {
93
+ const nameNode = attr.getNameNode();
94
+ if (nameNode.getText() === "imageKey") {
95
+ attr.remove();
96
+ }
97
+ }
98
+ }
99
+ }
100
+ function renameCeraphCamerasToCameraView(source) {
101
+ let count = 0;
102
+ for (let safety = 0; safety < 10_000; safety++) {
103
+ const tags = listJsxByName(source, SHIM_NAMED);
104
+ if (tags.length === 0)
105
+ break;
106
+ const target = tags[0];
107
+ target.getTagNameNode().replaceWithText(EXPO_NAMED);
108
+ if (Node.isJsxOpeningElement(target)) {
109
+ const parent = target.getParentIfKind(SyntaxKind.JsxElement);
110
+ if (parent) {
111
+ const closing = parent.getClosingElement();
112
+ closing.getTagNameNode().replaceWithText(EXPO_NAMED);
113
+ }
114
+ }
115
+ removeImageKeyAttribute(target);
116
+ count++;
117
+ }
118
+ return count;
119
+ }
120
+ function dropCeraphCameraFromShimImport(source) {
121
+ const decl = source.getImportDeclaration(SHIM_MODULE);
122
+ if (!decl)
123
+ return false;
124
+ const named = decl.getNamedImports();
125
+ const target = named.find((n) => n.getName() === SHIM_NAMED);
126
+ if (!target)
127
+ return false;
128
+ target.remove();
129
+ const remaining = decl.getNamedImports();
130
+ const hasDefault = decl.getDefaultImport() != null;
131
+ if (remaining.length === 0 && !hasDefault) {
132
+ decl.remove();
133
+ }
134
+ return true;
135
+ }
136
+ function ensureCameraViewImport(source) {
137
+ const decl = source.getImportDeclaration(EXPO_MODULE);
138
+ if (decl) {
139
+ const named = decl.getNamedImports();
140
+ if (named.some((n) => n.getName() === EXPO_NAMED))
141
+ return false;
142
+ decl.addNamedImport(EXPO_NAMED);
143
+ return true;
144
+ }
145
+ source.addImportDeclaration({
146
+ moduleSpecifier: EXPO_MODULE,
147
+ namedImports: [EXPO_NAMED],
148
+ });
149
+ return true;
150
+ }
151
+ export async function revertCamera(projectDir) {
152
+ const candidates = await listCandidateFiles(projectDir);
153
+ const project = buildProject(candidates);
154
+ const files = [];
155
+ let filesScanned = 0;
156
+ for (const source of project.getSourceFiles()) {
157
+ filesScanned++;
158
+ const tags = listJsxByName(source, SHIM_NAMED);
159
+ if (tags.length === 0) {
160
+ const importDecl = source.getImportDeclaration(SHIM_MODULE);
161
+ const hasOrphanImport = importDecl != null &&
162
+ importDecl.getNamedImports().some((n) => n.getName() === SHIM_NAMED);
163
+ if (!hasOrphanImport)
164
+ continue;
165
+ const dropped = dropCeraphCameraFromShimImport(source);
166
+ if (!dropped)
167
+ continue;
168
+ const filePath = source.getFilePath();
169
+ await writeFile(filePath, source.getFullText(), "utf-8");
170
+ files.push({
171
+ path: filePath,
172
+ relPath: relative(projectDir, filePath).split(sep).join("/"),
173
+ ceraphCamerasRemoved: 0,
174
+ importDropped: true,
175
+ importRestored: false,
176
+ });
177
+ continue;
178
+ }
179
+ const ceraphCamerasRemoved = renameCeraphCamerasToCameraView(source);
180
+ const remainingShimTags = listJsxByName(source, SHIM_NAMED);
181
+ const importDropped = remainingShimTags.length === 0
182
+ ? dropCeraphCameraFromShimImport(source)
183
+ : false;
184
+ const hasCameraViewJsx = listJsxByName(source, EXPO_NAMED).length > 0;
185
+ const importRestored = hasCameraViewJsx
186
+ ? ensureCameraViewImport(source)
187
+ : false;
188
+ const filePath = source.getFilePath();
189
+ await writeFile(filePath, source.getFullText(), "utf-8");
190
+ files.push({
191
+ path: filePath,
192
+ relPath: relative(projectDir, filePath).split(sep).join("/"),
193
+ ceraphCamerasRemoved,
194
+ importDropped,
195
+ importRestored,
196
+ });
197
+ }
198
+ return { files, filesScanned };
199
+ }
@@ -0,0 +1,27 @@
1
+ export type RevertCeraphDirResult = {
2
+ status: "skipped-needs-confirmation";
3
+ path: string;
4
+ paths: string[];
5
+ } | {
6
+ status: "deleted";
7
+ path: string;
8
+ deletedEntries: string[];
9
+ } | {
10
+ status: "already-reverted";
11
+ path: string;
12
+ };
13
+ export interface RevertCeraphDirInput {
14
+ projectDir: string;
15
+ confirm: boolean;
16
+ }
17
+ export interface RevertCeraphDirDeps {
18
+ rm?: (p: string, opts: {
19
+ recursive: true;
20
+ force: true;
21
+ }) => Promise<void>;
22
+ readdir?: (p: string) => Promise<string[]>;
23
+ stat?: (p: string) => Promise<{
24
+ isDirectory: () => boolean;
25
+ }>;
26
+ }
27
+ export declare function revertCeraphDir(input: RevertCeraphDirInput, deps?: RevertCeraphDirDeps): Promise<RevertCeraphDirResult>;
@@ -0,0 +1,38 @@
1
+ import { rm, readdir, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ async function dirExists(path, statFn) {
4
+ try {
5
+ const s = await statFn(path);
6
+ return s.isDirectory();
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ export async function revertCeraphDir(input, deps = {}) {
13
+ const ceraphPath = join(input.projectDir, ".ceraph");
14
+ const rmFn = deps.rm ?? ((p, o) => rm(p, o));
15
+ const readdirFn = deps.readdir ?? ((p) => readdir(p));
16
+ const statFn = deps.stat ?? ((p) => stat(p));
17
+ const exists = await dirExists(ceraphPath, statFn);
18
+ if (!exists) {
19
+ return { status: "already-reverted", path: ceraphPath };
20
+ }
21
+ let entries = [];
22
+ try {
23
+ entries = await readdirFn(ceraphPath);
24
+ }
25
+ catch {
26
+ entries = [];
27
+ }
28
+ entries.sort();
29
+ if (!input.confirm) {
30
+ return {
31
+ status: "skipped-needs-confirmation",
32
+ path: ceraphPath,
33
+ paths: entries.map((e) => join(ceraphPath, e)),
34
+ };
35
+ }
36
+ await rmFn(ceraphPath, { recursive: true, force: true });
37
+ return { status: "deleted", path: ceraphPath, deletedEntries: entries };
38
+ }
@@ -0,0 +1,19 @@
1
+ export type RevertClaudeHooksStatus = "reverted" | "already-reverted" | "manual" | "skipped";
2
+ export interface RevertClaudeHooksResult {
3
+ status: RevertClaudeHooksStatus;
4
+ details: {
5
+ scriptsDeleted: string[];
6
+ scriptsLeftAlone: string[];
7
+ settingsFilesModified: string[];
8
+ settingsFilesUntouched: string[];
9
+ };
10
+ }
11
+ export interface RevertClaudeHooksDeps {
12
+ readFile?: (p: string) => Promise<string>;
13
+ writeFile?: (p: string, c: string) => Promise<void>;
14
+ unlink?: (p: string) => Promise<void>;
15
+ fileExists?: (p: string) => Promise<boolean>;
16
+ }
17
+ export declare function revertClaudeHooks(input: {
18
+ projectDir: string;
19
+ }, deps?: RevertClaudeHooksDeps): Promise<RevertClaudeHooksResult>;
@@ -0,0 +1,191 @@
1
+ import { access, readFile, unlink } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { CLAUDE_SETTINGS_REL_PATHS, ERROR_HOOK_COMMAND, ERROR_HOOK_MATCHER, ERROR_HOOK_REL_PATH, FLOW_PROGRESS_HOOK_COMMAND, FLOW_PROGRESS_HOOK_MATCHER, FLOW_PROGRESS_HOOK_REL_PATH, HOOK_SCRIPT, FLOW_PROGRESS_HOOK_SCRIPT, } from "../init/claude-hook-constants.js";
4
+ import { writeFileAtomic } from "../utils/atomic-write.js";
5
+ async function defaultExists(p) {
6
+ try {
7
+ await access(p);
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ export async function revertClaudeHooks(input, deps = {}) {
15
+ const read = deps.readFile ?? ((p) => readFile(p, "utf-8"));
16
+ const write = deps.writeFile ?? writeFileAtomic;
17
+ const unlinkFn = deps.unlink ?? ((p) => unlink(p));
18
+ const exists = deps.fileExists ?? defaultExists;
19
+ const hooks = [
20
+ {
21
+ scriptAbsPath: join(input.projectDir, ...ERROR_HOOK_REL_PATH),
22
+ canonicalScript: HOOK_SCRIPT,
23
+ matcher: ERROR_HOOK_MATCHER,
24
+ command: ERROR_HOOK_COMMAND,
25
+ },
26
+ {
27
+ scriptAbsPath: join(input.projectDir, ...FLOW_PROGRESS_HOOK_REL_PATH),
28
+ canonicalScript: FLOW_PROGRESS_HOOK_SCRIPT,
29
+ matcher: FLOW_PROGRESS_HOOK_MATCHER,
30
+ command: FLOW_PROGRESS_HOOK_COMMAND,
31
+ },
32
+ ];
33
+ const scriptsDeleted = [];
34
+ const scriptsLeftAlone = [];
35
+ for (const h of hooks) {
36
+ if (!(await exists(h.scriptAbsPath)))
37
+ continue;
38
+ let onDisk;
39
+ try {
40
+ onDisk = await read(h.scriptAbsPath);
41
+ }
42
+ catch {
43
+ scriptsLeftAlone.push(h.scriptAbsPath);
44
+ continue;
45
+ }
46
+ if (onDisk === h.canonicalScript) {
47
+ await unlinkFn(h.scriptAbsPath);
48
+ scriptsDeleted.push(h.scriptAbsPath);
49
+ }
50
+ else {
51
+ scriptsLeftAlone.push(h.scriptAbsPath);
52
+ }
53
+ }
54
+ const settingsFilesModified = [];
55
+ const settingsFilesUntouched = [];
56
+ for (const relParts of CLAUDE_SETTINGS_REL_PATHS) {
57
+ const abs = join(input.projectDir, ...relParts);
58
+ if (!(await exists(abs)))
59
+ continue;
60
+ let raw;
61
+ try {
62
+ raw = await read(abs);
63
+ }
64
+ catch {
65
+ settingsFilesUntouched.push(abs);
66
+ continue;
67
+ }
68
+ let parsed;
69
+ try {
70
+ parsed = JSON.parse(raw);
71
+ }
72
+ catch {
73
+ settingsFilesUntouched.push(abs);
74
+ continue;
75
+ }
76
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
77
+ settingsFilesUntouched.push(abs);
78
+ continue;
79
+ }
80
+ const result = stripHooksFromSettings(parsed, hooks);
81
+ if (!result.mutated) {
82
+ settingsFilesUntouched.push(abs);
83
+ continue;
84
+ }
85
+ const indentMatch = raw.match(/\n(\s+)"/);
86
+ const indent = indentMatch?.[1] ?? " ";
87
+ const hadTrailingNewline = raw.endsWith("\n");
88
+ const out = JSON.stringify(result.next, null, indent);
89
+ await write(abs, hadTrailingNewline ? out + "\n" : out);
90
+ settingsFilesModified.push(abs);
91
+ }
92
+ const anyAction = scriptsDeleted.length > 0 || settingsFilesModified.length > 0;
93
+ let status;
94
+ if (scriptsLeftAlone.length > 0) {
95
+ status = "manual";
96
+ }
97
+ else if (anyAction) {
98
+ status = "reverted";
99
+ }
100
+ else {
101
+ status = "already-reverted";
102
+ }
103
+ return {
104
+ status,
105
+ details: {
106
+ scriptsDeleted,
107
+ scriptsLeftAlone,
108
+ settingsFilesModified,
109
+ settingsFilesUntouched,
110
+ },
111
+ };
112
+ }
113
+ function stripHooksFromSettings(obj, managed) {
114
+ const hooksVal = obj.hooks;
115
+ if (!hooksVal || typeof hooksVal !== "object" || Array.isArray(hooksVal)) {
116
+ return { mutated: false, next: obj };
117
+ }
118
+ const hooks = hooksVal;
119
+ const fileChanged = hooks.FileChanged;
120
+ if (!Array.isArray(fileChanged)) {
121
+ return { mutated: false, next: obj };
122
+ }
123
+ const managedByMatcher = new Map();
124
+ for (const m of managed)
125
+ managedByMatcher.set(m.matcher, m);
126
+ const keptEntries = [];
127
+ let mutated = false;
128
+ for (const raw of fileChanged) {
129
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
130
+ keptEntries.push(raw);
131
+ continue;
132
+ }
133
+ const entry = raw;
134
+ const matcher = entry.matcher;
135
+ if (typeof matcher !== "string") {
136
+ keptEntries.push(raw);
137
+ continue;
138
+ }
139
+ const managedHook = managedByMatcher.get(matcher);
140
+ if (!managedHook) {
141
+ keptEntries.push(raw);
142
+ continue;
143
+ }
144
+ const innerList = entry.hooks;
145
+ if (!Array.isArray(innerList)) {
146
+ keptEntries.push(raw);
147
+ continue;
148
+ }
149
+ const keptInner = [];
150
+ let droppedAny = false;
151
+ for (const innerRaw of innerList) {
152
+ if (!innerRaw || typeof innerRaw !== "object" || Array.isArray(innerRaw)) {
153
+ keptInner.push(innerRaw);
154
+ continue;
155
+ }
156
+ const innerObj = innerRaw;
157
+ const cmd = innerObj.command;
158
+ if (typeof cmd === "string" && cmd === managedHook.command) {
159
+ droppedAny = true;
160
+ continue;
161
+ }
162
+ keptInner.push(innerRaw);
163
+ }
164
+ if (!droppedAny) {
165
+ keptEntries.push(raw);
166
+ continue;
167
+ }
168
+ mutated = true;
169
+ if (keptInner.length === 0) {
170
+ continue;
171
+ }
172
+ keptEntries.push({ ...entry, hooks: keptInner });
173
+ }
174
+ if (!mutated)
175
+ return { mutated: false, next: obj };
176
+ const nextHooks = { ...hooks };
177
+ if (keptEntries.length === 0) {
178
+ delete nextHooks.FileChanged;
179
+ }
180
+ else {
181
+ nextHooks.FileChanged = keptEntries;
182
+ }
183
+ const next = { ...obj };
184
+ if (Object.keys(nextHooks).length === 0) {
185
+ delete next.hooks;
186
+ }
187
+ else {
188
+ next.hooks = nextHooks;
189
+ }
190
+ return { mutated: true, next };
191
+ }
@@ -0,0 +1,17 @@
1
+ export declare const GITIGNORE_ENTRIES: readonly [".rn-errors.json", ".rn-flow-progress.json", ".rn-mcp-cache/", ".rn-mcp-cache"];
2
+ export type RevertGitignoreResult = {
3
+ status: "reverted";
4
+ path: string;
5
+ removedLines: string[];
6
+ } | {
7
+ status: "already-reverted";
8
+ path: string;
9
+ } | {
10
+ status: "no-gitignore";
11
+ path: string;
12
+ };
13
+ export interface RevertGitignoreDeps {
14
+ readFile?: (p: string) => Promise<string>;
15
+ writeFile?: (p: string, c: string) => Promise<void>;
16
+ }
17
+ export declare function revertGitignore(projectDir: string, deps?: RevertGitignoreDeps): Promise<RevertGitignoreResult>;
@@ -0,0 +1,43 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ export const GITIGNORE_ENTRIES = [
4
+ ".rn-errors.json",
5
+ ".rn-flow-progress.json",
6
+ ".rn-mcp-cache/",
7
+ ".rn-mcp-cache",
8
+ ];
9
+ export async function revertGitignore(projectDir, deps = {}) {
10
+ const path = join(projectDir, ".gitignore");
11
+ const read = deps.readFile ?? ((p) => readFile(p, "utf-8"));
12
+ const write = deps.writeFile ?? ((p, c) => writeFile(p, c, "utf-8"));
13
+ let content;
14
+ try {
15
+ content = await read(path);
16
+ }
17
+ catch {
18
+ return { status: "no-gitignore", path };
19
+ }
20
+ const hadTrailingNewline = content.endsWith("\n");
21
+ const lines = content.split(/\r?\n/);
22
+ if (hadTrailingNewline && lines.length > 0 && lines[lines.length - 1] === "") {
23
+ lines.pop();
24
+ }
25
+ const targets = new Set(GITIGNORE_ENTRIES);
26
+ const removedLines = [];
27
+ const kept = [];
28
+ for (const line of lines) {
29
+ if (targets.has(line.trim())) {
30
+ removedLines.push(line);
31
+ continue;
32
+ }
33
+ kept.push(line);
34
+ }
35
+ if (removedLines.length === 0) {
36
+ return { status: "already-reverted", path };
37
+ }
38
+ const nextContent = hadTrailingNewline
39
+ ? kept.join("\n") + "\n"
40
+ : kept.join("\n");
41
+ await write(path, nextContent);
42
+ return { status: "reverted", path, removedLines };
43
+ }
@@ -0,0 +1,57 @@
1
+ export declare const JSON_CLIENT_PATHS: readonly [".mcp.json", string, string];
2
+ export declare const TOML_CLIENT_PATHS: readonly [string];
3
+ export interface UserGlobalMcpClient {
4
+ label: string;
5
+ dir: readonly string[];
6
+ file: readonly string[];
7
+ }
8
+ export declare const USER_GLOBAL_MCP_CLIENTS: readonly UserGlobalMcpClient[];
9
+ export declare function userGlobalMcpClientPaths(home?: string): Array<{
10
+ label: string;
11
+ path: string;
12
+ }>;
13
+ export declare const SERVER_KEYS: readonly ["mobile-mcp", "react-native-mcp"];
14
+ export type RevertMcpClientFileResult = {
15
+ path: string;
16
+ status: "removed-entries";
17
+ removedKeys: string[];
18
+ } | {
19
+ path: string;
20
+ status: "deleted-file";
21
+ removedKeys: string[];
22
+ } | {
23
+ path: string;
24
+ status: "no-changes";
25
+ } | {
26
+ path: string;
27
+ status: "not-found";
28
+ } | {
29
+ path: string;
30
+ status: "malformed";
31
+ };
32
+ export interface RevertMcpClientsResult {
33
+ files: RevertMcpClientFileResult[];
34
+ }
35
+ export interface RevertMcpClientsDeps {
36
+ readFile?: (p: string) => Promise<string>;
37
+ writeFile?: (p: string, c: string) => Promise<void>;
38
+ unlink?: (p: string) => Promise<void>;
39
+ fileExists?: (p: string) => Promise<boolean>;
40
+ }
41
+ export declare function revertMcpClients(projectDir: string, deps?: RevertMcpClientsDeps): Promise<RevertMcpClientsResult>;
42
+ declare function stripTomlSections(raw: string, sectionHeadings: ReadonlySet<string>): {
43
+ next: string;
44
+ removedKeys: string[];
45
+ };
46
+ export { stripTomlSections };
47
+ export interface RevertUserGlobalMcpClientsResult {
48
+ files: RevertMcpClientFileResult[];
49
+ }
50
+ export interface RevertUserGlobalMcpClientsDeps {
51
+ readFile?: (p: string) => Promise<string>;
52
+ writeFile?: (p: string, c: string) => Promise<void>;
53
+ unlink?: (p: string) => Promise<void>;
54
+ fileExists?: (p: string) => Promise<boolean>;
55
+ home?: string;
56
+ }
57
+ export declare function revertUserGlobalMcpClients(deps?: RevertUserGlobalMcpClientsDeps): Promise<RevertUserGlobalMcpClientsResult>;