@hubspot/cli 7.10.0-beta.1 → 7.10.0-experimental.0

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 (201) hide show
  1. package/bin/cli.js +5 -4
  2. package/commands/__tests__/getStarted.test.js +10 -0
  3. package/commands/__tests__/project.test.js +3 -0
  4. package/commands/account/__tests__/rename.test.js +10 -3
  5. package/commands/account/auth.js +10 -14
  6. package/commands/account/clean.js +11 -19
  7. package/commands/account/createOverride.js +15 -11
  8. package/commands/account/info.js +8 -5
  9. package/commands/account/list.js +15 -19
  10. package/commands/account/remove.js +23 -22
  11. package/commands/account/removeOverride.js +6 -6
  12. package/commands/account/rename.js +2 -2
  13. package/commands/account/use.js +19 -8
  14. package/commands/app/__tests__/migrate.test.js +8 -4
  15. package/commands/app/migrate.js +2 -2
  16. package/commands/auth.js +18 -14
  17. package/commands/config/migrate.js +5 -5
  18. package/commands/customObject/createSchema.js +2 -3
  19. package/commands/customObject/updateSchema.js +2 -3
  20. package/commands/getStarted.js +2 -3
  21. package/commands/hubdb/__tests__/list.test.js +1 -0
  22. package/commands/hubdb/list.js +2 -2
  23. package/commands/init.js +36 -32
  24. package/commands/project/__tests__/deploy.test.js +10 -5
  25. package/commands/project/__tests__/devUnifiedFlow.test.js +6 -4
  26. package/commands/project/__tests__/lint.test.js +709 -0
  27. package/commands/project/__tests__/logs.test.js +4 -0
  28. package/commands/project/__tests__/validate.test.js +2 -2
  29. package/commands/project/cloneApp.js +2 -2
  30. package/commands/project/deploy.js +2 -2
  31. package/commands/project/dev/deprecatedFlow.js +4 -5
  32. package/commands/project/dev/index.js +6 -3
  33. package/commands/project/dev/unifiedFlow.js +4 -5
  34. package/commands/project/lint.d.ts +6 -0
  35. package/commands/project/lint.js +178 -0
  36. package/commands/project/logs.js +2 -3
  37. package/commands/project/profile/add.js +6 -7
  38. package/commands/project/profile/delete.js +2 -2
  39. package/commands/project/upload.js +2 -2
  40. package/commands/project/validate.js +2 -2
  41. package/commands/project.js +2 -0
  42. package/commands/sandbox/__tests__/create.test.js +14 -5
  43. package/commands/sandbox/create.js +4 -5
  44. package/commands/sandbox/delete.js +23 -20
  45. package/commands/testAccount/__tests__/create.test.js +5 -5
  46. package/commands/testAccount/create.js +2 -2
  47. package/commands/testAccount/delete.js +9 -8
  48. package/lang/en.d.ts +40 -6
  49. package/lang/en.js +54 -14
  50. package/lib/__tests__/buildAccount.test.js +22 -30
  51. package/lib/__tests__/commonOpts.test.js +9 -13
  52. package/lib/__tests__/developerTestAccounts.test.js +29 -17
  53. package/lib/__tests__/importData.test.js +20 -10
  54. package/lib/__tests__/oauth.test.js +19 -8
  55. package/lib/__tests__/sandboxSync.test.js +33 -11
  56. package/lib/__tests__/sandboxes.test.js +30 -19
  57. package/lib/__tests__/usageTracking.test.js +10 -10
  58. package/lib/__tests__/validation.test.js +32 -32
  59. package/lib/accountTypes.d.ts +9 -9
  60. package/lib/accountTypes.js +2 -4
  61. package/lib/app/__tests__/migrate.test.js +15 -0
  62. package/lib/app/__tests__/migrate_legacy.test.js +9 -0
  63. package/lib/app/migrate_legacy.d.ts +2 -2
  64. package/lib/buildAccount.d.ts +4 -4
  65. package/lib/buildAccount.js +7 -14
  66. package/lib/commonOpts.js +3 -3
  67. package/lib/configMigrate.d.ts +2 -2
  68. package/lib/configMigrate.js +42 -18
  69. package/lib/configOptions.js +3 -2
  70. package/lib/developerTestAccounts.d.ts +3 -3
  71. package/lib/developerTestAccounts.js +4 -7
  72. package/lib/doctor/DiagnosticInfoBuilder.d.ts +1 -1
  73. package/lib/doctor/DiagnosticInfoBuilder.js +9 -6
  74. package/lib/doctor/Doctor.js +4 -3
  75. package/lib/doctor/__tests__/Diagnosis.test.js +4 -3
  76. package/lib/doctor/__tests__/DiagnosticInfoBuilder.test.js +17 -9
  77. package/lib/doctor/__tests__/Doctor.test.js +14 -0
  78. package/lib/importData.js +8 -7
  79. package/lib/links.js +5 -5
  80. package/lib/middleware/{__test__ → __tests__}/commandTargetingUtils.test.js +3 -3
  81. package/lib/middleware/{__test__ → __tests__}/configMiddleware.test.js +23 -22
  82. package/lib/middleware/{__test__ → __tests__}/gitMiddleware.test.js +9 -7
  83. package/lib/middleware/autoUpdateMiddleware.js +12 -4
  84. package/lib/middleware/commandTargetingUtils.js +3 -2
  85. package/lib/middleware/configMiddleware.d.ts +6 -1
  86. package/lib/middleware/configMiddleware.js +36 -15
  87. package/lib/middleware/gitMiddleware.js +8 -4
  88. package/lib/oauth.d.ts +2 -2
  89. package/lib/oauth.js +8 -10
  90. package/lib/projects/__tests__/AppDevModeInterface.test.js +17 -6
  91. package/lib/projects/__tests__/DevServerManager.test.js +1 -0
  92. package/lib/projects/__tests__/LocalDevProcess.test.js +1 -0
  93. package/lib/projects/__tests__/components.test.js +1 -1
  94. package/lib/projects/__tests__/deploy.test.js +1 -0
  95. package/lib/projects/__tests__/uieLinting.test.js +640 -0
  96. package/lib/projects/components.js +1 -1
  97. package/lib/projects/create/__tests__/v2.test.js +11 -0
  98. package/lib/projects/localDev/AppDevModeInterface.js +2 -2
  99. package/lib/projects/localDev/DevServerManager_DEPRECATED.js +2 -2
  100. package/lib/projects/localDev/DevSessionManager.d.ts +17 -0
  101. package/lib/projects/localDev/DevSessionManager.js +56 -0
  102. package/lib/projects/localDev/LocalDevLogger.d.ts +3 -0
  103. package/lib/projects/localDev/LocalDevLogger.js +13 -4
  104. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -3
  105. package/lib/projects/localDev/LocalDevProcess.d.ts +1 -0
  106. package/lib/projects/localDev/LocalDevProcess.js +12 -1
  107. package/lib/projects/localDev/LocalDevState.d.ts +3 -0
  108. package/lib/projects/localDev/LocalDevState.js +9 -0
  109. package/lib/projects/localDev/helpers/account.d.ts +10 -10
  110. package/lib/projects/localDev/helpers/account.js +6 -11
  111. package/lib/projects/localDev/helpers/devSessionsApi.d.ts +9 -0
  112. package/lib/projects/localDev/helpers/devSessionsApi.js +19 -0
  113. package/lib/projects/uieLinting.d.ts +33 -0
  114. package/lib/projects/uieLinting.js +222 -0
  115. package/lib/projects/urls.js +5 -6
  116. package/lib/prompts/__tests__/downloadProjectPrompt.test.js +7 -5
  117. package/lib/prompts/accountNamePrompt.js +3 -3
  118. package/lib/prompts/accountsPrompt.d.ts +1 -1
  119. package/lib/prompts/accountsPrompt.js +6 -7
  120. package/lib/prompts/confirmImportDataPrompt.js +2 -2
  121. package/lib/prompts/downloadProjectPrompt.d.ts +1 -0
  122. package/lib/prompts/downloadProjectPrompt.js +5 -2
  123. package/lib/prompts/importDataTestAccountSelectPrompt.js +4 -5
  124. package/lib/prompts/personalAccessKeyPrompt.js +2 -2
  125. package/lib/prompts/projectDevTargetAccountPrompt.d.ts +3 -3
  126. package/lib/prompts/projectDevTargetAccountPrompt.js +5 -7
  127. package/lib/prompts/sandboxesPrompt.js +7 -8
  128. package/lib/prompts/setAsDefaultAccountPrompt.js +7 -6
  129. package/lib/sandboxSync.d.ts +2 -2
  130. package/lib/sandboxSync.js +3 -9
  131. package/lib/sandboxes.d.ts +4 -4
  132. package/lib/sandboxes.js +6 -11
  133. package/lib/serverlessLogs.js +2 -2
  134. package/lib/theme/__tests__/migrate.test.js +15 -0
  135. package/lib/ui/index.js +6 -3
  136. package/lib/usageTracking.js +15 -8
  137. package/lib/validation.js +13 -11
  138. package/mcp-server/tools/cms/HsCreateFunctionTool.js +4 -2
  139. package/mcp-server/tools/cms/HsCreateModuleTool.js +4 -2
  140. package/mcp-server/tools/cms/HsCreateTemplateTool.js +4 -2
  141. package/mcp-server/tools/cms/HsFunctionLogsTool.js +4 -2
  142. package/mcp-server/tools/cms/HsListFunctionsTool.js +3 -1
  143. package/mcp-server/tools/cms/HsListTool.js +3 -1
  144. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -0
  145. package/mcp-server/tools/index.js +4 -0
  146. package/mcp-server/tools/project/AddFeatureToProjectTool.js +4 -2
  147. package/mcp-server/tools/project/CreateProjectTool.js +4 -2
  148. package/mcp-server/tools/project/CreateTestAccountTool.js +42 -19
  149. package/mcp-server/tools/project/DeployProjectTool.js +3 -1
  150. package/mcp-server/tools/project/DocFetchTool.js +6 -4
  151. package/mcp-server/tools/project/DocsSearchTool.d.ts +1 -1
  152. package/mcp-server/tools/project/DocsSearchTool.js +10 -8
  153. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +1 -1
  154. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +9 -7
  155. package/mcp-server/tools/project/GetApplicationInfoTool.js +8 -6
  156. package/mcp-server/tools/project/GetBuildLogsTool.d.ts +26 -0
  157. package/mcp-server/tools/project/GetBuildLogsTool.js +125 -0
  158. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +26 -0
  159. package/mcp-server/tools/project/GetBuildStatusTool.js +166 -0
  160. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +1 -1
  161. package/mcp-server/tools/project/GetConfigValuesTool.js +9 -7
  162. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +1 -1
  163. package/mcp-server/tools/project/GuidedWalkthroughTool.js +5 -3
  164. package/mcp-server/tools/project/UploadProjectTools.js +3 -1
  165. package/mcp-server/tools/project/ValidateProjectTool.js +4 -2
  166. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +226 -3
  167. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +5 -1
  168. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +23 -11
  169. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +7 -5
  170. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +7 -5
  171. package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.d.ts +1 -0
  172. package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.js +305 -0
  173. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.d.ts +1 -0
  174. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.js +240 -0
  175. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +8 -6
  176. package/mcp-server/utils/__tests__/content.test.js +21 -20
  177. package/mcp-server/utils/__tests__/feedbackTracking.test.js +34 -28
  178. package/mcp-server/utils/config.d.ts +1 -0
  179. package/mcp-server/utils/config.js +10 -0
  180. package/mcp-server/utils/content.d.ts +1 -1
  181. package/mcp-server/utils/content.js +2 -2
  182. package/mcp-server/utils/feedbackTracking.d.ts +1 -1
  183. package/mcp-server/utils/feedbackTracking.js +3 -3
  184. package/mcp-server/utils/toolUsageTracking.js +4 -3
  185. package/package.json +8 -7
  186. package/types/LocalDev.d.ts +1 -0
  187. package/lib/middleware/__test__/notificationsMiddleware.test.js +0 -8
  188. package/lib/middleware/notificationsMiddleware.d.ts +0 -1
  189. package/lib/middleware/notificationsMiddleware.js +0 -28
  190. package/mcp-server/utils/__tests__/cliConfig.test.js +0 -110
  191. package/mcp-server/utils/cliConfig.d.ts +0 -1
  192. package/mcp-server/utils/cliConfig.js +0 -12
  193. /package/{lib/middleware/__test__/commandTargetingUtils.test.d.ts → commands/project/__tests__/lint.test.d.ts} +0 -0
  194. /package/lib/middleware/{__test__/configMiddleware.test.d.ts → __tests__/commandTargetingUtils.test.d.ts} +0 -0
  195. /package/lib/middleware/{__test__/gitMiddleware.test.d.ts → __tests__/configMiddleware.test.d.ts} +0 -0
  196. /package/lib/middleware/{__test__/notificationsMiddleware.test.d.ts → __tests__/gitMiddleware.test.d.ts} +0 -0
  197. /package/lib/middleware/{__test__ → __tests__}/requestMiddleware.test.d.ts +0 -0
  198. /package/lib/middleware/{__test__ → __tests__}/requestMiddleware.test.js +0 -0
  199. /package/lib/middleware/{__test__ → __tests__}/yargsChecksMiddleware.test.d.ts +0 -0
  200. /package/lib/middleware/{__test__ → __tests__}/yargsChecksMiddleware.test.js +0 -0
  201. /package/{mcp-server/utils/__tests__/cliConfig.test.d.ts → lib/projects/__tests__/uieLinting.test.d.ts} +0 -0
@@ -2,14 +2,20 @@ import { CreateTestAccountTool, } from '../CreateTestAccountTool.js';
2
2
  import { runCommandInDir } from '../../../utils/project.js';
3
3
  import { addFlag } from '../../../utils/command.js';
4
4
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
5
+ import fs from 'fs';
6
+ import * as config from '@hubspot/local-dev-lib/config';
5
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
8
  vi.mock('../../../utils/project');
7
9
  vi.mock('../../../utils/command');
8
10
  vi.mock('../../../utils/toolUsageTracking');
9
11
  vi.mock('../../../utils/feedbackTracking');
12
+ vi.mock('fs');
13
+ vi.mock('@hubspot/local-dev-lib/config');
10
14
  const mockMcpFeedbackRequest = mcpFeedbackRequest;
11
15
  const mockRunCommandInDir = runCommandInDir;
12
16
  const mockAddFlag = addFlag;
17
+ const mockReadFileSync = fs.readFileSync;
18
+ const mockGetConfigAccountByName = vi.spyOn(config, 'getConfigAccountByName');
13
19
  describe('mcp-server/tools/project/CreateTestAccountTool', () => {
14
20
  let mockMcpServer;
15
21
  let tool;
@@ -26,6 +32,14 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
26
32
  tool = new CreateTestAccountTool(mockMcpServer);
27
33
  // Mock addFlag to simulate command building
28
34
  mockAddFlag.mockImplementation((command, flag, value) => `${command} --${flag} "${value}"`);
35
+ // Mock fs.readFileSync for config file tests
36
+ mockReadFileSync.mockReturnValue(JSON.stringify({
37
+ accountName: 'TestAccountFromConfig',
38
+ description: 'Test description',
39
+ marketingLevel: 'PROFESSIONAL',
40
+ }));
41
+ // @ts-expect-error breaking things
42
+ mockGetConfigAccountByName.mockReturnValue(undefined);
29
43
  });
30
44
  describe('register', () => {
31
45
  it('should register tool with correct parameters', () => {
@@ -54,6 +68,12 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
54
68
  const baseInput = {
55
69
  absoluteCurrentWorkingDirectory: '/test/workspace',
56
70
  configPath: './test-account.json',
71
+ description: 'Test account',
72
+ marketingLevel: 'ENTERPRISE',
73
+ opsLevel: 'ENTERPRISE',
74
+ serviceLevel: 'ENTERPRISE',
75
+ salesLevel: 'ENTERPRISE',
76
+ contentLevel: 'ENTERPRISE',
57
77
  };
58
78
  it('should create test account with config path', async () => {
59
79
  mockRunCommandInDir.mockResolvedValue({
@@ -65,6 +85,7 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
65
85
  expect(mockRunCommandInDir).toHaveBeenCalledWith('/test/workspace', 'hs test-account create --config-path "./test-account.json"');
66
86
  expect(result).toEqual({
67
87
  content: [
88
+ { type: 'text', text: '/test/workspace' },
68
89
  {
69
90
  type: 'text',
70
91
  text: 'Test account created successfully\nAccount ID: 12345678',
@@ -81,6 +102,12 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
81
102
  const input = {
82
103
  absoluteCurrentWorkingDirectory: '/test/workspace',
83
104
  configPath: '/absolute/path/to/config.json',
105
+ description: 'Test account',
106
+ marketingLevel: 'ENTERPRISE',
107
+ opsLevel: 'ENTERPRISE',
108
+ serviceLevel: 'ENTERPRISE',
109
+ salesLevel: 'ENTERPRISE',
110
+ contentLevel: 'ENTERPRISE',
84
111
  };
85
112
  await tool.handler(input);
86
113
  expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'config-path', '/absolute/path/to/config.json');
@@ -96,15 +123,57 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
96
123
  configPath: './test-account.json',
97
124
  name: 'FlagAccount',
98
125
  description: 'This should be ignored',
126
+ marketingLevel: 'ENTERPRISE',
127
+ opsLevel: 'ENTERPRISE',
128
+ serviceLevel: 'ENTERPRISE',
129
+ salesLevel: 'ENTERPRISE',
130
+ contentLevel: 'ENTERPRISE',
99
131
  };
100
132
  await tool.handler(input);
101
133
  expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'config-path', './test-account.json');
102
134
  // Should not call addFlag for name or description
103
135
  expect(mockAddFlag).not.toHaveBeenCalledWith(expect.anything(), 'name', expect.anything());
104
136
  });
137
+ it('should return helpful error when config file does not exist', async () => {
138
+ mockReadFileSync.mockImplementation(() => {
139
+ throw new Error("ENOENT: no such file or directory, open './missing-config.json'");
140
+ });
141
+ const input = {
142
+ absoluteCurrentWorkingDirectory: '/test/workspace',
143
+ configPath: './missing-config.json',
144
+ };
145
+ const result = await tool.handler(input);
146
+ expect(mockRunCommandInDir).not.toHaveBeenCalled();
147
+ expect(result).toEqual({
148
+ content: [
149
+ {
150
+ type: 'text',
151
+ text: expect.stringContaining('Failed to read or parse config file at "./missing-config.json"'),
152
+ },
153
+ ],
154
+ });
155
+ expect(result.content[0]).toHaveProperty('text', expect.stringContaining('Please ensure the file exists and contains valid JSON'));
156
+ });
157
+ it('should return helpful error when config file contains invalid JSON', async () => {
158
+ mockReadFileSync.mockReturnValue('{ invalid json }');
159
+ const input = {
160
+ absoluteCurrentWorkingDirectory: '/test/workspace',
161
+ configPath: './invalid-config.json',
162
+ };
163
+ const result = await tool.handler(input);
164
+ expect(mockRunCommandInDir).not.toHaveBeenCalled();
165
+ expect(result).toEqual({
166
+ content: [
167
+ {
168
+ type: 'text',
169
+ text: expect.stringContaining('Failed to read or parse config file at "./invalid-config.json"'),
170
+ },
171
+ ],
172
+ });
173
+ });
105
174
  });
106
175
  describe('flag-based approach', () => {
107
- it('should create test account with just account name', async () => {
176
+ it('should create test account with name and all defaults', async () => {
108
177
  mockRunCommandInDir.mockResolvedValue({
109
178
  stdout: 'Test account created successfully',
110
179
  stderr: '',
@@ -112,10 +181,30 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
112
181
  const input = {
113
182
  absoluteCurrentWorkingDirectory: '/test/workspace',
114
183
  name: 'MyTestAccount',
184
+ description: '',
185
+ marketingLevel: 'ENTERPRISE',
186
+ opsLevel: 'ENTERPRISE',
187
+ serviceLevel: 'ENTERPRISE',
188
+ salesLevel: 'ENTERPRISE',
189
+ contentLevel: 'ENTERPRISE',
115
190
  };
116
191
  await tool.handler(input);
117
192
  expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'MyTestAccount');
118
- expect(mockRunCommandInDir).toHaveBeenCalledWith('/test/workspace', 'hs test-account create --name "MyTestAccount"');
193
+ });
194
+ it('should add all flags with defaults when only name is provided', async () => {
195
+ mockRunCommandInDir.mockResolvedValue({
196
+ stdout: 'Test account created successfully',
197
+ stderr: '',
198
+ });
199
+ const input = {
200
+ absoluteCurrentWorkingDirectory: '/test/workspace',
201
+ name: 'MyTestAccount',
202
+ };
203
+ await tool.handler(input);
204
+ expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'MyTestAccount');
205
+ // Implementation uses name as fallback for description, and adds all hub levels with ENTERPRISE defaults
206
+ expect(mockAddFlag).toHaveBeenCalledTimes(7);
207
+ expect(mockRunCommandInDir).toHaveBeenCalled();
119
208
  });
120
209
  it('should create test account with account name and description', async () => {
121
210
  mockRunCommandInDir.mockResolvedValue({
@@ -126,6 +215,11 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
126
215
  absoluteCurrentWorkingDirectory: '/test/workspace',
127
216
  name: 'MyTestAccount',
128
217
  description: 'Test account for development',
218
+ marketingLevel: 'ENTERPRISE',
219
+ opsLevel: 'ENTERPRISE',
220
+ serviceLevel: 'ENTERPRISE',
221
+ salesLevel: 'ENTERPRISE',
222
+ contentLevel: 'ENTERPRISE',
129
223
  };
130
224
  await tool.handler(input);
131
225
  expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'MyTestAccount');
@@ -139,9 +233,12 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
139
233
  const input = {
140
234
  absoluteCurrentWorkingDirectory: '/test/workspace',
141
235
  name: 'MixedTierAccount',
236
+ description: 'Test account',
142
237
  marketingLevel: 'PROFESSIONAL',
143
238
  salesLevel: 'STARTER',
144
239
  contentLevel: 'FREE',
240
+ serviceLevel: 'ENTERPRISE',
241
+ opsLevel: 'ENTERPRISE',
145
242
  };
146
243
  await tool.handler(input);
147
244
  expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'MixedTierAccount');
@@ -174,10 +271,120 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
174
271
  expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'content-level', 'PROFESSIONAL');
175
272
  });
176
273
  });
274
+ describe('handler defaults', () => {
275
+ it('should use ENTERPRISE defaults for all hub levels when not specified', async () => {
276
+ mockRunCommandInDir.mockResolvedValue({
277
+ stdout: 'Test account created',
278
+ stderr: '',
279
+ });
280
+ const input = {
281
+ absoluteCurrentWorkingDirectory: '/test/workspace',
282
+ name: 'DefaultLevelsAccount',
283
+ description: '',
284
+ marketingLevel: 'ENTERPRISE',
285
+ opsLevel: 'ENTERPRISE',
286
+ serviceLevel: 'ENTERPRISE',
287
+ salesLevel: 'ENTERPRISE',
288
+ contentLevel: 'ENTERPRISE',
289
+ };
290
+ await tool.handler(input);
291
+ expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'DefaultLevelsAccount');
292
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'marketing-level', 'ENTERPRISE');
293
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'ops-level', 'ENTERPRISE');
294
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'service-level', 'ENTERPRISE');
295
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'sales-level', 'ENTERPRISE');
296
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'content-level', 'ENTERPRISE');
297
+ });
298
+ it('should use name as fallback for description when description is empty', async () => {
299
+ mockRunCommandInDir.mockResolvedValue({
300
+ stdout: 'Test account created',
301
+ stderr: '',
302
+ });
303
+ const input = {
304
+ absoluteCurrentWorkingDirectory: '/test/workspace',
305
+ name: 'NoDescriptionAccount',
306
+ description: '',
307
+ marketingLevel: 'ENTERPRISE',
308
+ opsLevel: 'ENTERPRISE',
309
+ serviceLevel: 'ENTERPRISE',
310
+ salesLevel: 'ENTERPRISE',
311
+ contentLevel: 'ENTERPRISE',
312
+ };
313
+ await tool.handler(input);
314
+ expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'NoDescriptionAccount');
315
+ // Implementation uses name as fallback for description
316
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'description', 'NoDescriptionAccount');
317
+ });
318
+ it('should use defaults for some hub levels while respecting explicit values', async () => {
319
+ mockRunCommandInDir.mockResolvedValue({
320
+ stdout: 'Test account created',
321
+ stderr: '',
322
+ });
323
+ const input = {
324
+ absoluteCurrentWorkingDirectory: '/test/workspace',
325
+ name: 'PartialLevelsAccount',
326
+ description: '',
327
+ marketingLevel: 'FREE',
328
+ salesLevel: 'STARTER',
329
+ opsLevel: 'ENTERPRISE',
330
+ serviceLevel: 'ENTERPRISE',
331
+ contentLevel: 'ENTERPRISE',
332
+ };
333
+ await tool.handler(input);
334
+ expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'PartialLevelsAccount');
335
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'marketing-level', 'FREE');
336
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'sales-level', 'STARTER');
337
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'ops-level', 'ENTERPRISE');
338
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'service-level', 'ENTERPRISE');
339
+ expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'content-level', 'ENTERPRISE');
340
+ });
341
+ it('should add all hub level flags when defaults are applied', async () => {
342
+ mockRunCommandInDir.mockResolvedValue({
343
+ stdout: 'Test account created with defaults',
344
+ stderr: '',
345
+ });
346
+ const input = {
347
+ absoluteCurrentWorkingDirectory: '/test/workspace',
348
+ name: 'MinimalAccount',
349
+ description: '',
350
+ marketingLevel: 'ENTERPRISE',
351
+ opsLevel: 'ENTERPRISE',
352
+ serviceLevel: 'ENTERPRISE',
353
+ salesLevel: 'ENTERPRISE',
354
+ contentLevel: 'ENTERPRISE',
355
+ };
356
+ const result = await tool.handler(input);
357
+ expect(mockRunCommandInDir).toHaveBeenCalled();
358
+ expect(mockAddFlag).toHaveBeenCalledTimes(7);
359
+ expect(result.content[1]).toEqual({
360
+ type: 'text',
361
+ text: 'Test account created with defaults',
362
+ });
363
+ });
364
+ it('should use ENTERPRISE defaults when values are undefined', async () => {
365
+ mockRunCommandInDir.mockResolvedValue({
366
+ stdout: 'Test account created',
367
+ stderr: '',
368
+ });
369
+ const input = {
370
+ absoluteCurrentWorkingDirectory: '/test/workspace',
371
+ name: 'BypassedDefaultsAccount',
372
+ };
373
+ await tool.handler(input);
374
+ expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'BypassedDefaultsAccount');
375
+ expect(mockAddFlag).toHaveBeenCalledTimes(7);
376
+ });
377
+ });
177
378
  describe('interactive mode', () => {
178
379
  it('should ask for parameters when neither config nor name provided', async () => {
179
380
  const input = {
180
381
  absoluteCurrentWorkingDirectory: '/test/workspace',
382
+ description: 'Test account',
383
+ marketingLevel: 'ENTERPRISE',
384
+ opsLevel: 'ENTERPRISE',
385
+ serviceLevel: 'ENTERPRISE',
386
+ salesLevel: 'ENTERPRISE',
387
+ contentLevel: 'ENTERPRISE',
181
388
  };
182
389
  const result = await tool.handler(input);
183
390
  // Should NOT run the command
@@ -202,10 +409,17 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
202
409
  const input = {
203
410
  absoluteCurrentWorkingDirectory: '/test/workspace',
204
411
  configPath: './test-account.json',
412
+ description: 'Test account',
413
+ marketingLevel: 'ENTERPRISE',
414
+ opsLevel: 'ENTERPRISE',
415
+ serviceLevel: 'ENTERPRISE',
416
+ salesLevel: 'ENTERPRISE',
417
+ contentLevel: 'ENTERPRISE',
205
418
  };
206
419
  const result = await tool.handler(input);
207
420
  expect(result).toEqual({
208
421
  content: [
422
+ { type: 'text', text: '/test/workspace' },
209
423
  { type: 'text', text: 'Test account created successfully' },
210
424
  {
211
425
  type: 'text',
@@ -220,10 +434,19 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
220
434
  const input = {
221
435
  absoluteCurrentWorkingDirectory: '/test/workspace',
222
436
  configPath: './test-account.json',
437
+ description: 'Test account',
438
+ marketingLevel: 'ENTERPRISE',
439
+ opsLevel: 'ENTERPRISE',
440
+ serviceLevel: 'ENTERPRISE',
441
+ salesLevel: 'ENTERPRISE',
442
+ contentLevel: 'ENTERPRISE',
223
443
  };
224
444
  const result = await tool.handler(input);
225
445
  expect(result).toEqual({
226
- content: [{ type: 'text', text: 'Failed to create test account' }],
446
+ content: [
447
+ { type: 'text', text: '/test/workspace' },
448
+ { type: 'text', text: 'Failed to create test account' },
449
+ ],
227
450
  });
228
451
  });
229
452
  });
@@ -2,12 +2,15 @@ import { DocFetchTool } from '../DocFetchTool.js';
2
2
  import { http } from '@hubspot/local-dev-lib/http/unauthed';
3
3
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
4
4
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
5
+ import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
5
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
7
  vi.mock('@hubspot/local-dev-lib/http/unauthed');
7
8
  vi.mock('@hubspot/local-dev-lib/errors/index');
8
- vi.mock('../../utils/toolUsageTracking');
9
+ vi.mock('@hubspot/local-dev-lib/config');
10
+ vi.mock('../../../utils/toolUsageTracking');
9
11
  vi.mock('../../../utils/feedbackTracking');
10
12
  const mockMcpFeedbackRequest = mcpFeedbackRequest;
13
+ const mockTrackToolUsage = trackToolUsage;
11
14
  const mockHttp = http;
12
15
  const mockIsHubSpotHttpError = vi.mocked(isHubSpotHttpError);
13
16
  describe('mcp-server/tools/project/DocFetchTool', () => {
@@ -23,6 +26,7 @@ describe('mcp-server/tools/project/DocFetchTool', () => {
23
26
  mockRegisteredTool = {};
24
27
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
25
28
  mockMcpFeedbackRequest.mockResolvedValue('');
29
+ mockTrackToolUsage.mockResolvedValue(undefined);
26
30
  tool = new DocFetchTool(mockMcpServer);
27
31
  });
28
32
  describe('register', () => {
@@ -1,18 +1,18 @@
1
1
  import { DocsSearchTool } from '../DocsSearchTool.js';
2
2
  import { http } from '@hubspot/local-dev-lib/http';
3
3
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
4
- import { getAccountIdFromCliConfig } from '../../../utils/cliConfig.js';
4
+ import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
7
7
  vi.mock('@hubspot/local-dev-lib/http');
8
8
  vi.mock('@hubspot/local-dev-lib/errors/index');
9
+ vi.mock('@hubspot/local-dev-lib/config');
9
10
  vi.mock('../../../utils/toolUsageTracking');
10
- vi.mock('../../../utils/cliConfig.js');
11
11
  vi.mock('../../../utils/feedbackTracking');
12
12
  const mockMcpFeedbackRequest = mcpFeedbackRequest;
13
13
  const mockHttp = http;
14
- const mockGetAccountIdFromCliConfig = getAccountIdFromCliConfig;
15
14
  const mockIsHubSpotHttpError = vi.mocked(isHubSpotHttpError);
15
+ const mockGetConfigDefaultAccountIfExists = getConfigDefaultAccountIfExists;
16
16
  describe('mcp-server/tools/project/DocsSearchTool', () => {
17
17
  let mockMcpServer;
18
18
  let tool;
@@ -45,7 +45,7 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
45
45
  absoluteCurrentWorkingDirectory: '/foo',
46
46
  };
47
47
  it('should return auth error message when no account ID is found', async () => {
48
- mockGetAccountIdFromCliConfig.mockReturnValue(null);
48
+ mockGetConfigDefaultAccountIfExists.mockReturnValue(undefined);
49
49
  const result = await tool.handler(mockInput);
50
50
  expect(result).toEqual({
51
51
  content: [
@@ -57,7 +57,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
57
57
  });
58
58
  });
59
59
  it('should return successful results when docs are found', async () => {
60
- mockGetAccountIdFromCliConfig.mockReturnValue(12345);
60
+ mockGetConfigDefaultAccountIfExists.mockReturnValue({
61
+ accountId: 12345,
62
+ });
61
63
  const mockResponse = {
62
64
  results: [
63
65
  {
@@ -81,7 +83,7 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
81
83
  data: mockResponse,
82
84
  });
83
85
  const result = await tool.handler(mockInput);
84
- expect(mockGetAccountIdFromCliConfig).toHaveBeenCalledWith('/foo');
86
+ expect(mockGetConfigDefaultAccountIfExists).toHaveBeenCalled();
85
87
  expect(mockHttp.post).toHaveBeenCalledWith(12345, {
86
88
  url: 'dev/docs/llms/v1/docs-search',
87
89
  data: {
@@ -109,7 +111,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
109
111
  expect(resultText).toContain('Test content 2');
110
112
  });
111
113
  it('should return no results message when no documentation is found', async () => {
112
- mockGetAccountIdFromCliConfig.mockReturnValue(12345);
114
+ mockGetConfigDefaultAccountIfExists.mockReturnValue({
115
+ accountId: 12345,
116
+ });
113
117
  const mockResponse = {
114
118
  results: [],
115
119
  };
@@ -128,7 +132,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
128
132
  });
129
133
  });
130
134
  it('should return no results message when results is null', async () => {
131
- mockGetAccountIdFromCliConfig.mockReturnValue(12345);
135
+ mockGetConfigDefaultAccountIfExists.mockReturnValue({
136
+ accountId: 12345,
137
+ });
132
138
  const mockResponse = {
133
139
  results: null,
134
140
  };
@@ -147,7 +153,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
147
153
  });
148
154
  });
149
155
  it('should handle HubSpot HTTP errors', async () => {
150
- mockGetAccountIdFromCliConfig.mockReturnValue(12345);
156
+ mockGetConfigDefaultAccountIfExists.mockReturnValue({
157
+ accountId: 12345,
158
+ });
151
159
  const mockError = {
152
160
  toString: () => 'HubSpot API Error: 404 Not Found',
153
161
  };
@@ -164,7 +172,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
164
172
  });
165
173
  });
166
174
  it('should handle generic errors', async () => {
167
- mockGetAccountIdFromCliConfig.mockReturnValue(12345);
175
+ mockGetConfigDefaultAccountIfExists.mockReturnValue({
176
+ accountId: 12345,
177
+ });
168
178
  const mockError = new Error('Network error');
169
179
  mockHttp.post.mockRejectedValue(mockError);
170
180
  mockIsHubSpotHttpError.mockReturnValue(false);
@@ -179,7 +189,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
179
189
  });
180
190
  });
181
191
  it('should handle non-Error rejections', async () => {
182
- mockGetAccountIdFromCliConfig.mockReturnValue(12345);
192
+ mockGetConfigDefaultAccountIfExists.mockReturnValue({
193
+ accountId: 12345,
194
+ });
183
195
  mockHttp.post.mockRejectedValue('String error');
184
196
  mockIsHubSpotHttpError.mockReturnValue(false);
185
197
  const result = await tool.handler(mockInput);
@@ -1,6 +1,6 @@
1
1
  import { GetApiUsagePatternsByAppIdTool } from '../GetApiUsagePatternsByAppIdTool.js';
2
2
  import { z } from 'zod';
3
- import { getAccountId } from '@hubspot/local-dev-lib/config';
3
+ import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
4
4
  import { http } from '@hubspot/local-dev-lib/http';
5
5
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
6
6
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
@@ -11,7 +11,7 @@ vi.mock('@hubspot/local-dev-lib/errors/index');
11
11
  vi.mock('@hubspot/local-dev-lib/config');
12
12
  vi.mock('../../../utils/feedbackTracking');
13
13
  const mockMcpFeedbackRequest = mcpFeedbackRequest;
14
- const mockGetAccountId = getAccountId;
14
+ const mockGetConfigDefaultAccountIfExists = getConfigDefaultAccountIfExists;
15
15
  const mockHttp = http;
16
16
  const mockIsHubSpotHttpError = isHubSpotHttpError;
17
17
  describe('mcp-server/tools/project/GetApiUsagePatternsByAppIdTool', () => {
@@ -89,7 +89,9 @@ describe('mcp-server/tools/project/GetApiUsagePatternsByAppIdTool', () => {
89
89
  endDate: '2025-12-31',
90
90
  };
91
91
  beforeEach(() => {
92
- mockGetAccountId.mockReturnValue(123456789);
92
+ mockGetConfigDefaultAccountIfExists.mockReturnValue({
93
+ accountId: 123456789,
94
+ });
93
95
  mockIsHubSpotHttpError.mockReturnValue(false);
94
96
  });
95
97
  it('should return API usage patterns successfully', async () => {
@@ -118,7 +120,7 @@ describe('mcp-server/tools/project/GetApiUsagePatternsByAppIdTool', () => {
118
120
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
121
  mockHttp.get.mockResolvedValue(mockResponse);
120
122
  const result = await tool.handler(input);
121
- expect(mockGetAccountId).toHaveBeenCalledWith();
123
+ expect(mockGetConfigDefaultAccountIfExists).toHaveBeenCalledWith();
122
124
  expect(mockHttp.get).toHaveBeenCalledWith(123456789, {
123
125
  url: 'app/feature/utilization/public/v3/insights/app/12345/usage-patterns',
124
126
  params: {
@@ -136,7 +138,7 @@ describe('mcp-server/tools/project/GetApiUsagePatternsByAppIdTool', () => {
136
138
  });
137
139
  });
138
140
  it('should return error when account ID cannot be determined', async () => {
139
- mockGetAccountId.mockReturnValue(null);
141
+ mockGetConfigDefaultAccountIfExists.mockReturnValue(undefined);
140
142
  const result = await tool.handler(input);
141
143
  expect(result).toEqual({
142
144
  content: [
@@ -1,5 +1,5 @@
1
1
  import { GetApplicationInfoTool } from '../GetApplicationInfoTool.js';
2
- import { getAccountId } from '@hubspot/local-dev-lib/config';
2
+ import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
3
3
  import { http } from '@hubspot/local-dev-lib/http';
4
4
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
@@ -10,7 +10,7 @@ vi.mock('@hubspot/local-dev-lib/errors/index');
10
10
  vi.mock('@hubspot/local-dev-lib/config');
11
11
  vi.mock('../../../utils/feedbackTracking');
12
12
  const mockMcpFeedbackRequest = mcpFeedbackRequest;
13
- const mockGetAccountId = getAccountId;
13
+ const mockGetConfigDefaultAccountIfExists = getConfigDefaultAccountIfExists;
14
14
  const mockHttp = http;
15
15
  const mockIsHubSpotHttpError = isHubSpotHttpError;
16
16
  describe('mcp-server/tools/project/GetApplicationInfoTool', () => {
@@ -42,7 +42,9 @@ describe('mcp-server/tools/project/GetApplicationInfoTool', () => {
42
42
  describe('handler', () => {
43
43
  const input = { absoluteCurrentWorkingDirectory: '/test/dir' };
44
44
  beforeEach(() => {
45
- mockGetAccountId.mockReturnValue(123456789);
45
+ mockGetConfigDefaultAccountIfExists.mockReturnValue({
46
+ accountId: 123456789,
47
+ });
46
48
  mockIsHubSpotHttpError.mockReturnValue(false);
47
49
  });
48
50
  it('should return application information successfully', async () => {
@@ -67,7 +69,7 @@ describe('mcp-server/tools/project/GetApplicationInfoTool', () => {
67
69
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
70
  mockHttp.get.mockResolvedValue(mockResponse);
69
71
  const result = await tool.handler(input);
70
- expect(mockGetAccountId).toHaveBeenCalledWith();
72
+ expect(mockGetConfigDefaultAccountIfExists).toHaveBeenCalledWith();
71
73
  expect(mockHttp.get).toHaveBeenCalledWith(123456789, {
72
74
  url: 'app/feature/utilization/public/v3/insights/apps',
73
75
  });
@@ -81,7 +83,7 @@ describe('mcp-server/tools/project/GetApplicationInfoTool', () => {
81
83
  });
82
84
  });
83
85
  it('should return error when account ID cannot be determined', async () => {
84
- mockGetAccountId.mockReturnValue(null);
86
+ mockGetConfigDefaultAccountIfExists.mockReturnValue(undefined);
85
87
  const result = await tool.handler(input);
86
88
  expect(result).toEqual({
87
89
  content: [