@react-grab/cli 0.1.46 → 0.1.47-dev.d5e8fc8

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 CHANGED
@@ -1,15 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import { a as previewCdnTransform, c as agentLabel, d as detectAvailableAgents, h as detectProject, i as hasFrameworkEntryPoint, l as installSkill, n as installPackages, o as previewOptionsTransform, r as applyTransform, s as previewTransform, t as getPackagesToInstall, u as removeSkill, y as findReactProjects } from "./install-Car5vKPk.js";
2
3
  import { Command } from "commander";
3
- import path, { basename, delimiter, dirname, join, relative, resolve } from "node:path";
4
+ import path, { join, relative, resolve } from "node:path";
4
5
  import pc from "picocolors";
5
- import fs, { accessSync, constants, existsSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
6
- import { detect } from "package-manager-detector/detect";
7
- import ignore from "ignore";
6
+ import fs, { existsSync, readFileSync } from "node:fs";
8
7
  import { fileURLToPath } from "node:url";
9
- import { add, detectInstalledSkillAgents, getCanonicalSkillsDir, getSkillAgentConfig, getSkillAgentDir, getSkillAgentTypes, isUniversalSkillAgent } from "agent-install/skill";
10
8
  import basePrompts from "prompts";
11
9
  import ora from "ora";
12
- import { x } from "tinyexec";
13
10
  import { spawn, spawnSync } from "node:child_process";
14
11
  import { createHash } from "node:crypto";
15
12
  //#region src/utils/is-non-interactive.ts
@@ -25,353 +22,6 @@ const AGENT_ENVIRONMENT_VARIABLES = [
25
22
  const isEnvironmentVariableSet = (variable) => Boolean(process.env[variable]);
26
23
  const detectNonInteractive = (yesFlag) => yesFlag || AGENT_ENVIRONMENT_VARIABLES.some(isEnvironmentVariableSet) || !process.stdin.isTTY;
27
24
  //#endregion
28
- //#region src/utils/react-grab-code.ts
29
- const REACT_GRAB_SPECIFIER_PATTERN = String.raw`react-grab(?:\/[^"']+)?`;
30
- const stripComments = (content) => content.replace(/<!--[\s\S]*?-->/g, "").replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "$1");
31
- const stripTypeOnlyReactGrabImports = (content) => {
32
- return content.replace(new RegExp(String.raw`import\s+type\s+[^;]+from\s+["']${REACT_GRAB_SPECIFIER_PATTERN}["'];?`, "g"), "").replace(new RegExp(String.raw`import\s*\{\s*type\s+[^,}]+(?:\s*,\s*type\s+[^,}]+)*\s*,?\s*\}\s*from\s+["']${REACT_GRAB_SPECIFIER_PATTERN}["'];?`, "g"), "");
33
- };
34
- const hasReactGrabSetupCode = (content) => {
35
- const setupCandidateContent = stripTypeOnlyReactGrabImports(stripComments(content));
36
- return [
37
- new RegExp(String.raw`import\s*\(\s*["']${REACT_GRAB_SPECIFIER_PATTERN}["']\s*\)`),
38
- new RegExp(String.raw`import\s+(?!type\b)(?:[^"';]+from\s+)?["']${REACT_GRAB_SPECIFIER_PATTERN}["']`),
39
- new RegExp(String.raw`require\s*\(\s*["']${REACT_GRAB_SPECIFIER_PATTERN}["']\s*\)`),
40
- /<Script[\s\S]*?src\s*=\s*(?:["'][^"']*react-grab[^"']*["']|\{(?:["'][^"']*react-grab[^"']*["']|`[^`]*react-grab[^`]*`)\})/i,
41
- /<script[\s\S]*?src\s*=\s*["'][^"']*react-grab[^"']*["']/i
42
- ].some((pattern) => pattern.test(setupCandidateContent));
43
- };
44
- //#endregion
45
- //#region src/utils/react-grab-setup-files.ts
46
- const COMPONENT_EXTENSIONS = [
47
- "tsx",
48
- "jsx",
49
- "ts",
50
- "js"
51
- ];
52
- const INSTRUMENTATION_EXTENSIONS = [
53
- "ts",
54
- "tsx",
55
- "js",
56
- "jsx",
57
- "mts",
58
- "cts",
59
- "mjs",
60
- "cjs"
61
- ];
62
- const ROUTE_EXTENSIONS = ["tsx", "jsx"];
63
- const createFileCandidates = (projectRoot, directories, baseName, extensions) => {
64
- const fileCandidates = [];
65
- for (const directory of directories) for (const extension of extensions) fileCandidates.push(join(projectRoot, directory, `${baseName}.${extension}`));
66
- return fileCandidates;
67
- };
68
- const findExistingFile = (fileCandidates) => {
69
- for (const filePath of fileCandidates) if (existsSync(filePath)) return filePath;
70
- return null;
71
- };
72
- const getLayoutFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["app", "src/app"], "layout", COMPONENT_EXTENSIONS);
73
- const getDocumentFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["pages", "src/pages"], "_document", COMPONENT_EXTENSIONS);
74
- const getInstrumentationFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["", "src"], "instrumentation-client", INSTRUMENTATION_EXTENSIONS);
75
- const getIndexHtmlCandidates = (projectRoot) => [join(projectRoot, "index.html"), join(projectRoot, "public", "index.html")];
76
- const getEntryFileCandidates = (projectRoot) => [...createFileCandidates(projectRoot, ["src"], "index", COMPONENT_EXTENSIONS), ...createFileCandidates(projectRoot, ["src"], "main", COMPONENT_EXTENSIONS)];
77
- const getTanStackRootFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["src/routes", "app/routes"], "__root", ROUTE_EXTENSIONS);
78
- const getReactGrabSetupFileCandidates = (projectRoot) => [
79
- ...getLayoutFileCandidates(projectRoot),
80
- ...getDocumentFileCandidates(projectRoot),
81
- ...getInstrumentationFileCandidates(projectRoot),
82
- ...getIndexHtmlCandidates(projectRoot),
83
- ...getEntryFileCandidates(projectRoot),
84
- ...getTanStackRootFileCandidates(projectRoot)
85
- ];
86
- const findLayoutFile = (projectRoot) => findExistingFile(getLayoutFileCandidates(projectRoot));
87
- const findDocumentFile = (projectRoot) => findExistingFile(getDocumentFileCandidates(projectRoot));
88
- const findIndexHtml = (projectRoot) => findExistingFile(getIndexHtmlCandidates(projectRoot));
89
- const findEntryFile = (projectRoot) => findExistingFile(getEntryFileCandidates(projectRoot));
90
- const findTanStackRootFile = (projectRoot) => findExistingFile(getTanStackRootFileCandidates(projectRoot));
91
- const isInstrumentationFile = (filePath) => /(?:^|[/\\])instrumentation-client\.[cm]?[jt]sx?$/.test(filePath);
92
- //#endregion
93
- //#region src/utils/detect.ts
94
- const VALID_PACKAGE_MANAGERS = new Set([
95
- "npm",
96
- "yarn",
97
- "pnpm",
98
- "bun"
99
- ]);
100
- const detectPackageManager = async (projectRoot) => {
101
- const result = await detect({ cwd: projectRoot });
102
- if (result?.agent) {
103
- const managerName = result.agent.split("@")[0];
104
- if (VALID_PACKAGE_MANAGERS.has(managerName)) return managerName;
105
- }
106
- return "npm";
107
- };
108
- const CONFIG_EXTENSIONS = [
109
- "ts",
110
- "mts",
111
- "cts",
112
- "js",
113
- "mjs",
114
- "cjs"
115
- ];
116
- const hasConfigFile = (projectRoot, configBaseName) => CONFIG_EXTENSIONS.some((extension) => existsSync(join(projectRoot, `${configBaseName}.${extension}`)));
117
- const readMergedDependencies = (projectRoot) => {
118
- const packageJsonPath = join(projectRoot, "package.json");
119
- if (!existsSync(packageJsonPath)) return null;
120
- try {
121
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
122
- return {
123
- ...packageJson.dependencies,
124
- ...packageJson.devDependencies
125
- };
126
- } catch {
127
- return null;
128
- }
129
- };
130
- const detectFrameworkFromDependencies = (dependencies) => {
131
- if (!dependencies) return "unknown";
132
- if (dependencies["next"]) return "next";
133
- if (dependencies["@tanstack/react-start"]) return "tanstack";
134
- if (dependencies["vite"]) return "vite";
135
- if (dependencies["webpack"]) return "webpack";
136
- return "unknown";
137
- };
138
- const detectFrameworkFromConfigFiles = (projectRoot) => {
139
- if (hasConfigFile(projectRoot, "next.config")) return "next";
140
- if (hasConfigFile(projectRoot, "app.config")) return "tanstack";
141
- if (hasConfigFile(projectRoot, "vite.config")) return "vite";
142
- if (hasConfigFile(projectRoot, "webpack.config")) return "webpack";
143
- return "unknown";
144
- };
145
- const findEnclosingMonorepoRoot = (projectRoot) => {
146
- let currentDirectory = dirname(projectRoot);
147
- while (currentDirectory !== dirname(currentDirectory)) {
148
- if (detectMonorepo(currentDirectory)) return currentDirectory;
149
- currentDirectory = dirname(currentDirectory);
150
- }
151
- return null;
152
- };
153
- const detectFramework = (projectRoot) => {
154
- const localFramework = detectFrameworkFromDependencies(readMergedDependencies(projectRoot));
155
- if (localFramework !== "unknown") return localFramework;
156
- return detectFrameworkFromConfigFiles(projectRoot);
157
- };
158
- const detectFrameworkFromMonorepoRoot = (projectRoot) => {
159
- const monorepoRoot = findEnclosingMonorepoRoot(projectRoot);
160
- if (!monorepoRoot) return "unknown";
161
- return detectFrameworkFromDependencies(readMergedDependencies(monorepoRoot));
162
- };
163
- const detectNextRouterType = (projectRoot) => {
164
- const hasAppDir = existsSync(join(projectRoot, "app"));
165
- const hasSrcAppDir = existsSync(join(projectRoot, "src", "app"));
166
- const hasPagesDir = existsSync(join(projectRoot, "pages"));
167
- const hasSrcPagesDir = existsSync(join(projectRoot, "src", "pages"));
168
- if (hasAppDir || hasSrcAppDir) return "app";
169
- if (hasPagesDir || hasSrcPagesDir) return "pages";
170
- return "unknown";
171
- };
172
- const detectMonorepo = (projectRoot) => {
173
- if (existsSync(join(projectRoot, "pnpm-workspace.yaml"))) return true;
174
- if (existsSync(join(projectRoot, "lerna.json"))) return true;
175
- const packageJsonPath = join(projectRoot, "package.json");
176
- if (existsSync(packageJsonPath)) try {
177
- if (JSON.parse(readFileSync(packageJsonPath, "utf-8")).workspaces) return true;
178
- } catch {
179
- return false;
180
- }
181
- return false;
182
- };
183
- const getWorkspacePatterns = (projectRoot) => {
184
- const patterns = [];
185
- const pnpmWorkspacePath = join(projectRoot, "pnpm-workspace.yaml");
186
- if (existsSync(pnpmWorkspacePath)) {
187
- const lines = readFileSync(pnpmWorkspacePath, "utf-8").split("\n");
188
- let inPackages = false;
189
- for (const line of lines) {
190
- if (line.match(/^packages:\s*$/)) {
191
- inPackages = true;
192
- continue;
193
- }
194
- if (inPackages) {
195
- if (line.match(/^[a-zA-Z]/) || line.trim() === "") {
196
- if (line.match(/^[a-zA-Z]/)) inPackages = false;
197
- continue;
198
- }
199
- const match = line.match(/^\s*-\s*['"]?([^'"#\n]+?)['"]?\s*$/);
200
- if (match) patterns.push(match[1].trim());
201
- }
202
- }
203
- }
204
- const lernaJsonPath = join(projectRoot, "lerna.json");
205
- if (existsSync(lernaJsonPath)) try {
206
- const lernaJson = JSON.parse(readFileSync(lernaJsonPath, "utf-8"));
207
- if (Array.isArray(lernaJson.packages)) patterns.push(...lernaJson.packages);
208
- } catch {}
209
- const packageJsonPath = join(projectRoot, "package.json");
210
- if (existsSync(packageJsonPath)) try {
211
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
212
- if (Array.isArray(packageJson.workspaces)) patterns.push(...packageJson.workspaces);
213
- else if (packageJson.workspaces?.packages) patterns.push(...packageJson.workspaces.packages);
214
- } catch {}
215
- return [...new Set(patterns)];
216
- };
217
- const expandWorkspacePattern = (projectRoot, pattern) => {
218
- const isGlob = pattern.endsWith("/*");
219
- const basePath = join(projectRoot, pattern.replace(/\/\*$/, ""));
220
- if (!existsSync(basePath)) return [];
221
- if (!isGlob) return existsSync(join(basePath, "package.json")) ? [basePath] : [];
222
- const results = [];
223
- try {
224
- const entries = readdirSync(basePath, { withFileTypes: true });
225
- for (const entry of entries) {
226
- if (!entry.isDirectory()) continue;
227
- if (existsSync(join(basePath, entry.name, "package.json"))) results.push(join(basePath, entry.name));
228
- }
229
- } catch {
230
- return results;
231
- }
232
- return results;
233
- };
234
- const hasReactDependency = (projectPath) => {
235
- const dependencies = readMergedDependencies(projectPath);
236
- if (!dependencies) return false;
237
- return Boolean(dependencies["react"] || dependencies["react-dom"]);
238
- };
239
- const buildReactProject = (projectPath) => {
240
- const framework = detectFramework(projectPath);
241
- if (!hasReactDependency(projectPath) && framework === "unknown") return null;
242
- let name = basename(projectPath);
243
- const packageJsonPath = join(projectPath, "package.json");
244
- try {
245
- name = JSON.parse(readFileSync(packageJsonPath, "utf-8")).name || name;
246
- } catch {}
247
- return {
248
- name,
249
- path: projectPath,
250
- framework
251
- };
252
- };
253
- const findWorkspaceProjects = (projectRoot) => {
254
- const patterns = getWorkspacePatterns(projectRoot);
255
- const projects = [];
256
- for (const pattern of patterns) for (const projectPath of expandWorkspacePattern(projectRoot, pattern)) {
257
- const project = buildReactProject(projectPath);
258
- if (project) projects.push(project);
259
- }
260
- return projects;
261
- };
262
- const ALWAYS_IGNORED_DIRECTORIES = [
263
- "node_modules",
264
- ".git",
265
- ".next",
266
- ".cache",
267
- ".turbo",
268
- "dist",
269
- "build",
270
- "coverage",
271
- "test-results"
272
- ];
273
- const loadGitignore = (projectRoot) => {
274
- const ignorer = ignore().add(ALWAYS_IGNORED_DIRECTORIES);
275
- const gitignorePath = join(projectRoot, ".gitignore");
276
- if (existsSync(gitignorePath)) try {
277
- ignorer.add(readFileSync(gitignorePath, "utf-8"));
278
- } catch {}
279
- return ignorer;
280
- };
281
- const scanDirectoryForProjects = (rootDirectory, ignorer, maxDepth, currentDepth = 0) => {
282
- if (currentDepth >= maxDepth) return [];
283
- if (!existsSync(rootDirectory)) return [];
284
- const projects = [];
285
- try {
286
- const entries = readdirSync(rootDirectory, { withFileTypes: true });
287
- for (const entry of entries) {
288
- if (!entry.isDirectory()) continue;
289
- if (ignorer.ignores(entry.name)) continue;
290
- const entryPath = join(rootDirectory, entry.name);
291
- if (existsSync(join(entryPath, "package.json"))) {
292
- const project = buildReactProject(entryPath);
293
- if (project) {
294
- projects.push(project);
295
- continue;
296
- }
297
- }
298
- projects.push(...scanDirectoryForProjects(entryPath, ignorer, maxDepth, currentDepth + 1));
299
- }
300
- } catch {
301
- return projects;
302
- }
303
- return projects;
304
- };
305
- const MAX_SCAN_DEPTH = 2;
306
- const normalizePathForComparison = (filePath) => filePath.replace(/\\/g, "/");
307
- const findReactProjects = (projectRoot) => {
308
- const monorepoRoot = detectMonorepo(projectRoot) ? projectRoot : findEnclosingMonorepoRoot(projectRoot);
309
- if (monorepoRoot) {
310
- const workspaceProjects = findWorkspaceProjects(monorepoRoot);
311
- const localProject = projectRoot === monorepoRoot ? null : buildReactProject(projectRoot);
312
- const projects = localProject ? [localProject, ...workspaceProjects.filter((project) => normalizePathForComparison(project.path) !== normalizePathForComparison(localProject.path))] : workspaceProjects;
313
- if (projects.length > 0) return projects;
314
- }
315
- const scannedProjects = scanDirectoryForProjects(projectRoot, loadGitignore(projectRoot), MAX_SCAN_DEPTH);
316
- if (scannedProjects.length > 0) return scannedProjects;
317
- let currentDirectory = dirname(projectRoot);
318
- while (currentDirectory !== dirname(currentDirectory)) {
319
- const parentProject = buildReactProject(currentDirectory);
320
- if (parentProject) return [parentProject];
321
- currentDirectory = dirname(currentDirectory);
322
- }
323
- return [];
324
- };
325
- const hasReactGrabSetupInFile = (filePath) => {
326
- if (!existsSync(filePath)) return false;
327
- try {
328
- return hasReactGrabSetupCode(readFileSync(filePath, "utf-8"));
329
- } catch {
330
- return false;
331
- }
332
- };
333
- const detectReactGrabDependency = (projectRoot) => {
334
- const dependencies = readMergedDependencies(projectRoot);
335
- return Boolean(dependencies?.["react-grab"]);
336
- };
337
- const detectReactGrabConfigured = (projectRoot) => {
338
- return getReactGrabSetupFileCandidates(projectRoot).some(hasReactGrabSetupInFile);
339
- };
340
- const detectUnsupportedFramework = (projectRoot) => {
341
- const dependencies = readMergedDependencies(projectRoot);
342
- if (!dependencies) return null;
343
- if (dependencies["@remix-run/react"] || dependencies["remix"]) return "remix";
344
- if (dependencies["astro"]) return "astro";
345
- if (dependencies["@sveltejs/kit"]) return "sveltekit";
346
- if (dependencies["gatsby"]) return "gatsby";
347
- return null;
348
- };
349
- const detectReactGrabVersion = (projectRoot) => {
350
- const installedPackageJsonPath = join(projectRoot, "node_modules", "react-grab", "package.json");
351
- if (existsSync(installedPackageJsonPath)) try {
352
- return JSON.parse(readFileSync(installedPackageJsonPath, "utf-8")).version ?? null;
353
- } catch {}
354
- return null;
355
- };
356
- const detectProject = async (projectRoot = process.cwd()) => {
357
- const localFramework = detectFramework(projectRoot);
358
- const framework = localFramework === "unknown" ? detectFrameworkFromMonorepoRoot(projectRoot) : localFramework;
359
- const packageManager = await detectPackageManager(projectRoot);
360
- const isMonorepo = detectMonorepo(projectRoot) || findEnclosingMonorepoRoot(projectRoot) !== null;
361
- const isReactGrabConfigured = detectReactGrabConfigured(projectRoot);
362
- return {
363
- packageManager,
364
- framework,
365
- nextRouterType: framework === "next" ? detectNextRouterType(projectRoot) : "unknown",
366
- isMonorepo,
367
- projectRoot,
368
- hasReactGrab: detectReactGrabDependency(projectRoot) || isReactGrabConfigured,
369
- isReactGrabConfigured,
370
- reactGrabVersion: detectReactGrabVersion(projectRoot),
371
- unsupportedFramework: detectUnsupportedFramework(projectRoot)
372
- };
373
- };
374
- //#endregion
375
25
  //#region src/utils/highlighter.ts
376
26
  const highlighter = {
377
27
  error: pc.red,
@@ -411,39 +61,6 @@ const handleError = (error) => {
411
61
  process.exit(1);
412
62
  };
413
63
  //#endregion
414
- //#region src/utils/detect-agents.ts
415
- const PATH_BINARIES = {
416
- "claude-code": ["claude"],
417
- codex: ["codex"],
418
- cursor: ["cursor", "cursor-agent"],
419
- droid: ["droid"],
420
- "gemini-cli": ["gemini"],
421
- "github-copilot": ["copilot"],
422
- opencode: ["opencode"],
423
- pi: ["pi", "omegon"]
424
- };
425
- const isCommandAvailable = (command) => {
426
- const pathDirectories = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
427
- for (const directory of pathDirectories) {
428
- const binaryPath = join(directory, command);
429
- try {
430
- if (statSync(binaryPath).isFile()) {
431
- accessSync(binaryPath, constants.X_OK);
432
- return true;
433
- }
434
- } catch {}
435
- }
436
- return false;
437
- };
438
- const detectAvailableAgents = async () => {
439
- const installedAgents = new Set(await detectInstalledSkillAgents());
440
- return getSkillAgentTypes().filter((agent) => {
441
- if (agent === "universal") return false;
442
- if (installedAgents.has(agent)) return true;
443
- return PATH_BINARIES[agent]?.some(isCommandAvailable) ?? false;
444
- });
445
- };
446
- //#endregion
447
64
  //#region src/utils/unref-stdin.ts
448
65
  const unrefStdin = () => {
449
66
  if (process.stdin.isTTY) return;
@@ -464,14 +81,7 @@ const prompts = (questions) => {
464
81
  //#region src/utils/spinner.ts
465
82
  const spinner = (text) => ora({ text });
466
83
  //#endregion
467
- //#region src/utils/install-skill.ts
468
- const SKILL_NAME = "react-grab";
469
- const SKILL_SOURCE = fileURLToPath(new URL("../skills/react-grab", import.meta.url));
470
- const agentLabel = (agent) => getSkillAgentConfig(agent).displayName;
471
- const installedSkillDir = (agent, global, cwd) => join(isUniversalSkillAgent(agent) ? getCanonicalSkillsDir(global, cwd) : getSkillAgentDir(agent, {
472
- global,
473
- cwd
474
- }), SKILL_NAME);
84
+ //#region src/utils/prompt-skill-install.ts
475
85
  const promptSkillInstall = async ({ yes = false, global = false, cwd = process.cwd() } = {}) => {
476
86
  const detectedAgents = await detectAvailableAgents();
477
87
  if (detectedAgents.length === 0) {
@@ -503,12 +113,10 @@ const promptSkillInstall = async ({ yes = false, global = false, cwd = process.c
503
113
  if (selectedAgents.length === 0) return false;
504
114
  }
505
115
  const installSpinner = spinner("Installing React Grab skill.").start();
506
- const { installed, failed } = await add({
507
- source: SKILL_SOURCE,
116
+ const { installed, failed } = await installSkill({
508
117
  agents: selectedAgents,
509
118
  global,
510
- cwd,
511
- mode: "copy"
119
+ cwd
512
120
  });
513
121
  if (installed.length === 0) {
514
122
  installSpinner.fail("Failed to install React Grab skill.");
@@ -518,27 +126,10 @@ const promptSkillInstall = async ({ yes = false, global = false, cwd = process.c
518
126
  for (const record of failed) logger.log(` ${highlighter.error("✗")} ${agentLabel(record.agent)} ${record.error}`);
519
127
  return true;
520
128
  };
521
- const removeSkill = async ({ cwd = process.cwd(), global = false } = {}) => {
522
- const agents = await detectAvailableAgents();
523
- const removedAgents = [];
524
- const dirsToRemove = /* @__PURE__ */ new Set();
525
- for (const agent of agents) {
526
- const skillDir = installedSkillDir(agent, global, cwd);
527
- if (!existsSync(skillDir)) continue;
528
- removedAgents.push(agent);
529
- dirsToRemove.add(skillDir);
530
- }
531
- for (const skillDir of dirsToRemove) rmSync(skillDir, {
532
- recursive: true,
533
- force: true
534
- });
535
- for (const agent of removedAgents) logger.log(` ${highlighter.success("✓")} ${agentLabel(agent)}`);
536
- return removedAgents.length;
537
- };
538
129
  //#endregion
539
130
  //#region src/commands/add.ts
540
- const VERSION$5 = "0.1.46";
541
- const add$1 = new Command().name("add").alias("install").description("install the React Grab skill for your agent").option("-y, --yes", "skip confirmation prompts", false).option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "install the skill globally instead of in the project", false).action(async (opts) => {
131
+ const VERSION$5 = "0.1.47-dev.d5e8fc8";
132
+ const add = new Command().name("add").alias("install").description("install the React Grab skill for your agent").option("-y, --yes", "skip confirmation prompts", false).option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "install the skill globally instead of in the project", false).action(async (opts) => {
542
133
  console.log(`${pc.magenta("✿")} ${pc.bold("React Grab")} ${pc.gray(VERSION$5)}`);
543
134
  console.log();
544
135
  try {
@@ -670,412 +261,6 @@ const printDiff = (filePath, originalContent, newContent) => {
670
261
  console.log("─".repeat(60));
671
262
  };
672
263
  //#endregion
673
- //#region src/utils/templates.ts
674
- const NEXT_APP_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
675
- <Script
676
- src="//unpkg.com/react-grab/dist/index.global.js"
677
- crossOrigin="anonymous"
678
- strategy="beforeInteractive"
679
- />
680
- )}`;
681
- const VITE_IMPORT = `if (import.meta.env.DEV) {
682
- import("react-grab");
683
- }`;
684
- const WEBPACK_IMPORT = `if (process.env.NODE_ENV === "development") {
685
- import("react-grab");
686
- }`;
687
- const TANSTACK_EFFECT = `useEffect(() => {
688
- if (import.meta.env.DEV) {
689
- void import("react-grab");
690
- }
691
- }, []);`;
692
- const SCRIPT_IMPORT = "import Script from \"next/script\";";
693
- //#endregion
694
- //#region src/utils/transform.ts
695
- const hasReactGrabInInstrumentation = (projectRoot) => {
696
- return findFileWithReactGrabSetup(getInstrumentationFileCandidates(projectRoot)) !== null;
697
- };
698
- const findFileWithReactGrabSetup = (fileCandidates) => {
699
- for (const filePath of fileCandidates) {
700
- if (!existsSync(filePath)) continue;
701
- if (hasReactGrabSetupCode(readFileSync(filePath, "utf-8"))) return filePath;
702
- }
703
- return null;
704
- };
705
- const alreadyConfiguredResult = (filePath) => ({
706
- success: true,
707
- filePath,
708
- message: "React Grab is already configured",
709
- noChanges: true
710
- });
711
- const transformNextAppRouter = (projectRoot, reactGrabAlreadyConfigured) => {
712
- const layoutPath = findLayoutFile(projectRoot);
713
- if (!layoutPath) return {
714
- success: false,
715
- filePath: "",
716
- message: "Could not find app/layout.tsx, app/layout.jsx, app/layout.ts, or app/layout.js"
717
- };
718
- const originalContent = readFileSync(layoutPath, "utf-8");
719
- let newContent = originalContent;
720
- const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
721
- const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
722
- if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(layoutPath);
723
- if (hasReactGrabInFile || hasReactGrabInInstrumentationFile) return {
724
- success: true,
725
- filePath: layoutPath,
726
- message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
727
- noChanges: true
728
- };
729
- if (!newContent.includes("import Script from \"next/script\"")) {
730
- const importMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
731
- if (importMatch) newContent = newContent.replace(importMatch[0], `${importMatch[0]}\n${SCRIPT_IMPORT}`);
732
- else newContent = `${SCRIPT_IMPORT}\n\n${newContent}`;
733
- }
734
- const headMatch = newContent.match(/<head[^>]*>/);
735
- if (headMatch) newContent = newContent.replace(headMatch[0], `${headMatch[0]}\n ${NEXT_APP_ROUTER_SCRIPT}`);
736
- else {
737
- const htmlMatch = newContent.match(/<html[^>]*>/);
738
- if (htmlMatch) newContent = newContent.replace(htmlMatch[0], `${htmlMatch[0]}\n <head>\n ${NEXT_APP_ROUTER_SCRIPT}\n </head>`);
739
- }
740
- return {
741
- success: true,
742
- filePath: layoutPath,
743
- message: "Add React Grab",
744
- originalContent,
745
- newContent
746
- };
747
- };
748
- const transformNextPagesRouter = (projectRoot, reactGrabAlreadyConfigured) => {
749
- const documentPath = findDocumentFile(projectRoot);
750
- if (!documentPath) return {
751
- success: false,
752
- filePath: "",
753
- message: "Could not find pages/_document.tsx, pages/_document.jsx, pages/_document.ts, or pages/_document.js.\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 }"
754
- };
755
- const originalContent = readFileSync(documentPath, "utf-8");
756
- let newContent = originalContent;
757
- const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
758
- const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
759
- if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(documentPath);
760
- if (hasReactGrabInFile || hasReactGrabInInstrumentationFile) return {
761
- success: true,
762
- filePath: documentPath,
763
- message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
764
- noChanges: true
765
- };
766
- if (!newContent.includes("import Script from \"next/script\"")) {
767
- const importMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
768
- if (importMatch) newContent = newContent.replace(importMatch[0], `${importMatch[0]}\n${SCRIPT_IMPORT}`);
769
- }
770
- const headMatch = newContent.match(/<Head[^>]*>/);
771
- if (headMatch) newContent = newContent.replace(headMatch[0], `${headMatch[0]}\n ${NEXT_APP_ROUTER_SCRIPT}`);
772
- return {
773
- success: true,
774
- filePath: documentPath,
775
- message: "Add React Grab",
776
- originalContent,
777
- newContent
778
- };
779
- };
780
- const checkExistingInstallation = (filePath, reactGrabAlreadyConfigured) => {
781
- if (!hasReactGrabSetupCode(readFileSync(filePath, "utf-8"))) return null;
782
- return {
783
- success: true,
784
- filePath,
785
- message: reactGrabAlreadyConfigured ? "React Grab is already configured" : "React Grab is already installed in this file",
786
- noChanges: true
787
- };
788
- };
789
- const transformVite = (projectRoot, reactGrabAlreadyConfigured) => {
790
- const entryPath = findEntryFile(projectRoot);
791
- const indexPath = findIndexHtml(projectRoot);
792
- if (indexPath) {
793
- const existingResult = checkExistingInstallation(indexPath, reactGrabAlreadyConfigured);
794
- if (existingResult) return existingResult;
795
- }
796
- if (!entryPath) return {
797
- success: false,
798
- filePath: "",
799
- message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)"
800
- };
801
- const existingResult = checkExistingInstallation(entryPath, reactGrabAlreadyConfigured);
802
- if (existingResult) return existingResult;
803
- const originalContent = readFileSync(entryPath, "utf-8");
804
- return {
805
- success: true,
806
- filePath: entryPath,
807
- message: "Add React Grab",
808
- originalContent,
809
- newContent: `${VITE_IMPORT}\n\n${originalContent}`
810
- };
811
- };
812
- const transformWebpack = (projectRoot, reactGrabAlreadyConfigured) => {
813
- const entryPath = findEntryFile(projectRoot);
814
- if (!entryPath) return {
815
- success: false,
816
- filePath: "",
817
- message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)"
818
- };
819
- const existingResult = checkExistingInstallation(entryPath, reactGrabAlreadyConfigured);
820
- if (existingResult) return existingResult;
821
- const originalContent = readFileSync(entryPath, "utf-8");
822
- return {
823
- success: true,
824
- filePath: entryPath,
825
- message: "Add React Grab",
826
- originalContent,
827
- newContent: `${WEBPACK_IMPORT}\n\n${originalContent}`
828
- };
829
- };
830
- const transformTanStack = (projectRoot, reactGrabAlreadyConfigured) => {
831
- const rootPath = findTanStackRootFile(projectRoot);
832
- if (!rootPath) return {
833
- success: false,
834
- filePath: "",
835
- message: "Could not find src/routes/__root.tsx or app/routes/__root.tsx.\n\nTo set up React Grab with TanStack Start, add this to your root route component:\n\n import { useEffect } from \"react\";\n\n useEffect(() => {\n if (import.meta.env.DEV) {\n void import(\"react-grab\");\n }\n }, []);"
836
- };
837
- const originalContent = readFileSync(rootPath, "utf-8");
838
- let newContent = originalContent;
839
- const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
840
- if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(rootPath);
841
- if (hasReactGrabInFile) return {
842
- success: true,
843
- filePath: rootPath,
844
- message: "React Grab is already installed in this file",
845
- noChanges: true
846
- };
847
- if (!/import\s+\{[^}]*useEffect[^}]*\}\s+from\s+["']react["']/.test(newContent)) {
848
- const reactImportMatch = newContent.match(/import\s+\{([^}]*)\}\s+from\s+["']react["'];?/);
849
- if (reactImportMatch) {
850
- const existingImports = reactImportMatch[1];
851
- newContent = newContent.replace(reactImportMatch[0], `import { ${existingImports.trim()}, useEffect } from "react";`);
852
- } else {
853
- const firstImportMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
854
- if (firstImportMatch) newContent = newContent.replace(firstImportMatch[0], `import { useEffect } from "react";\n${firstImportMatch[0]}`);
855
- else newContent = `import { useEffect } from "react";\n\n${newContent}`;
856
- }
857
- }
858
- const componentMatch = newContent.match(/function\s+(\w+)\s*\([^)]*\)\s*\{/);
859
- if (componentMatch) {
860
- const insertPosition = componentMatch.index + componentMatch[0].length;
861
- newContent = newContent.slice(0, insertPosition) + `\n ${TANSTACK_EFFECT}\n` + newContent.slice(insertPosition);
862
- } else return {
863
- success: false,
864
- filePath: rootPath,
865
- message: "Could not find a component function in the root file"
866
- };
867
- return {
868
- success: true,
869
- filePath: rootPath,
870
- message: "Add React Grab",
871
- originalContent,
872
- newContent
873
- };
874
- };
875
- const hasFrameworkEntryPoint = (projectRoot, framework, nextRouterType) => {
876
- switch (framework) {
877
- case "next": return nextRouterType === "app" ? findLayoutFile(projectRoot) !== null : findDocumentFile(projectRoot) !== null;
878
- case "vite":
879
- case "webpack": return findEntryFile(projectRoot) !== null;
880
- case "tanstack": return findTanStackRootFile(projectRoot) !== null;
881
- default: return false;
882
- }
883
- };
884
- const previewTransform = (projectRoot, framework, nextRouterType, reactGrabAlreadyConfigured = false) => {
885
- switch (framework) {
886
- case "next":
887
- if (nextRouterType === "app") return transformNextAppRouter(projectRoot, reactGrabAlreadyConfigured);
888
- return transformNextPagesRouter(projectRoot, reactGrabAlreadyConfigured);
889
- case "vite": return transformVite(projectRoot, reactGrabAlreadyConfigured);
890
- case "tanstack": return transformTanStack(projectRoot, reactGrabAlreadyConfigured);
891
- case "webpack": return transformWebpack(projectRoot, reactGrabAlreadyConfigured);
892
- default: return {
893
- success: false,
894
- filePath: "",
895
- message: `Unknown framework: ${framework}. Please add React Grab manually.`
896
- };
897
- }
898
- };
899
- const canWriteToFile = (filePath) => {
900
- try {
901
- accessSync(filePath, constants.W_OK);
902
- return true;
903
- } catch {
904
- return false;
905
- }
906
- };
907
- const applyTransform = (result) => {
908
- if (result.success && result.newContent && result.filePath) {
909
- if (!canWriteToFile(result.filePath)) return {
910
- success: false,
911
- error: `Cannot write to ${result.filePath}. Check file permissions.`
912
- };
913
- try {
914
- writeFileSync(result.filePath, result.newContent);
915
- return { success: true };
916
- } catch (error) {
917
- return {
918
- success: false,
919
- error: `Failed to write to ${result.filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
920
- };
921
- }
922
- }
923
- return { success: true };
924
- };
925
- const formatOptionsForNextjs = (options) => {
926
- const parts = [];
927
- if (options.activationKey) parts.push(`activationKey: ${JSON.stringify(options.activationKey)}`);
928
- if (options.activationMode) parts.push(`activationMode: "${options.activationMode}"`);
929
- if (options.keyHoldDuration !== void 0) parts.push(`keyHoldDuration: ${options.keyHoldDuration}`);
930
- if (options.allowActivationInsideInput !== void 0) parts.push(`allowActivationInsideInput: ${options.allowActivationInsideInput}`);
931
- if (options.maxContextLines !== void 0) parts.push(`maxContextLines: ${options.maxContextLines}`);
932
- return `{ ${parts.join(", ")} }`;
933
- };
934
- const formatOptionsAsJson = (options) => {
935
- const cleanOptions = {};
936
- if (options.activationKey) cleanOptions.activationKey = options.activationKey;
937
- if (options.activationMode) cleanOptions.activationMode = options.activationMode;
938
- if (options.keyHoldDuration !== void 0) cleanOptions.keyHoldDuration = options.keyHoldDuration;
939
- if (options.allowActivationInsideInput !== void 0) cleanOptions.allowActivationInsideInput = options.allowActivationInsideInput;
940
- if (options.maxContextLines !== void 0) cleanOptions.maxContextLines = options.maxContextLines;
941
- return JSON.stringify(cleanOptions);
942
- };
943
- const findReactGrabFile = (projectRoot, framework, nextRouterType) => {
944
- switch (framework) {
945
- case "next": {
946
- const primaryFile = nextRouterType === "app" ? findLayoutFile(projectRoot) : findDocumentFile(projectRoot);
947
- const primarySetupFile = findFileWithReactGrabSetup(nextRouterType === "app" ? getLayoutFileCandidates(projectRoot) : getDocumentFileCandidates(projectRoot));
948
- if (primarySetupFile) return primarySetupFile;
949
- const instrumentationFile = findFileWithReactGrabSetup(getInstrumentationFileCandidates(projectRoot));
950
- if (instrumentationFile) return instrumentationFile;
951
- return primaryFile;
952
- }
953
- case "vite": {
954
- const entryFile = findEntryFile(projectRoot);
955
- const entrySetupFile = findFileWithReactGrabSetup(getEntryFileCandidates(projectRoot));
956
- if (entrySetupFile) return entrySetupFile;
957
- const indexHtml = findFileWithReactGrabSetup(getIndexHtmlCandidates(projectRoot));
958
- if (indexHtml) return indexHtml;
959
- return entryFile;
960
- }
961
- case "tanstack": return findFileWithReactGrabSetup(getTanStackRootFileCandidates(projectRoot)) ?? findTanStackRootFile(projectRoot);
962
- case "webpack": return findFileWithReactGrabSetup(getEntryFileCandidates(projectRoot)) ?? findEntryFile(projectRoot);
963
- default: return null;
964
- }
965
- };
966
- const addOptionsToNextScript = (originalContent, options, filePath) => {
967
- const reactGrabScriptMatch = originalContent.match(/(<Script[\s\S]*?react-grab[\s\S]*?)\s*(\/?>)/i);
968
- if (!reactGrabScriptMatch) return {
969
- success: false,
970
- filePath,
971
- message: "Could not find React Grab Script tag"
972
- };
973
- const scriptTag = reactGrabScriptMatch[0];
974
- const scriptOpening = reactGrabScriptMatch[1];
975
- const scriptClosing = reactGrabScriptMatch[2];
976
- const existingDataOptionsMatch = scriptTag.match(/data-options=\{JSON\.stringify\([^)]+\)\}/);
977
- const dataOptionsAttr = `data-options={JSON.stringify(\n ${formatOptionsForNextjs(options)}\n )}`;
978
- let newScriptTag;
979
- if (existingDataOptionsMatch) newScriptTag = scriptTag.replace(existingDataOptionsMatch[0], dataOptionsAttr);
980
- else newScriptTag = `${scriptOpening}\n ${dataOptionsAttr}\n ${scriptClosing}`;
981
- return {
982
- success: true,
983
- filePath,
984
- message: "Update React Grab options",
985
- originalContent,
986
- newContent: originalContent.replace(scriptTag, newScriptTag)
987
- };
988
- };
989
- const addOptionsToDynamicImport = (originalContent, options, filePath) => {
990
- const reactGrabImportWithInitMatch = originalContent.match(/(void\s+)?import\s*\(\s*["']react-grab(?:\/[^"']+)?["']\s*\)(?:\.then\s*\(\s*(?:\(m\)\s*=>\s*m\.init\s*\([^)]*\)|\(\{\s*init\s*\}\)\s*=>\s*init\s*\([^)]*\))\s*\))?/);
991
- if (!reactGrabImportWithInitMatch) return {
992
- success: false,
993
- filePath,
994
- message: "Could not find React Grab import"
995
- };
996
- const optionsJson = formatOptionsAsJson(options);
997
- const newImport = `${reactGrabImportWithInitMatch[1] ?? ""}import("react-grab").then((m) => m.init(${optionsJson}))`;
998
- return {
999
- success: true,
1000
- filePath,
1001
- message: "Update React Grab options",
1002
- originalContent,
1003
- newContent: originalContent.replace(reactGrabImportWithInitMatch[0], newImport)
1004
- };
1005
- };
1006
- const addOptionsToTanStackImport = (originalContent, options, filePath) => {
1007
- const reactGrabImportWithInitMatch = originalContent.match(/(?:(void\s+)?import\s*\(\s*["']react-grab\/core["']\s*\)\.then\s*\(\s*(?:\(\s*\{\s*init\s*\}\s*\)\s*=>\s*init\s*\([^)]*\)|\(m\)\s*=>\s*m\.init\s*\([^)]*\))\s*\)|(void\s+)?import\s*\(\s*["']react-grab(?!\/core)(?:\/[^"']+)?["']\s*\))/);
1008
- if (!reactGrabImportWithInitMatch) return {
1009
- success: false,
1010
- filePath,
1011
- message: "Could not find React Grab import"
1012
- };
1013
- const optionsJson = formatOptionsAsJson(options);
1014
- const newImport = `${reactGrabImportWithInitMatch[1] ?? reactGrabImportWithInitMatch[2] ?? ""}import("react-grab/core").then(({ init }) => init(${optionsJson}))`;
1015
- return {
1016
- success: true,
1017
- filePath,
1018
- message: "Update React Grab options",
1019
- originalContent,
1020
- newContent: originalContent.replace(reactGrabImportWithInitMatch[0], newImport)
1021
- };
1022
- };
1023
- const addOptionsToAnyImport = (originalContent, options, filePath) => {
1024
- const dynamicImportResult = addOptionsToDynamicImport(originalContent, options, filePath);
1025
- if (dynamicImportResult.success) return dynamicImportResult;
1026
- return addOptionsToTanStackImport(originalContent, options, filePath);
1027
- };
1028
- const previewOptionsTransform = (projectRoot, framework, nextRouterType, options) => {
1029
- const filePath = findReactGrabFile(projectRoot, framework, nextRouterType);
1030
- if (!filePath) return {
1031
- success: false,
1032
- filePath: "",
1033
- message: "Could not find file containing React Grab configuration"
1034
- };
1035
- const originalContent = readFileSync(filePath, "utf-8");
1036
- if (!hasReactGrabSetupCode(originalContent)) return {
1037
- success: false,
1038
- filePath,
1039
- message: "Could not find React Grab code in the file"
1040
- };
1041
- switch (framework) {
1042
- case "next":
1043
- if (isInstrumentationFile(filePath)) return addOptionsToAnyImport(originalContent, options, filePath);
1044
- return addOptionsToNextScript(originalContent, options, filePath);
1045
- case "vite": return addOptionsToDynamicImport(originalContent, options, filePath);
1046
- case "tanstack": return addOptionsToTanStackImport(originalContent, options, filePath);
1047
- case "webpack": return addOptionsToDynamicImport(originalContent, options, filePath);
1048
- default: return {
1049
- success: false,
1050
- filePath,
1051
- message: `Unknown framework: ${framework}`
1052
- };
1053
- }
1054
- };
1055
- const previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDomain) => {
1056
- const filePath = findReactGrabFile(projectRoot, framework, nextRouterType);
1057
- if (!filePath) return {
1058
- success: false,
1059
- filePath: "",
1060
- message: "Could not find React Grab file"
1061
- };
1062
- const originalContent = readFileSync(filePath, "utf-8");
1063
- const newContent = originalContent.replace(/(https?:)?\/\/[^/\s"']+(?=\/(?:@?react-grab))/g, `//${targetCdnDomain}`).replace(/(https?:)?\/\/[^/\s"']*react-grab[^/\s"']*\.com(?=\/script\.js)/g, `//${targetCdnDomain}`);
1064
- if (newContent === originalContent) return {
1065
- success: true,
1066
- filePath,
1067
- message: "CDN already set",
1068
- noChanges: true
1069
- };
1070
- return {
1071
- success: true,
1072
- filePath,
1073
- message: "Update CDN",
1074
- originalContent,
1075
- newContent
1076
- };
1077
- };
1078
- //#endregion
1079
264
  //#region src/utils/constants.ts
1080
265
  const MAX_KEY_HOLD_DURATION_MS = 2e3;
1081
266
  const DEFAULT_WATCH_DIR = ".react-grab";
@@ -1099,7 +284,7 @@ const formatActivationKeyDisplay = (activationKey) => {
1099
284
  };
1100
285
  //#endregion
1101
286
  //#region src/commands/configure.ts
1102
- const VERSION$4 = "0.1.46";
287
+ const VERSION$4 = "0.1.47-dev.d5e8fc8";
1103
288
  const isMac = process.platform === "darwin";
1104
289
  const META_LABEL = isMac ? "Cmd" : "Win";
1105
290
  const ALT_LABEL = isMac ? "Option" : "Alt";
@@ -1390,7 +575,7 @@ const CONFIG_OPTIONS = [
1390
575
  {
1391
576
  id: "maxContextLines",
1392
577
  title: "Max Context Lines",
1393
- description: "Number of surrounding code lines to include in context"
578
+ description: "Max source-location lines in copied context (raise for large apps)"
1394
579
  }
1395
580
  ];
1396
581
  const comboToString = (combo) => {
@@ -1664,38 +849,6 @@ const configure = new Command().name("configure").alias("config").description("c
1664
849
  }
1665
850
  });
1666
851
  //#endregion
1667
- //#region src/utils/install.ts
1668
- const installPackages = async (packages, options = {}) => {
1669
- if (packages.length === 0) return;
1670
- const detectedAgent = options.packageManager ?? await detectPackageManager(options.cwd ?? process.cwd());
1671
- const args = [];
1672
- if (options.preferOffline) args.push("--prefer-offline");
1673
- if (detectedAgent === "pnpm") {
1674
- args.push("--prod=false");
1675
- if (existsSync(resolve(options.cwd ?? process.cwd(), "pnpm-workspace.yaml"))) args.push("-w");
1676
- }
1677
- if (options.additionalArgs) args.push(...options.additionalArgs);
1678
- await x(detectedAgent, [
1679
- detectedAgent === "npm" ? "install" : "add",
1680
- ...options.isDev !== false ? ["-D"] : [],
1681
- ...args,
1682
- ...packages
1683
- ], {
1684
- nodeOptions: {
1685
- stdio: options.silent ? "ignore" : "inherit",
1686
- cwd: options.cwd,
1687
- env: {
1688
- ...process.env,
1689
- REACT_GRAB_INIT: "1"
1690
- }
1691
- },
1692
- throwOnError: true
1693
- });
1694
- };
1695
- const getPackagesToInstall = (includeReactGrab = true) => {
1696
- return includeReactGrab ? ["react-grab"] : [];
1697
- };
1698
- //#endregion
1699
852
  //#region src/utils/cli-helpers.ts
1700
853
  const applyTransformWithFeedback = (result, message) => {
1701
854
  const writeSpinner = spinner(message ?? `Applying changes to ${result.filePath}.`).start();
@@ -1731,7 +884,7 @@ const isTelemetryEnabled = () => {
1731
884
  };
1732
885
  //#endregion
1733
886
  //#region src/commands/init.ts
1734
- const VERSION$3 = "0.1.46";
887
+ const VERSION$3 = "0.1.47-dev.d5e8fc8";
1735
888
  const REPORT_URL = "https://react-grab.com/api/report-cli";
1736
889
  const DOCS_URL = "https://github.com/aidenybai/react-grab";
1737
890
  const reportToCli = (type, config, error) => {
@@ -2682,19 +1835,20 @@ const pull = new Command().name("pull").description("start the watcher if needed
2682
1835
  });
2683
1836
  //#endregion
2684
1837
  //#region src/commands/remove.ts
2685
- const VERSION$2 = "0.1.46";
1838
+ const VERSION$2 = "0.1.47-dev.d5e8fc8";
2686
1839
  const remove = new Command().name("remove").description("uninstall the React Grab skill from your agent").option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "remove the globally-installed skill instead of the project's", false).action(async (opts) => {
2687
1840
  console.log(`${pc.magenta("✿")} ${pc.bold("React Grab")} ${pc.gray(VERSION$2)}`);
2688
1841
  console.log();
2689
1842
  try {
2690
1843
  logger.break();
2691
- const removedCount = await removeSkill({
1844
+ const removedAgents = await removeSkill({
2692
1845
  cwd: resolve(opts.cwd),
2693
1846
  global: opts.global
2694
1847
  });
1848
+ for (const agent of removedAgents) logger.log(` ${highlighter.success("✓")} ${agentLabel(agent)}`);
2695
1849
  logger.break();
2696
- if (removedCount === 0) logger.log("React Grab skill is not installed in any detected agent.");
2697
- else logger.log(`${highlighter.success("Removed")} the React Grab skill from ${removedCount} agent${removedCount === 1 ? "" : "s"}.`);
1850
+ if (removedAgents.length === 0) logger.log("React Grab skill is not installed in any detected agent.");
1851
+ else logger.log(`${highlighter.success("Removed")} the React Grab skill from ${removedAgents.length} agent${removedAgents.length === 1 ? "" : "s"}.`);
2698
1852
  logger.break();
2699
1853
  } catch (error) {
2700
1854
  handleError(error);
@@ -2710,7 +1864,7 @@ const stop = new Command().name("stop").description("stop the React Grab watcher
2710
1864
  });
2711
1865
  //#endregion
2712
1866
  //#region src/commands/upgrade.ts
2713
- const VERSION$1 = "0.1.46";
1867
+ const VERSION$1 = "0.1.47-dev.d5e8fc8";
2714
1868
  const NPM_REGISTRY_URL = "https://registry.npmjs.org/react-grab/latest";
2715
1869
  const fetchLatestVersion = async () => {
2716
1870
  try {
@@ -2822,7 +1976,7 @@ const watch = new Command().name("watch").description("run the React Grab captur
2822
1976
  });
2823
1977
  //#endregion
2824
1978
  //#region src/cli.ts
2825
- const VERSION = "0.1.46";
1979
+ const VERSION = "0.1.47-dev.d5e8fc8";
2826
1980
  const VERSION_API_URL = "https://www.react-grab.com/api/version";
2827
1981
  process.on("SIGINT", () => process.exit(0));
2828
1982
  process.on("SIGTERM", () => process.exit(0));
@@ -2831,7 +1985,7 @@ try {
2831
1985
  } catch {}
2832
1986
  const program = new Command().name("grab").description("add React Grab to your project").version(VERSION, "-v, --version", "display the version number");
2833
1987
  program.addCommand(init);
2834
- program.addCommand(add$1);
1988
+ program.addCommand(add);
2835
1989
  program.addCommand(remove);
2836
1990
  program.addCommand(configure);
2837
1991
  program.addCommand(upgrade);