@ceraph/react-native-mcp 0.3.3 → 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.
- 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/url-scheme.js
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, access } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
export const CERAPH_SCHEME = "ceraph";
|
|
4
|
-
async function defaultExists(p) {
|
|
5
|
-
try {
|
|
6
|
-
await access(p);
|
|
7
|
-
return true;
|
|
8
|
-
}
|
|
9
|
-
catch {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
export async function ensureCeraphUrlScheme(projectDir, deps = {}) {
|
|
14
|
-
const exists = deps.fileExists ?? defaultExists;
|
|
15
|
-
const read = deps.readFile ?? ((p) => readFile(p, "utf-8"));
|
|
16
|
-
const write = deps.writeFile ?? ((p, c) => writeFile(p, c, "utf-8"));
|
|
17
|
-
const appJsonPath = join(projectDir, "app.json");
|
|
18
|
-
const appConfigJs = join(projectDir, "app.config.js");
|
|
19
|
-
const appConfigTs = join(projectDir, "app.config.ts");
|
|
20
|
-
const iosDir = join(projectDir, "ios");
|
|
21
|
-
const androidDir = join(projectDir, "android");
|
|
22
|
-
const hasAppConfigJs = await exists(appConfigJs);
|
|
23
|
-
const hasAppConfigTs = await exists(appConfigTs);
|
|
24
|
-
if (hasAppConfigJs || hasAppConfigTs) {
|
|
25
|
-
return {
|
|
26
|
-
kind: "expo-config-needs-manual",
|
|
27
|
-
path: hasAppConfigTs ? appConfigTs : appConfigJs,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
if (await exists(appJsonPath)) {
|
|
31
|
-
const raw = await read(appJsonPath);
|
|
32
|
-
const parsed = parseAppJson(raw);
|
|
33
|
-
if (!parsed.ok) {
|
|
34
|
-
return { kind: "expo-config-needs-manual", path: appJsonPath };
|
|
35
|
-
}
|
|
36
|
-
const result = injectSchemeIntoAppJson(parsed.parsed, CERAPH_SCHEME);
|
|
37
|
-
if (result.action === "already") {
|
|
38
|
-
return {
|
|
39
|
-
kind: "expo-json-already-registered",
|
|
40
|
-
scheme: result.scheme,
|
|
41
|
-
path: appJsonPath,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
await write(appJsonPath, serialiseAppJson(parsed.parsed, raw));
|
|
45
|
-
return {
|
|
46
|
-
kind: "expo-json-added",
|
|
47
|
-
previousScheme: result.previousScheme,
|
|
48
|
-
nextScheme: result.nextScheme,
|
|
49
|
-
path: appJsonPath,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
if ((await exists(iosDir)) || (await exists(androidDir))) {
|
|
53
|
-
return { kind: "bare-rn-needs-manual" };
|
|
54
|
-
}
|
|
55
|
-
return { kind: "no-rn-project" };
|
|
56
|
-
}
|
|
57
|
-
function parseAppJson(raw) {
|
|
58
|
-
try {
|
|
59
|
-
const parsed = JSON.parse(raw);
|
|
60
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
61
|
-
return { ok: false, parsed: {} };
|
|
62
|
-
}
|
|
63
|
-
return { ok: true, parsed: parsed };
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return { ok: false, parsed: {} };
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function serialiseAppJson(parsed, original) {
|
|
70
|
-
const indentMatch = original.match(/\n(\s+)"/);
|
|
71
|
-
const indent = indentMatch?.[1] ?? " ";
|
|
72
|
-
const hadTrailingNewline = original.endsWith("\n");
|
|
73
|
-
const output = JSON.stringify(parsed, null, indent);
|
|
74
|
-
return hadTrailingNewline ? output + "\n" : output;
|
|
75
|
-
}
|
|
76
|
-
export function injectSchemeIntoAppJson(parsed, schemeToAdd) {
|
|
77
|
-
if (!parsed.expo || typeof parsed.expo !== "object") {
|
|
78
|
-
parsed.expo = {};
|
|
79
|
-
}
|
|
80
|
-
const expo = parsed.expo;
|
|
81
|
-
const current = expo.scheme;
|
|
82
|
-
if (typeof current === "string" && current === schemeToAdd) {
|
|
83
|
-
return {
|
|
84
|
-
action: "already",
|
|
85
|
-
scheme: current,
|
|
86
|
-
previousScheme: current,
|
|
87
|
-
nextScheme: [current],
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
if (Array.isArray(current) && current.includes(schemeToAdd)) {
|
|
91
|
-
return {
|
|
92
|
-
action: "already",
|
|
93
|
-
scheme: current,
|
|
94
|
-
previousScheme: current,
|
|
95
|
-
nextScheme: [...current],
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
if (current === undefined) {
|
|
99
|
-
expo.scheme = schemeToAdd;
|
|
100
|
-
return {
|
|
101
|
-
action: "added",
|
|
102
|
-
scheme: schemeToAdd,
|
|
103
|
-
previousScheme: undefined,
|
|
104
|
-
nextScheme: [schemeToAdd],
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
if (typeof current === "string") {
|
|
108
|
-
const next = [current, schemeToAdd];
|
|
109
|
-
expo.scheme = next;
|
|
110
|
-
return {
|
|
111
|
-
action: "added",
|
|
112
|
-
scheme: next,
|
|
113
|
-
previousScheme: current,
|
|
114
|
-
nextScheme: next,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
if (Array.isArray(current)) {
|
|
118
|
-
const next = [...current, schemeToAdd];
|
|
119
|
-
expo.scheme = next;
|
|
120
|
-
return {
|
|
121
|
-
action: "added",
|
|
122
|
-
scheme: next,
|
|
123
|
-
previousScheme: current,
|
|
124
|
-
nextScheme: next,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
expo.scheme = schemeToAdd;
|
|
128
|
-
return {
|
|
129
|
-
action: "added",
|
|
130
|
-
scheme: schemeToAdd,
|
|
131
|
-
previousScheme: current,
|
|
132
|
-
nextScheme: [schemeToAdd],
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
export function bareRnInstructions() {
|
|
136
|
-
return `
|
|
137
|
-
Add Ceraph's custom URL scheme so deep links (ceraph://test-image,
|
|
138
|
-
ceraph://reload, ceraph://reset) reach the running app.
|
|
139
|
-
|
|
140
|
-
iOS — edit ios/<YourApp>/Info.plist:
|
|
141
|
-
|
|
142
|
-
<key>CFBundleURLTypes</key>
|
|
143
|
-
<array>
|
|
144
|
-
<dict>
|
|
145
|
-
<key>CFBundleURLName</key>
|
|
146
|
-
<string>com.yourcompany.yourapp</string>
|
|
147
|
-
<key>CFBundleURLSchemes</key>
|
|
148
|
-
<array>
|
|
149
|
-
<string>ceraph</string>
|
|
150
|
-
</array>
|
|
151
|
-
</dict>
|
|
152
|
-
</array>
|
|
153
|
-
|
|
154
|
-
Android — edit android/app/src/main/AndroidManifest.xml inside the
|
|
155
|
-
MainActivity <activity> block:
|
|
156
|
-
|
|
157
|
-
<intent-filter>
|
|
158
|
-
<action android:name="android.intent.action.VIEW" />
|
|
159
|
-
<category android:name="android.intent.category.DEFAULT" />
|
|
160
|
-
<category android:name="android.intent.category.BROWSABLE" />
|
|
161
|
-
<data android:scheme="ceraph" />
|
|
162
|
-
</intent-filter>
|
|
163
|
-
|
|
164
|
-
Then rebuild the native app (pod install + xcodebuild on iOS, ./gradlew
|
|
165
|
-
assembleDebug on Android). Existing JS-only reloads do not pick up
|
|
166
|
-
native config changes.
|
|
167
|
-
`.trimStart();
|
|
168
|
-
}
|
|
169
|
-
export function expoDynamicConfigInstructions(configPath) {
|
|
170
|
-
return `
|
|
171
|
-
Your project uses ${configPath} (dynamic Expo config) — we can't
|
|
172
|
-
safely auto-edit a JS/TS file. Add Ceraph's URL scheme manually:
|
|
173
|
-
|
|
174
|
-
export default ({ config }) => ({
|
|
175
|
-
...config,
|
|
176
|
-
scheme: Array.isArray(config.scheme)
|
|
177
|
-
? [...config.scheme, "ceraph"]
|
|
178
|
-
: config.scheme
|
|
179
|
-
? [config.scheme, "ceraph"]
|
|
180
|
-
: "ceraph",
|
|
181
|
-
// ... rest of your expo config
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
Then re-run \`expo prebuild --clean\` if you've ejected, or just
|
|
185
|
-
restart the dev client.
|
|
186
|
-
`.trimStart();
|
|
187
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { type PackageManager } from "./package-manager.js";
|
|
2
|
-
import { type StoredAuth } from "./oauth.js";
|
|
3
|
-
import { type RootComponentTarget } from "./ast-layout.js";
|
|
4
|
-
import { type PromptDeps } from "./prompt.js";
|
|
5
|
-
import { type MonorepoDetectionResult } from "./monorepo.js";
|
|
6
|
-
import { type SchemeAction } from "./url-scheme.js";
|
|
7
|
-
import { type LanIpCandidate } from "./lan-ip.js";
|
|
8
|
-
import { type WriteSignalHostEnvResult } from "./shell-profile.js";
|
|
9
|
-
export interface WalkthroughDeps extends PromptDeps {
|
|
10
|
-
agentMode?: boolean;
|
|
11
|
-
installPackage?: (input: {
|
|
12
|
-
bin: string;
|
|
13
|
-
args: string[];
|
|
14
|
-
cwd: string;
|
|
15
|
-
}) => Promise<{
|
|
16
|
-
exitCode: number;
|
|
17
|
-
}>;
|
|
18
|
-
authenticate?: () => Promise<StoredAuth>;
|
|
19
|
-
loadAuth?: () => Promise<StoredAuth | null>;
|
|
20
|
-
runPreflight?: () => Promise<{
|
|
21
|
-
ok: boolean;
|
|
22
|
-
summary: string;
|
|
23
|
-
passedCount: number;
|
|
24
|
-
totalCount: number;
|
|
25
|
-
}>;
|
|
26
|
-
skipInstall?: boolean;
|
|
27
|
-
detectMonorepo?: (projectDir: string) => Promise<MonorepoDetectionResult>;
|
|
28
|
-
ensureUrlScheme?: (projectDir: string) => Promise<SchemeAction>;
|
|
29
|
-
detectLanIpCandidates?: () => LanIpCandidate[];
|
|
30
|
-
writeSignalHostEnv?: (ip: string) => Promise<WriteSignalHostEnvResult>;
|
|
31
|
-
}
|
|
32
|
-
export interface WalkthroughResult {
|
|
33
|
-
packageInstalled: boolean;
|
|
34
|
-
packageManager: PackageManager;
|
|
35
|
-
authLogin: string | null;
|
|
36
|
-
cameraEdits: {
|
|
37
|
-
filesEdited: string[];
|
|
38
|
-
totalReplacements: number;
|
|
39
|
-
};
|
|
40
|
-
layoutInjection: {
|
|
41
|
-
applied: boolean;
|
|
42
|
-
target: RootComponentTarget | null;
|
|
43
|
-
reason?: string;
|
|
44
|
-
};
|
|
45
|
-
cameraRegistrySync: {
|
|
46
|
-
state: "written" | "unchanged" | "missing-dir";
|
|
47
|
-
registered: number;
|
|
48
|
-
};
|
|
49
|
-
preflight: {
|
|
50
|
-
ok: boolean;
|
|
51
|
-
summary: string;
|
|
52
|
-
passedCount: number;
|
|
53
|
-
totalCount: number;
|
|
54
|
-
} | null;
|
|
55
|
-
workingDir: string;
|
|
56
|
-
urlScheme: SchemeAction | null;
|
|
57
|
-
signalHost: {
|
|
58
|
-
ip: string;
|
|
59
|
-
kind: LanIpCandidate["kind"];
|
|
60
|
-
interfaceName: string;
|
|
61
|
-
profile: WriteSignalHostEnvResult;
|
|
62
|
-
otherCandidates: LanIpCandidate[];
|
|
63
|
-
} | null;
|
|
64
|
-
agentHint: AgentInitHint | null;
|
|
65
|
-
}
|
|
66
|
-
export interface AgentInitHint {
|
|
67
|
-
cameras: Array<{
|
|
68
|
-
file: string;
|
|
69
|
-
line: number;
|
|
70
|
-
suggestedKey: string;
|
|
71
|
-
}>;
|
|
72
|
-
setupRequired: boolean;
|
|
73
|
-
hintText: string;
|
|
74
|
-
}
|
|
75
|
-
export declare const AGENT_HINT_SENTINEL = "CERAPH_INIT_AGENT_HINT=";
|
|
76
|
-
export declare function runWalkthrough(projectDir: string, deps?: WalkthroughDeps): Promise<WalkthroughResult>;
|
package/dist/init/walkthrough.js
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { detectPackageManager, installArgv, isPackageInstalled, } from "./package-manager.js";
|
|
4
|
-
import { readExistingAuth, pollUntilToken, } from "./oauth.js";
|
|
5
|
-
import { scanCameraViews, applyCameraEdits, snapshotFile, restoreFile, } from "./ast-camera.js";
|
|
6
|
-
import { detectRootComponent, injectInstallCeraph, snapshotLayout, restoreLayout, } from "./ast-layout.js";
|
|
7
|
-
import { confirm, chooseFromList, print, } from "./prompt.js";
|
|
8
|
-
import { syncCameraRegistry, relativeRegistryPath, } from "../camera-registry-sync.js";
|
|
9
|
-
import { detectMonorepoSubpackages, } from "./monorepo.js";
|
|
10
|
-
import { ensureCeraphUrlScheme, bareRnInstructions, expoDynamicConfigInstructions, } from "./url-scheme.js";
|
|
11
|
-
import { detectMacLanIpCandidates, } from "./lan-ip.js";
|
|
12
|
-
import { writeSignalHostEnv, } from "./shell-profile.js";
|
|
13
|
-
export const AGENT_HINT_SENTINEL = "CERAPH_INIT_AGENT_HINT=";
|
|
14
|
-
function defaultInstallPackage(input) {
|
|
15
|
-
return new Promise((resolve) => {
|
|
16
|
-
const child = spawn(input.bin, input.args, {
|
|
17
|
-
cwd: input.cwd,
|
|
18
|
-
stdio: "inherit",
|
|
19
|
-
});
|
|
20
|
-
child.on("error", () => resolve({ exitCode: 127 }));
|
|
21
|
-
child.on("exit", (code) => resolve({ exitCode: code ?? 1 }));
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
function fmt(line) {
|
|
25
|
-
return ` ${line}\n`;
|
|
26
|
-
}
|
|
27
|
-
export async function runWalkthrough(projectDir, deps = {}) {
|
|
28
|
-
const say = (msg) => print(msg, deps);
|
|
29
|
-
say("\n@ceraph/react-native-mcp init\n\n");
|
|
30
|
-
const workingDir = await resolveWorkingDir(projectDir, deps);
|
|
31
|
-
const pm = await detectPackageManager(workingDir);
|
|
32
|
-
const alreadyInstalled = await isPackageInstalled(workingDir);
|
|
33
|
-
let packageInstalled = alreadyInstalled;
|
|
34
|
-
if (!alreadyInstalled && !deps.skipInstall) {
|
|
35
|
-
const argv = installArgv(pm);
|
|
36
|
-
const ok = await confirm(`Install @ceraph/react-native-mcp with ${pm}?`, { ...deps, defaultYes: true });
|
|
37
|
-
if (ok) {
|
|
38
|
-
say(fmt(`Running: ${argv.bin} ${argv.args.join(" ")}`));
|
|
39
|
-
const installer = deps.installPackage ?? defaultInstallPackage;
|
|
40
|
-
const res = await installer({
|
|
41
|
-
bin: argv.bin,
|
|
42
|
-
args: argv.args,
|
|
43
|
-
cwd: workingDir,
|
|
44
|
-
});
|
|
45
|
-
if (res.exitCode !== 0) {
|
|
46
|
-
throw new Error(`${pm} install exited with code ${res.exitCode}. ` +
|
|
47
|
-
`Fix the error above and re-run \`npx @ceraph/react-native-mcp init\`.`);
|
|
48
|
-
}
|
|
49
|
-
packageInstalled = true;
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
say(fmt(`Skipped install — add @ceraph/react-native-mcp manually later.`));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else if (alreadyInstalled) {
|
|
56
|
-
say(fmt(`@ceraph/react-native-mcp already in package.json`));
|
|
57
|
-
}
|
|
58
|
-
const loadAuth = deps.loadAuth ?? readExistingAuth;
|
|
59
|
-
const existing = await loadAuth();
|
|
60
|
-
let auth = existing;
|
|
61
|
-
if (existing) {
|
|
62
|
-
say(fmt(`Authenticated as ${existing.githubUser.login} (GitHub) [cached]`));
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
say(fmt(`GitHub authentication required. Starting device flow…`));
|
|
66
|
-
const authenticate = deps.authenticate ?? defaultAuthenticate;
|
|
67
|
-
auth = await authenticate();
|
|
68
|
-
say(fmt(`Authenticated as ${auth.githubUser.login} (GitHub)`));
|
|
69
|
-
}
|
|
70
|
-
const scan = await scanCameraViews(workingDir);
|
|
71
|
-
const fileSnapshots = new Map();
|
|
72
|
-
let cameraEdits = { filesEdited: [], totalReplacements: 0 };
|
|
73
|
-
if (scan.edits.length === 0) {
|
|
74
|
-
say(fmt(`No <CameraView> usages found in app/, src/, screens/, App.tsx`));
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
say(fmt(`Replacing ${scan.edits.length} <CameraView> usage(s) across ${scan.filesScanned} file(s):`));
|
|
78
|
-
for (const edit of scan.edits) {
|
|
79
|
-
say(fmt(` - ${edit.relPath}:${edit.line} → <CeraphCamera />${edit.alreadyHasImageKey ? " (existing imageKey preserved)" : ""}`));
|
|
80
|
-
}
|
|
81
|
-
cameraEdits = await applyEditsWithSnapshot(workingDir, scan.edits, fileSnapshots, deps, { omitImageKey: true });
|
|
82
|
-
}
|
|
83
|
-
const target = await detectRootComponent(workingDir);
|
|
84
|
-
let layoutInjection = {
|
|
85
|
-
applied: false,
|
|
86
|
-
target,
|
|
87
|
-
};
|
|
88
|
-
if (!target) {
|
|
89
|
-
say(fmt(`Could not find app/_layout.tsx or App.tsx. ` +
|
|
90
|
-
`Add \`useEffect(() => { installCeraph(); }, [])\` to your root component manually.`));
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
const ok = await confirm(`Wire installCeraph() into ${displayPath(workingDir, target.filePath)}?`, { ...deps, defaultYes: true });
|
|
94
|
-
if (ok) {
|
|
95
|
-
const snapshot = await snapshotLayout(target);
|
|
96
|
-
try {
|
|
97
|
-
const result = await injectInstallCeraph(target);
|
|
98
|
-
layoutInjection = {
|
|
99
|
-
applied: result.applied,
|
|
100
|
-
target,
|
|
101
|
-
reason: result.reason,
|
|
102
|
-
};
|
|
103
|
-
if (result.applied) {
|
|
104
|
-
say(fmt(`installCeraph() wired in ${displayPath(workingDir, target.filePath)}`));
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
say(fmt(`Skipped (auto-injection): ${result.reason ?? "unknown reason"}`));
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
if (snapshot != null)
|
|
112
|
-
await restoreLayout(target, snapshot);
|
|
113
|
-
throw err;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
say(fmt(`Skipped installCeraph() injection.`));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
let urlScheme = null;
|
|
121
|
-
const detectUrlScheme = deps.ensureUrlScheme ?? ensureCeraphUrlScheme;
|
|
122
|
-
const schemeResult = await detectUrlScheme(workingDir);
|
|
123
|
-
urlScheme = schemeResult;
|
|
124
|
-
switch (schemeResult.kind) {
|
|
125
|
-
case "expo-json-added": {
|
|
126
|
-
const prev = schemeResult.previousScheme;
|
|
127
|
-
const prevDisplay = prev === undefined
|
|
128
|
-
? "(none)"
|
|
129
|
-
: Array.isArray(prev)
|
|
130
|
-
? `[${prev.join(", ")}]`
|
|
131
|
-
: prev;
|
|
132
|
-
say(fmt(`Registered ceraph:// URL scheme in ${displayPath(workingDir, schemeResult.path)} (was: ${prevDisplay})`));
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
case "expo-json-already-registered":
|
|
136
|
-
say(fmt(`ceraph:// URL scheme already registered in ${displayPath(workingDir, schemeResult.path)}`));
|
|
137
|
-
break;
|
|
138
|
-
case "expo-config-needs-manual":
|
|
139
|
-
say(fmt(`Detected ${displayPath(workingDir, schemeResult.path)} (dynamic Expo config).`));
|
|
140
|
-
say(expoDynamicConfigInstructions(displayPath(workingDir, schemeResult.path)));
|
|
141
|
-
break;
|
|
142
|
-
case "bare-rn-needs-manual":
|
|
143
|
-
say(fmt(`Detected bare React Native project (ios/ and/or android/).`));
|
|
144
|
-
say(bareRnInstructions());
|
|
145
|
-
break;
|
|
146
|
-
case "no-rn-project":
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
const detectCandidates = deps.detectLanIpCandidates ?? detectMacLanIpCandidates;
|
|
150
|
-
const writeSignalHost = deps.writeSignalHostEnv ?? writeSignalHostEnv;
|
|
151
|
-
let signalHost = null;
|
|
152
|
-
try {
|
|
153
|
-
const candidates = detectCandidates();
|
|
154
|
-
if (candidates.length === 0) {
|
|
155
|
-
say(fmt(`Could not auto-detect a LAN IP for CERAPH_SIGNAL_HOST. ` +
|
|
156
|
-
`Real-device signals from the shim need the Mac's reachable IP — ` +
|
|
157
|
-
`set it manually with: export CERAPH_SIGNAL_HOST=<your-mac-ip>`));
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
const picked = candidates[0];
|
|
161
|
-
if (candidates.length > 1) {
|
|
162
|
-
const summary = candidates
|
|
163
|
-
.map((c) => `${c.interfaceName} ${c.address} (${c.kind})`)
|
|
164
|
-
.join(", ");
|
|
165
|
-
say(fmt(`Multiple LAN interfaces detected (${summary}). ` +
|
|
166
|
-
`Using ${picked.interfaceName} ${picked.address} as CERAPH_SIGNAL_HOST.`));
|
|
167
|
-
}
|
|
168
|
-
const profile = await writeSignalHost(picked.address);
|
|
169
|
-
signalHost = {
|
|
170
|
-
ip: picked.address,
|
|
171
|
-
kind: picked.kind,
|
|
172
|
-
interfaceName: picked.interfaceName,
|
|
173
|
-
profile,
|
|
174
|
-
otherCandidates: candidates.slice(1),
|
|
175
|
-
};
|
|
176
|
-
if (profile.state === "unchanged") {
|
|
177
|
-
say(fmt(`CERAPH_SIGNAL_HOST already set to ${picked.address} in ${profile.path}.`));
|
|
178
|
-
}
|
|
179
|
-
else if (profile.state === "added") {
|
|
180
|
-
say(fmt(`Wrote CERAPH_SIGNAL_HOST=${picked.address} to ${profile.path} ` +
|
|
181
|
-
`(${profile.shell}). Restart your shell or run \`source ${profile.path}\` to pick it up.`));
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
say(fmt(`Updated CERAPH_SIGNAL_HOST to ${picked.address} in ${profile.path}. ` +
|
|
185
|
-
`Restart your shell or run \`source ${profile.path}\` to pick it up.`));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
catch (err) {
|
|
190
|
-
say(fmt(`Could not write CERAPH_SIGNAL_HOST: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
191
|
-
`Set it manually with: export CERAPH_SIGNAL_HOST=<your-mac-ip>`));
|
|
192
|
-
}
|
|
193
|
-
let cameraRegistrySync = {
|
|
194
|
-
state: "missing-dir",
|
|
195
|
-
registered: 0,
|
|
196
|
-
};
|
|
197
|
-
if (cameraEdits.totalReplacements > 0) {
|
|
198
|
-
const sync = await syncCameraRegistry(workingDir);
|
|
199
|
-
cameraRegistrySync = {
|
|
200
|
-
state: sync.writtenOrUnchanged,
|
|
201
|
-
registered: sync.registered.length,
|
|
202
|
-
};
|
|
203
|
-
if (sync.writtenOrUnchanged === "missing-dir") {
|
|
204
|
-
say(fmt(`Test images: call \`ceraph_add_camera_image\` (writes + auto-syncs the registry), or drop files into ${join(".ceraph", "camera-images")}/ and call \`rn_sync_camera_registry\` to regenerate.`));
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
say(fmt(`Test image registry ${sync.writtenOrUnchanged} (${sync.registered.length} image(s)) at ${relativeRegistryPath(workingDir, sync.registryPath)}.`));
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
let preflight = null;
|
|
211
|
-
if (deps.runPreflight) {
|
|
212
|
-
preflight = await deps.runPreflight();
|
|
213
|
-
say(fmt(`Preflight: ${preflight.passedCount}/${preflight.totalCount} ${preflight.ok ? "passed" : "failed"}`));
|
|
214
|
-
if (preflight.summary)
|
|
215
|
-
say(preflight.summary);
|
|
216
|
-
}
|
|
217
|
-
say("\nNext: ask Claude Code (or your MCP client):\n");
|
|
218
|
-
say(` \"Run ceraph_init to confirm the project is wired up, then run rn_preflight.\"\n\n`);
|
|
219
|
-
const cameras = scan.edits.map((e) => ({
|
|
220
|
-
file: e.relPath,
|
|
221
|
-
line: e.line,
|
|
222
|
-
suggestedKey: e.suggestedKey,
|
|
223
|
-
}));
|
|
224
|
-
const agentHint = deps.agentMode
|
|
225
|
-
? {
|
|
226
|
-
cameras,
|
|
227
|
-
setupRequired: cameras.length > 0,
|
|
228
|
-
hintText: cameras.length === 0
|
|
229
|
-
? "No <CeraphCamera> components detected. No camera-setup conversation needed; proceed with the rest of the project work."
|
|
230
|
-
: "For each <CeraphCamera> below, ask the user which scenario this " +
|
|
231
|
-
"camera serves and call ceraph_init_replace_camera with the " +
|
|
232
|
-
"chosen imageKey. Three options: " +
|
|
233
|
-
"(A) imageKey=\"default\" — bundled black PNG fallback. Right " +
|
|
234
|
-
"answer when the test doesn't care about image content (most " +
|
|
235
|
-
"form-submit / file-upload flows). " +
|
|
236
|
-
"(B) imageKey=\"<key>\" — a specific test image. Ask the user " +
|
|
237
|
-
"for a descriptive lowercase-dashes name AND drop the file at " +
|
|
238
|
-
".ceraph/camera-images/<key>.{jpg,png,webp}, then call " +
|
|
239
|
-
"rn_sync_camera_registry. " +
|
|
240
|
-
"(C) imageKey=\"@runtime\" — the camera serves multiple " +
|
|
241
|
-
"scenarios per flow; the planner picks the image per-step at " +
|
|
242
|
-
"runtime. Use when one camera screen feeds different outcomes.",
|
|
243
|
-
}
|
|
244
|
-
: null;
|
|
245
|
-
if (deps.agentMode && agentHint) {
|
|
246
|
-
say(`${AGENT_HINT_SENTINEL}${JSON.stringify(agentHint)}\n`);
|
|
247
|
-
}
|
|
248
|
-
return {
|
|
249
|
-
packageInstalled,
|
|
250
|
-
packageManager: pm,
|
|
251
|
-
authLogin: auth?.githubUser.login ?? null,
|
|
252
|
-
cameraEdits,
|
|
253
|
-
layoutInjection,
|
|
254
|
-
cameraRegistrySync,
|
|
255
|
-
preflight,
|
|
256
|
-
workingDir,
|
|
257
|
-
urlScheme,
|
|
258
|
-
signalHost,
|
|
259
|
-
agentHint,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
async function resolveWorkingDir(projectDir, deps) {
|
|
263
|
-
const detect = deps.detectMonorepo ?? detectMonorepoSubpackages;
|
|
264
|
-
const detection = await detect(projectDir);
|
|
265
|
-
if (detection.kind === "none") {
|
|
266
|
-
if (detection.isMonorepo && !detection.rootIsRnApp) {
|
|
267
|
-
throw new Error("Detected a monorepo at the current directory but could not find " +
|
|
268
|
-
"a React Native or Expo subpackage. Re-run `npx @ceraph/react-native-mcp init` " +
|
|
269
|
-
"from inside the RN/Expo app folder, or add the app to your workspaces.");
|
|
270
|
-
}
|
|
271
|
-
return projectDir;
|
|
272
|
-
}
|
|
273
|
-
if (detection.kind === "single") {
|
|
274
|
-
const match = detection.matches[0];
|
|
275
|
-
if (!match)
|
|
276
|
-
return projectDir;
|
|
277
|
-
print(fmt(`Detected React Native app at ${match.relPath}/ — working from there`), deps);
|
|
278
|
-
process.chdir(match.absPath);
|
|
279
|
-
return match.absPath;
|
|
280
|
-
}
|
|
281
|
-
print(fmt(`Multiple React Native subpackages detected:`), deps);
|
|
282
|
-
const options = detection.matches.map((m) => `${m.relPath}/ [${m.signals.join(", ")}]`);
|
|
283
|
-
const picked = await chooseFromList(`Which one should init configure?`, options, deps);
|
|
284
|
-
if (picked == null) {
|
|
285
|
-
throw new Error("No valid selection. Re-run init and pick a numbered option.");
|
|
286
|
-
}
|
|
287
|
-
const choice = detection.matches[picked];
|
|
288
|
-
if (!choice) {
|
|
289
|
-
throw new Error("Picked index out of range.");
|
|
290
|
-
}
|
|
291
|
-
print(fmt(`Working from ${choice.relPath}/`), deps);
|
|
292
|
-
process.chdir(choice.absPath);
|
|
293
|
-
return choice.absPath;
|
|
294
|
-
}
|
|
295
|
-
function displayPath(projectDir, absPath) {
|
|
296
|
-
if (absPath.startsWith(projectDir)) {
|
|
297
|
-
const rel = absPath.slice(projectDir.length + 1);
|
|
298
|
-
return rel.length > 0 ? rel : absPath;
|
|
299
|
-
}
|
|
300
|
-
return absPath;
|
|
301
|
-
}
|
|
302
|
-
async function defaultAuthenticate() {
|
|
303
|
-
let printedTick = false;
|
|
304
|
-
return pollUntilToken({
|
|
305
|
-
onPrompt: (codes) => {
|
|
306
|
-
print(fmt(`Visit ${codes.verificationUri}`));
|
|
307
|
-
print(fmt(`Enter code: ${codes.userCode}`));
|
|
308
|
-
},
|
|
309
|
-
onTick: (remaining) => {
|
|
310
|
-
if (!printedTick && remaining < 60) {
|
|
311
|
-
printedTick = true;
|
|
312
|
-
print(fmt(`(Authorization pending — ~${remaining}s left)`));
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
async function applyEditsWithSnapshot(projectDir, edits, fileSnapshots, deps, options = {}) {
|
|
318
|
-
for (const e of edits) {
|
|
319
|
-
if (!fileSnapshots.has(e.filePath)) {
|
|
320
|
-
fileSnapshots.set(e.filePath, await snapshotFile(e.filePath));
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
try {
|
|
324
|
-
const res = await applyCameraEdits(projectDir, edits, options);
|
|
325
|
-
print(fmt(`Replaced ${res.totalReplacements} <CameraView> → <CeraphCamera> across ${res.filesEdited.length} file(s)`), deps);
|
|
326
|
-
return res;
|
|
327
|
-
}
|
|
328
|
-
catch (err) {
|
|
329
|
-
for (const [filePath, snap] of fileSnapshots) {
|
|
330
|
-
if (snap != null) {
|
|
331
|
-
try {
|
|
332
|
-
await restoreFile(filePath, snap);
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
throw err;
|
|
339
|
-
}
|
|
340
|
-
}
|
package/dist/init.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export declare function setupJsonMcp(configPath: string, label: string): Promise<boolean>;
|
|
3
|
-
export declare function setupClaudeHook(projectDir?: string): Promise<void>;
|
|
4
|
-
export declare function setupMcpClients(): Promise<void>;
|
|
5
|
-
export interface RunInitOptions {
|
|
6
|
-
agentMode?: boolean;
|
|
7
|
-
}
|
|
8
|
-
export declare function runInit(opts?: RunInitOptions): Promise<void>;
|