@hubspot/cli 8.0.8-experimental.0 → 8.0.8-experimental.10

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 (56) hide show
  1. package/commands/cms/theme/preview.js +11 -64
  2. package/commands/mcp/__tests__/start.test.js +1 -8
  3. package/commands/mcp/start.js +1 -0
  4. package/lang/en.d.ts +0 -2
  5. package/lang/en.js +0 -2
  6. package/lib/__tests__/commandSuggestion.test.js +2 -0
  7. package/lib/cms/devServerProcess.d.ts +13 -0
  8. package/lib/cms/devServerProcess.js +188 -0
  9. package/lib/commandSuggestion.js +1 -7
  10. package/lib/mcp/__tests__/setup.test.js +0 -15
  11. package/lib/mcp/setup.d.ts +0 -1
  12. package/lib/mcp/setup.js +34 -105
  13. package/mcp-server/tools/cms/HsCreateFunctionTool.js +1 -1
  14. package/mcp-server/tools/cms/HsCreateModuleTool.js +1 -1
  15. package/mcp-server/tools/cms/HsCreateTemplateTool.js +1 -1
  16. package/mcp-server/tools/cms/HsFunctionLogsTool.js +1 -1
  17. package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
  18. package/mcp-server/tools/cms/HsListTool.js +1 -1
  19. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +2 -1
  20. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +2 -2
  21. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +2 -2
  22. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +2 -2
  23. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +2 -2
  24. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +2 -2
  25. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +20 -3
  26. package/mcp-server/tools/project/AddFeatureToProjectTool.js +7 -11
  27. package/mcp-server/tools/project/CreateProjectTool.d.ts +24 -4
  28. package/mcp-server/tools/project/CreateProjectTool.js +6 -11
  29. package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
  30. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  31. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +5 -8
  32. package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
  33. package/mcp-server/tools/project/GetBuildLogsTool.js +6 -7
  34. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
  35. package/mcp-server/tools/project/GetBuildStatusTool.js +3 -4
  36. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +6 -1
  37. package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -6
  38. package/mcp-server/tools/project/UploadProjectTools.js +1 -1
  39. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  40. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +2 -2
  41. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +2 -2
  42. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +2 -2
  43. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +2 -2
  44. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +0 -32
  45. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +2 -10
  46. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
  47. package/mcp-server/tools/project/constants.d.ts +12 -1
  48. package/mcp-server/tools/project/constants.js +12 -16
  49. package/mcp-server/utils/__tests__/command.test.js +3 -233
  50. package/mcp-server/utils/__tests__/project.test.d.ts +1 -0
  51. package/mcp-server/utils/__tests__/project.test.js +140 -0
  52. package/mcp-server/utils/command.d.ts +0 -5
  53. package/mcp-server/utils/command.js +0 -24
  54. package/mcp-server/utils/project.d.ts +5 -0
  55. package/mcp-server/utils/project.js +18 -0
  56. package/package.json +1 -1
@@ -1,16 +1,14 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import cliProgress from 'cli-progress';
4
3
  import { commands } from '../../../lang/en.js';
5
4
  import { getCwd } from '@hubspot/local-dev-lib/path';
6
- import { FILE_UPLOAD_RESULT_TYPES } from '@hubspot/local-dev-lib/constants/files';
7
5
  import { getThemeJSONPath } from '@hubspot/local-dev-lib/cms/themes';
8
- import { createDevServer } from '@hubspot/cms-dev-server';
9
- import { getUploadableFileList } from '../../../lib/upload.js';
6
+ // Use subprocess approach to avoid React version conflicts
7
+ // import { createDevServer } from '@hubspot/cms-dev-server';
8
+ import { spawnDevServer } from '../../../lib/cms/devServerProcess.js';
10
9
  import { trackCommandUsage } from '../../../lib/usageTracking.js';
11
10
  import { previewPrompt, previewProjectPrompt, } from '../../../lib/prompts/previewPrompt.js';
12
11
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
13
- import { ApiErrorContext, logError } from '../../../lib/errorHandlers/index.js';
14
12
  import { getProjectConfig } from '../../../lib/projects/config.js';
15
13
  import { findProjectComponents } from '../../../lib/projects/structure.js';
16
14
  import { ComponentTypes } from '../../../types/Projects.js';
@@ -73,67 +71,16 @@ async function determineSrcAndDest(args) {
73
71
  async function handler(args) {
74
72
  const { derivedAccountId, noSsl, resetSession, port, generateFieldsTypes } = args;
75
73
  const { absoluteSrc, dest } = await determineSrcAndDest(args);
76
- const filePaths = await getUploadableFileList(absoluteSrc, false);
77
- function startProgressBar(numFiles) {
78
- const initialUploadProgressBar = new cliProgress.SingleBar({
79
- gracefulExit: true,
80
- format: '[{bar}] {percentage}% | {value}/{total} | {label}',
81
- hideCursor: true,
82
- }, cliProgress.Presets.rect);
83
- initialUploadProgressBar.start(numFiles, 0, {
84
- label: commands.cms.subcommands.theme.subcommands.preview
85
- .initialUploadProgressBar.start,
86
- });
87
- let uploadsHaveStarted = false;
88
- const uploadOptions = {
89
- onAttemptCallback: () => {
90
- /* Intentionally blank */
91
- },
92
- onSuccessCallback: () => {
93
- initialUploadProgressBar.increment();
94
- if (!uploadsHaveStarted) {
95
- uploadsHaveStarted = true;
96
- initialUploadProgressBar.update(0, {
97
- label: commands.cms.subcommands.theme.subcommands.preview
98
- .initialUploadProgressBar.uploading,
99
- });
100
- }
101
- },
102
- onFirstErrorCallback: () => {
103
- /* Intentionally blank */
104
- },
105
- onRetryCallback: () => {
106
- /* Intentionally blank */
107
- },
108
- onFinalErrorCallback: () => initialUploadProgressBar.increment(),
109
- onFinishCallback: (results) => {
110
- initialUploadProgressBar.update(numFiles, {
111
- label: commands.cms.subcommands.theme.subcommands.preview
112
- .initialUploadProgressBar.finish,
113
- });
114
- initialUploadProgressBar.stop();
115
- results.forEach(result => {
116
- if (result.resultType == FILE_UPLOAD_RESULT_TYPES.FAILURE) {
117
- uiLogger.error(commands.cms.subcommands.theme.subcommands.preview.errors.uploadFailed(result.file, dest));
118
- logError(result.error, new ApiErrorContext({
119
- accountId: derivedAccountId,
120
- request: dest,
121
- payload: result.file,
122
- }));
123
- }
124
- });
125
- },
126
- };
127
- return uploadOptions;
128
- }
129
74
  trackCommandUsage('preview', {}, derivedAccountId);
130
- if (port) {
131
- process.env['PORT'] = port.toString();
132
- }
133
- createDevServer(absoluteSrc, false, '', '', !noSsl, generateFieldsTypes, {
134
- filePaths,
75
+ // Spawn dev server in isolated subprocess to avoid React version conflicts
76
+ // File listing and progress bars are handled within the subprocess
77
+ spawnDevServer({
78
+ absoluteSrc,
79
+ accountName: derivedAccountId?.toString(),
80
+ noSsl,
81
+ port,
82
+ generateFieldsTypes,
135
83
  resetSession: resetSession || false,
136
- startProgressBar,
137
84
  dest,
138
85
  });
139
86
  }
@@ -7,20 +7,14 @@ import * as errorHandlers from '../../../lib/errorHandlers/index.js';
7
7
  import * as usageTrackingLib from '../../../lib/usageTracking.js';
8
8
  import * as processLib from '../../../lib/process.js';
9
9
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
10
- // Create a mock execAsync function before importing the module
11
- const execAsyncMock = vi.fn();
10
+ import startCommand from '../start.js';
12
11
  vi.mock('yargs');
13
12
  vi.mock('../../../lib/commonOpts');
14
13
  vi.mock('node:child_process');
15
- vi.mock('node:util', () => ({
16
- promisify: vi.fn(() => execAsyncMock),
17
- }));
18
14
  vi.mock('fs');
19
15
  vi.mock('@hubspot/local-dev-lib/config');
20
16
  vi.mock('../../../lib/errorHandlers/index.js');
21
17
  vi.mock('../../../lib/process.js');
22
- // Import after mocks are set up
23
- const startCommand = await import('../start.js').then(m => m.default);
24
18
  const spawnSpy = vi.mocked(spawn);
25
19
  const existsSyncSpy = vi.spyOn(fs, 'existsSync');
26
20
  const trackCommandUsageSpy = vi.spyOn(usageTrackingLib, 'trackCommandUsage');
@@ -42,7 +36,6 @@ describe('commands/mcp/start', () => {
42
36
  processExitSpy.mockImplementation(() => { });
43
37
  // Mock config to prevent reading actual config file in CI
44
38
  getConfigAccountIfExistsSpy.mockReturnValue(undefined);
45
- execAsyncMock.mockClear();
46
39
  });
47
40
  describe('command', () => {
48
41
  it('should have the correct command structure', () => {
@@ -28,6 +28,7 @@ async function startMcpServer(aiAgent) {
28
28
  uiLogger.debug(commands.mcp.start.startingServer);
29
29
  uiLogger.debug(commands.mcp.start.stopInstructions);
30
30
  const args = [serverPath];
31
+ // Start the server using ts-node
31
32
  const child = spawn(`node`, args, {
32
33
  stdio: 'inherit',
33
34
  env: {
package/lang/en.d.ts CHANGED
@@ -1295,8 +1295,6 @@ export declare const commands: {
1295
1295
  prompts: {
1296
1296
  targets: string;
1297
1297
  targetsRequired: string;
1298
- standaloneMode: string;
1299
- cliVersion: string;
1300
1298
  };
1301
1299
  };
1302
1300
  start: {
package/lang/en.js CHANGED
@@ -1311,8 +1311,6 @@ export const commands = {
1311
1311
  prompts: {
1312
1312
  targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
1313
1313
  targetsRequired: 'Must choose at least one app to configure.',
1314
- standaloneMode: 'Do you want to run in standalone mode? (This will use npx @hubspot/cli instead of the installed hs command)',
1315
- cliVersion: 'Specify a CLI version to pin (leave blank for latest):',
1316
1314
  },
1317
1315
  },
1318
1316
  start: {
@@ -71,6 +71,8 @@ describe('lib/commandSuggestion', () => {
71
71
  // Create a mock yargs builder with strict method
72
72
  const mockYargsBuilder = {
73
73
  strict: vi.fn().mockReturnThis(),
74
+ help: vi.fn().mockReturnThis(),
75
+ version: vi.fn().mockReturnThis(),
74
76
  };
75
77
  await commandModule.builder(mockYargsBuilder);
76
78
  expect(mockYargsBuilder.strict).toHaveBeenCalledWith(false);
@@ -0,0 +1,13 @@
1
+ import { ChildProcess } from 'child_process';
2
+ interface DevServerOptions {
3
+ absoluteSrc: string;
4
+ accountName?: string;
5
+ configPath?: string;
6
+ noSsl?: boolean;
7
+ port?: number;
8
+ generateFieldsTypes?: boolean;
9
+ resetSession?: boolean;
10
+ dest?: string;
11
+ }
12
+ export declare function spawnDevServer(options: DevServerOptions): ChildProcess;
13
+ export {};
@@ -0,0 +1,188 @@
1
+ import { spawn, execSync } from 'child_process';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import os from 'os';
5
+ import { fileURLToPath } from 'url';
6
+ /**
7
+ * Ensures cms-dev-server is installed in an isolated cache directory.
8
+ * This prevents React version conflicts with the CLI.
9
+ */
10
+ function ensureCmsDevServerCache(targetVersion) {
11
+ const cacheDir = path.join(os.homedir(), '.hubspot', 'cms-dev-server-cache');
12
+ const packageJsonPath = path.join(cacheDir, 'node_modules', '@hubspot', 'cms-dev-server', 'package.json');
13
+ // Check if already installed with correct version
14
+ let needsInstall = true;
15
+ if (fs.existsSync(packageJsonPath)) {
16
+ try {
17
+ const installedPackage = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
18
+ if (installedPackage.version === targetVersion) {
19
+ needsInstall = false;
20
+ }
21
+ }
22
+ catch (e) {
23
+ // If we can't read the package.json, reinstall
24
+ needsInstall = true;
25
+ }
26
+ }
27
+ if (needsInstall) {
28
+ console.log(`Setting up isolated cms-dev-server ${targetVersion}...`);
29
+ // Create cache directory
30
+ fs.mkdirSync(cacheDir, { recursive: true });
31
+ // Clear old installation if exists
32
+ const nodeModulesDir = path.join(cacheDir, 'node_modules');
33
+ if (fs.existsSync(nodeModulesDir)) {
34
+ fs.rmSync(nodeModulesDir, { recursive: true, force: true });
35
+ }
36
+ // Install cms-dev-server with production dependencies only
37
+ try {
38
+ execSync(`npm install @hubspot/cms-dev-server@${targetVersion} --production --no-save --loglevel=error`, {
39
+ cwd: cacheDir,
40
+ stdio: 'ignore', // Suppress npm output
41
+ });
42
+ console.log('✓ cms-dev-server setup complete');
43
+ }
44
+ catch (e) {
45
+ console.error('Failed to install cms-dev-server:', e);
46
+ process.exit(1);
47
+ }
48
+ }
49
+ return cacheDir;
50
+ }
51
+ export function spawnDevServer(options) {
52
+ const { absoluteSrc, accountName, noSsl, port, generateFieldsTypes, resetSession, dest, } = options;
53
+ // Get the version from CLI's package.json
54
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
55
+ const cliPackageJsonPath = path.join(__dirname, '../../package.json');
56
+ const cliPackageJson = JSON.parse(fs.readFileSync(cliPackageJsonPath, 'utf8'));
57
+ const targetVersion = cliPackageJson.dependencies['@hubspot/cms-dev-server'];
58
+ if (!targetVersion) {
59
+ console.error('Could not determine cms-dev-server version from package.json');
60
+ process.exit(1);
61
+ }
62
+ // Ensure cms-dev-server is installed in isolated cache
63
+ const cacheDir = ensureCmsDevServerCache(targetVersion);
64
+ // Build a Node script that imports and calls createDevServer
65
+ // This runs in the isolated cache directory, completely separate from CLI's node_modules
66
+ const script = `
67
+ // Suppress library deprecation warnings (e.g., body-parser)
68
+ process.noDeprecation = true;
69
+
70
+ const { createDevServer } = await import('@hubspot/cms-dev-server');
71
+ const { walk } = await import('@hubspot/local-dev-lib/fs');
72
+ const { createIgnoreFilter } = await import('@hubspot/local-dev-lib/ignoreRules');
73
+ const { isAllowedExtension } = await import('@hubspot/local-dev-lib/path');
74
+ const { FILE_UPLOAD_RESULT_TYPES } = await import('@hubspot/local-dev-lib/constants/files');
75
+ const cliProgress = (await import('cli-progress')).default;
76
+
77
+ ${dest
78
+ ? `
79
+ // Get uploadable files for preview
80
+ let filePaths = [];
81
+ try {
82
+ filePaths = await walk(${JSON.stringify(absoluteSrc)});
83
+ } catch (e) {
84
+ console.error('Error walking directory:', e);
85
+ }
86
+ filePaths = filePaths
87
+ .filter(file => isAllowedExtension(file))
88
+ .filter(createIgnoreFilter(false));
89
+
90
+ // Create progress bar for initial upload
91
+ function startProgressBar(numFiles) {
92
+ const initialUploadProgressBar = new cliProgress.SingleBar(
93
+ {
94
+ gracefulExit: true,
95
+ format: '[{bar}] {percentage}% | {value}/{total} | {label}',
96
+ hideCursor: true,
97
+ },
98
+ cliProgress.Presets.rect
99
+ );
100
+ initialUploadProgressBar.start(numFiles, 0, {
101
+ label: 'Preparing upload...',
102
+ });
103
+ let uploadsHaveStarted = false;
104
+ return {
105
+ onAttemptCallback: () => {},
106
+ onSuccessCallback: () => {
107
+ initialUploadProgressBar.increment();
108
+ if (!uploadsHaveStarted) {
109
+ uploadsHaveStarted = true;
110
+ initialUploadProgressBar.update(0, {
111
+ label: 'Uploading files...',
112
+ });
113
+ }
114
+ },
115
+ onFirstErrorCallback: () => {},
116
+ onRetryCallback: () => {},
117
+ onFinalErrorCallback: () => initialUploadProgressBar.increment(),
118
+ onFinishCallback: (results) => {
119
+ initialUploadProgressBar.update(numFiles, {
120
+ label: 'Upload complete',
121
+ });
122
+ initialUploadProgressBar.stop();
123
+ results.forEach(result => {
124
+ if (result.resultType == FILE_UPLOAD_RESULT_TYPES.FAILURE) {
125
+ console.error(\`Failed to upload \${result.file}\`);
126
+ }
127
+ });
128
+ },
129
+ };
130
+ }
131
+
132
+ const themePreviewOptions = {
133
+ filePaths,
134
+ startProgressBar,
135
+ resetSession: ${Boolean(resetSession)},
136
+ dest: ${JSON.stringify(dest)},
137
+ };
138
+ `
139
+ : 'const themePreviewOptions = undefined;'}
140
+
141
+ createDevServer(
142
+ ${JSON.stringify(absoluteSrc)},
143
+ false, // storybook
144
+ '', // config (uses env var HUBSPOT_CONFIG_PATH)
145
+ ${JSON.stringify(accountName || '')},
146
+ ${!noSsl}, // sslEnabled
147
+ ${Boolean(generateFieldsTypes)}, // fieldGenEnabled
148
+ themePreviewOptions
149
+ );
150
+ `;
151
+ // Set environment variables
152
+ const env = { ...process.env };
153
+ if (port) {
154
+ env.PORT = port.toString();
155
+ }
156
+ // Suppress Node.js deprecation warnings
157
+ env.NODE_NO_WARNINGS = '1';
158
+ // Spawn Node with the inline script from the isolated cache directory
159
+ // This ensures complete isolation from CLI's React 19
160
+ const devServer = spawn('node', ['--input-type=module', '-e', script], {
161
+ stdio: 'inherit',
162
+ env,
163
+ cwd: cacheDir,
164
+ });
165
+ // Handle process events
166
+ devServer.on('error', error => {
167
+ console.error('Failed to start dev server:', error);
168
+ process.exit(1);
169
+ });
170
+ devServer.on('exit', (code, signal) => {
171
+ if (code !== 0 && code !== null) {
172
+ console.error(`Dev server exited with code ${code}`);
173
+ process.exit(code);
174
+ }
175
+ if (signal) {
176
+ console.error(`Dev server killed with signal ${signal}`);
177
+ process.exit(1);
178
+ }
179
+ });
180
+ // Handle CLI termination
181
+ process.on('SIGINT', () => {
182
+ devServer.kill('SIGINT');
183
+ });
184
+ process.on('SIGTERM', () => {
185
+ devServer.kill('SIGTERM');
186
+ });
187
+ return devServer;
188
+ }
@@ -18,12 +18,6 @@ export const commandSuggestionMappings = {
18
18
  'theme generate-selectors': 'hs cms theme generate-selectors',
19
19
  'theme marketplace-validate': 'hs cms theme marketplace-validate',
20
20
  'theme preview': 'hs cms theme preview',
21
- 'custom-object schema create': 'hs custom-object create-schema',
22
- 'custom-object schema delete': 'hs custom-object delete-schema',
23
- 'custom-object schema fetch-all': 'hs custom-object fetch-all-schemas',
24
- 'custom-object schema fetch': 'hs custom-object fetch-schema',
25
- 'custom-object schema list': 'hs custom-object list-schemas',
26
- 'custom-object schema update': 'hs custom-object update-schema',
27
21
  };
28
22
  function createCommandSuggestionHandler(newCommand) {
29
23
  return () => {
@@ -35,7 +29,7 @@ function createCommandSuggestion(oldCommand, newCommand) {
35
29
  return {
36
30
  command: oldCommand,
37
31
  builder: async (yargs) => {
38
- return yargs.strict(false);
32
+ return yargs.strict(false).help(false).version(false);
39
33
  },
40
34
  handler: createCommandSuggestionHandler(newCommand),
41
35
  };
@@ -132,21 +132,6 @@ describe('lib/mcp/setup', () => {
132
132
  });
133
133
  expect(mockedLogError).toHaveBeenCalledWith(error);
134
134
  });
135
- it('should pass through environment variables in command', async () => {
136
- const mockMcpCommandWithEnv = {
137
- command: 'test-command',
138
- args: ['--arg1'],
139
- env: { HUBSPOT_MCP_STANDALONE: 'true' },
140
- };
141
- mockedExecAsync.mockResolvedValueOnce({
142
- stdout: 'codex version 1.0.0',
143
- stderr: '',
144
- });
145
- mockedExecAsync.mockResolvedValueOnce({ stdout: '', stderr: '' });
146
- const result = await setupCodex(mockMcpCommandWithEnv);
147
- expect(result).toBe(true);
148
- expect(mockedExecAsync).toHaveBeenCalledWith('codex mcp add "HubSpotDev" -- test-command --arg1 --ai-agent codex --env HUBSPOT_MCP_STANDALONE=true');
149
- });
150
135
  });
151
136
  describe('setupGemini', () => {
152
137
  const mockMcpCommand = {
@@ -5,7 +5,6 @@ export declare const supportedTools: {
5
5
  interface McpCommand {
6
6
  command: string;
7
7
  args: string[];
8
- env?: Record<string, string>;
9
8
  }
10
9
  export declare function addMcpServerToConfig(targets: string[] | undefined): Promise<string[]>;
11
10
  export declare function setupVsCode(mcpCommand?: McpCommand): Promise<boolean>;
package/lib/mcp/setup.js CHANGED
@@ -47,56 +47,23 @@ export async function addMcpServerToConfig(targets) {
47
47
  else {
48
48
  derivedTargets = targets;
49
49
  }
50
- // Prompt for standalone mode
51
- const { useStandaloneMode } = await promptUser({
52
- name: 'useStandaloneMode',
53
- type: 'confirm',
54
- message: commands.mcp.setup.prompts.standaloneMode,
55
- default: false,
56
- });
57
- const { cliVersion } = useStandaloneMode
58
- ? await promptUser({
59
- name: 'cliVersion',
60
- type: 'input',
61
- message: commands.mcp.setup.prompts.cliVersion,
62
- validate: (v) => !v || /^[\d]+\.[\d]+\.[\d]+([-+][\w.]+)?$/.test(v.trim())
63
- ? true
64
- : 'Please enter a valid semver version (e.g. 8.0.1) or leave blank for latest.',
65
- })
66
- : { cliVersion: '' };
67
- const cliPackage = cliVersion
68
- ? `@hubspot/cli@${cliVersion}`
69
- : '@hubspot/cli';
70
- const standaloneEnv = {
71
- HUBSPOT_MCP_STANDALONE: 'true',
72
- };
73
- if (cliVersion) {
74
- standaloneEnv.HUBSPOT_CLI_VERSION = cliVersion;
75
- }
76
- const mcpCommand = useStandaloneMode
77
- ? {
78
- command: 'npx',
79
- args: ['-y', '-p', cliPackage, 'hs', 'mcp', 'start'],
80
- env: standaloneEnv,
81
- }
82
- : defaultMcpCommand;
83
50
  if (derivedTargets.includes(claudeCode)) {
84
- await runSetupFunction(() => setupClaudeCode(mcpCommand));
51
+ await runSetupFunction(setupClaudeCode);
85
52
  }
86
53
  if (derivedTargets.includes(cursor)) {
87
- await runSetupFunction(() => setupCursor(mcpCommand));
54
+ await runSetupFunction(setupCursor);
88
55
  }
89
56
  if (derivedTargets.includes(windsurf)) {
90
- await runSetupFunction(() => setupWindsurf(mcpCommand));
57
+ await runSetupFunction(setupWindsurf);
91
58
  }
92
59
  if (derivedTargets.includes(vscode)) {
93
- await runSetupFunction(() => setupVsCode(mcpCommand));
60
+ await runSetupFunction(setupVsCode);
94
61
  }
95
62
  if (derivedTargets.includes(codex)) {
96
- await runSetupFunction(() => setupCodex(mcpCommand));
63
+ await runSetupFunction(setupCodex);
97
64
  }
98
65
  if (derivedTargets.includes(gemini)) {
99
- await runSetupFunction(() => setupGemini(mcpCommand));
66
+ await runSetupFunction(setupGemini);
100
67
  }
101
68
  uiLogger.info(commands.mcp.setup.success(derivedTargets));
102
69
  return derivedTargets;
@@ -155,14 +122,9 @@ function setupMcpConfigFile(config) {
155
122
  mcpConfig.mcpServers = {};
156
123
  }
157
124
  // Add or update HubSpot CLI MCP server
158
- const serverConfig = {
159
- command: config.mcpCommand.command,
160
- args: config.mcpCommand.args,
125
+ mcpConfig.mcpServers[mcpServerName] = {
126
+ ...config.mcpCommand,
161
127
  };
162
- if (config.mcpCommand.env) {
163
- serverConfig.env = config.mcpCommand.env;
164
- }
165
- mcpConfig.mcpServers[mcpServerName] = serverConfig;
166
128
  // Write the updated config
167
129
  fs.writeFileSync(config.configPath, JSON.stringify(mcpConfig, null, 2));
168
130
  SpinniesManager.succeed('spinner', {
@@ -183,16 +145,10 @@ export async function setupVsCode(mcpCommand = defaultMcpCommand) {
183
145
  SpinniesManager.add('vsCode', {
184
146
  text: commands.mcp.setup.spinners.configuringVsCode,
185
147
  });
186
- const commandWithAgent = buildCommandWithAgentString(mcpCommand, vscode);
187
- const configObject = {
148
+ const mcpConfig = JSON.stringify({
188
149
  name: mcpServerName,
189
- command: commandWithAgent.command,
190
- args: commandWithAgent.args,
191
- };
192
- if (commandWithAgent.env) {
193
- configObject.env = commandWithAgent.env;
194
- }
195
- const mcpConfig = JSON.stringify(configObject);
150
+ ...buildCommandWithAgentString(mcpCommand, vscode),
151
+ });
196
152
  await execAsync(`code --add-mcp ${JSON.stringify(mcpConfig)}`);
197
153
  SpinniesManager.succeed('vsCode', {
198
154
  text: commands.mcp.setup.spinners.configuredVsCode,
@@ -223,44 +179,30 @@ export async function setupClaudeCode(mcpCommand = defaultMcpCommand) {
223
179
  try {
224
180
  // Check if claude command is available
225
181
  await execAsync('claude --version');
226
- // Run claude mcp add command
227
- const commandWithAgent = buildCommandWithAgentString(mcpCommand, claudeCode);
228
- const configObject = {
229
- type: 'stdio',
230
- command: commandWithAgent.command,
231
- args: commandWithAgent.args,
232
- };
233
- if (commandWithAgent.env) {
234
- configObject.env = commandWithAgent.env;
235
- }
236
- const mcpConfig = JSON.stringify(configObject);
237
- const { stdout } = await execAsync('claude mcp list');
238
- if (stdout.includes(mcpServerName)) {
239
- SpinniesManager.update('claudeCode', {
240
- text: commands.mcp.setup.spinners.alreadyInstalled,
241
- });
242
- await execAsync(`claude mcp remove "${mcpServerName}" --scope user`);
243
- }
244
- await execAsync(`claude mcp add-json "${mcpServerName}" ${JSON.stringify(mcpConfig)} --scope user`);
245
- SpinniesManager.succeed('claudeCode', {
246
- text: commands.mcp.setup.spinners.configuredClaudeCode,
247
- });
248
- return true;
249
182
  }
250
- catch (error) {
251
- if (error instanceof Error && error.message.includes('claude')) {
252
- SpinniesManager.fail('claudeCode', {
253
- text: commands.mcp.setup.spinners.claudeCodeNotFound,
254
- });
255
- }
256
- else {
257
- SpinniesManager.fail('claudeCode', {
258
- text: commands.mcp.setup.spinners.claudeCodeInstallFailed,
259
- });
260
- logError(error);
261
- }
183
+ catch (e) {
184
+ SpinniesManager.fail('claudeCode', {
185
+ text: commands.mcp.setup.spinners.claudeCodeNotFound,
186
+ });
262
187
  return false;
263
188
  }
189
+ // Run claude mcp add command
190
+ const mcpConfig = JSON.stringify({
191
+ type: 'stdio',
192
+ ...buildCommandWithAgentString(mcpCommand, claudeCode),
193
+ });
194
+ const { stdout } = await execAsync('claude mcp list');
195
+ if (stdout.includes(mcpServerName)) {
196
+ SpinniesManager.update('claudeCode', {
197
+ text: commands.mcp.setup.spinners.alreadyInstalled,
198
+ });
199
+ await execAsync(`claude mcp remove "${mcpServerName}" --scope user`);
200
+ }
201
+ await execAsync(`claude mcp add-json "${mcpServerName}" ${JSON.stringify(mcpConfig)} --scope user`);
202
+ SpinniesManager.succeed('claudeCode', {
203
+ text: commands.mcp.setup.spinners.configuredClaudeCode,
204
+ });
205
+ return true;
264
206
  }
265
207
  catch (error) {
266
208
  SpinniesManager.fail('claudeCode', {
@@ -306,7 +248,7 @@ export async function setupCodex(mcpCommand = defaultMcpCommand) {
306
248
  return false;
307
249
  }
308
250
  const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, codex);
309
- await execAsync(`codex mcp add "${mcpServerName}" -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}${buildEnvFlagString(mcpCommand)}`);
251
+ await execAsync(`codex mcp add "${mcpServerName}" -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
310
252
  SpinniesManager.succeed('codexSpinner', {
311
253
  text: commands.mcp.setup.spinners.configuredCodex,
312
254
  });
@@ -335,7 +277,7 @@ export async function setupGemini(mcpCommand = defaultMcpCommand) {
335
277
  return false;
336
278
  }
337
279
  const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, gemini);
338
- await execAsync(`gemini mcp add -s user "${mcpServerName}" ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}${buildEnvFlagString(mcpCommand)}`);
280
+ await execAsync(`gemini mcp add -s user "${mcpServerName}" ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
339
281
  SpinniesManager.succeed('geminiSpinner', {
340
282
  text: commands.mcp.setup.spinners.configuredGemini,
341
283
  });
@@ -354,16 +296,3 @@ function buildCommandWithAgentString(mcpCommand, agent) {
354
296
  mcpCommandCopy.args.push('--ai-agent', agent);
355
297
  return mcpCommandCopy;
356
298
  }
357
- function buildEnvFlagString(mcpCommand) {
358
- const envFlags = [];
359
- if (mcpCommand.env) {
360
- const env = Object.entries(mcpCommand.env);
361
- env.forEach(([key, value]) => {
362
- envFlags.push(`--env ${key}=${value}`);
363
- });
364
- }
365
- if (envFlags.length === 0) {
366
- return '';
367
- }
368
- return ` ${envFlags.join(' ')}`;
369
- }
@@ -1,7 +1,7 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
4
- import { runCommandInDir } from '../../utils/command.js';
4
+ import { runCommandInDir } from '../../utils/project.js';
5
5
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
6
6
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
7
  import { addFlag } from '../../utils/command.js';
@@ -1,7 +1,7 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
4
- import { runCommandInDir } from '../../utils/command.js';
4
+ import { runCommandInDir } from '../../utils/project.js';
5
5
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
6
6
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
7
  import { addFlag } from '../../utils/command.js';
@@ -1,7 +1,7 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
4
- import { runCommandInDir } from '../../utils/command.js';
4
+ import { runCommandInDir } from '../../utils/project.js';
5
5
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
6
6
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
7
  import { addFlag } from '../../utils/command.js';
@@ -2,7 +2,7 @@ import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { addFlag } from '../../utils/command.js';
4
4
  import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
5
- import { runCommandInDir } from '../../utils/command.js';
5
+ import { runCommandInDir } from '../../utils/project.js';
6
6
  import { formatTextContents } from '../../utils/content.js';
7
7
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
8
8
  import { setupHubSpotConfig } from '../../utils/config.js';