@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
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import { access, readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { detectMonorepoSubpackages } from "../init/monorepo.js";
|
|
4
|
-
import { detectPackageManager, } from "./revert-package.js";
|
|
5
|
-
import { detectRevertTarget } from "./revert-boot.js";
|
|
6
|
-
import { userGlobalMcpClientPaths } from "./revert-mcp-clients.js";
|
|
7
|
-
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, } from "../init/claude-hook-constants.js";
|
|
8
|
-
async function fileExists(path) {
|
|
9
|
-
try {
|
|
10
|
-
await access(path);
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
async function readPackageJson(projectDir) {
|
|
18
|
-
try {
|
|
19
|
-
const raw = await readFile(join(projectDir, "package.json"), "utf-8");
|
|
20
|
-
return JSON.parse(raw);
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
async function detectGitignoreEntries(projectDir) {
|
|
27
|
-
const path = join(projectDir, ".gitignore");
|
|
28
|
-
let content;
|
|
29
|
-
try {
|
|
30
|
-
content = await readFile(path, "utf-8");
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
const TARGETS = [
|
|
36
|
-
".rn-errors.json",
|
|
37
|
-
".rn-flow-progress.json",
|
|
38
|
-
".rn-mcp-cache/",
|
|
39
|
-
".rn-mcp-cache",
|
|
40
|
-
];
|
|
41
|
-
return TARGETS.filter((t) => content.split(/\r?\n/).some((line) => line.trim() === t));
|
|
42
|
-
}
|
|
43
|
-
async function detectSchemeRegistered(projectDir) {
|
|
44
|
-
const appJsonPath = join(projectDir, "app.json");
|
|
45
|
-
let raw;
|
|
46
|
-
try {
|
|
47
|
-
raw = await readFile(appJsonPath, "utf-8");
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
let parsed;
|
|
53
|
-
try {
|
|
54
|
-
parsed = JSON.parse(raw);
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
const scheme = parsed.expo?.scheme;
|
|
60
|
-
if (typeof scheme === "string")
|
|
61
|
-
return scheme === "ceraph";
|
|
62
|
-
if (Array.isArray(scheme))
|
|
63
|
-
return scheme.includes("ceraph");
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
async function countCeraphCameraUsages(projectDir) {
|
|
67
|
-
const SCAN_ROOTS = ["app", "src", "screens"];
|
|
68
|
-
const SCAN_EXT = new Set([".tsx", ".jsx"]);
|
|
69
|
-
const SKIP_DIRS = new Set([
|
|
70
|
-
"node_modules",
|
|
71
|
-
"dist",
|
|
72
|
-
"build",
|
|
73
|
-
".git",
|
|
74
|
-
".ceraph",
|
|
75
|
-
".rn-mcp-cache",
|
|
76
|
-
".expo",
|
|
77
|
-
".next",
|
|
78
|
-
"ios",
|
|
79
|
-
"android",
|
|
80
|
-
]);
|
|
81
|
-
let count = 0;
|
|
82
|
-
const walk = async (dir) => {
|
|
83
|
-
let entries;
|
|
84
|
-
try {
|
|
85
|
-
const { readdir } = await import("node:fs/promises");
|
|
86
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
for (const entry of entries) {
|
|
92
|
-
if (entry.isDirectory()) {
|
|
93
|
-
if (SKIP_DIRS.has(entry.name))
|
|
94
|
-
continue;
|
|
95
|
-
await walk(join(dir, entry.name));
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (!entry.isFile())
|
|
99
|
-
continue;
|
|
100
|
-
const dot = entry.name.lastIndexOf(".");
|
|
101
|
-
if (dot < 0)
|
|
102
|
-
continue;
|
|
103
|
-
const ext = entry.name.slice(dot).toLowerCase();
|
|
104
|
-
if (!SCAN_EXT.has(ext))
|
|
105
|
-
continue;
|
|
106
|
-
try {
|
|
107
|
-
const raw = await readFile(join(dir, entry.name), "utf-8");
|
|
108
|
-
const matches = raw.match(/<CeraphCamera/g);
|
|
109
|
-
if (matches)
|
|
110
|
-
count += matches.length;
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
for (const root of SCAN_ROOTS) {
|
|
117
|
-
await walk(join(projectDir, root));
|
|
118
|
-
}
|
|
119
|
-
for (const single of ["App.tsx", "App.jsx"]) {
|
|
120
|
-
try {
|
|
121
|
-
const raw = await readFile(join(projectDir, single), "utf-8");
|
|
122
|
-
const matches = raw.match(/<CeraphCamera/g);
|
|
123
|
-
if (matches)
|
|
124
|
-
count += matches.length;
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return count;
|
|
130
|
-
}
|
|
131
|
-
async function detectMcpClientConfigured(projectDir, home) {
|
|
132
|
-
const hasEntry = async (path, kind) => {
|
|
133
|
-
try {
|
|
134
|
-
const raw = await readFile(path, "utf-8");
|
|
135
|
-
if (kind === "json") {
|
|
136
|
-
const parsed = JSON.parse(raw);
|
|
137
|
-
const servers = parsed.mcpServers ?? {};
|
|
138
|
-
return Boolean(servers["react-native-mcp"] || servers["mobile-mcp"]);
|
|
139
|
-
}
|
|
140
|
-
return (raw.includes("[mcp_servers.react-native-mcp]") ||
|
|
141
|
-
raw.includes("[mcp_servers.mobile-mcp]"));
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
const globals = userGlobalMcpClientPaths(home || undefined);
|
|
148
|
-
const byLabel = {};
|
|
149
|
-
for (const g of globals)
|
|
150
|
-
byLabel[g.label] = g.path;
|
|
151
|
-
return {
|
|
152
|
-
claudeCode: await hasEntry(join(projectDir, ".mcp.json"), "json"),
|
|
153
|
-
cursor: await hasEntry(join(projectDir, ".cursor", "mcp.json"), "json"),
|
|
154
|
-
codex: await hasEntry(join(projectDir, ".codex", "config.toml"), "toml"),
|
|
155
|
-
vscode: await hasEntry(join(projectDir, ".vscode", "mcp.json"), "json"),
|
|
156
|
-
windsurf: home
|
|
157
|
-
? await hasEntry(byLabel.Windsurf ?? "", "json")
|
|
158
|
-
: false,
|
|
159
|
-
antigravity: home
|
|
160
|
-
? await hasEntry(byLabel.Antigravity ?? "", "json")
|
|
161
|
-
: false,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
async function detectClaudeHooksPresent(projectDir) {
|
|
165
|
-
const errorScript = await fileExists(join(projectDir, ...ERROR_HOOK_REL_PATH));
|
|
166
|
-
const flowProgressScript = await fileExists(join(projectDir, ...FLOW_PROGRESS_HOOK_REL_PATH));
|
|
167
|
-
let settingsEntries = false;
|
|
168
|
-
for (const relParts of CLAUDE_SETTINGS_REL_PATHS) {
|
|
169
|
-
const abs = join(projectDir, ...relParts);
|
|
170
|
-
let raw;
|
|
171
|
-
try {
|
|
172
|
-
raw = await readFile(abs, "utf-8");
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
let parsed;
|
|
178
|
-
try {
|
|
179
|
-
parsed = JSON.parse(raw);
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
185
|
-
continue;
|
|
186
|
-
const hooks = parsed.hooks;
|
|
187
|
-
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks))
|
|
188
|
-
continue;
|
|
189
|
-
const fc = hooks.FileChanged;
|
|
190
|
-
if (!Array.isArray(fc))
|
|
191
|
-
continue;
|
|
192
|
-
for (const entry of fc) {
|
|
193
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
194
|
-
continue;
|
|
195
|
-
const e = entry;
|
|
196
|
-
const matcher = e.matcher;
|
|
197
|
-
if (matcher !== ERROR_HOOK_MATCHER && matcher !== FLOW_PROGRESS_HOOK_MATCHER) {
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
const ourCommand = matcher === ERROR_HOOK_MATCHER
|
|
201
|
-
? ERROR_HOOK_COMMAND
|
|
202
|
-
: FLOW_PROGRESS_HOOK_COMMAND;
|
|
203
|
-
const list = e.hooks;
|
|
204
|
-
if (!Array.isArray(list))
|
|
205
|
-
continue;
|
|
206
|
-
for (const raw2 of list) {
|
|
207
|
-
if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2))
|
|
208
|
-
continue;
|
|
209
|
-
const cmd = raw2.command;
|
|
210
|
-
if (typeof cmd === "string" && cmd === ourCommand) {
|
|
211
|
-
settingsEntries = true;
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (settingsEntries)
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
if (settingsEntries)
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
return { errorScript, flowProgressScript, settingsEntries };
|
|
222
|
-
}
|
|
223
|
-
export async function getInstalledFootprint(projectDir, deps = {}) {
|
|
224
|
-
const detect = deps.detectMonorepo ?? detectMonorepoSubpackages;
|
|
225
|
-
const detection = await detect(projectDir);
|
|
226
|
-
let workingDir = projectDir;
|
|
227
|
-
let monorepoConflict = null;
|
|
228
|
-
if (detection.kind === "single" && detection.matches[0]) {
|
|
229
|
-
workingDir = detection.matches[0].absPath;
|
|
230
|
-
}
|
|
231
|
-
else if (detection.kind === "multi") {
|
|
232
|
-
monorepoConflict = {
|
|
233
|
-
matches: detection.matches.map((m) => ({
|
|
234
|
-
relPath: m.relPath,
|
|
235
|
-
absPath: m.absPath,
|
|
236
|
-
})),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
const home = deps.homeDir?.() ??
|
|
240
|
-
process.env.HOME ??
|
|
241
|
-
process.env.USERPROFILE ??
|
|
242
|
-
"";
|
|
243
|
-
const pkg = await readPackageJson(workingDir);
|
|
244
|
-
const allDeps = {
|
|
245
|
-
...(pkg?.dependencies ?? {}),
|
|
246
|
-
...(pkg?.devDependencies ?? {}),
|
|
247
|
-
};
|
|
248
|
-
const packageInstalled = Boolean(allDeps["@ceraph/react-native-mcp"]);
|
|
249
|
-
const appiumPresent = Boolean(allDeps["appium-webdriveragent"]);
|
|
250
|
-
const packageManager = await detectPackageManager(workingDir);
|
|
251
|
-
const gitignoreEntries = await detectGitignoreEntries(workingDir);
|
|
252
|
-
const schemeRegistered = await detectSchemeRegistered(workingDir);
|
|
253
|
-
const bootTarget = await detectRevertTarget(workingDir);
|
|
254
|
-
let bootInjected = null;
|
|
255
|
-
if (bootTarget) {
|
|
256
|
-
try {
|
|
257
|
-
const raw = await readFile(bootTarget.filePath, "utf-8");
|
|
258
|
-
bootInjected = raw.includes("installCeraph(");
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
261
|
-
bootInjected = null;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
const ceraphCameraUsageCount = await countCeraphCameraUsages(workingDir);
|
|
265
|
-
const mcpClientsConfigured = await detectMcpClientConfigured(workingDir, home);
|
|
266
|
-
const claudeHooksPresent = await detectClaudeHooksPresent(workingDir);
|
|
267
|
-
const ceraphDirPresent = await fileExists(join(workingDir, ".ceraph"));
|
|
268
|
-
const authPresent = home
|
|
269
|
-
? await fileExists(join(home, ".ceraph", "auth.json"))
|
|
270
|
-
: false;
|
|
271
|
-
return {
|
|
272
|
-
workingDir,
|
|
273
|
-
monorepoConflict,
|
|
274
|
-
packageInstalled,
|
|
275
|
-
packageManager,
|
|
276
|
-
appiumPresent,
|
|
277
|
-
gitignoreEntriesPresent: gitignoreEntries.length > 0,
|
|
278
|
-
gitignoreEntries,
|
|
279
|
-
schemeRegistered,
|
|
280
|
-
bootTargetPath: bootTarget?.filePath ?? null,
|
|
281
|
-
bootInjected,
|
|
282
|
-
ceraphCameraUsageCount,
|
|
283
|
-
mcpClientsConfigured,
|
|
284
|
-
claudeHooksPresent,
|
|
285
|
-
ceraphDirPresent,
|
|
286
|
-
authPresent,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { type UninstallWalkthroughResult } from "./walkthrough.js";
|
|
3
|
-
interface RegisterServer {
|
|
4
|
-
tool: (name: string, description: string, schema: Record<string, z.ZodTypeAny>, handler: (args: Record<string, unknown>) => Promise<{
|
|
5
|
-
content: {
|
|
6
|
-
type: "text";
|
|
7
|
-
text: string;
|
|
8
|
-
}[];
|
|
9
|
-
isError?: true;
|
|
10
|
-
}>) => void;
|
|
11
|
-
}
|
|
12
|
-
export declare function compactWalkthroughResult(result: UninstallWalkthroughResult, maxBytes?: number): UninstallWalkthroughResult;
|
|
13
|
-
export declare function registerUninstallTools(server: RegisterServer): void;
|
|
14
|
-
export {};
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { getInstalledFootprint } from "./footprint.js";
|
|
4
|
-
import { runUninstallWalkthrough, } from "./walkthrough.js";
|
|
5
|
-
const MAX_TOOL_PAYLOAD_BYTES = 64 * 1024;
|
|
6
|
-
const MODULE_LOAD_CWD = process.cwd();
|
|
7
|
-
function defaultCwd() {
|
|
8
|
-
return MODULE_LOAD_CWD;
|
|
9
|
-
}
|
|
10
|
-
function toolResult(payload) {
|
|
11
|
-
return {
|
|
12
|
-
content: [
|
|
13
|
-
{ type: "text", text: JSON.stringify(payload, null, 2) },
|
|
14
|
-
],
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
const MAX_STRING_FIELD_BYTES = 2 * 1024;
|
|
18
|
-
function truncateString(s, max) {
|
|
19
|
-
if (s.length <= max)
|
|
20
|
-
return s;
|
|
21
|
-
const head = s.slice(0, Math.floor(max / 2) - 16);
|
|
22
|
-
const tail = s.slice(s.length - (Math.floor(max / 2) - 16));
|
|
23
|
-
return `${head} … <${s.length - max} bytes truncated> … ${tail}`;
|
|
24
|
-
}
|
|
25
|
-
export function compactWalkthroughResult(result, maxBytes = MAX_TOOL_PAYLOAD_BYTES) {
|
|
26
|
-
const full = JSON.stringify(result, null, 2);
|
|
27
|
-
if (full.length <= maxBytes)
|
|
28
|
-
return result;
|
|
29
|
-
const compactSteps = result.steps.map((step) => {
|
|
30
|
-
if (step.details === undefined)
|
|
31
|
-
return step;
|
|
32
|
-
const detailsJson = JSON.stringify(step.details);
|
|
33
|
-
return {
|
|
34
|
-
...step,
|
|
35
|
-
details: {
|
|
36
|
-
truncated: true,
|
|
37
|
-
originalSize: detailsJson.length,
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
});
|
|
41
|
-
const afterPass1 = {
|
|
42
|
-
...result,
|
|
43
|
-
steps: compactSteps,
|
|
44
|
-
};
|
|
45
|
-
const pass1Json = JSON.stringify(afterPass1, null, 2);
|
|
46
|
-
if (pass1Json.length <= maxBytes)
|
|
47
|
-
return afterPass1;
|
|
48
|
-
let lo = 32;
|
|
49
|
-
let hi = MAX_STRING_FIELD_BYTES;
|
|
50
|
-
let best = afterPass1;
|
|
51
|
-
while (lo <= hi) {
|
|
52
|
-
const mid = Math.floor((lo + hi) / 2);
|
|
53
|
-
const candidate = {
|
|
54
|
-
...afterPass1,
|
|
55
|
-
workingDir: truncateString(afterPass1.workingDir, mid),
|
|
56
|
-
steps: afterPass1.steps.map((step) => ({
|
|
57
|
-
...step,
|
|
58
|
-
remediation: step.remediation
|
|
59
|
-
? truncateString(step.remediation, mid)
|
|
60
|
-
: step.remediation,
|
|
61
|
-
})),
|
|
62
|
-
summary: {
|
|
63
|
-
...afterPass1.summary,
|
|
64
|
-
warnings: afterPass1.summary.warnings.map((w) => truncateString(w, mid)),
|
|
65
|
-
manualSteps: afterPass1.summary.manualSteps.map((m) => truncateString(m, mid)),
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
const size = JSON.stringify(candidate, null, 2).length;
|
|
69
|
-
if (size <= maxBytes) {
|
|
70
|
-
best = candidate;
|
|
71
|
-
lo = mid + 1;
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
hi = mid - 1;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return best;
|
|
78
|
-
}
|
|
79
|
-
function toolError(toolName, err) {
|
|
80
|
-
return {
|
|
81
|
-
content: [
|
|
82
|
-
{
|
|
83
|
-
type: "text",
|
|
84
|
-
text: `${toolName} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
isError: true,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
function bufferedSpawnRemove(input) {
|
|
91
|
-
return new Promise((resolve) => {
|
|
92
|
-
const child = spawn(input.bin, input.args, {
|
|
93
|
-
cwd: input.cwd,
|
|
94
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
95
|
-
});
|
|
96
|
-
child.stdout?.on("data", () => undefined);
|
|
97
|
-
child.stderr?.on("data", () => undefined);
|
|
98
|
-
child.on("error", () => resolve({ exitCode: 127 }));
|
|
99
|
-
child.on("exit", (code) => resolve({ exitCode: code ?? 1 }));
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
export function registerUninstallTools(server) {
|
|
103
|
-
server.tool("ceraph_uninstall_status", "Snapshot what Ceraph has installed in this project — package " +
|
|
104
|
-
"presence, gitignore entries, MCP-client configs, root-layout " +
|
|
105
|
-
"boot wiring, <CeraphCamera> usage count, and whether the user-" +
|
|
106
|
-
"global auth token is present. Use this BEFORE ceraph_uninstall_run " +
|
|
107
|
-
"to show the user what would change. Returns the footprint without " +
|
|
108
|
-
"mutating anything.", {
|
|
109
|
-
projectDir: z
|
|
110
|
-
.string()
|
|
111
|
-
.optional()
|
|
112
|
-
.describe("Project directory to inspect. Defaults to the MCP server's " +
|
|
113
|
-
"cwd. In a monorepo with multiple RN subpackages, the tool " +
|
|
114
|
-
"returns `monorepoConflict` listing each candidate — pass " +
|
|
115
|
-
"the chosen subpackage's path here to disambiguate."),
|
|
116
|
-
}, async (args) => {
|
|
117
|
-
try {
|
|
118
|
-
const projectDir = args.projectDir ?? defaultCwd();
|
|
119
|
-
const footprint = await getInstalledFootprint(projectDir);
|
|
120
|
-
return toolResult(footprint);
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
return toolError("ceraph_uninstall_status", err);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
server.tool("ceraph_uninstall_run", "Uninstall Ceraph from this project. Reverts source-tree edits " +
|
|
127
|
-
"(<CeraphCamera> → <CameraView>, installCeraph() boot wiring, " +
|
|
128
|
-
"ceraph URL scheme), strips MCP client configs, removes gitignore " +
|
|
129
|
-
"entries + the shell signal-host export, and uninstalls the " +
|
|
130
|
-
"package via the project's package manager. Default leaves " +
|
|
131
|
-
"`.ceraph/` (test images + snapshots) and `~/.ceraph/auth.json` " +
|
|
132
|
-
"(user-global OAuth token) untouched. Pass `purgeImages` and " +
|
|
133
|
-
"`global` to nuke those too. Always call with `dryRun: true` " +
|
|
134
|
-
"first to preview the step list before a real run.", {
|
|
135
|
-
projectDir: z
|
|
136
|
-
.string()
|
|
137
|
-
.optional()
|
|
138
|
-
.describe("Project directory to operate on. Defaults to the MCP " +
|
|
139
|
-
"server's cwd. Required to disambiguate monorepo " +
|
|
140
|
-
"multi-match."),
|
|
141
|
-
purgeImages: z
|
|
142
|
-
.boolean()
|
|
143
|
-
.optional()
|
|
144
|
-
.describe("If true, also delete `.ceraph/` (camera test images + " +
|
|
145
|
-
"design snapshots + run artifacts). Default false."),
|
|
146
|
-
global: z
|
|
147
|
-
.boolean()
|
|
148
|
-
.optional()
|
|
149
|
-
.describe("If true, also delete `~/.ceraph/auth.json` (the user-global " +
|
|
150
|
-
"OAuth token; this logs the user out of every Ceraph " +
|
|
151
|
-
"install on this machine). Default false."),
|
|
152
|
-
dryRun: z
|
|
153
|
-
.boolean()
|
|
154
|
-
.optional()
|
|
155
|
-
.describe("If true, return the step list as a preview without " +
|
|
156
|
-
"mutating anything. Use this first to show the user what " +
|
|
157
|
-
"would happen."),
|
|
158
|
-
}, async (args) => {
|
|
159
|
-
try {
|
|
160
|
-
const projectDir = args.projectDir ?? defaultCwd();
|
|
161
|
-
const result = await runUninstallWalkthrough({
|
|
162
|
-
projectDir,
|
|
163
|
-
purgeImages: args.purgeImages === true,
|
|
164
|
-
global: args.global === true,
|
|
165
|
-
dryRun: args.dryRun === true,
|
|
166
|
-
nonInteractive: true,
|
|
167
|
-
spawnRemove: bufferedSpawnRemove,
|
|
168
|
-
});
|
|
169
|
-
return toolResult(compactWalkthroughResult(result));
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
return toolError("ceraph_uninstall_run", err);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export type RevertAuthResult = {
|
|
2
|
-
status: "skipped";
|
|
3
|
-
path: string;
|
|
4
|
-
note: string;
|
|
5
|
-
} | {
|
|
6
|
-
status: "deleted";
|
|
7
|
-
path: string;
|
|
8
|
-
} | {
|
|
9
|
-
status: "already-reverted";
|
|
10
|
-
path: string;
|
|
11
|
-
};
|
|
12
|
-
export interface RevertAuthInput {
|
|
13
|
-
global: boolean;
|
|
14
|
-
}
|
|
15
|
-
export interface RevertAuthDeps {
|
|
16
|
-
unlink?: (p: string) => Promise<void>;
|
|
17
|
-
home?: string;
|
|
18
|
-
}
|
|
19
|
-
export declare function authJsonPath(deps?: {
|
|
20
|
-
home?: string;
|
|
21
|
-
}): string;
|
|
22
|
-
export declare function revertAuth(input: RevertAuthInput, deps?: RevertAuthDeps): Promise<RevertAuthResult>;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { unlink } from "node:fs/promises";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
export function authJsonPath(deps = {}) {
|
|
5
|
-
return join(deps.home ?? homedir(), ".ceraph", "auth.json");
|
|
6
|
-
}
|
|
7
|
-
export async function revertAuth(input, deps = {}) {
|
|
8
|
-
const path = authJsonPath({ home: deps.home });
|
|
9
|
-
const unlinkFn = deps.unlink ?? ((p) => unlink(p));
|
|
10
|
-
if (!input.global) {
|
|
11
|
-
return {
|
|
12
|
-
status: "skipped",
|
|
13
|
-
path,
|
|
14
|
-
note: "Pass global: true to remove ~/.ceraph/auth.json — the token is user-global and removing it logs you out of every Ceraph install.",
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
try {
|
|
18
|
-
await unlinkFn(path);
|
|
19
|
-
return { status: "deleted", path };
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
const isMissing = typeof err === "object" &&
|
|
23
|
-
err !== null &&
|
|
24
|
-
"code" in err &&
|
|
25
|
-
err.code === "ENOENT";
|
|
26
|
-
if (isMissing) {
|
|
27
|
-
return { status: "already-reverted", path };
|
|
28
|
-
}
|
|
29
|
-
throw err;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export type RevertBootTarget = {
|
|
2
|
-
kind: "expo-router";
|
|
3
|
-
filePath: string;
|
|
4
|
-
} | {
|
|
5
|
-
kind: "bare-rn";
|
|
6
|
-
filePath: string;
|
|
7
|
-
};
|
|
8
|
-
export type RevertBootResult = {
|
|
9
|
-
status: "reverted";
|
|
10
|
-
target: RevertBootTarget;
|
|
11
|
-
removedUseEffect: boolean;
|
|
12
|
-
removedImport: boolean;
|
|
13
|
-
} | {
|
|
14
|
-
status: "already-reverted";
|
|
15
|
-
target: RevertBootTarget;
|
|
16
|
-
} | {
|
|
17
|
-
status: "skipped";
|
|
18
|
-
target: RevertBootTarget;
|
|
19
|
-
reason: string;
|
|
20
|
-
} | {
|
|
21
|
-
status: "no-layout-found";
|
|
22
|
-
};
|
|
23
|
-
export declare function detectRevertTarget(projectDir: string): Promise<RevertBootTarget | null>;
|
|
24
|
-
export declare function revertBoot(projectDir: string): Promise<RevertBootResult>;
|