@react-grab/cli 0.0.68

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.js ADDED
@@ -0,0 +1,927 @@
1
+ #!/usr/bin/env node
2
+ import { select, confirm } from '@inquirer/prompts';
3
+ import pc from 'picocolors';
4
+ import yargs from 'yargs';
5
+ import { hideBin } from 'yargs/helpers';
6
+ import { writeFileSync, existsSync, readFileSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { detect } from '@antfu/ni';
9
+ import { execSync } from 'child_process';
10
+
11
+ var detectPackageManager = async (projectRoot) => {
12
+ const detected = await detect({ cwd: projectRoot });
13
+ if (detected && ["npm", "yarn", "pnpm", "bun"].includes(detected)) {
14
+ return detected;
15
+ }
16
+ return "npm";
17
+ };
18
+ var detectFramework = (projectRoot) => {
19
+ const packageJsonPath = join(projectRoot, "package.json");
20
+ if (!existsSync(packageJsonPath)) {
21
+ return "unknown";
22
+ }
23
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
24
+ const allDependencies = {
25
+ ...packageJson.dependencies,
26
+ ...packageJson.devDependencies
27
+ };
28
+ if (allDependencies["next"]) {
29
+ return "next";
30
+ }
31
+ if (allDependencies["vite"]) {
32
+ return "vite";
33
+ }
34
+ if (allDependencies["webpack"]) {
35
+ return "webpack";
36
+ }
37
+ return "unknown";
38
+ };
39
+ var detectNextRouterType = (projectRoot) => {
40
+ const hasAppDir = existsSync(join(projectRoot, "app"));
41
+ const hasSrcAppDir = existsSync(join(projectRoot, "src", "app"));
42
+ const hasPagesDir = existsSync(join(projectRoot, "pages"));
43
+ const hasSrcPagesDir = existsSync(join(projectRoot, "src", "pages"));
44
+ if (hasAppDir || hasSrcAppDir) {
45
+ return "app";
46
+ }
47
+ if (hasPagesDir || hasSrcPagesDir) {
48
+ return "pages";
49
+ }
50
+ return "unknown";
51
+ };
52
+ var detectMonorepo = (projectRoot) => {
53
+ if (existsSync(join(projectRoot, "pnpm-workspace.yaml"))) {
54
+ return true;
55
+ }
56
+ if (existsSync(join(projectRoot, "lerna.json"))) {
57
+ return true;
58
+ }
59
+ const packageJsonPath = join(projectRoot, "package.json");
60
+ if (existsSync(packageJsonPath)) {
61
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
62
+ if (packageJson.workspaces) {
63
+ return true;
64
+ }
65
+ }
66
+ return false;
67
+ };
68
+ var detectReactGrab = (projectRoot) => {
69
+ const packageJsonPath = join(projectRoot, "package.json");
70
+ if (!existsSync(packageJsonPath)) {
71
+ return false;
72
+ }
73
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
74
+ const allDependencies = {
75
+ ...packageJson.dependencies,
76
+ ...packageJson.devDependencies
77
+ };
78
+ return Boolean(allDependencies["react-grab"]);
79
+ };
80
+ var AGENT_PACKAGES = ["@react-grab/claude-code", "@react-grab/cursor", "@react-grab/opencode"];
81
+ var detectInstalledAgents = (projectRoot) => {
82
+ const packageJsonPath = join(projectRoot, "package.json");
83
+ if (!existsSync(packageJsonPath)) {
84
+ return [];
85
+ }
86
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
87
+ const allDependencies = {
88
+ ...packageJson.dependencies,
89
+ ...packageJson.devDependencies
90
+ };
91
+ return AGENT_PACKAGES.filter((agent) => Boolean(allDependencies[agent])).map(
92
+ (agent) => agent.replace("@react-grab/", "")
93
+ );
94
+ };
95
+ var detectProject = async (projectRoot = process.cwd()) => {
96
+ const framework = detectFramework(projectRoot);
97
+ const packageManager = await detectPackageManager(projectRoot);
98
+ return {
99
+ packageManager,
100
+ framework,
101
+ nextRouterType: framework === "next" ? detectNextRouterType(projectRoot) : "unknown",
102
+ isMonorepo: detectMonorepo(projectRoot),
103
+ projectRoot,
104
+ hasReactGrab: detectReactGrab(projectRoot),
105
+ installedAgents: detectInstalledAgents(projectRoot)
106
+ };
107
+ };
108
+
109
+ // src/diff.ts
110
+ var RED = "\x1B[31m";
111
+ var GREEN = "\x1B[32m";
112
+ var GRAY = "\x1B[90m";
113
+ var RESET = "\x1B[0m";
114
+ var BOLD = "\x1B[1m";
115
+ var generateDiff = (originalContent, newContent) => {
116
+ const originalLines = originalContent.split("\n");
117
+ const newLines = newContent.split("\n");
118
+ const diff = [];
119
+ Math.max(originalLines.length, newLines.length);
120
+ let originalIndex = 0;
121
+ let newIndex = 0;
122
+ while (originalIndex < originalLines.length || newIndex < newLines.length) {
123
+ const originalLine = originalLines[originalIndex];
124
+ const newLine = newLines[newIndex];
125
+ if (originalLine === newLine) {
126
+ diff.push({ type: "unchanged", content: originalLine, lineNumber: newIndex + 1 });
127
+ originalIndex++;
128
+ newIndex++;
129
+ } else if (originalLine === void 0) {
130
+ diff.push({ type: "added", content: newLine, lineNumber: newIndex + 1 });
131
+ newIndex++;
132
+ } else if (newLine === void 0) {
133
+ diff.push({ type: "removed", content: originalLine });
134
+ originalIndex++;
135
+ } else {
136
+ const originalInNew = newLines.indexOf(originalLine, newIndex);
137
+ const newInOriginal = originalLines.indexOf(newLine, originalIndex);
138
+ if (originalInNew !== -1 && (newInOriginal === -1 || originalInNew - newIndex < newInOriginal - originalIndex)) {
139
+ while (newIndex < originalInNew) {
140
+ diff.push({ type: "added", content: newLines[newIndex], lineNumber: newIndex + 1 });
141
+ newIndex++;
142
+ }
143
+ } else if (newInOriginal !== -1) {
144
+ while (originalIndex < newInOriginal) {
145
+ diff.push({ type: "removed", content: originalLines[originalIndex] });
146
+ originalIndex++;
147
+ }
148
+ } else {
149
+ diff.push({ type: "removed", content: originalLine });
150
+ diff.push({ type: "added", content: newLine, lineNumber: newIndex + 1 });
151
+ originalIndex++;
152
+ newIndex++;
153
+ }
154
+ }
155
+ }
156
+ return diff;
157
+ };
158
+ var formatDiff = (diff, contextLines = 3) => {
159
+ const lines = [];
160
+ let lastPrintedIndex = -1;
161
+ let hasChanges = false;
162
+ const changedIndices = diff.map((line, index) => line.type !== "unchanged" ? index : -1).filter((index) => index !== -1);
163
+ if (changedIndices.length === 0) {
164
+ return `${GRAY}No changes${RESET}`;
165
+ }
166
+ for (const changedIndex of changedIndices) {
167
+ const startContext = Math.max(0, changedIndex - contextLines);
168
+ const endContext = Math.min(diff.length - 1, changedIndex + contextLines);
169
+ if (startContext > lastPrintedIndex + 1 && lastPrintedIndex !== -1) {
170
+ lines.push(`${GRAY} ...${RESET}`);
171
+ }
172
+ for (let lineIndex = Math.max(startContext, lastPrintedIndex + 1); lineIndex <= endContext; lineIndex++) {
173
+ const diffLine = diff[lineIndex];
174
+ if (diffLine.type === "added") {
175
+ lines.push(`${GREEN}+ ${diffLine.content}${RESET}`);
176
+ hasChanges = true;
177
+ } else if (diffLine.type === "removed") {
178
+ lines.push(`${RED}- ${diffLine.content}${RESET}`);
179
+ hasChanges = true;
180
+ } else {
181
+ lines.push(`${GRAY} ${diffLine.content}${RESET}`);
182
+ }
183
+ lastPrintedIndex = lineIndex;
184
+ }
185
+ }
186
+ return hasChanges ? lines.join("\n") : `${GRAY}No changes${RESET}`;
187
+ };
188
+ var printDiff = (filePath, originalContent, newContent) => {
189
+ console.log(`
190
+ ${BOLD}File: ${filePath}${RESET}`);
191
+ console.log("\u2500".repeat(60));
192
+ const diff = generateDiff(originalContent, newContent);
193
+ console.log(formatDiff(diff));
194
+ console.log("\u2500".repeat(60));
195
+ };
196
+ var INSTALL_COMMANDS = {
197
+ npm: "npm install",
198
+ yarn: "yarn add",
199
+ pnpm: "pnpm add",
200
+ bun: "bun add"
201
+ };
202
+ var installPackages = (packages, packageManager, projectRoot, isDev = true) => {
203
+ if (packages.length === 0) {
204
+ return;
205
+ }
206
+ const command = INSTALL_COMMANDS[packageManager];
207
+ const devFlag = isDev ? " -D" : "";
208
+ const fullCommand = `${command}${devFlag} ${packages.join(" ")}`;
209
+ console.log(`Running: ${fullCommand}
210
+ `);
211
+ execSync(fullCommand, {
212
+ cwd: projectRoot,
213
+ stdio: "inherit"
214
+ });
215
+ };
216
+ var getPackagesToInstall = (agent, includeReactGrab = true) => {
217
+ const packages = [];
218
+ if (includeReactGrab) {
219
+ packages.push("react-grab");
220
+ }
221
+ if (agent !== "none") {
222
+ packages.push(`@react-grab/${agent}`);
223
+ }
224
+ return packages;
225
+ };
226
+
227
+ // src/templates.ts
228
+ var NEXT_APP_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
229
+ <Script
230
+ src="//unpkg.com/react-grab/dist/index.global.js"
231
+ crossOrigin="anonymous"
232
+ strategy="beforeInteractive"
233
+ />
234
+ )}`;
235
+ var NEXT_APP_ROUTER_SCRIPT_WITH_AGENT = (agent) => {
236
+ if (agent === "none") return NEXT_APP_ROUTER_SCRIPT;
237
+ const agentScript = `<Script
238
+ src="//unpkg.com/@react-grab/${agent}/dist/client.global.js"
239
+ strategy="lazyOnload"
240
+ />`;
241
+ return `{process.env.NODE_ENV === "development" && (
242
+ <>
243
+ <Script
244
+ src="//unpkg.com/react-grab/dist/index.global.js"
245
+ strategy="beforeInteractive"
246
+ />
247
+ ${agentScript}
248
+ </>
249
+ )}`;
250
+ };
251
+ var NEXT_PAGES_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
252
+ <Script
253
+ src="//unpkg.com/react-grab/dist/index.global.js"
254
+ crossOrigin="anonymous"
255
+ strategy="beforeInteractive"
256
+ />
257
+ )}`;
258
+ var NEXT_PAGES_ROUTER_SCRIPT_WITH_AGENT = (agent) => {
259
+ if (agent === "none") return NEXT_PAGES_ROUTER_SCRIPT;
260
+ const agentScript = `<Script
261
+ src="//unpkg.com/@react-grab/${agent}/dist/client.global.js"
262
+ strategy="lazyOnload"
263
+ />`;
264
+ return `{process.env.NODE_ENV === "development" && (
265
+ <>
266
+ <Script
267
+ src="//unpkg.com/react-grab/dist/index.global.js"
268
+ strategy="beforeInteractive"
269
+ />
270
+ ${agentScript}
271
+ </>
272
+ )}`;
273
+ };
274
+ var VITE_SCRIPT = `<script type="module">
275
+ if (import.meta.env.DEV) {
276
+ import("react-grab");
277
+ }
278
+ </script>`;
279
+ var VITE_SCRIPT_WITH_AGENT = (agent) => {
280
+ if (agent === "none") return VITE_SCRIPT;
281
+ return `<script type="module">
282
+ if (import.meta.env.DEV) {
283
+ import("react-grab");
284
+ import("@react-grab/${agent}/client");
285
+ }
286
+ </script>`;
287
+ };
288
+ var WEBPACK_IMPORT = `if (process.env.NODE_ENV === "development") {
289
+ import("react-grab");
290
+ }`;
291
+ var WEBPACK_IMPORT_WITH_AGENT = (agent) => {
292
+ if (agent === "none") return WEBPACK_IMPORT;
293
+ return `if (process.env.NODE_ENV === "development") {
294
+ import("react-grab");
295
+ import("@react-grab/${agent}/client");
296
+ }`;
297
+ };
298
+ var SCRIPT_IMPORT = 'import Script from "next/script";';
299
+
300
+ // src/transform.ts
301
+ var findLayoutFile = (projectRoot) => {
302
+ const possiblePaths = [
303
+ join(projectRoot, "app", "layout.tsx"),
304
+ join(projectRoot, "app", "layout.jsx"),
305
+ join(projectRoot, "src", "app", "layout.tsx"),
306
+ join(projectRoot, "src", "app", "layout.jsx")
307
+ ];
308
+ for (const filePath of possiblePaths) {
309
+ if (existsSync(filePath)) {
310
+ return filePath;
311
+ }
312
+ }
313
+ return null;
314
+ };
315
+ var findDocumentFile = (projectRoot) => {
316
+ const possiblePaths = [
317
+ join(projectRoot, "pages", "_document.tsx"),
318
+ join(projectRoot, "pages", "_document.jsx"),
319
+ join(projectRoot, "src", "pages", "_document.tsx"),
320
+ join(projectRoot, "src", "pages", "_document.jsx")
321
+ ];
322
+ for (const filePath of possiblePaths) {
323
+ if (existsSync(filePath)) {
324
+ return filePath;
325
+ }
326
+ }
327
+ return null;
328
+ };
329
+ var findIndexHtml = (projectRoot) => {
330
+ const possiblePaths = [
331
+ join(projectRoot, "index.html"),
332
+ join(projectRoot, "public", "index.html")
333
+ ];
334
+ for (const filePath of possiblePaths) {
335
+ if (existsSync(filePath)) {
336
+ return filePath;
337
+ }
338
+ }
339
+ return null;
340
+ };
341
+ var findEntryFile = (projectRoot) => {
342
+ const possiblePaths = [
343
+ join(projectRoot, "src", "index.tsx"),
344
+ join(projectRoot, "src", "index.jsx"),
345
+ join(projectRoot, "src", "index.ts"),
346
+ join(projectRoot, "src", "index.js"),
347
+ join(projectRoot, "src", "main.tsx"),
348
+ join(projectRoot, "src", "main.jsx"),
349
+ join(projectRoot, "src", "main.ts"),
350
+ join(projectRoot, "src", "main.js")
351
+ ];
352
+ for (const filePath of possiblePaths) {
353
+ if (existsSync(filePath)) {
354
+ return filePath;
355
+ }
356
+ }
357
+ return null;
358
+ };
359
+ var addAgentToExistingNextApp = (originalContent, agent, filePath) => {
360
+ if (agent === "none") {
361
+ return {
362
+ success: true,
363
+ filePath,
364
+ message: "React Grab is already configured",
365
+ noChanges: true
366
+ };
367
+ }
368
+ const agentPackage = `@react-grab/${agent}`;
369
+ if (originalContent.includes(agentPackage)) {
370
+ return {
371
+ success: true,
372
+ filePath,
373
+ message: `Agent ${agent} is already configured`,
374
+ noChanges: true
375
+ };
376
+ }
377
+ const agentScript = `<Script
378
+ src="//unpkg.com/${agentPackage}/dist/client.global.js"
379
+ strategy="lazyOnload"
380
+ />`;
381
+ const reactGrabScriptMatch = originalContent.match(
382
+ /<Script[^>]*src="[^"]*react-grab[^"]*"[^>]*\/?>/
383
+ );
384
+ if (reactGrabScriptMatch) {
385
+ const newContent = originalContent.replace(
386
+ reactGrabScriptMatch[0],
387
+ `${reactGrabScriptMatch[0]}
388
+ ${agentScript}`
389
+ );
390
+ return {
391
+ success: true,
392
+ filePath,
393
+ message: `Add ${agent} agent`,
394
+ originalContent,
395
+ newContent
396
+ };
397
+ }
398
+ return {
399
+ success: false,
400
+ filePath,
401
+ message: "Could not find React Grab script to add agent after"
402
+ };
403
+ };
404
+ var addAgentToExistingVite = (originalContent, agent, filePath) => {
405
+ if (agent === "none") {
406
+ return {
407
+ success: true,
408
+ filePath,
409
+ message: "React Grab is already configured",
410
+ noChanges: true
411
+ };
412
+ }
413
+ const agentPackage = `@react-grab/${agent}`;
414
+ if (originalContent.includes(agentPackage)) {
415
+ return {
416
+ success: true,
417
+ filePath,
418
+ message: `Agent ${agent} is already configured`,
419
+ noChanges: true
420
+ };
421
+ }
422
+ const agentImport = `import("${agentPackage}/client");`;
423
+ const reactGrabImportMatch = originalContent.match(/import\s*\(\s*["']react-grab["']\s*\)/);
424
+ if (reactGrabImportMatch) {
425
+ const newContent = originalContent.replace(
426
+ reactGrabImportMatch[0],
427
+ `${reactGrabImportMatch[0]};
428
+ ${agentImport}`
429
+ );
430
+ return {
431
+ success: true,
432
+ filePath,
433
+ message: `Add ${agent} agent`,
434
+ originalContent,
435
+ newContent
436
+ };
437
+ }
438
+ return {
439
+ success: false,
440
+ filePath,
441
+ message: "Could not find React Grab import to add agent after"
442
+ };
443
+ };
444
+ var addAgentToExistingWebpack = (originalContent, agent, filePath) => {
445
+ if (agent === "none") {
446
+ return {
447
+ success: true,
448
+ filePath,
449
+ message: "React Grab is already configured",
450
+ noChanges: true
451
+ };
452
+ }
453
+ const agentPackage = `@react-grab/${agent}`;
454
+ if (originalContent.includes(agentPackage)) {
455
+ return {
456
+ success: true,
457
+ filePath,
458
+ message: `Agent ${agent} is already configured`,
459
+ noChanges: true
460
+ };
461
+ }
462
+ const agentImport = `import("${agentPackage}/client");`;
463
+ const reactGrabImportMatch = originalContent.match(/import\s*\(\s*["']react-grab["']\s*\)/);
464
+ if (reactGrabImportMatch) {
465
+ const newContent = originalContent.replace(
466
+ reactGrabImportMatch[0],
467
+ `${reactGrabImportMatch[0]};
468
+ ${agentImport}`
469
+ );
470
+ return {
471
+ success: true,
472
+ filePath,
473
+ message: `Add ${agent} agent`,
474
+ originalContent,
475
+ newContent
476
+ };
477
+ }
478
+ return {
479
+ success: false,
480
+ filePath,
481
+ message: "Could not find React Grab import to add agent after"
482
+ };
483
+ };
484
+ var transformNextAppRouter = (projectRoot, agent, reactGrabAlreadyConfigured) => {
485
+ const layoutPath = findLayoutFile(projectRoot);
486
+ if (!layoutPath) {
487
+ return {
488
+ success: false,
489
+ filePath: "",
490
+ message: "Could not find app/layout.tsx or app/layout.jsx"
491
+ };
492
+ }
493
+ const originalContent = readFileSync(layoutPath, "utf-8");
494
+ let newContent = originalContent;
495
+ const hasReactGrabInFile = originalContent.includes("react-grab");
496
+ if (hasReactGrabInFile && reactGrabAlreadyConfigured) {
497
+ return addAgentToExistingNextApp(originalContent, agent, layoutPath);
498
+ }
499
+ if (hasReactGrabInFile) {
500
+ return {
501
+ success: true,
502
+ filePath: layoutPath,
503
+ message: "React Grab is already installed in this file",
504
+ noChanges: true
505
+ };
506
+ }
507
+ if (!newContent.includes('import Script from "next/script"')) {
508
+ const importMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
509
+ if (importMatch) {
510
+ newContent = newContent.replace(importMatch[0], `${importMatch[0]}
511
+ ${SCRIPT_IMPORT}`);
512
+ } else {
513
+ newContent = `${SCRIPT_IMPORT}
514
+
515
+ ${newContent}`;
516
+ }
517
+ }
518
+ const scriptBlock = NEXT_APP_ROUTER_SCRIPT_WITH_AGENT(agent);
519
+ const headMatch = newContent.match(/<head[^>]*>/);
520
+ if (headMatch) {
521
+ newContent = newContent.replace(headMatch[0], `${headMatch[0]}
522
+ ${scriptBlock}`);
523
+ } else {
524
+ const htmlMatch = newContent.match(/<html[^>]*>/);
525
+ if (htmlMatch) {
526
+ newContent = newContent.replace(
527
+ htmlMatch[0],
528
+ `${htmlMatch[0]}
529
+ <head>
530
+ ${scriptBlock}
531
+ </head>`
532
+ );
533
+ }
534
+ }
535
+ return {
536
+ success: true,
537
+ filePath: layoutPath,
538
+ message: "Add React Grab" + (agent !== "none" ? ` with ${agent} agent` : ""),
539
+ originalContent,
540
+ newContent
541
+ };
542
+ };
543
+ var transformNextPagesRouter = (projectRoot, agent, reactGrabAlreadyConfigured) => {
544
+ const documentPath = findDocumentFile(projectRoot);
545
+ if (!documentPath) {
546
+ return {
547
+ success: false,
548
+ filePath: "",
549
+ message: "Could not find pages/_document.tsx. Please create one or add React Grab manually."
550
+ };
551
+ }
552
+ const originalContent = readFileSync(documentPath, "utf-8");
553
+ let newContent = originalContent;
554
+ const hasReactGrabInFile = originalContent.includes("react-grab");
555
+ if (hasReactGrabInFile && reactGrabAlreadyConfigured) {
556
+ return addAgentToExistingNextApp(originalContent, agent, documentPath);
557
+ }
558
+ if (hasReactGrabInFile) {
559
+ return {
560
+ success: true,
561
+ filePath: documentPath,
562
+ message: "React Grab is already installed in this file",
563
+ noChanges: true
564
+ };
565
+ }
566
+ if (!newContent.includes('import Script from "next/script"')) {
567
+ const importMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
568
+ if (importMatch) {
569
+ newContent = newContent.replace(importMatch[0], `${importMatch[0]}
570
+ ${SCRIPT_IMPORT}`);
571
+ }
572
+ }
573
+ const scriptBlock = NEXT_PAGES_ROUTER_SCRIPT_WITH_AGENT(agent);
574
+ const headMatch = newContent.match(/<Head[^>]*>/);
575
+ if (headMatch) {
576
+ newContent = newContent.replace(headMatch[0], `${headMatch[0]}
577
+ ${scriptBlock}`);
578
+ }
579
+ return {
580
+ success: true,
581
+ filePath: documentPath,
582
+ message: "Add React Grab" + (agent !== "none" ? ` with ${agent} agent` : ""),
583
+ originalContent,
584
+ newContent
585
+ };
586
+ };
587
+ var transformVite = (projectRoot, agent, reactGrabAlreadyConfigured) => {
588
+ const indexPath = findIndexHtml(projectRoot);
589
+ if (!indexPath) {
590
+ return {
591
+ success: false,
592
+ filePath: "",
593
+ message: "Could not find index.html"
594
+ };
595
+ }
596
+ const originalContent = readFileSync(indexPath, "utf-8");
597
+ let newContent = originalContent;
598
+ const hasReactGrabInFile = originalContent.includes("react-grab");
599
+ if (hasReactGrabInFile && reactGrabAlreadyConfigured) {
600
+ return addAgentToExistingVite(originalContent, agent, indexPath);
601
+ }
602
+ if (hasReactGrabInFile) {
603
+ return {
604
+ success: true,
605
+ filePath: indexPath,
606
+ message: "React Grab is already installed in this file",
607
+ noChanges: true
608
+ };
609
+ }
610
+ const scriptBlock = VITE_SCRIPT_WITH_AGENT(agent);
611
+ const headMatch = newContent.match(/<head[^>]*>/i);
612
+ if (headMatch) {
613
+ newContent = newContent.replace(headMatch[0], `${headMatch[0]}
614
+ ${scriptBlock}`);
615
+ }
616
+ return {
617
+ success: true,
618
+ filePath: indexPath,
619
+ message: "Add React Grab" + (agent !== "none" ? ` with ${agent} agent` : ""),
620
+ originalContent,
621
+ newContent
622
+ };
623
+ };
624
+ var transformWebpack = (projectRoot, agent, reactGrabAlreadyConfigured) => {
625
+ const entryPath = findEntryFile(projectRoot);
626
+ if (!entryPath) {
627
+ return {
628
+ success: false,
629
+ filePath: "",
630
+ message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)"
631
+ };
632
+ }
633
+ const originalContent = readFileSync(entryPath, "utf-8");
634
+ const hasReactGrabInFile = originalContent.includes("react-grab");
635
+ if (hasReactGrabInFile && reactGrabAlreadyConfigured) {
636
+ return addAgentToExistingWebpack(originalContent, agent, entryPath);
637
+ }
638
+ if (hasReactGrabInFile) {
639
+ return {
640
+ success: true,
641
+ filePath: entryPath,
642
+ message: "React Grab is already installed in this file",
643
+ noChanges: true
644
+ };
645
+ }
646
+ const importBlock = WEBPACK_IMPORT_WITH_AGENT(agent);
647
+ const newContent = `${importBlock}
648
+
649
+ ${originalContent}`;
650
+ return {
651
+ success: true,
652
+ filePath: entryPath,
653
+ message: "Add React Grab" + (agent !== "none" ? ` with ${agent} agent` : ""),
654
+ originalContent,
655
+ newContent
656
+ };
657
+ };
658
+ var previewTransform = (projectRoot, framework, nextRouterType, agent, reactGrabAlreadyConfigured = false) => {
659
+ switch (framework) {
660
+ case "next":
661
+ if (nextRouterType === "app") {
662
+ return transformNextAppRouter(projectRoot, agent, reactGrabAlreadyConfigured);
663
+ }
664
+ return transformNextPagesRouter(projectRoot, agent, reactGrabAlreadyConfigured);
665
+ case "vite":
666
+ return transformVite(projectRoot, agent, reactGrabAlreadyConfigured);
667
+ case "webpack":
668
+ return transformWebpack(projectRoot, agent, reactGrabAlreadyConfigured);
669
+ default:
670
+ return {
671
+ success: false,
672
+ filePath: "",
673
+ message: `Unknown framework: ${framework}. Please add React Grab manually.`
674
+ };
675
+ }
676
+ };
677
+ var applyTransform = (result) => {
678
+ if (result.success && result.newContent && result.filePath) {
679
+ writeFileSync(result.filePath, result.newContent);
680
+ }
681
+ };
682
+
683
+ // src/cli.ts
684
+ var VERSION = "0.0.1";
685
+ var FRAMEWORK_NAMES = {
686
+ next: "Next.js",
687
+ vite: "Vite",
688
+ webpack: "Webpack",
689
+ unknown: "Unknown"
690
+ };
691
+ var PACKAGE_MANAGER_NAMES = {
692
+ npm: "npm",
693
+ yarn: "Yarn",
694
+ pnpm: "pnpm",
695
+ bun: "Bun"
696
+ };
697
+ var AGENT_NAMES = {
698
+ "claude-code": "Claude Code",
699
+ cursor: "Cursor",
700
+ opencode: "Opencode"
701
+ };
702
+ var DOCS_URL = "https://react-grab.com/docs";
703
+ var showDocsLink = () => {
704
+ console.log("\nFor manual installation instructions, visit:");
705
+ console.log(` ${DOCS_URL}
706
+ `);
707
+ };
708
+ var parseArgs = async () => {
709
+ const argv = await yargs(hideBin(process.argv)).usage("Usage: $0 [options]").option("framework", {
710
+ alias: "f",
711
+ type: "string",
712
+ choices: ["next", "vite", "webpack"],
713
+ description: "Framework to configure (next, vite, webpack)"
714
+ }).option("package-manager", {
715
+ alias: "p",
716
+ type: "string",
717
+ choices: ["npm", "yarn", "pnpm", "bun"],
718
+ description: "Package manager to use"
719
+ }).option("router", {
720
+ alias: "r",
721
+ type: "string",
722
+ choices: ["app", "pages"],
723
+ description: "Next.js router type (app or pages)"
724
+ }).option("agent", {
725
+ alias: "a",
726
+ type: "string",
727
+ choices: ["claude-code", "cursor", "opencode", "none"],
728
+ description: "Agent integration to add"
729
+ }).option("yes", {
730
+ alias: "y",
731
+ type: "boolean",
732
+ default: false,
733
+ description: "Skip all confirmation prompts"
734
+ }).option("skip-install", {
735
+ type: "boolean",
736
+ default: false,
737
+ description: "Skip package installation (only modify files)"
738
+ }).help().alias("help", "h").version(VERSION).alias("version", "v").example("$0", "Run interactive setup").example("$0 -y", "Auto-detect and install without prompts").example("$0 -f next -r app -a cursor", "Install for Next.js App Router with Cursor agent").example("$0 -p pnpm -a claude-code -y", "Use pnpm and add Claude Code agent").parse();
739
+ return {
740
+ framework: argv.framework,
741
+ packageManager: argv["package-manager"],
742
+ router: argv.router,
743
+ agent: argv.agent,
744
+ yes: argv.yes,
745
+ skipInstall: argv["skip-install"]
746
+ };
747
+ };
748
+ var main = async () => {
749
+ const args = await parseArgs();
750
+ const isNonInteractive = args.yes;
751
+ console.log(`
752
+ ${pc.magenta("\u269B")} ${pc.bold("React Grab")} ${pc.gray(VERSION)}`);
753
+ const projectInfo = await detectProject(process.cwd());
754
+ console.log(`- Framework: ${pc.cyan(FRAMEWORK_NAMES[projectInfo.framework])}`);
755
+ console.log(`- Package Manager: ${pc.cyan(PACKAGE_MANAGER_NAMES[projectInfo.packageManager])}`);
756
+ if (projectInfo.framework === "next") {
757
+ console.log(`- Router Type: ${pc.cyan(projectInfo.nextRouterType === "app" ? "App Router" : "Pages Router")}`);
758
+ }
759
+ console.log(`- Monorepo: ${pc.cyan(projectInfo.isMonorepo ? "Yes" : "No")}`);
760
+ console.log(`- React Grab: ${projectInfo.hasReactGrab ? pc.green("Installed") : pc.yellow("Not installed")}`);
761
+ if (projectInfo.installedAgents.length > 0) {
762
+ console.log(`- Agents: ${pc.cyan(projectInfo.installedAgents.map((agent) => AGENT_NAMES[agent] || agent).join(", "))}`);
763
+ }
764
+ console.log("");
765
+ let action = "install-all";
766
+ if (projectInfo.hasReactGrab && !isNonInteractive) {
767
+ action = await select({
768
+ message: "React Grab is already installed. What would you like to do?",
769
+ choices: [
770
+ { name: "Add an agent integration", value: "add-agent" },
771
+ { name: "Reconfigure project files", value: "reconfigure" },
772
+ { name: "Reinstall everything", value: "install-all" }
773
+ ]
774
+ });
775
+ } else if (projectInfo.hasReactGrab && args.agent && args.agent !== "none") {
776
+ action = "add-agent";
777
+ }
778
+ let finalFramework = args.framework || projectInfo.framework;
779
+ let finalPackageManager = args.packageManager || projectInfo.packageManager;
780
+ let finalNextRouterType = args.router || projectInfo.nextRouterType;
781
+ if (!isNonInteractive && !args.framework) {
782
+ const confirmSettings = await confirm({
783
+ message: "Are these settings correct?",
784
+ default: true
785
+ });
786
+ if (!confirmSettings) {
787
+ finalFramework = await select({
788
+ message: "Select your framework:",
789
+ choices: [
790
+ { name: "Next.js", value: "next" },
791
+ { name: "Vite", value: "vite" },
792
+ { name: "Webpack", value: "webpack" }
793
+ ],
794
+ default: projectInfo.framework
795
+ });
796
+ finalPackageManager = await select({
797
+ message: "Select your package manager:",
798
+ choices: [
799
+ { name: "npm", value: "npm" },
800
+ { name: "Yarn", value: "yarn" },
801
+ { name: "pnpm", value: "pnpm" },
802
+ { name: "Bun", value: "bun" }
803
+ ],
804
+ default: projectInfo.packageManager
805
+ });
806
+ if (finalFramework === "next") {
807
+ finalNextRouterType = await select({
808
+ message: "Select your Next.js router type:",
809
+ choices: [
810
+ { name: "App Router", value: "app" },
811
+ { name: "Pages Router", value: "pages" }
812
+ ],
813
+ default: projectInfo.nextRouterType === "app" ? "app" : "pages"
814
+ });
815
+ }
816
+ }
817
+ }
818
+ let agentIntegration = args.agent || "none";
819
+ const shouldAskForAgent = (action === "install-all" || action === "add-agent") && !args.agent;
820
+ if (shouldAskForAgent && !isNonInteractive) {
821
+ const availableAgents = [
822
+ { name: "Claude Code", value: "claude-code" },
823
+ { name: "Cursor", value: "cursor" },
824
+ { name: "Opencode", value: "opencode" }
825
+ ].filter((agent) => !projectInfo.installedAgents.includes(agent.value));
826
+ if (availableAgents.length === 0) {
827
+ console.log(`
828
+ ${pc.green("All agent integrations are already installed.")}
829
+ `);
830
+ } else if (action === "add-agent") {
831
+ agentIntegration = await select({
832
+ message: "Select an agent integration to add:",
833
+ choices: availableAgents
834
+ });
835
+ } else {
836
+ const wantAgentIntegration = await confirm({
837
+ message: "Do you want to add an agent integration (Claude Code, Cursor, or Opencode)?",
838
+ default: false
839
+ });
840
+ if (wantAgentIntegration) {
841
+ agentIntegration = await select({
842
+ message: "Select an agent integration:",
843
+ choices: availableAgents
844
+ });
845
+ }
846
+ }
847
+ }
848
+ const shouldTransform = action === "reconfigure" || action === "install-all" || action === "add-agent" && agentIntegration !== "none";
849
+ if (shouldTransform) {
850
+ console.log(`
851
+ ${pc.magenta("\u269B")} Previewing changes...
852
+ `);
853
+ const result = previewTransform(
854
+ projectInfo.projectRoot,
855
+ finalFramework,
856
+ finalNextRouterType,
857
+ agentIntegration,
858
+ projectInfo.hasReactGrab || action === "add-agent"
859
+ );
860
+ if (!result.success) {
861
+ console.error(`${pc.red("Error:")} ${result.message}`);
862
+ showDocsLink();
863
+ process.exit(1);
864
+ }
865
+ if (result.noChanges) {
866
+ console.log(`${pc.cyan("Info:")} ${result.message}`);
867
+ } else if (result.originalContent && result.newContent) {
868
+ printDiff(result.filePath, result.originalContent, result.newContent);
869
+ if (!isNonInteractive) {
870
+ const confirmChanges = await confirm({
871
+ message: "Apply these changes?",
872
+ default: true
873
+ });
874
+ if (!confirmChanges) {
875
+ console.log(`
876
+ ${pc.yellow("Changes cancelled.")}
877
+ `);
878
+ process.exit(0);
879
+ }
880
+ }
881
+ const shouldInstallReactGrab = action === "install-all" && !projectInfo.hasReactGrab;
882
+ const shouldInstallAgent = agentIntegration !== "none" && !projectInfo.installedAgents.includes(agentIntegration);
883
+ if (!args.skipInstall && (shouldInstallReactGrab || shouldInstallAgent)) {
884
+ const packages = getPackagesToInstall(agentIntegration, shouldInstallReactGrab);
885
+ if (packages.length > 0) {
886
+ console.log(`
887
+ ${pc.magenta("\u269B")} Installing: ${pc.cyan(packages.join(", "))}
888
+ `);
889
+ try {
890
+ installPackages(packages, finalPackageManager, projectInfo.projectRoot);
891
+ console.log(`
892
+ ${pc.green("Packages installed successfully!")}
893
+ `);
894
+ } catch (error) {
895
+ console.error(`
896
+ ${pc.red("Failed to install packages:")}`, error);
897
+ showDocsLink();
898
+ process.exit(1);
899
+ }
900
+ }
901
+ }
902
+ applyTransform(result);
903
+ console.log(`
904
+ ${pc.green("Applied:")} ${result.filePath}`);
905
+ }
906
+ }
907
+ console.log(`
908
+ ${pc.green("Done!")}`);
909
+ console.log(`
910
+ Next steps:`);
911
+ console.log(` - Start your development server`);
912
+ console.log(` - Select an element to copy its context`);
913
+ console.log(` - Learn more at ${pc.cyan("https://react-grab.com")}
914
+ `);
915
+ if (agentIntegration !== "none") {
916
+ console.log(`${pc.magenta("\u269B")} Agent: ${pc.cyan(AGENT_NAMES[agentIntegration])}`);
917
+ console.log(` Make sure to start the agent server before using it.
918
+ `);
919
+ }
920
+ };
921
+ main().catch((error) => {
922
+ console.error(`${pc.red("Error:")}`, error);
923
+ console.log("\nFor manual installation instructions, visit:");
924
+ console.log(` ${DOCS_URL}
925
+ `);
926
+ process.exit(1);
927
+ });