@hubspot/cli 8.0.8-experimental.1 → 8.0.8-experimental.2

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 (49) hide show
  1. package/commands/mcp/__tests__/start.test.js +8 -1
  2. package/commands/mcp/start.js +0 -1
  3. package/lang/en.d.ts +7 -0
  4. package/lang/en.js +8 -1
  5. package/lib/getStartedV2Actions.d.ts +13 -0
  6. package/lib/getStartedV2Actions.js +53 -0
  7. package/lib/mcp/__tests__/setup.test.js +15 -0
  8. package/lib/mcp/setup.d.ts +1 -0
  9. package/lib/mcp/setup.js +105 -34
  10. package/mcp-server/tools/cms/HsCreateFunctionTool.js +1 -1
  11. package/mcp-server/tools/cms/HsCreateModuleTool.js +1 -1
  12. package/mcp-server/tools/cms/HsCreateTemplateTool.js +1 -1
  13. package/mcp-server/tools/cms/HsFunctionLogsTool.js +1 -1
  14. package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
  15. package/mcp-server/tools/cms/HsListTool.js +1 -1
  16. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -2
  17. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +2 -2
  18. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +2 -2
  19. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +2 -2
  20. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +2 -2
  21. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +2 -2
  22. package/mcp-server/tools/project/AddFeatureToProjectTool.js +1 -1
  23. package/mcp-server/tools/project/CreateProjectTool.js +1 -1
  24. package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
  25. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  26. package/mcp-server/tools/project/UploadProjectTools.js +1 -1
  27. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  28. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +2 -2
  29. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +2 -2
  30. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +2 -2
  31. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +2 -2
  32. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +10 -2
  33. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
  34. package/mcp-server/utils/__tests__/command.test.js +233 -3
  35. package/mcp-server/utils/command.d.ts +5 -0
  36. package/mcp-server/utils/command.js +24 -0
  37. package/package.json +2 -8
  38. package/ui/components/getStarted/GetStartedFlow.js +79 -2
  39. package/ui/components/getStarted/reducer.d.ts +20 -0
  40. package/ui/components/getStarted/reducer.js +36 -0
  41. package/ui/components/getStarted/screens/InstallationScreen.d.ts +7 -0
  42. package/ui/components/getStarted/screens/InstallationScreen.js +16 -0
  43. package/ui/components/getStarted/screens/ProjectSetupScreen.js +2 -1
  44. package/ui/lib/constants.d.ts +1 -0
  45. package/ui/lib/constants.js +1 -0
  46. package/mcp-server/utils/__tests__/project.test.d.ts +0 -1
  47. package/mcp-server/utils/__tests__/project.test.js +0 -140
  48. package/mcp-server/utils/project.d.ts +0 -5
  49. package/mcp-server/utils/project.js +0 -18
@@ -7,14 +7,20 @@ 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
- import startCommand from '../start.js';
10
+ // Create a mock execAsync function before importing the module
11
+ const execAsyncMock = vi.fn();
11
12
  vi.mock('yargs');
12
13
  vi.mock('../../../lib/commonOpts');
13
14
  vi.mock('node:child_process');
15
+ vi.mock('node:util', () => ({
16
+ promisify: vi.fn(() => execAsyncMock),
17
+ }));
14
18
  vi.mock('fs');
15
19
  vi.mock('@hubspot/local-dev-lib/config');
16
20
  vi.mock('../../../lib/errorHandlers/index.js');
17
21
  vi.mock('../../../lib/process.js');
22
+ // Import after mocks are set up
23
+ const startCommand = await import('../start.js').then(m => m.default);
18
24
  const spawnSpy = vi.mocked(spawn);
19
25
  const existsSyncSpy = vi.spyOn(fs, 'existsSync');
20
26
  const trackCommandUsageSpy = vi.spyOn(usageTrackingLib, 'trackCommandUsage');
@@ -36,6 +42,7 @@ describe('commands/mcp/start', () => {
36
42
  processExitSpy.mockImplementation(() => { });
37
43
  // Mock config to prevent reading actual config file in CI
38
44
  getConfigAccountIfExistsSpy.mockReturnValue(undefined);
45
+ execAsyncMock.mockClear();
39
46
  });
40
47
  describe('command', () => {
41
48
  it('should have the correct command structure', () => {
@@ -28,7 +28,6 @@ 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
32
31
  const child = spawn(`node`, args, {
33
32
  stdio: 'inherit',
34
33
  env: {
package/lang/en.d.ts CHANGED
@@ -43,6 +43,11 @@ export declare const commands: {
43
43
  checkOutConfig: (configPath: string) => string;
44
44
  pressEnterToInstall: (accountName: string) => string;
45
45
  pressKeyToExit: string;
46
+ installingApp: (appName: string, accountName: string) => string;
47
+ installInstructions: string;
48
+ browserFailedToOpen: (url: string) => string;
49
+ pollingTimeout: (minutes: number) => string;
50
+ pressEnterToContinueSetup: string;
46
51
  prompts: {
47
52
  selectOptionV2: string;
48
53
  options: {
@@ -1290,6 +1295,8 @@ export declare const commands: {
1290
1295
  prompts: {
1291
1296
  targets: string;
1292
1297
  targetsRequired: string;
1298
+ standaloneMode: string;
1299
+ cliVersion: string;
1293
1300
  };
1294
1301
  };
1295
1302
  start: {
package/lang/en.js CHANGED
@@ -51,6 +51,11 @@ export const commands = {
51
51
  checkOutConfig: (configPath) => `Check out ${chalk.cyan(configPath)} for the full configuration.`,
52
52
  pressEnterToInstall: (accountName) => `? Press ${chalk.bold('<enter>')} to continue installing and previewing this app in ${chalk.bold(accountName)}`,
53
53
  pressKeyToExit: `Press any key to exit...`,
54
+ installingApp: (appName, accountName) => `Installing ${chalk.bold(appName)} in ${chalk.bold(accountName)}...`,
55
+ installInstructions: `We'll take you to your HubSpot account and walk you through installing your app.`,
56
+ browserFailedToOpen: (url) => `⚠️ Failed to open browser automatically. Please open this URL manually:\n${chalk.cyan(url)}`,
57
+ pollingTimeout: (minutes) => `⚠️ Installation polling timed out after ${minutes} minutes. The app may still be installing in the background.`,
58
+ pressEnterToContinueSetup: `Press ${chalk.bold('<enter>')} to continue with card setup...`,
54
59
  prompts: {
55
60
  selectOptionV2: 'Choose a component type to get started',
56
61
  options: {
@@ -1306,6 +1311,8 @@ export const commands = {
1306
1311
  prompts: {
1307
1312
  targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
1308
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):',
1309
1316
  },
1310
1317
  },
1311
1318
  start: {
@@ -3076,7 +3083,7 @@ export const lib = {
3076
3083
  updateSucceeded: (latestVersion) => `Successfully updated HubSpot CLI to version ${chalk.bold(latestVersion)}`,
3077
3084
  notInstalledGlobally: 'Cannot auto-update the HubSpot CLI because NPM is not installed globally',
3078
3085
  updateFailed: (latestVersion) => `Failed to update HubSpot CLI to version ${chalk.bold(latestVersion)}`,
3079
- enableAutoUpdatesMessage: `The HubSpot CLI can automatically keep itself up to date.\n\nThis helps ensure compatibility with the HubSpot platform. You can change this later at any time.\n\nRun${uiCommandReference('hs config set --allow-auto-updates=true')}`,
3086
+ enableAutoUpdatesMessage: `The HubSpot CLI can automatically keep itself up to date.\n\nThis helps ensure compatibility with the HubSpot platform. You can change this later at any time.\n\nRun ${uiCommandReference('hs config set --allow-auto-updates=true')}`,
3080
3087
  },
3081
3088
  },
3082
3089
  projectProfiles: {
@@ -35,3 +35,16 @@ export declare function uploadAndDeployAction({ accountId, projectDest, }: {
35
35
  projectDest: string;
36
36
  }): Promise<UploadAndDeployResult>;
37
37
  export declare function trackGetStartedUsage(params: Record<string, unknown>, accountId: number): Promise<void>;
38
+ export type PollAppInstallationOptions = {
39
+ accountId: number;
40
+ projectId: number;
41
+ appUid: string;
42
+ requiredScopes?: string[];
43
+ optionalScopes?: string[];
44
+ timeoutMs?: number;
45
+ intervalMs?: number;
46
+ onTimeout?: () => void;
47
+ };
48
+ export declare function pollAppInstallation({ accountId, projectId, appUid, requiredScopes, optionalScopes, timeoutMs, // 2 minutes
49
+ intervalMs, // 2 seconds
50
+ onTimeout, }: PollAppInstallationOptions): Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import { fetchPublicAppsForPortal } from '@hubspot/local-dev-lib/api/appsDev';
4
+ import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
4
5
  import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
5
6
  import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
6
7
  import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
@@ -144,3 +145,55 @@ export async function uploadAndDeployAction({ accountId, projectDest, }) {
144
145
  export function trackGetStartedUsage(params, accountId) {
145
146
  return trackCommandMetadataUsage('get-started', params, accountId);
146
147
  }
148
+ export async function pollAppInstallation({ accountId, projectId, appUid, requiredScopes = [], optionalScopes = [], timeoutMs = 2 * 60 * 1000, // 2 minutes
149
+ intervalMs = 2000, // 2 seconds
150
+ onTimeout, }) {
151
+ return new Promise((resolve, reject) => {
152
+ let consecutiveErrors = 0;
153
+ const MAX_CONSECUTIVE_ERRORS = 5;
154
+ let pollInterval = null;
155
+ let pollTimeout = null;
156
+ const cleanup = () => {
157
+ if (pollInterval) {
158
+ clearTimeout(pollInterval);
159
+ pollInterval = null;
160
+ }
161
+ if (pollTimeout) {
162
+ clearTimeout(pollTimeout);
163
+ pollTimeout = null;
164
+ }
165
+ };
166
+ pollTimeout = setTimeout(() => {
167
+ cleanup();
168
+ if (onTimeout) {
169
+ onTimeout();
170
+ }
171
+ resolve(); // Resolve instead of reject to allow continuing with timeout state
172
+ }, timeoutMs);
173
+ const poll = async () => {
174
+ try {
175
+ const { data } = await fetchAppInstallationData(accountId, projectId, appUid, requiredScopes, optionalScopes);
176
+ // Reset error counter on successful fetch
177
+ consecutiveErrors = 0;
178
+ if (data.isInstalledWithScopeGroups) {
179
+ cleanup();
180
+ resolve();
181
+ }
182
+ else if (pollInterval) {
183
+ pollInterval = setTimeout(poll, intervalMs);
184
+ }
185
+ }
186
+ catch (error) {
187
+ consecutiveErrors++;
188
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
189
+ cleanup();
190
+ reject(new Error(`Failed to check app installation status after ${MAX_CONSECUTIVE_ERRORS} consecutive errors`, { cause: error }));
191
+ }
192
+ else if (pollInterval !== null) {
193
+ pollInterval = setTimeout(poll, intervalMs);
194
+ }
195
+ }
196
+ };
197
+ pollInterval = setTimeout(poll, 0);
198
+ });
199
+ }
@@ -132,6 +132,21 @@ 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" --env HUBSPOT_MCP_STANDALONE=true -- test-command --arg1 --ai-agent codex');
149
+ });
135
150
  });
136
151
  describe('setupGemini', () => {
137
152
  const mockMcpCommand = {
@@ -5,6 +5,7 @@ export declare const supportedTools: {
5
5
  interface McpCommand {
6
6
  command: string;
7
7
  args: string[];
8
+ env?: Record<string, string>;
8
9
  }
9
10
  export declare function addMcpServerToConfig(targets: string[] | undefined): Promise<string[]>;
10
11
  export declare function setupVsCode(mcpCommand?: McpCommand): Promise<boolean>;
package/lib/mcp/setup.js CHANGED
@@ -47,23 +47,56 @@ 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;
50
83
  if (derivedTargets.includes(claudeCode)) {
51
- await runSetupFunction(setupClaudeCode);
84
+ await runSetupFunction(() => setupClaudeCode(mcpCommand));
52
85
  }
53
86
  if (derivedTargets.includes(cursor)) {
54
- await runSetupFunction(setupCursor);
87
+ await runSetupFunction(() => setupCursor(mcpCommand));
55
88
  }
56
89
  if (derivedTargets.includes(windsurf)) {
57
- await runSetupFunction(setupWindsurf);
90
+ await runSetupFunction(() => setupWindsurf(mcpCommand));
58
91
  }
59
92
  if (derivedTargets.includes(vscode)) {
60
- await runSetupFunction(setupVsCode);
93
+ await runSetupFunction(() => setupVsCode(mcpCommand));
61
94
  }
62
95
  if (derivedTargets.includes(codex)) {
63
- await runSetupFunction(setupCodex);
96
+ await runSetupFunction(() => setupCodex(mcpCommand));
64
97
  }
65
98
  if (derivedTargets.includes(gemini)) {
66
- await runSetupFunction(setupGemini);
99
+ await runSetupFunction(() => setupGemini(mcpCommand));
67
100
  }
68
101
  uiLogger.info(commands.mcp.setup.success(derivedTargets));
69
102
  return derivedTargets;
@@ -122,9 +155,14 @@ function setupMcpConfigFile(config) {
122
155
  mcpConfig.mcpServers = {};
123
156
  }
124
157
  // Add or update HubSpot CLI MCP server
125
- mcpConfig.mcpServers[mcpServerName] = {
126
- ...config.mcpCommand,
158
+ const serverConfig = {
159
+ command: config.mcpCommand.command,
160
+ args: config.mcpCommand.args,
127
161
  };
162
+ if (config.mcpCommand.env) {
163
+ serverConfig.env = config.mcpCommand.env;
164
+ }
165
+ mcpConfig.mcpServers[mcpServerName] = serverConfig;
128
166
  // Write the updated config
129
167
  fs.writeFileSync(config.configPath, JSON.stringify(mcpConfig, null, 2));
130
168
  SpinniesManager.succeed('spinner', {
@@ -145,10 +183,16 @@ export async function setupVsCode(mcpCommand = defaultMcpCommand) {
145
183
  SpinniesManager.add('vsCode', {
146
184
  text: commands.mcp.setup.spinners.configuringVsCode,
147
185
  });
148
- const mcpConfig = JSON.stringify({
186
+ const commandWithAgent = buildCommandWithAgentString(mcpCommand, vscode);
187
+ const configObject = {
149
188
  name: mcpServerName,
150
- ...buildCommandWithAgentString(mcpCommand, vscode),
151
- });
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);
152
196
  await execAsync(`code --add-mcp ${JSON.stringify(mcpConfig)}`);
153
197
  SpinniesManager.succeed('vsCode', {
154
198
  text: commands.mcp.setup.spinners.configuredVsCode,
@@ -179,30 +223,44 @@ export async function setupClaudeCode(mcpCommand = defaultMcpCommand) {
179
223
  try {
180
224
  // Check if claude command is available
181
225
  await execAsync('claude --version');
182
- }
183
- catch (e) {
184
- SpinniesManager.fail('claudeCode', {
185
- text: commands.mcp.setup.spinners.claudeCodeNotFound,
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,
186
247
  });
187
- return false;
248
+ return true;
188
249
  }
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`);
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
+ }
262
+ return false;
200
263
  }
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;
206
264
  }
207
265
  catch (error) {
208
266
  SpinniesManager.fail('claudeCode', {
@@ -248,7 +306,7 @@ export async function setupCodex(mcpCommand = defaultMcpCommand) {
248
306
  return false;
249
307
  }
250
308
  const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, codex);
251
- await execAsync(`codex mcp add "${mcpServerName}" -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
309
+ await execAsync(`codex mcp add "${mcpServerName}"${buildEnvFlagString(mcpCommand)} -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
252
310
  SpinniesManager.succeed('codexSpinner', {
253
311
  text: commands.mcp.setup.spinners.configuredCodex,
254
312
  });
@@ -277,7 +335,7 @@ export async function setupGemini(mcpCommand = defaultMcpCommand) {
277
335
  return false;
278
336
  }
279
337
  const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, gemini);
280
- await execAsync(`gemini mcp add -s user "${mcpServerName}" ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
338
+ await execAsync(`gemini mcp add -s user${buildEnvFlagString(mcpCommand)} "${mcpServerName}" ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
281
339
  SpinniesManager.succeed('geminiSpinner', {
282
340
  text: commands.mcp.setup.spinners.configuredGemini,
283
341
  });
@@ -296,3 +354,16 @@ function buildCommandWithAgentString(mcpCommand, agent) {
296
354
  mcpCommandCopy.args.push('--ai-agent', agent);
297
355
  return mcpCommandCopy;
298
356
  }
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/project.js';
4
+ import { runCommandInDir } from '../../utils/command.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/project.js';
4
+ import { runCommandInDir } from '../../utils/command.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/project.js';
4
+ import { runCommandInDir } from '../../utils/command.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/project.js';
5
+ import { runCommandInDir } from '../../utils/command.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';
@@ -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/project.js';
5
+ import { runCommandInDir } from '../../utils/command.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';
@@ -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/project.js';
5
+ import { runCommandInDir } from '../../utils/command.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';
@@ -1,11 +1,10 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsCreateFunctionTool } from '../HsCreateFunctionTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
9
8
  vi.mock('../../../utils/command');
10
9
  vi.mock('../../../utils/toolUsageTracking');
11
10
  vi.mock('../../../utils/feedbackTracking');
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsCreateModuleTool } from '../HsCreateModuleTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../utils/toolUsageTracking');
11
11
  vi.mock('../../../utils/feedbackTracking');
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsCreateTemplateTool } from '../HsCreateTemplateTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../utils/toolUsageTracking');
11
11
  vi.mock('../../../utils/feedbackTracking');
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsFunctionLogsTool } from '../HsFunctionLogsTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../utils/toolUsageTracking');
11
11
  vi.mock('../../../utils/feedbackTracking');
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsListFunctionsTool } from '../HsListFunctionsTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../utils/toolUsageTracking');
11
11
  vi.mock('../../../utils/feedbackTracking');
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsListTool } from '../HsListTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../utils/toolUsageTracking');
11
11
  vi.mock('../../../utils/feedbackTracking');
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, } from '../../../lib/constants.js';
4
4
  import { addFlag } from '../../utils/command.js';
5
5
  import { absoluteCurrentWorkingDirectory, absoluteProjectPath, features, } from './constants.js';
6
- import { runCommandInDir } from '../../utils/project.js';
6
+ import { runCommandInDir } from '../../utils/command.js';
7
7
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
8
8
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
9
9
  import { setupHubSpotConfig } from '../../utils/config.js';
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, EMPTY_PROJECT, PROJECT_WITH_APP, } from '../../../lib/constants.js';
4
4
  import { addFlag } from '../../utils/command.js';
5
5
  import { absoluteCurrentWorkingDirectory, features } from './constants.js';
6
- import { runCommandInDir } from '../../utils/project.js';
6
+ import { runCommandInDir } from '../../utils/command.js';
7
7
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
8
8
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
9
9
  import { setupHubSpotConfig } from '../../utils/config.js';
@@ -3,7 +3,7 @@ import fs from 'fs';
3
3
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
4
4
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
5
5
  import { addFlag } from '../../utils/command.js';
6
- import { runCommandInDir } from '../../utils/project.js';
6
+ import { runCommandInDir } from '../../utils/command.js';
7
7
  import { ACCOUNT_LEVELS, ACCOUNT_LEVEL_CHOICES, } from '../../../lib/constants.js';
8
8
  import { Tool } from '../../types.js';
9
9
  import { absoluteCurrentWorkingDirectory } from './constants.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, absoluteProjectPath, } from './constants.js';
5
- import { runCommandInDir } from '../../utils/project.js';
5
+ import { runCommandInDir } from '../../utils/command.js';
6
6
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
7
7
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
8
8
  import { setupHubSpotConfig } from '../../utils/config.js';
@@ -3,7 +3,7 @@ import z from 'zod';
3
3
  import { getAllHsProfiles } from '@hubspot/project-parsing-lib/profiles';
4
4
  import { getProjectConfig } from '../../../lib/projects/config.js';
5
5
  import { Tool } from '../../types.js';
6
- import { runCommandInDir } from '../../utils/project.js';
6
+ import { runCommandInDir } from '../../utils/command.js';
7
7
  import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
8
8
  import { formatTextContent, formatTextContents } from '../../utils/content.js';
9
9
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
@@ -1,7 +1,7 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
4
- import { runCommandInDir } from '../../utils/project.js';
4
+ import { runCommandInDir } from '../../utils/command.js';
5
5
  import { formatTextContents } from '../../utils/content.js';
6
6
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
7
  import { setupHubSpotConfig } from '../../utils/config.js';
@@ -1,11 +1,11 @@
1
1
  import { AddFeatureToProjectTool, } from '../AddFeatureToProjectTool.js';
2
- import { runCommandInDir } from '../../../utils/project.js';
2
+ import { runCommandInDir } from '../../../utils/command.js';
3
3
  import { addFlag } from '../../../utils/command.js';
4
4
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, } from '../../../../lib/constants.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../../lib/constants');
11
11
  vi.mock('../../../utils/toolUsageTracking');