@react-grab/cli 0.1.10 → 0.1.12

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 +382 -23
  2. package/dist/cli.js +380 -25
  3. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -8,12 +8,18 @@ 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 os = require('os');
12
+ var process2 = require('process');
11
13
  var ora = require('ora');
12
14
 
13
15
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
16
 
15
17
  var pc__default = /*#__PURE__*/_interopDefault(pc);
16
18
  var basePrompts__default = /*#__PURE__*/_interopDefault(basePrompts);
19
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
20
+ var path__default = /*#__PURE__*/_interopDefault(path);
21
+ var os__default = /*#__PURE__*/_interopDefault(os);
22
+ var process2__default = /*#__PURE__*/_interopDefault(process2);
17
23
  var ora__default = /*#__PURE__*/_interopDefault(ora);
18
24
 
19
25
  var highlighter = {
@@ -533,6 +539,301 @@ var getPackagesToUninstall = (agent) => {
533
539
  };
534
540
  var spinner = (text, options) => ora__default.default({ text, isSilent: options?.silent });
535
541
 
542
+ // src/utils/install-mcp.ts
543
+ var SERVER_NAME = "react-grab-mcp";
544
+ var PACKAGE_NAME = "@react-grab/mcp";
545
+ var getBaseDir = () => {
546
+ const homeDir = os__default.default.homedir();
547
+ if (process2__default.default.platform === "win32") {
548
+ return process2__default.default.env.APPDATA || path__default.default.join(homeDir, "AppData", "Roaming");
549
+ }
550
+ if (process2__default.default.platform === "darwin") {
551
+ return path__default.default.join(homeDir, "Library", "Application Support");
552
+ }
553
+ return process2__default.default.env.XDG_CONFIG_HOME || path__default.default.join(homeDir, ".config");
554
+ };
555
+ var getZedConfigPath = () => {
556
+ const homeDir = os__default.default.homedir();
557
+ if (process2__default.default.platform === "win32") {
558
+ const appData = process2__default.default.env.APPDATA || path__default.default.join(homeDir, "AppData", "Roaming");
559
+ return path__default.default.join(appData, "Zed", "settings.json");
560
+ }
561
+ return path__default.default.join(homeDir, ".config", "zed", "settings.json");
562
+ };
563
+ var getClients = () => {
564
+ const homeDir = os__default.default.homedir();
565
+ const baseDir = getBaseDir();
566
+ const stdioConfig = {
567
+ command: "npx",
568
+ args: ["-y", PACKAGE_NAME, "--stdio"]
569
+ };
570
+ return [
571
+ {
572
+ name: "Cursor",
573
+ configPath: path__default.default.join(homeDir, ".cursor", "mcp.json"),
574
+ configKey: "mcpServers",
575
+ format: "json",
576
+ serverConfig: stdioConfig
577
+ },
578
+ {
579
+ name: "VS Code",
580
+ configPath: path__default.default.join(baseDir, "Code", "User", "mcp.json"),
581
+ configKey: "servers",
582
+ format: "json",
583
+ serverConfig: { type: "stdio", ...stdioConfig }
584
+ },
585
+ {
586
+ name: "Claude Code",
587
+ configPath: path__default.default.join(homeDir, ".claude.json"),
588
+ configKey: "mcpServers",
589
+ format: "json",
590
+ serverConfig: stdioConfig
591
+ },
592
+ {
593
+ name: "Amp",
594
+ configPath: path__default.default.join(homeDir, ".config", "amp", "settings.json"),
595
+ configKey: "amp.mcpServers",
596
+ format: "json",
597
+ serverConfig: stdioConfig
598
+ },
599
+ {
600
+ name: "Droid",
601
+ configPath: path__default.default.join(homeDir, ".factory", "mcp.json"),
602
+ configKey: "mcpServers",
603
+ format: "json",
604
+ serverConfig: { type: "stdio", ...stdioConfig }
605
+ },
606
+ {
607
+ name: "Codex",
608
+ configPath: path__default.default.join(
609
+ process2__default.default.env.CODEX_HOME || path__default.default.join(homeDir, ".codex"),
610
+ "config.toml"
611
+ ),
612
+ configKey: "mcp_servers",
613
+ format: "toml",
614
+ serverConfig: stdioConfig
615
+ },
616
+ {
617
+ name: "Zed",
618
+ configPath: getZedConfigPath(),
619
+ configKey: "context_servers",
620
+ format: "json",
621
+ serverConfig: { source: "custom", ...stdioConfig, env: {} }
622
+ },
623
+ {
624
+ name: "Windsurf",
625
+ configPath: path__default.default.join(homeDir, ".codeium", "windsurf", "mcp_config.json"),
626
+ configKey: "mcpServers",
627
+ format: "json",
628
+ serverConfig: stdioConfig
629
+ }
630
+ ];
631
+ };
632
+ var ensureDirectory = (filePath) => {
633
+ const directory = path__default.default.dirname(filePath);
634
+ if (!fs__default.default.existsSync(directory)) {
635
+ fs__default.default.mkdirSync(directory, { recursive: true });
636
+ }
637
+ };
638
+ var indentJson = (json, baseIndent) => json.split("\n").map((line, index) => index === 0 ? line : baseIndent + line).join("\n");
639
+ var insertIntoJsonc = (filePath, content, configKey, serverName, serverConfig) => {
640
+ if (content.includes(`"${serverName}"`)) return;
641
+ const serverJson = indentJson(
642
+ JSON.stringify(serverConfig, null, 2),
643
+ " "
644
+ );
645
+ const escapedConfigKey = configKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
646
+ const keyPattern = new RegExp(`"${escapedConfigKey}"\\s*:\\s*\\{`);
647
+ const keyMatch = keyPattern.exec(content);
648
+ if (keyMatch) {
649
+ const insertPosition = keyMatch.index + keyMatch[0].length;
650
+ const entry = `
651
+ "${serverName}": ${serverJson},`;
652
+ fs__default.default.writeFileSync(
653
+ filePath,
654
+ content.slice(0, insertPosition) + entry + content.slice(insertPosition)
655
+ );
656
+ return;
657
+ }
658
+ const lastBrace = content.lastIndexOf("}");
659
+ if (lastBrace === -1) return;
660
+ const beforeBrace = content.slice(0, lastBrace).trimEnd();
661
+ const withoutComments = beforeBrace.replace(/\/\/.*$/, "").trimEnd();
662
+ const lastChar = withoutComments[withoutComments.length - 1];
663
+ const needsComma = lastChar !== void 0 && lastChar !== "{" && lastChar !== ",";
664
+ const section = `${needsComma ? "," : ""}
665
+ "${configKey}": {
666
+ "${serverName}": ${serverJson}
667
+ }`;
668
+ fs__default.default.writeFileSync(filePath, beforeBrace + section + "\n}\n");
669
+ };
670
+ var installJsonClient = (client) => {
671
+ ensureDirectory(client.configPath);
672
+ if (!fs__default.default.existsSync(client.configPath)) {
673
+ const config = {
674
+ [client.configKey]: { [SERVER_NAME]: client.serverConfig }
675
+ };
676
+ fs__default.default.writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
677
+ return;
678
+ }
679
+ const content = fs__default.default.readFileSync(client.configPath, "utf8");
680
+ try {
681
+ const config = JSON.parse(content);
682
+ const servers = config[client.configKey] ?? {};
683
+ servers[SERVER_NAME] = client.serverConfig;
684
+ config[client.configKey] = servers;
685
+ fs__default.default.writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
686
+ } catch {
687
+ insertIntoJsonc(
688
+ client.configPath,
689
+ content,
690
+ client.configKey,
691
+ SERVER_NAME,
692
+ client.serverConfig
693
+ );
694
+ }
695
+ };
696
+ var buildTomlSection = (configKey, serverConfig) => {
697
+ const lines = [`[${configKey}.${SERVER_NAME}]`];
698
+ for (const [key, value] of Object.entries(serverConfig)) {
699
+ if (typeof value === "string") {
700
+ lines.push(`${key} = "${value}"`);
701
+ } else if (Array.isArray(value)) {
702
+ const items = value.map((item) => `"${item}"`).join(", ");
703
+ lines.push(`${key} = [${items}]`);
704
+ }
705
+ }
706
+ return lines.join("\n");
707
+ };
708
+ var installTomlClient = (client) => {
709
+ ensureDirectory(client.configPath);
710
+ const sectionHeader = `[${client.configKey}.${SERVER_NAME}]`;
711
+ const newSection = buildTomlSection(client.configKey, client.serverConfig);
712
+ if (!fs__default.default.existsSync(client.configPath)) {
713
+ fs__default.default.writeFileSync(client.configPath, newSection + "\n");
714
+ return;
715
+ }
716
+ const content = fs__default.default.readFileSync(client.configPath, "utf8");
717
+ if (!content.includes(sectionHeader)) {
718
+ fs__default.default.writeFileSync(
719
+ client.configPath,
720
+ content.trimEnd() + "\n\n" + newSection + "\n"
721
+ );
722
+ return;
723
+ }
724
+ const lines = content.split("\n");
725
+ const resultLines = [];
726
+ let isInsideOurSection = false;
727
+ let didInsertReplacement = false;
728
+ for (const line of lines) {
729
+ if (line.trim() === sectionHeader) {
730
+ isInsideOurSection = true;
731
+ if (!didInsertReplacement) {
732
+ resultLines.push(newSection);
733
+ didInsertReplacement = true;
734
+ }
735
+ continue;
736
+ }
737
+ if (isInsideOurSection && line.startsWith("[")) {
738
+ isInsideOurSection = false;
739
+ }
740
+ if (!isInsideOurSection) {
741
+ resultLines.push(line);
742
+ }
743
+ }
744
+ fs__default.default.writeFileSync(client.configPath, resultLines.join("\n"));
745
+ };
746
+ var getMcpClientNames = () => getClients().map((client) => client.name);
747
+ var installMcpServers = (selectedClients) => {
748
+ const allClients = getClients();
749
+ const clients = selectedClients ? allClients.filter((client) => selectedClients.includes(client.name)) : allClients;
750
+ const results = [];
751
+ const installSpinner = spinner("Installing MCP server.").start();
752
+ for (const client of clients) {
753
+ try {
754
+ if (client.format === "toml") {
755
+ installTomlClient(client);
756
+ } else {
757
+ installJsonClient(client);
758
+ }
759
+ results.push({
760
+ client: client.name,
761
+ configPath: client.configPath,
762
+ success: true
763
+ });
764
+ } catch (error) {
765
+ const message = error instanceof Error ? error.message : String(error);
766
+ results.push({
767
+ client: client.name,
768
+ configPath: client.configPath,
769
+ success: false,
770
+ error: message
771
+ });
772
+ }
773
+ }
774
+ const successCount = results.filter((result) => result.success).length;
775
+ const failedCount = results.length - successCount;
776
+ if (failedCount > 0) {
777
+ installSpinner.warn(
778
+ `Installed to ${successCount}/${results.length} agents.`
779
+ );
780
+ } else {
781
+ installSpinner.succeed(`Installed to ${successCount} agents.`);
782
+ }
783
+ for (const result of results) {
784
+ if (result.success) {
785
+ logger.log(
786
+ ` ${highlighter.success("\u2713")} ${result.client} ${highlighter.dim("\u2192")} ${highlighter.dim(result.configPath)}`
787
+ );
788
+ } else {
789
+ logger.log(
790
+ ` ${highlighter.error("\u2717")} ${result.client} ${highlighter.dim("\u2192")} ${result.error}`
791
+ );
792
+ }
793
+ }
794
+ return results;
795
+ };
796
+ var promptConnectionMode = async () => {
797
+ const { connectionMode } = await prompts({
798
+ type: "select",
799
+ name: "connectionMode",
800
+ message: "How would you like to connect?",
801
+ choices: [
802
+ {
803
+ title: `MCP ${highlighter.dim("(recommended)")}`,
804
+ description: "Installs to all supported agents at once",
805
+ value: "mcp"
806
+ },
807
+ {
808
+ title: "Legacy",
809
+ description: "Install a per-project agent package",
810
+ value: "legacy"
811
+ }
812
+ ]
813
+ });
814
+ return connectionMode;
815
+ };
816
+ var promptMcpInstall = async () => {
817
+ const clientNames = getMcpClientNames();
818
+ const { selectedAgents } = await prompts({
819
+ type: "multiselect",
820
+ name: "selectedAgents",
821
+ message: "Select agents to install MCP server for:",
822
+ choices: clientNames.map((name) => ({
823
+ title: name,
824
+ value: name,
825
+ selected: true
826
+ }))
827
+ });
828
+ if (selectedAgents === void 0 || selectedAgents.length === 0) {
829
+ return false;
830
+ }
831
+ logger.break();
832
+ const results = installMcpServers(selectedAgents);
833
+ const hasSuccess = results.some((result) => result.success);
834
+ return hasSuccess;
835
+ };
836
+
536
837
  // src/utils/templates.ts
537
838
  var AGENTS = [
538
839
  "claude-code",
@@ -541,7 +842,8 @@ var AGENTS = [
541
842
  "codex",
542
843
  "gemini",
543
844
  "amp",
544
- "ami"
845
+ "ami",
846
+ "droid"
545
847
  ];
546
848
  var AGENT_NAMES = {
547
849
  "claude-code": "Claude Code",
@@ -550,7 +852,8 @@ var AGENT_NAMES = {
550
852
  codex: "Codex",
551
853
  gemini: "Gemini",
552
854
  amp: "Amp",
553
- ami: "Ami"
855
+ ami: "Ami",
856
+ droid: "Droid"
554
857
  };
555
858
  var NEXT_APP_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
556
859
  <Script
@@ -1842,7 +2145,10 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
1842
2145
  }
1843
2146
  const originalContent = fs.readFileSync(filePath, "utf-8");
1844
2147
  const newContent = originalContent.replace(
1845
- /\/\/[^/\s"']+(?=\/(?:@?react-grab))/g,
2148
+ /(https?:)?\/\/[^/\s"']+(?=\/(?:@?react-grab))/g,
2149
+ `//${targetCdnDomain}`
2150
+ ).replace(
2151
+ /(https?:)?\/\/[^/\s"']*react-grab[^/\s"']*\.com(?=\/script\.js)/g,
1846
2152
  `//${targetCdnDomain}`
1847
2153
  );
1848
2154
  if (newContent === originalContent) {
@@ -1863,7 +2169,7 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
1863
2169
  };
1864
2170
 
1865
2171
  // src/commands/add.ts
1866
- var VERSION = "0.1.10";
2172
+ var VERSION = "0.1.12";
1867
2173
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
1868
2174
  var add = new commander.Command().name("add").alias("install").description("add an agent integration").argument("[agent]", `agent to add (${AGENTS.join(", ")})`).option("-y, --yes", "skip confirmation prompts", false).option(
1869
2175
  "-c, --cwd <cwd>",
@@ -1956,6 +2262,25 @@ var add = new commander.Command().name("add").alias("install").description("add
1956
2262
  logger.warn(`Currently installed: ${installedNames}`);
1957
2263
  logger.break();
1958
2264
  }
2265
+ const connectionMode = await promptConnectionMode();
2266
+ if (connectionMode === void 0) {
2267
+ logger.break();
2268
+ process.exit(1);
2269
+ }
2270
+ if (connectionMode === "mcp") {
2271
+ const didInstall = await promptMcpInstall();
2272
+ if (!didInstall) {
2273
+ logger.break();
2274
+ process.exit(0);
2275
+ }
2276
+ logger.break();
2277
+ logger.log(
2278
+ `${highlighter.success("Success!")} MCP server has been configured.`
2279
+ );
2280
+ logger.log("Restart your agents to activate.");
2281
+ logger.break();
2282
+ process.exit(0);
2283
+ }
1959
2284
  const { agent } = await prompts({
1960
2285
  type: "select",
1961
2286
  name: "agent",
@@ -2199,7 +2524,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2199
2524
  var MAX_CONTEXT_LINES = 50;
2200
2525
 
2201
2526
  // src/commands/configure.ts
2202
- var VERSION2 = "0.1.10";
2527
+ var VERSION2 = "0.1.12";
2203
2528
  var isMac = process.platform === "darwin";
2204
2529
  var META_LABEL = isMac ? "Cmd" : "Win";
2205
2530
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -2755,7 +3080,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
2755
3080
  };
2756
3081
 
2757
3082
  // src/commands/init.ts
2758
- var VERSION3 = "0.1.10";
3083
+ var VERSION3 = "0.1.12";
2759
3084
  var REPORT_URL = "https://react-grab.com/api/report-cli";
2760
3085
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
2761
3086
  var reportToCli = (type, config, error) => {
@@ -3043,6 +3368,23 @@ var init = new commander.Command().name("init").description("initialize React Gr
3043
3368
  process.exit(1);
3044
3369
  }
3045
3370
  if (wantAddAgent) {
3371
+ const connectionMode = await promptConnectionMode();
3372
+ if (connectionMode === void 0) {
3373
+ logger.break();
3374
+ process.exit(1);
3375
+ }
3376
+ if (connectionMode === "mcp") {
3377
+ const didInstall = await promptMcpInstall();
3378
+ if (!didInstall) {
3379
+ logger.break();
3380
+ process.exit(0);
3381
+ }
3382
+ logger.break();
3383
+ logger.success("MCP server has been configured.");
3384
+ logger.log("Restart your agents to activate.");
3385
+ logger.break();
3386
+ process.exit(0);
3387
+ }
3046
3388
  const { agent } = await prompts({
3047
3389
  type: "select",
3048
3390
  name: "agent",
@@ -3365,24 +3707,41 @@ var init = new commander.Command().name("init").description("initialize React Gr
3365
3707
  process.exit(1);
3366
3708
  }
3367
3709
  if (wantAddAgent) {
3368
- const { agent } = await prompts({
3369
- type: "select",
3370
- name: "agent",
3371
- message: `Which ${highlighter.info("agent integration")} would you like to add?`,
3372
- choices: [
3373
- ...AGENTS.map((innerAgent) => ({
3374
- title: getAgentName(innerAgent),
3375
- value: innerAgent
3376
- })),
3377
- { title: "Skip", value: "skip" }
3378
- ]
3379
- });
3380
- if (agent === void 0) {
3710
+ const connectionMode = await promptConnectionMode();
3711
+ if (connectionMode === void 0) {
3381
3712
  logger.break();
3382
3713
  process.exit(1);
3383
3714
  }
3384
- if (agent !== "skip") {
3385
- agentIntegration = agent;
3715
+ if (connectionMode === "mcp") {
3716
+ const didInstall = await promptMcpInstall();
3717
+ if (!didInstall) {
3718
+ logger.break();
3719
+ process.exit(0);
3720
+ }
3721
+ logger.break();
3722
+ logger.success("MCP server has been configured.");
3723
+ logger.log("Continuing with React Grab installation...");
3724
+ logger.break();
3725
+ } else {
3726
+ const { agent } = await prompts({
3727
+ type: "select",
3728
+ name: "agent",
3729
+ message: `Which ${highlighter.info("agent integration")} would you like to add?`,
3730
+ choices: [
3731
+ ...AGENTS.map((innerAgent) => ({
3732
+ title: getAgentName(innerAgent),
3733
+ value: innerAgent
3734
+ })),
3735
+ { title: "Skip", value: "skip" }
3736
+ ]
3737
+ });
3738
+ if (agent === void 0) {
3739
+ logger.break();
3740
+ process.exit(1);
3741
+ }
3742
+ if (agent !== "skip") {
3743
+ agentIntegration = agent;
3744
+ }
3386
3745
  }
3387
3746
  }
3388
3747
  }
@@ -3491,7 +3850,7 @@ var init = new commander.Command().name("init").description("initialize React Gr
3491
3850
  reportToCli("error", void 0, error);
3492
3851
  }
3493
3852
  });
3494
- var VERSION4 = "0.1.10";
3853
+ var VERSION4 = "0.1.12";
3495
3854
  var remove = new commander.Command().name("remove").description("remove an agent integration").argument(
3496
3855
  "[agent]",
3497
3856
  "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami)"
@@ -3670,7 +4029,7 @@ var remove = new commander.Command().name("remove").description("remove an agent
3670
4029
  });
3671
4030
 
3672
4031
  // src/cli.ts
3673
- var VERSION5 = "0.1.10";
4032
+ var VERSION5 = "0.1.12";
3674
4033
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
3675
4034
  process.on("SIGINT", () => process.exit(0));
3676
4035
  process.on("SIGTERM", () => process.exit(0));
package/dist/cli.js CHANGED
@@ -3,9 +3,11 @@ 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 { readFileSync, existsSync, writeFileSync, accessSync, constants, readdirSync } from 'fs';
7
- import { join, basename } from 'path';
6
+ import fs, { readFileSync, existsSync, writeFileSync, accessSync, constants, readdirSync } from 'fs';
7
+ import path, { join, basename } from 'path';
8
8
  import { detect } from '@antfu/ni';
9
+ import os from 'os';
10
+ import process2 from 'process';
9
11
  import ora from 'ora';
10
12
 
11
13
  var highlighter = {
@@ -525,6 +527,301 @@ var getPackagesToUninstall = (agent) => {
525
527
  };
526
528
  var spinner = (text, options) => ora({ text, isSilent: options?.silent });
527
529
 
530
+ // src/utils/install-mcp.ts
531
+ var SERVER_NAME = "react-grab-mcp";
532
+ var PACKAGE_NAME = "@react-grab/mcp";
533
+ var getBaseDir = () => {
534
+ const homeDir = os.homedir();
535
+ if (process2.platform === "win32") {
536
+ return process2.env.APPDATA || path.join(homeDir, "AppData", "Roaming");
537
+ }
538
+ if (process2.platform === "darwin") {
539
+ return path.join(homeDir, "Library", "Application Support");
540
+ }
541
+ return process2.env.XDG_CONFIG_HOME || path.join(homeDir, ".config");
542
+ };
543
+ var getZedConfigPath = () => {
544
+ const homeDir = os.homedir();
545
+ if (process2.platform === "win32") {
546
+ const appData = process2.env.APPDATA || path.join(homeDir, "AppData", "Roaming");
547
+ return path.join(appData, "Zed", "settings.json");
548
+ }
549
+ return path.join(homeDir, ".config", "zed", "settings.json");
550
+ };
551
+ var getClients = () => {
552
+ const homeDir = os.homedir();
553
+ const baseDir = getBaseDir();
554
+ const stdioConfig = {
555
+ command: "npx",
556
+ args: ["-y", PACKAGE_NAME, "--stdio"]
557
+ };
558
+ return [
559
+ {
560
+ name: "Cursor",
561
+ configPath: path.join(homeDir, ".cursor", "mcp.json"),
562
+ configKey: "mcpServers",
563
+ format: "json",
564
+ serverConfig: stdioConfig
565
+ },
566
+ {
567
+ name: "VS Code",
568
+ configPath: path.join(baseDir, "Code", "User", "mcp.json"),
569
+ configKey: "servers",
570
+ format: "json",
571
+ serverConfig: { type: "stdio", ...stdioConfig }
572
+ },
573
+ {
574
+ name: "Claude Code",
575
+ configPath: path.join(homeDir, ".claude.json"),
576
+ configKey: "mcpServers",
577
+ format: "json",
578
+ serverConfig: stdioConfig
579
+ },
580
+ {
581
+ name: "Amp",
582
+ configPath: path.join(homeDir, ".config", "amp", "settings.json"),
583
+ configKey: "amp.mcpServers",
584
+ format: "json",
585
+ serverConfig: stdioConfig
586
+ },
587
+ {
588
+ name: "Droid",
589
+ configPath: path.join(homeDir, ".factory", "mcp.json"),
590
+ configKey: "mcpServers",
591
+ format: "json",
592
+ serverConfig: { type: "stdio", ...stdioConfig }
593
+ },
594
+ {
595
+ name: "Codex",
596
+ configPath: path.join(
597
+ process2.env.CODEX_HOME || path.join(homeDir, ".codex"),
598
+ "config.toml"
599
+ ),
600
+ configKey: "mcp_servers",
601
+ format: "toml",
602
+ serverConfig: stdioConfig
603
+ },
604
+ {
605
+ name: "Zed",
606
+ configPath: getZedConfigPath(),
607
+ configKey: "context_servers",
608
+ format: "json",
609
+ serverConfig: { source: "custom", ...stdioConfig, env: {} }
610
+ },
611
+ {
612
+ name: "Windsurf",
613
+ configPath: path.join(homeDir, ".codeium", "windsurf", "mcp_config.json"),
614
+ configKey: "mcpServers",
615
+ format: "json",
616
+ serverConfig: stdioConfig
617
+ }
618
+ ];
619
+ };
620
+ var ensureDirectory = (filePath) => {
621
+ const directory = path.dirname(filePath);
622
+ if (!fs.existsSync(directory)) {
623
+ fs.mkdirSync(directory, { recursive: true });
624
+ }
625
+ };
626
+ var indentJson = (json, baseIndent) => json.split("\n").map((line, index) => index === 0 ? line : baseIndent + line).join("\n");
627
+ var insertIntoJsonc = (filePath, content, configKey, serverName, serverConfig) => {
628
+ if (content.includes(`"${serverName}"`)) return;
629
+ const serverJson = indentJson(
630
+ JSON.stringify(serverConfig, null, 2),
631
+ " "
632
+ );
633
+ const escapedConfigKey = configKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
634
+ const keyPattern = new RegExp(`"${escapedConfigKey}"\\s*:\\s*\\{`);
635
+ const keyMatch = keyPattern.exec(content);
636
+ if (keyMatch) {
637
+ const insertPosition = keyMatch.index + keyMatch[0].length;
638
+ const entry = `
639
+ "${serverName}": ${serverJson},`;
640
+ fs.writeFileSync(
641
+ filePath,
642
+ content.slice(0, insertPosition) + entry + content.slice(insertPosition)
643
+ );
644
+ return;
645
+ }
646
+ const lastBrace = content.lastIndexOf("}");
647
+ if (lastBrace === -1) return;
648
+ const beforeBrace = content.slice(0, lastBrace).trimEnd();
649
+ const withoutComments = beforeBrace.replace(/\/\/.*$/, "").trimEnd();
650
+ const lastChar = withoutComments[withoutComments.length - 1];
651
+ const needsComma = lastChar !== void 0 && lastChar !== "{" && lastChar !== ",";
652
+ const section = `${needsComma ? "," : ""}
653
+ "${configKey}": {
654
+ "${serverName}": ${serverJson}
655
+ }`;
656
+ fs.writeFileSync(filePath, beforeBrace + section + "\n}\n");
657
+ };
658
+ var installJsonClient = (client) => {
659
+ ensureDirectory(client.configPath);
660
+ if (!fs.existsSync(client.configPath)) {
661
+ const config = {
662
+ [client.configKey]: { [SERVER_NAME]: client.serverConfig }
663
+ };
664
+ fs.writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
665
+ return;
666
+ }
667
+ const content = fs.readFileSync(client.configPath, "utf8");
668
+ try {
669
+ const config = JSON.parse(content);
670
+ const servers = config[client.configKey] ?? {};
671
+ servers[SERVER_NAME] = client.serverConfig;
672
+ config[client.configKey] = servers;
673
+ fs.writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
674
+ } catch {
675
+ insertIntoJsonc(
676
+ client.configPath,
677
+ content,
678
+ client.configKey,
679
+ SERVER_NAME,
680
+ client.serverConfig
681
+ );
682
+ }
683
+ };
684
+ var buildTomlSection = (configKey, serverConfig) => {
685
+ const lines = [`[${configKey}.${SERVER_NAME}]`];
686
+ for (const [key, value] of Object.entries(serverConfig)) {
687
+ if (typeof value === "string") {
688
+ lines.push(`${key} = "${value}"`);
689
+ } else if (Array.isArray(value)) {
690
+ const items = value.map((item) => `"${item}"`).join(", ");
691
+ lines.push(`${key} = [${items}]`);
692
+ }
693
+ }
694
+ return lines.join("\n");
695
+ };
696
+ var installTomlClient = (client) => {
697
+ ensureDirectory(client.configPath);
698
+ const sectionHeader = `[${client.configKey}.${SERVER_NAME}]`;
699
+ const newSection = buildTomlSection(client.configKey, client.serverConfig);
700
+ if (!fs.existsSync(client.configPath)) {
701
+ fs.writeFileSync(client.configPath, newSection + "\n");
702
+ return;
703
+ }
704
+ const content = fs.readFileSync(client.configPath, "utf8");
705
+ if (!content.includes(sectionHeader)) {
706
+ fs.writeFileSync(
707
+ client.configPath,
708
+ content.trimEnd() + "\n\n" + newSection + "\n"
709
+ );
710
+ return;
711
+ }
712
+ const lines = content.split("\n");
713
+ const resultLines = [];
714
+ let isInsideOurSection = false;
715
+ let didInsertReplacement = false;
716
+ for (const line of lines) {
717
+ if (line.trim() === sectionHeader) {
718
+ isInsideOurSection = true;
719
+ if (!didInsertReplacement) {
720
+ resultLines.push(newSection);
721
+ didInsertReplacement = true;
722
+ }
723
+ continue;
724
+ }
725
+ if (isInsideOurSection && line.startsWith("[")) {
726
+ isInsideOurSection = false;
727
+ }
728
+ if (!isInsideOurSection) {
729
+ resultLines.push(line);
730
+ }
731
+ }
732
+ fs.writeFileSync(client.configPath, resultLines.join("\n"));
733
+ };
734
+ var getMcpClientNames = () => getClients().map((client) => client.name);
735
+ var installMcpServers = (selectedClients) => {
736
+ const allClients = getClients();
737
+ const clients = selectedClients ? allClients.filter((client) => selectedClients.includes(client.name)) : allClients;
738
+ const results = [];
739
+ const installSpinner = spinner("Installing MCP server.").start();
740
+ for (const client of clients) {
741
+ try {
742
+ if (client.format === "toml") {
743
+ installTomlClient(client);
744
+ } else {
745
+ installJsonClient(client);
746
+ }
747
+ results.push({
748
+ client: client.name,
749
+ configPath: client.configPath,
750
+ success: true
751
+ });
752
+ } catch (error) {
753
+ const message = error instanceof Error ? error.message : String(error);
754
+ results.push({
755
+ client: client.name,
756
+ configPath: client.configPath,
757
+ success: false,
758
+ error: message
759
+ });
760
+ }
761
+ }
762
+ const successCount = results.filter((result) => result.success).length;
763
+ const failedCount = results.length - successCount;
764
+ if (failedCount > 0) {
765
+ installSpinner.warn(
766
+ `Installed to ${successCount}/${results.length} agents.`
767
+ );
768
+ } else {
769
+ installSpinner.succeed(`Installed to ${successCount} agents.`);
770
+ }
771
+ for (const result of results) {
772
+ if (result.success) {
773
+ logger.log(
774
+ ` ${highlighter.success("\u2713")} ${result.client} ${highlighter.dim("\u2192")} ${highlighter.dim(result.configPath)}`
775
+ );
776
+ } else {
777
+ logger.log(
778
+ ` ${highlighter.error("\u2717")} ${result.client} ${highlighter.dim("\u2192")} ${result.error}`
779
+ );
780
+ }
781
+ }
782
+ return results;
783
+ };
784
+ var promptConnectionMode = async () => {
785
+ const { connectionMode } = await prompts({
786
+ type: "select",
787
+ name: "connectionMode",
788
+ message: "How would you like to connect?",
789
+ choices: [
790
+ {
791
+ title: `MCP ${highlighter.dim("(recommended)")}`,
792
+ description: "Installs to all supported agents at once",
793
+ value: "mcp"
794
+ },
795
+ {
796
+ title: "Legacy",
797
+ description: "Install a per-project agent package",
798
+ value: "legacy"
799
+ }
800
+ ]
801
+ });
802
+ return connectionMode;
803
+ };
804
+ var promptMcpInstall = async () => {
805
+ const clientNames = getMcpClientNames();
806
+ const { selectedAgents } = await prompts({
807
+ type: "multiselect",
808
+ name: "selectedAgents",
809
+ message: "Select agents to install MCP server for:",
810
+ choices: clientNames.map((name) => ({
811
+ title: name,
812
+ value: name,
813
+ selected: true
814
+ }))
815
+ });
816
+ if (selectedAgents === void 0 || selectedAgents.length === 0) {
817
+ return false;
818
+ }
819
+ logger.break();
820
+ const results = installMcpServers(selectedAgents);
821
+ const hasSuccess = results.some((result) => result.success);
822
+ return hasSuccess;
823
+ };
824
+
528
825
  // src/utils/templates.ts
529
826
  var AGENTS = [
530
827
  "claude-code",
@@ -533,7 +830,8 @@ var AGENTS = [
533
830
  "codex",
534
831
  "gemini",
535
832
  "amp",
536
- "ami"
833
+ "ami",
834
+ "droid"
537
835
  ];
538
836
  var AGENT_NAMES = {
539
837
  "claude-code": "Claude Code",
@@ -542,7 +840,8 @@ var AGENT_NAMES = {
542
840
  codex: "Codex",
543
841
  gemini: "Gemini",
544
842
  amp: "Amp",
545
- ami: "Ami"
843
+ ami: "Ami",
844
+ droid: "Droid"
546
845
  };
547
846
  var NEXT_APP_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
548
847
  <Script
@@ -1834,7 +2133,10 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
1834
2133
  }
1835
2134
  const originalContent = readFileSync(filePath, "utf-8");
1836
2135
  const newContent = originalContent.replace(
1837
- /\/\/[^/\s"']+(?=\/(?:@?react-grab))/g,
2136
+ /(https?:)?\/\/[^/\s"']+(?=\/(?:@?react-grab))/g,
2137
+ `//${targetCdnDomain}`
2138
+ ).replace(
2139
+ /(https?:)?\/\/[^/\s"']*react-grab[^/\s"']*\.com(?=\/script\.js)/g,
1838
2140
  `//${targetCdnDomain}`
1839
2141
  );
1840
2142
  if (newContent === originalContent) {
@@ -1855,7 +2157,7 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
1855
2157
  };
1856
2158
 
1857
2159
  // src/commands/add.ts
1858
- var VERSION = "0.1.10";
2160
+ var VERSION = "0.1.12";
1859
2161
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
1860
2162
  var add = new Command().name("add").alias("install").description("add an agent integration").argument("[agent]", `agent to add (${AGENTS.join(", ")})`).option("-y, --yes", "skip confirmation prompts", false).option(
1861
2163
  "-c, --cwd <cwd>",
@@ -1948,6 +2250,25 @@ var add = new Command().name("add").alias("install").description("add an agent i
1948
2250
  logger.warn(`Currently installed: ${installedNames}`);
1949
2251
  logger.break();
1950
2252
  }
2253
+ const connectionMode = await promptConnectionMode();
2254
+ if (connectionMode === void 0) {
2255
+ logger.break();
2256
+ process.exit(1);
2257
+ }
2258
+ if (connectionMode === "mcp") {
2259
+ const didInstall = await promptMcpInstall();
2260
+ if (!didInstall) {
2261
+ logger.break();
2262
+ process.exit(0);
2263
+ }
2264
+ logger.break();
2265
+ logger.log(
2266
+ `${highlighter.success("Success!")} MCP server has been configured.`
2267
+ );
2268
+ logger.log("Restart your agents to activate.");
2269
+ logger.break();
2270
+ process.exit(0);
2271
+ }
1951
2272
  const { agent } = await prompts({
1952
2273
  type: "select",
1953
2274
  name: "agent",
@@ -2191,7 +2512,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2191
2512
  var MAX_CONTEXT_LINES = 50;
2192
2513
 
2193
2514
  // src/commands/configure.ts
2194
- var VERSION2 = "0.1.10";
2515
+ var VERSION2 = "0.1.12";
2195
2516
  var isMac = process.platform === "darwin";
2196
2517
  var META_LABEL = isMac ? "Cmd" : "Win";
2197
2518
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -2747,7 +3068,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
2747
3068
  };
2748
3069
 
2749
3070
  // src/commands/init.ts
2750
- var VERSION3 = "0.1.10";
3071
+ var VERSION3 = "0.1.12";
2751
3072
  var REPORT_URL = "https://react-grab.com/api/report-cli";
2752
3073
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
2753
3074
  var reportToCli = (type, config, error) => {
@@ -3035,6 +3356,23 @@ var init = new Command().name("init").description("initialize React Grab in your
3035
3356
  process.exit(1);
3036
3357
  }
3037
3358
  if (wantAddAgent) {
3359
+ const connectionMode = await promptConnectionMode();
3360
+ if (connectionMode === void 0) {
3361
+ logger.break();
3362
+ process.exit(1);
3363
+ }
3364
+ if (connectionMode === "mcp") {
3365
+ const didInstall = await promptMcpInstall();
3366
+ if (!didInstall) {
3367
+ logger.break();
3368
+ process.exit(0);
3369
+ }
3370
+ logger.break();
3371
+ logger.success("MCP server has been configured.");
3372
+ logger.log("Restart your agents to activate.");
3373
+ logger.break();
3374
+ process.exit(0);
3375
+ }
3038
3376
  const { agent } = await prompts({
3039
3377
  type: "select",
3040
3378
  name: "agent",
@@ -3357,24 +3695,41 @@ var init = new Command().name("init").description("initialize React Grab in your
3357
3695
  process.exit(1);
3358
3696
  }
3359
3697
  if (wantAddAgent) {
3360
- const { agent } = await prompts({
3361
- type: "select",
3362
- name: "agent",
3363
- message: `Which ${highlighter.info("agent integration")} would you like to add?`,
3364
- choices: [
3365
- ...AGENTS.map((innerAgent) => ({
3366
- title: getAgentName(innerAgent),
3367
- value: innerAgent
3368
- })),
3369
- { title: "Skip", value: "skip" }
3370
- ]
3371
- });
3372
- if (agent === void 0) {
3698
+ const connectionMode = await promptConnectionMode();
3699
+ if (connectionMode === void 0) {
3373
3700
  logger.break();
3374
3701
  process.exit(1);
3375
3702
  }
3376
- if (agent !== "skip") {
3377
- agentIntegration = agent;
3703
+ if (connectionMode === "mcp") {
3704
+ const didInstall = await promptMcpInstall();
3705
+ if (!didInstall) {
3706
+ logger.break();
3707
+ process.exit(0);
3708
+ }
3709
+ logger.break();
3710
+ logger.success("MCP server has been configured.");
3711
+ logger.log("Continuing with React Grab installation...");
3712
+ logger.break();
3713
+ } else {
3714
+ const { agent } = await prompts({
3715
+ type: "select",
3716
+ name: "agent",
3717
+ message: `Which ${highlighter.info("agent integration")} would you like to add?`,
3718
+ choices: [
3719
+ ...AGENTS.map((innerAgent) => ({
3720
+ title: getAgentName(innerAgent),
3721
+ value: innerAgent
3722
+ })),
3723
+ { title: "Skip", value: "skip" }
3724
+ ]
3725
+ });
3726
+ if (agent === void 0) {
3727
+ logger.break();
3728
+ process.exit(1);
3729
+ }
3730
+ if (agent !== "skip") {
3731
+ agentIntegration = agent;
3732
+ }
3378
3733
  }
3379
3734
  }
3380
3735
  }
@@ -3483,7 +3838,7 @@ var init = new Command().name("init").description("initialize React Grab in your
3483
3838
  reportToCli("error", void 0, error);
3484
3839
  }
3485
3840
  });
3486
- var VERSION4 = "0.1.10";
3841
+ var VERSION4 = "0.1.12";
3487
3842
  var remove = new Command().name("remove").description("remove an agent integration").argument(
3488
3843
  "[agent]",
3489
3844
  "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami)"
@@ -3662,7 +4017,7 @@ var remove = new Command().name("remove").description("remove an agent integrati
3662
4017
  });
3663
4018
 
3664
4019
  // src/cli.ts
3665
- var VERSION5 = "0.1.10";
4020
+ var VERSION5 = "0.1.12";
3666
4021
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
3667
4022
  process.on("SIGINT", () => process.exit(0));
3668
4023
  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.10",
3
+ "version": "0.1.12",
4
4
  "bin": {
5
5
  "react-grab": "./dist/cli.js"
6
6
  },