@react-grab/cli 0.1.13 → 0.1.15

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.
Files changed (3) hide show
  1. package/dist/cli.cjs +196 -94
  2. package/dist/cli.js +197 -96
  3. package/package.json +2 -1
package/dist/cli.cjs CHANGED
@@ -8,6 +8,7 @@ var child_process = require('child_process');
8
8
  var fs = require('fs');
9
9
  var path = require('path');
10
10
  var ni = require('@antfu/ni');
11
+ var ignore = require('ignore');
11
12
  var os = require('os');
12
13
  var process2 = require('process');
13
14
  var ora = require('ora');
@@ -18,10 +19,20 @@ var pc__default = /*#__PURE__*/_interopDefault(pc);
18
19
  var basePrompts__default = /*#__PURE__*/_interopDefault(basePrompts);
19
20
  var fs__default = /*#__PURE__*/_interopDefault(fs);
20
21
  var path__default = /*#__PURE__*/_interopDefault(path);
22
+ var ignore__default = /*#__PURE__*/_interopDefault(ignore);
21
23
  var os__default = /*#__PURE__*/_interopDefault(os);
22
24
  var process2__default = /*#__PURE__*/_interopDefault(process2);
23
25
  var ora__default = /*#__PURE__*/_interopDefault(ora);
24
26
 
27
+ // src/utils/is-non-interactive.ts
28
+ var NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [
29
+ "CI",
30
+ "CLAUDECODE",
31
+ "AMI"
32
+ ];
33
+ var detectNonInteractive = (yesFlag) => yesFlag || NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some(
34
+ (variable) => process.env[variable] === "true"
35
+ ) || !process.stdin.isTTY;
25
36
  var highlighter = {
26
37
  error: pc__default.default.red,
27
38
  warn: pc__default.default.yellow,
@@ -65,10 +76,19 @@ var onCancel = () => {
65
76
  var prompts = (questions) => {
66
77
  return basePrompts__default.default(questions, { onCancel });
67
78
  };
79
+ var VALID_PACKAGE_MANAGERS = /* @__PURE__ */ new Set([
80
+ "npm",
81
+ "yarn",
82
+ "pnpm",
83
+ "bun"
84
+ ]);
68
85
  var detectPackageManager = async (projectRoot) => {
69
86
  const detected = await ni.detect({ cwd: projectRoot });
70
- if (detected && ["npm", "yarn", "pnpm", "bun"].includes(detected)) {
71
- return detected;
87
+ if (detected) {
88
+ const managerName = detected.split("@")[0];
89
+ if (VALID_PACKAGE_MANAGERS.has(managerName)) {
90
+ return managerName;
91
+ }
72
92
  }
73
93
  return "npm";
74
94
  };
@@ -182,18 +202,22 @@ var getWorkspacePatterns = (projectRoot) => {
182
202
  return [...new Set(patterns)];
183
203
  };
184
204
  var expandWorkspacePattern = (projectRoot, pattern) => {
185
- const results = [];
205
+ const isGlob = pattern.endsWith("/*");
186
206
  const cleanPattern = pattern.replace(/\/\*$/, "");
187
207
  const basePath = path.join(projectRoot, cleanPattern);
188
- if (!fs.existsSync(basePath)) return results;
208
+ if (!fs.existsSync(basePath)) return [];
209
+ if (!isGlob) {
210
+ const hasPackageJson = fs.existsSync(path.join(basePath, "package.json"));
211
+ return hasPackageJson ? [basePath] : [];
212
+ }
213
+ const results = [];
189
214
  try {
190
215
  const entries = fs.readdirSync(basePath, { withFileTypes: true });
191
216
  for (const entry of entries) {
192
- if (entry.isDirectory()) {
193
- const packageJsonPath = path.join(basePath, entry.name, "package.json");
194
- if (fs.existsSync(packageJsonPath)) {
195
- results.push(path.join(basePath, entry.name));
196
- }
217
+ if (!entry.isDirectory()) continue;
218
+ const packageJsonPath = path.join(basePath, entry.name, "package.json");
219
+ if (fs.existsSync(packageJsonPath)) {
220
+ results.push(path.join(basePath, entry.name));
197
221
  }
198
222
  }
199
223
  } catch {
@@ -215,35 +239,92 @@ var hasReactDependency = (projectPath) => {
215
239
  return false;
216
240
  }
217
241
  };
242
+ var buildReactProject = (projectPath) => {
243
+ const framework = detectFramework(projectPath);
244
+ const hasReact = hasReactDependency(projectPath);
245
+ if (!hasReact && framework === "unknown") return null;
246
+ let name = path.basename(projectPath);
247
+ const packageJsonPath = path.join(projectPath, "package.json");
248
+ try {
249
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
250
+ name = packageJson.name || name;
251
+ } catch {
252
+ }
253
+ return { name, path: projectPath, framework, hasReact };
254
+ };
218
255
  var findWorkspaceProjects = (projectRoot) => {
219
256
  const patterns = getWorkspacePatterns(projectRoot);
220
257
  const projects = [];
221
258
  for (const pattern of patterns) {
222
- const projectPaths = expandWorkspacePattern(projectRoot, pattern);
223
- for (const projectPath of projectPaths) {
224
- const framework = detectFramework(projectPath);
225
- const hasReact = hasReactDependency(projectPath);
226
- if (hasReact || framework !== "unknown") {
227
- const packageJsonPath = path.join(projectPath, "package.json");
228
- let name = path.basename(projectPath);
229
- try {
230
- const packageJson = JSON.parse(
231
- fs.readFileSync(packageJsonPath, "utf-8")
232
- );
233
- name = packageJson.name || name;
234
- } catch {
259
+ for (const projectPath of expandWorkspacePattern(projectRoot, pattern)) {
260
+ const project = buildReactProject(projectPath);
261
+ if (project) projects.push(project);
262
+ }
263
+ }
264
+ return projects;
265
+ };
266
+ var ALWAYS_IGNORED_DIRECTORIES = [
267
+ "node_modules",
268
+ ".git",
269
+ ".next",
270
+ ".cache",
271
+ ".turbo",
272
+ "dist",
273
+ "build",
274
+ "coverage",
275
+ "test-results"
276
+ ];
277
+ var loadGitignore = (projectRoot) => {
278
+ const ignorer = ignore__default.default().add(ALWAYS_IGNORED_DIRECTORIES);
279
+ const gitignorePath = path.join(projectRoot, ".gitignore");
280
+ if (fs.existsSync(gitignorePath)) {
281
+ try {
282
+ ignorer.add(fs.readFileSync(gitignorePath, "utf-8"));
283
+ } catch {
284
+ }
285
+ }
286
+ return ignorer;
287
+ };
288
+ var scanDirectoryForProjects = (rootDirectory, ignorer, maxDepth, currentDepth = 0) => {
289
+ if (currentDepth >= maxDepth) return [];
290
+ if (!fs.existsSync(rootDirectory)) return [];
291
+ const projects = [];
292
+ try {
293
+ const entries = fs.readdirSync(rootDirectory, { withFileTypes: true });
294
+ for (const entry of entries) {
295
+ if (!entry.isDirectory()) continue;
296
+ if (ignorer.ignores(entry.name)) continue;
297
+ const entryPath = path.join(rootDirectory, entry.name);
298
+ const hasPackageJson = fs.existsSync(path.join(entryPath, "package.json"));
299
+ if (hasPackageJson) {
300
+ const project = buildReactProject(entryPath);
301
+ if (project) {
302
+ projects.push(project);
303
+ continue;
235
304
  }
236
- projects.push({
237
- name,
238
- path: projectPath,
239
- framework,
240
- hasReact
241
- });
242
305
  }
306
+ projects.push(
307
+ ...scanDirectoryForProjects(
308
+ entryPath,
309
+ ignorer,
310
+ maxDepth,
311
+ currentDepth + 1
312
+ )
313
+ );
243
314
  }
315
+ } catch {
316
+ return projects;
244
317
  }
245
318
  return projects;
246
319
  };
320
+ var MAX_SCAN_DEPTH = 2;
321
+ var findReactProjects = (projectRoot) => {
322
+ if (detectMonorepo(projectRoot)) {
323
+ return findWorkspaceProjects(projectRoot);
324
+ }
325
+ const ignorer = loadGitignore(projectRoot);
326
+ return scanDirectoryForProjects(projectRoot, ignorer, MAX_SCAN_DEPTH);
327
+ };
247
328
  var hasReactGrabInFile = (filePath) => {
248
329
  if (!fs.existsSync(filePath)) return false;
249
330
  try {
@@ -2185,9 +2266,9 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
2185
2266
  };
2186
2267
 
2187
2268
  // src/commands/add.ts
2188
- var VERSION = "0.1.13";
2269
+ var VERSION = "0.1.15";
2189
2270
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
2190
- var add = new commander.Command().name("add").alias("install").description("connect React Grab to your agent").argument("[agent]", `agent to add (${AGENTS.join(", ")})`).option("-y, --yes", "skip confirmation prompts", false).option(
2271
+ var add = new commander.Command().name("add").alias("install").description("connect React Grab to your agent").argument("[agent]", `agent to connect (${AGENTS.join(", ")}, mcp)`).option("-y, --yes", "skip confirmation prompts", false).option(
2191
2272
  "-c, --cwd <cwd>",
2192
2273
  "working directory (defaults to current directory)",
2193
2274
  process.cwd()
@@ -2198,7 +2279,7 @@ var add = new commander.Command().name("add").alias("install").description("conn
2198
2279
  console.log();
2199
2280
  try {
2200
2281
  const cwd = opts.cwd;
2201
- const isNonInteractive = opts.yes;
2282
+ const isNonInteractive = detectNonInteractive(opts.yes);
2202
2283
  const preflightSpinner = spinner("Preflight checks.").start();
2203
2284
  const projectInfo = await detectProject(cwd);
2204
2285
  if (!projectInfo.hasReactGrab) {
@@ -2349,7 +2430,7 @@ var add = new commander.Command().name("add").alias("install").description("conn
2349
2430
  }
2350
2431
  } else {
2351
2432
  logger.break();
2352
- logger.error("Please specify an agent to add.");
2433
+ logger.error("Please specify an agent to connect.");
2353
2434
  logger.error("Available agents: " + availableAgents.join(", "));
2354
2435
  logger.break();
2355
2436
  process.exit(1);
@@ -2546,7 +2627,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2546
2627
  var MAX_CONTEXT_LINES = 50;
2547
2628
 
2548
2629
  // src/commands/configure.ts
2549
- var VERSION2 = "0.1.13";
2630
+ var VERSION2 = "0.1.15";
2550
2631
  var isMac = process.platform === "darwin";
2551
2632
  var META_LABEL = isMac ? "Cmd" : "Win";
2552
2633
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -3102,7 +3183,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
3102
3183
  };
3103
3184
 
3104
3185
  // src/commands/init.ts
3105
- var VERSION3 = "0.1.13";
3186
+ var VERSION3 = "0.1.15";
3106
3187
  var REPORT_URL = "https://react-grab.com/api/report-cli";
3107
3188
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
3108
3189
  var reportToCli = (type, config, error) => {
@@ -3139,6 +3220,34 @@ var UNSUPPORTED_FRAMEWORK_NAMES = {
3139
3220
  gatsby: "Gatsby"
3140
3221
  };
3141
3222
  var getAgentName = getAgentDisplayName;
3223
+ var sortProjectsByFramework = (projects) => [...projects].sort((projectA, projectB) => {
3224
+ if (projectA.framework === "unknown" && projectB.framework !== "unknown")
3225
+ return 1;
3226
+ if (projectA.framework !== "unknown" && projectB.framework === "unknown")
3227
+ return -1;
3228
+ return 0;
3229
+ });
3230
+ var printSubprojects = (searchRoot, sortedProjects) => {
3231
+ logger.break();
3232
+ logger.log("Found the following projects:");
3233
+ logger.break();
3234
+ for (const project of sortedProjects) {
3235
+ const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3236
+ const relativePath = path.relative(searchRoot, project.path);
3237
+ logger.log(
3238
+ ` ${highlighter.info(project.name)}${frameworkLabel} ${highlighter.dim(relativePath)}`
3239
+ );
3240
+ }
3241
+ logger.break();
3242
+ logger.log(
3243
+ `Re-run with ${highlighter.info("-c <path>")} to specify a project:`
3244
+ );
3245
+ logger.break();
3246
+ logger.log(
3247
+ ` ${highlighter.dim("$")} npx -y grab@latest init -c ${path.relative(searchRoot, sortedProjects[0].path)}`
3248
+ );
3249
+ logger.break();
3250
+ };
3142
3251
  var formatActivationKeyDisplay2 = (activationKey) => {
3143
3252
  if (!activationKey) return "Default (Option/Alt)";
3144
3253
  return activationKey.split("+").map((part) => {
@@ -3153,7 +3262,7 @@ var formatActivationKeyDisplay2 = (activationKey) => {
3153
3262
  };
3154
3263
  var init = new commander.Command().name("init").description("initialize React Grab in your project").option("-y, --yes", "skip confirmation prompts", false).option("-f, --force", "force overwrite existing config", false).option(
3155
3264
  "-a, --agent <agent>",
3156
- "connect to your agent (claude-code, cursor, opencode, codex, gemini, amp, mcp)"
3265
+ `connect to your agent (${AGENTS.join(", ")}, mcp)`
3157
3266
  ).option(
3158
3267
  "-k, --key <key>",
3159
3268
  "activation key (e.g., Meta+K, Ctrl+Shift+G, Space)"
@@ -3167,8 +3276,14 @@ var init = new commander.Command().name("init").description("initialize React Gr
3167
3276
  );
3168
3277
  console.log();
3169
3278
  try {
3170
- const cwd = opts.cwd;
3171
- const isNonInteractive = opts.yes;
3279
+ const cwd = path.resolve(opts.cwd);
3280
+ const isNonInteractive = detectNonInteractive(opts.yes);
3281
+ if (!fs.existsSync(cwd)) {
3282
+ logger.break();
3283
+ logger.error(`Directory does not exist: ${highlighter.info(cwd)}`);
3284
+ logger.break();
3285
+ process.exit(1);
3286
+ }
3172
3287
  const preflightSpinner = spinner("Preflight checks.").start();
3173
3288
  const projectInfo = await detectProject(cwd);
3174
3289
  const removeAgents = async (agentsToRemove2, skipInstall = false) => {
@@ -3695,58 +3810,48 @@ var init = new commander.Command().name("init").description("initialize React Gr
3695
3810
  process.exit(1);
3696
3811
  }
3697
3812
  if (projectInfo.framework === "unknown") {
3698
- if (projectInfo.isMonorepo && !isNonInteractive) {
3699
- frameworkSpinner.info("Verifying framework. Found monorepo.");
3700
- const workspaceProjects = findWorkspaceProjects(
3701
- projectInfo.projectRoot
3702
- );
3703
- const reactProjects = workspaceProjects.filter(
3704
- (project) => project.hasReact || project.framework !== "unknown"
3813
+ let searchRoot = cwd;
3814
+ let reactProjects = findReactProjects(searchRoot);
3815
+ if (reactProjects.length === 0 && cwd !== process.cwd()) {
3816
+ searchRoot = process.cwd();
3817
+ reactProjects = findReactProjects(searchRoot);
3818
+ }
3819
+ if (reactProjects.length > 0) {
3820
+ frameworkSpinner.info(
3821
+ `Verifying framework. Found ${reactProjects.length} project${reactProjects.length === 1 ? "" : "s"}.`
3705
3822
  );
3706
- if (reactProjects.length > 0) {
3707
- logger.break();
3708
- const sortedProjects = [...reactProjects].sort(
3709
- (projectA, projectB) => {
3710
- if (projectA.framework === "unknown" && projectB.framework !== "unknown")
3711
- return 1;
3712
- if (projectA.framework !== "unknown" && projectB.framework === "unknown")
3713
- return -1;
3714
- return 0;
3715
- }
3716
- );
3717
- const { selectedProject } = await prompts({
3718
- type: "select",
3719
- name: "selectedProject",
3720
- message: "Select a project to install React Grab:",
3721
- choices: [
3722
- ...sortedProjects.map((project) => {
3723
- const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3724
- return {
3725
- title: `${project.name}${frameworkLabel}`,
3726
- value: project.path
3727
- };
3728
- }),
3729
- { title: "Skip", value: "skip" }
3730
- ]
3731
- });
3732
- if (!selectedProject || selectedProject === "skip") {
3733
- logger.break();
3734
- process.exit(0);
3735
- }
3736
- process.chdir(selectedProject);
3737
- const newProjectInfo = await detectProject(selectedProject);
3738
- Object.assign(projectInfo, newProjectInfo);
3739
- const newFrameworkSpinner = spinner("Verifying framework.").start();
3740
- newFrameworkSpinner.succeed(
3741
- `Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
3742
- );
3743
- } else {
3744
- frameworkSpinner.fail("Could not detect a supported framework.");
3745
- logger.break();
3746
- logger.log(`Visit ${highlighter.info(DOCS_URL)} for manual setup.`);
3747
- logger.break();
3823
+ const sortedProjects = sortProjectsByFramework(reactProjects);
3824
+ if (isNonInteractive) {
3825
+ printSubprojects(searchRoot, sortedProjects);
3748
3826
  process.exit(1);
3749
3827
  }
3828
+ logger.break();
3829
+ const { selectedProject } = await prompts({
3830
+ type: "select",
3831
+ name: "selectedProject",
3832
+ message: "Select a project to install React Grab:",
3833
+ choices: [
3834
+ ...sortedProjects.map((project) => {
3835
+ const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3836
+ return {
3837
+ title: `${project.name}${frameworkLabel}`,
3838
+ value: project.path
3839
+ };
3840
+ }),
3841
+ { title: "Skip", value: "skip" }
3842
+ ]
3843
+ });
3844
+ if (!selectedProject || selectedProject === "skip") {
3845
+ logger.break();
3846
+ process.exit(0);
3847
+ }
3848
+ process.chdir(selectedProject);
3849
+ const newProjectInfo = await detectProject(selectedProject);
3850
+ Object.assign(projectInfo, newProjectInfo);
3851
+ const newFrameworkSpinner = spinner("Verifying framework.").start();
3852
+ newFrameworkSpinner.succeed(
3853
+ `Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
3854
+ );
3750
3855
  } else {
3751
3856
  frameworkSpinner.fail("Could not detect a supported framework.");
3752
3857
  logger.break();
@@ -3936,11 +4041,8 @@ var init = new commander.Command().name("init").description("initialize React Gr
3936
4041
  reportToCli("error", void 0, error);
3937
4042
  }
3938
4043
  });
3939
- var VERSION4 = "0.1.13";
3940
- var remove = new commander.Command().name("remove").description("disconnect React Grab from your agent").argument(
3941
- "[agent]",
3942
- "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami, mcp)"
3943
- ).option("-y, --yes", "skip confirmation prompts", false).option(
4044
+ var VERSION4 = "0.1.15";
4045
+ var remove = new commander.Command().name("remove").description("disconnect React Grab from your agent").argument("[agent]", `agent to disconnect (${AGENTS.join(", ")}, mcp)`).option("-y, --yes", "skip confirmation prompts", false).option(
3944
4046
  "-c, --cwd <cwd>",
3945
4047
  "working directory (defaults to current directory)",
3946
4048
  process.cwd()
@@ -3951,7 +4053,7 @@ var remove = new commander.Command().name("remove").description("disconnect Reac
3951
4053
  console.log();
3952
4054
  try {
3953
4055
  const cwd = opts.cwd;
3954
- const isNonInteractive = opts.yes;
4056
+ const isNonInteractive = detectNonInteractive(opts.yes);
3955
4057
  const preflightSpinner = spinner("Preflight checks.").start();
3956
4058
  const projectInfo = await detectProject(cwd);
3957
4059
  if (!projectInfo.hasReactGrab) {
@@ -4001,7 +4103,7 @@ var remove = new commander.Command().name("remove").description("disconnect Reac
4001
4103
  agentToRemove = agent;
4002
4104
  } else {
4003
4105
  logger.break();
4004
- logger.error("Please specify an agent to remove.");
4106
+ logger.error("Please specify an agent to disconnect.");
4005
4107
  logger.error(
4006
4108
  "Installed agents: " + projectInfo.installedAgents.join(", ")
4007
4109
  );
@@ -4115,7 +4217,7 @@ var remove = new commander.Command().name("remove").description("disconnect Reac
4115
4217
  });
4116
4218
 
4117
4219
  // src/cli.ts
4118
- var VERSION5 = "0.1.13";
4220
+ var VERSION5 = "0.1.15";
4119
4221
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
4120
4222
  process.on("SIGINT", () => process.exit(0));
4121
4223
  process.on("SIGTERM", () => process.exit(0));
package/dist/cli.js CHANGED
@@ -3,13 +3,23 @@ import { Command } from 'commander';
3
3
  import pc from 'picocolors';
4
4
  import basePrompts from 'prompts';
5
5
  import { execSync } from 'child_process';
6
- import fs, { readFileSync, existsSync, writeFileSync, accessSync, constants, readdirSync } from 'fs';
7
- import path, { join, basename } from 'path';
6
+ import fs, { existsSync, readFileSync, writeFileSync, accessSync, constants, readdirSync } from 'fs';
7
+ import path, { resolve, join, relative, basename } from 'path';
8
8
  import { detect } from '@antfu/ni';
9
+ import ignore from 'ignore';
9
10
  import os from 'os';
10
11
  import process2 from 'process';
11
12
  import ora from 'ora';
12
13
 
14
+ // src/utils/is-non-interactive.ts
15
+ var NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [
16
+ "CI",
17
+ "CLAUDECODE",
18
+ "AMI"
19
+ ];
20
+ var detectNonInteractive = (yesFlag) => yesFlag || NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some(
21
+ (variable) => process.env[variable] === "true"
22
+ ) || !process.stdin.isTTY;
13
23
  var highlighter = {
14
24
  error: pc.red,
15
25
  warn: pc.yellow,
@@ -53,10 +63,19 @@ var onCancel = () => {
53
63
  var prompts = (questions) => {
54
64
  return basePrompts(questions, { onCancel });
55
65
  };
66
+ var VALID_PACKAGE_MANAGERS = /* @__PURE__ */ new Set([
67
+ "npm",
68
+ "yarn",
69
+ "pnpm",
70
+ "bun"
71
+ ]);
56
72
  var detectPackageManager = async (projectRoot) => {
57
73
  const detected = await detect({ cwd: projectRoot });
58
- if (detected && ["npm", "yarn", "pnpm", "bun"].includes(detected)) {
59
- return detected;
74
+ if (detected) {
75
+ const managerName = detected.split("@")[0];
76
+ if (VALID_PACKAGE_MANAGERS.has(managerName)) {
77
+ return managerName;
78
+ }
60
79
  }
61
80
  return "npm";
62
81
  };
@@ -170,18 +189,22 @@ var getWorkspacePatterns = (projectRoot) => {
170
189
  return [...new Set(patterns)];
171
190
  };
172
191
  var expandWorkspacePattern = (projectRoot, pattern) => {
173
- const results = [];
192
+ const isGlob = pattern.endsWith("/*");
174
193
  const cleanPattern = pattern.replace(/\/\*$/, "");
175
194
  const basePath = join(projectRoot, cleanPattern);
176
- if (!existsSync(basePath)) return results;
195
+ if (!existsSync(basePath)) return [];
196
+ if (!isGlob) {
197
+ const hasPackageJson = existsSync(join(basePath, "package.json"));
198
+ return hasPackageJson ? [basePath] : [];
199
+ }
200
+ const results = [];
177
201
  try {
178
202
  const entries = readdirSync(basePath, { withFileTypes: true });
179
203
  for (const entry of entries) {
180
- if (entry.isDirectory()) {
181
- const packageJsonPath = join(basePath, entry.name, "package.json");
182
- if (existsSync(packageJsonPath)) {
183
- results.push(join(basePath, entry.name));
184
- }
204
+ if (!entry.isDirectory()) continue;
205
+ const packageJsonPath = join(basePath, entry.name, "package.json");
206
+ if (existsSync(packageJsonPath)) {
207
+ results.push(join(basePath, entry.name));
185
208
  }
186
209
  }
187
210
  } catch {
@@ -203,35 +226,92 @@ var hasReactDependency = (projectPath) => {
203
226
  return false;
204
227
  }
205
228
  };
229
+ var buildReactProject = (projectPath) => {
230
+ const framework = detectFramework(projectPath);
231
+ const hasReact = hasReactDependency(projectPath);
232
+ if (!hasReact && framework === "unknown") return null;
233
+ let name = basename(projectPath);
234
+ const packageJsonPath = join(projectPath, "package.json");
235
+ try {
236
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
237
+ name = packageJson.name || name;
238
+ } catch {
239
+ }
240
+ return { name, path: projectPath, framework, hasReact };
241
+ };
206
242
  var findWorkspaceProjects = (projectRoot) => {
207
243
  const patterns = getWorkspacePatterns(projectRoot);
208
244
  const projects = [];
209
245
  for (const pattern of patterns) {
210
- const projectPaths = expandWorkspacePattern(projectRoot, pattern);
211
- for (const projectPath of projectPaths) {
212
- const framework = detectFramework(projectPath);
213
- const hasReact = hasReactDependency(projectPath);
214
- if (hasReact || framework !== "unknown") {
215
- const packageJsonPath = join(projectPath, "package.json");
216
- let name = basename(projectPath);
217
- try {
218
- const packageJson = JSON.parse(
219
- readFileSync(packageJsonPath, "utf-8")
220
- );
221
- name = packageJson.name || name;
222
- } catch {
246
+ for (const projectPath of expandWorkspacePattern(projectRoot, pattern)) {
247
+ const project = buildReactProject(projectPath);
248
+ if (project) projects.push(project);
249
+ }
250
+ }
251
+ return projects;
252
+ };
253
+ var ALWAYS_IGNORED_DIRECTORIES = [
254
+ "node_modules",
255
+ ".git",
256
+ ".next",
257
+ ".cache",
258
+ ".turbo",
259
+ "dist",
260
+ "build",
261
+ "coverage",
262
+ "test-results"
263
+ ];
264
+ var loadGitignore = (projectRoot) => {
265
+ const ignorer = ignore().add(ALWAYS_IGNORED_DIRECTORIES);
266
+ const gitignorePath = join(projectRoot, ".gitignore");
267
+ if (existsSync(gitignorePath)) {
268
+ try {
269
+ ignorer.add(readFileSync(gitignorePath, "utf-8"));
270
+ } catch {
271
+ }
272
+ }
273
+ return ignorer;
274
+ };
275
+ var scanDirectoryForProjects = (rootDirectory, ignorer, maxDepth, currentDepth = 0) => {
276
+ if (currentDepth >= maxDepth) return [];
277
+ if (!existsSync(rootDirectory)) return [];
278
+ const projects = [];
279
+ try {
280
+ const entries = readdirSync(rootDirectory, { withFileTypes: true });
281
+ for (const entry of entries) {
282
+ if (!entry.isDirectory()) continue;
283
+ if (ignorer.ignores(entry.name)) continue;
284
+ const entryPath = join(rootDirectory, entry.name);
285
+ const hasPackageJson = existsSync(join(entryPath, "package.json"));
286
+ if (hasPackageJson) {
287
+ const project = buildReactProject(entryPath);
288
+ if (project) {
289
+ projects.push(project);
290
+ continue;
223
291
  }
224
- projects.push({
225
- name,
226
- path: projectPath,
227
- framework,
228
- hasReact
229
- });
230
292
  }
293
+ projects.push(
294
+ ...scanDirectoryForProjects(
295
+ entryPath,
296
+ ignorer,
297
+ maxDepth,
298
+ currentDepth + 1
299
+ )
300
+ );
231
301
  }
302
+ } catch {
303
+ return projects;
232
304
  }
233
305
  return projects;
234
306
  };
307
+ var MAX_SCAN_DEPTH = 2;
308
+ var findReactProjects = (projectRoot) => {
309
+ if (detectMonorepo(projectRoot)) {
310
+ return findWorkspaceProjects(projectRoot);
311
+ }
312
+ const ignorer = loadGitignore(projectRoot);
313
+ return scanDirectoryForProjects(projectRoot, ignorer, MAX_SCAN_DEPTH);
314
+ };
235
315
  var hasReactGrabInFile = (filePath) => {
236
316
  if (!existsSync(filePath)) return false;
237
317
  try {
@@ -2173,9 +2253,9 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
2173
2253
  };
2174
2254
 
2175
2255
  // src/commands/add.ts
2176
- var VERSION = "0.1.13";
2256
+ var VERSION = "0.1.15";
2177
2257
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
2178
- var add = new Command().name("add").alias("install").description("connect React Grab to your agent").argument("[agent]", `agent to add (${AGENTS.join(", ")})`).option("-y, --yes", "skip confirmation prompts", false).option(
2258
+ var add = new Command().name("add").alias("install").description("connect React Grab to your agent").argument("[agent]", `agent to connect (${AGENTS.join(", ")}, mcp)`).option("-y, --yes", "skip confirmation prompts", false).option(
2179
2259
  "-c, --cwd <cwd>",
2180
2260
  "working directory (defaults to current directory)",
2181
2261
  process.cwd()
@@ -2186,7 +2266,7 @@ var add = new Command().name("add").alias("install").description("connect React
2186
2266
  console.log();
2187
2267
  try {
2188
2268
  const cwd = opts.cwd;
2189
- const isNonInteractive = opts.yes;
2269
+ const isNonInteractive = detectNonInteractive(opts.yes);
2190
2270
  const preflightSpinner = spinner("Preflight checks.").start();
2191
2271
  const projectInfo = await detectProject(cwd);
2192
2272
  if (!projectInfo.hasReactGrab) {
@@ -2337,7 +2417,7 @@ var add = new Command().name("add").alias("install").description("connect React
2337
2417
  }
2338
2418
  } else {
2339
2419
  logger.break();
2340
- logger.error("Please specify an agent to add.");
2420
+ logger.error("Please specify an agent to connect.");
2341
2421
  logger.error("Available agents: " + availableAgents.join(", "));
2342
2422
  logger.break();
2343
2423
  process.exit(1);
@@ -2534,7 +2614,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2534
2614
  var MAX_CONTEXT_LINES = 50;
2535
2615
 
2536
2616
  // src/commands/configure.ts
2537
- var VERSION2 = "0.1.13";
2617
+ var VERSION2 = "0.1.15";
2538
2618
  var isMac = process.platform === "darwin";
2539
2619
  var META_LABEL = isMac ? "Cmd" : "Win";
2540
2620
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -3090,7 +3170,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
3090
3170
  };
3091
3171
 
3092
3172
  // src/commands/init.ts
3093
- var VERSION3 = "0.1.13";
3173
+ var VERSION3 = "0.1.15";
3094
3174
  var REPORT_URL = "https://react-grab.com/api/report-cli";
3095
3175
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
3096
3176
  var reportToCli = (type, config, error) => {
@@ -3127,6 +3207,34 @@ var UNSUPPORTED_FRAMEWORK_NAMES = {
3127
3207
  gatsby: "Gatsby"
3128
3208
  };
3129
3209
  var getAgentName = getAgentDisplayName;
3210
+ var sortProjectsByFramework = (projects) => [...projects].sort((projectA, projectB) => {
3211
+ if (projectA.framework === "unknown" && projectB.framework !== "unknown")
3212
+ return 1;
3213
+ if (projectA.framework !== "unknown" && projectB.framework === "unknown")
3214
+ return -1;
3215
+ return 0;
3216
+ });
3217
+ var printSubprojects = (searchRoot, sortedProjects) => {
3218
+ logger.break();
3219
+ logger.log("Found the following projects:");
3220
+ logger.break();
3221
+ for (const project of sortedProjects) {
3222
+ const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3223
+ const relativePath = relative(searchRoot, project.path);
3224
+ logger.log(
3225
+ ` ${highlighter.info(project.name)}${frameworkLabel} ${highlighter.dim(relativePath)}`
3226
+ );
3227
+ }
3228
+ logger.break();
3229
+ logger.log(
3230
+ `Re-run with ${highlighter.info("-c <path>")} to specify a project:`
3231
+ );
3232
+ logger.break();
3233
+ logger.log(
3234
+ ` ${highlighter.dim("$")} npx -y grab@latest init -c ${relative(searchRoot, sortedProjects[0].path)}`
3235
+ );
3236
+ logger.break();
3237
+ };
3130
3238
  var formatActivationKeyDisplay2 = (activationKey) => {
3131
3239
  if (!activationKey) return "Default (Option/Alt)";
3132
3240
  return activationKey.split("+").map((part) => {
@@ -3141,7 +3249,7 @@ var formatActivationKeyDisplay2 = (activationKey) => {
3141
3249
  };
3142
3250
  var init = new Command().name("init").description("initialize React Grab in your project").option("-y, --yes", "skip confirmation prompts", false).option("-f, --force", "force overwrite existing config", false).option(
3143
3251
  "-a, --agent <agent>",
3144
- "connect to your agent (claude-code, cursor, opencode, codex, gemini, amp, mcp)"
3252
+ `connect to your agent (${AGENTS.join(", ")}, mcp)`
3145
3253
  ).option(
3146
3254
  "-k, --key <key>",
3147
3255
  "activation key (e.g., Meta+K, Ctrl+Shift+G, Space)"
@@ -3155,8 +3263,14 @@ var init = new Command().name("init").description("initialize React Grab in your
3155
3263
  );
3156
3264
  console.log();
3157
3265
  try {
3158
- const cwd = opts.cwd;
3159
- const isNonInteractive = opts.yes;
3266
+ const cwd = resolve(opts.cwd);
3267
+ const isNonInteractive = detectNonInteractive(opts.yes);
3268
+ if (!existsSync(cwd)) {
3269
+ logger.break();
3270
+ logger.error(`Directory does not exist: ${highlighter.info(cwd)}`);
3271
+ logger.break();
3272
+ process.exit(1);
3273
+ }
3160
3274
  const preflightSpinner = spinner("Preflight checks.").start();
3161
3275
  const projectInfo = await detectProject(cwd);
3162
3276
  const removeAgents = async (agentsToRemove2, skipInstall = false) => {
@@ -3683,58 +3797,48 @@ var init = new Command().name("init").description("initialize React Grab in your
3683
3797
  process.exit(1);
3684
3798
  }
3685
3799
  if (projectInfo.framework === "unknown") {
3686
- if (projectInfo.isMonorepo && !isNonInteractive) {
3687
- frameworkSpinner.info("Verifying framework. Found monorepo.");
3688
- const workspaceProjects = findWorkspaceProjects(
3689
- projectInfo.projectRoot
3690
- );
3691
- const reactProjects = workspaceProjects.filter(
3692
- (project) => project.hasReact || project.framework !== "unknown"
3800
+ let searchRoot = cwd;
3801
+ let reactProjects = findReactProjects(searchRoot);
3802
+ if (reactProjects.length === 0 && cwd !== process.cwd()) {
3803
+ searchRoot = process.cwd();
3804
+ reactProjects = findReactProjects(searchRoot);
3805
+ }
3806
+ if (reactProjects.length > 0) {
3807
+ frameworkSpinner.info(
3808
+ `Verifying framework. Found ${reactProjects.length} project${reactProjects.length === 1 ? "" : "s"}.`
3693
3809
  );
3694
- if (reactProjects.length > 0) {
3695
- logger.break();
3696
- const sortedProjects = [...reactProjects].sort(
3697
- (projectA, projectB) => {
3698
- if (projectA.framework === "unknown" && projectB.framework !== "unknown")
3699
- return 1;
3700
- if (projectA.framework !== "unknown" && projectB.framework === "unknown")
3701
- return -1;
3702
- return 0;
3703
- }
3704
- );
3705
- const { selectedProject } = await prompts({
3706
- type: "select",
3707
- name: "selectedProject",
3708
- message: "Select a project to install React Grab:",
3709
- choices: [
3710
- ...sortedProjects.map((project) => {
3711
- const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3712
- return {
3713
- title: `${project.name}${frameworkLabel}`,
3714
- value: project.path
3715
- };
3716
- }),
3717
- { title: "Skip", value: "skip" }
3718
- ]
3719
- });
3720
- if (!selectedProject || selectedProject === "skip") {
3721
- logger.break();
3722
- process.exit(0);
3723
- }
3724
- process.chdir(selectedProject);
3725
- const newProjectInfo = await detectProject(selectedProject);
3726
- Object.assign(projectInfo, newProjectInfo);
3727
- const newFrameworkSpinner = spinner("Verifying framework.").start();
3728
- newFrameworkSpinner.succeed(
3729
- `Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
3730
- );
3731
- } else {
3732
- frameworkSpinner.fail("Could not detect a supported framework.");
3733
- logger.break();
3734
- logger.log(`Visit ${highlighter.info(DOCS_URL)} for manual setup.`);
3735
- logger.break();
3810
+ const sortedProjects = sortProjectsByFramework(reactProjects);
3811
+ if (isNonInteractive) {
3812
+ printSubprojects(searchRoot, sortedProjects);
3736
3813
  process.exit(1);
3737
3814
  }
3815
+ logger.break();
3816
+ const { selectedProject } = await prompts({
3817
+ type: "select",
3818
+ name: "selectedProject",
3819
+ message: "Select a project to install React Grab:",
3820
+ choices: [
3821
+ ...sortedProjects.map((project) => {
3822
+ const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3823
+ return {
3824
+ title: `${project.name}${frameworkLabel}`,
3825
+ value: project.path
3826
+ };
3827
+ }),
3828
+ { title: "Skip", value: "skip" }
3829
+ ]
3830
+ });
3831
+ if (!selectedProject || selectedProject === "skip") {
3832
+ logger.break();
3833
+ process.exit(0);
3834
+ }
3835
+ process.chdir(selectedProject);
3836
+ const newProjectInfo = await detectProject(selectedProject);
3837
+ Object.assign(projectInfo, newProjectInfo);
3838
+ const newFrameworkSpinner = spinner("Verifying framework.").start();
3839
+ newFrameworkSpinner.succeed(
3840
+ `Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
3841
+ );
3738
3842
  } else {
3739
3843
  frameworkSpinner.fail("Could not detect a supported framework.");
3740
3844
  logger.break();
@@ -3924,11 +4028,8 @@ var init = new Command().name("init").description("initialize React Grab in your
3924
4028
  reportToCli("error", void 0, error);
3925
4029
  }
3926
4030
  });
3927
- var VERSION4 = "0.1.13";
3928
- var remove = new Command().name("remove").description("disconnect React Grab from your agent").argument(
3929
- "[agent]",
3930
- "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami, mcp)"
3931
- ).option("-y, --yes", "skip confirmation prompts", false).option(
4031
+ var VERSION4 = "0.1.15";
4032
+ var remove = new Command().name("remove").description("disconnect React Grab from your agent").argument("[agent]", `agent to disconnect (${AGENTS.join(", ")}, mcp)`).option("-y, --yes", "skip confirmation prompts", false).option(
3932
4033
  "-c, --cwd <cwd>",
3933
4034
  "working directory (defaults to current directory)",
3934
4035
  process.cwd()
@@ -3939,7 +4040,7 @@ var remove = new Command().name("remove").description("disconnect React Grab fro
3939
4040
  console.log();
3940
4041
  try {
3941
4042
  const cwd = opts.cwd;
3942
- const isNonInteractive = opts.yes;
4043
+ const isNonInteractive = detectNonInteractive(opts.yes);
3943
4044
  const preflightSpinner = spinner("Preflight checks.").start();
3944
4045
  const projectInfo = await detectProject(cwd);
3945
4046
  if (!projectInfo.hasReactGrab) {
@@ -3989,7 +4090,7 @@ var remove = new Command().name("remove").description("disconnect React Grab fro
3989
4090
  agentToRemove = agent;
3990
4091
  } else {
3991
4092
  logger.break();
3992
- logger.error("Please specify an agent to remove.");
4093
+ logger.error("Please specify an agent to disconnect.");
3993
4094
  logger.error(
3994
4095
  "Installed agents: " + projectInfo.installedAgents.join(", ")
3995
4096
  );
@@ -4103,7 +4204,7 @@ var remove = new Command().name("remove").description("disconnect React Grab fro
4103
4204
  });
4104
4205
 
4105
4206
  // src/cli.ts
4106
- var VERSION5 = "0.1.13";
4207
+ var VERSION5 = "0.1.15";
4107
4208
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
4108
4209
  process.on("SIGINT", () => process.exit(0));
4109
4210
  process.on("SIGTERM", () => process.exit(0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-grab/cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "bin": {
5
5
  "react-grab": "./dist/cli.js"
6
6
  },
@@ -18,6 +18,7 @@
18
18
  "dependencies": {
19
19
  "@antfu/ni": "^0.23.0",
20
20
  "commander": "^14.0.0",
21
+ "ignore": "^7.0.5",
21
22
  "ora": "^8.2.0",
22
23
  "picocolors": "^1.1.1",
23
24
  "prompts": "^2.4.2"