@react-grab/cli 0.1.15 → 0.1.17

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 +103 -141
  2. package/dist/cli.js +83 -141
  3. package/package.json +4 -2
package/dist/cli.cjs CHANGED
@@ -11,10 +11,30 @@ var ni = require('@antfu/ni');
11
11
  var ignore = require('ignore');
12
12
  var os = require('os');
13
13
  var process2 = require('process');
14
+ var jsonc = require('jsonc-parser');
15
+ var TOML = require('smol-toml');
14
16
  var ora = require('ora');
15
17
 
16
18
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
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
+
18
38
  var pc__default = /*#__PURE__*/_interopDefault(pc);
19
39
  var basePrompts__default = /*#__PURE__*/_interopDefault(basePrompts);
20
40
  var fs__default = /*#__PURE__*/_interopDefault(fs);
@@ -22,17 +42,22 @@ var path__default = /*#__PURE__*/_interopDefault(path);
22
42
  var ignore__default = /*#__PURE__*/_interopDefault(ignore);
23
43
  var os__default = /*#__PURE__*/_interopDefault(os);
24
44
  var process2__default = /*#__PURE__*/_interopDefault(process2);
45
+ var jsonc__namespace = /*#__PURE__*/_interopNamespace(jsonc);
46
+ var TOML__namespace = /*#__PURE__*/_interopNamespace(TOML);
25
47
  var ora__default = /*#__PURE__*/_interopDefault(ora);
26
48
 
27
49
  // src/utils/is-non-interactive.ts
28
- var NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [
50
+ var AGENT_ENVIRONMENT_VARIABLES = [
29
51
  "CI",
30
52
  "CLAUDECODE",
53
+ "CURSOR_AGENT",
54
+ "CODEX_CI",
55
+ "OPENCODE",
56
+ "AMP_HOME",
31
57
  "AMI"
32
58
  ];
33
- var detectNonInteractive = (yesFlag) => yesFlag || NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some(
34
- (variable) => process.env[variable] === "true"
35
- ) || !process.stdin.isTTY;
59
+ var isEnvironmentVariableSet = (variable) => Boolean(process.env[variable]);
60
+ var detectNonInteractive = (yesFlag) => yesFlag || AGENT_ENVIRONMENT_VARIABLES.some(isEnvironmentVariableSet) || !process.stdin.isTTY;
36
61
  var highlighter = {
37
62
  error: pc__default.default.red,
38
63
  warn: pc__default.default.yellow,
@@ -390,6 +415,8 @@ var AGENT_PACKAGES = [
390
415
  "@react-grab/gemini",
391
416
  "@react-grab/amp",
392
417
  "@react-grab/ami",
418
+ "@react-grab/droid",
419
+ "@react-grab/copilot",
393
420
  "@react-grab/mcp"
394
421
  ];
395
422
  var detectUnsupportedFramework = (projectRoot) => {
@@ -624,6 +651,7 @@ var spinner = (text, options) => ora__default.default({ text, isSilent: options?
624
651
  // src/utils/install-mcp.ts
625
652
  var SERVER_NAME = "react-grab-mcp";
626
653
  var PACKAGE_NAME = "@react-grab/mcp";
654
+ var getXdgConfigHome = () => process2__default.default.env.XDG_CONFIG_HOME || path__default.default.join(os__default.default.homedir(), ".config");
627
655
  var getBaseDir = () => {
628
656
  const homeDir = os__default.default.homedir();
629
657
  if (process2__default.default.platform === "win32") {
@@ -632,15 +660,13 @@ var getBaseDir = () => {
632
660
  if (process2__default.default.platform === "darwin") {
633
661
  return path__default.default.join(homeDir, "Library", "Application Support");
634
662
  }
635
- return process2__default.default.env.XDG_CONFIG_HOME || path__default.default.join(homeDir, ".config");
663
+ return getXdgConfigHome();
636
664
  };
637
665
  var getZedConfigPath = () => {
638
- const homeDir = os__default.default.homedir();
639
666
  if (process2__default.default.platform === "win32") {
640
- const appData = process2__default.default.env.APPDATA || path__default.default.join(homeDir, "AppData", "Roaming");
641
- return path__default.default.join(appData, "Zed", "settings.json");
667
+ return path__default.default.join(getBaseDir(), "Zed", "settings.json");
642
668
  }
643
- return path__default.default.join(homeDir, ".config", "zed", "settings.json");
669
+ return path__default.default.join(os__default.default.homedir(), ".config", "zed", "settings.json");
644
670
  };
645
671
  var getClients = () => {
646
672
  const homeDir = os__default.default.homedir();
@@ -650,6 +676,23 @@ var getClients = () => {
650
676
  args: ["-y", PACKAGE_NAME, "--stdio"]
651
677
  };
652
678
  return [
679
+ {
680
+ name: "Claude Code",
681
+ configPath: path__default.default.join(homeDir, ".claude.json"),
682
+ configKey: "mcpServers",
683
+ format: "json",
684
+ serverConfig: stdioConfig
685
+ },
686
+ {
687
+ name: "Codex",
688
+ configPath: path__default.default.join(
689
+ process2__default.default.env.CODEX_HOME || path__default.default.join(homeDir, ".codex"),
690
+ "config.toml"
691
+ ),
692
+ configKey: "mcp_servers",
693
+ format: "toml",
694
+ serverConfig: stdioConfig
695
+ },
653
696
  {
654
697
  name: "Cursor",
655
698
  configPath: path__default.default.join(homeDir, ".cursor", "mcp.json"),
@@ -657,6 +700,16 @@ var getClients = () => {
657
700
  format: "json",
658
701
  serverConfig: stdioConfig
659
702
  },
703
+ {
704
+ name: "OpenCode",
705
+ configPath: path__default.default.join(getXdgConfigHome(), "opencode", "opencode.json"),
706
+ configKey: "mcp",
707
+ format: "json",
708
+ serverConfig: {
709
+ type: "local",
710
+ command: ["npx", "-y", PACKAGE_NAME, "--stdio"]
711
+ }
712
+ },
660
713
  {
661
714
  name: "VS Code",
662
715
  configPath: path__default.default.join(baseDir, "Code", "User", "mcp.json"),
@@ -664,13 +717,6 @@ var getClients = () => {
664
717
  format: "json",
665
718
  serverConfig: { type: "stdio", ...stdioConfig }
666
719
  },
667
- {
668
- name: "Claude Code",
669
- configPath: path__default.default.join(homeDir, ".claude.json"),
670
- configKey: "mcpServers",
671
- format: "json",
672
- serverConfig: stdioConfig
673
- },
674
720
  {
675
721
  name: "Amp",
676
722
  configPath: path__default.default.join(homeDir, ".config", "amp", "settings.json"),
@@ -686,13 +732,10 @@ var getClients = () => {
686
732
  serverConfig: { type: "stdio", ...stdioConfig }
687
733
  },
688
734
  {
689
- name: "Codex",
690
- configPath: path__default.default.join(
691
- process2__default.default.env.CODEX_HOME || path__default.default.join(homeDir, ".codex"),
692
- "config.toml"
693
- ),
694
- configKey: "mcp_servers",
695
- format: "toml",
735
+ name: "Windsurf",
736
+ configPath: path__default.default.join(homeDir, ".codeium", "windsurf", "mcp_config.json"),
737
+ configKey: "mcpServers",
738
+ format: "json",
696
739
  serverConfig: stdioConfig
697
740
  },
698
741
  {
@@ -701,13 +744,6 @@ var getClients = () => {
701
744
  configKey: "context_servers",
702
745
  format: "json",
703
746
  serverConfig: { source: "custom", ...stdioConfig, env: {} }
704
- },
705
- {
706
- name: "Windsurf",
707
- configPath: path__default.default.join(homeDir, ".codeium", "windsurf", "mcp_config.json"),
708
- configKey: "mcpServers",
709
- format: "json",
710
- serverConfig: stdioConfig
711
747
  }
712
748
  ];
713
749
  };
@@ -717,113 +753,36 @@ var ensureDirectory = (filePath) => {
717
753
  fs__default.default.mkdirSync(directory, { recursive: true });
718
754
  }
719
755
  };
720
- var indentJson = (json, baseIndent) => json.split("\n").map((line, index) => index === 0 ? line : baseIndent + line).join("\n");
721
- var insertIntoJsonc = (filePath, content, configKey, serverName, serverConfig) => {
722
- if (content.includes(`"${serverName}"`)) return;
723
- const serverJson = indentJson(
724
- JSON.stringify(serverConfig, null, 2),
725
- " "
726
- );
727
- const escapedConfigKey = configKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
728
- const keyPattern = new RegExp(`"${escapedConfigKey}"\\s*:\\s*\\{`);
729
- const keyMatch = keyPattern.exec(content);
730
- if (keyMatch) {
731
- const insertPosition = keyMatch.index + keyMatch[0].length;
732
- const entry = `
733
- "${serverName}": ${serverJson},`;
734
- fs__default.default.writeFileSync(
735
- filePath,
736
- content.slice(0, insertPosition) + entry + content.slice(insertPosition)
737
- );
738
- return;
739
- }
740
- const lastBrace = content.lastIndexOf("}");
741
- if (lastBrace === -1) return;
742
- const beforeBrace = content.slice(0, lastBrace).trimEnd();
743
- const withoutComments = beforeBrace.replace(/\/\/.*$/, "").trimEnd();
744
- const lastChar = withoutComments[withoutComments.length - 1];
745
- const needsComma = lastChar !== void 0 && lastChar !== "{" && lastChar !== ",";
746
- const section = `${needsComma ? "," : ""}
747
- "${configKey}": {
748
- "${serverName}": ${serverJson}
749
- }`;
750
- fs__default.default.writeFileSync(filePath, beforeBrace + section + "\n}\n");
756
+ var JSONC_FORMAT_OPTIONS = {
757
+ tabSize: 2,
758
+ insertSpaces: true
759
+ };
760
+ var upsertIntoJsonc = (filePath, content, configKey, serverName, serverConfig) => {
761
+ const edits = jsonc__namespace.modify(content, [configKey, serverName], serverConfig, {
762
+ formattingOptions: JSONC_FORMAT_OPTIONS
763
+ });
764
+ fs__default.default.writeFileSync(filePath, jsonc__namespace.applyEdits(content, edits));
751
765
  };
752
766
  var installJsonClient = (client) => {
753
767
  ensureDirectory(client.configPath);
754
- if (!fs__default.default.existsSync(client.configPath)) {
755
- const config = {
756
- [client.configKey]: { [SERVER_NAME]: client.serverConfig }
757
- };
758
- fs__default.default.writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
759
- return;
760
- }
761
- const content = fs__default.default.readFileSync(client.configPath, "utf8");
762
- try {
763
- const config = JSON.parse(content);
764
- const servers = config[client.configKey] ?? {};
765
- servers[SERVER_NAME] = client.serverConfig;
766
- config[client.configKey] = servers;
767
- fs__default.default.writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
768
- } catch {
769
- insertIntoJsonc(
770
- client.configPath,
771
- content,
772
- client.configKey,
773
- SERVER_NAME,
774
- client.serverConfig
775
- );
776
- }
777
- };
778
- var buildTomlSection = (configKey, serverConfig) => {
779
- const lines = [`[${configKey}.${SERVER_NAME}]`];
780
- for (const [key, value] of Object.entries(serverConfig)) {
781
- if (typeof value === "string") {
782
- lines.push(`${key} = "${value}"`);
783
- } else if (Array.isArray(value)) {
784
- const items = value.map((item) => `"${item}"`).join(", ");
785
- lines.push(`${key} = [${items}]`);
786
- }
787
- }
788
- return lines.join("\n");
768
+ const content = fs__default.default.existsSync(client.configPath) ? fs__default.default.readFileSync(client.configPath, "utf8") : "{}";
769
+ upsertIntoJsonc(
770
+ client.configPath,
771
+ content,
772
+ client.configKey,
773
+ SERVER_NAME,
774
+ client.serverConfig
775
+ );
789
776
  };
790
777
  var installTomlClient = (client) => {
791
778
  ensureDirectory(client.configPath);
792
- const sectionHeader = `[${client.configKey}.${SERVER_NAME}]`;
793
- const newSection = buildTomlSection(client.configKey, client.serverConfig);
794
- if (!fs__default.default.existsSync(client.configPath)) {
795
- fs__default.default.writeFileSync(client.configPath, newSection + "\n");
796
- return;
797
- }
798
- const content = fs__default.default.readFileSync(client.configPath, "utf8");
799
- if (!content.includes(sectionHeader)) {
800
- fs__default.default.writeFileSync(
801
- client.configPath,
802
- content.trimEnd() + "\n\n" + newSection + "\n"
803
- );
804
- return;
805
- }
806
- const lines = content.split("\n");
807
- const resultLines = [];
808
- let isInsideOurSection = false;
809
- let didInsertReplacement = false;
810
- for (const line of lines) {
811
- if (line.trim() === sectionHeader) {
812
- isInsideOurSection = true;
813
- if (!didInsertReplacement) {
814
- resultLines.push(newSection);
815
- didInsertReplacement = true;
816
- }
817
- continue;
818
- }
819
- if (isInsideOurSection && line.startsWith("[")) {
820
- isInsideOurSection = false;
821
- }
822
- if (!isInsideOurSection) {
823
- resultLines.push(line);
824
- }
825
- }
826
- fs__default.default.writeFileSync(client.configPath, resultLines.join("\n"));
779
+ const existingConfig = fs__default.default.existsSync(
780
+ client.configPath
781
+ ) ? TOML__namespace.parse(fs__default.default.readFileSync(client.configPath, "utf8")) : {};
782
+ const serverSection = existingConfig[client.configKey] ?? {};
783
+ serverSection[SERVER_NAME] = client.serverConfig;
784
+ existingConfig[client.configKey] = serverSection;
785
+ fs__default.default.writeFileSync(client.configPath, TOML__namespace.stringify(existingConfig));
827
786
  };
828
787
  var getMcpClientNames = () => getClients().map((client) => client.name);
829
788
  var installMcpServers = (selectedClients) => {
@@ -854,8 +813,7 @@ var installMcpServers = (selectedClients) => {
854
813
  }
855
814
  }
856
815
  const successCount = results.filter((result) => result.success).length;
857
- const failedCount = results.length - successCount;
858
- if (failedCount > 0) {
816
+ if (successCount < results.length) {
859
817
  installSpinner.warn(
860
818
  `Installed to ${successCount}/${results.length} agents.`
861
819
  );
@@ -925,7 +883,8 @@ var AGENTS = [
925
883
  "gemini",
926
884
  "amp",
927
885
  "ami",
928
- "droid"
886
+ "droid",
887
+ "copilot"
929
888
  ];
930
889
  var AGENT_NAMES = {
931
890
  "claude-code": "Claude Code",
@@ -935,7 +894,8 @@ var AGENT_NAMES = {
935
894
  gemini: "Gemini",
936
895
  amp: "Amp",
937
896
  ami: "Ami",
938
- droid: "Droid"
897
+ droid: "Droid",
898
+ copilot: "Copilot"
939
899
  };
940
900
  var getAgentDisplayName = (agent) => {
941
901
  if (agent === "mcp") return "MCP";
@@ -1670,7 +1630,9 @@ var AGENT_PACKAGES2 = {
1670
1630
  opencode: "@react-grab/opencode@latest",
1671
1631
  codex: "@react-grab/codex@latest",
1672
1632
  gemini: "@react-grab/gemini@latest",
1673
- amp: "@react-grab/amp@latest"
1633
+ amp: "@react-grab/amp@latest",
1634
+ droid: "@react-grab/droid@latest",
1635
+ copilot: "@react-grab/copilot@latest"
1674
1636
  };
1675
1637
  var getAgentPrefix = (agent, packageManager) => {
1676
1638
  const agentPackage = AGENT_PACKAGES2[agent];
@@ -2266,7 +2228,7 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
2266
2228
  };
2267
2229
 
2268
2230
  // src/commands/add.ts
2269
- var VERSION = "0.1.15";
2231
+ var VERSION = "0.1.17";
2270
2232
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
2271
2233
  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(
2272
2234
  "-c, --cwd <cwd>",
@@ -2627,7 +2589,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2627
2589
  var MAX_CONTEXT_LINES = 50;
2628
2590
 
2629
2591
  // src/commands/configure.ts
2630
- var VERSION2 = "0.1.15";
2592
+ var VERSION2 = "0.1.17";
2631
2593
  var isMac = process.platform === "darwin";
2632
2594
  var META_LABEL = isMac ? "Cmd" : "Win";
2633
2595
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -3183,7 +3145,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
3183
3145
  };
3184
3146
 
3185
3147
  // src/commands/init.ts
3186
- var VERSION3 = "0.1.15";
3148
+ var VERSION3 = "0.1.17";
3187
3149
  var REPORT_URL = "https://react-grab.com/api/report-cli";
3188
3150
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
3189
3151
  var reportToCli = (type, config, error) => {
@@ -4041,7 +4003,7 @@ var init = new commander.Command().name("init").description("initialize React Gr
4041
4003
  reportToCli("error", void 0, error);
4042
4004
  }
4043
4005
  });
4044
- var VERSION4 = "0.1.15";
4006
+ var VERSION4 = "0.1.17";
4045
4007
  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(
4046
4008
  "-c, --cwd <cwd>",
4047
4009
  "working directory (defaults to current directory)",
@@ -4217,7 +4179,7 @@ var remove = new commander.Command().name("remove").description("disconnect Reac
4217
4179
  });
4218
4180
 
4219
4181
  // src/cli.ts
4220
- var VERSION5 = "0.1.15";
4182
+ var VERSION5 = "0.1.17";
4221
4183
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
4222
4184
  process.on("SIGINT", () => process.exit(0));
4223
4185
  process.on("SIGTERM", () => process.exit(0));
package/dist/cli.js CHANGED
@@ -9,17 +9,22 @@ import { detect } from '@antfu/ni';
9
9
  import ignore from 'ignore';
10
10
  import os from 'os';
11
11
  import process2 from 'process';
12
+ import * as jsonc from 'jsonc-parser';
13
+ import * as TOML from 'smol-toml';
12
14
  import ora from 'ora';
13
15
 
14
16
  // src/utils/is-non-interactive.ts
15
- var NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [
17
+ var AGENT_ENVIRONMENT_VARIABLES = [
16
18
  "CI",
17
19
  "CLAUDECODE",
20
+ "CURSOR_AGENT",
21
+ "CODEX_CI",
22
+ "OPENCODE",
23
+ "AMP_HOME",
18
24
  "AMI"
19
25
  ];
20
- var detectNonInteractive = (yesFlag) => yesFlag || NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some(
21
- (variable) => process.env[variable] === "true"
22
- ) || !process.stdin.isTTY;
26
+ var isEnvironmentVariableSet = (variable) => Boolean(process.env[variable]);
27
+ var detectNonInteractive = (yesFlag) => yesFlag || AGENT_ENVIRONMENT_VARIABLES.some(isEnvironmentVariableSet) || !process.stdin.isTTY;
23
28
  var highlighter = {
24
29
  error: pc.red,
25
30
  warn: pc.yellow,
@@ -377,6 +382,8 @@ var AGENT_PACKAGES = [
377
382
  "@react-grab/gemini",
378
383
  "@react-grab/amp",
379
384
  "@react-grab/ami",
385
+ "@react-grab/droid",
386
+ "@react-grab/copilot",
380
387
  "@react-grab/mcp"
381
388
  ];
382
389
  var detectUnsupportedFramework = (projectRoot) => {
@@ -611,6 +618,7 @@ var spinner = (text, options) => ora({ text, isSilent: options?.silent });
611
618
  // src/utils/install-mcp.ts
612
619
  var SERVER_NAME = "react-grab-mcp";
613
620
  var PACKAGE_NAME = "@react-grab/mcp";
621
+ var getXdgConfigHome = () => process2.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
614
622
  var getBaseDir = () => {
615
623
  const homeDir = os.homedir();
616
624
  if (process2.platform === "win32") {
@@ -619,15 +627,13 @@ var getBaseDir = () => {
619
627
  if (process2.platform === "darwin") {
620
628
  return path.join(homeDir, "Library", "Application Support");
621
629
  }
622
- return process2.env.XDG_CONFIG_HOME || path.join(homeDir, ".config");
630
+ return getXdgConfigHome();
623
631
  };
624
632
  var getZedConfigPath = () => {
625
- const homeDir = os.homedir();
626
633
  if (process2.platform === "win32") {
627
- const appData = process2.env.APPDATA || path.join(homeDir, "AppData", "Roaming");
628
- return path.join(appData, "Zed", "settings.json");
634
+ return path.join(getBaseDir(), "Zed", "settings.json");
629
635
  }
630
- return path.join(homeDir, ".config", "zed", "settings.json");
636
+ return path.join(os.homedir(), ".config", "zed", "settings.json");
631
637
  };
632
638
  var getClients = () => {
633
639
  const homeDir = os.homedir();
@@ -637,6 +643,23 @@ var getClients = () => {
637
643
  args: ["-y", PACKAGE_NAME, "--stdio"]
638
644
  };
639
645
  return [
646
+ {
647
+ name: "Claude Code",
648
+ configPath: path.join(homeDir, ".claude.json"),
649
+ configKey: "mcpServers",
650
+ format: "json",
651
+ serverConfig: stdioConfig
652
+ },
653
+ {
654
+ name: "Codex",
655
+ configPath: path.join(
656
+ process2.env.CODEX_HOME || path.join(homeDir, ".codex"),
657
+ "config.toml"
658
+ ),
659
+ configKey: "mcp_servers",
660
+ format: "toml",
661
+ serverConfig: stdioConfig
662
+ },
640
663
  {
641
664
  name: "Cursor",
642
665
  configPath: path.join(homeDir, ".cursor", "mcp.json"),
@@ -644,6 +667,16 @@ var getClients = () => {
644
667
  format: "json",
645
668
  serverConfig: stdioConfig
646
669
  },
670
+ {
671
+ name: "OpenCode",
672
+ configPath: path.join(getXdgConfigHome(), "opencode", "opencode.json"),
673
+ configKey: "mcp",
674
+ format: "json",
675
+ serverConfig: {
676
+ type: "local",
677
+ command: ["npx", "-y", PACKAGE_NAME, "--stdio"]
678
+ }
679
+ },
647
680
  {
648
681
  name: "VS Code",
649
682
  configPath: path.join(baseDir, "Code", "User", "mcp.json"),
@@ -651,13 +684,6 @@ var getClients = () => {
651
684
  format: "json",
652
685
  serverConfig: { type: "stdio", ...stdioConfig }
653
686
  },
654
- {
655
- name: "Claude Code",
656
- configPath: path.join(homeDir, ".claude.json"),
657
- configKey: "mcpServers",
658
- format: "json",
659
- serverConfig: stdioConfig
660
- },
661
687
  {
662
688
  name: "Amp",
663
689
  configPath: path.join(homeDir, ".config", "amp", "settings.json"),
@@ -673,13 +699,10 @@ var getClients = () => {
673
699
  serverConfig: { type: "stdio", ...stdioConfig }
674
700
  },
675
701
  {
676
- name: "Codex",
677
- configPath: path.join(
678
- process2.env.CODEX_HOME || path.join(homeDir, ".codex"),
679
- "config.toml"
680
- ),
681
- configKey: "mcp_servers",
682
- format: "toml",
702
+ name: "Windsurf",
703
+ configPath: path.join(homeDir, ".codeium", "windsurf", "mcp_config.json"),
704
+ configKey: "mcpServers",
705
+ format: "json",
683
706
  serverConfig: stdioConfig
684
707
  },
685
708
  {
@@ -688,13 +711,6 @@ var getClients = () => {
688
711
  configKey: "context_servers",
689
712
  format: "json",
690
713
  serverConfig: { source: "custom", ...stdioConfig, env: {} }
691
- },
692
- {
693
- name: "Windsurf",
694
- configPath: path.join(homeDir, ".codeium", "windsurf", "mcp_config.json"),
695
- configKey: "mcpServers",
696
- format: "json",
697
- serverConfig: stdioConfig
698
714
  }
699
715
  ];
700
716
  };
@@ -704,113 +720,36 @@ var ensureDirectory = (filePath) => {
704
720
  fs.mkdirSync(directory, { recursive: true });
705
721
  }
706
722
  };
707
- var indentJson = (json, baseIndent) => json.split("\n").map((line, index) => index === 0 ? line : baseIndent + line).join("\n");
708
- var insertIntoJsonc = (filePath, content, configKey, serverName, serverConfig) => {
709
- if (content.includes(`"${serverName}"`)) return;
710
- const serverJson = indentJson(
711
- JSON.stringify(serverConfig, null, 2),
712
- " "
713
- );
714
- const escapedConfigKey = configKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
715
- const keyPattern = new RegExp(`"${escapedConfigKey}"\\s*:\\s*\\{`);
716
- const keyMatch = keyPattern.exec(content);
717
- if (keyMatch) {
718
- const insertPosition = keyMatch.index + keyMatch[0].length;
719
- const entry = `
720
- "${serverName}": ${serverJson},`;
721
- fs.writeFileSync(
722
- filePath,
723
- content.slice(0, insertPosition) + entry + content.slice(insertPosition)
724
- );
725
- return;
726
- }
727
- const lastBrace = content.lastIndexOf("}");
728
- if (lastBrace === -1) return;
729
- const beforeBrace = content.slice(0, lastBrace).trimEnd();
730
- const withoutComments = beforeBrace.replace(/\/\/.*$/, "").trimEnd();
731
- const lastChar = withoutComments[withoutComments.length - 1];
732
- const needsComma = lastChar !== void 0 && lastChar !== "{" && lastChar !== ",";
733
- const section = `${needsComma ? "," : ""}
734
- "${configKey}": {
735
- "${serverName}": ${serverJson}
736
- }`;
737
- fs.writeFileSync(filePath, beforeBrace + section + "\n}\n");
723
+ var JSONC_FORMAT_OPTIONS = {
724
+ tabSize: 2,
725
+ insertSpaces: true
726
+ };
727
+ var upsertIntoJsonc = (filePath, content, configKey, serverName, serverConfig) => {
728
+ const edits = jsonc.modify(content, [configKey, serverName], serverConfig, {
729
+ formattingOptions: JSONC_FORMAT_OPTIONS
730
+ });
731
+ fs.writeFileSync(filePath, jsonc.applyEdits(content, edits));
738
732
  };
739
733
  var installJsonClient = (client) => {
740
734
  ensureDirectory(client.configPath);
741
- if (!fs.existsSync(client.configPath)) {
742
- const config = {
743
- [client.configKey]: { [SERVER_NAME]: client.serverConfig }
744
- };
745
- fs.writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
746
- return;
747
- }
748
- const content = fs.readFileSync(client.configPath, "utf8");
749
- try {
750
- const config = JSON.parse(content);
751
- const servers = config[client.configKey] ?? {};
752
- servers[SERVER_NAME] = client.serverConfig;
753
- config[client.configKey] = servers;
754
- fs.writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
755
- } catch {
756
- insertIntoJsonc(
757
- client.configPath,
758
- content,
759
- client.configKey,
760
- SERVER_NAME,
761
- client.serverConfig
762
- );
763
- }
764
- };
765
- var buildTomlSection = (configKey, serverConfig) => {
766
- const lines = [`[${configKey}.${SERVER_NAME}]`];
767
- for (const [key, value] of Object.entries(serverConfig)) {
768
- if (typeof value === "string") {
769
- lines.push(`${key} = "${value}"`);
770
- } else if (Array.isArray(value)) {
771
- const items = value.map((item) => `"${item}"`).join(", ");
772
- lines.push(`${key} = [${items}]`);
773
- }
774
- }
775
- return lines.join("\n");
735
+ const content = fs.existsSync(client.configPath) ? fs.readFileSync(client.configPath, "utf8") : "{}";
736
+ upsertIntoJsonc(
737
+ client.configPath,
738
+ content,
739
+ client.configKey,
740
+ SERVER_NAME,
741
+ client.serverConfig
742
+ );
776
743
  };
777
744
  var installTomlClient = (client) => {
778
745
  ensureDirectory(client.configPath);
779
- const sectionHeader = `[${client.configKey}.${SERVER_NAME}]`;
780
- const newSection = buildTomlSection(client.configKey, client.serverConfig);
781
- if (!fs.existsSync(client.configPath)) {
782
- fs.writeFileSync(client.configPath, newSection + "\n");
783
- return;
784
- }
785
- const content = fs.readFileSync(client.configPath, "utf8");
786
- if (!content.includes(sectionHeader)) {
787
- fs.writeFileSync(
788
- client.configPath,
789
- content.trimEnd() + "\n\n" + newSection + "\n"
790
- );
791
- return;
792
- }
793
- const lines = content.split("\n");
794
- const resultLines = [];
795
- let isInsideOurSection = false;
796
- let didInsertReplacement = false;
797
- for (const line of lines) {
798
- if (line.trim() === sectionHeader) {
799
- isInsideOurSection = true;
800
- if (!didInsertReplacement) {
801
- resultLines.push(newSection);
802
- didInsertReplacement = true;
803
- }
804
- continue;
805
- }
806
- if (isInsideOurSection && line.startsWith("[")) {
807
- isInsideOurSection = false;
808
- }
809
- if (!isInsideOurSection) {
810
- resultLines.push(line);
811
- }
812
- }
813
- fs.writeFileSync(client.configPath, resultLines.join("\n"));
746
+ const existingConfig = fs.existsSync(
747
+ client.configPath
748
+ ) ? TOML.parse(fs.readFileSync(client.configPath, "utf8")) : {};
749
+ const serverSection = existingConfig[client.configKey] ?? {};
750
+ serverSection[SERVER_NAME] = client.serverConfig;
751
+ existingConfig[client.configKey] = serverSection;
752
+ fs.writeFileSync(client.configPath, TOML.stringify(existingConfig));
814
753
  };
815
754
  var getMcpClientNames = () => getClients().map((client) => client.name);
816
755
  var installMcpServers = (selectedClients) => {
@@ -841,8 +780,7 @@ var installMcpServers = (selectedClients) => {
841
780
  }
842
781
  }
843
782
  const successCount = results.filter((result) => result.success).length;
844
- const failedCount = results.length - successCount;
845
- if (failedCount > 0) {
783
+ if (successCount < results.length) {
846
784
  installSpinner.warn(
847
785
  `Installed to ${successCount}/${results.length} agents.`
848
786
  );
@@ -912,7 +850,8 @@ var AGENTS = [
912
850
  "gemini",
913
851
  "amp",
914
852
  "ami",
915
- "droid"
853
+ "droid",
854
+ "copilot"
916
855
  ];
917
856
  var AGENT_NAMES = {
918
857
  "claude-code": "Claude Code",
@@ -922,7 +861,8 @@ var AGENT_NAMES = {
922
861
  gemini: "Gemini",
923
862
  amp: "Amp",
924
863
  ami: "Ami",
925
- droid: "Droid"
864
+ droid: "Droid",
865
+ copilot: "Copilot"
926
866
  };
927
867
  var getAgentDisplayName = (agent) => {
928
868
  if (agent === "mcp") return "MCP";
@@ -1657,7 +1597,9 @@ var AGENT_PACKAGES2 = {
1657
1597
  opencode: "@react-grab/opencode@latest",
1658
1598
  codex: "@react-grab/codex@latest",
1659
1599
  gemini: "@react-grab/gemini@latest",
1660
- amp: "@react-grab/amp@latest"
1600
+ amp: "@react-grab/amp@latest",
1601
+ droid: "@react-grab/droid@latest",
1602
+ copilot: "@react-grab/copilot@latest"
1661
1603
  };
1662
1604
  var getAgentPrefix = (agent, packageManager) => {
1663
1605
  const agentPackage = AGENT_PACKAGES2[agent];
@@ -2253,7 +2195,7 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
2253
2195
  };
2254
2196
 
2255
2197
  // src/commands/add.ts
2256
- var VERSION = "0.1.15";
2198
+ var VERSION = "0.1.17";
2257
2199
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
2258
2200
  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(
2259
2201
  "-c, --cwd <cwd>",
@@ -2614,7 +2556,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2614
2556
  var MAX_CONTEXT_LINES = 50;
2615
2557
 
2616
2558
  // src/commands/configure.ts
2617
- var VERSION2 = "0.1.15";
2559
+ var VERSION2 = "0.1.17";
2618
2560
  var isMac = process.platform === "darwin";
2619
2561
  var META_LABEL = isMac ? "Cmd" : "Win";
2620
2562
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -3170,7 +3112,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
3170
3112
  };
3171
3113
 
3172
3114
  // src/commands/init.ts
3173
- var VERSION3 = "0.1.15";
3115
+ var VERSION3 = "0.1.17";
3174
3116
  var REPORT_URL = "https://react-grab.com/api/report-cli";
3175
3117
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
3176
3118
  var reportToCli = (type, config, error) => {
@@ -4028,7 +3970,7 @@ var init = new Command().name("init").description("initialize React Grab in your
4028
3970
  reportToCli("error", void 0, error);
4029
3971
  }
4030
3972
  });
4031
- var VERSION4 = "0.1.15";
3973
+ var VERSION4 = "0.1.17";
4032
3974
  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(
4033
3975
  "-c, --cwd <cwd>",
4034
3976
  "working directory (defaults to current directory)",
@@ -4204,7 +4146,7 @@ var remove = new Command().name("remove").description("disconnect React Grab fro
4204
4146
  });
4205
4147
 
4206
4148
  // src/cli.ts
4207
- var VERSION5 = "0.1.15";
4149
+ var VERSION5 = "0.1.17";
4208
4150
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
4209
4151
  process.on("SIGINT", () => process.exit(0));
4210
4152
  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.15",
3
+ "version": "0.1.17",
4
4
  "bin": {
5
5
  "react-grab": "./dist/cli.js"
6
6
  },
@@ -19,9 +19,11 @@
19
19
  "@antfu/ni": "^0.23.0",
20
20
  "commander": "^14.0.0",
21
21
  "ignore": "^7.0.5",
22
+ "jsonc-parser": "^3.3.1",
22
23
  "ora": "^8.2.0",
23
24
  "picocolors": "^1.1.1",
24
- "prompts": "^2.4.2"
25
+ "prompts": "^2.4.2",
26
+ "smol-toml": "^1.6.0"
25
27
  },
26
28
  "devDependencies": {
27
29
  "@types/prompts": "^2.4.9",