@react-grab/cli 0.1.11 → 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 +379 -25
  2. package/dist/cli.js +377 -27
  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",
@@ -1868,7 +2169,7 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
1868
2169
  };
1869
2170
 
1870
2171
  // src/commands/add.ts
1871
- var VERSION = "0.1.11";
2172
+ var VERSION = "0.1.12";
1872
2173
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
1873
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(
1874
2175
  "-c, --cwd <cwd>",
@@ -1961,10 +2262,29 @@ var add = new commander.Command().name("add").alias("install").description("add
1961
2262
  logger.warn(`Currently installed: ${installedNames}`);
1962
2263
  logger.break();
1963
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
+ }
1964
2284
  const { agent } = await prompts({
1965
2285
  type: "select",
1966
2286
  name: "agent",
1967
- message: `Which ${highlighter.info("coding agent")} would you like to connect?`,
2287
+ message: `Which ${highlighter.info("agent integration")} would you like to add?`,
1968
2288
  choices: availableAgents.map((availableAgent) => ({
1969
2289
  title: AGENT_NAMES[availableAgent],
1970
2290
  value: availableAgent
@@ -2204,7 +2524,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2204
2524
  var MAX_CONTEXT_LINES = 50;
2205
2525
 
2206
2526
  // src/commands/configure.ts
2207
- var VERSION2 = "0.1.11";
2527
+ var VERSION2 = "0.1.12";
2208
2528
  var isMac = process.platform === "darwin";
2209
2529
  var META_LABEL = isMac ? "Cmd" : "Win";
2210
2530
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -2760,7 +3080,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
2760
3080
  };
2761
3081
 
2762
3082
  // src/commands/init.ts
2763
- var VERSION3 = "0.1.11";
3083
+ var VERSION3 = "0.1.12";
2764
3084
  var REPORT_URL = "https://react-grab.com/api/report-cli";
2765
3085
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
2766
3086
  var reportToCli = (type, config, error) => {
@@ -2816,7 +3136,7 @@ var formatActivationKeyDisplay2 = (activationKey) => {
2816
3136
  };
2817
3137
  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(
2818
3138
  "-a, --agent <agent>",
2819
- "agent integration (claude-code, cursor, opencode, codex, gemini, amp, droid)"
3139
+ "agent integration (claude-code, cursor, opencode, codex, gemini, amp)"
2820
3140
  ).option(
2821
3141
  "-k, --key <key>",
2822
3142
  "activation key (e.g., Meta+K, Ctrl+Shift+G, Space)"
@@ -3040,7 +3360,7 @@ var init = new commander.Command().name("init").description("initialize React Gr
3040
3360
  const { wantAddAgent } = await prompts({
3041
3361
  type: "confirm",
3042
3362
  name: "wantAddAgent",
3043
- message: `Would you like to connect React Grab to a ${highlighter.info("coding agent")}? ${highlighter.dim("(optional)")}`,
3363
+ message: `Would you like to add an ${highlighter.info("agent integration")}?`,
3044
3364
  initial: false
3045
3365
  });
3046
3366
  if (wantAddAgent === void 0) {
@@ -3048,10 +3368,27 @@ var init = new commander.Command().name("init").description("initialize React Gr
3048
3368
  process.exit(1);
3049
3369
  }
3050
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
+ }
3051
3388
  const { agent } = await prompts({
3052
3389
  type: "select",
3053
3390
  name: "agent",
3054
- message: `Which ${highlighter.info("coding agent")} would you like to connect?`,
3391
+ message: `Which ${highlighter.info("agent integration")} would you like to add?`,
3055
3392
  choices: [
3056
3393
  ...availableAgents.map((innerAgent) => ({
3057
3394
  title: getAgentName(innerAgent),
@@ -3362,7 +3699,7 @@ var init = new commander.Command().name("init").description("initialize React Gr
3362
3699
  const { wantAddAgent } = await prompts({
3363
3700
  type: "confirm",
3364
3701
  name: "wantAddAgent",
3365
- message: `Would you like to connect React Grab to a ${highlighter.info("coding agent")}? ${highlighter.dim("(optional)")}`,
3702
+ message: `Would you like to add an ${highlighter.info("agent integration")}?`,
3366
3703
  initial: false
3367
3704
  });
3368
3705
  if (wantAddAgent === void 0) {
@@ -3370,24 +3707,41 @@ var init = new commander.Command().name("init").description("initialize React Gr
3370
3707
  process.exit(1);
3371
3708
  }
3372
3709
  if (wantAddAgent) {
3373
- const { agent } = await prompts({
3374
- type: "select",
3375
- name: "agent",
3376
- message: `Which ${highlighter.info("coding agent")} would you like to connect?`,
3377
- choices: [
3378
- ...AGENTS.map((innerAgent) => ({
3379
- title: getAgentName(innerAgent),
3380
- value: innerAgent
3381
- })),
3382
- { title: "Skip", value: "skip" }
3383
- ]
3384
- });
3385
- if (agent === void 0) {
3710
+ const connectionMode = await promptConnectionMode();
3711
+ if (connectionMode === void 0) {
3386
3712
  logger.break();
3387
3713
  process.exit(1);
3388
3714
  }
3389
- if (agent !== "skip") {
3390
- 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
+ }
3391
3745
  }
3392
3746
  }
3393
3747
  }
@@ -3496,7 +3850,7 @@ var init = new commander.Command().name("init").description("initialize React Gr
3496
3850
  reportToCli("error", void 0, error);
3497
3851
  }
3498
3852
  });
3499
- var VERSION4 = "0.1.11";
3853
+ var VERSION4 = "0.1.12";
3500
3854
  var remove = new commander.Command().name("remove").description("remove an agent integration").argument(
3501
3855
  "[agent]",
3502
3856
  "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami)"
@@ -3675,7 +4029,7 @@ var remove = new commander.Command().name("remove").description("remove an agent
3675
4029
  });
3676
4030
 
3677
4031
  // src/cli.ts
3678
- var VERSION5 = "0.1.11";
4032
+ var VERSION5 = "0.1.12";
3679
4033
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
3680
4034
  process.on("SIGINT", () => process.exit(0));
3681
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",
@@ -1860,7 +2157,7 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
1860
2157
  };
1861
2158
 
1862
2159
  // src/commands/add.ts
1863
- var VERSION = "0.1.11";
2160
+ var VERSION = "0.1.12";
1864
2161
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
1865
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(
1866
2163
  "-c, --cwd <cwd>",
@@ -1953,10 +2250,29 @@ var add = new Command().name("add").alias("install").description("add an agent i
1953
2250
  logger.warn(`Currently installed: ${installedNames}`);
1954
2251
  logger.break();
1955
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
+ }
1956
2272
  const { agent } = await prompts({
1957
2273
  type: "select",
1958
2274
  name: "agent",
1959
- message: `Which ${highlighter.info("coding agent")} would you like to connect?`,
2275
+ message: `Which ${highlighter.info("agent integration")} would you like to add?`,
1960
2276
  choices: availableAgents.map((availableAgent) => ({
1961
2277
  title: AGENT_NAMES[availableAgent],
1962
2278
  value: availableAgent
@@ -2196,7 +2512,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2196
2512
  var MAX_CONTEXT_LINES = 50;
2197
2513
 
2198
2514
  // src/commands/configure.ts
2199
- var VERSION2 = "0.1.11";
2515
+ var VERSION2 = "0.1.12";
2200
2516
  var isMac = process.platform === "darwin";
2201
2517
  var META_LABEL = isMac ? "Cmd" : "Win";
2202
2518
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -2752,7 +3068,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
2752
3068
  };
2753
3069
 
2754
3070
  // src/commands/init.ts
2755
- var VERSION3 = "0.1.11";
3071
+ var VERSION3 = "0.1.12";
2756
3072
  var REPORT_URL = "https://react-grab.com/api/report-cli";
2757
3073
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
2758
3074
  var reportToCli = (type, config, error) => {
@@ -2808,7 +3124,7 @@ var formatActivationKeyDisplay2 = (activationKey) => {
2808
3124
  };
2809
3125
  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(
2810
3126
  "-a, --agent <agent>",
2811
- "agent integration (claude-code, cursor, opencode, codex, gemini, amp, droid)"
3127
+ "agent integration (claude-code, cursor, opencode, codex, gemini, amp)"
2812
3128
  ).option(
2813
3129
  "-k, --key <key>",
2814
3130
  "activation key (e.g., Meta+K, Ctrl+Shift+G, Space)"
@@ -3032,7 +3348,7 @@ var init = new Command().name("init").description("initialize React Grab in your
3032
3348
  const { wantAddAgent } = await prompts({
3033
3349
  type: "confirm",
3034
3350
  name: "wantAddAgent",
3035
- message: `Would you like to connect React Grab to a ${highlighter.info("coding agent")}? ${highlighter.dim("(optional)")}`,
3351
+ message: `Would you like to add an ${highlighter.info("agent integration")}?`,
3036
3352
  initial: false
3037
3353
  });
3038
3354
  if (wantAddAgent === void 0) {
@@ -3040,10 +3356,27 @@ var init = new Command().name("init").description("initialize React Grab in your
3040
3356
  process.exit(1);
3041
3357
  }
3042
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
+ }
3043
3376
  const { agent } = await prompts({
3044
3377
  type: "select",
3045
3378
  name: "agent",
3046
- message: `Which ${highlighter.info("coding agent")} would you like to connect?`,
3379
+ message: `Which ${highlighter.info("agent integration")} would you like to add?`,
3047
3380
  choices: [
3048
3381
  ...availableAgents.map((innerAgent) => ({
3049
3382
  title: getAgentName(innerAgent),
@@ -3354,7 +3687,7 @@ var init = new Command().name("init").description("initialize React Grab in your
3354
3687
  const { wantAddAgent } = await prompts({
3355
3688
  type: "confirm",
3356
3689
  name: "wantAddAgent",
3357
- message: `Would you like to connect React Grab to a ${highlighter.info("coding agent")}? ${highlighter.dim("(optional)")}`,
3690
+ message: `Would you like to add an ${highlighter.info("agent integration")}?`,
3358
3691
  initial: false
3359
3692
  });
3360
3693
  if (wantAddAgent === void 0) {
@@ -3362,24 +3695,41 @@ var init = new Command().name("init").description("initialize React Grab in your
3362
3695
  process.exit(1);
3363
3696
  }
3364
3697
  if (wantAddAgent) {
3365
- const { agent } = await prompts({
3366
- type: "select",
3367
- name: "agent",
3368
- message: `Which ${highlighter.info("coding agent")} would you like to connect?`,
3369
- choices: [
3370
- ...AGENTS.map((innerAgent) => ({
3371
- title: getAgentName(innerAgent),
3372
- value: innerAgent
3373
- })),
3374
- { title: "Skip", value: "skip" }
3375
- ]
3376
- });
3377
- if (agent === void 0) {
3698
+ const connectionMode = await promptConnectionMode();
3699
+ if (connectionMode === void 0) {
3378
3700
  logger.break();
3379
3701
  process.exit(1);
3380
3702
  }
3381
- if (agent !== "skip") {
3382
- 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
+ }
3383
3733
  }
3384
3734
  }
3385
3735
  }
@@ -3488,7 +3838,7 @@ var init = new Command().name("init").description("initialize React Grab in your
3488
3838
  reportToCli("error", void 0, error);
3489
3839
  }
3490
3840
  });
3491
- var VERSION4 = "0.1.11";
3841
+ var VERSION4 = "0.1.12";
3492
3842
  var remove = new Command().name("remove").description("remove an agent integration").argument(
3493
3843
  "[agent]",
3494
3844
  "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami)"
@@ -3667,7 +4017,7 @@ var remove = new Command().name("remove").description("remove an agent integrati
3667
4017
  });
3668
4018
 
3669
4019
  // src/cli.ts
3670
- var VERSION5 = "0.1.11";
4020
+ var VERSION5 = "0.1.12";
3671
4021
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
3672
4022
  process.on("SIGINT", () => process.exit(0));
3673
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.11",
3
+ "version": "0.1.12",
4
4
  "bin": {
5
5
  "react-grab": "./dist/cli.js"
6
6
  },