@react-grab/cli 0.1.0-beta.4 → 0.1.0-beta.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/dist/cli.cjs +1982 -1929
- package/dist/cli.js +1983 -1930
- package/package.json +2 -3
package/dist/cli.js
CHANGED
|
@@ -3,13 +3,12 @@ import { Command } from 'commander';
|
|
|
3
3
|
import e from 'assert';
|
|
4
4
|
import pc from 'picocolors';
|
|
5
5
|
import prompts3 from 'prompts';
|
|
6
|
-
import {
|
|
7
|
-
import { rmSync, mkdirSync, writeFileSync, readFileSync, existsSync, accessSync, constants, readdirSync } from 'fs';
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync, accessSync, constants, readdirSync } from 'fs';
|
|
8
7
|
import { join, dirname, basename } from 'path';
|
|
9
|
-
import {
|
|
8
|
+
import { execSync } from 'child_process';
|
|
10
9
|
import ora from 'ora';
|
|
10
|
+
import { detect } from '@antfu/ni';
|
|
11
11
|
import { createRequire } from 'module';
|
|
12
|
-
import { fileURLToPath } from 'url';
|
|
13
12
|
import { chromium } from 'playwright-core';
|
|
14
13
|
import { findInstalledBrowsers, BROWSER_DISPLAY_NAMES, dumpCookies, COOKIE_PREVIEW_LIMIT, DEFAULT_SERVER_PORT, isServerRunning, getServerInfo, spawnServer, serve, toPlaywrightCookies, applyStealthScripts, deleteServerInfo, DEFAULT_NAVIGATION_TIMEOUT_MS, findPageByTargetId, isLinux, installLinuxDeps, getDefaultBrowser, SUPPORTED_BROWSERS, isServerHealthy, stopServer, getSnapshotScript } from '@react-grab/browser';
|
|
15
14
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
@@ -1050,723 +1049,213 @@ function h(e3, t2, n2) {
|
|
|
1050
1049
|
n2.set(t2, e3);
|
|
1051
1050
|
for (let r2 of e3.commands) r2.name() !== `complete` && h(r2, t2 ? `${t2} ${r2.name()}` : r2.name(), n2);
|
|
1052
1051
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1052
|
+
|
|
1053
|
+
// src/utils/templates.ts
|
|
1054
|
+
var AGENTS = [
|
|
1055
|
+
"claude-code",
|
|
1056
|
+
"cursor",
|
|
1057
|
+
"opencode",
|
|
1058
|
+
"codex",
|
|
1059
|
+
"gemini",
|
|
1060
|
+
"amp",
|
|
1061
|
+
"ami",
|
|
1062
|
+
"visual-edit"
|
|
1063
|
+
];
|
|
1064
|
+
var AGENT_NAMES = {
|
|
1065
|
+
"claude-code": "Claude Code",
|
|
1066
|
+
cursor: "Cursor",
|
|
1067
|
+
opencode: "OpenCode",
|
|
1068
|
+
codex: "Codex",
|
|
1069
|
+
gemini: "Gemini",
|
|
1070
|
+
amp: "Amp",
|
|
1071
|
+
ami: "Ami",
|
|
1072
|
+
"visual-edit": "Visual Edit"
|
|
1059
1073
|
};
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1074
|
+
AGENTS.filter((agent) => agent !== "ami").map(
|
|
1075
|
+
(agent) => `@react-grab/${agent}`
|
|
1076
|
+
);
|
|
1077
|
+
var NEXT_APP_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
|
|
1078
|
+
<Script
|
|
1079
|
+
src="//unpkg.com/react-grab/dist/index.global.js"
|
|
1080
|
+
crossOrigin="anonymous"
|
|
1081
|
+
strategy="beforeInteractive"
|
|
1082
|
+
/>
|
|
1083
|
+
)}`;
|
|
1084
|
+
var NEXT_APP_ROUTER_SCRIPT_WITH_AGENT = (agent) => {
|
|
1085
|
+
if (agent === "none") return NEXT_APP_ROUTER_SCRIPT;
|
|
1086
|
+
return `{process.env.NODE_ENV === "development" && (
|
|
1087
|
+
<Script
|
|
1088
|
+
src="//unpkg.com/react-grab/dist/index.global.js"
|
|
1089
|
+
crossOrigin="anonymous"
|
|
1090
|
+
strategy="beforeInteractive"
|
|
1091
|
+
/>
|
|
1092
|
+
)}
|
|
1093
|
+
{process.env.NODE_ENV === "development" && (
|
|
1094
|
+
<Script
|
|
1095
|
+
src="//unpkg.com/@react-grab/${agent}/dist/client.global.js"
|
|
1096
|
+
strategy="lazyOnload"
|
|
1097
|
+
/>
|
|
1098
|
+
)}`;
|
|
1099
|
+
};
|
|
1100
|
+
var NEXT_PAGES_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
|
|
1101
|
+
<Script
|
|
1102
|
+
src="//unpkg.com/react-grab/dist/index.global.js"
|
|
1103
|
+
crossOrigin="anonymous"
|
|
1104
|
+
strategy="beforeInteractive"
|
|
1105
|
+
/>
|
|
1106
|
+
)}`;
|
|
1107
|
+
var NEXT_PAGES_ROUTER_SCRIPT_WITH_AGENT = (agent) => {
|
|
1108
|
+
if (agent === "none") return NEXT_PAGES_ROUTER_SCRIPT;
|
|
1109
|
+
return `{process.env.NODE_ENV === "development" && (
|
|
1110
|
+
<Script
|
|
1111
|
+
src="//unpkg.com/react-grab/dist/index.global.js"
|
|
1112
|
+
crossOrigin="anonymous"
|
|
1113
|
+
strategy="beforeInteractive"
|
|
1114
|
+
/>
|
|
1115
|
+
)}
|
|
1116
|
+
{process.env.NODE_ENV === "development" && (
|
|
1117
|
+
<Script
|
|
1118
|
+
src="//unpkg.com/@react-grab/${agent}/dist/client.global.js"
|
|
1119
|
+
strategy="lazyOnload"
|
|
1120
|
+
/>
|
|
1121
|
+
)}`;
|
|
1122
|
+
};
|
|
1123
|
+
var VITE_SCRIPT = `<script type="module">
|
|
1124
|
+
if (import.meta.env.DEV) {
|
|
1125
|
+
import("react-grab");
|
|
1126
|
+
}
|
|
1127
|
+
</script>`;
|
|
1128
|
+
var VITE_SCRIPT_WITH_AGENT = (agent) => {
|
|
1129
|
+
if (agent === "none") return VITE_SCRIPT;
|
|
1130
|
+
return `<script type="module">
|
|
1131
|
+
if (import.meta.env.DEV) {
|
|
1132
|
+
import("react-grab");
|
|
1133
|
+
import("@react-grab/${agent}/client");
|
|
1134
|
+
}
|
|
1135
|
+
</script>`;
|
|
1136
|
+
};
|
|
1137
|
+
var WEBPACK_IMPORT = `if (process.env.NODE_ENV === "development") {
|
|
1138
|
+
import("react-grab");
|
|
1139
|
+
}`;
|
|
1140
|
+
var WEBPACK_IMPORT_WITH_AGENT = (agent) => {
|
|
1141
|
+
if (agent === "none") return WEBPACK_IMPORT;
|
|
1142
|
+
return `if (process.env.NODE_ENV === "development") {
|
|
1143
|
+
import("react-grab");
|
|
1144
|
+
import("@react-grab/${agent}/client");
|
|
1145
|
+
}`;
|
|
1146
|
+
};
|
|
1147
|
+
var SCRIPT_IMPORT = 'import Script from "next/script";';
|
|
1148
|
+
var MCP_CLIENTS = [
|
|
1149
|
+
"cursor",
|
|
1150
|
+
"claude-code",
|
|
1151
|
+
"vscode",
|
|
1152
|
+
"opencode",
|
|
1153
|
+
"codex",
|
|
1154
|
+
"gemini-cli",
|
|
1155
|
+
// "cline",
|
|
1156
|
+
// "roo-cline",
|
|
1157
|
+
"windsurf",
|
|
1158
|
+
"zed",
|
|
1159
|
+
// "warp",
|
|
1160
|
+
"droid"
|
|
1161
|
+
// "claude",
|
|
1162
|
+
];
|
|
1163
|
+
var MCP_CLIENT_NAMES = {
|
|
1164
|
+
"cursor": "Cursor",
|
|
1165
|
+
"claude-code": "Claude Code",
|
|
1166
|
+
"vscode": "VSCode",
|
|
1167
|
+
"opencode": "OpenCode",
|
|
1168
|
+
"codex": "Codex",
|
|
1169
|
+
"gemini-cli": "Gemini CLI",
|
|
1170
|
+
// "cline": "Cline",
|
|
1171
|
+
// "roo-cline": "Roo Cline",
|
|
1172
|
+
"windsurf": "Windsurf",
|
|
1173
|
+
"zed": "Zed",
|
|
1174
|
+
// "warp": "Warp",
|
|
1175
|
+
"droid": "Droid"
|
|
1176
|
+
// "claude": "Claude Desktop",
|
|
1177
|
+
};
|
|
1178
|
+
var SKILL_AGENTS = [
|
|
1179
|
+
{ id: "opencode", name: "OpenCode", folder: ".opencode" },
|
|
1180
|
+
{ id: "claude-code", name: "Claude Code", folder: ".claude" },
|
|
1181
|
+
{ id: "codex", name: "Codex", folder: ".codex" },
|
|
1182
|
+
{ id: "cursor", name: "Cursor", folder: ".cursor" },
|
|
1183
|
+
{ id: "vscode", name: "VSCode", folder: ".github" }
|
|
1184
|
+
];
|
|
1185
|
+
|
|
1186
|
+
// src/utils/transform.ts
|
|
1187
|
+
var hasReactGrabCode = (content) => {
|
|
1188
|
+
const fuzzyPatterns = [
|
|
1189
|
+
/["'`][^"'`]*react-grab/,
|
|
1190
|
+
/react-grab[^"'`]*["'`]/,
|
|
1191
|
+
/<[^>]*react-grab/i,
|
|
1192
|
+
/import[^;]*react-grab/i,
|
|
1193
|
+
/require[^)]*react-grab/i,
|
|
1194
|
+
/from\s+[^;]*react-grab/i,
|
|
1195
|
+
/src[^>]*react-grab/i,
|
|
1196
|
+
/href[^>]*react-grab/i
|
|
1197
|
+
];
|
|
1198
|
+
return fuzzyPatterns.some((pattern) => pattern.test(content));
|
|
1199
|
+
};
|
|
1200
|
+
var findLayoutFile = (projectRoot) => {
|
|
1201
|
+
const possiblePaths = [
|
|
1202
|
+
join(projectRoot, "app", "layout.tsx"),
|
|
1203
|
+
join(projectRoot, "app", "layout.jsx"),
|
|
1204
|
+
join(projectRoot, "src", "app", "layout.tsx"),
|
|
1205
|
+
join(projectRoot, "src", "app", "layout.jsx")
|
|
1206
|
+
];
|
|
1207
|
+
for (const filePath of possiblePaths) {
|
|
1208
|
+
if (existsSync(filePath)) {
|
|
1209
|
+
return filePath;
|
|
1079
1210
|
}
|
|
1080
|
-
return "unknown";
|
|
1081
|
-
} catch {
|
|
1082
|
-
return "unknown";
|
|
1083
1211
|
}
|
|
1212
|
+
return null;
|
|
1084
1213
|
};
|
|
1085
|
-
var
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1214
|
+
var findInstrumentationFile = (projectRoot) => {
|
|
1215
|
+
const possiblePaths = [
|
|
1216
|
+
join(projectRoot, "instrumentation-client.ts"),
|
|
1217
|
+
join(projectRoot, "instrumentation-client.js"),
|
|
1218
|
+
join(projectRoot, "src", "instrumentation-client.ts"),
|
|
1219
|
+
join(projectRoot, "src", "instrumentation-client.js")
|
|
1220
|
+
];
|
|
1221
|
+
for (const filePath of possiblePaths) {
|
|
1222
|
+
if (existsSync(filePath)) {
|
|
1223
|
+
return filePath;
|
|
1224
|
+
}
|
|
1095
1225
|
}
|
|
1096
|
-
return
|
|
1226
|
+
return null;
|
|
1097
1227
|
};
|
|
1098
|
-
var
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1228
|
+
var hasReactGrabInInstrumentation = (projectRoot) => {
|
|
1229
|
+
const instrumentationPath = findInstrumentationFile(projectRoot);
|
|
1230
|
+
if (!instrumentationPath) return false;
|
|
1231
|
+
const content = readFileSync(instrumentationPath, "utf-8");
|
|
1232
|
+
return hasReactGrabCode(content);
|
|
1233
|
+
};
|
|
1234
|
+
var findDocumentFile = (projectRoot) => {
|
|
1235
|
+
const possiblePaths = [
|
|
1236
|
+
join(projectRoot, "pages", "_document.tsx"),
|
|
1237
|
+
join(projectRoot, "pages", "_document.jsx"),
|
|
1238
|
+
join(projectRoot, "src", "pages", "_document.tsx"),
|
|
1239
|
+
join(projectRoot, "src", "pages", "_document.jsx")
|
|
1240
|
+
];
|
|
1241
|
+
for (const filePath of possiblePaths) {
|
|
1242
|
+
if (existsSync(filePath)) {
|
|
1243
|
+
return filePath;
|
|
1244
|
+
}
|
|
1104
1245
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1246
|
+
return null;
|
|
1247
|
+
};
|
|
1248
|
+
var findIndexHtml = (projectRoot) => {
|
|
1249
|
+
const possiblePaths = [
|
|
1250
|
+
join(projectRoot, "index.html"),
|
|
1251
|
+
join(projectRoot, "public", "index.html")
|
|
1252
|
+
];
|
|
1253
|
+
for (const filePath of possiblePaths) {
|
|
1254
|
+
if (existsSync(filePath)) {
|
|
1255
|
+
return filePath;
|
|
1114
1256
|
}
|
|
1115
1257
|
}
|
|
1116
|
-
return
|
|
1117
|
-
};
|
|
1118
|
-
var getWorkspacePatterns = (projectRoot) => {
|
|
1119
|
-
const patterns = [];
|
|
1120
|
-
const pnpmWorkspacePath = join(projectRoot, "pnpm-workspace.yaml");
|
|
1121
|
-
if (existsSync(pnpmWorkspacePath)) {
|
|
1122
|
-
const content = readFileSync(pnpmWorkspacePath, "utf-8");
|
|
1123
|
-
const lines = content.split("\n");
|
|
1124
|
-
let inPackages = false;
|
|
1125
|
-
for (const line of lines) {
|
|
1126
|
-
if (line.match(/^packages:\s*$/)) {
|
|
1127
|
-
inPackages = true;
|
|
1128
|
-
continue;
|
|
1129
|
-
}
|
|
1130
|
-
if (inPackages) {
|
|
1131
|
-
if (line.match(/^[a-zA-Z]/) || line.trim() === "") {
|
|
1132
|
-
if (line.match(/^[a-zA-Z]/)) inPackages = false;
|
|
1133
|
-
continue;
|
|
1134
|
-
}
|
|
1135
|
-
const match = line.match(/^\s*-\s*['"]?([^'"#\n]+?)['"]?\s*$/);
|
|
1136
|
-
if (match) {
|
|
1137
|
-
patterns.push(match[1].trim());
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
const lernaJsonPath = join(projectRoot, "lerna.json");
|
|
1143
|
-
if (existsSync(lernaJsonPath)) {
|
|
1144
|
-
try {
|
|
1145
|
-
const lernaJson = JSON.parse(readFileSync(lernaJsonPath, "utf-8"));
|
|
1146
|
-
if (Array.isArray(lernaJson.packages)) {
|
|
1147
|
-
patterns.push(...lernaJson.packages);
|
|
1148
|
-
}
|
|
1149
|
-
} catch {
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
const packageJsonPath = join(projectRoot, "package.json");
|
|
1153
|
-
if (existsSync(packageJsonPath)) {
|
|
1154
|
-
try {
|
|
1155
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1156
|
-
if (Array.isArray(packageJson.workspaces)) {
|
|
1157
|
-
patterns.push(...packageJson.workspaces);
|
|
1158
|
-
} else if (packageJson.workspaces?.packages) {
|
|
1159
|
-
patterns.push(...packageJson.workspaces.packages);
|
|
1160
|
-
}
|
|
1161
|
-
} catch {
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
return [...new Set(patterns)];
|
|
1165
|
-
};
|
|
1166
|
-
var expandWorkspacePattern = (projectRoot, pattern) => {
|
|
1167
|
-
const results = [];
|
|
1168
|
-
const cleanPattern = pattern.replace(/\/\*$/, "");
|
|
1169
|
-
const basePath = join(projectRoot, cleanPattern);
|
|
1170
|
-
if (!existsSync(basePath)) return results;
|
|
1171
|
-
try {
|
|
1172
|
-
const entries = readdirSync(basePath, { withFileTypes: true });
|
|
1173
|
-
for (const entry of entries) {
|
|
1174
|
-
if (entry.isDirectory()) {
|
|
1175
|
-
const packageJsonPath = join(basePath, entry.name, "package.json");
|
|
1176
|
-
if (existsSync(packageJsonPath)) {
|
|
1177
|
-
results.push(join(basePath, entry.name));
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
} catch {
|
|
1182
|
-
return results;
|
|
1183
|
-
}
|
|
1184
|
-
return results;
|
|
1185
|
-
};
|
|
1186
|
-
var hasReactDependency = (projectPath) => {
|
|
1187
|
-
const packageJsonPath = join(projectPath, "package.json");
|
|
1188
|
-
if (!existsSync(packageJsonPath)) return false;
|
|
1189
|
-
try {
|
|
1190
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1191
|
-
const allDeps = {
|
|
1192
|
-
...packageJson.dependencies,
|
|
1193
|
-
...packageJson.devDependencies
|
|
1194
|
-
};
|
|
1195
|
-
return Boolean(allDeps["react"] || allDeps["react-dom"]);
|
|
1196
|
-
} catch {
|
|
1197
|
-
return false;
|
|
1198
|
-
}
|
|
1199
|
-
};
|
|
1200
|
-
var findWorkspaceProjects = (projectRoot) => {
|
|
1201
|
-
const patterns = getWorkspacePatterns(projectRoot);
|
|
1202
|
-
const projects = [];
|
|
1203
|
-
for (const pattern of patterns) {
|
|
1204
|
-
const projectPaths = expandWorkspacePattern(projectRoot, pattern);
|
|
1205
|
-
for (const projectPath of projectPaths) {
|
|
1206
|
-
const framework = detectFramework(projectPath);
|
|
1207
|
-
const hasReact = hasReactDependency(projectPath);
|
|
1208
|
-
if (hasReact || framework !== "unknown") {
|
|
1209
|
-
const packageJsonPath = join(projectPath, "package.json");
|
|
1210
|
-
let name = basename(projectPath);
|
|
1211
|
-
try {
|
|
1212
|
-
const packageJson = JSON.parse(
|
|
1213
|
-
readFileSync(packageJsonPath, "utf-8")
|
|
1214
|
-
);
|
|
1215
|
-
name = packageJson.name || name;
|
|
1216
|
-
} catch {
|
|
1217
|
-
}
|
|
1218
|
-
projects.push({
|
|
1219
|
-
name,
|
|
1220
|
-
path: projectPath,
|
|
1221
|
-
framework,
|
|
1222
|
-
hasReact
|
|
1223
|
-
});
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
return projects;
|
|
1228
|
-
};
|
|
1229
|
-
var hasReactGrabInFile = (filePath) => {
|
|
1230
|
-
if (!existsSync(filePath)) return false;
|
|
1231
|
-
try {
|
|
1232
|
-
const content = readFileSync(filePath, "utf-8");
|
|
1233
|
-
const fuzzyPatterns = [
|
|
1234
|
-
/["'`][^"'`]*react-grab/,
|
|
1235
|
-
/react-grab[^"'`]*["'`]/,
|
|
1236
|
-
/<[^>]*react-grab/i,
|
|
1237
|
-
/import[^;]*react-grab/i,
|
|
1238
|
-
/require[^)]*react-grab/i,
|
|
1239
|
-
/from\s+[^;]*react-grab/i,
|
|
1240
|
-
/src[^>]*react-grab/i
|
|
1241
|
-
];
|
|
1242
|
-
return fuzzyPatterns.some((pattern) => pattern.test(content));
|
|
1243
|
-
} catch {
|
|
1244
|
-
return false;
|
|
1245
|
-
}
|
|
1246
|
-
};
|
|
1247
|
-
var detectReactGrab = (projectRoot) => {
|
|
1248
|
-
const packageJsonPath = join(projectRoot, "package.json");
|
|
1249
|
-
if (existsSync(packageJsonPath)) {
|
|
1250
|
-
try {
|
|
1251
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1252
|
-
const allDependencies = {
|
|
1253
|
-
...packageJson.dependencies,
|
|
1254
|
-
...packageJson.devDependencies
|
|
1255
|
-
};
|
|
1256
|
-
if (allDependencies["react-grab"]) {
|
|
1257
|
-
return true;
|
|
1258
|
-
}
|
|
1259
|
-
} catch {
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
const filesToCheck = [
|
|
1263
|
-
join(projectRoot, "app", "layout.tsx"),
|
|
1264
|
-
join(projectRoot, "app", "layout.jsx"),
|
|
1265
|
-
join(projectRoot, "src", "app", "layout.tsx"),
|
|
1266
|
-
join(projectRoot, "src", "app", "layout.jsx"),
|
|
1267
|
-
join(projectRoot, "pages", "_document.tsx"),
|
|
1268
|
-
join(projectRoot, "pages", "_document.jsx"),
|
|
1269
|
-
join(projectRoot, "instrumentation-client.ts"),
|
|
1270
|
-
join(projectRoot, "instrumentation-client.js"),
|
|
1271
|
-
join(projectRoot, "src", "instrumentation-client.ts"),
|
|
1272
|
-
join(projectRoot, "src", "instrumentation-client.js"),
|
|
1273
|
-
join(projectRoot, "index.html"),
|
|
1274
|
-
join(projectRoot, "public", "index.html"),
|
|
1275
|
-
join(projectRoot, "src", "index.tsx"),
|
|
1276
|
-
join(projectRoot, "src", "index.ts"),
|
|
1277
|
-
join(projectRoot, "src", "main.tsx"),
|
|
1278
|
-
join(projectRoot, "src", "main.ts")
|
|
1279
|
-
];
|
|
1280
|
-
return filesToCheck.some(hasReactGrabInFile);
|
|
1281
|
-
};
|
|
1282
|
-
var AGENT_PACKAGES = [
|
|
1283
|
-
"@react-grab/claude-code",
|
|
1284
|
-
"@react-grab/cursor",
|
|
1285
|
-
"@react-grab/opencode",
|
|
1286
|
-
"@react-grab/codex",
|
|
1287
|
-
"@react-grab/gemini",
|
|
1288
|
-
"@react-grab/amp",
|
|
1289
|
-
"@react-grab/ami",
|
|
1290
|
-
"@react-grab/visual-edit"
|
|
1291
|
-
];
|
|
1292
|
-
var detectUnsupportedFramework = (projectRoot) => {
|
|
1293
|
-
const packageJsonPath = join(projectRoot, "package.json");
|
|
1294
|
-
if (!existsSync(packageJsonPath)) {
|
|
1295
|
-
return null;
|
|
1296
|
-
}
|
|
1297
|
-
try {
|
|
1298
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1299
|
-
const allDependencies = {
|
|
1300
|
-
...packageJson.dependencies,
|
|
1301
|
-
...packageJson.devDependencies
|
|
1302
|
-
};
|
|
1303
|
-
if (allDependencies["@remix-run/react"] || allDependencies["remix"]) {
|
|
1304
|
-
return "remix";
|
|
1305
|
-
}
|
|
1306
|
-
if (allDependencies["astro"]) {
|
|
1307
|
-
return "astro";
|
|
1308
|
-
}
|
|
1309
|
-
if (allDependencies["@sveltejs/kit"]) {
|
|
1310
|
-
return "sveltekit";
|
|
1311
|
-
}
|
|
1312
|
-
if (allDependencies["gatsby"]) {
|
|
1313
|
-
return "gatsby";
|
|
1314
|
-
}
|
|
1315
|
-
return null;
|
|
1316
|
-
} catch {
|
|
1317
|
-
return null;
|
|
1318
|
-
}
|
|
1319
|
-
};
|
|
1320
|
-
var detectInstalledAgents = (projectRoot) => {
|
|
1321
|
-
const packageJsonPath = join(projectRoot, "package.json");
|
|
1322
|
-
if (!existsSync(packageJsonPath)) {
|
|
1323
|
-
return [];
|
|
1324
|
-
}
|
|
1325
|
-
try {
|
|
1326
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1327
|
-
const allDependencies = {
|
|
1328
|
-
...packageJson.dependencies,
|
|
1329
|
-
...packageJson.devDependencies
|
|
1330
|
-
};
|
|
1331
|
-
return AGENT_PACKAGES.filter(
|
|
1332
|
-
(agent) => Boolean(allDependencies[agent])
|
|
1333
|
-
).map((agent) => agent.replace("@react-grab/", ""));
|
|
1334
|
-
} catch {
|
|
1335
|
-
return [];
|
|
1336
|
-
}
|
|
1337
|
-
};
|
|
1338
|
-
var detectProject = async (projectRoot = process.cwd()) => {
|
|
1339
|
-
const framework = detectFramework(projectRoot);
|
|
1340
|
-
const packageManager = await detectPackageManager(projectRoot);
|
|
1341
|
-
return {
|
|
1342
|
-
packageManager,
|
|
1343
|
-
framework,
|
|
1344
|
-
nextRouterType: framework === "next" ? detectNextRouterType(projectRoot) : "unknown",
|
|
1345
|
-
isMonorepo: detectMonorepo(projectRoot),
|
|
1346
|
-
projectRoot,
|
|
1347
|
-
hasReactGrab: detectReactGrab(projectRoot),
|
|
1348
|
-
installedAgents: detectInstalledAgents(projectRoot),
|
|
1349
|
-
unsupportedFramework: detectUnsupportedFramework(projectRoot)
|
|
1350
|
-
};
|
|
1351
|
-
};
|
|
1352
|
-
|
|
1353
|
-
// src/utils/diff.ts
|
|
1354
|
-
var RED = "\x1B[31m";
|
|
1355
|
-
var GREEN = "\x1B[32m";
|
|
1356
|
-
var GRAY = "\x1B[90m";
|
|
1357
|
-
var RESET = "\x1B[0m";
|
|
1358
|
-
var BOLD = "\x1B[1m";
|
|
1359
|
-
var generateDiff = (originalContent, newContent) => {
|
|
1360
|
-
const originalLines = originalContent.split("\n");
|
|
1361
|
-
const newLines = newContent.split("\n");
|
|
1362
|
-
const diff = [];
|
|
1363
|
-
Math.max(originalLines.length, newLines.length);
|
|
1364
|
-
let originalIndex = 0;
|
|
1365
|
-
let newIndex = 0;
|
|
1366
|
-
while (originalIndex < originalLines.length || newIndex < newLines.length) {
|
|
1367
|
-
const originalLine = originalLines[originalIndex];
|
|
1368
|
-
const newLine = newLines[newIndex];
|
|
1369
|
-
if (originalLine === newLine) {
|
|
1370
|
-
diff.push({
|
|
1371
|
-
type: "unchanged",
|
|
1372
|
-
content: originalLine,
|
|
1373
|
-
lineNumber: newIndex + 1
|
|
1374
|
-
});
|
|
1375
|
-
originalIndex++;
|
|
1376
|
-
newIndex++;
|
|
1377
|
-
} else if (originalLine === void 0) {
|
|
1378
|
-
diff.push({ type: "added", content: newLine, lineNumber: newIndex + 1 });
|
|
1379
|
-
newIndex++;
|
|
1380
|
-
} else if (newLine === void 0) {
|
|
1381
|
-
diff.push({ type: "removed", content: originalLine });
|
|
1382
|
-
originalIndex++;
|
|
1383
|
-
} else {
|
|
1384
|
-
const originalInNew = newLines.indexOf(originalLine, newIndex);
|
|
1385
|
-
const newInOriginal = originalLines.indexOf(newLine, originalIndex);
|
|
1386
|
-
if (originalInNew !== -1 && (newInOriginal === -1 || originalInNew - newIndex < newInOriginal - originalIndex)) {
|
|
1387
|
-
while (newIndex < originalInNew) {
|
|
1388
|
-
diff.push({
|
|
1389
|
-
type: "added",
|
|
1390
|
-
content: newLines[newIndex],
|
|
1391
|
-
lineNumber: newIndex + 1
|
|
1392
|
-
});
|
|
1393
|
-
newIndex++;
|
|
1394
|
-
}
|
|
1395
|
-
} else if (newInOriginal !== -1) {
|
|
1396
|
-
while (originalIndex < newInOriginal) {
|
|
1397
|
-
diff.push({ type: "removed", content: originalLines[originalIndex] });
|
|
1398
|
-
originalIndex++;
|
|
1399
|
-
}
|
|
1400
|
-
} else {
|
|
1401
|
-
diff.push({ type: "removed", content: originalLine });
|
|
1402
|
-
diff.push({
|
|
1403
|
-
type: "added",
|
|
1404
|
-
content: newLine,
|
|
1405
|
-
lineNumber: newIndex + 1
|
|
1406
|
-
});
|
|
1407
|
-
originalIndex++;
|
|
1408
|
-
newIndex++;
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
return diff;
|
|
1413
|
-
};
|
|
1414
|
-
var formatDiff = (diff, contextLines = 3) => {
|
|
1415
|
-
const lines = [];
|
|
1416
|
-
let lastPrintedIndex = -1;
|
|
1417
|
-
let hasChanges = false;
|
|
1418
|
-
const changedIndices = diff.map((line, index) => line.type !== "unchanged" ? index : -1).filter((index) => index !== -1);
|
|
1419
|
-
if (changedIndices.length === 0) {
|
|
1420
|
-
return `${GRAY}No changes${RESET}`;
|
|
1421
|
-
}
|
|
1422
|
-
for (const changedIndex of changedIndices) {
|
|
1423
|
-
const startContext = Math.max(0, changedIndex - contextLines);
|
|
1424
|
-
const endContext = Math.min(diff.length - 1, changedIndex + contextLines);
|
|
1425
|
-
if (startContext > lastPrintedIndex + 1 && lastPrintedIndex !== -1) {
|
|
1426
|
-
lines.push(`${GRAY} ...${RESET}`);
|
|
1427
|
-
}
|
|
1428
|
-
for (let lineIndex = Math.max(startContext, lastPrintedIndex + 1); lineIndex <= endContext; lineIndex++) {
|
|
1429
|
-
const diffLine = diff[lineIndex];
|
|
1430
|
-
if (diffLine.type === "added") {
|
|
1431
|
-
lines.push(`${GREEN}+ ${diffLine.content}${RESET}`);
|
|
1432
|
-
hasChanges = true;
|
|
1433
|
-
} else if (diffLine.type === "removed") {
|
|
1434
|
-
lines.push(`${RED}- ${diffLine.content}${RESET}`);
|
|
1435
|
-
hasChanges = true;
|
|
1436
|
-
} else {
|
|
1437
|
-
lines.push(`${GRAY} ${diffLine.content}${RESET}`);
|
|
1438
|
-
}
|
|
1439
|
-
lastPrintedIndex = lineIndex;
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
return hasChanges ? lines.join("\n") : `${GRAY}No changes${RESET}`;
|
|
1443
|
-
};
|
|
1444
|
-
var printDiff = (filePath, originalContent, newContent) => {
|
|
1445
|
-
console.log(`
|
|
1446
|
-
${BOLD}File: ${filePath}${RESET}`);
|
|
1447
|
-
console.log("\u2500".repeat(60));
|
|
1448
|
-
const diff = generateDiff(originalContent, newContent);
|
|
1449
|
-
console.log(formatDiff(diff));
|
|
1450
|
-
console.log("\u2500".repeat(60));
|
|
1451
|
-
};
|
|
1452
|
-
var highlighter = {
|
|
1453
|
-
error: pc.red,
|
|
1454
|
-
warn: pc.yellow,
|
|
1455
|
-
info: pc.cyan,
|
|
1456
|
-
success: pc.green,
|
|
1457
|
-
dim: pc.dim
|
|
1458
|
-
};
|
|
1459
|
-
|
|
1460
|
-
// src/utils/logger.ts
|
|
1461
|
-
var logger = {
|
|
1462
|
-
error(...args) {
|
|
1463
|
-
console.log(highlighter.error(args.join(" ")));
|
|
1464
|
-
},
|
|
1465
|
-
warn(...args) {
|
|
1466
|
-
console.log(highlighter.warn(args.join(" ")));
|
|
1467
|
-
},
|
|
1468
|
-
info(...args) {
|
|
1469
|
-
console.log(highlighter.info(args.join(" ")));
|
|
1470
|
-
},
|
|
1471
|
-
success(...args) {
|
|
1472
|
-
console.log(highlighter.success(args.join(" ")));
|
|
1473
|
-
},
|
|
1474
|
-
dim(...args) {
|
|
1475
|
-
console.log(highlighter.dim(args.join(" ")));
|
|
1476
|
-
},
|
|
1477
|
-
log(...args) {
|
|
1478
|
-
console.log(args.join(" "));
|
|
1479
|
-
},
|
|
1480
|
-
break() {
|
|
1481
|
-
console.log("");
|
|
1482
|
-
}
|
|
1483
|
-
};
|
|
1484
|
-
|
|
1485
|
-
// src/utils/handle-error.ts
|
|
1486
|
-
var handleError = (error48) => {
|
|
1487
|
-
logger.break();
|
|
1488
|
-
logger.error(
|
|
1489
|
-
"Something went wrong. Please check the error below for more details."
|
|
1490
|
-
);
|
|
1491
|
-
logger.error("If the problem persists, please open an issue on GitHub.");
|
|
1492
|
-
logger.error("");
|
|
1493
|
-
if (error48 instanceof Error) {
|
|
1494
|
-
logger.error(error48.message);
|
|
1495
|
-
}
|
|
1496
|
-
logger.break();
|
|
1497
|
-
process.exit(1);
|
|
1498
|
-
};
|
|
1499
|
-
var INSTALL_COMMANDS = {
|
|
1500
|
-
npm: "npm install",
|
|
1501
|
-
yarn: "yarn add",
|
|
1502
|
-
pnpm: "pnpm add",
|
|
1503
|
-
bun: "bun add"
|
|
1504
|
-
};
|
|
1505
|
-
var UNINSTALL_COMMANDS = {
|
|
1506
|
-
npm: "npm uninstall",
|
|
1507
|
-
yarn: "yarn remove",
|
|
1508
|
-
pnpm: "pnpm remove",
|
|
1509
|
-
bun: "bun remove"
|
|
1510
|
-
};
|
|
1511
|
-
var installPackages = (packages, packageManager, projectRoot, isDev = true) => {
|
|
1512
|
-
if (packages.length === 0) {
|
|
1513
|
-
return;
|
|
1514
|
-
}
|
|
1515
|
-
const command = INSTALL_COMMANDS[packageManager];
|
|
1516
|
-
const devFlag = isDev ? " -D" : "";
|
|
1517
|
-
const fullCommand = `${command}${devFlag} ${packages.join(" ")}`;
|
|
1518
|
-
console.log(`Running: ${fullCommand}
|
|
1519
|
-
`);
|
|
1520
|
-
execSync(fullCommand, {
|
|
1521
|
-
cwd: projectRoot,
|
|
1522
|
-
stdio: "inherit"
|
|
1523
|
-
});
|
|
1524
|
-
};
|
|
1525
|
-
var getPackagesToInstall = (agent, includeReactGrab = true) => {
|
|
1526
|
-
const packages = [];
|
|
1527
|
-
if (includeReactGrab) {
|
|
1528
|
-
packages.push("react-grab");
|
|
1529
|
-
}
|
|
1530
|
-
if (agent !== "none") {
|
|
1531
|
-
packages.push(`@react-grab/${agent}`);
|
|
1532
|
-
}
|
|
1533
|
-
return packages;
|
|
1534
|
-
};
|
|
1535
|
-
var uninstallPackages = (packages, packageManager, projectRoot) => {
|
|
1536
|
-
if (packages.length === 0) {
|
|
1537
|
-
return;
|
|
1538
|
-
}
|
|
1539
|
-
const command = UNINSTALL_COMMANDS[packageManager];
|
|
1540
|
-
const fullCommand = `${command} ${packages.join(" ")}`;
|
|
1541
|
-
console.log(`Running: ${fullCommand}
|
|
1542
|
-
`);
|
|
1543
|
-
execSync(fullCommand, {
|
|
1544
|
-
cwd: projectRoot,
|
|
1545
|
-
stdio: "inherit"
|
|
1546
|
-
});
|
|
1547
|
-
};
|
|
1548
|
-
var getPackagesToUninstall = (agent) => {
|
|
1549
|
-
return [`@react-grab/${agent}`];
|
|
1550
|
-
};
|
|
1551
|
-
var spinner = (text, options2) => ora({ text, isSilent: options2?.silent });
|
|
1552
|
-
|
|
1553
|
-
// src/utils/templates.ts
|
|
1554
|
-
var AGENTS = [
|
|
1555
|
-
"claude-code",
|
|
1556
|
-
"cursor",
|
|
1557
|
-
"opencode",
|
|
1558
|
-
"codex",
|
|
1559
|
-
"gemini",
|
|
1560
|
-
"amp",
|
|
1561
|
-
"ami",
|
|
1562
|
-
"visual-edit"
|
|
1563
|
-
];
|
|
1564
|
-
var AGENT_NAMES = {
|
|
1565
|
-
"claude-code": "Claude Code",
|
|
1566
|
-
cursor: "Cursor",
|
|
1567
|
-
opencode: "OpenCode",
|
|
1568
|
-
codex: "Codex",
|
|
1569
|
-
gemini: "Gemini",
|
|
1570
|
-
amp: "Amp",
|
|
1571
|
-
ami: "Ami",
|
|
1572
|
-
"visual-edit": "Visual Edit"
|
|
1573
|
-
};
|
|
1574
|
-
AGENTS.filter((agent) => agent !== "ami").map(
|
|
1575
|
-
(agent) => `@react-grab/${agent}`
|
|
1576
|
-
);
|
|
1577
|
-
var NEXT_APP_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
|
|
1578
|
-
<Script
|
|
1579
|
-
src="//unpkg.com/react-grab/dist/index.global.js"
|
|
1580
|
-
crossOrigin="anonymous"
|
|
1581
|
-
strategy="beforeInteractive"
|
|
1582
|
-
/>
|
|
1583
|
-
)}`;
|
|
1584
|
-
var NEXT_APP_ROUTER_SCRIPT_WITH_AGENT = (agent) => {
|
|
1585
|
-
if (agent === "none") return NEXT_APP_ROUTER_SCRIPT;
|
|
1586
|
-
return `{process.env.NODE_ENV === "development" && (
|
|
1587
|
-
<Script
|
|
1588
|
-
src="//unpkg.com/react-grab/dist/index.global.js"
|
|
1589
|
-
crossOrigin="anonymous"
|
|
1590
|
-
strategy="beforeInteractive"
|
|
1591
|
-
/>
|
|
1592
|
-
)}
|
|
1593
|
-
{process.env.NODE_ENV === "development" && (
|
|
1594
|
-
<Script
|
|
1595
|
-
src="//unpkg.com/@react-grab/${agent}/dist/client.global.js"
|
|
1596
|
-
strategy="lazyOnload"
|
|
1597
|
-
/>
|
|
1598
|
-
)}`;
|
|
1599
|
-
};
|
|
1600
|
-
var NEXT_PAGES_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
|
|
1601
|
-
<Script
|
|
1602
|
-
src="//unpkg.com/react-grab/dist/index.global.js"
|
|
1603
|
-
crossOrigin="anonymous"
|
|
1604
|
-
strategy="beforeInteractive"
|
|
1605
|
-
/>
|
|
1606
|
-
)}`;
|
|
1607
|
-
var NEXT_PAGES_ROUTER_SCRIPT_WITH_AGENT = (agent) => {
|
|
1608
|
-
if (agent === "none") return NEXT_PAGES_ROUTER_SCRIPT;
|
|
1609
|
-
return `{process.env.NODE_ENV === "development" && (
|
|
1610
|
-
<Script
|
|
1611
|
-
src="//unpkg.com/react-grab/dist/index.global.js"
|
|
1612
|
-
crossOrigin="anonymous"
|
|
1613
|
-
strategy="beforeInteractive"
|
|
1614
|
-
/>
|
|
1615
|
-
)}
|
|
1616
|
-
{process.env.NODE_ENV === "development" && (
|
|
1617
|
-
<Script
|
|
1618
|
-
src="//unpkg.com/@react-grab/${agent}/dist/client.global.js"
|
|
1619
|
-
strategy="lazyOnload"
|
|
1620
|
-
/>
|
|
1621
|
-
)}`;
|
|
1622
|
-
};
|
|
1623
|
-
var VITE_SCRIPT = `<script type="module">
|
|
1624
|
-
if (import.meta.env.DEV) {
|
|
1625
|
-
import("react-grab");
|
|
1626
|
-
}
|
|
1627
|
-
</script>`;
|
|
1628
|
-
var VITE_SCRIPT_WITH_AGENT = (agent) => {
|
|
1629
|
-
if (agent === "none") return VITE_SCRIPT;
|
|
1630
|
-
return `<script type="module">
|
|
1631
|
-
if (import.meta.env.DEV) {
|
|
1632
|
-
import("react-grab");
|
|
1633
|
-
import("@react-grab/${agent}/client");
|
|
1634
|
-
}
|
|
1635
|
-
</script>`;
|
|
1636
|
-
};
|
|
1637
|
-
var WEBPACK_IMPORT = `if (process.env.NODE_ENV === "development") {
|
|
1638
|
-
import("react-grab");
|
|
1639
|
-
}`;
|
|
1640
|
-
var WEBPACK_IMPORT_WITH_AGENT = (agent) => {
|
|
1641
|
-
if (agent === "none") return WEBPACK_IMPORT;
|
|
1642
|
-
return `if (process.env.NODE_ENV === "development") {
|
|
1643
|
-
import("react-grab");
|
|
1644
|
-
import("@react-grab/${agent}/client");
|
|
1645
|
-
}`;
|
|
1646
|
-
};
|
|
1647
|
-
var SCRIPT_IMPORT = 'import Script from "next/script";';
|
|
1648
|
-
var MCP_CLIENTS = [
|
|
1649
|
-
"cursor",
|
|
1650
|
-
"claude-code",
|
|
1651
|
-
"vscode",
|
|
1652
|
-
"opencode",
|
|
1653
|
-
"codex",
|
|
1654
|
-
"gemini-cli",
|
|
1655
|
-
// "cline",
|
|
1656
|
-
// "roo-cline",
|
|
1657
|
-
"windsurf",
|
|
1658
|
-
"zed",
|
|
1659
|
-
// "warp",
|
|
1660
|
-
"droid"
|
|
1661
|
-
// "claude",
|
|
1662
|
-
];
|
|
1663
|
-
var MCP_CLIENT_NAMES = {
|
|
1664
|
-
"cursor": "Cursor",
|
|
1665
|
-
"claude-code": "Claude Code",
|
|
1666
|
-
"vscode": "VSCode",
|
|
1667
|
-
"opencode": "OpenCode",
|
|
1668
|
-
"codex": "Codex",
|
|
1669
|
-
"gemini-cli": "Gemini CLI",
|
|
1670
|
-
// "cline": "Cline",
|
|
1671
|
-
// "roo-cline": "Roo Cline",
|
|
1672
|
-
"windsurf": "Windsurf",
|
|
1673
|
-
"zed": "Zed",
|
|
1674
|
-
// "warp": "Warp",
|
|
1675
|
-
"droid": "Droid"
|
|
1676
|
-
// "claude": "Claude Desktop",
|
|
1677
|
-
};
|
|
1678
|
-
|
|
1679
|
-
// src/utils/skill-files.ts
|
|
1680
|
-
var REPO = "aidenybai/react-grab";
|
|
1681
|
-
var BRANCH = "main";
|
|
1682
|
-
var BASE_URL = `https://raw.githubusercontent.com/${REPO}/${BRANCH}`;
|
|
1683
|
-
var fetchSkillFile = async () => {
|
|
1684
|
-
const skillUrl = `${BASE_URL}/skills/react-grab-browser/SKILL.md`;
|
|
1685
|
-
const response = await fetch(skillUrl);
|
|
1686
|
-
if (!response.ok) {
|
|
1687
|
-
throw new Error(`Failed to fetch SKILL.md: ${response.status}`);
|
|
1688
|
-
}
|
|
1689
|
-
return response.text();
|
|
1690
|
-
};
|
|
1691
|
-
var AGENT_TARGETS = {
|
|
1692
|
-
claude: ".claude/skills/react-grab-browser",
|
|
1693
|
-
codex: ".codex/skills/react-grab-browser",
|
|
1694
|
-
amp: ".agents/skills/react-grab-browser",
|
|
1695
|
-
vscode: ".vscode/skills/react-grab-browser"
|
|
1696
|
-
};
|
|
1697
|
-
var SUPPORTED_TARGETS = Object.keys(AGENT_TARGETS);
|
|
1698
|
-
var hasReactGrabCode = (content) => {
|
|
1699
|
-
const fuzzyPatterns = [
|
|
1700
|
-
/["'`][^"'`]*react-grab/,
|
|
1701
|
-
/react-grab[^"'`]*["'`]/,
|
|
1702
|
-
/<[^>]*react-grab/i,
|
|
1703
|
-
/import[^;]*react-grab/i,
|
|
1704
|
-
/require[^)]*react-grab/i,
|
|
1705
|
-
/from\s+[^;]*react-grab/i,
|
|
1706
|
-
/src[^>]*react-grab/i,
|
|
1707
|
-
/href[^>]*react-grab/i
|
|
1708
|
-
];
|
|
1709
|
-
return fuzzyPatterns.some((pattern) => pattern.test(content));
|
|
1710
|
-
};
|
|
1711
|
-
var findLayoutFile = (projectRoot) => {
|
|
1712
|
-
const possiblePaths = [
|
|
1713
|
-
join(projectRoot, "app", "layout.tsx"),
|
|
1714
|
-
join(projectRoot, "app", "layout.jsx"),
|
|
1715
|
-
join(projectRoot, "src", "app", "layout.tsx"),
|
|
1716
|
-
join(projectRoot, "src", "app", "layout.jsx")
|
|
1717
|
-
];
|
|
1718
|
-
for (const filePath of possiblePaths) {
|
|
1719
|
-
if (existsSync(filePath)) {
|
|
1720
|
-
return filePath;
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
return null;
|
|
1724
|
-
};
|
|
1725
|
-
var findInstrumentationFile = (projectRoot) => {
|
|
1726
|
-
const possiblePaths = [
|
|
1727
|
-
join(projectRoot, "instrumentation-client.ts"),
|
|
1728
|
-
join(projectRoot, "instrumentation-client.js"),
|
|
1729
|
-
join(projectRoot, "src", "instrumentation-client.ts"),
|
|
1730
|
-
join(projectRoot, "src", "instrumentation-client.js")
|
|
1731
|
-
];
|
|
1732
|
-
for (const filePath of possiblePaths) {
|
|
1733
|
-
if (existsSync(filePath)) {
|
|
1734
|
-
return filePath;
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
return null;
|
|
1738
|
-
};
|
|
1739
|
-
var hasReactGrabInInstrumentation = (projectRoot) => {
|
|
1740
|
-
const instrumentationPath = findInstrumentationFile(projectRoot);
|
|
1741
|
-
if (!instrumentationPath) return false;
|
|
1742
|
-
const content = readFileSync(instrumentationPath, "utf-8");
|
|
1743
|
-
return hasReactGrabCode(content);
|
|
1744
|
-
};
|
|
1745
|
-
var findDocumentFile = (projectRoot) => {
|
|
1746
|
-
const possiblePaths = [
|
|
1747
|
-
join(projectRoot, "pages", "_document.tsx"),
|
|
1748
|
-
join(projectRoot, "pages", "_document.jsx"),
|
|
1749
|
-
join(projectRoot, "src", "pages", "_document.tsx"),
|
|
1750
|
-
join(projectRoot, "src", "pages", "_document.jsx")
|
|
1751
|
-
];
|
|
1752
|
-
for (const filePath of possiblePaths) {
|
|
1753
|
-
if (existsSync(filePath)) {
|
|
1754
|
-
return filePath;
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
return null;
|
|
1758
|
-
};
|
|
1759
|
-
var findIndexHtml = (projectRoot) => {
|
|
1760
|
-
const possiblePaths = [
|
|
1761
|
-
join(projectRoot, "index.html"),
|
|
1762
|
-
join(projectRoot, "public", "index.html")
|
|
1763
|
-
];
|
|
1764
|
-
for (const filePath of possiblePaths) {
|
|
1765
|
-
if (existsSync(filePath)) {
|
|
1766
|
-
return filePath;
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
return null;
|
|
1258
|
+
return null;
|
|
1770
1259
|
};
|
|
1771
1260
|
var findEntryFile = (projectRoot) => {
|
|
1772
1261
|
const possiblePaths = [
|
|
@@ -1795,512 +1284,895 @@ var addAgentToExistingNextApp = (originalContent, agent, filePath) => {
|
|
|
1795
1284
|
noChanges: true
|
|
1796
1285
|
};
|
|
1797
1286
|
}
|
|
1798
|
-
const agentPackage = `@react-grab/${agent}`;
|
|
1799
|
-
if (originalContent.includes(agentPackage)) {
|
|
1287
|
+
const agentPackage = `@react-grab/${agent}`;
|
|
1288
|
+
if (originalContent.includes(agentPackage)) {
|
|
1289
|
+
return {
|
|
1290
|
+
success: true,
|
|
1291
|
+
filePath,
|
|
1292
|
+
message: `Agent ${agent} is already configured`,
|
|
1293
|
+
noChanges: true
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
const agentScript = `{process.env.NODE_ENV === "development" && (
|
|
1297
|
+
<Script
|
|
1298
|
+
src="//unpkg.com/${agentPackage}/dist/client.global.js"
|
|
1299
|
+
strategy="lazyOnload"
|
|
1300
|
+
/>
|
|
1301
|
+
)}`;
|
|
1302
|
+
const reactGrabBlockMatch = originalContent.match(
|
|
1303
|
+
/\{process\.env\.NODE_ENV\s*===\s*["']development["']\s*&&\s*\(\s*<Script[^>]*react-grab[^>]*\/>\s*\)\}/is
|
|
1304
|
+
);
|
|
1305
|
+
if (reactGrabBlockMatch) {
|
|
1306
|
+
const newContent = originalContent.replace(
|
|
1307
|
+
reactGrabBlockMatch[0],
|
|
1308
|
+
`${reactGrabBlockMatch[0]}
|
|
1309
|
+
${agentScript}`
|
|
1310
|
+
);
|
|
1311
|
+
return {
|
|
1312
|
+
success: true,
|
|
1313
|
+
filePath,
|
|
1314
|
+
message: `Add ${agent} agent`,
|
|
1315
|
+
originalContent,
|
|
1316
|
+
newContent
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
const bareScriptMatch = originalContent.match(
|
|
1320
|
+
/<Script[^>]*react-grab[^>]*\/>/i
|
|
1321
|
+
);
|
|
1322
|
+
if (bareScriptMatch) {
|
|
1323
|
+
const newContent = originalContent.replace(
|
|
1324
|
+
bareScriptMatch[0],
|
|
1325
|
+
`${bareScriptMatch[0]}
|
|
1326
|
+
<Script src="//unpkg.com/${agentPackage}/dist/client.global.js" strategy="lazyOnload" />`
|
|
1327
|
+
);
|
|
1328
|
+
return {
|
|
1329
|
+
success: true,
|
|
1330
|
+
filePath,
|
|
1331
|
+
message: `Add ${agent} agent`,
|
|
1332
|
+
originalContent,
|
|
1333
|
+
newContent
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
return {
|
|
1337
|
+
success: false,
|
|
1338
|
+
filePath,
|
|
1339
|
+
message: "Could not find React Grab script to add agent after"
|
|
1340
|
+
};
|
|
1341
|
+
};
|
|
1342
|
+
var addAgentToExistingVite = (originalContent, agent, filePath) => {
|
|
1343
|
+
if (agent === "none") {
|
|
1344
|
+
return {
|
|
1345
|
+
success: true,
|
|
1346
|
+
filePath,
|
|
1347
|
+
message: "React Grab is already configured",
|
|
1348
|
+
noChanges: true
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
const agentPackage = `@react-grab/${agent}`;
|
|
1352
|
+
if (originalContent.includes(agentPackage)) {
|
|
1353
|
+
return {
|
|
1354
|
+
success: true,
|
|
1355
|
+
filePath,
|
|
1356
|
+
message: `Agent ${agent} is already configured`,
|
|
1357
|
+
noChanges: true
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
const agentImport = `import("${agentPackage}/client");`;
|
|
1361
|
+
const reactGrabImportMatch = originalContent.match(
|
|
1362
|
+
/import\s*\(\s*["']react-grab["']\s*\);?/
|
|
1363
|
+
);
|
|
1364
|
+
if (reactGrabImportMatch) {
|
|
1365
|
+
const matchedText = reactGrabImportMatch[0];
|
|
1366
|
+
const hasSemicolon = matchedText.endsWith(";");
|
|
1367
|
+
const newContent = originalContent.replace(
|
|
1368
|
+
matchedText,
|
|
1369
|
+
`${hasSemicolon ? matchedText.slice(0, -1) : matchedText};
|
|
1370
|
+
${agentImport}`
|
|
1371
|
+
);
|
|
1372
|
+
return {
|
|
1373
|
+
success: true,
|
|
1374
|
+
filePath,
|
|
1375
|
+
message: `Add ${agent} agent`,
|
|
1376
|
+
originalContent,
|
|
1377
|
+
newContent
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
return {
|
|
1381
|
+
success: false,
|
|
1382
|
+
filePath,
|
|
1383
|
+
message: "Could not find React Grab import to add agent after"
|
|
1384
|
+
};
|
|
1385
|
+
};
|
|
1386
|
+
var addAgentToExistingWebpack = (originalContent, agent, filePath) => {
|
|
1387
|
+
if (agent === "none") {
|
|
1388
|
+
return {
|
|
1389
|
+
success: true,
|
|
1390
|
+
filePath,
|
|
1391
|
+
message: "React Grab is already configured",
|
|
1392
|
+
noChanges: true
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
const agentPackage = `@react-grab/${agent}`;
|
|
1396
|
+
if (originalContent.includes(agentPackage)) {
|
|
1397
|
+
return {
|
|
1398
|
+
success: true,
|
|
1399
|
+
filePath,
|
|
1400
|
+
message: `Agent ${agent} is already configured`,
|
|
1401
|
+
noChanges: true
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
const agentImport = `import("${agentPackage}/client");`;
|
|
1405
|
+
const reactGrabImportMatch = originalContent.match(
|
|
1406
|
+
/import\s*\(\s*["']react-grab["']\s*\);?/
|
|
1407
|
+
);
|
|
1408
|
+
if (reactGrabImportMatch) {
|
|
1409
|
+
const matchedText = reactGrabImportMatch[0];
|
|
1410
|
+
const hasSemicolon = matchedText.endsWith(";");
|
|
1411
|
+
const newContent = originalContent.replace(
|
|
1412
|
+
matchedText,
|
|
1413
|
+
`${hasSemicolon ? matchedText.slice(0, -1) : matchedText};
|
|
1414
|
+
${agentImport}`
|
|
1415
|
+
);
|
|
1416
|
+
return {
|
|
1417
|
+
success: true,
|
|
1418
|
+
filePath,
|
|
1419
|
+
message: `Add ${agent} agent`,
|
|
1420
|
+
originalContent,
|
|
1421
|
+
newContent
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
success: false,
|
|
1426
|
+
filePath,
|
|
1427
|
+
message: "Could not find React Grab import to add agent after"
|
|
1428
|
+
};
|
|
1429
|
+
};
|
|
1430
|
+
var transformNextAppRouter = (projectRoot, agent, reactGrabAlreadyConfigured) => {
|
|
1431
|
+
const layoutPath = findLayoutFile(projectRoot);
|
|
1432
|
+
if (!layoutPath) {
|
|
1433
|
+
return {
|
|
1434
|
+
success: false,
|
|
1435
|
+
filePath: "",
|
|
1436
|
+
message: "Could not find app/layout.tsx or app/layout.jsx"
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
const originalContent = readFileSync(layoutPath, "utf-8");
|
|
1440
|
+
let newContent = originalContent;
|
|
1441
|
+
const hasReactGrabInFile2 = hasReactGrabCode(originalContent);
|
|
1442
|
+
const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
|
|
1443
|
+
if (hasReactGrabInFile2 && reactGrabAlreadyConfigured) {
|
|
1444
|
+
return addAgentToExistingNextApp(originalContent, agent, layoutPath);
|
|
1445
|
+
}
|
|
1446
|
+
if (hasReactGrabInFile2 || hasReactGrabInInstrumentationFile) {
|
|
1447
|
+
return {
|
|
1448
|
+
success: true,
|
|
1449
|
+
filePath: layoutPath,
|
|
1450
|
+
message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
|
|
1451
|
+
noChanges: true
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
if (!newContent.includes('import Script from "next/script"')) {
|
|
1455
|
+
const importMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
|
|
1456
|
+
if (importMatch) {
|
|
1457
|
+
newContent = newContent.replace(
|
|
1458
|
+
importMatch[0],
|
|
1459
|
+
`${importMatch[0]}
|
|
1460
|
+
${SCRIPT_IMPORT}`
|
|
1461
|
+
);
|
|
1462
|
+
} else {
|
|
1463
|
+
newContent = `${SCRIPT_IMPORT}
|
|
1464
|
+
|
|
1465
|
+
${newContent}`;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
const scriptBlock = NEXT_APP_ROUTER_SCRIPT_WITH_AGENT(agent);
|
|
1469
|
+
const headMatch = newContent.match(/<head[^>]*>/);
|
|
1470
|
+
if (headMatch) {
|
|
1471
|
+
newContent = newContent.replace(
|
|
1472
|
+
headMatch[0],
|
|
1473
|
+
`${headMatch[0]}
|
|
1474
|
+
${scriptBlock}`
|
|
1475
|
+
);
|
|
1476
|
+
} else {
|
|
1477
|
+
const htmlMatch = newContent.match(/<html[^>]*>/);
|
|
1478
|
+
if (htmlMatch) {
|
|
1479
|
+
newContent = newContent.replace(
|
|
1480
|
+
htmlMatch[0],
|
|
1481
|
+
`${htmlMatch[0]}
|
|
1482
|
+
<head>
|
|
1483
|
+
${scriptBlock}
|
|
1484
|
+
</head>`
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
return {
|
|
1489
|
+
success: true,
|
|
1490
|
+
filePath: layoutPath,
|
|
1491
|
+
message: "Add React Grab" + (agent !== "none" ? ` with ${agent} agent` : ""),
|
|
1492
|
+
originalContent,
|
|
1493
|
+
newContent
|
|
1494
|
+
};
|
|
1495
|
+
};
|
|
1496
|
+
var transformNextPagesRouter = (projectRoot, agent, reactGrabAlreadyConfigured) => {
|
|
1497
|
+
const documentPath = findDocumentFile(projectRoot);
|
|
1498
|
+
if (!documentPath) {
|
|
1499
|
+
return {
|
|
1500
|
+
success: false,
|
|
1501
|
+
filePath: "",
|
|
1502
|
+
message: 'Could not find pages/_document.tsx or pages/_document.jsx.\n\nTo set up React Grab with Pages Router, create pages/_document.tsx with:\n\n import { Html, Head, Main, NextScript } from "next/document";\n import Script from "next/script";\n\n export default function Document() {\n return (\n <Html>\n <Head>\n {process.env.NODE_ENV === "development" && (\n <Script src="//unpkg.com/react-grab/dist/index.global.js" strategy="beforeInteractive" />\n )}\n </Head>\n <body>\n <Main />\n <NextScript />\n </body>\n </Html>\n );\n }'
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
const originalContent = readFileSync(documentPath, "utf-8");
|
|
1506
|
+
let newContent = originalContent;
|
|
1507
|
+
const hasReactGrabInFile2 = hasReactGrabCode(originalContent);
|
|
1508
|
+
const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
|
|
1509
|
+
if (hasReactGrabInFile2 && reactGrabAlreadyConfigured) {
|
|
1510
|
+
return addAgentToExistingNextApp(originalContent, agent, documentPath);
|
|
1511
|
+
}
|
|
1512
|
+
if (hasReactGrabInFile2 || hasReactGrabInInstrumentationFile) {
|
|
1800
1513
|
return {
|
|
1801
1514
|
success: true,
|
|
1802
|
-
filePath,
|
|
1803
|
-
message:
|
|
1515
|
+
filePath: documentPath,
|
|
1516
|
+
message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
|
|
1804
1517
|
noChanges: true
|
|
1805
1518
|
};
|
|
1806
1519
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1520
|
+
if (!newContent.includes('import Script from "next/script"')) {
|
|
1521
|
+
const importMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
|
|
1522
|
+
if (importMatch) {
|
|
1523
|
+
newContent = newContent.replace(
|
|
1524
|
+
importMatch[0],
|
|
1525
|
+
`${importMatch[0]}
|
|
1526
|
+
${SCRIPT_IMPORT}`
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
const scriptBlock = NEXT_PAGES_ROUTER_SCRIPT_WITH_AGENT(agent);
|
|
1531
|
+
const headMatch = newContent.match(/<Head[^>]*>/);
|
|
1532
|
+
if (headMatch) {
|
|
1533
|
+
newContent = newContent.replace(
|
|
1534
|
+
headMatch[0],
|
|
1535
|
+
`${headMatch[0]}
|
|
1536
|
+
${scriptBlock}`
|
|
1821
1537
|
);
|
|
1538
|
+
}
|
|
1539
|
+
return {
|
|
1540
|
+
success: true,
|
|
1541
|
+
filePath: documentPath,
|
|
1542
|
+
message: "Add React Grab" + (agent !== "none" ? ` with ${agent} agent` : ""),
|
|
1543
|
+
originalContent,
|
|
1544
|
+
newContent
|
|
1545
|
+
};
|
|
1546
|
+
};
|
|
1547
|
+
var transformVite = (projectRoot, agent, reactGrabAlreadyConfigured) => {
|
|
1548
|
+
const indexPath = findIndexHtml(projectRoot);
|
|
1549
|
+
if (!indexPath) {
|
|
1822
1550
|
return {
|
|
1823
|
-
success:
|
|
1824
|
-
filePath,
|
|
1825
|
-
message:
|
|
1826
|
-
originalContent,
|
|
1827
|
-
newContent
|
|
1551
|
+
success: false,
|
|
1552
|
+
filePath: "",
|
|
1553
|
+
message: "Could not find index.html"
|
|
1828
1554
|
};
|
|
1829
1555
|
}
|
|
1830
|
-
const
|
|
1831
|
-
|
|
1832
|
-
);
|
|
1833
|
-
if (
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
<Script src="//unpkg.com/${agentPackage}/dist/client.global.js" strategy="lazyOnload" />`
|
|
1838
|
-
);
|
|
1556
|
+
const originalContent = readFileSync(indexPath, "utf-8");
|
|
1557
|
+
let newContent = originalContent;
|
|
1558
|
+
const hasReactGrabInFile2 = hasReactGrabCode(originalContent);
|
|
1559
|
+
if (hasReactGrabInFile2 && reactGrabAlreadyConfigured) {
|
|
1560
|
+
return addAgentToExistingVite(originalContent, agent, indexPath);
|
|
1561
|
+
}
|
|
1562
|
+
if (hasReactGrabInFile2) {
|
|
1839
1563
|
return {
|
|
1840
1564
|
success: true,
|
|
1841
|
-
filePath,
|
|
1842
|
-
message:
|
|
1843
|
-
|
|
1844
|
-
newContent
|
|
1565
|
+
filePath: indexPath,
|
|
1566
|
+
message: "React Grab is already installed in this file",
|
|
1567
|
+
noChanges: true
|
|
1845
1568
|
};
|
|
1846
1569
|
}
|
|
1570
|
+
const scriptBlock = VITE_SCRIPT_WITH_AGENT(agent);
|
|
1571
|
+
const headMatch = newContent.match(/<head[^>]*>/i);
|
|
1572
|
+
if (headMatch) {
|
|
1573
|
+
newContent = newContent.replace(
|
|
1574
|
+
headMatch[0],
|
|
1575
|
+
`${headMatch[0]}
|
|
1576
|
+
${scriptBlock}`
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1847
1579
|
return {
|
|
1848
|
-
success:
|
|
1849
|
-
filePath,
|
|
1850
|
-
message: "
|
|
1580
|
+
success: true,
|
|
1581
|
+
filePath: indexPath,
|
|
1582
|
+
message: "Add React Grab" + (agent !== "none" ? ` with ${agent} agent` : ""),
|
|
1583
|
+
originalContent,
|
|
1584
|
+
newContent
|
|
1851
1585
|
};
|
|
1852
1586
|
};
|
|
1853
|
-
var
|
|
1854
|
-
|
|
1587
|
+
var transformWebpack = (projectRoot, agent, reactGrabAlreadyConfigured) => {
|
|
1588
|
+
const entryPath = findEntryFile(projectRoot);
|
|
1589
|
+
if (!entryPath) {
|
|
1590
|
+
return {
|
|
1591
|
+
success: false,
|
|
1592
|
+
filePath: "",
|
|
1593
|
+
message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)"
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
const originalContent = readFileSync(entryPath, "utf-8");
|
|
1597
|
+
const hasReactGrabInFile2 = hasReactGrabCode(originalContent);
|
|
1598
|
+
if (hasReactGrabInFile2 && reactGrabAlreadyConfigured) {
|
|
1599
|
+
return addAgentToExistingWebpack(originalContent, agent, entryPath);
|
|
1600
|
+
}
|
|
1601
|
+
if (hasReactGrabInFile2) {
|
|
1855
1602
|
return {
|
|
1856
1603
|
success: true,
|
|
1857
|
-
filePath,
|
|
1858
|
-
message: "React Grab is already
|
|
1604
|
+
filePath: entryPath,
|
|
1605
|
+
message: "React Grab is already installed in this file",
|
|
1859
1606
|
noChanges: true
|
|
1860
1607
|
};
|
|
1861
1608
|
}
|
|
1862
|
-
const
|
|
1863
|
-
|
|
1609
|
+
const importBlock = WEBPACK_IMPORT_WITH_AGENT(agent);
|
|
1610
|
+
const newContent = `${importBlock}
|
|
1611
|
+
|
|
1612
|
+
${originalContent}`;
|
|
1613
|
+
return {
|
|
1614
|
+
success: true,
|
|
1615
|
+
filePath: entryPath,
|
|
1616
|
+
message: "Add React Grab" + (agent !== "none" ? ` with ${agent} agent` : ""),
|
|
1617
|
+
originalContent,
|
|
1618
|
+
newContent
|
|
1619
|
+
};
|
|
1620
|
+
};
|
|
1621
|
+
var previewTransform = (projectRoot, framework, nextRouterType, agent, reactGrabAlreadyConfigured = false) => {
|
|
1622
|
+
switch (framework) {
|
|
1623
|
+
case "next":
|
|
1624
|
+
if (nextRouterType === "app") {
|
|
1625
|
+
return transformNextAppRouter(
|
|
1626
|
+
projectRoot,
|
|
1627
|
+
agent,
|
|
1628
|
+
reactGrabAlreadyConfigured
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
return transformNextPagesRouter(
|
|
1632
|
+
projectRoot,
|
|
1633
|
+
agent,
|
|
1634
|
+
reactGrabAlreadyConfigured
|
|
1635
|
+
);
|
|
1636
|
+
case "vite":
|
|
1637
|
+
return transformVite(projectRoot, agent, reactGrabAlreadyConfigured);
|
|
1638
|
+
case "webpack":
|
|
1639
|
+
return transformWebpack(projectRoot, agent, reactGrabAlreadyConfigured);
|
|
1640
|
+
default:
|
|
1641
|
+
return {
|
|
1642
|
+
success: false,
|
|
1643
|
+
filePath: "",
|
|
1644
|
+
message: `Unknown framework: ${framework}. Please add React Grab manually.`
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
var canWriteToFile = (filePath) => {
|
|
1649
|
+
try {
|
|
1650
|
+
accessSync(filePath, constants.W_OK);
|
|
1651
|
+
return true;
|
|
1652
|
+
} catch {
|
|
1653
|
+
return false;
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
var applyTransform = (result) => {
|
|
1657
|
+
if (result.success && result.newContent && result.filePath) {
|
|
1658
|
+
if (!canWriteToFile(result.filePath)) {
|
|
1659
|
+
return {
|
|
1660
|
+
success: false,
|
|
1661
|
+
error: `Cannot write to ${result.filePath}. Check file permissions.`
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
try {
|
|
1665
|
+
writeFileSync(result.filePath, result.newContent);
|
|
1666
|
+
return { success: true };
|
|
1667
|
+
} catch (error48) {
|
|
1668
|
+
return {
|
|
1669
|
+
success: false,
|
|
1670
|
+
error: `Failed to write to ${result.filePath}: ${error48 instanceof Error ? error48.message : "Unknown error"}`
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
return { success: true };
|
|
1675
|
+
};
|
|
1676
|
+
var getPackageExecutor = (packageManager) => {
|
|
1677
|
+
switch (packageManager) {
|
|
1678
|
+
case "bun":
|
|
1679
|
+
return "bunx";
|
|
1680
|
+
case "pnpm":
|
|
1681
|
+
return "pnpm dlx";
|
|
1682
|
+
case "yarn":
|
|
1683
|
+
return "npx";
|
|
1684
|
+
case "npm":
|
|
1685
|
+
default:
|
|
1686
|
+
return "npx";
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
var AGENT_PACKAGES = {
|
|
1690
|
+
"claude-code": "@react-grab/claude-code@latest",
|
|
1691
|
+
cursor: "@react-grab/cursor@latest",
|
|
1692
|
+
opencode: "@react-grab/opencode@latest",
|
|
1693
|
+
codex: "@react-grab/codex@latest",
|
|
1694
|
+
gemini: "@react-grab/gemini@latest",
|
|
1695
|
+
amp: "@react-grab/amp@latest"
|
|
1696
|
+
};
|
|
1697
|
+
var getAgentPrefix = (agent, packageManager) => {
|
|
1698
|
+
const agentPackage = AGENT_PACKAGES[agent];
|
|
1699
|
+
if (!agentPackage) return null;
|
|
1700
|
+
const executor = getPackageExecutor(packageManager);
|
|
1701
|
+
return `${executor} ${agentPackage} &&`;
|
|
1702
|
+
};
|
|
1703
|
+
var getAllAgentPrefixVariants = (agent) => {
|
|
1704
|
+
const agentPackage = AGENT_PACKAGES[agent];
|
|
1705
|
+
if (!agentPackage) return [];
|
|
1706
|
+
return [
|
|
1707
|
+
`npx ${agentPackage} &&`,
|
|
1708
|
+
`bunx ${agentPackage} &&`,
|
|
1709
|
+
`pnpm dlx ${agentPackage} &&`,
|
|
1710
|
+
`yarn dlx ${agentPackage} &&`
|
|
1711
|
+
];
|
|
1712
|
+
};
|
|
1713
|
+
var previewPackageJsonTransform = (projectRoot, agent, installedAgents, packageManager = "npm") => {
|
|
1714
|
+
if (agent === "none" || agent === "visual-edit") {
|
|
1864
1715
|
return {
|
|
1865
1716
|
success: true,
|
|
1866
|
-
filePath,
|
|
1867
|
-
message:
|
|
1717
|
+
filePath: "",
|
|
1718
|
+
message: agent === "visual-edit" ? "Visual Edit does not require package.json modification" : "No agent selected, skipping package.json modification",
|
|
1868
1719
|
noChanges: true
|
|
1869
1720
|
};
|
|
1870
1721
|
}
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1873
|
-
/import\s*\(\s*["']react-grab["']\s*\);?/
|
|
1874
|
-
);
|
|
1875
|
-
if (reactGrabImportMatch) {
|
|
1876
|
-
const matchedText = reactGrabImportMatch[0];
|
|
1877
|
-
const hasSemicolon = matchedText.endsWith(";");
|
|
1878
|
-
const newContent = originalContent.replace(
|
|
1879
|
-
matchedText,
|
|
1880
|
-
`${hasSemicolon ? matchedText.slice(0, -1) : matchedText};
|
|
1881
|
-
${agentImport}`
|
|
1882
|
-
);
|
|
1722
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
1723
|
+
if (!existsSync(packageJsonPath)) {
|
|
1883
1724
|
return {
|
|
1884
|
-
success:
|
|
1885
|
-
filePath,
|
|
1886
|
-
message:
|
|
1887
|
-
originalContent,
|
|
1888
|
-
newContent
|
|
1725
|
+
success: false,
|
|
1726
|
+
filePath: "",
|
|
1727
|
+
message: "Could not find package.json"
|
|
1889
1728
|
};
|
|
1890
1729
|
}
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
message: "Could not find React Grab import to add agent after"
|
|
1895
|
-
};
|
|
1896
|
-
};
|
|
1897
|
-
var addAgentToExistingWebpack = (originalContent, agent, filePath) => {
|
|
1898
|
-
if (agent === "none") {
|
|
1730
|
+
const originalContent = readFileSync(packageJsonPath, "utf-8");
|
|
1731
|
+
const agentPrefix = getAgentPrefix(agent, packageManager);
|
|
1732
|
+
if (!agentPrefix) {
|
|
1899
1733
|
return {
|
|
1900
|
-
success:
|
|
1901
|
-
filePath,
|
|
1902
|
-
message:
|
|
1903
|
-
noChanges: true
|
|
1734
|
+
success: false,
|
|
1735
|
+
filePath: packageJsonPath,
|
|
1736
|
+
message: `Unknown agent: ${agent}`
|
|
1904
1737
|
};
|
|
1905
1738
|
}
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1739
|
+
const allPrefixVariants = getAllAgentPrefixVariants(agent);
|
|
1740
|
+
const hasExistingPrefix = allPrefixVariants.some(
|
|
1741
|
+
(prefix) => originalContent.includes(prefix)
|
|
1742
|
+
);
|
|
1743
|
+
if (hasExistingPrefix) {
|
|
1908
1744
|
return {
|
|
1909
1745
|
success: true,
|
|
1910
|
-
filePath,
|
|
1911
|
-
message: `Agent ${agent} is already configured`,
|
|
1746
|
+
filePath: packageJsonPath,
|
|
1747
|
+
message: `Agent ${agent} dev script is already configured`,
|
|
1912
1748
|
noChanges: true
|
|
1913
1749
|
};
|
|
1914
1750
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1751
|
+
try {
|
|
1752
|
+
const packageJson = JSON.parse(originalContent);
|
|
1753
|
+
let targetScriptKey = "dev";
|
|
1754
|
+
if (!packageJson.scripts?.dev) {
|
|
1755
|
+
const devScriptKeys = Object.keys(packageJson.scripts || {}).filter(
|
|
1756
|
+
(key) => key.startsWith("dev")
|
|
1757
|
+
);
|
|
1758
|
+
if (devScriptKeys.length > 0) {
|
|
1759
|
+
targetScriptKey = devScriptKeys[0];
|
|
1760
|
+
} else {
|
|
1761
|
+
return {
|
|
1762
|
+
success: true,
|
|
1763
|
+
filePath: packageJsonPath,
|
|
1764
|
+
message: "No dev script found in package.json",
|
|
1765
|
+
noChanges: true,
|
|
1766
|
+
warning: `Could not inject agent into package.json (no dev script found).
|
|
1767
|
+
Run this command manually before starting your dev server:
|
|
1768
|
+
${agentPrefix} <your dev command>`
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
const currentDevScript = packageJson.scripts[targetScriptKey];
|
|
1773
|
+
for (const installedAgent of installedAgents) {
|
|
1774
|
+
const installedPrefixVariants = getAllAgentPrefixVariants(installedAgent);
|
|
1775
|
+
const hasInstalledAgentPrefix = installedPrefixVariants.some(
|
|
1776
|
+
(prefix) => currentDevScript.includes(prefix)
|
|
1777
|
+
);
|
|
1778
|
+
if (hasInstalledAgentPrefix) {
|
|
1779
|
+
return {
|
|
1780
|
+
success: true,
|
|
1781
|
+
filePath: packageJsonPath,
|
|
1782
|
+
message: `Agent ${installedAgent} is already in ${targetScriptKey} script`,
|
|
1783
|
+
noChanges: true
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
packageJson.scripts[targetScriptKey] = `${agentPrefix} ${currentDevScript}`;
|
|
1788
|
+
const newContent = JSON.stringify(packageJson, null, 2) + "\n";
|
|
1927
1789
|
return {
|
|
1928
1790
|
success: true,
|
|
1929
|
-
filePath,
|
|
1930
|
-
message: `Add ${agent}
|
|
1791
|
+
filePath: packageJsonPath,
|
|
1792
|
+
message: `Add ${agent} server to ${targetScriptKey} script`,
|
|
1931
1793
|
originalContent,
|
|
1932
1794
|
newContent
|
|
1933
1795
|
};
|
|
1934
|
-
}
|
|
1935
|
-
return {
|
|
1936
|
-
success: false,
|
|
1937
|
-
filePath,
|
|
1938
|
-
message: "Could not find React Grab import to add agent after"
|
|
1939
|
-
};
|
|
1940
|
-
};
|
|
1941
|
-
var transformNextAppRouter = (projectRoot, agent, reactGrabAlreadyConfigured) => {
|
|
1942
|
-
const layoutPath = findLayoutFile(projectRoot);
|
|
1943
|
-
if (!layoutPath) {
|
|
1796
|
+
} catch {
|
|
1944
1797
|
return {
|
|
1945
1798
|
success: false,
|
|
1946
|
-
filePath:
|
|
1947
|
-
message: "
|
|
1799
|
+
filePath: packageJsonPath,
|
|
1800
|
+
message: "Failed to parse package.json"
|
|
1948
1801
|
};
|
|
1949
1802
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1803
|
+
};
|
|
1804
|
+
var applyPackageJsonTransform = (result) => {
|
|
1805
|
+
if (result.success && result.newContent && result.filePath) {
|
|
1806
|
+
if (!canWriteToFile(result.filePath)) {
|
|
1807
|
+
return {
|
|
1808
|
+
success: false,
|
|
1809
|
+
error: `Cannot write to ${result.filePath}. Check file permissions.`
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
writeFileSync(result.filePath, result.newContent);
|
|
1814
|
+
return { success: true };
|
|
1815
|
+
} catch (error48) {
|
|
1816
|
+
return {
|
|
1817
|
+
success: false,
|
|
1818
|
+
error: `Failed to write to ${result.filePath}: ${error48 instanceof Error ? error48.message : "Unknown error"}`
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1956
1821
|
}
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
};
|
|
1822
|
+
return { success: true };
|
|
1823
|
+
};
|
|
1824
|
+
var formatOptionsForNextjs = (options2) => {
|
|
1825
|
+
const parts = [];
|
|
1826
|
+
if (options2.activationKey) {
|
|
1827
|
+
parts.push(`activationKey: ${JSON.stringify(options2.activationKey)}`);
|
|
1964
1828
|
}
|
|
1965
|
-
if (
|
|
1966
|
-
|
|
1967
|
-
if (importMatch) {
|
|
1968
|
-
newContent = newContent.replace(
|
|
1969
|
-
importMatch[0],
|
|
1970
|
-
`${importMatch[0]}
|
|
1971
|
-
${SCRIPT_IMPORT}`
|
|
1972
|
-
);
|
|
1973
|
-
} else {
|
|
1974
|
-
newContent = `${SCRIPT_IMPORT}
|
|
1975
|
-
|
|
1976
|
-
${newContent}`;
|
|
1977
|
-
}
|
|
1829
|
+
if (options2.activationMode) {
|
|
1830
|
+
parts.push(`activationMode: "${options2.activationMode}"`);
|
|
1978
1831
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
${scriptBlock}`
|
|
1832
|
+
if (options2.keyHoldDuration !== void 0) {
|
|
1833
|
+
parts.push(`keyHoldDuration: ${options2.keyHoldDuration}`);
|
|
1834
|
+
}
|
|
1835
|
+
if (options2.allowActivationInsideInput !== void 0) {
|
|
1836
|
+
parts.push(
|
|
1837
|
+
`allowActivationInsideInput: ${options2.allowActivationInsideInput}`
|
|
1986
1838
|
);
|
|
1987
|
-
} else {
|
|
1988
|
-
const htmlMatch = newContent.match(/<html[^>]*>/);
|
|
1989
|
-
if (htmlMatch) {
|
|
1990
|
-
newContent = newContent.replace(
|
|
1991
|
-
htmlMatch[0],
|
|
1992
|
-
`${htmlMatch[0]}
|
|
1993
|
-
<head>
|
|
1994
|
-
${scriptBlock}
|
|
1995
|
-
</head>`
|
|
1996
|
-
);
|
|
1997
|
-
}
|
|
1998
1839
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
originalContent,
|
|
2004
|
-
newContent
|
|
2005
|
-
};
|
|
1840
|
+
if (options2.maxContextLines !== void 0) {
|
|
1841
|
+
parts.push(`maxContextLines: ${options2.maxContextLines}`);
|
|
1842
|
+
}
|
|
1843
|
+
return `{ ${parts.join(", ")} }`;
|
|
2006
1844
|
};
|
|
2007
|
-
var
|
|
2008
|
-
const
|
|
2009
|
-
if (
|
|
2010
|
-
|
|
2011
|
-
success: false,
|
|
2012
|
-
filePath: "",
|
|
2013
|
-
message: 'Could not find pages/_document.tsx or pages/_document.jsx.\n\nTo set up React Grab with Pages Router, create pages/_document.tsx with:\n\n import { Html, Head, Main, NextScript } from "next/document";\n import Script from "next/script";\n\n export default function Document() {\n return (\n <Html>\n <Head>\n {process.env.NODE_ENV === "development" && (\n <Script src="//unpkg.com/react-grab/dist/index.global.js" strategy="beforeInteractive" />\n )}\n </Head>\n <body>\n <Main />\n <NextScript />\n </body>\n </Html>\n );\n }'
|
|
2014
|
-
};
|
|
1845
|
+
var formatOptionsAsJson = (options2) => {
|
|
1846
|
+
const cleanOptions = {};
|
|
1847
|
+
if (options2.activationKey) {
|
|
1848
|
+
cleanOptions.activationKey = options2.activationKey;
|
|
2015
1849
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
const hasReactGrabInFile2 = hasReactGrabCode(originalContent);
|
|
2019
|
-
const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
|
|
2020
|
-
if (hasReactGrabInFile2 && reactGrabAlreadyConfigured) {
|
|
2021
|
-
return addAgentToExistingNextApp(originalContent, agent, documentPath);
|
|
1850
|
+
if (options2.activationMode) {
|
|
1851
|
+
cleanOptions.activationMode = options2.activationMode;
|
|
2022
1852
|
}
|
|
2023
|
-
if (
|
|
2024
|
-
|
|
2025
|
-
success: true,
|
|
2026
|
-
filePath: documentPath,
|
|
2027
|
-
message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
|
|
2028
|
-
noChanges: true
|
|
2029
|
-
};
|
|
1853
|
+
if (options2.keyHoldDuration !== void 0) {
|
|
1854
|
+
cleanOptions.keyHoldDuration = options2.keyHoldDuration;
|
|
2030
1855
|
}
|
|
2031
|
-
if (
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
1856
|
+
if (options2.allowActivationInsideInput !== void 0) {
|
|
1857
|
+
cleanOptions.allowActivationInsideInput = options2.allowActivationInsideInput;
|
|
1858
|
+
}
|
|
1859
|
+
if (options2.maxContextLines !== void 0) {
|
|
1860
|
+
cleanOptions.maxContextLines = options2.maxContextLines;
|
|
1861
|
+
}
|
|
1862
|
+
return JSON.stringify(cleanOptions);
|
|
1863
|
+
};
|
|
1864
|
+
var findReactGrabFile = (projectRoot, framework, nextRouterType) => {
|
|
1865
|
+
switch (framework) {
|
|
1866
|
+
case "next":
|
|
1867
|
+
if (nextRouterType === "app") {
|
|
1868
|
+
return findLayoutFile(projectRoot);
|
|
1869
|
+
}
|
|
1870
|
+
return findDocumentFile(projectRoot);
|
|
1871
|
+
case "vite":
|
|
1872
|
+
return findIndexHtml(projectRoot);
|
|
1873
|
+
case "webpack":
|
|
1874
|
+
return findEntryFile(projectRoot);
|
|
1875
|
+
default:
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
var addOptionsToNextScript = (originalContent, options2, filePath) => {
|
|
1880
|
+
const reactGrabScriptMatch = originalContent.match(
|
|
1881
|
+
/(<Script[^>]*react-grab[^>]*)(\/?>)/is
|
|
1882
|
+
);
|
|
1883
|
+
if (!reactGrabScriptMatch) {
|
|
1884
|
+
return {
|
|
1885
|
+
success: false,
|
|
1886
|
+
filePath,
|
|
1887
|
+
message: "Could not find React Grab Script tag"
|
|
1888
|
+
};
|
|
2040
1889
|
}
|
|
2041
|
-
const
|
|
2042
|
-
const
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
1890
|
+
const scriptTag = reactGrabScriptMatch[0];
|
|
1891
|
+
const scriptOpening = reactGrabScriptMatch[1];
|
|
1892
|
+
const scriptClosing = reactGrabScriptMatch[2];
|
|
1893
|
+
const existingDataOptionsMatch = scriptTag.match(
|
|
1894
|
+
/data-options=\{JSON\.stringify\([^)]+\)\}/
|
|
1895
|
+
);
|
|
1896
|
+
const dataOptionsAttr = `data-options={JSON.stringify(
|
|
1897
|
+
${formatOptionsForNextjs(options2)}
|
|
1898
|
+
)}`;
|
|
1899
|
+
let newScriptTag;
|
|
1900
|
+
if (existingDataOptionsMatch) {
|
|
1901
|
+
newScriptTag = scriptTag.replace(
|
|
1902
|
+
existingDataOptionsMatch[0],
|
|
1903
|
+
dataOptionsAttr
|
|
2048
1904
|
);
|
|
1905
|
+
} else {
|
|
1906
|
+
newScriptTag = `${scriptOpening}
|
|
1907
|
+
${dataOptionsAttr}
|
|
1908
|
+
${scriptClosing}`;
|
|
2049
1909
|
}
|
|
1910
|
+
const newContent = originalContent.replace(scriptTag, newScriptTag);
|
|
2050
1911
|
return {
|
|
2051
1912
|
success: true,
|
|
2052
|
-
filePath
|
|
2053
|
-
message: "
|
|
1913
|
+
filePath,
|
|
1914
|
+
message: "Update React Grab options",
|
|
2054
1915
|
originalContent,
|
|
2055
1916
|
newContent
|
|
2056
1917
|
};
|
|
2057
1918
|
};
|
|
2058
|
-
var
|
|
2059
|
-
const
|
|
2060
|
-
|
|
1919
|
+
var addOptionsToViteScript = (originalContent, options2, filePath) => {
|
|
1920
|
+
const reactGrabImportMatch = originalContent.match(
|
|
1921
|
+
/import\s*\(\s*["']react-grab["']\s*\)/
|
|
1922
|
+
);
|
|
1923
|
+
if (!reactGrabImportMatch) {
|
|
2061
1924
|
return {
|
|
2062
1925
|
success: false,
|
|
2063
|
-
filePath
|
|
2064
|
-
message: "Could not find
|
|
1926
|
+
filePath,
|
|
1927
|
+
message: "Could not find React Grab import"
|
|
2065
1928
|
};
|
|
2066
1929
|
}
|
|
2067
|
-
const
|
|
2068
|
-
|
|
2069
|
-
const
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
1930
|
+
const optionsJson = formatOptionsAsJson(options2);
|
|
1931
|
+
const newImport = `import("react-grab").then((m) => m.init(${optionsJson}))`;
|
|
1932
|
+
const newContent = originalContent.replace(
|
|
1933
|
+
reactGrabImportMatch[0],
|
|
1934
|
+
newImport
|
|
1935
|
+
);
|
|
1936
|
+
return {
|
|
1937
|
+
success: true,
|
|
1938
|
+
filePath,
|
|
1939
|
+
message: "Update React Grab options",
|
|
1940
|
+
originalContent,
|
|
1941
|
+
newContent
|
|
1942
|
+
};
|
|
1943
|
+
};
|
|
1944
|
+
var addOptionsToWebpackImport = (originalContent, options2, filePath) => {
|
|
1945
|
+
const reactGrabImportMatch = originalContent.match(
|
|
1946
|
+
/import\s*\(\s*["']react-grab["']\s*\)/
|
|
1947
|
+
);
|
|
1948
|
+
if (!reactGrabImportMatch) {
|
|
2074
1949
|
return {
|
|
2075
|
-
success:
|
|
2076
|
-
filePath
|
|
2077
|
-
message: "
|
|
2078
|
-
noChanges: true
|
|
1950
|
+
success: false,
|
|
1951
|
+
filePath,
|
|
1952
|
+
message: "Could not find React Grab import"
|
|
2079
1953
|
};
|
|
2080
1954
|
}
|
|
2081
|
-
const
|
|
2082
|
-
const
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
${scriptBlock}`
|
|
2088
|
-
);
|
|
2089
|
-
}
|
|
1955
|
+
const optionsJson = formatOptionsAsJson(options2);
|
|
1956
|
+
const newImport = `import("react-grab").then((m) => m.init(${optionsJson}))`;
|
|
1957
|
+
const newContent = originalContent.replace(
|
|
1958
|
+
reactGrabImportMatch[0],
|
|
1959
|
+
newImport
|
|
1960
|
+
);
|
|
2090
1961
|
return {
|
|
2091
1962
|
success: true,
|
|
2092
|
-
filePath
|
|
2093
|
-
message: "
|
|
1963
|
+
filePath,
|
|
1964
|
+
message: "Update React Grab options",
|
|
2094
1965
|
originalContent,
|
|
2095
1966
|
newContent
|
|
2096
1967
|
};
|
|
2097
1968
|
};
|
|
2098
|
-
var
|
|
2099
|
-
const
|
|
2100
|
-
if (!
|
|
1969
|
+
var previewOptionsTransform = (projectRoot, framework, nextRouterType, options2) => {
|
|
1970
|
+
const filePath = findReactGrabFile(projectRoot, framework, nextRouterType);
|
|
1971
|
+
if (!filePath) {
|
|
2101
1972
|
return {
|
|
2102
1973
|
success: false,
|
|
2103
1974
|
filePath: "",
|
|
2104
|
-
message: "Could not find
|
|
1975
|
+
message: "Could not find file containing React Grab configuration"
|
|
2105
1976
|
};
|
|
2106
1977
|
}
|
|
2107
|
-
const originalContent = readFileSync(
|
|
2108
|
-
|
|
2109
|
-
if (hasReactGrabInFile2 && reactGrabAlreadyConfigured) {
|
|
2110
|
-
return addAgentToExistingWebpack(originalContent, agent, entryPath);
|
|
2111
|
-
}
|
|
2112
|
-
if (hasReactGrabInFile2) {
|
|
1978
|
+
const originalContent = readFileSync(filePath, "utf-8");
|
|
1979
|
+
if (!hasReactGrabCode(originalContent)) {
|
|
2113
1980
|
return {
|
|
2114
|
-
success:
|
|
2115
|
-
filePath
|
|
2116
|
-
message: "React Grab
|
|
2117
|
-
noChanges: true
|
|
1981
|
+
success: false,
|
|
1982
|
+
filePath,
|
|
1983
|
+
message: "Could not find React Grab code in the file"
|
|
2118
1984
|
};
|
|
2119
1985
|
}
|
|
2120
|
-
const importBlock = WEBPACK_IMPORT_WITH_AGENT(agent);
|
|
2121
|
-
const newContent = `${importBlock}
|
|
2122
|
-
|
|
2123
|
-
${originalContent}`;
|
|
2124
|
-
return {
|
|
2125
|
-
success: true,
|
|
2126
|
-
filePath: entryPath,
|
|
2127
|
-
message: "Add React Grab" + (agent !== "none" ? ` with ${agent} agent` : ""),
|
|
2128
|
-
originalContent,
|
|
2129
|
-
newContent
|
|
2130
|
-
};
|
|
2131
|
-
};
|
|
2132
|
-
var previewTransform = (projectRoot, framework, nextRouterType, agent, reactGrabAlreadyConfigured = false) => {
|
|
2133
1986
|
switch (framework) {
|
|
2134
1987
|
case "next":
|
|
2135
|
-
|
|
2136
|
-
return transformNextAppRouter(
|
|
2137
|
-
projectRoot,
|
|
2138
|
-
agent,
|
|
2139
|
-
reactGrabAlreadyConfigured
|
|
2140
|
-
);
|
|
2141
|
-
}
|
|
2142
|
-
return transformNextPagesRouter(
|
|
2143
|
-
projectRoot,
|
|
2144
|
-
agent,
|
|
2145
|
-
reactGrabAlreadyConfigured
|
|
2146
|
-
);
|
|
1988
|
+
return addOptionsToNextScript(originalContent, options2, filePath);
|
|
2147
1989
|
case "vite":
|
|
2148
|
-
return
|
|
1990
|
+
return addOptionsToViteScript(originalContent, options2, filePath);
|
|
2149
1991
|
case "webpack":
|
|
2150
|
-
return
|
|
1992
|
+
return addOptionsToWebpackImport(originalContent, options2, filePath);
|
|
2151
1993
|
default:
|
|
2152
1994
|
return {
|
|
2153
1995
|
success: false,
|
|
2154
|
-
filePath
|
|
2155
|
-
message: `Unknown framework: ${framework}
|
|
1996
|
+
filePath,
|
|
1997
|
+
message: `Unknown framework: ${framework}`
|
|
2156
1998
|
};
|
|
2157
1999
|
}
|
|
2158
2000
|
};
|
|
2159
|
-
var
|
|
2160
|
-
|
|
2161
|
-
accessSync(filePath, constants.W_OK);
|
|
2162
|
-
return true;
|
|
2163
|
-
} catch {
|
|
2164
|
-
return false;
|
|
2165
|
-
}
|
|
2001
|
+
var applyOptionsTransform = (result) => {
|
|
2002
|
+
return applyTransform(result);
|
|
2166
2003
|
};
|
|
2167
|
-
var
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
writeFileSync(result.filePath, result.newContent);
|
|
2177
|
-
return { success: true };
|
|
2178
|
-
} catch (error48) {
|
|
2179
|
-
return {
|
|
2180
|
-
success: false,
|
|
2181
|
-
error: `Failed to write to ${result.filePath}: ${error48 instanceof Error ? error48.message : "Unknown error"}`
|
|
2182
|
-
};
|
|
2183
|
-
}
|
|
2004
|
+
var removeAgentFromNextApp = (originalContent, agent, filePath) => {
|
|
2005
|
+
const agentPackage = `@react-grab/${agent}`;
|
|
2006
|
+
if (!originalContent.includes(agentPackage)) {
|
|
2007
|
+
return {
|
|
2008
|
+
success: true,
|
|
2009
|
+
filePath,
|
|
2010
|
+
message: `Agent ${agent} is not configured in this file`,
|
|
2011
|
+
noChanges: true
|
|
2012
|
+
};
|
|
2184
2013
|
}
|
|
2185
|
-
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
default:
|
|
2197
|
-
return "npx";
|
|
2014
|
+
const agentScriptPattern = new RegExp(
|
|
2015
|
+
`\\s*\\{process\\.env\\.NODE_ENV === "development" && \\(\\s*<Script[^>]*${agentPackage.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^>]*\\/>\\s*\\)\\}`,
|
|
2016
|
+
"gs"
|
|
2017
|
+
);
|
|
2018
|
+
const simpleScriptPattern = new RegExp(
|
|
2019
|
+
`\\s*<Script[^>]*${agentPackage.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^>]*\\/>`,
|
|
2020
|
+
"gi"
|
|
2021
|
+
);
|
|
2022
|
+
let newContent = originalContent.replace(agentScriptPattern, "");
|
|
2023
|
+
if (newContent === originalContent) {
|
|
2024
|
+
newContent = originalContent.replace(simpleScriptPattern, "");
|
|
2198
2025
|
}
|
|
2026
|
+
if (newContent === originalContent) {
|
|
2027
|
+
return {
|
|
2028
|
+
success: false,
|
|
2029
|
+
filePath,
|
|
2030
|
+
message: `Could not find agent ${agent} script to remove`
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
return {
|
|
2034
|
+
success: true,
|
|
2035
|
+
filePath,
|
|
2036
|
+
message: `Remove ${agent} agent`,
|
|
2037
|
+
originalContent,
|
|
2038
|
+
newContent
|
|
2039
|
+
};
|
|
2199
2040
|
};
|
|
2200
|
-
var
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2041
|
+
var removeAgentFromVite = (originalContent, agent, filePath) => {
|
|
2042
|
+
const agentPackage = `@react-grab/${agent}`;
|
|
2043
|
+
if (!originalContent.includes(agentPackage)) {
|
|
2044
|
+
return {
|
|
2045
|
+
success: true,
|
|
2046
|
+
filePath,
|
|
2047
|
+
message: `Agent ${agent} is not configured in this file`,
|
|
2048
|
+
noChanges: true
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
const agentImportPattern = new RegExp(
|
|
2052
|
+
`\\s*import\\s*\\(\\s*["']${agentPackage.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/client["']\\s*\\);?`,
|
|
2053
|
+
"g"
|
|
2054
|
+
);
|
|
2055
|
+
const newContent = originalContent.replace(agentImportPattern, "");
|
|
2056
|
+
if (newContent === originalContent) {
|
|
2057
|
+
return {
|
|
2058
|
+
success: false,
|
|
2059
|
+
filePath,
|
|
2060
|
+
message: `Could not find agent ${agent} import to remove`
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
return {
|
|
2064
|
+
success: true,
|
|
2065
|
+
filePath,
|
|
2066
|
+
message: `Remove ${agent} agent`,
|
|
2067
|
+
originalContent,
|
|
2068
|
+
newContent
|
|
2069
|
+
};
|
|
2213
2070
|
};
|
|
2214
|
-
var
|
|
2215
|
-
const agentPackage =
|
|
2216
|
-
if (!agentPackage)
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2071
|
+
var removeAgentFromWebpack = (originalContent, agent, filePath) => {
|
|
2072
|
+
const agentPackage = `@react-grab/${agent}`;
|
|
2073
|
+
if (!originalContent.includes(agentPackage)) {
|
|
2074
|
+
return {
|
|
2075
|
+
success: true,
|
|
2076
|
+
filePath,
|
|
2077
|
+
message: `Agent ${agent} is not configured in this file`,
|
|
2078
|
+
noChanges: true
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
const agentImportPattern = new RegExp(
|
|
2082
|
+
`\\s*import\\s*\\(\\s*["']${agentPackage.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/client["']\\s*\\);?`,
|
|
2083
|
+
"g"
|
|
2084
|
+
);
|
|
2085
|
+
const newContent = originalContent.replace(agentImportPattern, "");
|
|
2086
|
+
if (newContent === originalContent) {
|
|
2087
|
+
return {
|
|
2088
|
+
success: false,
|
|
2089
|
+
filePath,
|
|
2090
|
+
message: `Could not find agent ${agent} import to remove`
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
return {
|
|
2094
|
+
success: true,
|
|
2095
|
+
filePath,
|
|
2096
|
+
message: `Remove ${agent} agent`,
|
|
2097
|
+
originalContent,
|
|
2098
|
+
newContent
|
|
2099
|
+
};
|
|
2223
2100
|
};
|
|
2224
|
-
var
|
|
2225
|
-
|
|
2101
|
+
var previewAgentRemoval = (projectRoot, framework, nextRouterType, agent) => {
|
|
2102
|
+
const filePath = findReactGrabFile(projectRoot, framework, nextRouterType);
|
|
2103
|
+
if (!filePath) {
|
|
2226
2104
|
return {
|
|
2227
2105
|
success: true,
|
|
2228
2106
|
filePath: "",
|
|
2229
|
-
message:
|
|
2107
|
+
message: "Could not find file containing React Grab configuration",
|
|
2230
2108
|
noChanges: true
|
|
2231
2109
|
};
|
|
2232
2110
|
}
|
|
2111
|
+
const originalContent = readFileSync(filePath, "utf-8");
|
|
2112
|
+
switch (framework) {
|
|
2113
|
+
case "next":
|
|
2114
|
+
return removeAgentFromNextApp(originalContent, agent, filePath);
|
|
2115
|
+
case "vite":
|
|
2116
|
+
return removeAgentFromVite(originalContent, agent, filePath);
|
|
2117
|
+
case "webpack":
|
|
2118
|
+
return removeAgentFromWebpack(originalContent, agent, filePath);
|
|
2119
|
+
default:
|
|
2120
|
+
return {
|
|
2121
|
+
success: false,
|
|
2122
|
+
filePath,
|
|
2123
|
+
message: `Unknown framework: ${framework}`
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
};
|
|
2127
|
+
var previewPackageJsonAgentRemoval = (projectRoot, agent) => {
|
|
2233
2128
|
const packageJsonPath = join(projectRoot, "package.json");
|
|
2234
2129
|
if (!existsSync(packageJsonPath)) {
|
|
2235
2130
|
return {
|
|
2236
|
-
success:
|
|
2131
|
+
success: true,
|
|
2237
2132
|
filePath: "",
|
|
2238
|
-
message: "Could not find package.json"
|
|
2133
|
+
message: "Could not find package.json",
|
|
2134
|
+
noChanges: true
|
|
2239
2135
|
};
|
|
2240
2136
|
}
|
|
2241
2137
|
const originalContent = readFileSync(packageJsonPath, "utf-8");
|
|
2242
|
-
const
|
|
2243
|
-
if (
|
|
2138
|
+
const allPrefixVariants = getAllAgentPrefixVariants(agent);
|
|
2139
|
+
if (allPrefixVariants.length === 0) {
|
|
2244
2140
|
return {
|
|
2245
|
-
success:
|
|
2141
|
+
success: true,
|
|
2246
2142
|
filePath: packageJsonPath,
|
|
2247
|
-
message: `Unknown agent: ${agent}
|
|
2143
|
+
message: `Unknown agent: ${agent}`,
|
|
2144
|
+
noChanges: true
|
|
2248
2145
|
};
|
|
2249
2146
|
}
|
|
2250
|
-
const
|
|
2251
|
-
const hasExistingPrefix = allPrefixVariants.some(
|
|
2147
|
+
const hasAnyPrefix = allPrefixVariants.some(
|
|
2252
2148
|
(prefix) => originalContent.includes(prefix)
|
|
2253
2149
|
);
|
|
2254
|
-
if (
|
|
2150
|
+
if (!hasAnyPrefix) {
|
|
2255
2151
|
return {
|
|
2256
2152
|
success: true,
|
|
2257
2153
|
filePath: packageJsonPath,
|
|
2258
|
-
message: `Agent ${agent} dev script is
|
|
2154
|
+
message: `Agent ${agent} dev script is not configured`,
|
|
2259
2155
|
noChanges: true
|
|
2260
2156
|
};
|
|
2261
2157
|
}
|
|
2262
2158
|
try {
|
|
2263
2159
|
const packageJson = JSON.parse(originalContent);
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
(
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
success: true,
|
|
2274
|
-
filePath: packageJsonPath,
|
|
2275
|
-
message: "No dev script found in package.json",
|
|
2276
|
-
noChanges: true,
|
|
2277
|
-
warning: `Could not inject agent into package.json (no dev script found).
|
|
2278
|
-
Run this command manually before starting your dev server:
|
|
2279
|
-
${agentPrefix} <your dev command>`
|
|
2280
|
-
};
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
const currentDevScript = packageJson.scripts[targetScriptKey];
|
|
2284
|
-
for (const installedAgent of installedAgents) {
|
|
2285
|
-
const installedPrefixVariants = getAllAgentPrefixVariants(installedAgent);
|
|
2286
|
-
const hasInstalledAgentPrefix = installedPrefixVariants.some(
|
|
2287
|
-
(prefix) => currentDevScript.includes(prefix)
|
|
2288
|
-
);
|
|
2289
|
-
if (hasInstalledAgentPrefix) {
|
|
2290
|
-
return {
|
|
2291
|
-
success: true,
|
|
2292
|
-
filePath: packageJsonPath,
|
|
2293
|
-
message: `Agent ${installedAgent} is already in ${targetScriptKey} script`,
|
|
2294
|
-
noChanges: true
|
|
2295
|
-
};
|
|
2160
|
+
for (const scriptKey of Object.keys(packageJson.scripts || {})) {
|
|
2161
|
+
let scriptValue = packageJson.scripts[scriptKey];
|
|
2162
|
+
if (typeof scriptValue === "string") {
|
|
2163
|
+
for (const prefix of allPrefixVariants) {
|
|
2164
|
+
if (scriptValue.includes(prefix)) {
|
|
2165
|
+
scriptValue = scriptValue.replace(prefix + " ", "").replace(prefix, "");
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
packageJson.scripts[scriptKey] = scriptValue;
|
|
2296
2169
|
}
|
|
2297
2170
|
}
|
|
2298
|
-
packageJson.scripts[targetScriptKey] = `${agentPrefix} ${currentDevScript}`;
|
|
2299
2171
|
const newContent = JSON.stringify(packageJson, null, 2) + "\n";
|
|
2300
2172
|
return {
|
|
2301
2173
|
success: true,
|
|
2302
2174
|
filePath: packageJsonPath,
|
|
2303
|
-
message: `
|
|
2175
|
+
message: `Remove ${agent} server from dev script`,
|
|
2304
2176
|
originalContent,
|
|
2305
2177
|
newContent
|
|
2306
2178
|
};
|
|
@@ -2312,393 +2184,596 @@ Run this command manually before starting your dev server:
|
|
|
2312
2184
|
};
|
|
2313
2185
|
}
|
|
2314
2186
|
};
|
|
2315
|
-
var
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2187
|
+
var highlighter = {
|
|
2188
|
+
error: pc.red,
|
|
2189
|
+
warn: pc.yellow,
|
|
2190
|
+
info: pc.cyan,
|
|
2191
|
+
success: pc.green,
|
|
2192
|
+
dim: pc.dim
|
|
2193
|
+
};
|
|
2194
|
+
|
|
2195
|
+
// src/utils/logger.ts
|
|
2196
|
+
var logger = {
|
|
2197
|
+
error(...args) {
|
|
2198
|
+
console.log(highlighter.error(args.join(" ")));
|
|
2199
|
+
},
|
|
2200
|
+
warn(...args) {
|
|
2201
|
+
console.log(highlighter.warn(args.join(" ")));
|
|
2202
|
+
},
|
|
2203
|
+
info(...args) {
|
|
2204
|
+
console.log(highlighter.info(args.join(" ")));
|
|
2205
|
+
},
|
|
2206
|
+
success(...args) {
|
|
2207
|
+
console.log(highlighter.success(args.join(" ")));
|
|
2208
|
+
},
|
|
2209
|
+
dim(...args) {
|
|
2210
|
+
console.log(highlighter.dim(args.join(" ")));
|
|
2211
|
+
},
|
|
2212
|
+
log(...args) {
|
|
2213
|
+
console.log(args.join(" "));
|
|
2214
|
+
},
|
|
2215
|
+
break() {
|
|
2216
|
+
console.log("");
|
|
2332
2217
|
}
|
|
2333
|
-
return { success: true };
|
|
2334
2218
|
};
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2219
|
+
|
|
2220
|
+
// src/utils/handle-error.ts
|
|
2221
|
+
var handleError = (error48) => {
|
|
2222
|
+
logger.break();
|
|
2223
|
+
logger.error(
|
|
2224
|
+
"Something went wrong. Please check the error below for more details."
|
|
2225
|
+
);
|
|
2226
|
+
logger.error("If the problem persists, please open an issue on GitHub.");
|
|
2227
|
+
logger.error("");
|
|
2228
|
+
if (error48 instanceof Error) {
|
|
2229
|
+
logger.error(error48.message);
|
|
2339
2230
|
}
|
|
2340
|
-
|
|
2341
|
-
|
|
2231
|
+
logger.break();
|
|
2232
|
+
process.exit(1);
|
|
2233
|
+
};
|
|
2234
|
+
var INSTALL_COMMANDS = {
|
|
2235
|
+
npm: "npm install",
|
|
2236
|
+
yarn: "yarn add",
|
|
2237
|
+
pnpm: "pnpm add",
|
|
2238
|
+
bun: "bun add"
|
|
2239
|
+
};
|
|
2240
|
+
var UNINSTALL_COMMANDS = {
|
|
2241
|
+
npm: "npm uninstall",
|
|
2242
|
+
yarn: "yarn remove",
|
|
2243
|
+
pnpm: "pnpm remove",
|
|
2244
|
+
bun: "bun remove"
|
|
2245
|
+
};
|
|
2246
|
+
var installPackages = (packages, packageManager, projectRoot, isDev = true) => {
|
|
2247
|
+
if (packages.length === 0) {
|
|
2248
|
+
return;
|
|
2342
2249
|
}
|
|
2343
|
-
|
|
2344
|
-
|
|
2250
|
+
const command = INSTALL_COMMANDS[packageManager];
|
|
2251
|
+
const devFlag = isDev ? " -D" : "";
|
|
2252
|
+
const fullCommand = `${command}${devFlag} ${packages.join(" ")}`;
|
|
2253
|
+
console.log(`Running: ${fullCommand}
|
|
2254
|
+
`);
|
|
2255
|
+
execSync(fullCommand, {
|
|
2256
|
+
cwd: projectRoot,
|
|
2257
|
+
stdio: "inherit"
|
|
2258
|
+
});
|
|
2259
|
+
};
|
|
2260
|
+
var getPackagesToInstall = (agent, includeReactGrab = true) => {
|
|
2261
|
+
const packages = [];
|
|
2262
|
+
if (includeReactGrab) {
|
|
2263
|
+
packages.push("react-grab");
|
|
2345
2264
|
}
|
|
2346
|
-
if (
|
|
2347
|
-
|
|
2348
|
-
`allowActivationInsideInput: ${options2.allowActivationInsideInput}`
|
|
2349
|
-
);
|
|
2265
|
+
if (agent !== "none") {
|
|
2266
|
+
packages.push(`@react-grab/${agent}`);
|
|
2350
2267
|
}
|
|
2351
|
-
|
|
2352
|
-
|
|
2268
|
+
return packages;
|
|
2269
|
+
};
|
|
2270
|
+
var uninstallPackages = (packages, packageManager, projectRoot) => {
|
|
2271
|
+
if (packages.length === 0) {
|
|
2272
|
+
return;
|
|
2353
2273
|
}
|
|
2354
|
-
|
|
2274
|
+
const command = UNINSTALL_COMMANDS[packageManager];
|
|
2275
|
+
const fullCommand = `${command} ${packages.join(" ")}`;
|
|
2276
|
+
console.log(`Running: ${fullCommand}
|
|
2277
|
+
`);
|
|
2278
|
+
execSync(fullCommand, {
|
|
2279
|
+
cwd: projectRoot,
|
|
2280
|
+
stdio: "inherit"
|
|
2281
|
+
});
|
|
2355
2282
|
};
|
|
2356
|
-
var
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2283
|
+
var getPackagesToUninstall = (agent) => {
|
|
2284
|
+
return [`@react-grab/${agent}`];
|
|
2285
|
+
};
|
|
2286
|
+
var spinner = (text, options2) => ora({ text, isSilent: options2?.silent });
|
|
2287
|
+
var detectSkillAgents = (cwd) => {
|
|
2288
|
+
return SKILL_AGENTS.filter((agent) => existsSync(join(cwd, agent.folder)));
|
|
2289
|
+
};
|
|
2290
|
+
var findSkillAgent = (id) => {
|
|
2291
|
+
return SKILL_AGENTS.find((agent) => agent.id === id);
|
|
2292
|
+
};
|
|
2293
|
+
var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] ?? agent).join(", ");
|
|
2294
|
+
var applyTransformWithFeedback = (result, message) => {
|
|
2295
|
+
const writeSpinner = spinner(message ?? `Applying changes to ${result.filePath}.`).start();
|
|
2296
|
+
const writeResult = applyTransform(result);
|
|
2297
|
+
if (!writeResult.success) {
|
|
2298
|
+
writeSpinner.fail();
|
|
2299
|
+
logger.break();
|
|
2300
|
+
logger.error(writeResult.error || "Failed to write file.");
|
|
2301
|
+
logger.break();
|
|
2302
|
+
process.exit(1);
|
|
2363
2303
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2304
|
+
writeSpinner.succeed();
|
|
2305
|
+
};
|
|
2306
|
+
var applyPackageJsonWithFeedback = (result, message) => {
|
|
2307
|
+
const writeSpinner = spinner(message ?? `Applying changes to ${result.filePath}.`).start();
|
|
2308
|
+
const writeResult = applyPackageJsonTransform(result);
|
|
2309
|
+
if (!writeResult.success) {
|
|
2310
|
+
writeSpinner.fail();
|
|
2311
|
+
logger.break();
|
|
2312
|
+
logger.error(writeResult.error || "Failed to write file.");
|
|
2313
|
+
logger.break();
|
|
2314
|
+
process.exit(1);
|
|
2366
2315
|
}
|
|
2367
|
-
|
|
2368
|
-
|
|
2316
|
+
writeSpinner.succeed();
|
|
2317
|
+
};
|
|
2318
|
+
var installPackagesWithFeedback = (packages, packageManager, projectRoot) => {
|
|
2319
|
+
if (packages.length === 0) return;
|
|
2320
|
+
const installSpinner = spinner(`Installing ${packages.join(", ")}.`).start();
|
|
2321
|
+
try {
|
|
2322
|
+
installPackages(packages, packageManager, projectRoot);
|
|
2323
|
+
installSpinner.succeed();
|
|
2324
|
+
} catch (error48) {
|
|
2325
|
+
installSpinner.fail();
|
|
2326
|
+
handleError(error48);
|
|
2369
2327
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2328
|
+
};
|
|
2329
|
+
var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
|
|
2330
|
+
if (packages.length === 0) return;
|
|
2331
|
+
const uninstallSpinner = spinner(`Removing ${packages.join(", ")}.`).start();
|
|
2332
|
+
try {
|
|
2333
|
+
uninstallPackages(packages, packageManager, projectRoot);
|
|
2334
|
+
uninstallSpinner.succeed();
|
|
2335
|
+
} catch (error48) {
|
|
2336
|
+
uninstallSpinner.fail();
|
|
2337
|
+
handleError(error48);
|
|
2372
2338
|
}
|
|
2373
|
-
return JSON.stringify(cleanOptions);
|
|
2374
2339
|
};
|
|
2375
|
-
var
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
return findLayoutFile(projectRoot);
|
|
2380
|
-
}
|
|
2381
|
-
return findDocumentFile(projectRoot);
|
|
2382
|
-
case "vite":
|
|
2383
|
-
return findIndexHtml(projectRoot);
|
|
2384
|
-
case "webpack":
|
|
2385
|
-
return findEntryFile(projectRoot);
|
|
2386
|
-
default:
|
|
2387
|
-
return null;
|
|
2340
|
+
var detectPackageManager = async (projectRoot) => {
|
|
2341
|
+
const detected = await detect({ cwd: projectRoot });
|
|
2342
|
+
if (detected && ["npm", "yarn", "pnpm", "bun"].includes(detected)) {
|
|
2343
|
+
return detected;
|
|
2388
2344
|
}
|
|
2345
|
+
return "npm";
|
|
2389
2346
|
};
|
|
2390
|
-
var
|
|
2391
|
-
const
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
if (!reactGrabScriptMatch) {
|
|
2395
|
-
return {
|
|
2396
|
-
success: false,
|
|
2397
|
-
filePath,
|
|
2398
|
-
message: "Could not find React Grab Script tag"
|
|
2399
|
-
};
|
|
2347
|
+
var detectFramework = (projectRoot) => {
|
|
2348
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
2349
|
+
if (!existsSync(packageJsonPath)) {
|
|
2350
|
+
return "unknown";
|
|
2400
2351
|
}
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
${scriptClosing}`;
|
|
2352
|
+
try {
|
|
2353
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
2354
|
+
const allDependencies = {
|
|
2355
|
+
...packageJson.dependencies,
|
|
2356
|
+
...packageJson.devDependencies
|
|
2357
|
+
};
|
|
2358
|
+
if (allDependencies["next"]) {
|
|
2359
|
+
return "next";
|
|
2360
|
+
}
|
|
2361
|
+
if (allDependencies["vite"]) {
|
|
2362
|
+
return "vite";
|
|
2363
|
+
}
|
|
2364
|
+
if (allDependencies["webpack"]) {
|
|
2365
|
+
return "webpack";
|
|
2366
|
+
}
|
|
2367
|
+
return "unknown";
|
|
2368
|
+
} catch {
|
|
2369
|
+
return "unknown";
|
|
2420
2370
|
}
|
|
2421
|
-
const newContent = originalContent.replace(scriptTag, newScriptTag);
|
|
2422
|
-
return {
|
|
2423
|
-
success: true,
|
|
2424
|
-
filePath,
|
|
2425
|
-
message: "Update React Grab options",
|
|
2426
|
-
originalContent,
|
|
2427
|
-
newContent
|
|
2428
|
-
};
|
|
2429
2371
|
};
|
|
2430
|
-
var
|
|
2431
|
-
const
|
|
2432
|
-
|
|
2433
|
-
);
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
filePath,
|
|
2438
|
-
message: "Could not find React Grab import"
|
|
2439
|
-
};
|
|
2372
|
+
var detectNextRouterType = (projectRoot) => {
|
|
2373
|
+
const hasAppDir = existsSync(join(projectRoot, "app"));
|
|
2374
|
+
const hasSrcAppDir = existsSync(join(projectRoot, "src", "app"));
|
|
2375
|
+
const hasPagesDir = existsSync(join(projectRoot, "pages"));
|
|
2376
|
+
const hasSrcPagesDir = existsSync(join(projectRoot, "src", "pages"));
|
|
2377
|
+
if (hasAppDir || hasSrcAppDir) {
|
|
2378
|
+
return "app";
|
|
2440
2379
|
}
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
newImport
|
|
2446
|
-
);
|
|
2447
|
-
return {
|
|
2448
|
-
success: true,
|
|
2449
|
-
filePath,
|
|
2450
|
-
message: "Update React Grab options",
|
|
2451
|
-
originalContent,
|
|
2452
|
-
newContent
|
|
2453
|
-
};
|
|
2380
|
+
if (hasPagesDir || hasSrcPagesDir) {
|
|
2381
|
+
return "pages";
|
|
2382
|
+
}
|
|
2383
|
+
return "unknown";
|
|
2454
2384
|
};
|
|
2455
|
-
var
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
);
|
|
2459
|
-
if (!reactGrabImportMatch) {
|
|
2460
|
-
return {
|
|
2461
|
-
success: false,
|
|
2462
|
-
filePath,
|
|
2463
|
-
message: "Could not find React Grab import"
|
|
2464
|
-
};
|
|
2385
|
+
var detectMonorepo = (projectRoot) => {
|
|
2386
|
+
if (existsSync(join(projectRoot, "pnpm-workspace.yaml"))) {
|
|
2387
|
+
return true;
|
|
2465
2388
|
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2389
|
+
if (existsSync(join(projectRoot, "lerna.json"))) {
|
|
2390
|
+
return true;
|
|
2391
|
+
}
|
|
2392
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
2393
|
+
if (existsSync(packageJsonPath)) {
|
|
2394
|
+
try {
|
|
2395
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
2396
|
+
if (packageJson.workspaces) {
|
|
2397
|
+
return true;
|
|
2398
|
+
}
|
|
2399
|
+
} catch {
|
|
2400
|
+
return false;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
return false;
|
|
2479
2404
|
};
|
|
2480
|
-
var
|
|
2481
|
-
const
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2405
|
+
var getWorkspacePatterns = (projectRoot) => {
|
|
2406
|
+
const patterns = [];
|
|
2407
|
+
const pnpmWorkspacePath = join(projectRoot, "pnpm-workspace.yaml");
|
|
2408
|
+
if (existsSync(pnpmWorkspacePath)) {
|
|
2409
|
+
const content = readFileSync(pnpmWorkspacePath, "utf-8");
|
|
2410
|
+
const lines = content.split("\n");
|
|
2411
|
+
let inPackages = false;
|
|
2412
|
+
for (const line of lines) {
|
|
2413
|
+
if (line.match(/^packages:\s*$/)) {
|
|
2414
|
+
inPackages = true;
|
|
2415
|
+
continue;
|
|
2416
|
+
}
|
|
2417
|
+
if (inPackages) {
|
|
2418
|
+
if (line.match(/^[a-zA-Z]/) || line.trim() === "") {
|
|
2419
|
+
if (line.match(/^[a-zA-Z]/)) inPackages = false;
|
|
2420
|
+
continue;
|
|
2421
|
+
}
|
|
2422
|
+
const match = line.match(/^\s*-\s*['"]?([^'"#\n]+?)['"]?\s*$/);
|
|
2423
|
+
if (match) {
|
|
2424
|
+
patterns.push(match[1].trim());
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2488
2428
|
}
|
|
2489
|
-
const
|
|
2490
|
-
if (
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2429
|
+
const lernaJsonPath = join(projectRoot, "lerna.json");
|
|
2430
|
+
if (existsSync(lernaJsonPath)) {
|
|
2431
|
+
try {
|
|
2432
|
+
const lernaJson = JSON.parse(readFileSync(lernaJsonPath, "utf-8"));
|
|
2433
|
+
if (Array.isArray(lernaJson.packages)) {
|
|
2434
|
+
patterns.push(...lernaJson.packages);
|
|
2435
|
+
}
|
|
2436
|
+
} catch {
|
|
2437
|
+
}
|
|
2496
2438
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
message: `Unknown framework: ${framework}`
|
|
2509
|
-
};
|
|
2439
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
2440
|
+
if (existsSync(packageJsonPath)) {
|
|
2441
|
+
try {
|
|
2442
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
2443
|
+
if (Array.isArray(packageJson.workspaces)) {
|
|
2444
|
+
patterns.push(...packageJson.workspaces);
|
|
2445
|
+
} else if (packageJson.workspaces?.packages) {
|
|
2446
|
+
patterns.push(...packageJson.workspaces.packages);
|
|
2447
|
+
}
|
|
2448
|
+
} catch {
|
|
2449
|
+
}
|
|
2510
2450
|
}
|
|
2451
|
+
return [...new Set(patterns)];
|
|
2511
2452
|
};
|
|
2512
|
-
var
|
|
2513
|
-
|
|
2453
|
+
var expandWorkspacePattern = (projectRoot, pattern) => {
|
|
2454
|
+
const results = [];
|
|
2455
|
+
const cleanPattern = pattern.replace(/\/\*$/, "");
|
|
2456
|
+
const basePath = join(projectRoot, cleanPattern);
|
|
2457
|
+
if (!existsSync(basePath)) return results;
|
|
2458
|
+
try {
|
|
2459
|
+
const entries = readdirSync(basePath, { withFileTypes: true });
|
|
2460
|
+
for (const entry of entries) {
|
|
2461
|
+
if (entry.isDirectory()) {
|
|
2462
|
+
const packageJsonPath = join(basePath, entry.name, "package.json");
|
|
2463
|
+
if (existsSync(packageJsonPath)) {
|
|
2464
|
+
results.push(join(basePath, entry.name));
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
} catch {
|
|
2469
|
+
return results;
|
|
2470
|
+
}
|
|
2471
|
+
return results;
|
|
2514
2472
|
};
|
|
2515
|
-
var
|
|
2516
|
-
const
|
|
2517
|
-
if (!
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2473
|
+
var hasReactDependency = (projectPath) => {
|
|
2474
|
+
const packageJsonPath = join(projectPath, "package.json");
|
|
2475
|
+
if (!existsSync(packageJsonPath)) return false;
|
|
2476
|
+
try {
|
|
2477
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
2478
|
+
const allDeps = {
|
|
2479
|
+
...packageJson.dependencies,
|
|
2480
|
+
...packageJson.devDependencies
|
|
2523
2481
|
};
|
|
2482
|
+
return Boolean(allDeps["react"] || allDeps["react-dom"]);
|
|
2483
|
+
} catch {
|
|
2484
|
+
return false;
|
|
2524
2485
|
}
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
const
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2486
|
+
};
|
|
2487
|
+
var findWorkspaceProjects = (projectRoot) => {
|
|
2488
|
+
const patterns = getWorkspacePatterns(projectRoot);
|
|
2489
|
+
const projects = [];
|
|
2490
|
+
for (const pattern of patterns) {
|
|
2491
|
+
const projectPaths = expandWorkspacePattern(projectRoot, pattern);
|
|
2492
|
+
for (const projectPath of projectPaths) {
|
|
2493
|
+
const framework = detectFramework(projectPath);
|
|
2494
|
+
const hasReact = hasReactDependency(projectPath);
|
|
2495
|
+
if (hasReact || framework !== "unknown") {
|
|
2496
|
+
const packageJsonPath = join(projectPath, "package.json");
|
|
2497
|
+
let name = basename(projectPath);
|
|
2498
|
+
try {
|
|
2499
|
+
const packageJson = JSON.parse(
|
|
2500
|
+
readFileSync(packageJsonPath, "utf-8")
|
|
2501
|
+
);
|
|
2502
|
+
name = packageJson.name || name;
|
|
2503
|
+
} catch {
|
|
2504
|
+
}
|
|
2505
|
+
projects.push({
|
|
2506
|
+
name,
|
|
2507
|
+
path: projectPath,
|
|
2508
|
+
framework,
|
|
2509
|
+
hasReact
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2543
2513
|
}
|
|
2544
|
-
return
|
|
2545
|
-
success: true,
|
|
2546
|
-
filePath,
|
|
2547
|
-
message: `Remove ${agent} agent`,
|
|
2548
|
-
originalContent,
|
|
2549
|
-
newContent
|
|
2550
|
-
};
|
|
2514
|
+
return projects;
|
|
2551
2515
|
};
|
|
2552
|
-
var
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2516
|
+
var hasReactGrabInFile = (filePath) => {
|
|
2517
|
+
if (!existsSync(filePath)) return false;
|
|
2518
|
+
try {
|
|
2519
|
+
const content = readFileSync(filePath, "utf-8");
|
|
2520
|
+
const fuzzyPatterns = [
|
|
2521
|
+
/["'`][^"'`]*react-grab/,
|
|
2522
|
+
/react-grab[^"'`]*["'`]/,
|
|
2523
|
+
/<[^>]*react-grab/i,
|
|
2524
|
+
/import[^;]*react-grab/i,
|
|
2525
|
+
/require[^)]*react-grab/i,
|
|
2526
|
+
/from\s+[^;]*react-grab/i,
|
|
2527
|
+
/src[^>]*react-grab/i
|
|
2528
|
+
];
|
|
2529
|
+
return fuzzyPatterns.some((pattern) => pattern.test(content));
|
|
2530
|
+
} catch {
|
|
2531
|
+
return false;
|
|
2561
2532
|
}
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
)
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2533
|
+
};
|
|
2534
|
+
var detectReactGrab = (projectRoot) => {
|
|
2535
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
2536
|
+
if (existsSync(packageJsonPath)) {
|
|
2537
|
+
try {
|
|
2538
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
2539
|
+
const allDependencies = {
|
|
2540
|
+
...packageJson.dependencies,
|
|
2541
|
+
...packageJson.devDependencies
|
|
2542
|
+
};
|
|
2543
|
+
if (allDependencies["react-grab"]) {
|
|
2544
|
+
return true;
|
|
2545
|
+
}
|
|
2546
|
+
} catch {
|
|
2547
|
+
}
|
|
2573
2548
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2549
|
+
const filesToCheck = [
|
|
2550
|
+
join(projectRoot, "app", "layout.tsx"),
|
|
2551
|
+
join(projectRoot, "app", "layout.jsx"),
|
|
2552
|
+
join(projectRoot, "src", "app", "layout.tsx"),
|
|
2553
|
+
join(projectRoot, "src", "app", "layout.jsx"),
|
|
2554
|
+
join(projectRoot, "pages", "_document.tsx"),
|
|
2555
|
+
join(projectRoot, "pages", "_document.jsx"),
|
|
2556
|
+
join(projectRoot, "instrumentation-client.ts"),
|
|
2557
|
+
join(projectRoot, "instrumentation-client.js"),
|
|
2558
|
+
join(projectRoot, "src", "instrumentation-client.ts"),
|
|
2559
|
+
join(projectRoot, "src", "instrumentation-client.js"),
|
|
2560
|
+
join(projectRoot, "index.html"),
|
|
2561
|
+
join(projectRoot, "public", "index.html"),
|
|
2562
|
+
join(projectRoot, "src", "index.tsx"),
|
|
2563
|
+
join(projectRoot, "src", "index.ts"),
|
|
2564
|
+
join(projectRoot, "src", "main.tsx"),
|
|
2565
|
+
join(projectRoot, "src", "main.ts")
|
|
2566
|
+
];
|
|
2567
|
+
return filesToCheck.some(hasReactGrabInFile);
|
|
2581
2568
|
};
|
|
2582
|
-
var
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2569
|
+
var AGENT_PACKAGES2 = [
|
|
2570
|
+
"@react-grab/claude-code",
|
|
2571
|
+
"@react-grab/cursor",
|
|
2572
|
+
"@react-grab/opencode",
|
|
2573
|
+
"@react-grab/codex",
|
|
2574
|
+
"@react-grab/gemini",
|
|
2575
|
+
"@react-grab/amp",
|
|
2576
|
+
"@react-grab/ami",
|
|
2577
|
+
"@react-grab/visual-edit"
|
|
2578
|
+
];
|
|
2579
|
+
var detectUnsupportedFramework = (projectRoot) => {
|
|
2580
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
2581
|
+
if (!existsSync(packageJsonPath)) {
|
|
2582
|
+
return null;
|
|
2583
|
+
}
|
|
2584
|
+
try {
|
|
2585
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
2586
|
+
const allDependencies = {
|
|
2587
|
+
...packageJson.dependencies,
|
|
2588
|
+
...packageJson.devDependencies
|
|
2590
2589
|
};
|
|
2590
|
+
if (allDependencies["@remix-run/react"] || allDependencies["remix"]) {
|
|
2591
|
+
return "remix";
|
|
2592
|
+
}
|
|
2593
|
+
if (allDependencies["astro"]) {
|
|
2594
|
+
return "astro";
|
|
2595
|
+
}
|
|
2596
|
+
if (allDependencies["@sveltejs/kit"]) {
|
|
2597
|
+
return "sveltekit";
|
|
2598
|
+
}
|
|
2599
|
+
if (allDependencies["gatsby"]) {
|
|
2600
|
+
return "gatsby";
|
|
2601
|
+
}
|
|
2602
|
+
return null;
|
|
2603
|
+
} catch {
|
|
2604
|
+
return null;
|
|
2591
2605
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
)
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2606
|
+
};
|
|
2607
|
+
var detectInstalledAgents = (projectRoot) => {
|
|
2608
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
2609
|
+
if (!existsSync(packageJsonPath)) {
|
|
2610
|
+
return [];
|
|
2611
|
+
}
|
|
2612
|
+
try {
|
|
2613
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
2614
|
+
const allDependencies = {
|
|
2615
|
+
...packageJson.dependencies,
|
|
2616
|
+
...packageJson.devDependencies
|
|
2602
2617
|
};
|
|
2618
|
+
return AGENT_PACKAGES2.filter(
|
|
2619
|
+
(agent) => Boolean(allDependencies[agent])
|
|
2620
|
+
).map((agent) => agent.replace("@react-grab/", ""));
|
|
2621
|
+
} catch {
|
|
2622
|
+
return [];
|
|
2603
2623
|
}
|
|
2624
|
+
};
|
|
2625
|
+
var detectProject = async (projectRoot = process.cwd()) => {
|
|
2626
|
+
const framework = detectFramework(projectRoot);
|
|
2627
|
+
const packageManager = await detectPackageManager(projectRoot);
|
|
2604
2628
|
return {
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2629
|
+
packageManager,
|
|
2630
|
+
framework,
|
|
2631
|
+
nextRouterType: framework === "next" ? detectNextRouterType(projectRoot) : "unknown",
|
|
2632
|
+
isMonorepo: detectMonorepo(projectRoot),
|
|
2633
|
+
projectRoot,
|
|
2634
|
+
hasReactGrab: detectReactGrab(projectRoot),
|
|
2635
|
+
installedAgents: detectInstalledAgents(projectRoot),
|
|
2636
|
+
unsupportedFramework: detectUnsupportedFramework(projectRoot)
|
|
2610
2637
|
};
|
|
2611
2638
|
};
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
const
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2639
|
+
|
|
2640
|
+
// src/utils/diff.ts
|
|
2641
|
+
var RED = "\x1B[31m";
|
|
2642
|
+
var GREEN = "\x1B[32m";
|
|
2643
|
+
var GRAY = "\x1B[90m";
|
|
2644
|
+
var RESET = "\x1B[0m";
|
|
2645
|
+
var BOLD = "\x1B[1m";
|
|
2646
|
+
var generateDiff = (originalContent, newContent) => {
|
|
2647
|
+
const originalLines = originalContent.split("\n");
|
|
2648
|
+
const newLines = newContent.split("\n");
|
|
2649
|
+
const diff = [];
|
|
2650
|
+
Math.max(originalLines.length, newLines.length);
|
|
2651
|
+
let originalIndex = 0;
|
|
2652
|
+
let newIndex = 0;
|
|
2653
|
+
while (originalIndex < originalLines.length || newIndex < newLines.length) {
|
|
2654
|
+
const originalLine = originalLines[originalIndex];
|
|
2655
|
+
const newLine = newLines[newIndex];
|
|
2656
|
+
if (originalLine === newLine) {
|
|
2657
|
+
diff.push({
|
|
2658
|
+
type: "unchanged",
|
|
2659
|
+
content: originalLine,
|
|
2660
|
+
lineNumber: newIndex + 1
|
|
2661
|
+
});
|
|
2662
|
+
originalIndex++;
|
|
2663
|
+
newIndex++;
|
|
2664
|
+
} else if (originalLine === void 0) {
|
|
2665
|
+
diff.push({ type: "added", content: newLine, lineNumber: newIndex + 1 });
|
|
2666
|
+
newIndex++;
|
|
2667
|
+
} else if (newLine === void 0) {
|
|
2668
|
+
diff.push({ type: "removed", content: originalLine });
|
|
2669
|
+
originalIndex++;
|
|
2670
|
+
} else {
|
|
2671
|
+
const originalInNew = newLines.indexOf(originalLine, newIndex);
|
|
2672
|
+
const newInOriginal = originalLines.indexOf(newLine, originalIndex);
|
|
2673
|
+
if (originalInNew !== -1 && (newInOriginal === -1 || originalInNew - newIndex < newInOriginal - originalIndex)) {
|
|
2674
|
+
while (newIndex < originalInNew) {
|
|
2675
|
+
diff.push({
|
|
2676
|
+
type: "added",
|
|
2677
|
+
content: newLines[newIndex],
|
|
2678
|
+
lineNumber: newIndex + 1
|
|
2679
|
+
});
|
|
2680
|
+
newIndex++;
|
|
2681
|
+
}
|
|
2682
|
+
} else if (newInOriginal !== -1) {
|
|
2683
|
+
while (originalIndex < newInOriginal) {
|
|
2684
|
+
diff.push({ type: "removed", content: originalLines[originalIndex] });
|
|
2685
|
+
originalIndex++;
|
|
2686
|
+
}
|
|
2687
|
+
} else {
|
|
2688
|
+
diff.push({ type: "removed", content: originalLine });
|
|
2689
|
+
diff.push({
|
|
2690
|
+
type: "added",
|
|
2691
|
+
content: newLine,
|
|
2692
|
+
lineNumber: newIndex + 1
|
|
2693
|
+
});
|
|
2694
|
+
originalIndex++;
|
|
2695
|
+
newIndex++;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2636
2698
|
}
|
|
2699
|
+
return diff;
|
|
2637
2700
|
};
|
|
2638
|
-
var
|
|
2639
|
-
const
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
noChanges: true
|
|
2646
|
-
};
|
|
2701
|
+
var formatDiff = (diff, contextLines = 3) => {
|
|
2702
|
+
const lines = [];
|
|
2703
|
+
let lastPrintedIndex = -1;
|
|
2704
|
+
let hasChanges = false;
|
|
2705
|
+
const changedIndices = diff.map((line, index) => line.type !== "unchanged" ? index : -1).filter((index) => index !== -1);
|
|
2706
|
+
if (changedIndices.length === 0) {
|
|
2707
|
+
return `${GRAY}No changes${RESET}`;
|
|
2647
2708
|
}
|
|
2648
|
-
const
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2709
|
+
for (const changedIndex of changedIndices) {
|
|
2710
|
+
const startContext = Math.max(0, changedIndex - contextLines);
|
|
2711
|
+
const endContext = Math.min(diff.length - 1, changedIndex + contextLines);
|
|
2712
|
+
if (startContext > lastPrintedIndex + 1 && lastPrintedIndex !== -1) {
|
|
2713
|
+
lines.push(`${GRAY} ...${RESET}`);
|
|
2714
|
+
}
|
|
2715
|
+
for (let lineIndex = Math.max(startContext, lastPrintedIndex + 1); lineIndex <= endContext; lineIndex++) {
|
|
2716
|
+
const diffLine = diff[lineIndex];
|
|
2717
|
+
if (diffLine.type === "added") {
|
|
2718
|
+
lines.push(`${GREEN}+ ${diffLine.content}${RESET}`);
|
|
2719
|
+
hasChanges = true;
|
|
2720
|
+
} else if (diffLine.type === "removed") {
|
|
2721
|
+
lines.push(`${RED}- ${diffLine.content}${RESET}`);
|
|
2722
|
+
hasChanges = true;
|
|
2723
|
+
} else {
|
|
2724
|
+
lines.push(`${GRAY} ${diffLine.content}${RESET}`);
|
|
2725
|
+
}
|
|
2726
|
+
lastPrintedIndex = lineIndex;
|
|
2727
|
+
}
|
|
2657
2728
|
}
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2729
|
+
return hasChanges ? lines.join("\n") : `${GRAY}No changes${RESET}`;
|
|
2730
|
+
};
|
|
2731
|
+
var printDiff = (filePath, originalContent, newContent) => {
|
|
2732
|
+
console.log(`
|
|
2733
|
+
${BOLD}File: ${filePath}${RESET}`);
|
|
2734
|
+
console.log("\u2500".repeat(60));
|
|
2735
|
+
const diff = generateDiff(originalContent, newContent);
|
|
2736
|
+
console.log(formatDiff(diff));
|
|
2737
|
+
console.log("\u2500".repeat(60));
|
|
2738
|
+
};
|
|
2739
|
+
var VERSION = "0.1.0-beta.5";
|
|
2740
|
+
var configureMcp = (mcpClient, cwd, customPkg) => {
|
|
2741
|
+
const mcpCommand = customPkg ? `npx -y ${customPkg} browser mcp` : `npx -y @react-grab/cli browser mcp`;
|
|
2742
|
+
const mcpSpinner = spinner(`Installing MCP server for ${MCP_CLIENT_NAMES[mcpClient]}`).start();
|
|
2743
|
+
try {
|
|
2744
|
+
execSync(
|
|
2745
|
+
`npx -y install-mcp '${mcpCommand}' --client ${mcpClient} --yes`,
|
|
2746
|
+
{ stdio: "ignore", cwd }
|
|
2747
|
+
);
|
|
2748
|
+
mcpSpinner.succeed(`MCP server installed for ${MCP_CLIENT_NAMES[mcpClient]}`);
|
|
2749
|
+
logger.break();
|
|
2750
|
+
process.exit(0);
|
|
2751
|
+
} catch {
|
|
2752
|
+
mcpSpinner.fail(`Failed to configure MCP for ${MCP_CLIENT_NAMES[mcpClient]}`);
|
|
2753
|
+
logger.dim(`Try manually: npx -y install-mcp '${mcpCommand}' --client ${mcpClient}`);
|
|
2754
|
+
logger.break();
|
|
2755
|
+
process.exit(1);
|
|
2668
2756
|
}
|
|
2757
|
+
};
|
|
2758
|
+
var installSkill = (agent, cwd) => {
|
|
2759
|
+
logger.break();
|
|
2760
|
+
const skillSpinner = spinner(`Installing skill for ${agent.name}`).start();
|
|
2669
2761
|
try {
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
}
|
|
2678
|
-
}
|
|
2679
|
-
packageJson.scripts[scriptKey] = scriptValue;
|
|
2680
|
-
}
|
|
2681
|
-
}
|
|
2682
|
-
const newContent = JSON.stringify(packageJson, null, 2) + "\n";
|
|
2683
|
-
return {
|
|
2684
|
-
success: true,
|
|
2685
|
-
filePath: packageJsonPath,
|
|
2686
|
-
message: `Remove ${agent} server from dev script`,
|
|
2687
|
-
originalContent,
|
|
2688
|
-
newContent
|
|
2689
|
-
};
|
|
2762
|
+
execSync(`npx -y add-skill aidenybai/react-grab -y --agent ${agent.id}`, {
|
|
2763
|
+
stdio: "ignore",
|
|
2764
|
+
cwd
|
|
2765
|
+
});
|
|
2766
|
+
skillSpinner.succeed(`Skill installed for ${agent.name}`);
|
|
2767
|
+
logger.break();
|
|
2768
|
+
process.exit(0);
|
|
2690
2769
|
} catch {
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
};
|
|
2770
|
+
skillSpinner.fail(`Failed to install skill for ${agent.name}`);
|
|
2771
|
+
logger.warn(`Try manually: npx -y add-skill aidenybai/react-grab --agent ${agent.id}`);
|
|
2772
|
+
logger.break();
|
|
2773
|
+
process.exit(1);
|
|
2696
2774
|
}
|
|
2697
2775
|
};
|
|
2698
|
-
|
|
2699
|
-
// src/commands/add.ts
|
|
2700
|
-
var VERSION = "0.1.0-beta.3";
|
|
2701
|
-
var add = new Command().name("add").alias("install").description("add an agent integration or MCP server").argument(
|
|
2776
|
+
var add = new Command().name("add").alias("install").description("add browser automation for your AI agent").argument(
|
|
2702
2777
|
"[agent]",
|
|
2703
2778
|
`agent to add (${AGENTS.join(", ")}, mcp, skill)`
|
|
2704
2779
|
).option("-y, --yes", "skip confirmation prompts", false).option(
|
|
@@ -2755,74 +2830,54 @@ var add = new Command().name("add").alias("install").description("add an agent i
|
|
|
2755
2830
|
logger.break();
|
|
2756
2831
|
process.exit(1);
|
|
2757
2832
|
}
|
|
2758
|
-
|
|
2759
|
-
const mcpCommand = customPkg ? `npx -y ${customPkg} browser mcp` : `npx -y @react-grab/cli browser mcp`;
|
|
2760
|
-
const mcpSpinner = spinner(`Configuring MCP for ${MCP_CLIENT_NAMES[mcpClient]}`).start();
|
|
2761
|
-
try {
|
|
2762
|
-
execSync(
|
|
2763
|
-
`npx -y install-mcp '${mcpCommand}' --client ${mcpClient} --yes`,
|
|
2764
|
-
{ stdio: "ignore", cwd }
|
|
2765
|
-
);
|
|
2766
|
-
mcpSpinner.succeed(`MCP configured for ${MCP_CLIENT_NAMES[mcpClient]}`);
|
|
2767
|
-
logger.break();
|
|
2768
|
-
process.exit(0);
|
|
2769
|
-
} catch {
|
|
2770
|
-
mcpSpinner.fail(`Failed to configure MCP for ${MCP_CLIENT_NAMES[mcpClient]}`);
|
|
2771
|
-
logger.dim(`Try manually: npx -y install-mcp '${mcpCommand}' --client ${mcpClient}`);
|
|
2772
|
-
logger.break();
|
|
2773
|
-
process.exit(1);
|
|
2774
|
-
}
|
|
2833
|
+
configureMcp(mcpClient, cwd, opts2.pkg);
|
|
2775
2834
|
}
|
|
2776
2835
|
if (agentArg === "skill") {
|
|
2777
|
-
|
|
2778
|
-
|
|
2836
|
+
const clientArg = opts2.client;
|
|
2837
|
+
let selectedAgent = clientArg ? findSkillAgent(clientArg) : void 0;
|
|
2838
|
+
const detectedAgents = detectSkillAgents(cwd);
|
|
2839
|
+
if (clientArg && !selectedAgent) {
|
|
2779
2840
|
logger.break();
|
|
2780
|
-
logger.error(`Invalid skill
|
|
2781
|
-
logger.error(`Available
|
|
2841
|
+
logger.error(`Invalid skill agent: ${clientArg}`);
|
|
2842
|
+
logger.error(`Available agents: ${SKILL_AGENTS.map((a2) => a2.id).join(", ")}`);
|
|
2782
2843
|
logger.break();
|
|
2783
2844
|
process.exit(1);
|
|
2784
2845
|
}
|
|
2785
|
-
if (!
|
|
2846
|
+
if (!selectedAgent && !isNonInteractive) {
|
|
2847
|
+
if (detectedAgents.length === 0) {
|
|
2848
|
+
logger.break();
|
|
2849
|
+
logger.warn("No supported agent folders detected.");
|
|
2850
|
+
logger.log("Supported agents: " + SKILL_AGENTS.map((a2) => a2.id).join(", "));
|
|
2851
|
+
logger.break();
|
|
2852
|
+
process.exit(0);
|
|
2853
|
+
}
|
|
2786
2854
|
logger.break();
|
|
2787
|
-
const {
|
|
2855
|
+
const { agent } = await prompts3({
|
|
2788
2856
|
type: "select",
|
|
2789
|
-
name: "
|
|
2857
|
+
name: "agent",
|
|
2790
2858
|
message: `Which ${highlighter.info("agent")} would you like to install the skill for?`,
|
|
2791
|
-
choices:
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2859
|
+
choices: [
|
|
2860
|
+
...detectedAgents.map((innerAgent) => ({
|
|
2861
|
+
title: innerAgent.name,
|
|
2862
|
+
value: innerAgent
|
|
2863
|
+
})),
|
|
2864
|
+
{ title: "Skip", value: null }
|
|
2865
|
+
]
|
|
2795
2866
|
});
|
|
2796
|
-
if (!
|
|
2867
|
+
if (!agent) {
|
|
2797
2868
|
logger.break();
|
|
2798
|
-
process.exit(
|
|
2869
|
+
process.exit(0);
|
|
2799
2870
|
}
|
|
2800
|
-
|
|
2871
|
+
selectedAgent = agent;
|
|
2801
2872
|
}
|
|
2802
|
-
if (!
|
|
2803
|
-
logger.break();
|
|
2804
|
-
logger.error("Please specify a target with --client");
|
|
2805
|
-
logger.error(`Available targets: ${SUPPORTED_TARGETS.join(", ")}`);
|
|
2873
|
+
if (!selectedAgent) {
|
|
2806
2874
|
logger.break();
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
logger.break();
|
|
2810
|
-
const installSpinner = spinner("Fetching skill file").start();
|
|
2811
|
-
try {
|
|
2812
|
-
const skill = await fetchSkillFile();
|
|
2813
|
-
const skillDir = join(cwd, AGENT_TARGETS[skillTarget]);
|
|
2814
|
-
rmSync(skillDir, { recursive: true, force: true });
|
|
2815
|
-
mkdirSync(skillDir, { recursive: true });
|
|
2816
|
-
writeFileSync(join(skillDir, "SKILL.md"), skill);
|
|
2817
|
-
installSpinner.succeed(`Skill installed to ${AGENT_TARGETS[skillTarget]}/`);
|
|
2818
|
-
} catch (error48) {
|
|
2819
|
-
installSpinner.fail("Failed to install skill");
|
|
2820
|
-
logger.error(error48 instanceof Error ? error48.message : "Unknown error");
|
|
2875
|
+
logger.error("Please specify an agent with --client");
|
|
2876
|
+
logger.error(`Available agents: ${SKILL_AGENTS.map((a2) => a2.id).join(", ")}`);
|
|
2821
2877
|
logger.break();
|
|
2822
2878
|
process.exit(1);
|
|
2823
2879
|
}
|
|
2824
|
-
|
|
2825
|
-
process.exit(0);
|
|
2880
|
+
installSkill(selectedAgent, cwd);
|
|
2826
2881
|
}
|
|
2827
2882
|
if (!agentArg && !isNonInteractive) {
|
|
2828
2883
|
logger.break();
|
|
@@ -2832,18 +2887,18 @@ var add = new Command().name("add").alias("install").description("add an agent i
|
|
|
2832
2887
|
message: "What would you like to add?",
|
|
2833
2888
|
choices: [
|
|
2834
2889
|
{
|
|
2835
|
-
title: "
|
|
2836
|
-
description: "
|
|
2837
|
-
value: "
|
|
2890
|
+
title: "Skill (recommended)",
|
|
2891
|
+
description: "Instructions for your agent to use the browser",
|
|
2892
|
+
value: "skill"
|
|
2838
2893
|
},
|
|
2839
2894
|
{
|
|
2840
|
-
title: "
|
|
2841
|
-
description: "
|
|
2842
|
-
value: "
|
|
2895
|
+
title: "MCP Server",
|
|
2896
|
+
description: "A server that provides browser tools to your agent",
|
|
2897
|
+
value: "mcp"
|
|
2843
2898
|
},
|
|
2844
2899
|
{
|
|
2845
2900
|
title: "Agent Integration",
|
|
2846
|
-
description: "
|
|
2901
|
+
description: "Add Claude Code, Cursor, etc. to the React Grab UI",
|
|
2847
2902
|
value: "agent"
|
|
2848
2903
|
}
|
|
2849
2904
|
]
|
|
@@ -2860,61 +2915,40 @@ var add = new Command().name("add").alias("install").description("add an agent i
|
|
|
2860
2915
|
choices: MCP_CLIENTS.map((innerClient) => ({
|
|
2861
2916
|
title: MCP_CLIENT_NAMES[innerClient],
|
|
2862
2917
|
value: innerClient
|
|
2863
|
-
}))
|
|
2864
|
-
});
|
|
2865
|
-
if (!client) {
|
|
2866
|
-
logger.break();
|
|
2867
|
-
process.exit(1);
|
|
2868
|
-
}
|
|
2869
|
-
const mcpClient = client;
|
|
2870
|
-
const customPkg = opts2.pkg;
|
|
2871
|
-
const mcpCommand = customPkg ? `npx -y ${customPkg} browser mcp` : `npx -y @react-grab/cli browser mcp`;
|
|
2872
|
-
const mcpSpinner = spinner(`Configuring MCP for ${MCP_CLIENT_NAMES[mcpClient]}`).start();
|
|
2873
|
-
try {
|
|
2874
|
-
execSync(
|
|
2875
|
-
`npx -y install-mcp '${mcpCommand}' --client ${mcpClient} --yes`,
|
|
2876
|
-
{ stdio: "ignore", cwd }
|
|
2877
|
-
);
|
|
2878
|
-
mcpSpinner.succeed(`MCP configured for ${MCP_CLIENT_NAMES[mcpClient]}`);
|
|
2879
|
-
logger.break();
|
|
2880
|
-
process.exit(0);
|
|
2881
|
-
} catch {
|
|
2882
|
-
mcpSpinner.fail(`Failed to configure MCP for ${MCP_CLIENT_NAMES[mcpClient]}`);
|
|
2883
|
-
logger.dim(`Try manually: npx install-mcp '${mcpCommand}' --client ${mcpClient}`);
|
|
2918
|
+
}))
|
|
2919
|
+
});
|
|
2920
|
+
if (!client) {
|
|
2884
2921
|
logger.break();
|
|
2885
2922
|
process.exit(1);
|
|
2886
2923
|
}
|
|
2924
|
+
configureMcp(client, cwd, opts2.pkg);
|
|
2887
2925
|
}
|
|
2888
2926
|
if (addType === "skill") {
|
|
2889
|
-
const
|
|
2927
|
+
const detectedAgents = detectSkillAgents(cwd);
|
|
2928
|
+
if (detectedAgents.length === 0) {
|
|
2929
|
+
logger.break();
|
|
2930
|
+
logger.warn("No supported agent folders detected.");
|
|
2931
|
+
logger.log("Supported agents: " + SKILL_AGENTS.map((a2) => a2.id).join(", "));
|
|
2932
|
+
logger.break();
|
|
2933
|
+
process.exit(0);
|
|
2934
|
+
}
|
|
2935
|
+
const { selectedAgent } = await prompts3({
|
|
2890
2936
|
type: "select",
|
|
2891
|
-
name: "
|
|
2937
|
+
name: "selectedAgent",
|
|
2892
2938
|
message: `Which ${highlighter.info("agent")} would you like to install the skill for?`,
|
|
2893
|
-
choices:
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2939
|
+
choices: [
|
|
2940
|
+
...detectedAgents.map((innerAgent) => ({
|
|
2941
|
+
title: innerAgent.name,
|
|
2942
|
+
value: innerAgent
|
|
2943
|
+
})),
|
|
2944
|
+
{ title: "Skip", value: null }
|
|
2945
|
+
]
|
|
2897
2946
|
});
|
|
2898
|
-
if (!
|
|
2899
|
-
logger.break();
|
|
2900
|
-
process.exit(1);
|
|
2901
|
-
}
|
|
2902
|
-
const installSpinner = spinner("Fetching skill file").start();
|
|
2903
|
-
try {
|
|
2904
|
-
const skill = await fetchSkillFile();
|
|
2905
|
-
const skillDir = join(cwd, AGENT_TARGETS[target]);
|
|
2906
|
-
rmSync(skillDir, { recursive: true, force: true });
|
|
2907
|
-
mkdirSync(skillDir, { recursive: true });
|
|
2908
|
-
writeFileSync(join(skillDir, "SKILL.md"), skill);
|
|
2909
|
-
installSpinner.succeed(`Skill installed to ${AGENT_TARGETS[target]}/`);
|
|
2910
|
-
} catch (error48) {
|
|
2911
|
-
installSpinner.fail("Failed to install skill");
|
|
2912
|
-
logger.error(error48 instanceof Error ? error48.message : "Unknown error");
|
|
2947
|
+
if (!selectedAgent) {
|
|
2913
2948
|
logger.break();
|
|
2914
|
-
process.exit(
|
|
2949
|
+
process.exit(0);
|
|
2915
2950
|
}
|
|
2916
|
-
|
|
2917
|
-
process.exit(0);
|
|
2951
|
+
installSkill(selectedAgent, cwd);
|
|
2918
2952
|
}
|
|
2919
2953
|
}
|
|
2920
2954
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
@@ -3232,9 +3266,11 @@ var MAX_SUGGESTIONS_COUNT = 30;
|
|
|
3232
3266
|
var MAX_KEY_HOLD_DURATION_MS = 2e3;
|
|
3233
3267
|
var MAX_CONTEXT_LINES = 50;
|
|
3234
3268
|
var COMPONENT_STACK_MAX_DEPTH = 10;
|
|
3269
|
+
var DEFAULT_COMPONENT_TREE_MAX_DEPTH = 50;
|
|
3270
|
+
var MAX_PROPS_DISPLAY_LENGTH = 100;
|
|
3271
|
+
var LOAD_STATES = /* @__PURE__ */ new Set(["load", "domcontentloaded", "networkidle"]);
|
|
3235
3272
|
|
|
3236
3273
|
// src/utils/browser-automation.ts
|
|
3237
|
-
var LOAD_STATES = /* @__PURE__ */ new Set(["load", "domcontentloaded", "networkidle"]);
|
|
3238
3274
|
var ensureHealthyServer = async (options2 = {}) => {
|
|
3239
3275
|
const cliPath = process.argv[1];
|
|
3240
3276
|
const serverRunning = await isServerRunning();
|
|
@@ -3309,13 +3345,7 @@ var getReactContextForActiveElement = async (page) => {
|
|
|
3309
3345
|
var createOutputJson = (getPage, pageName) => {
|
|
3310
3346
|
return async (ok, result, error48) => {
|
|
3311
3347
|
const page = getPage();
|
|
3312
|
-
|
|
3313
|
-
if (page && ok) {
|
|
3314
|
-
const context = await getReactContextForActiveElement(page);
|
|
3315
|
-
if (context) {
|
|
3316
|
-
reactContext = context;
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3348
|
+
const reactContext = page && ok ? await getReactContextForActiveElement(page) ?? void 0 : void 0;
|
|
3319
3349
|
return {
|
|
3320
3350
|
ok,
|
|
3321
3351
|
url: page?.url() ?? "",
|
|
@@ -3356,7 +3386,7 @@ var createRefHelper = (getActivePage2) => {
|
|
|
3356
3386
|
const g2 = globalThis;
|
|
3357
3387
|
const refs = g2.__REACT_GRAB_REFS__;
|
|
3358
3388
|
if (!refs) {
|
|
3359
|
-
throw new Error("No refs found. Call
|
|
3389
|
+
throw new Error("No refs found. Call getSnapshot() first.");
|
|
3360
3390
|
}
|
|
3361
3391
|
const element2 = refs[id];
|
|
3362
3392
|
if (!element2) {
|
|
@@ -3375,56 +3405,73 @@ var createRefHelper = (getActivePage2) => {
|
|
|
3375
3405
|
};
|
|
3376
3406
|
const getSource = async (refId) => {
|
|
3377
3407
|
const element = await getElement(refId);
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3408
|
+
try {
|
|
3409
|
+
const currentPage2 = getActivePage2();
|
|
3410
|
+
return await currentPage2.evaluate((el) => {
|
|
3411
|
+
const g2 = globalThis;
|
|
3412
|
+
if (!g2.__REACT_GRAB__) return null;
|
|
3413
|
+
return g2.__REACT_GRAB__.getSource(el);
|
|
3414
|
+
}, element);
|
|
3415
|
+
} finally {
|
|
3416
|
+
await element.dispose();
|
|
3417
|
+
}
|
|
3384
3418
|
};
|
|
3385
3419
|
const getProps = async (refId) => {
|
|
3386
3420
|
const element = await getElement(refId);
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3421
|
+
try {
|
|
3422
|
+
const currentPage2 = getActivePage2();
|
|
3423
|
+
return await currentPage2.evaluate((el) => {
|
|
3424
|
+
const g2 = globalThis;
|
|
3425
|
+
if (!g2.__REACT_GRAB_GET_PROPS__) return null;
|
|
3426
|
+
return g2.__REACT_GRAB_GET_PROPS__(el);
|
|
3427
|
+
}, element);
|
|
3428
|
+
} finally {
|
|
3429
|
+
await element.dispose();
|
|
3430
|
+
}
|
|
3393
3431
|
};
|
|
3394
3432
|
const getState = async (refId) => {
|
|
3395
3433
|
const element = await getElement(refId);
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3434
|
+
try {
|
|
3435
|
+
const currentPage2 = getActivePage2();
|
|
3436
|
+
return await currentPage2.evaluate((el) => {
|
|
3437
|
+
const g2 = globalThis;
|
|
3438
|
+
if (!g2.__REACT_GRAB_GET_STATE__) return null;
|
|
3439
|
+
return g2.__REACT_GRAB_GET_STATE__(el);
|
|
3440
|
+
}, element);
|
|
3441
|
+
} finally {
|
|
3442
|
+
await element.dispose();
|
|
3443
|
+
}
|
|
3402
3444
|
};
|
|
3403
3445
|
return (refId) => {
|
|
3446
|
+
const customMethods = {
|
|
3447
|
+
then: () => (resolve, reject) => getElement(refId).then(resolve, reject),
|
|
3448
|
+
source: () => () => getSource(refId),
|
|
3449
|
+
props: () => () => getProps(refId),
|
|
3450
|
+
state: () => () => getState(refId),
|
|
3451
|
+
screenshot: () => async (options2) => {
|
|
3452
|
+
const element = await getElement(refId);
|
|
3453
|
+
try {
|
|
3454
|
+
return await element.screenshot({ scale: "css", ...options2 });
|
|
3455
|
+
} finally {
|
|
3456
|
+
await element.dispose();
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
};
|
|
3404
3460
|
return new Proxy(
|
|
3405
3461
|
{},
|
|
3406
3462
|
{
|
|
3407
3463
|
get(_, prop) {
|
|
3408
|
-
if (prop
|
|
3409
|
-
return (
|
|
3464
|
+
if (prop in customMethods) {
|
|
3465
|
+
return customMethods[prop]();
|
|
3410
3466
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
}
|
|
3420
|
-
if (prop === "screenshot") {
|
|
3421
|
-
return (options2) => getElement(refId).then(
|
|
3422
|
-
(el) => el.screenshot({ scale: "css", ...options2 })
|
|
3423
|
-
);
|
|
3424
|
-
}
|
|
3425
|
-
return (...args) => getElement(refId).then(
|
|
3426
|
-
(el) => el[prop](...args)
|
|
3427
|
-
);
|
|
3467
|
+
return async (...args) => {
|
|
3468
|
+
const element = await getElement(refId);
|
|
3469
|
+
try {
|
|
3470
|
+
return await element[prop](...args);
|
|
3471
|
+
} finally {
|
|
3472
|
+
await element.dispose();
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3428
3475
|
}
|
|
3429
3476
|
}
|
|
3430
3477
|
);
|
|
@@ -3451,8 +3498,8 @@ var createComponentHelper = (getActivePage2) => {
|
|
|
3451
3498
|
},
|
|
3452
3499
|
{ name: componentName, nth }
|
|
3453
3500
|
);
|
|
3454
|
-
const
|
|
3455
|
-
if (
|
|
3501
|
+
const isNull = await currentPage2.evaluate((value) => value === null, elementHandles);
|
|
3502
|
+
if (isNull) {
|
|
3456
3503
|
await elementHandles.dispose();
|
|
3457
3504
|
return null;
|
|
3458
3505
|
}
|
|
@@ -3474,9 +3521,9 @@ var createComponentHelper = (getActivePage2) => {
|
|
|
3474
3521
|
return handles;
|
|
3475
3522
|
};
|
|
3476
3523
|
};
|
|
3477
|
-
var createFillHelper = (
|
|
3524
|
+
var createFillHelper = (getRef, getActivePage2) => {
|
|
3478
3525
|
return async (refId, text) => {
|
|
3479
|
-
const element = await
|
|
3526
|
+
const element = await getRef(refId);
|
|
3480
3527
|
await element.click();
|
|
3481
3528
|
const currentPage2 = getActivePage2();
|
|
3482
3529
|
const isMac2 = process.platform === "darwin";
|
|
@@ -3594,11 +3641,15 @@ var createGrabHelper = (ref, getActivePage2) => {
|
|
|
3594
3641
|
copyElement: async (refId) => {
|
|
3595
3642
|
const element = await ref(refId);
|
|
3596
3643
|
if (!element) return false;
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3644
|
+
try {
|
|
3645
|
+
const currentPage2 = getActivePage2();
|
|
3646
|
+
return await currentPage2.evaluate((el) => {
|
|
3647
|
+
const g2 = globalThis;
|
|
3648
|
+
return g2.__REACT_GRAB__?.copyElement([el]) ?? false;
|
|
3649
|
+
}, element);
|
|
3650
|
+
} finally {
|
|
3651
|
+
await element.dispose();
|
|
3652
|
+
}
|
|
3602
3653
|
}
|
|
3603
3654
|
};
|
|
3604
3655
|
};
|
|
@@ -3618,7 +3669,7 @@ var createWaitForHelper = (getActivePage2) => {
|
|
|
3618
3669
|
await currentPage2.waitForLoadState(selectorOrState, { timeout });
|
|
3619
3670
|
return;
|
|
3620
3671
|
}
|
|
3621
|
-
if (
|
|
3672
|
+
if (/^e\d+$/.test(selectorOrState)) {
|
|
3622
3673
|
await currentPage2.waitForSelector(`[aria-ref="${selectorOrState}"]`, { timeout });
|
|
3623
3674
|
return;
|
|
3624
3675
|
}
|
|
@@ -17421,59 +17472,93 @@ var startMcpServer = async () => {
|
|
|
17421
17472
|
server.registerTool(
|
|
17422
17473
|
"browser_snapshot",
|
|
17423
17474
|
{
|
|
17424
|
-
description: `Get ARIA accessibility tree with element refs
|
|
17475
|
+
description: `Get ARIA accessibility tree with element refs and React component info.
|
|
17476
|
+
|
|
17477
|
+
OUTPUT FORMAT:
|
|
17478
|
+
- button "Submit" [ref=e1] [component=Button] [source=form.tsx:42]
|
|
17479
|
+
- ComponentName [ref=e2] [source=file.tsx:10]: text content
|
|
17480
|
+
|
|
17481
|
+
REACT INFO IS ALREADY INCLUDED (no extra calls needed):
|
|
17482
|
+
- [component=X] \u2014 React component name (replaces "generic" when available)
|
|
17483
|
+
- [source=file.tsx:line] \u2014 Source file location
|
|
17484
|
+
Just parse these from the snapshot string.
|
|
17425
17485
|
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
|
|
17429
|
-
- [component=ComponentName] for React components
|
|
17430
|
-
- [source=file.tsx:line] for source location
|
|
17486
|
+
FOR MORE REACT DETAILS on a specific element:
|
|
17487
|
+
Use browser_execute: return await getRef('e1').source()
|
|
17488
|
+
Returns: { filePath, lineNumber, componentName }
|
|
17431
17489
|
|
|
17432
|
-
SCREENSHOT STRATEGY -
|
|
17490
|
+
SCREENSHOT STRATEGY - prefer element screenshots:
|
|
17433
17491
|
1. First: Get refs with snapshot (this tool)
|
|
17434
|
-
2. Then: Screenshot specific element via browser_execute: return await
|
|
17492
|
+
2. Then: Screenshot specific element via browser_execute: return await getRef('e1').screenshot()
|
|
17435
17493
|
|
|
17436
|
-
USE ELEMENT SCREENSHOTS (
|
|
17437
|
-
- Visual bugs: "wrong color", "broken", "misaligned", "styling issue"
|
|
17438
|
-
- Appearance checks: "how does X look", "show me the button"
|
|
17439
|
-
- UI verification: "is it visible", "check the layout"
|
|
17440
|
-
- Any visual concern about a SPECIFIC component
|
|
17494
|
+
USE ELEMENT SCREENSHOTS (getRef('eX').screenshot()) FOR:
|
|
17495
|
+
- Visual bugs: "wrong color", "broken", "misaligned", "styling issue"
|
|
17496
|
+
- Appearance checks: "how does X look", "show me the button"
|
|
17497
|
+
- UI verification: "is it visible", "check the layout"
|
|
17441
17498
|
|
|
17442
17499
|
USE VIEWPORT screenshot=true ONLY FOR:
|
|
17443
17500
|
- "screenshot the page", "what's on screen"
|
|
17444
|
-
- No specific element mentioned
|
|
17501
|
+
- No specific element mentioned
|
|
17445
17502
|
|
|
17446
|
-
|
|
17447
|
-
- interactableOnly:true = much smaller output (recommended)
|
|
17448
|
-
- format:'compact' = minimal ref:role:name@Component output
|
|
17449
|
-
- maxDepth = limit tree depth
|
|
17450
|
-
|
|
17451
|
-
After getting refs, use browser_execute with: ref('e1').click()`,
|
|
17503
|
+
After getting refs, use browser_execute with: getRef('e1').click()`,
|
|
17452
17504
|
inputSchema: {
|
|
17453
17505
|
page: external_exports.string().optional().default("default").describe("Named page context"),
|
|
17454
17506
|
maxDepth: external_exports.number().optional().describe("Limit tree depth"),
|
|
17455
|
-
interactableOnly: external_exports.boolean().optional().describe("Only clickable/input elements (recommended)"),
|
|
17456
|
-
format: external_exports.enum(["yaml", "compact"]).optional().default("yaml").describe("'yaml' or 'compact'"),
|
|
17457
17507
|
screenshot: external_exports.boolean().optional().default(false).describe(
|
|
17458
|
-
"Viewport screenshot. For element screenshots (PREFERRED), use browser_execute:
|
|
17459
|
-
)
|
|
17508
|
+
"Viewport screenshot. For element screenshots (PREFERRED), use browser_execute: getRef('eX').screenshot()"
|
|
17509
|
+
),
|
|
17510
|
+
reactTree: external_exports.boolean().optional().default(false).describe("(Experimental) React tree view. Note: Regular snapshot already includes [component=X] and [source=X] - prefer that."),
|
|
17511
|
+
includeProps: external_exports.boolean().optional().default(false).describe("Include props when reactTree=true")
|
|
17460
17512
|
}
|
|
17461
17513
|
},
|
|
17462
17514
|
async ({
|
|
17463
17515
|
page: pageName,
|
|
17464
17516
|
maxDepth,
|
|
17465
|
-
|
|
17466
|
-
|
|
17467
|
-
|
|
17517
|
+
screenshot,
|
|
17518
|
+
reactTree,
|
|
17519
|
+
includeProps
|
|
17468
17520
|
}) => {
|
|
17469
17521
|
let browser2 = null;
|
|
17470
17522
|
try {
|
|
17471
17523
|
const connection = await connectToBrowserPage(pageName);
|
|
17472
17524
|
browser2 = connection.browser;
|
|
17473
17525
|
const activePage = connection.page;
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17526
|
+
let textResult;
|
|
17527
|
+
if (reactTree) {
|
|
17528
|
+
const componentTree = await activePage.evaluate(
|
|
17529
|
+
async (opts2) => {
|
|
17530
|
+
const g2 = globalThis;
|
|
17531
|
+
if (g2.__REACT_GRAB_SNAPSHOT__) {
|
|
17532
|
+
await g2.__REACT_GRAB_SNAPSHOT__();
|
|
17533
|
+
}
|
|
17534
|
+
if (!g2.__REACT_GRAB_GET_COMPONENT_TREE__) {
|
|
17535
|
+
return [];
|
|
17536
|
+
}
|
|
17537
|
+
return g2.__REACT_GRAB_GET_COMPONENT_TREE__(opts2);
|
|
17538
|
+
},
|
|
17539
|
+
{ maxDepth: maxDepth ?? DEFAULT_COMPONENT_TREE_MAX_DEPTH, includeProps: includeProps ?? false }
|
|
17540
|
+
);
|
|
17541
|
+
const renderTree = (nodes) => {
|
|
17542
|
+
const lines = [];
|
|
17543
|
+
for (const node of nodes) {
|
|
17544
|
+
const indent = " ".repeat(node.depth);
|
|
17545
|
+
let line = `${indent}- ${node.name}`;
|
|
17546
|
+
if (node.ref) line += ` [ref=${node.ref}]`;
|
|
17547
|
+
if (node.source) line += ` [source=${node.source}]`;
|
|
17548
|
+
if (node.props && Object.keys(node.props).length > 0) {
|
|
17549
|
+
const propsStr = JSON.stringify(node.props);
|
|
17550
|
+
if (propsStr.length < MAX_PROPS_DISPLAY_LENGTH) {
|
|
17551
|
+
line += ` [props=${propsStr}]`;
|
|
17552
|
+
}
|
|
17553
|
+
}
|
|
17554
|
+
lines.push(line);
|
|
17555
|
+
}
|
|
17556
|
+
return lines.join("\n");
|
|
17557
|
+
};
|
|
17558
|
+
textResult = renderTree(componentTree) || "No React components found. Make sure react-grab is installed and the page uses React.";
|
|
17559
|
+
} else {
|
|
17560
|
+
textResult = await createSnapshotHelper(() => activePage)({ maxDepth });
|
|
17561
|
+
}
|
|
17477
17562
|
if (screenshot) {
|
|
17478
17563
|
const screenshotBuffer = await activePage.screenshot({
|
|
17479
17564
|
fullPage: false,
|
|
@@ -17481,7 +17566,7 @@ After getting refs, use browser_execute with: ref('e1').click()`,
|
|
|
17481
17566
|
});
|
|
17482
17567
|
return {
|
|
17483
17568
|
content: [
|
|
17484
|
-
{ type: "text", text:
|
|
17569
|
+
{ type: "text", text: textResult },
|
|
17485
17570
|
{
|
|
17486
17571
|
type: "image",
|
|
17487
17572
|
data: screenshotBuffer.toString("base64"),
|
|
@@ -17491,7 +17576,7 @@ After getting refs, use browser_execute with: ref('e1').click()`,
|
|
|
17491
17576
|
};
|
|
17492
17577
|
}
|
|
17493
17578
|
return {
|
|
17494
|
-
content: [{ type: "text", text:
|
|
17579
|
+
content: [{ type: "text", text: textResult }]
|
|
17495
17580
|
};
|
|
17496
17581
|
} catch (error48) {
|
|
17497
17582
|
return createMcpErrorResponse(error48);
|
|
@@ -17505,40 +17590,38 @@ After getting refs, use browser_execute with: ref('e1').click()`,
|
|
|
17505
17590
|
{
|
|
17506
17591
|
description: `Execute Playwright code with helpers for element interaction.
|
|
17507
17592
|
|
|
17508
|
-
IMPORTANT: Always call
|
|
17593
|
+
IMPORTANT: Always call getSnapshot() first to get element refs (e1, e2...), then use getRef('e1') to interact.
|
|
17509
17594
|
|
|
17510
17595
|
AVAILABLE HELPERS:
|
|
17511
|
-
- page: Playwright Page object
|
|
17512
|
-
-
|
|
17513
|
-
-
|
|
17514
|
-
-
|
|
17515
|
-
-
|
|
17516
|
-
-
|
|
17517
|
-
-
|
|
17518
|
-
- fill(id, text): Clear and fill input (works with rich text editors)
|
|
17596
|
+
- page: Playwright Page object
|
|
17597
|
+
- getSnapshot(opts?): Get ARIA tree with React info. opts: {maxDepth}
|
|
17598
|
+
- getRef(id): Get element by ref ID, chainable with ElementHandle methods
|
|
17599
|
+
- getRef(id).source(): Get React source {filePath, lineNumber, componentName}
|
|
17600
|
+
- getRef(id).props(): Get React component props
|
|
17601
|
+
- getRef(id).state(): Get React component state/hooks
|
|
17602
|
+
- fill(id, text): Clear and fill input
|
|
17519
17603
|
- drag({from, to, dataTransfer?}): Drag with custom MIME types
|
|
17520
17604
|
- dispatch({target, event, dataTransfer?, detail?}): Dispatch custom events
|
|
17521
17605
|
- waitFor(target): Wait for selector/ref/state. e.g. waitFor('e1'), waitFor('networkidle')
|
|
17522
17606
|
- grab: React Grab client API (activate, deactivate, toggle, isActive, copyElement, getState)
|
|
17523
17607
|
|
|
17524
|
-
REACT
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
- Get component state: return await ref('e1').state()
|
|
17528
|
-
- Find by component: const btn = await component('Button', {nth: 0})
|
|
17608
|
+
GETTING REACT INFO (ranked by preference):
|
|
17609
|
+
1. Parse snapshot \u2014 [component=X] and [source=file:line] are already in the output
|
|
17610
|
+
2. getRef('eX').source() \u2014 for detailed info on a specific element
|
|
17529
17611
|
|
|
17530
|
-
ELEMENT SCREENSHOTS (
|
|
17531
|
-
- return await
|
|
17532
|
-
Use for: wrong color, broken styling, visual bugs,
|
|
17612
|
+
ELEMENT SCREENSHOTS (for visual issues):
|
|
17613
|
+
- return await getRef('e1').screenshot()
|
|
17614
|
+
Use for: wrong color, broken styling, visual bugs, UI verification
|
|
17533
17615
|
|
|
17534
17616
|
COMMON PATTERNS:
|
|
17535
|
-
- Click: await
|
|
17617
|
+
- Click: await getRef('e1').click()
|
|
17536
17618
|
- Fill input: await fill('e1', 'hello')
|
|
17537
|
-
- Get attribute: return await
|
|
17619
|
+
- Get attribute: return await getRef('e1').getAttribute('href')
|
|
17538
17620
|
- Navigate: await page.goto('https://example.com')
|
|
17539
|
-
- Full page screenshot (rare): return await page.screenshot()
|
|
17540
17621
|
|
|
17541
|
-
|
|
17622
|
+
DON'T manually traverse __reactFiber$ \u2014 use getRef('eX').source() instead.
|
|
17623
|
+
|
|
17624
|
+
PERFORMANCE: Batch multiple actions in one execute call.`,
|
|
17542
17625
|
inputSchema: {
|
|
17543
17626
|
code: external_exports.string().describe(
|
|
17544
17627
|
"JavaScript code. Use 'page' for Playwright, 'ref(id)' for elements, 'return' for output"
|
|
@@ -17569,19 +17652,19 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
|
|
|
17569
17652
|
};
|
|
17570
17653
|
context.on("page", pageOpenHandler);
|
|
17571
17654
|
const getActivePage2 = createActivePageGetter(context, () => activePage);
|
|
17572
|
-
const
|
|
17573
|
-
const
|
|
17574
|
-
const fill = createFillHelper(
|
|
17655
|
+
const getSnapshot = createSnapshotHelper(getActivePage2);
|
|
17656
|
+
const getRef = createRefHelper(getActivePage2);
|
|
17657
|
+
const fill = createFillHelper(getRef, getActivePage2);
|
|
17575
17658
|
const drag = createDragHelper(getActivePage2);
|
|
17576
17659
|
const dispatch = createDispatchHelper(getActivePage2);
|
|
17577
|
-
const grab = createGrabHelper(
|
|
17660
|
+
const grab = createGrabHelper(getRef, getActivePage2);
|
|
17578
17661
|
const waitFor = createWaitForHelper(getActivePage2);
|
|
17579
17662
|
const component = createComponentHelper(getActivePage2);
|
|
17580
17663
|
const executeFunction = new Function(
|
|
17581
17664
|
"page",
|
|
17582
17665
|
"getActivePage",
|
|
17583
|
-
"
|
|
17584
|
-
"
|
|
17666
|
+
"getSnapshot",
|
|
17667
|
+
"getRef",
|
|
17585
17668
|
"fill",
|
|
17586
17669
|
"drag",
|
|
17587
17670
|
"dispatch",
|
|
@@ -17593,8 +17676,8 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
|
|
|
17593
17676
|
const result = await executeFunction(
|
|
17594
17677
|
getActivePage2(),
|
|
17595
17678
|
getActivePage2,
|
|
17596
|
-
|
|
17597
|
-
|
|
17679
|
+
getSnapshot,
|
|
17680
|
+
getRef,
|
|
17598
17681
|
fill,
|
|
17599
17682
|
drag,
|
|
17600
17683
|
dispatch,
|
|
@@ -17637,82 +17720,12 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
|
|
|
17637
17720
|
}
|
|
17638
17721
|
}
|
|
17639
17722
|
);
|
|
17640
|
-
server.registerTool(
|
|
17641
|
-
"browser_react_tree",
|
|
17642
|
-
{
|
|
17643
|
-
description: `Get React component tree hierarchy (separate from ARIA tree).
|
|
17644
|
-
|
|
17645
|
-
Shows the React component structure with:
|
|
17646
|
-
- Component names and nesting
|
|
17647
|
-
- Source file locations
|
|
17648
|
-
- Element refs where available
|
|
17649
|
-
- Optional props (serialized)
|
|
17650
|
-
|
|
17651
|
-
Use this when you need to understand React component architecture rather than accessibility tree.
|
|
17652
|
-
For interacting with elements, use browser_snapshot to get refs first.`,
|
|
17653
|
-
inputSchema: {
|
|
17654
|
-
page: external_exports.string().optional().default("default").describe("Named page context"),
|
|
17655
|
-
maxDepth: external_exports.number().optional().default(50).describe("Maximum tree depth"),
|
|
17656
|
-
includeProps: external_exports.boolean().optional().default(false).describe("Include component props (increases output size)")
|
|
17657
|
-
}
|
|
17658
|
-
},
|
|
17659
|
-
async ({ page: pageName, maxDepth, includeProps }) => {
|
|
17660
|
-
let browser2 = null;
|
|
17661
|
-
try {
|
|
17662
|
-
const connection = await connectToBrowserPage(pageName);
|
|
17663
|
-
browser2 = connection.browser;
|
|
17664
|
-
const activePage = connection.page;
|
|
17665
|
-
const componentTree = await activePage.evaluate(
|
|
17666
|
-
async (opts2) => {
|
|
17667
|
-
const g2 = globalThis;
|
|
17668
|
-
if (!g2.__REACT_GRAB_GET_COMPONENT_TREE__) {
|
|
17669
|
-
return [];
|
|
17670
|
-
}
|
|
17671
|
-
return g2.__REACT_GRAB_GET_COMPONENT_TREE__(opts2);
|
|
17672
|
-
},
|
|
17673
|
-
{ maxDepth: maxDepth ?? 50, includeProps: includeProps ?? false }
|
|
17674
|
-
);
|
|
17675
|
-
const renderTree = (nodes) => {
|
|
17676
|
-
const lines = [];
|
|
17677
|
-
for (const node of nodes) {
|
|
17678
|
-
const indent = " ".repeat(node.depth);
|
|
17679
|
-
let line = `${indent}- ${node.name}`;
|
|
17680
|
-
if (node.ref) line += ` [ref=${node.ref}]`;
|
|
17681
|
-
if (node.source) line += ` [source=${node.source}]`;
|
|
17682
|
-
if (node.props && Object.keys(node.props).length > 0) {
|
|
17683
|
-
const propsStr = JSON.stringify(node.props);
|
|
17684
|
-
if (propsStr.length < 100) {
|
|
17685
|
-
line += ` [props=${propsStr}]`;
|
|
17686
|
-
} else {
|
|
17687
|
-
line += ` [props=...]`;
|
|
17688
|
-
}
|
|
17689
|
-
}
|
|
17690
|
-
lines.push(line);
|
|
17691
|
-
}
|
|
17692
|
-
return lines.join("\n");
|
|
17693
|
-
};
|
|
17694
|
-
const treeOutput = renderTree(componentTree);
|
|
17695
|
-
return {
|
|
17696
|
-
content: [
|
|
17697
|
-
{
|
|
17698
|
-
type: "text",
|
|
17699
|
-
text: treeOutput || "No React components found. Make sure react-grab is installed and the page uses React."
|
|
17700
|
-
}
|
|
17701
|
-
]
|
|
17702
|
-
};
|
|
17703
|
-
} catch (error48) {
|
|
17704
|
-
return createMcpErrorResponse(error48);
|
|
17705
|
-
} finally {
|
|
17706
|
-
await browser2?.close();
|
|
17707
|
-
}
|
|
17708
|
-
}
|
|
17709
|
-
);
|
|
17710
17723
|
const transport = new StdioServerTransport();
|
|
17711
17724
|
await server.connect(transport);
|
|
17712
17725
|
};
|
|
17713
17726
|
|
|
17714
17727
|
// src/commands/browser.ts
|
|
17715
|
-
var VERSION2 = "0.1.0-beta.
|
|
17728
|
+
var VERSION2 = "0.1.0-beta.5";
|
|
17716
17729
|
var printHeader = () => {
|
|
17717
17730
|
console.log(
|
|
17718
17731
|
`${pc.magenta("\u273F")} ${pc.bold("React Grab")} ${pc.gray(VERSION2)}`
|
|
@@ -17723,20 +17736,6 @@ var exitWithError = (error48) => {
|
|
|
17723
17736
|
logger.error(error48 instanceof Error ? error48.message : "Failed");
|
|
17724
17737
|
process.exit(1);
|
|
17725
17738
|
};
|
|
17726
|
-
var rebuildNativeModuleAndRestart = async (browserPkgDir) => {
|
|
17727
|
-
execSync("npm rebuild better-sqlite3", {
|
|
17728
|
-
stdio: "ignore",
|
|
17729
|
-
cwd: browserPkgDir
|
|
17730
|
-
});
|
|
17731
|
-
const rebuildSpinner = spinner("Restarting server").start();
|
|
17732
|
-
rebuildSpinner.succeed("Restarting server");
|
|
17733
|
-
const args = process.argv.slice(2);
|
|
17734
|
-
const child = spawn(process.argv[0], [process.argv[1], ...args], {
|
|
17735
|
-
stdio: "inherit",
|
|
17736
|
-
detached: false
|
|
17737
|
-
});
|
|
17738
|
-
child.on("exit", (code) => process.exit(code ?? 0));
|
|
17739
|
-
};
|
|
17740
17739
|
var isSupportedBrowser = (value) => {
|
|
17741
17740
|
return SUPPORTED_BROWSERS.includes(value);
|
|
17742
17741
|
};
|
|
@@ -17793,16 +17792,23 @@ var dump = new Command().name("dump").description("dump cookies from a browser")
|
|
|
17793
17792
|
}
|
|
17794
17793
|
});
|
|
17795
17794
|
var start = new Command().name("start").description("start browser server manually (auto-starts on first execute)").option("-p, --port <port>", "HTTP API port", String(DEFAULT_SERVER_PORT)).option("--headed", "show browser window (default is headless)").option("-b, --browser <browser>", "source browser for cookies (chrome, edge, brave, arc)").option("-d, --domain <domain>", "only load cookies matching this domain").option("--foreground", "run in foreground instead of detaching").action(async (options2) => {
|
|
17796
|
-
|
|
17795
|
+
const isForeground = options2.foreground;
|
|
17796
|
+
if (!isForeground) {
|
|
17797
|
+
printHeader();
|
|
17798
|
+
}
|
|
17797
17799
|
if (await isServerRunning()) {
|
|
17798
17800
|
const info = getServerInfo();
|
|
17799
|
-
|
|
17801
|
+
if (isForeground) {
|
|
17802
|
+
console.error(`Server already running on port ${info?.port}`);
|
|
17803
|
+
} else {
|
|
17804
|
+
logger.error(`Server already running on port ${info?.port}`);
|
|
17805
|
+
}
|
|
17800
17806
|
process.exit(1);
|
|
17801
17807
|
}
|
|
17802
17808
|
const sourceBrowser = resolveSourceBrowser(options2.browser);
|
|
17803
17809
|
const port = parseInt(options2.port, 10);
|
|
17804
|
-
if (!
|
|
17805
|
-
const
|
|
17810
|
+
if (!isForeground) {
|
|
17811
|
+
const serverSpinner = spinner("Starting server").start();
|
|
17806
17812
|
try {
|
|
17807
17813
|
const browserServer = await spawnServer({
|
|
17808
17814
|
port,
|
|
@@ -17811,53 +17817,30 @@ var start = new Command().name("start").description("start browser server manual
|
|
|
17811
17817
|
browser: options2.browser,
|
|
17812
17818
|
domain: options2.domain
|
|
17813
17819
|
});
|
|
17814
|
-
|
|
17820
|
+
serverSpinner.succeed(`Server running on port ${browserServer.port}`);
|
|
17815
17821
|
logger.dim(`CDP: ${browserServer.wsEndpoint}`);
|
|
17816
17822
|
} catch (error48) {
|
|
17817
|
-
|
|
17823
|
+
serverSpinner.fail();
|
|
17818
17824
|
exitWithError(error48);
|
|
17819
17825
|
}
|
|
17820
17826
|
return;
|
|
17821
17827
|
}
|
|
17822
|
-
const serverSpinner = spinner("Starting server").start();
|
|
17823
17828
|
try {
|
|
17824
17829
|
const browserServer = await serve({
|
|
17825
17830
|
port,
|
|
17826
17831
|
headless: !options2.headed
|
|
17827
17832
|
});
|
|
17828
|
-
|
|
17829
|
-
|
|
17830
|
-
const
|
|
17831
|
-
|
|
17832
|
-
|
|
17833
|
-
|
|
17834
|
-
const browser2 = await chromium.connectOverCDP(browserServer.wsEndpoint);
|
|
17835
|
-
const contexts = browser2.contexts();
|
|
17836
|
-
if (contexts.length > 0 && playwrightCookies.length > 0) {
|
|
17833
|
+
const cookies = dumpCookies(sourceBrowser, { domain: options2.domain });
|
|
17834
|
+
const playwrightCookies = toPlaywrightCookies(cookies);
|
|
17835
|
+
const browser2 = await chromium.connectOverCDP(browserServer.wsEndpoint);
|
|
17836
|
+
const contexts = browser2.contexts();
|
|
17837
|
+
if (contexts.length > 0) {
|
|
17838
|
+
if (playwrightCookies.length > 0) {
|
|
17837
17839
|
await contexts[0].addCookies(playwrightCookies);
|
|
17838
|
-
await applyStealthScripts(contexts[0]);
|
|
17839
|
-
}
|
|
17840
|
-
await browser2.close();
|
|
17841
|
-
cookieSpinner.succeed(`Loaded ${playwrightCookies.length} cookies from ${sourceBrowser}`);
|
|
17842
|
-
} catch (cookieError) {
|
|
17843
|
-
const errorMessage = cookieError instanceof Error ? cookieError.message : "Unknown error";
|
|
17844
|
-
const isModuleVersionError = errorMessage.includes("NODE_MODULE_VERSION");
|
|
17845
|
-
if (!isModuleVersionError) {
|
|
17846
|
-
cookieSpinner.fail(`Failed to load cookies from ${sourceBrowser}`);
|
|
17847
|
-
} else {
|
|
17848
|
-
cookieSpinner.info("Native module mismatch. Rebuilding...");
|
|
17849
|
-
try {
|
|
17850
|
-
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
17851
|
-
const browserPkgDir = join(currentDir, "..", "..");
|
|
17852
|
-
await browserServer.stop();
|
|
17853
|
-
await rebuildNativeModuleAndRestart(browserPkgDir);
|
|
17854
|
-
return;
|
|
17855
|
-
} catch {
|
|
17856
|
-
cookieSpinner.fail("Auto-rebuild failed. Run: npm rebuild better-sqlite3");
|
|
17857
|
-
}
|
|
17858
17840
|
}
|
|
17841
|
+
await applyStealthScripts(contexts[0]);
|
|
17859
17842
|
}
|
|
17860
|
-
|
|
17843
|
+
await browser2.close();
|
|
17861
17844
|
const shutdownHandler = () => {
|
|
17862
17845
|
browserServer.stop().catch(() => {
|
|
17863
17846
|
}).finally(() => process.exit(0));
|
|
@@ -17868,8 +17851,8 @@ var start = new Command().name("start").description("start browser server manual
|
|
|
17868
17851
|
await new Promise(() => {
|
|
17869
17852
|
});
|
|
17870
17853
|
} catch (error48) {
|
|
17871
|
-
|
|
17872
|
-
|
|
17854
|
+
console.error(error48 instanceof Error ? error48.message : "Failed to start server");
|
|
17855
|
+
process.exit(1);
|
|
17873
17856
|
}
|
|
17874
17857
|
});
|
|
17875
17858
|
var stop = new Command().name("stop").description("stop the browser server").action(async () => {
|
|
@@ -17934,7 +17917,7 @@ var execute = new Command().name("execute").description("run Playwright code wit
|
|
|
17934
17917
|
let activePage = null;
|
|
17935
17918
|
let browser2 = null;
|
|
17936
17919
|
let pageOpenHandler = null;
|
|
17937
|
-
const
|
|
17920
|
+
const outputJson = createOutputJson(() => activePage, pageName);
|
|
17938
17921
|
let exitCode = 0;
|
|
17939
17922
|
try {
|
|
17940
17923
|
const { serverUrl } = await ensureHealthyServer({
|
|
@@ -17959,29 +17942,42 @@ var execute = new Command().name("execute").description("run Playwright code wit
|
|
|
17959
17942
|
};
|
|
17960
17943
|
context.on("page", pageOpenHandler);
|
|
17961
17944
|
const getActivePage2 = createActivePageGetter(context, () => activePage);
|
|
17962
|
-
const
|
|
17963
|
-
const
|
|
17964
|
-
const fill = createFillHelper(
|
|
17945
|
+
const getSnapshot = createSnapshotHelper(getActivePage2);
|
|
17946
|
+
const getRef = createRefHelper(getActivePage2);
|
|
17947
|
+
const fill = createFillHelper(getRef, getActivePage2);
|
|
17965
17948
|
const drag = createDragHelper(getActivePage2);
|
|
17966
17949
|
const dispatch = createDispatchHelper(getActivePage2);
|
|
17967
|
-
const grab = createGrabHelper(
|
|
17950
|
+
const grab = createGrabHelper(getRef, getActivePage2);
|
|
17968
17951
|
const waitFor = createWaitForHelper(getActivePage2);
|
|
17952
|
+
const component = createComponentHelper(getActivePage2);
|
|
17969
17953
|
const executeFunction = new Function(
|
|
17970
17954
|
"page",
|
|
17971
17955
|
"getActivePage",
|
|
17972
|
-
"
|
|
17973
|
-
"
|
|
17956
|
+
"getSnapshot",
|
|
17957
|
+
"getRef",
|
|
17974
17958
|
"fill",
|
|
17975
17959
|
"drag",
|
|
17976
17960
|
"dispatch",
|
|
17977
17961
|
"grab",
|
|
17978
17962
|
"waitFor",
|
|
17963
|
+
"component",
|
|
17979
17964
|
`return (async () => { ${code} })();`
|
|
17980
17965
|
);
|
|
17981
|
-
const result = await executeFunction(
|
|
17982
|
-
|
|
17966
|
+
const result = await executeFunction(
|
|
17967
|
+
getActivePage2(),
|
|
17968
|
+
getActivePage2,
|
|
17969
|
+
getSnapshot,
|
|
17970
|
+
getRef,
|
|
17971
|
+
fill,
|
|
17972
|
+
drag,
|
|
17973
|
+
dispatch,
|
|
17974
|
+
grab,
|
|
17975
|
+
waitFor,
|
|
17976
|
+
component
|
|
17977
|
+
);
|
|
17978
|
+
console.log(JSON.stringify(await outputJson(true, result)));
|
|
17983
17979
|
} catch (error48) {
|
|
17984
|
-
console.log(JSON.stringify(await
|
|
17980
|
+
console.log(JSON.stringify(await outputJson(false, void 0, error48 instanceof Error ? error48.message : "Failed")));
|
|
17985
17981
|
exitCode = 1;
|
|
17986
17982
|
} finally {
|
|
17987
17983
|
if (activePage && pageOpenHandler) {
|
|
@@ -18036,32 +18032,32 @@ PERFORMANCE TIPS
|
|
|
18036
18032
|
1. Batch multiple actions in a single execute call to minimize round-trips.
|
|
18037
18033
|
Each execute spawns a new connection, so combining actions is 3-5x faster.
|
|
18038
18034
|
|
|
18039
|
-
2. Use
|
|
18040
|
-
-
|
|
18041
|
-
- snapshot({interactableOnly: true}) -> only clickable/input elements
|
|
18042
|
-
- snapshot({maxDepth: 5}) -> limit tree depth
|
|
18035
|
+
2. Use maxDepth to limit tree depth for smaller snapshots (faster, fewer tokens).
|
|
18036
|
+
- getSnapshot({maxDepth: 5}) -> limit tree depth
|
|
18043
18037
|
|
|
18044
|
-
# SLOW: 3 separate round-trips
|
|
18038
|
+
# SLOW: 3 separate round-trips
|
|
18045
18039
|
execute "await page.goto('https://example.com')"
|
|
18046
|
-
execute "await
|
|
18047
|
-
execute "return await
|
|
18040
|
+
execute "await getRef('e1').click()"
|
|
18041
|
+
execute "return await getSnapshot()"
|
|
18048
18042
|
|
|
18049
|
-
# FAST: 1 round-trip,
|
|
18043
|
+
# FAST: 1 round-trip (same result, 3x faster)
|
|
18050
18044
|
execute "
|
|
18051
18045
|
await page.goto('https://example.com');
|
|
18052
|
-
await
|
|
18053
|
-
return await
|
|
18046
|
+
await getRef('e1').click();
|
|
18047
|
+
return await getSnapshot();
|
|
18054
18048
|
"
|
|
18055
18049
|
|
|
18056
18050
|
HELPERS
|
|
18057
18051
|
page - Playwright Page object
|
|
18058
|
-
|
|
18052
|
+
getSnapshot(opts?)- Get ARIA accessibility tree with refs
|
|
18059
18053
|
opts.maxDepth: limit tree depth (e.g., 5)
|
|
18060
|
-
|
|
18061
|
-
|
|
18062
|
-
|
|
18063
|
-
|
|
18064
|
-
|
|
18054
|
+
getRef(id) - Get element by ref ID (chainable - supports all ElementHandle methods)
|
|
18055
|
+
Example: await getRef('e1').click()
|
|
18056
|
+
Example: await getRef('e1').getAttribute('data-foo')
|
|
18057
|
+
getRef(id).source() - Get React component source file info for element
|
|
18058
|
+
Returns { filePath, lineNumber, componentName } or null
|
|
18059
|
+
getRef(id).props() - Get React component props (serialized)
|
|
18060
|
+
getRef(id).state() - Get React component state/hooks (serialized)
|
|
18065
18061
|
fill(id, text) - Clear and fill input (works with rich text editors)
|
|
18066
18062
|
drag(opts) - Drag with custom MIME types
|
|
18067
18063
|
opts.from: source selector or ref ID (e.g., "e1" or "text=src")
|
|
@@ -18077,22 +18073,14 @@ HELPERS
|
|
|
18077
18073
|
await waitFor('.btn') - wait for selector
|
|
18078
18074
|
await waitFor('networkidle') - wait for network idle
|
|
18079
18075
|
await waitFor('load') - wait for page load
|
|
18080
|
-
ref(id).source() - Get React component source file info for element
|
|
18081
|
-
Returns { filePath, lineNumber, componentName } or null
|
|
18082
18076
|
grab - React Grab client API (activate, copyElement, etc)
|
|
18083
18077
|
|
|
18084
|
-
SNAPSHOT
|
|
18085
|
-
# Full YAML tree (default
|
|
18086
|
-
execute "return await
|
|
18087
|
-
|
|
18088
|
-
# Interactable only (recommended - much smaller!)
|
|
18089
|
-
execute "return await snapshot({interactableOnly: true})"
|
|
18090
|
-
|
|
18091
|
-
# Compact format (minimal output: ref:role:name|ref:role:name)
|
|
18092
|
-
execute "return await snapshot({format: 'compact'})"
|
|
18078
|
+
SNAPSHOT OPTIONS
|
|
18079
|
+
# Full YAML tree (default)
|
|
18080
|
+
execute "return await getSnapshot()"
|
|
18093
18081
|
|
|
18094
|
-
#
|
|
18095
|
-
execute "return await
|
|
18082
|
+
# With depth limit (smaller output)
|
|
18083
|
+
execute "return await getSnapshot({maxDepth: 6})"
|
|
18096
18084
|
|
|
18097
18085
|
SCREENSHOTS - PREFER ELEMENT OVER FULL PAGE
|
|
18098
18086
|
For visual issues (wrong color, broken styling, misalignment), ALWAYS screenshot
|
|
@@ -18102,8 +18090,8 @@ SCREENSHOTS - PREFER ELEMENT OVER FULL PAGE
|
|
|
18102
18090
|
- Easier to compare
|
|
18103
18091
|
|
|
18104
18092
|
# Element screenshot (PREFERRED)
|
|
18105
|
-
execute "await
|
|
18106
|
-
execute "await
|
|
18093
|
+
execute "await getRef('e1').screenshot({path: '/tmp/button.png'})"
|
|
18094
|
+
execute "await getRef('e5').screenshot({path: '/tmp/card.png'})"
|
|
18107
18095
|
|
|
18108
18096
|
# Full page (only when needed)
|
|
18109
18097
|
execute "await page.screenshot({path: '/tmp/full.png'})"
|
|
@@ -18113,10 +18101,10 @@ SCREENSHOTS - PREFER ELEMENT OVER FULL PAGE
|
|
|
18113
18101
|
|
|
18114
18102
|
COMMON PATTERNS
|
|
18115
18103
|
# Click by ref (chainable - no double await needed!)
|
|
18116
|
-
execute "await
|
|
18104
|
+
execute "await getRef('e1').click()"
|
|
18117
18105
|
|
|
18118
18106
|
# Get element attribute
|
|
18119
|
-
execute "return await
|
|
18107
|
+
execute "return await getRef('e1').getAttribute('data-id')"
|
|
18120
18108
|
|
|
18121
18109
|
# Fill input (clears existing content)
|
|
18122
18110
|
execute "await fill('e1', 'text')"
|
|
@@ -18135,18 +18123,25 @@ COMMON PATTERNS
|
|
|
18135
18123
|
dataTransfer: { 'application/x-custom': 'data' }
|
|
18136
18124
|
})"
|
|
18137
18125
|
|
|
18138
|
-
# Get React component source file
|
|
18139
|
-
execute "return await ref('e1').source()"
|
|
18140
|
-
|
|
18141
18126
|
# Get page info
|
|
18142
18127
|
execute "return {url: page.url(), title: await page.title()}"
|
|
18143
18128
|
|
|
18144
18129
|
# CSS selector fallback (refs are now in DOM as aria-ref)
|
|
18145
18130
|
execute "await page.click('[aria-ref="e1"]')"
|
|
18146
18131
|
|
|
18132
|
+
REACT-SPECIFIC PATTERNS
|
|
18133
|
+
# Get React component source file
|
|
18134
|
+
execute "return await getRef('e1').source()"
|
|
18135
|
+
|
|
18136
|
+
# Get component props
|
|
18137
|
+
execute "return await getRef('e1').props()"
|
|
18138
|
+
|
|
18139
|
+
# Get component state
|
|
18140
|
+
execute "return await getRef('e1').state()"
|
|
18141
|
+
|
|
18147
18142
|
MULTI-PAGE SESSIONS
|
|
18148
18143
|
execute "await page.goto('https://github.com')" --page github
|
|
18149
|
-
execute "return await
|
|
18144
|
+
execute "return await getSnapshot()" --page github
|
|
18150
18145
|
|
|
18151
18146
|
PLAYWRIGHT DOCS: https://playwright.dev/docs/api/class-page
|
|
18152
18147
|
`;
|
|
@@ -18200,7 +18195,7 @@ browser.addCommand(status);
|
|
|
18200
18195
|
browser.addCommand(execute);
|
|
18201
18196
|
browser.addCommand(pages);
|
|
18202
18197
|
browser.addCommand(mcp);
|
|
18203
|
-
var VERSION3 = "0.1.0-beta.
|
|
18198
|
+
var VERSION3 = "0.1.0-beta.5";
|
|
18204
18199
|
var isMac = process.platform === "darwin";
|
|
18205
18200
|
var META_LABEL = isMac ? "Cmd" : "Win";
|
|
18206
18201
|
var ALT_LABEL = isMac ? "Option" : "Alt";
|
|
@@ -18638,112 +18633,48 @@ var configure = new Command().name("configure").alias("config").description("con
|
|
|
18638
18633
|
handleError(error48);
|
|
18639
18634
|
}
|
|
18640
18635
|
});
|
|
18641
|
-
|
|
18642
|
-
// src/utils/cli-helpers.ts
|
|
18643
|
-
var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] ?? agent).join(", ");
|
|
18644
|
-
var applyTransformWithFeedback = (result, message) => {
|
|
18645
|
-
const writeSpinner = spinner(message ?? `Applying changes to ${result.filePath}.`).start();
|
|
18646
|
-
const writeResult = applyTransform(result);
|
|
18647
|
-
if (!writeResult.success) {
|
|
18648
|
-
writeSpinner.fail();
|
|
18649
|
-
logger.break();
|
|
18650
|
-
logger.error(writeResult.error || "Failed to write file.");
|
|
18651
|
-
logger.break();
|
|
18652
|
-
process.exit(1);
|
|
18653
|
-
}
|
|
18654
|
-
writeSpinner.succeed();
|
|
18655
|
-
};
|
|
18656
|
-
var applyPackageJsonWithFeedback = (result, message) => {
|
|
18657
|
-
const writeSpinner = spinner(message ?? `Applying changes to ${result.filePath}.`).start();
|
|
18658
|
-
const writeResult = applyPackageJsonTransform(result);
|
|
18659
|
-
if (!writeResult.success) {
|
|
18660
|
-
writeSpinner.fail();
|
|
18661
|
-
logger.break();
|
|
18662
|
-
logger.error(writeResult.error || "Failed to write file.");
|
|
18663
|
-
logger.break();
|
|
18664
|
-
process.exit(1);
|
|
18665
|
-
}
|
|
18666
|
-
writeSpinner.succeed();
|
|
18667
|
-
};
|
|
18668
|
-
var installPackagesWithFeedback = (packages, packageManager, projectRoot) => {
|
|
18669
|
-
if (packages.length === 0) return;
|
|
18670
|
-
const installSpinner = spinner(`Installing ${packages.join(", ")}.`).start();
|
|
18671
|
-
try {
|
|
18672
|
-
installPackages(packages, packageManager, projectRoot);
|
|
18673
|
-
installSpinner.succeed();
|
|
18674
|
-
} catch (error48) {
|
|
18675
|
-
installSpinner.fail();
|
|
18676
|
-
handleError(error48);
|
|
18677
|
-
}
|
|
18678
|
-
};
|
|
18679
|
-
var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
|
|
18680
|
-
if (packages.length === 0) return;
|
|
18681
|
-
const uninstallSpinner = spinner(`Removing ${packages.join(", ")}.`).start();
|
|
18682
|
-
try {
|
|
18683
|
-
uninstallPackages(packages, packageManager, projectRoot);
|
|
18684
|
-
uninstallSpinner.succeed();
|
|
18685
|
-
} catch (error48) {
|
|
18686
|
-
uninstallSpinner.fail();
|
|
18687
|
-
handleError(error48);
|
|
18688
|
-
}
|
|
18689
|
-
};
|
|
18690
|
-
var VERSION4 = "0.1.0-beta.3";
|
|
18636
|
+
var VERSION4 = "0.1.0-beta.5";
|
|
18691
18637
|
var REPORT_URL = "https://react-grab.com/api/report-cli";
|
|
18692
18638
|
var DOCS_URL = "https://github.com/aidenybai/react-grab";
|
|
18693
|
-
var
|
|
18694
|
-
const
|
|
18639
|
+
var promptSkillInstall = async (cwd) => {
|
|
18640
|
+
const detectedAgents = detectSkillAgents(cwd);
|
|
18641
|
+
if (detectedAgents.length === 0) {
|
|
18642
|
+
return;
|
|
18643
|
+
}
|
|
18644
|
+
logger.log(`The ${highlighter.info("React Grab skill")} gives your agent access to the browser.`);
|
|
18645
|
+
logger.log(`Learn more at ${highlighter.info("https://skill.md")}`);
|
|
18646
|
+
logger.break();
|
|
18647
|
+
const { wantSkill } = await prompts3({
|
|
18648
|
+
type: "confirm",
|
|
18649
|
+
name: "wantSkill",
|
|
18650
|
+
message: `Would you like to install the skill?`,
|
|
18651
|
+
initial: true
|
|
18652
|
+
});
|
|
18653
|
+
if (!wantSkill) return;
|
|
18654
|
+
const { selectedAgent } = await prompts3({
|
|
18695
18655
|
type: "select",
|
|
18696
|
-
name: "
|
|
18697
|
-
message: `
|
|
18656
|
+
name: "selectedAgent",
|
|
18657
|
+
message: `Which ${highlighter.info("agent")} would you like to install the skill for?`,
|
|
18698
18658
|
choices: [
|
|
18699
|
-
|
|
18700
|
-
|
|
18701
|
-
|
|
18702
|
-
|
|
18659
|
+
...detectedAgents.map((agent) => ({
|
|
18660
|
+
title: agent.name,
|
|
18661
|
+
value: agent
|
|
18662
|
+
})),
|
|
18663
|
+
{ title: "Skip", value: null }
|
|
18703
18664
|
]
|
|
18704
18665
|
});
|
|
18705
|
-
if (
|
|
18706
|
-
if (integrationType === "mcp" || integrationType === "both") {
|
|
18707
|
-
const { mcpClient } = await prompts3({
|
|
18708
|
-
type: "select",
|
|
18709
|
-
name: "mcpClient",
|
|
18710
|
-
message: `Which ${highlighter.info("MCP client")} would you like to configure?`,
|
|
18711
|
-
choices: MCP_CLIENTS.map((client) => ({
|
|
18712
|
-
title: MCP_CLIENT_NAMES[client],
|
|
18713
|
-
value: client
|
|
18714
|
-
}))
|
|
18715
|
-
});
|
|
18716
|
-
if (mcpClient) {
|
|
18717
|
-
const mcpCommand = customPkg ? `npx -y ${customPkg} browser mcp` : `npx -y @react-grab/cli browser mcp`;
|
|
18718
|
-
logger.break();
|
|
18719
|
-
try {
|
|
18720
|
-
execSync(
|
|
18721
|
-
`npx -y install-mcp '${mcpCommand}' --client ${mcpClient} --yes`,
|
|
18722
|
-
{ stdio: "inherit", cwd }
|
|
18723
|
-
);
|
|
18724
|
-
logger.break();
|
|
18725
|
-
logger.success("MCP server has been configured.");
|
|
18726
|
-
} catch {
|
|
18727
|
-
logger.break();
|
|
18728
|
-
logger.warn("Failed to configure MCP server. You can try again later with:");
|
|
18729
|
-
logger.log(` npx -y install-mcp '${mcpCommand}' --client ${mcpClient}`);
|
|
18730
|
-
}
|
|
18731
|
-
}
|
|
18732
|
-
}
|
|
18733
|
-
if (integrationType === "skill" || integrationType === "both") {
|
|
18666
|
+
if (selectedAgent) {
|
|
18734
18667
|
logger.break();
|
|
18735
|
-
const skillSpinner = spinner(
|
|
18668
|
+
const skillSpinner = spinner(`Installing skill for ${selectedAgent.name}`).start();
|
|
18736
18669
|
try {
|
|
18737
|
-
execSync(`npx -y
|
|
18738
|
-
stdio: "
|
|
18670
|
+
execSync(`npx -y add-skill aidenybai/react-grab -y --agent ${selectedAgent.id}`, {
|
|
18671
|
+
stdio: "ignore",
|
|
18739
18672
|
cwd
|
|
18740
18673
|
});
|
|
18741
|
-
|
|
18742
|
-
skillSpinner.succeed("Skill installed to .claude/skills/");
|
|
18674
|
+
skillSpinner.succeed(`Skill installed for ${selectedAgent.name}.`);
|
|
18743
18675
|
} catch {
|
|
18744
|
-
|
|
18745
|
-
|
|
18746
|
-
logger.dim("Try manually: npx -y openskills install aidenybai/react-grab");
|
|
18676
|
+
skillSpinner.fail(`Failed to install skill for ${selectedAgent.name}.`);
|
|
18677
|
+
logger.warn(`Try manually: npx -y add-skill aidenybai/react-grab --agent ${selectedAgent.id}`);
|
|
18747
18678
|
}
|
|
18748
18679
|
}
|
|
18749
18680
|
logger.break();
|
|
@@ -18877,7 +18808,7 @@ var init = new Command().name("init").description("initialize React Grab in your
|
|
|
18877
18808
|
);
|
|
18878
18809
|
logger.break();
|
|
18879
18810
|
}
|
|
18880
|
-
await
|
|
18811
|
+
await promptSkillInstall(cwd);
|
|
18881
18812
|
const { wantCustomizeOptions } = await prompts3({
|
|
18882
18813
|
type: "confirm",
|
|
18883
18814
|
name: "wantCustomizeOptions",
|
|
@@ -19043,14 +18974,17 @@ var init = new Command().name("init").description("initialize React Grab in your
|
|
|
19043
18974
|
type: "select",
|
|
19044
18975
|
name: "agent",
|
|
19045
18976
|
message: `Which ${highlighter.info("agent integration")} would you like to add?`,
|
|
19046
|
-
choices:
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
18977
|
+
choices: [
|
|
18978
|
+
...availableAgents.map((innerAgent) => ({
|
|
18979
|
+
title: getAgentName(innerAgent),
|
|
18980
|
+
value: innerAgent
|
|
18981
|
+
})),
|
|
18982
|
+
{ title: "Skip", value: "skip" }
|
|
18983
|
+
]
|
|
19050
18984
|
});
|
|
19051
|
-
if (agent === void 0) {
|
|
18985
|
+
if (agent === void 0 || agent === "skip") {
|
|
19052
18986
|
logger.break();
|
|
19053
|
-
process.exit(
|
|
18987
|
+
process.exit(0);
|
|
19054
18988
|
}
|
|
19055
18989
|
const agentIntegration2 = agent;
|
|
19056
18990
|
let agentsToRemove2 = [];
|
|
@@ -19213,17 +19147,21 @@ var init = new Command().name("init").description("initialize React Grab in your
|
|
|
19213
19147
|
type: "select",
|
|
19214
19148
|
name: "selectedProject",
|
|
19215
19149
|
message: "Select a project to install React Grab:",
|
|
19216
|
-
choices:
|
|
19217
|
-
|
|
19218
|
-
|
|
19219
|
-
|
|
19220
|
-
|
|
19221
|
-
|
|
19222
|
-
|
|
19150
|
+
choices: [
|
|
19151
|
+
...sortedProjects.map((project) => {
|
|
19152
|
+
const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
|
|
19153
|
+
return {
|
|
19154
|
+
title: `${project.name}${frameworkLabel}`,
|
|
19155
|
+
value: project.path
|
|
19156
|
+
};
|
|
19157
|
+
}),
|
|
19158
|
+
{ title: "Skip", value: "skip" }
|
|
19159
|
+
]
|
|
19223
19160
|
});
|
|
19224
|
-
if (!selectedProject) {
|
|
19161
|
+
if (!selectedProject || selectedProject === "skip") {
|
|
19225
19162
|
logger.break();
|
|
19226
|
-
|
|
19163
|
+
await promptSkillInstall(cwd);
|
|
19164
|
+
process.exit(0);
|
|
19227
19165
|
}
|
|
19228
19166
|
process.chdir(selectedProject);
|
|
19229
19167
|
const newProjectInfo = await detectProject(selectedProject);
|
|
@@ -19365,7 +19303,7 @@ var init = new Command().name("init").description("initialize React Grab in your
|
|
|
19365
19303
|
}
|
|
19366
19304
|
logger.break();
|
|
19367
19305
|
if (!isNonInteractive) {
|
|
19368
|
-
await
|
|
19306
|
+
await promptSkillInstall(cwd);
|
|
19369
19307
|
}
|
|
19370
19308
|
await reportToCli("completed", {
|
|
19371
19309
|
framework: finalFramework,
|
|
@@ -19379,7 +19317,7 @@ var init = new Command().name("init").description("initialize React Grab in your
|
|
|
19379
19317
|
await reportToCli("error", void 0, error48);
|
|
19380
19318
|
}
|
|
19381
19319
|
});
|
|
19382
|
-
var VERSION5 = "0.1.0-beta.
|
|
19320
|
+
var VERSION5 = "0.1.0-beta.5";
|
|
19383
19321
|
var remove = new Command().name("remove").description("remove an agent integration").argument(
|
|
19384
19322
|
"[agent]",
|
|
19385
19323
|
"agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami, visual-edit)"
|
|
@@ -19556,9 +19494,123 @@ var remove = new Command().name("remove").description("remove an agent integrati
|
|
|
19556
19494
|
handleError(error48);
|
|
19557
19495
|
}
|
|
19558
19496
|
});
|
|
19497
|
+
var VERSION6 = "0.1.0-beta.5";
|
|
19498
|
+
var UPDATE_COMMANDS = {
|
|
19499
|
+
npm: "npm install -g grab@latest",
|
|
19500
|
+
yarn: "yarn global add grab@latest",
|
|
19501
|
+
pnpm: "pnpm add -g grab@latest",
|
|
19502
|
+
bun: "bun add -g grab@latest"
|
|
19503
|
+
};
|
|
19504
|
+
var MANAGER_NAMES = {
|
|
19505
|
+
npm: "npm",
|
|
19506
|
+
yarn: "Yarn",
|
|
19507
|
+
pnpm: "pnpm",
|
|
19508
|
+
bun: "Bun"
|
|
19509
|
+
};
|
|
19510
|
+
var execSilent = (command) => {
|
|
19511
|
+
try {
|
|
19512
|
+
return execSync(command, {
|
|
19513
|
+
encoding: "utf-8",
|
|
19514
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
19515
|
+
}).trim();
|
|
19516
|
+
} catch {
|
|
19517
|
+
return null;
|
|
19518
|
+
}
|
|
19519
|
+
};
|
|
19520
|
+
var getInstalledVersion = () => {
|
|
19521
|
+
const output = execSilent("grab --version");
|
|
19522
|
+
if (!output) return null;
|
|
19523
|
+
const match = output.match(/(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)/);
|
|
19524
|
+
return match ? match[1] : null;
|
|
19525
|
+
};
|
|
19526
|
+
var getLatestVersion = () => execSilent("npm view grab version");
|
|
19527
|
+
var isPackageManagerInstalled = (manager) => execSilent(`${manager} --version`) !== null;
|
|
19528
|
+
var detectGlobalPackageManager = () => {
|
|
19529
|
+
const managers = ["pnpm", "yarn", "bun"];
|
|
19530
|
+
for (const manager of managers) {
|
|
19531
|
+
if (isPackageManagerInstalled(manager)) {
|
|
19532
|
+
return manager;
|
|
19533
|
+
}
|
|
19534
|
+
}
|
|
19535
|
+
return "npm";
|
|
19536
|
+
};
|
|
19537
|
+
var update = new Command().name("update").alias("upgrade").description("update grab to the latest version").option("-y, --yes", "skip confirmation prompts", false).option("--check", "only check for updates, don't install", false).action(async (opts2) => {
|
|
19538
|
+
console.log(
|
|
19539
|
+
`${pc.magenta("\u273F")} ${pc.bold("React Grab")} ${pc.gray(VERSION6)}`
|
|
19540
|
+
);
|
|
19541
|
+
console.log();
|
|
19542
|
+
const checkSpinner = spinner("Checking for updates").start();
|
|
19543
|
+
const installedVersion = getInstalledVersion();
|
|
19544
|
+
const latestVersion = getLatestVersion();
|
|
19545
|
+
if (!latestVersion) {
|
|
19546
|
+
checkSpinner.fail("Failed to fetch latest version from npm");
|
|
19547
|
+
logger.break();
|
|
19548
|
+
logger.error("Could not reach npm registry. Check your network connection.");
|
|
19549
|
+
logger.break();
|
|
19550
|
+
process.exit(1);
|
|
19551
|
+
}
|
|
19552
|
+
if (!installedVersion) {
|
|
19553
|
+
checkSpinner.fail("grab is not installed globally");
|
|
19554
|
+
logger.break();
|
|
19555
|
+
logger.log(`Install it with: ${highlighter.info("npm install -g grab")}`);
|
|
19556
|
+
logger.break();
|
|
19557
|
+
process.exit(1);
|
|
19558
|
+
}
|
|
19559
|
+
checkSpinner.succeed(`Current version: ${highlighter.info(installedVersion)}`);
|
|
19560
|
+
if (installedVersion === latestVersion) {
|
|
19561
|
+
logger.break();
|
|
19562
|
+
logger.success(`You're already on the latest version (${latestVersion})`);
|
|
19563
|
+
logger.break();
|
|
19564
|
+
process.exit(0);
|
|
19565
|
+
}
|
|
19566
|
+
logger.log(`Latest version: ${highlighter.success(latestVersion)}`);
|
|
19567
|
+
logger.break();
|
|
19568
|
+
if (opts2.check) {
|
|
19569
|
+
logger.log(`Update available: ${installedVersion} \u2192 ${latestVersion}`);
|
|
19570
|
+
logger.log(`Run ${highlighter.info("grab update")} to install the update.`);
|
|
19571
|
+
logger.break();
|
|
19572
|
+
process.exit(0);
|
|
19573
|
+
}
|
|
19574
|
+
const packageManager = detectGlobalPackageManager();
|
|
19575
|
+
if (!opts2.yes) {
|
|
19576
|
+
const { shouldProceed } = await prompts3({
|
|
19577
|
+
type: "confirm",
|
|
19578
|
+
name: "shouldProceed",
|
|
19579
|
+
message: `Update grab from ${installedVersion} to ${latestVersion} using ${MANAGER_NAMES[packageManager]}?`,
|
|
19580
|
+
initial: true
|
|
19581
|
+
});
|
|
19582
|
+
if (!shouldProceed) {
|
|
19583
|
+
logger.break();
|
|
19584
|
+
logger.log("Update cancelled.");
|
|
19585
|
+
logger.break();
|
|
19586
|
+
process.exit(0);
|
|
19587
|
+
}
|
|
19588
|
+
}
|
|
19589
|
+
logger.break();
|
|
19590
|
+
const updateSpinner = spinner(`Updating grab to ${latestVersion}`).start();
|
|
19591
|
+
try {
|
|
19592
|
+
const command = UPDATE_COMMANDS[packageManager];
|
|
19593
|
+
execSync(command, {
|
|
19594
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
19595
|
+
});
|
|
19596
|
+
updateSpinner.succeed(`Updated grab to ${latestVersion}`);
|
|
19597
|
+
logger.break();
|
|
19598
|
+
logger.success("Update complete!");
|
|
19599
|
+
logger.break();
|
|
19600
|
+
} catch (error48) {
|
|
19601
|
+
updateSpinner.fail("Failed to update grab");
|
|
19602
|
+
logger.break();
|
|
19603
|
+
logger.error(`Update failed. Try manually: ${highlighter.info(UPDATE_COMMANDS[packageManager])}`);
|
|
19604
|
+
if (error48 instanceof Error && error48.message) {
|
|
19605
|
+
logger.dim(error48.message);
|
|
19606
|
+
}
|
|
19607
|
+
logger.break();
|
|
19608
|
+
process.exit(1);
|
|
19609
|
+
}
|
|
19610
|
+
});
|
|
19559
19611
|
|
|
19560
19612
|
// src/cli.ts
|
|
19561
|
-
var
|
|
19613
|
+
var VERSION7 = "0.1.0-beta.5";
|
|
19562
19614
|
var VERSION_API_URL = "https://www.react-grab.com/api/version";
|
|
19563
19615
|
process.on("SIGINT", () => process.exit(0));
|
|
19564
19616
|
process.on("SIGTERM", () => process.exit(0));
|
|
@@ -19567,12 +19619,13 @@ try {
|
|
|
19567
19619
|
});
|
|
19568
19620
|
} catch {
|
|
19569
19621
|
}
|
|
19570
|
-
var program = new Command().name("
|
|
19622
|
+
var program = new Command().name("grab").description("add React Grab to your project").version(VERSION7, "-v, --version", "display the version number");
|
|
19571
19623
|
program.addCommand(init);
|
|
19572
19624
|
program.addCommand(add);
|
|
19573
19625
|
program.addCommand(remove);
|
|
19574
19626
|
program.addCommand(configure);
|
|
19575
19627
|
program.addCommand(browser);
|
|
19628
|
+
program.addCommand(update);
|
|
19576
19629
|
var completion = f2(program);
|
|
19577
19630
|
var initCommand = completion.commands.get("init");
|
|
19578
19631
|
var initAgentOption = initCommand?.options.get("agent");
|
|
@@ -19596,8 +19649,8 @@ var addCommand = completion.commands.get("add");
|
|
|
19596
19649
|
var addAgentArg = addCommand?.arguments.get("agent");
|
|
19597
19650
|
if (addAgentArg) {
|
|
19598
19651
|
addAgentArg.handler = (complete) => {
|
|
19599
|
-
complete("
|
|
19600
|
-
complete("
|
|
19652
|
+
complete("skill", "Instructions for your agent to use the browser (recommended)");
|
|
19653
|
+
complete("mcp", "A server that provides browser tools to your agent");
|
|
19601
19654
|
for (const agent of AGENTS) {
|
|
19602
19655
|
complete(agent, "");
|
|
19603
19656
|
}
|