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