@react-grab/cli 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +240 -187
- package/dist/cli.js +221 -189
- package/package.json +5 -2
package/dist/cli.cjs
CHANGED
|
@@ -8,20 +8,56 @@ 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');
|
|
14
|
+
var jsonc = require('jsonc-parser');
|
|
15
|
+
var TOML = require('smol-toml');
|
|
13
16
|
var ora = require('ora');
|
|
14
17
|
|
|
15
18
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
19
|
|
|
20
|
+
function _interopNamespace(e) {
|
|
21
|
+
if (e && e.__esModule) return e;
|
|
22
|
+
var n = Object.create(null);
|
|
23
|
+
if (e) {
|
|
24
|
+
Object.keys(e).forEach(function (k) {
|
|
25
|
+
if (k !== 'default') {
|
|
26
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
27
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
get: function () { return e[k]; }
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
n.default = e;
|
|
35
|
+
return Object.freeze(n);
|
|
36
|
+
}
|
|
37
|
+
|
|
17
38
|
var pc__default = /*#__PURE__*/_interopDefault(pc);
|
|
18
39
|
var basePrompts__default = /*#__PURE__*/_interopDefault(basePrompts);
|
|
19
40
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
20
41
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
42
|
+
var ignore__default = /*#__PURE__*/_interopDefault(ignore);
|
|
21
43
|
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
22
44
|
var process2__default = /*#__PURE__*/_interopDefault(process2);
|
|
45
|
+
var jsonc__namespace = /*#__PURE__*/_interopNamespace(jsonc);
|
|
46
|
+
var TOML__namespace = /*#__PURE__*/_interopNamespace(TOML);
|
|
23
47
|
var ora__default = /*#__PURE__*/_interopDefault(ora);
|
|
24
48
|
|
|
49
|
+
// src/utils/is-non-interactive.ts
|
|
50
|
+
var AGENT_ENVIRONMENT_VARIABLES = [
|
|
51
|
+
"CI",
|
|
52
|
+
"CLAUDECODE",
|
|
53
|
+
"CURSOR_AGENT",
|
|
54
|
+
"CODEX_CI",
|
|
55
|
+
"OPENCODE",
|
|
56
|
+
"AMP_HOME",
|
|
57
|
+
"AMI"
|
|
58
|
+
];
|
|
59
|
+
var isEnvironmentVariableSet = (variable) => Boolean(process.env[variable]);
|
|
60
|
+
var detectNonInteractive = (yesFlag) => yesFlag || AGENT_ENVIRONMENT_VARIABLES.some(isEnvironmentVariableSet) || !process.stdin.isTTY;
|
|
25
61
|
var highlighter = {
|
|
26
62
|
error: pc__default.default.red,
|
|
27
63
|
warn: pc__default.default.yellow,
|
|
@@ -191,18 +227,22 @@ var getWorkspacePatterns = (projectRoot) => {
|
|
|
191
227
|
return [...new Set(patterns)];
|
|
192
228
|
};
|
|
193
229
|
var expandWorkspacePattern = (projectRoot, pattern) => {
|
|
194
|
-
const
|
|
230
|
+
const isGlob = pattern.endsWith("/*");
|
|
195
231
|
const cleanPattern = pattern.replace(/\/\*$/, "");
|
|
196
232
|
const basePath = path.join(projectRoot, cleanPattern);
|
|
197
|
-
if (!fs.existsSync(basePath)) return
|
|
233
|
+
if (!fs.existsSync(basePath)) return [];
|
|
234
|
+
if (!isGlob) {
|
|
235
|
+
const hasPackageJson = fs.existsSync(path.join(basePath, "package.json"));
|
|
236
|
+
return hasPackageJson ? [basePath] : [];
|
|
237
|
+
}
|
|
238
|
+
const results = [];
|
|
198
239
|
try {
|
|
199
240
|
const entries = fs.readdirSync(basePath, { withFileTypes: true });
|
|
200
241
|
for (const entry of entries) {
|
|
201
|
-
if (entry.isDirectory())
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
242
|
+
if (!entry.isDirectory()) continue;
|
|
243
|
+
const packageJsonPath = path.join(basePath, entry.name, "package.json");
|
|
244
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
245
|
+
results.push(path.join(basePath, entry.name));
|
|
206
246
|
}
|
|
207
247
|
}
|
|
208
248
|
} catch {
|
|
@@ -224,35 +264,92 @@ var hasReactDependency = (projectPath) => {
|
|
|
224
264
|
return false;
|
|
225
265
|
}
|
|
226
266
|
};
|
|
267
|
+
var buildReactProject = (projectPath) => {
|
|
268
|
+
const framework = detectFramework(projectPath);
|
|
269
|
+
const hasReact = hasReactDependency(projectPath);
|
|
270
|
+
if (!hasReact && framework === "unknown") return null;
|
|
271
|
+
let name = path.basename(projectPath);
|
|
272
|
+
const packageJsonPath = path.join(projectPath, "package.json");
|
|
273
|
+
try {
|
|
274
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
275
|
+
name = packageJson.name || name;
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
return { name, path: projectPath, framework, hasReact };
|
|
279
|
+
};
|
|
227
280
|
var findWorkspaceProjects = (projectRoot) => {
|
|
228
281
|
const patterns = getWorkspacePatterns(projectRoot);
|
|
229
282
|
const projects = [];
|
|
230
283
|
for (const pattern of patterns) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
284
|
+
for (const projectPath of expandWorkspacePattern(projectRoot, pattern)) {
|
|
285
|
+
const project = buildReactProject(projectPath);
|
|
286
|
+
if (project) projects.push(project);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return projects;
|
|
290
|
+
};
|
|
291
|
+
var ALWAYS_IGNORED_DIRECTORIES = [
|
|
292
|
+
"node_modules",
|
|
293
|
+
".git",
|
|
294
|
+
".next",
|
|
295
|
+
".cache",
|
|
296
|
+
".turbo",
|
|
297
|
+
"dist",
|
|
298
|
+
"build",
|
|
299
|
+
"coverage",
|
|
300
|
+
"test-results"
|
|
301
|
+
];
|
|
302
|
+
var loadGitignore = (projectRoot) => {
|
|
303
|
+
const ignorer = ignore__default.default().add(ALWAYS_IGNORED_DIRECTORIES);
|
|
304
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
305
|
+
if (fs.existsSync(gitignorePath)) {
|
|
306
|
+
try {
|
|
307
|
+
ignorer.add(fs.readFileSync(gitignorePath, "utf-8"));
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return ignorer;
|
|
312
|
+
};
|
|
313
|
+
var scanDirectoryForProjects = (rootDirectory, ignorer, maxDepth, currentDepth = 0) => {
|
|
314
|
+
if (currentDepth >= maxDepth) return [];
|
|
315
|
+
if (!fs.existsSync(rootDirectory)) return [];
|
|
316
|
+
const projects = [];
|
|
317
|
+
try {
|
|
318
|
+
const entries = fs.readdirSync(rootDirectory, { withFileTypes: true });
|
|
319
|
+
for (const entry of entries) {
|
|
320
|
+
if (!entry.isDirectory()) continue;
|
|
321
|
+
if (ignorer.ignores(entry.name)) continue;
|
|
322
|
+
const entryPath = path.join(rootDirectory, entry.name);
|
|
323
|
+
const hasPackageJson = fs.existsSync(path.join(entryPath, "package.json"));
|
|
324
|
+
if (hasPackageJson) {
|
|
325
|
+
const project = buildReactProject(entryPath);
|
|
326
|
+
if (project) {
|
|
327
|
+
projects.push(project);
|
|
328
|
+
continue;
|
|
244
329
|
}
|
|
245
|
-
projects.push({
|
|
246
|
-
name,
|
|
247
|
-
path: projectPath,
|
|
248
|
-
framework,
|
|
249
|
-
hasReact
|
|
250
|
-
});
|
|
251
330
|
}
|
|
331
|
+
projects.push(
|
|
332
|
+
...scanDirectoryForProjects(
|
|
333
|
+
entryPath,
|
|
334
|
+
ignorer,
|
|
335
|
+
maxDepth,
|
|
336
|
+
currentDepth + 1
|
|
337
|
+
)
|
|
338
|
+
);
|
|
252
339
|
}
|
|
340
|
+
} catch {
|
|
341
|
+
return projects;
|
|
253
342
|
}
|
|
254
343
|
return projects;
|
|
255
344
|
};
|
|
345
|
+
var MAX_SCAN_DEPTH = 2;
|
|
346
|
+
var findReactProjects = (projectRoot) => {
|
|
347
|
+
if (detectMonorepo(projectRoot)) {
|
|
348
|
+
return findWorkspaceProjects(projectRoot);
|
|
349
|
+
}
|
|
350
|
+
const ignorer = loadGitignore(projectRoot);
|
|
351
|
+
return scanDirectoryForProjects(projectRoot, ignorer, MAX_SCAN_DEPTH);
|
|
352
|
+
};
|
|
256
353
|
var hasReactGrabInFile = (filePath) => {
|
|
257
354
|
if (!fs.existsSync(filePath)) return false;
|
|
258
355
|
try {
|
|
@@ -318,6 +415,8 @@ var AGENT_PACKAGES = [
|
|
|
318
415
|
"@react-grab/gemini",
|
|
319
416
|
"@react-grab/amp",
|
|
320
417
|
"@react-grab/ami",
|
|
418
|
+
"@react-grab/droid",
|
|
419
|
+
"@react-grab/copilot",
|
|
321
420
|
"@react-grab/mcp"
|
|
322
421
|
];
|
|
323
422
|
var detectUnsupportedFramework = (projectRoot) => {
|
|
@@ -645,113 +744,39 @@ var ensureDirectory = (filePath) => {
|
|
|
645
744
|
fs__default.default.mkdirSync(directory, { recursive: true });
|
|
646
745
|
}
|
|
647
746
|
};
|
|
648
|
-
var
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
747
|
+
var JSONC_FORMAT_OPTIONS = {
|
|
748
|
+
tabSize: 2,
|
|
749
|
+
insertSpaces: true
|
|
750
|
+
};
|
|
751
|
+
var upsertIntoJsonc = (filePath, content, configKey, serverName, serverConfig) => {
|
|
752
|
+
const edits = jsonc__namespace.modify(
|
|
753
|
+
content,
|
|
754
|
+
[configKey, serverName],
|
|
755
|
+
serverConfig,
|
|
756
|
+
{ formattingOptions: JSONC_FORMAT_OPTIONS }
|
|
654
757
|
);
|
|
655
|
-
|
|
656
|
-
const keyPattern = new RegExp(`"${escapedConfigKey}"\\s*:\\s*\\{`);
|
|
657
|
-
const keyMatch = keyPattern.exec(content);
|
|
658
|
-
if (keyMatch) {
|
|
659
|
-
const insertPosition = keyMatch.index + keyMatch[0].length;
|
|
660
|
-
const entry = `
|
|
661
|
-
"${serverName}": ${serverJson},`;
|
|
662
|
-
fs__default.default.writeFileSync(
|
|
663
|
-
filePath,
|
|
664
|
-
content.slice(0, insertPosition) + entry + content.slice(insertPosition)
|
|
665
|
-
);
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
const lastBrace = content.lastIndexOf("}");
|
|
669
|
-
if (lastBrace === -1) return;
|
|
670
|
-
const beforeBrace = content.slice(0, lastBrace).trimEnd();
|
|
671
|
-
const withoutComments = beforeBrace.replace(/\/\/.*$/, "").trimEnd();
|
|
672
|
-
const lastChar = withoutComments[withoutComments.length - 1];
|
|
673
|
-
const needsComma = lastChar !== void 0 && lastChar !== "{" && lastChar !== ",";
|
|
674
|
-
const section = `${needsComma ? "," : ""}
|
|
675
|
-
"${configKey}": {
|
|
676
|
-
"${serverName}": ${serverJson}
|
|
677
|
-
}`;
|
|
678
|
-
fs__default.default.writeFileSync(filePath, beforeBrace + section + "\n}\n");
|
|
758
|
+
fs__default.default.writeFileSync(filePath, jsonc__namespace.applyEdits(content, edits));
|
|
679
759
|
};
|
|
680
760
|
var installJsonClient = (client) => {
|
|
681
761
|
ensureDirectory(client.configPath);
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
try {
|
|
691
|
-
const config = JSON.parse(content);
|
|
692
|
-
const servers = config[client.configKey] ?? {};
|
|
693
|
-
servers[SERVER_NAME] = client.serverConfig;
|
|
694
|
-
config[client.configKey] = servers;
|
|
695
|
-
fs__default.default.writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
|
|
696
|
-
} catch {
|
|
697
|
-
insertIntoJsonc(
|
|
698
|
-
client.configPath,
|
|
699
|
-
content,
|
|
700
|
-
client.configKey,
|
|
701
|
-
SERVER_NAME,
|
|
702
|
-
client.serverConfig
|
|
703
|
-
);
|
|
704
|
-
}
|
|
705
|
-
};
|
|
706
|
-
var buildTomlSection = (configKey, serverConfig) => {
|
|
707
|
-
const lines = [`[${configKey}.${SERVER_NAME}]`];
|
|
708
|
-
for (const [key, value] of Object.entries(serverConfig)) {
|
|
709
|
-
if (typeof value === "string") {
|
|
710
|
-
lines.push(`${key} = "${value}"`);
|
|
711
|
-
} else if (Array.isArray(value)) {
|
|
712
|
-
const items = value.map((item) => `"${item}"`).join(", ");
|
|
713
|
-
lines.push(`${key} = [${items}]`);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
return lines.join("\n");
|
|
762
|
+
const content = fs__default.default.existsSync(client.configPath) ? fs__default.default.readFileSync(client.configPath, "utf8") : "{}";
|
|
763
|
+
upsertIntoJsonc(
|
|
764
|
+
client.configPath,
|
|
765
|
+
content,
|
|
766
|
+
client.configKey,
|
|
767
|
+
SERVER_NAME,
|
|
768
|
+
client.serverConfig
|
|
769
|
+
);
|
|
717
770
|
};
|
|
718
771
|
var installTomlClient = (client) => {
|
|
719
772
|
ensureDirectory(client.configPath);
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
if (!content.includes(sectionHeader)) {
|
|
728
|
-
fs__default.default.writeFileSync(
|
|
729
|
-
client.configPath,
|
|
730
|
-
content.trimEnd() + "\n\n" + newSection + "\n"
|
|
731
|
-
);
|
|
732
|
-
return;
|
|
733
|
-
}
|
|
734
|
-
const lines = content.split("\n");
|
|
735
|
-
const resultLines = [];
|
|
736
|
-
let isInsideOurSection = false;
|
|
737
|
-
let didInsertReplacement = false;
|
|
738
|
-
for (const line of lines) {
|
|
739
|
-
if (line.trim() === sectionHeader) {
|
|
740
|
-
isInsideOurSection = true;
|
|
741
|
-
if (!didInsertReplacement) {
|
|
742
|
-
resultLines.push(newSection);
|
|
743
|
-
didInsertReplacement = true;
|
|
744
|
-
}
|
|
745
|
-
continue;
|
|
746
|
-
}
|
|
747
|
-
if (isInsideOurSection && line.startsWith("[")) {
|
|
748
|
-
isInsideOurSection = false;
|
|
749
|
-
}
|
|
750
|
-
if (!isInsideOurSection) {
|
|
751
|
-
resultLines.push(line);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
fs__default.default.writeFileSync(client.configPath, resultLines.join("\n"));
|
|
773
|
+
const existingConfig = fs__default.default.existsSync(
|
|
774
|
+
client.configPath
|
|
775
|
+
) ? TOML__namespace.parse(fs__default.default.readFileSync(client.configPath, "utf8")) : {};
|
|
776
|
+
const serverSection = existingConfig[client.configKey] ?? {};
|
|
777
|
+
serverSection[SERVER_NAME] = client.serverConfig;
|
|
778
|
+
existingConfig[client.configKey] = serverSection;
|
|
779
|
+
fs__default.default.writeFileSync(client.configPath, TOML__namespace.stringify(existingConfig));
|
|
755
780
|
};
|
|
756
781
|
var getMcpClientNames = () => getClients().map((client) => client.name);
|
|
757
782
|
var installMcpServers = (selectedClients) => {
|
|
@@ -853,7 +878,8 @@ var AGENTS = [
|
|
|
853
878
|
"gemini",
|
|
854
879
|
"amp",
|
|
855
880
|
"ami",
|
|
856
|
-
"droid"
|
|
881
|
+
"droid",
|
|
882
|
+
"copilot"
|
|
857
883
|
];
|
|
858
884
|
var AGENT_NAMES = {
|
|
859
885
|
"claude-code": "Claude Code",
|
|
@@ -863,7 +889,8 @@ var AGENT_NAMES = {
|
|
|
863
889
|
gemini: "Gemini",
|
|
864
890
|
amp: "Amp",
|
|
865
891
|
ami: "Ami",
|
|
866
|
-
droid: "Droid"
|
|
892
|
+
droid: "Droid",
|
|
893
|
+
copilot: "Copilot"
|
|
867
894
|
};
|
|
868
895
|
var getAgentDisplayName = (agent) => {
|
|
869
896
|
if (agent === "mcp") return "MCP";
|
|
@@ -1598,7 +1625,9 @@ var AGENT_PACKAGES2 = {
|
|
|
1598
1625
|
opencode: "@react-grab/opencode@latest",
|
|
1599
1626
|
codex: "@react-grab/codex@latest",
|
|
1600
1627
|
gemini: "@react-grab/gemini@latest",
|
|
1601
|
-
amp: "@react-grab/amp@latest"
|
|
1628
|
+
amp: "@react-grab/amp@latest",
|
|
1629
|
+
droid: "@react-grab/droid@latest",
|
|
1630
|
+
copilot: "@react-grab/copilot@latest"
|
|
1602
1631
|
};
|
|
1603
1632
|
var getAgentPrefix = (agent, packageManager) => {
|
|
1604
1633
|
const agentPackage = AGENT_PACKAGES2[agent];
|
|
@@ -2194,7 +2223,7 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
|
|
|
2194
2223
|
};
|
|
2195
2224
|
|
|
2196
2225
|
// src/commands/add.ts
|
|
2197
|
-
var VERSION = "0.1.
|
|
2226
|
+
var VERSION = "0.1.16";
|
|
2198
2227
|
var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
|
|
2199
2228
|
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(
|
|
2200
2229
|
"-c, --cwd <cwd>",
|
|
@@ -2207,7 +2236,7 @@ var add = new commander.Command().name("add").alias("install").description("conn
|
|
|
2207
2236
|
console.log();
|
|
2208
2237
|
try {
|
|
2209
2238
|
const cwd = opts.cwd;
|
|
2210
|
-
const isNonInteractive = opts.yes;
|
|
2239
|
+
const isNonInteractive = detectNonInteractive(opts.yes);
|
|
2211
2240
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
2212
2241
|
const projectInfo = await detectProject(cwd);
|
|
2213
2242
|
if (!projectInfo.hasReactGrab) {
|
|
@@ -2555,7 +2584,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
|
|
|
2555
2584
|
var MAX_CONTEXT_LINES = 50;
|
|
2556
2585
|
|
|
2557
2586
|
// src/commands/configure.ts
|
|
2558
|
-
var VERSION2 = "0.1.
|
|
2587
|
+
var VERSION2 = "0.1.16";
|
|
2559
2588
|
var isMac = process.platform === "darwin";
|
|
2560
2589
|
var META_LABEL = isMac ? "Cmd" : "Win";
|
|
2561
2590
|
var ALT_LABEL = isMac ? "Option" : "Alt";
|
|
@@ -3111,7 +3140,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
|
|
|
3111
3140
|
};
|
|
3112
3141
|
|
|
3113
3142
|
// src/commands/init.ts
|
|
3114
|
-
var VERSION3 = "0.1.
|
|
3143
|
+
var VERSION3 = "0.1.16";
|
|
3115
3144
|
var REPORT_URL = "https://react-grab.com/api/report-cli";
|
|
3116
3145
|
var DOCS_URL = "https://github.com/aidenybai/react-grab";
|
|
3117
3146
|
var reportToCli = (type, config, error) => {
|
|
@@ -3148,6 +3177,34 @@ var UNSUPPORTED_FRAMEWORK_NAMES = {
|
|
|
3148
3177
|
gatsby: "Gatsby"
|
|
3149
3178
|
};
|
|
3150
3179
|
var getAgentName = getAgentDisplayName;
|
|
3180
|
+
var sortProjectsByFramework = (projects) => [...projects].sort((projectA, projectB) => {
|
|
3181
|
+
if (projectA.framework === "unknown" && projectB.framework !== "unknown")
|
|
3182
|
+
return 1;
|
|
3183
|
+
if (projectA.framework !== "unknown" && projectB.framework === "unknown")
|
|
3184
|
+
return -1;
|
|
3185
|
+
return 0;
|
|
3186
|
+
});
|
|
3187
|
+
var printSubprojects = (searchRoot, sortedProjects) => {
|
|
3188
|
+
logger.break();
|
|
3189
|
+
logger.log("Found the following projects:");
|
|
3190
|
+
logger.break();
|
|
3191
|
+
for (const project of sortedProjects) {
|
|
3192
|
+
const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
|
|
3193
|
+
const relativePath = path.relative(searchRoot, project.path);
|
|
3194
|
+
logger.log(
|
|
3195
|
+
` ${highlighter.info(project.name)}${frameworkLabel} ${highlighter.dim(relativePath)}`
|
|
3196
|
+
);
|
|
3197
|
+
}
|
|
3198
|
+
logger.break();
|
|
3199
|
+
logger.log(
|
|
3200
|
+
`Re-run with ${highlighter.info("-c <path>")} to specify a project:`
|
|
3201
|
+
);
|
|
3202
|
+
logger.break();
|
|
3203
|
+
logger.log(
|
|
3204
|
+
` ${highlighter.dim("$")} npx -y grab@latest init -c ${path.relative(searchRoot, sortedProjects[0].path)}`
|
|
3205
|
+
);
|
|
3206
|
+
logger.break();
|
|
3207
|
+
};
|
|
3151
3208
|
var formatActivationKeyDisplay2 = (activationKey) => {
|
|
3152
3209
|
if (!activationKey) return "Default (Option/Alt)";
|
|
3153
3210
|
return activationKey.split("+").map((part) => {
|
|
@@ -3176,8 +3233,14 @@ var init = new commander.Command().name("init").description("initialize React Gr
|
|
|
3176
3233
|
);
|
|
3177
3234
|
console.log();
|
|
3178
3235
|
try {
|
|
3179
|
-
const cwd = opts.cwd;
|
|
3180
|
-
const isNonInteractive = opts.yes;
|
|
3236
|
+
const cwd = path.resolve(opts.cwd);
|
|
3237
|
+
const isNonInteractive = detectNonInteractive(opts.yes);
|
|
3238
|
+
if (!fs.existsSync(cwd)) {
|
|
3239
|
+
logger.break();
|
|
3240
|
+
logger.error(`Directory does not exist: ${highlighter.info(cwd)}`);
|
|
3241
|
+
logger.break();
|
|
3242
|
+
process.exit(1);
|
|
3243
|
+
}
|
|
3181
3244
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
3182
3245
|
const projectInfo = await detectProject(cwd);
|
|
3183
3246
|
const removeAgents = async (agentsToRemove2, skipInstall = false) => {
|
|
@@ -3704,58 +3767,48 @@ var init = new commander.Command().name("init").description("initialize React Gr
|
|
|
3704
3767
|
process.exit(1);
|
|
3705
3768
|
}
|
|
3706
3769
|
if (projectInfo.framework === "unknown") {
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
);
|
|
3712
|
-
|
|
3713
|
-
|
|
3770
|
+
let searchRoot = cwd;
|
|
3771
|
+
let reactProjects = findReactProjects(searchRoot);
|
|
3772
|
+
if (reactProjects.length === 0 && cwd !== process.cwd()) {
|
|
3773
|
+
searchRoot = process.cwd();
|
|
3774
|
+
reactProjects = findReactProjects(searchRoot);
|
|
3775
|
+
}
|
|
3776
|
+
if (reactProjects.length > 0) {
|
|
3777
|
+
frameworkSpinner.info(
|
|
3778
|
+
`Verifying framework. Found ${reactProjects.length} project${reactProjects.length === 1 ? "" : "s"}.`
|
|
3714
3779
|
);
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
(projectA, projectB) => {
|
|
3719
|
-
if (projectA.framework === "unknown" && projectB.framework !== "unknown")
|
|
3720
|
-
return 1;
|
|
3721
|
-
if (projectA.framework !== "unknown" && projectB.framework === "unknown")
|
|
3722
|
-
return -1;
|
|
3723
|
-
return 0;
|
|
3724
|
-
}
|
|
3725
|
-
);
|
|
3726
|
-
const { selectedProject } = await prompts({
|
|
3727
|
-
type: "select",
|
|
3728
|
-
name: "selectedProject",
|
|
3729
|
-
message: "Select a project to install React Grab:",
|
|
3730
|
-
choices: [
|
|
3731
|
-
...sortedProjects.map((project) => {
|
|
3732
|
-
const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
|
|
3733
|
-
return {
|
|
3734
|
-
title: `${project.name}${frameworkLabel}`,
|
|
3735
|
-
value: project.path
|
|
3736
|
-
};
|
|
3737
|
-
}),
|
|
3738
|
-
{ title: "Skip", value: "skip" }
|
|
3739
|
-
]
|
|
3740
|
-
});
|
|
3741
|
-
if (!selectedProject || selectedProject === "skip") {
|
|
3742
|
-
logger.break();
|
|
3743
|
-
process.exit(0);
|
|
3744
|
-
}
|
|
3745
|
-
process.chdir(selectedProject);
|
|
3746
|
-
const newProjectInfo = await detectProject(selectedProject);
|
|
3747
|
-
Object.assign(projectInfo, newProjectInfo);
|
|
3748
|
-
const newFrameworkSpinner = spinner("Verifying framework.").start();
|
|
3749
|
-
newFrameworkSpinner.succeed(
|
|
3750
|
-
`Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
|
|
3751
|
-
);
|
|
3752
|
-
} else {
|
|
3753
|
-
frameworkSpinner.fail("Could not detect a supported framework.");
|
|
3754
|
-
logger.break();
|
|
3755
|
-
logger.log(`Visit ${highlighter.info(DOCS_URL)} for manual setup.`);
|
|
3756
|
-
logger.break();
|
|
3780
|
+
const sortedProjects = sortProjectsByFramework(reactProjects);
|
|
3781
|
+
if (isNonInteractive) {
|
|
3782
|
+
printSubprojects(searchRoot, sortedProjects);
|
|
3757
3783
|
process.exit(1);
|
|
3758
3784
|
}
|
|
3785
|
+
logger.break();
|
|
3786
|
+
const { selectedProject } = await prompts({
|
|
3787
|
+
type: "select",
|
|
3788
|
+
name: "selectedProject",
|
|
3789
|
+
message: "Select a project to install React Grab:",
|
|
3790
|
+
choices: [
|
|
3791
|
+
...sortedProjects.map((project) => {
|
|
3792
|
+
const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
|
|
3793
|
+
return {
|
|
3794
|
+
title: `${project.name}${frameworkLabel}`,
|
|
3795
|
+
value: project.path
|
|
3796
|
+
};
|
|
3797
|
+
}),
|
|
3798
|
+
{ title: "Skip", value: "skip" }
|
|
3799
|
+
]
|
|
3800
|
+
});
|
|
3801
|
+
if (!selectedProject || selectedProject === "skip") {
|
|
3802
|
+
logger.break();
|
|
3803
|
+
process.exit(0);
|
|
3804
|
+
}
|
|
3805
|
+
process.chdir(selectedProject);
|
|
3806
|
+
const newProjectInfo = await detectProject(selectedProject);
|
|
3807
|
+
Object.assign(projectInfo, newProjectInfo);
|
|
3808
|
+
const newFrameworkSpinner = spinner("Verifying framework.").start();
|
|
3809
|
+
newFrameworkSpinner.succeed(
|
|
3810
|
+
`Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
|
|
3811
|
+
);
|
|
3759
3812
|
} else {
|
|
3760
3813
|
frameworkSpinner.fail("Could not detect a supported framework.");
|
|
3761
3814
|
logger.break();
|
|
@@ -3945,7 +3998,7 @@ var init = new commander.Command().name("init").description("initialize React Gr
|
|
|
3945
3998
|
reportToCli("error", void 0, error);
|
|
3946
3999
|
}
|
|
3947
4000
|
});
|
|
3948
|
-
var VERSION4 = "0.1.
|
|
4001
|
+
var VERSION4 = "0.1.16";
|
|
3949
4002
|
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(
|
|
3950
4003
|
"-c, --cwd <cwd>",
|
|
3951
4004
|
"working directory (defaults to current directory)",
|
|
@@ -3957,7 +4010,7 @@ var remove = new commander.Command().name("remove").description("disconnect Reac
|
|
|
3957
4010
|
console.log();
|
|
3958
4011
|
try {
|
|
3959
4012
|
const cwd = opts.cwd;
|
|
3960
|
-
const isNonInteractive = opts.yes;
|
|
4013
|
+
const isNonInteractive = detectNonInteractive(opts.yes);
|
|
3961
4014
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
3962
4015
|
const projectInfo = await detectProject(cwd);
|
|
3963
4016
|
if (!projectInfo.hasReactGrab) {
|
|
@@ -4121,7 +4174,7 @@ var remove = new commander.Command().name("remove").description("disconnect Reac
|
|
|
4121
4174
|
});
|
|
4122
4175
|
|
|
4123
4176
|
// src/cli.ts
|
|
4124
|
-
var VERSION5 = "0.1.
|
|
4177
|
+
var VERSION5 = "0.1.16";
|
|
4125
4178
|
var VERSION_API_URL = "https://www.react-grab.com/api/version";
|
|
4126
4179
|
process.on("SIGINT", () => process.exit(0));
|
|
4127
4180
|
process.on("SIGTERM", () => process.exit(0));
|
package/dist/cli.js
CHANGED
|
@@ -3,13 +3,28 @@ 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, {
|
|
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';
|
|
12
|
+
import * as jsonc from 'jsonc-parser';
|
|
13
|
+
import * as TOML from 'smol-toml';
|
|
11
14
|
import ora from 'ora';
|
|
12
15
|
|
|
16
|
+
// src/utils/is-non-interactive.ts
|
|
17
|
+
var AGENT_ENVIRONMENT_VARIABLES = [
|
|
18
|
+
"CI",
|
|
19
|
+
"CLAUDECODE",
|
|
20
|
+
"CURSOR_AGENT",
|
|
21
|
+
"CODEX_CI",
|
|
22
|
+
"OPENCODE",
|
|
23
|
+
"AMP_HOME",
|
|
24
|
+
"AMI"
|
|
25
|
+
];
|
|
26
|
+
var isEnvironmentVariableSet = (variable) => Boolean(process.env[variable]);
|
|
27
|
+
var detectNonInteractive = (yesFlag) => yesFlag || AGENT_ENVIRONMENT_VARIABLES.some(isEnvironmentVariableSet) || !process.stdin.isTTY;
|
|
13
28
|
var highlighter = {
|
|
14
29
|
error: pc.red,
|
|
15
30
|
warn: pc.yellow,
|
|
@@ -179,18 +194,22 @@ var getWorkspacePatterns = (projectRoot) => {
|
|
|
179
194
|
return [...new Set(patterns)];
|
|
180
195
|
};
|
|
181
196
|
var expandWorkspacePattern = (projectRoot, pattern) => {
|
|
182
|
-
const
|
|
197
|
+
const isGlob = pattern.endsWith("/*");
|
|
183
198
|
const cleanPattern = pattern.replace(/\/\*$/, "");
|
|
184
199
|
const basePath = join(projectRoot, cleanPattern);
|
|
185
|
-
if (!existsSync(basePath)) return
|
|
200
|
+
if (!existsSync(basePath)) return [];
|
|
201
|
+
if (!isGlob) {
|
|
202
|
+
const hasPackageJson = existsSync(join(basePath, "package.json"));
|
|
203
|
+
return hasPackageJson ? [basePath] : [];
|
|
204
|
+
}
|
|
205
|
+
const results = [];
|
|
186
206
|
try {
|
|
187
207
|
const entries = readdirSync(basePath, { withFileTypes: true });
|
|
188
208
|
for (const entry of entries) {
|
|
189
|
-
if (entry.isDirectory())
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
209
|
+
if (!entry.isDirectory()) continue;
|
|
210
|
+
const packageJsonPath = join(basePath, entry.name, "package.json");
|
|
211
|
+
if (existsSync(packageJsonPath)) {
|
|
212
|
+
results.push(join(basePath, entry.name));
|
|
194
213
|
}
|
|
195
214
|
}
|
|
196
215
|
} catch {
|
|
@@ -212,35 +231,92 @@ var hasReactDependency = (projectPath) => {
|
|
|
212
231
|
return false;
|
|
213
232
|
}
|
|
214
233
|
};
|
|
234
|
+
var buildReactProject = (projectPath) => {
|
|
235
|
+
const framework = detectFramework(projectPath);
|
|
236
|
+
const hasReact = hasReactDependency(projectPath);
|
|
237
|
+
if (!hasReact && framework === "unknown") return null;
|
|
238
|
+
let name = basename(projectPath);
|
|
239
|
+
const packageJsonPath = join(projectPath, "package.json");
|
|
240
|
+
try {
|
|
241
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
242
|
+
name = packageJson.name || name;
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
return { name, path: projectPath, framework, hasReact };
|
|
246
|
+
};
|
|
215
247
|
var findWorkspaceProjects = (projectRoot) => {
|
|
216
248
|
const patterns = getWorkspacePatterns(projectRoot);
|
|
217
249
|
const projects = [];
|
|
218
250
|
for (const pattern of patterns) {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
251
|
+
for (const projectPath of expandWorkspacePattern(projectRoot, pattern)) {
|
|
252
|
+
const project = buildReactProject(projectPath);
|
|
253
|
+
if (project) projects.push(project);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return projects;
|
|
257
|
+
};
|
|
258
|
+
var ALWAYS_IGNORED_DIRECTORIES = [
|
|
259
|
+
"node_modules",
|
|
260
|
+
".git",
|
|
261
|
+
".next",
|
|
262
|
+
".cache",
|
|
263
|
+
".turbo",
|
|
264
|
+
"dist",
|
|
265
|
+
"build",
|
|
266
|
+
"coverage",
|
|
267
|
+
"test-results"
|
|
268
|
+
];
|
|
269
|
+
var loadGitignore = (projectRoot) => {
|
|
270
|
+
const ignorer = ignore().add(ALWAYS_IGNORED_DIRECTORIES);
|
|
271
|
+
const gitignorePath = join(projectRoot, ".gitignore");
|
|
272
|
+
if (existsSync(gitignorePath)) {
|
|
273
|
+
try {
|
|
274
|
+
ignorer.add(readFileSync(gitignorePath, "utf-8"));
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return ignorer;
|
|
279
|
+
};
|
|
280
|
+
var scanDirectoryForProjects = (rootDirectory, ignorer, maxDepth, currentDepth = 0) => {
|
|
281
|
+
if (currentDepth >= maxDepth) return [];
|
|
282
|
+
if (!existsSync(rootDirectory)) return [];
|
|
283
|
+
const projects = [];
|
|
284
|
+
try {
|
|
285
|
+
const entries = readdirSync(rootDirectory, { withFileTypes: true });
|
|
286
|
+
for (const entry of entries) {
|
|
287
|
+
if (!entry.isDirectory()) continue;
|
|
288
|
+
if (ignorer.ignores(entry.name)) continue;
|
|
289
|
+
const entryPath = join(rootDirectory, entry.name);
|
|
290
|
+
const hasPackageJson = existsSync(join(entryPath, "package.json"));
|
|
291
|
+
if (hasPackageJson) {
|
|
292
|
+
const project = buildReactProject(entryPath);
|
|
293
|
+
if (project) {
|
|
294
|
+
projects.push(project);
|
|
295
|
+
continue;
|
|
232
296
|
}
|
|
233
|
-
projects.push({
|
|
234
|
-
name,
|
|
235
|
-
path: projectPath,
|
|
236
|
-
framework,
|
|
237
|
-
hasReact
|
|
238
|
-
});
|
|
239
297
|
}
|
|
298
|
+
projects.push(
|
|
299
|
+
...scanDirectoryForProjects(
|
|
300
|
+
entryPath,
|
|
301
|
+
ignorer,
|
|
302
|
+
maxDepth,
|
|
303
|
+
currentDepth + 1
|
|
304
|
+
)
|
|
305
|
+
);
|
|
240
306
|
}
|
|
307
|
+
} catch {
|
|
308
|
+
return projects;
|
|
241
309
|
}
|
|
242
310
|
return projects;
|
|
243
311
|
};
|
|
312
|
+
var MAX_SCAN_DEPTH = 2;
|
|
313
|
+
var findReactProjects = (projectRoot) => {
|
|
314
|
+
if (detectMonorepo(projectRoot)) {
|
|
315
|
+
return findWorkspaceProjects(projectRoot);
|
|
316
|
+
}
|
|
317
|
+
const ignorer = loadGitignore(projectRoot);
|
|
318
|
+
return scanDirectoryForProjects(projectRoot, ignorer, MAX_SCAN_DEPTH);
|
|
319
|
+
};
|
|
244
320
|
var hasReactGrabInFile = (filePath) => {
|
|
245
321
|
if (!existsSync(filePath)) return false;
|
|
246
322
|
try {
|
|
@@ -306,6 +382,8 @@ var AGENT_PACKAGES = [
|
|
|
306
382
|
"@react-grab/gemini",
|
|
307
383
|
"@react-grab/amp",
|
|
308
384
|
"@react-grab/ami",
|
|
385
|
+
"@react-grab/droid",
|
|
386
|
+
"@react-grab/copilot",
|
|
309
387
|
"@react-grab/mcp"
|
|
310
388
|
];
|
|
311
389
|
var detectUnsupportedFramework = (projectRoot) => {
|
|
@@ -633,113 +711,39 @@ var ensureDirectory = (filePath) => {
|
|
|
633
711
|
fs.mkdirSync(directory, { recursive: true });
|
|
634
712
|
}
|
|
635
713
|
};
|
|
636
|
-
var
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
714
|
+
var JSONC_FORMAT_OPTIONS = {
|
|
715
|
+
tabSize: 2,
|
|
716
|
+
insertSpaces: true
|
|
717
|
+
};
|
|
718
|
+
var upsertIntoJsonc = (filePath, content, configKey, serverName, serverConfig) => {
|
|
719
|
+
const edits = jsonc.modify(
|
|
720
|
+
content,
|
|
721
|
+
[configKey, serverName],
|
|
722
|
+
serverConfig,
|
|
723
|
+
{ formattingOptions: JSONC_FORMAT_OPTIONS }
|
|
642
724
|
);
|
|
643
|
-
|
|
644
|
-
const keyPattern = new RegExp(`"${escapedConfigKey}"\\s*:\\s*\\{`);
|
|
645
|
-
const keyMatch = keyPattern.exec(content);
|
|
646
|
-
if (keyMatch) {
|
|
647
|
-
const insertPosition = keyMatch.index + keyMatch[0].length;
|
|
648
|
-
const entry = `
|
|
649
|
-
"${serverName}": ${serverJson},`;
|
|
650
|
-
fs.writeFileSync(
|
|
651
|
-
filePath,
|
|
652
|
-
content.slice(0, insertPosition) + entry + content.slice(insertPosition)
|
|
653
|
-
);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
const lastBrace = content.lastIndexOf("}");
|
|
657
|
-
if (lastBrace === -1) return;
|
|
658
|
-
const beforeBrace = content.slice(0, lastBrace).trimEnd();
|
|
659
|
-
const withoutComments = beforeBrace.replace(/\/\/.*$/, "").trimEnd();
|
|
660
|
-
const lastChar = withoutComments[withoutComments.length - 1];
|
|
661
|
-
const needsComma = lastChar !== void 0 && lastChar !== "{" && lastChar !== ",";
|
|
662
|
-
const section = `${needsComma ? "," : ""}
|
|
663
|
-
"${configKey}": {
|
|
664
|
-
"${serverName}": ${serverJson}
|
|
665
|
-
}`;
|
|
666
|
-
fs.writeFileSync(filePath, beforeBrace + section + "\n}\n");
|
|
725
|
+
fs.writeFileSync(filePath, jsonc.applyEdits(content, edits));
|
|
667
726
|
};
|
|
668
727
|
var installJsonClient = (client) => {
|
|
669
728
|
ensureDirectory(client.configPath);
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
try {
|
|
679
|
-
const config = JSON.parse(content);
|
|
680
|
-
const servers = config[client.configKey] ?? {};
|
|
681
|
-
servers[SERVER_NAME] = client.serverConfig;
|
|
682
|
-
config[client.configKey] = servers;
|
|
683
|
-
fs.writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
|
|
684
|
-
} catch {
|
|
685
|
-
insertIntoJsonc(
|
|
686
|
-
client.configPath,
|
|
687
|
-
content,
|
|
688
|
-
client.configKey,
|
|
689
|
-
SERVER_NAME,
|
|
690
|
-
client.serverConfig
|
|
691
|
-
);
|
|
692
|
-
}
|
|
693
|
-
};
|
|
694
|
-
var buildTomlSection = (configKey, serverConfig) => {
|
|
695
|
-
const lines = [`[${configKey}.${SERVER_NAME}]`];
|
|
696
|
-
for (const [key, value] of Object.entries(serverConfig)) {
|
|
697
|
-
if (typeof value === "string") {
|
|
698
|
-
lines.push(`${key} = "${value}"`);
|
|
699
|
-
} else if (Array.isArray(value)) {
|
|
700
|
-
const items = value.map((item) => `"${item}"`).join(", ");
|
|
701
|
-
lines.push(`${key} = [${items}]`);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
return lines.join("\n");
|
|
729
|
+
const content = fs.existsSync(client.configPath) ? fs.readFileSync(client.configPath, "utf8") : "{}";
|
|
730
|
+
upsertIntoJsonc(
|
|
731
|
+
client.configPath,
|
|
732
|
+
content,
|
|
733
|
+
client.configKey,
|
|
734
|
+
SERVER_NAME,
|
|
735
|
+
client.serverConfig
|
|
736
|
+
);
|
|
705
737
|
};
|
|
706
738
|
var installTomlClient = (client) => {
|
|
707
739
|
ensureDirectory(client.configPath);
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (!content.includes(sectionHeader)) {
|
|
716
|
-
fs.writeFileSync(
|
|
717
|
-
client.configPath,
|
|
718
|
-
content.trimEnd() + "\n\n" + newSection + "\n"
|
|
719
|
-
);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
const lines = content.split("\n");
|
|
723
|
-
const resultLines = [];
|
|
724
|
-
let isInsideOurSection = false;
|
|
725
|
-
let didInsertReplacement = false;
|
|
726
|
-
for (const line of lines) {
|
|
727
|
-
if (line.trim() === sectionHeader) {
|
|
728
|
-
isInsideOurSection = true;
|
|
729
|
-
if (!didInsertReplacement) {
|
|
730
|
-
resultLines.push(newSection);
|
|
731
|
-
didInsertReplacement = true;
|
|
732
|
-
}
|
|
733
|
-
continue;
|
|
734
|
-
}
|
|
735
|
-
if (isInsideOurSection && line.startsWith("[")) {
|
|
736
|
-
isInsideOurSection = false;
|
|
737
|
-
}
|
|
738
|
-
if (!isInsideOurSection) {
|
|
739
|
-
resultLines.push(line);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
fs.writeFileSync(client.configPath, resultLines.join("\n"));
|
|
740
|
+
const existingConfig = fs.existsSync(
|
|
741
|
+
client.configPath
|
|
742
|
+
) ? TOML.parse(fs.readFileSync(client.configPath, "utf8")) : {};
|
|
743
|
+
const serverSection = existingConfig[client.configKey] ?? {};
|
|
744
|
+
serverSection[SERVER_NAME] = client.serverConfig;
|
|
745
|
+
existingConfig[client.configKey] = serverSection;
|
|
746
|
+
fs.writeFileSync(client.configPath, TOML.stringify(existingConfig));
|
|
743
747
|
};
|
|
744
748
|
var getMcpClientNames = () => getClients().map((client) => client.name);
|
|
745
749
|
var installMcpServers = (selectedClients) => {
|
|
@@ -841,7 +845,8 @@ var AGENTS = [
|
|
|
841
845
|
"gemini",
|
|
842
846
|
"amp",
|
|
843
847
|
"ami",
|
|
844
|
-
"droid"
|
|
848
|
+
"droid",
|
|
849
|
+
"copilot"
|
|
845
850
|
];
|
|
846
851
|
var AGENT_NAMES = {
|
|
847
852
|
"claude-code": "Claude Code",
|
|
@@ -851,7 +856,8 @@ var AGENT_NAMES = {
|
|
|
851
856
|
gemini: "Gemini",
|
|
852
857
|
amp: "Amp",
|
|
853
858
|
ami: "Ami",
|
|
854
|
-
droid: "Droid"
|
|
859
|
+
droid: "Droid",
|
|
860
|
+
copilot: "Copilot"
|
|
855
861
|
};
|
|
856
862
|
var getAgentDisplayName = (agent) => {
|
|
857
863
|
if (agent === "mcp") return "MCP";
|
|
@@ -1586,7 +1592,9 @@ var AGENT_PACKAGES2 = {
|
|
|
1586
1592
|
opencode: "@react-grab/opencode@latest",
|
|
1587
1593
|
codex: "@react-grab/codex@latest",
|
|
1588
1594
|
gemini: "@react-grab/gemini@latest",
|
|
1589
|
-
amp: "@react-grab/amp@latest"
|
|
1595
|
+
amp: "@react-grab/amp@latest",
|
|
1596
|
+
droid: "@react-grab/droid@latest",
|
|
1597
|
+
copilot: "@react-grab/copilot@latest"
|
|
1590
1598
|
};
|
|
1591
1599
|
var getAgentPrefix = (agent, packageManager) => {
|
|
1592
1600
|
const agentPackage = AGENT_PACKAGES2[agent];
|
|
@@ -2182,7 +2190,7 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
|
|
|
2182
2190
|
};
|
|
2183
2191
|
|
|
2184
2192
|
// src/commands/add.ts
|
|
2185
|
-
var VERSION = "0.1.
|
|
2193
|
+
var VERSION = "0.1.16";
|
|
2186
2194
|
var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
|
|
2187
2195
|
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(
|
|
2188
2196
|
"-c, --cwd <cwd>",
|
|
@@ -2195,7 +2203,7 @@ var add = new Command().name("add").alias("install").description("connect React
|
|
|
2195
2203
|
console.log();
|
|
2196
2204
|
try {
|
|
2197
2205
|
const cwd = opts.cwd;
|
|
2198
|
-
const isNonInteractive = opts.yes;
|
|
2206
|
+
const isNonInteractive = detectNonInteractive(opts.yes);
|
|
2199
2207
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
2200
2208
|
const projectInfo = await detectProject(cwd);
|
|
2201
2209
|
if (!projectInfo.hasReactGrab) {
|
|
@@ -2543,7 +2551,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
|
|
|
2543
2551
|
var MAX_CONTEXT_LINES = 50;
|
|
2544
2552
|
|
|
2545
2553
|
// src/commands/configure.ts
|
|
2546
|
-
var VERSION2 = "0.1.
|
|
2554
|
+
var VERSION2 = "0.1.16";
|
|
2547
2555
|
var isMac = process.platform === "darwin";
|
|
2548
2556
|
var META_LABEL = isMac ? "Cmd" : "Win";
|
|
2549
2557
|
var ALT_LABEL = isMac ? "Option" : "Alt";
|
|
@@ -3099,7 +3107,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
|
|
|
3099
3107
|
};
|
|
3100
3108
|
|
|
3101
3109
|
// src/commands/init.ts
|
|
3102
|
-
var VERSION3 = "0.1.
|
|
3110
|
+
var VERSION3 = "0.1.16";
|
|
3103
3111
|
var REPORT_URL = "https://react-grab.com/api/report-cli";
|
|
3104
3112
|
var DOCS_URL = "https://github.com/aidenybai/react-grab";
|
|
3105
3113
|
var reportToCli = (type, config, error) => {
|
|
@@ -3136,6 +3144,34 @@ var UNSUPPORTED_FRAMEWORK_NAMES = {
|
|
|
3136
3144
|
gatsby: "Gatsby"
|
|
3137
3145
|
};
|
|
3138
3146
|
var getAgentName = getAgentDisplayName;
|
|
3147
|
+
var sortProjectsByFramework = (projects) => [...projects].sort((projectA, projectB) => {
|
|
3148
|
+
if (projectA.framework === "unknown" && projectB.framework !== "unknown")
|
|
3149
|
+
return 1;
|
|
3150
|
+
if (projectA.framework !== "unknown" && projectB.framework === "unknown")
|
|
3151
|
+
return -1;
|
|
3152
|
+
return 0;
|
|
3153
|
+
});
|
|
3154
|
+
var printSubprojects = (searchRoot, sortedProjects) => {
|
|
3155
|
+
logger.break();
|
|
3156
|
+
logger.log("Found the following projects:");
|
|
3157
|
+
logger.break();
|
|
3158
|
+
for (const project of sortedProjects) {
|
|
3159
|
+
const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
|
|
3160
|
+
const relativePath = relative(searchRoot, project.path);
|
|
3161
|
+
logger.log(
|
|
3162
|
+
` ${highlighter.info(project.name)}${frameworkLabel} ${highlighter.dim(relativePath)}`
|
|
3163
|
+
);
|
|
3164
|
+
}
|
|
3165
|
+
logger.break();
|
|
3166
|
+
logger.log(
|
|
3167
|
+
`Re-run with ${highlighter.info("-c <path>")} to specify a project:`
|
|
3168
|
+
);
|
|
3169
|
+
logger.break();
|
|
3170
|
+
logger.log(
|
|
3171
|
+
` ${highlighter.dim("$")} npx -y grab@latest init -c ${relative(searchRoot, sortedProjects[0].path)}`
|
|
3172
|
+
);
|
|
3173
|
+
logger.break();
|
|
3174
|
+
};
|
|
3139
3175
|
var formatActivationKeyDisplay2 = (activationKey) => {
|
|
3140
3176
|
if (!activationKey) return "Default (Option/Alt)";
|
|
3141
3177
|
return activationKey.split("+").map((part) => {
|
|
@@ -3164,8 +3200,14 @@ var init = new Command().name("init").description("initialize React Grab in your
|
|
|
3164
3200
|
);
|
|
3165
3201
|
console.log();
|
|
3166
3202
|
try {
|
|
3167
|
-
const cwd = opts.cwd;
|
|
3168
|
-
const isNonInteractive = opts.yes;
|
|
3203
|
+
const cwd = resolve(opts.cwd);
|
|
3204
|
+
const isNonInteractive = detectNonInteractive(opts.yes);
|
|
3205
|
+
if (!existsSync(cwd)) {
|
|
3206
|
+
logger.break();
|
|
3207
|
+
logger.error(`Directory does not exist: ${highlighter.info(cwd)}`);
|
|
3208
|
+
logger.break();
|
|
3209
|
+
process.exit(1);
|
|
3210
|
+
}
|
|
3169
3211
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
3170
3212
|
const projectInfo = await detectProject(cwd);
|
|
3171
3213
|
const removeAgents = async (agentsToRemove2, skipInstall = false) => {
|
|
@@ -3692,58 +3734,48 @@ var init = new Command().name("init").description("initialize React Grab in your
|
|
|
3692
3734
|
process.exit(1);
|
|
3693
3735
|
}
|
|
3694
3736
|
if (projectInfo.framework === "unknown") {
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
);
|
|
3700
|
-
|
|
3701
|
-
|
|
3737
|
+
let searchRoot = cwd;
|
|
3738
|
+
let reactProjects = findReactProjects(searchRoot);
|
|
3739
|
+
if (reactProjects.length === 0 && cwd !== process.cwd()) {
|
|
3740
|
+
searchRoot = process.cwd();
|
|
3741
|
+
reactProjects = findReactProjects(searchRoot);
|
|
3742
|
+
}
|
|
3743
|
+
if (reactProjects.length > 0) {
|
|
3744
|
+
frameworkSpinner.info(
|
|
3745
|
+
`Verifying framework. Found ${reactProjects.length} project${reactProjects.length === 1 ? "" : "s"}.`
|
|
3702
3746
|
);
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
(projectA, projectB) => {
|
|
3707
|
-
if (projectA.framework === "unknown" && projectB.framework !== "unknown")
|
|
3708
|
-
return 1;
|
|
3709
|
-
if (projectA.framework !== "unknown" && projectB.framework === "unknown")
|
|
3710
|
-
return -1;
|
|
3711
|
-
return 0;
|
|
3712
|
-
}
|
|
3713
|
-
);
|
|
3714
|
-
const { selectedProject } = await prompts({
|
|
3715
|
-
type: "select",
|
|
3716
|
-
name: "selectedProject",
|
|
3717
|
-
message: "Select a project to install React Grab:",
|
|
3718
|
-
choices: [
|
|
3719
|
-
...sortedProjects.map((project) => {
|
|
3720
|
-
const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
|
|
3721
|
-
return {
|
|
3722
|
-
title: `${project.name}${frameworkLabel}`,
|
|
3723
|
-
value: project.path
|
|
3724
|
-
};
|
|
3725
|
-
}),
|
|
3726
|
-
{ title: "Skip", value: "skip" }
|
|
3727
|
-
]
|
|
3728
|
-
});
|
|
3729
|
-
if (!selectedProject || selectedProject === "skip") {
|
|
3730
|
-
logger.break();
|
|
3731
|
-
process.exit(0);
|
|
3732
|
-
}
|
|
3733
|
-
process.chdir(selectedProject);
|
|
3734
|
-
const newProjectInfo = await detectProject(selectedProject);
|
|
3735
|
-
Object.assign(projectInfo, newProjectInfo);
|
|
3736
|
-
const newFrameworkSpinner = spinner("Verifying framework.").start();
|
|
3737
|
-
newFrameworkSpinner.succeed(
|
|
3738
|
-
`Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
|
|
3739
|
-
);
|
|
3740
|
-
} else {
|
|
3741
|
-
frameworkSpinner.fail("Could not detect a supported framework.");
|
|
3742
|
-
logger.break();
|
|
3743
|
-
logger.log(`Visit ${highlighter.info(DOCS_URL)} for manual setup.`);
|
|
3744
|
-
logger.break();
|
|
3747
|
+
const sortedProjects = sortProjectsByFramework(reactProjects);
|
|
3748
|
+
if (isNonInteractive) {
|
|
3749
|
+
printSubprojects(searchRoot, sortedProjects);
|
|
3745
3750
|
process.exit(1);
|
|
3746
3751
|
}
|
|
3752
|
+
logger.break();
|
|
3753
|
+
const { selectedProject } = await prompts({
|
|
3754
|
+
type: "select",
|
|
3755
|
+
name: "selectedProject",
|
|
3756
|
+
message: "Select a project to install React Grab:",
|
|
3757
|
+
choices: [
|
|
3758
|
+
...sortedProjects.map((project) => {
|
|
3759
|
+
const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
|
|
3760
|
+
return {
|
|
3761
|
+
title: `${project.name}${frameworkLabel}`,
|
|
3762
|
+
value: project.path
|
|
3763
|
+
};
|
|
3764
|
+
}),
|
|
3765
|
+
{ title: "Skip", value: "skip" }
|
|
3766
|
+
]
|
|
3767
|
+
});
|
|
3768
|
+
if (!selectedProject || selectedProject === "skip") {
|
|
3769
|
+
logger.break();
|
|
3770
|
+
process.exit(0);
|
|
3771
|
+
}
|
|
3772
|
+
process.chdir(selectedProject);
|
|
3773
|
+
const newProjectInfo = await detectProject(selectedProject);
|
|
3774
|
+
Object.assign(projectInfo, newProjectInfo);
|
|
3775
|
+
const newFrameworkSpinner = spinner("Verifying framework.").start();
|
|
3776
|
+
newFrameworkSpinner.succeed(
|
|
3777
|
+
`Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
|
|
3778
|
+
);
|
|
3747
3779
|
} else {
|
|
3748
3780
|
frameworkSpinner.fail("Could not detect a supported framework.");
|
|
3749
3781
|
logger.break();
|
|
@@ -3933,7 +3965,7 @@ var init = new Command().name("init").description("initialize React Grab in your
|
|
|
3933
3965
|
reportToCli("error", void 0, error);
|
|
3934
3966
|
}
|
|
3935
3967
|
});
|
|
3936
|
-
var VERSION4 = "0.1.
|
|
3968
|
+
var VERSION4 = "0.1.16";
|
|
3937
3969
|
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(
|
|
3938
3970
|
"-c, --cwd <cwd>",
|
|
3939
3971
|
"working directory (defaults to current directory)",
|
|
@@ -3945,7 +3977,7 @@ var remove = new Command().name("remove").description("disconnect React Grab fro
|
|
|
3945
3977
|
console.log();
|
|
3946
3978
|
try {
|
|
3947
3979
|
const cwd = opts.cwd;
|
|
3948
|
-
const isNonInteractive = opts.yes;
|
|
3980
|
+
const isNonInteractive = detectNonInteractive(opts.yes);
|
|
3949
3981
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
3950
3982
|
const projectInfo = await detectProject(cwd);
|
|
3951
3983
|
if (!projectInfo.hasReactGrab) {
|
|
@@ -4109,7 +4141,7 @@ var remove = new Command().name("remove").description("disconnect React Grab fro
|
|
|
4109
4141
|
});
|
|
4110
4142
|
|
|
4111
4143
|
// src/cli.ts
|
|
4112
|
-
var VERSION5 = "0.1.
|
|
4144
|
+
var VERSION5 = "0.1.16";
|
|
4113
4145
|
var VERSION_API_URL = "https://www.react-grab.com/api/version";
|
|
4114
4146
|
process.on("SIGINT", () => process.exit(0));
|
|
4115
4147
|
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.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"bin": {
|
|
5
5
|
"react-grab": "./dist/cli.js"
|
|
6
6
|
},
|
|
@@ -18,9 +18,12 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@antfu/ni": "^0.23.0",
|
|
20
20
|
"commander": "^14.0.0",
|
|
21
|
+
"ignore": "^7.0.5",
|
|
22
|
+
"jsonc-parser": "^3.3.1",
|
|
21
23
|
"ora": "^8.2.0",
|
|
22
24
|
"picocolors": "^1.1.1",
|
|
23
|
-
"prompts": "^2.4.2"
|
|
25
|
+
"prompts": "^2.4.2",
|
|
26
|
+
"smol-toml": "^1.6.0"
|
|
24
27
|
},
|
|
25
28
|
"devDependencies": {
|
|
26
29
|
"@types/prompts": "^2.4.9",
|