@hubspot/cli 7.7.31-experimental.0 → 7.7.33-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 (115) hide show
  1. package/commands/app.js +1 -6
  2. package/commands/getStarted.js +5 -4
  3. package/commands/project/__tests__/add.test.js +3 -5
  4. package/commands/project/__tests__/deploy.test.js +3 -2
  5. package/commands/project/add.js +2 -4
  6. package/commands/project/deploy.js +9 -61
  7. package/commands/project/dev/index.js +1 -1
  8. package/commands/project/dev/unifiedFlow.js +3 -0
  9. package/commands/project/upload.d.ts +2 -2
  10. package/commands/project/upload.js +3 -3
  11. package/commands/project/validate.js +1 -1
  12. package/commands/project/watch.js +2 -2
  13. package/commands/testAccount/create.js +0 -3
  14. package/lang/en.d.ts +8 -26
  15. package/lang/en.js +9 -27
  16. package/lib/__tests__/hasFeature.test.js +145 -7
  17. package/lib/__tests__/importData.test.js +1 -1
  18. package/lib/app/migrate.js +9 -2
  19. package/lib/constants.d.ts +2 -0
  20. package/lib/constants.js +2 -0
  21. package/lib/errorHandlers/index.d.ts +4 -0
  22. package/lib/errorHandlers/index.js +1 -1
  23. package/lib/hasFeature.js +6 -0
  24. package/lib/importData.js +1 -1
  25. package/lib/mcp/setup.js +1 -1
  26. package/lib/projectProfiles.d.ts +1 -1
  27. package/lib/projectProfiles.js +2 -10
  28. package/lib/projects/__tests__/AppDevModeInterface.test.js +61 -44
  29. package/lib/projects/__tests__/LocalDevProcess.test.js +1 -0
  30. package/lib/projects/__tests__/deploy.test.js +164 -0
  31. package/lib/projects/__tests__/{buildAndDeploy.test.js → platformVersion.test.js} +2 -2
  32. package/lib/projects/add/__tests__/legacyAddComponent.test.js +49 -6
  33. package/lib/projects/add/__tests__/v3AddComponent.test.js +71 -1
  34. package/lib/projects/add/legacyAddComponent.d.ts +1 -1
  35. package/lib/projects/add/legacyAddComponent.js +5 -1
  36. package/lib/projects/add/v3AddComponent.d.ts +1 -0
  37. package/lib/projects/add/v3AddComponent.js +2 -2
  38. package/lib/projects/create/__tests__/v3.test.js +97 -9
  39. package/lib/projects/create/index.js +2 -2
  40. package/lib/projects/create/legacy.js +1 -1
  41. package/lib/projects/create/v3.d.ts +2 -2
  42. package/lib/projects/create/v3.js +35 -12
  43. package/lib/projects/deploy.d.ts +13 -0
  44. package/lib/projects/deploy.js +63 -0
  45. package/lib/projects/localDev/AppDevModeInterface.d.ts +0 -2
  46. package/lib/projects/localDev/AppDevModeInterface.js +65 -36
  47. package/lib/projects/localDev/DevServerManagerV2.js +1 -0
  48. package/lib/projects/localDev/LocalDevProcess.js +3 -1
  49. package/lib/projects/localDev/LocalDevState.d.ts +5 -2
  50. package/lib/projects/localDev/LocalDevState.js +9 -1
  51. package/lib/projects/localDev/helpers/project.js +1 -1
  52. package/lib/projects/platformVersion.d.ts +1 -0
  53. package/lib/projects/platformVersion.js +10 -0
  54. package/lib/projects/{buildAndDeploy.d.ts → pollProjectBuildAndDeploy.d.ts} +0 -1
  55. package/lib/projects/{buildAndDeploy.js → pollProjectBuildAndDeploy.js} +0 -10
  56. package/lib/projects/structure.d.ts +2 -2
  57. package/lib/projects/upload.d.ts +1 -2
  58. package/lib/projects/upload.js +1 -2
  59. package/lib/projects/urls.d.ts +1 -0
  60. package/lib/projects/urls.js +3 -0
  61. package/lib/prompts/__tests__/projectAddPrompt.test.d.ts +1 -0
  62. package/lib/prompts/__tests__/projectAddPrompt.test.js +143 -0
  63. package/lib/prompts/__tests__/selectProjectTemplatePrompt.test.d.ts +1 -0
  64. package/lib/prompts/__tests__/selectProjectTemplatePrompt.test.js +160 -0
  65. package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +1 -0
  66. package/lib/prompts/importDataFilePathPrompt.js +4 -2
  67. package/lib/prompts/installAppPrompt.d.ts +6 -1
  68. package/lib/prompts/installAppPrompt.js +6 -1
  69. package/lib/prompts/projectAddPrompt.js +1 -1
  70. package/lib/prompts/selectProjectTemplatePrompt.js +1 -1
  71. package/mcp-server/tools/cms/HsCreateFunctionTool.d.ts +32 -0
  72. package/mcp-server/tools/cms/HsCreateFunctionTool.js +96 -0
  73. package/mcp-server/tools/cms/HsCreateTemplateTool.d.ts +26 -0
  74. package/mcp-server/tools/cms/HsCreateTemplateTool.js +75 -0
  75. package/mcp-server/tools/cms/HsFunctionLogsTool.d.ts +32 -0
  76. package/mcp-server/tools/cms/HsFunctionLogsTool.js +76 -0
  77. package/mcp-server/tools/cms/HsListFunctionsTool.d.ts +23 -0
  78. package/mcp-server/tools/cms/HsListFunctionsTool.js +58 -0
  79. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.d.ts +1 -0
  80. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +251 -0
  81. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.d.ts +1 -0
  82. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +206 -0
  83. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.d.ts +1 -0
  84. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +183 -0
  85. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.d.ts +1 -0
  86. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +120 -0
  87. package/mcp-server/tools/index.js +8 -0
  88. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +3 -3
  89. package/mcp-server/tools/project/CreateProjectTool.d.ts +3 -3
  90. package/mcp-server/tools/project/GetConfigValuesTool.js +3 -3
  91. package/mcp-server/tools/project/constants.d.ts +1 -1
  92. package/mcp-server/tools/project/constants.js +6 -4
  93. package/package.json +4 -3
  94. package/types/LocalDev.d.ts +2 -1
  95. package/types/Projects.d.ts +1 -0
  96. package/types/Yargs.d.ts +1 -1
  97. package/ui/components/BoxWithTitle.d.ts +8 -0
  98. package/ui/components/BoxWithTitle.js +9 -0
  99. package/ui/components/HorizontalSelectPrompt.d.ts +8 -0
  100. package/ui/components/HorizontalSelectPrompt.js +30 -0
  101. package/ui/components/StatusMessageBoxes.d.ts +12 -0
  102. package/ui/components/StatusMessageBoxes.js +31 -0
  103. package/ui/lib/ui-testing-utils.d.ts +9 -0
  104. package/ui/lib/ui-testing-utils.js +47 -0
  105. package/ui/lib/useTerminalSize.d.ts +13 -0
  106. package/ui/lib/useTerminalSize.js +31 -0
  107. package/ui/styles.d.ts +18 -0
  108. package/ui/styles.js +18 -0
  109. package/ui/views/UiSandbox.d.ts +5 -0
  110. package/ui/views/UiSandbox.js +25 -0
  111. package/commands/app/__tests__/install.test.js +0 -47
  112. package/commands/app/install.d.ts +0 -8
  113. package/commands/app/install.js +0 -122
  114. /package/{commands/app/__tests__/install.test.d.ts → lib/projects/__tests__/deploy.test.d.ts} +0 -0
  115. /package/lib/projects/__tests__/{buildAndDeploy.test.d.ts → platformVersion.test.d.ts} +0 -0
@@ -0,0 +1,160 @@
1
+ import { Separator } from '@inquirer/prompts';
2
+ import { selectProjectTemplatePrompt } from '../selectProjectTemplatePrompt.js';
3
+ import { promptUser } from '../promptUtils.js';
4
+ vi.mock('../promptUtils');
5
+ const mockedPromptUser = vi.mocked(promptUser);
6
+ describe('lib/prompts/selectProjectTemplatePrompt', () => {
7
+ const mockComponentTemplate = {
8
+ label: 'Test Module',
9
+ path: 'test-module',
10
+ type: 'module',
11
+ supportedAuthTypes: ['oauth'],
12
+ supportedDistributions: ['private'],
13
+ };
14
+ const mockComponentTemplateWithCliSelector = {
15
+ label: 'Workflow Action Tool',
16
+ path: 'workflow-action-tool',
17
+ type: 'workflow-action',
18
+ cliSelector: 'workflow-action-tool',
19
+ supportedAuthTypes: ['oauth'],
20
+ supportedDistributions: ['private'],
21
+ };
22
+ beforeEach(() => {
23
+ mockedPromptUser.mockResolvedValue({});
24
+ });
25
+ describe('selectProjectTemplatePrompt with component templates', () => {
26
+ it('should select component based on cliSelector when provided', async () => {
27
+ const templateChoice = {
28
+ name: 'Workflow Action Tool',
29
+ value: mockComponentTemplateWithCliSelector,
30
+ };
31
+ const componentTemplates = [templateChoice];
32
+ const promptOptions = {
33
+ features: ['workflow-action-tool'],
34
+ };
35
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
36
+ expect(result.componentTemplates).toEqual([
37
+ mockComponentTemplateWithCliSelector,
38
+ ]);
39
+ expect(mockedPromptUser).toHaveBeenCalledWith([
40
+ expect.objectContaining({ name: 'projectTemplate' }),
41
+ expect.objectContaining({ name: 'componentTemplates' }),
42
+ ]);
43
+ });
44
+ it('should select component based on type when cliSelector not provided', async () => {
45
+ const templateChoice = {
46
+ name: 'Test Module',
47
+ value: mockComponentTemplate,
48
+ };
49
+ const componentTemplates = [templateChoice];
50
+ const promptOptions = {
51
+ features: ['module'],
52
+ };
53
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
54
+ expect(result.componentTemplates).toEqual([mockComponentTemplate]);
55
+ });
56
+ it('should prefer cliSelector over type when both are available', async () => {
57
+ const templateChoice = {
58
+ name: 'Workflow Action Tool',
59
+ value: mockComponentTemplateWithCliSelector,
60
+ };
61
+ const componentTemplates = [templateChoice];
62
+ const promptOptions = {
63
+ features: ['workflow-action-tool'], // matches cliSelector, not type
64
+ };
65
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
66
+ expect(result.componentTemplates).toEqual([
67
+ mockComponentTemplateWithCliSelector,
68
+ ]);
69
+ });
70
+ it('should not select component when neither cliSelector nor type matches', async () => {
71
+ const templateChoice = {
72
+ name: 'Test Module',
73
+ value: mockComponentTemplate,
74
+ };
75
+ const componentTemplates = [templateChoice];
76
+ const promptOptions = {
77
+ features: ['non-matching-feature'],
78
+ };
79
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
80
+ expect(result.componentTemplates).toEqual([]);
81
+ });
82
+ it('should throw error when selected feature component is disabled', async () => {
83
+ const disabledTemplateChoice = {
84
+ name: 'Disabled Component',
85
+ value: mockComponentTemplateWithCliSelector,
86
+ disabled: 'Component is disabled for testing',
87
+ };
88
+ const componentTemplates = [disabledTemplateChoice];
89
+ const promptOptions = {
90
+ features: ['workflow-action-tool'],
91
+ };
92
+ await expect(selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates)).rejects.toThrow(/Cannot create project with template.*workflow-action/);
93
+ });
94
+ it('should handle multiple components with mixed cliSelector availability', async () => {
95
+ const choice1 = {
96
+ name: 'Test Module',
97
+ value: mockComponentTemplate,
98
+ };
99
+ const choice2 = {
100
+ name: 'Workflow Action Tool',
101
+ value: mockComponentTemplateWithCliSelector,
102
+ };
103
+ const componentTemplates = [choice1, choice2];
104
+ const promptOptions = {
105
+ features: ['module', 'workflow-action-tool'],
106
+ };
107
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
108
+ expect(result.componentTemplates).toEqual([
109
+ mockComponentTemplate,
110
+ mockComponentTemplateWithCliSelector,
111
+ ]);
112
+ });
113
+ it('should skip Separator instances when processing components', async () => {
114
+ const separator = new Separator();
115
+ const templateChoice = {
116
+ name: 'Test Module',
117
+ value: mockComponentTemplate,
118
+ };
119
+ const componentTemplates = [separator, templateChoice];
120
+ const promptOptions = {
121
+ features: ['module'],
122
+ };
123
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
124
+ expect(result.componentTemplates).toEqual([mockComponentTemplate]);
125
+ });
126
+ it('should prompt user when no features provided', async () => {
127
+ const templateChoice = {
128
+ name: 'Test Module',
129
+ value: mockComponentTemplate,
130
+ };
131
+ const componentTemplates = [templateChoice];
132
+ const promptOptions = {};
133
+ mockedPromptUser.mockResolvedValue({
134
+ componentTemplates: [mockComponentTemplate],
135
+ });
136
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
137
+ expect(mockedPromptUser).toHaveBeenCalledWith([
138
+ expect.objectContaining({ name: 'projectTemplate' }),
139
+ expect.objectContaining({
140
+ name: 'componentTemplates',
141
+ type: 'checkbox',
142
+ choices: componentTemplates,
143
+ }),
144
+ ]);
145
+ expect(result.componentTemplates).toEqual([mockComponentTemplate]);
146
+ });
147
+ it('should handle empty componentTemplates selection', async () => {
148
+ const templateChoice = {
149
+ name: 'Test Module',
150
+ value: mockComponentTemplate,
151
+ };
152
+ const componentTemplates = [templateChoice];
153
+ const promptOptions = {
154
+ features: ['non-matching-feature'],
155
+ };
156
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
157
+ expect(result.componentTemplates).toEqual([]);
158
+ });
159
+ });
160
+ });
@@ -101,6 +101,7 @@ export async function createDeveloperTestAccountConfigPrompt(args = {}, supportF
101
101
  type: 'checkbox',
102
102
  pageSize: 13,
103
103
  choices: TEST_ACCOUNT_TIERS,
104
+ loop: false,
104
105
  validate: choices => {
105
106
  if (choices?.length < Object.keys(hubs).length) {
106
107
  return lib.prompts.createDeveloperTestAccountConfigPrompt.errors
@@ -1,8 +1,8 @@
1
1
  import { validateImportRequestFile } from '@hubspot/local-dev-lib/crm';
2
2
  import { promptUser } from './promptUtils.js';
3
3
  import { lib } from '../../lang/en.js';
4
- import { logError } from '../errorHandlers/index.js';
5
4
  import { uiLogger } from '../ui/logger.js';
5
+ import { isErrorWithMessageOrReason } from '../errorHandlers/index.js';
6
6
  export async function importDataFilePathPrompt() {
7
7
  uiLogger.log(lib.prompts.importDataFilePathPrompt.promptContext);
8
8
  const { filePath } = await promptUser({
@@ -15,7 +15,9 @@ export async function importDataFilePathPrompt() {
15
15
  return true;
16
16
  }
17
17
  catch (error) {
18
- logError(error);
18
+ if (isErrorWithMessageOrReason(error) && error.message) {
19
+ return error.message;
20
+ }
19
21
  return false;
20
22
  }
21
23
  },
@@ -1,2 +1,7 @@
1
- export declare function installAppBrowserPrompt(installUrl: string, isReinstall?: boolean): Promise<void>;
1
+ export declare function installAppBrowserPrompt(installUrl: string, isReinstall?: boolean, staticAuthInstallOptions?: {
2
+ testingAccountId: number;
3
+ projectAccountId: number;
4
+ projectName: string;
5
+ appUid: string;
6
+ }): Promise<void>;
2
7
  export declare function installAppAutoPrompt(): Promise<boolean>;
@@ -3,7 +3,7 @@ import { promptUser } from './promptUtils.js';
3
3
  import { EXIT_CODES } from '../enums/exitCodes.js';
4
4
  import { lib } from '../../lang/en.js';
5
5
  import { uiLogger } from '../ui/logger.js';
6
- export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
6
+ export async function installAppBrowserPrompt(installUrl, isReinstall = false, staticAuthInstallOptions) {
7
7
  uiLogger.log('');
8
8
  if (isReinstall) {
9
9
  uiLogger.log(lib.prompts.installAppPrompt.reinstallExplanation);
@@ -11,6 +11,11 @@ export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
11
11
  else {
12
12
  uiLogger.log(lib.prompts.installAppPrompt.explanation);
13
13
  }
14
+ if (staticAuthInstallOptions) {
15
+ const { testingAccountId, projectAccountId, projectName, appUid } = staticAuthInstallOptions;
16
+ uiLogger.log(lib.prompts.installAppPrompt.staticAuthExplanation(projectAccountId, testingAccountId, projectName, appUid));
17
+ uiLogger.log('');
18
+ }
14
19
  const { shouldOpenBrowser } = await promptUser({
15
20
  name: 'shouldOpenBrowser',
16
21
  type: 'confirm',
@@ -51,7 +51,7 @@ export async function projectAddPromptV3(components, selectedFeatures) {
51
51
  if (template instanceof Separator || !template.value) {
52
52
  return;
53
53
  }
54
- if (selectedFeatures?.includes(template.value.type)) {
54
+ if (selectedFeatures?.includes(template.value.cliSelector || template.value.type)) {
55
55
  if (template.disabled) {
56
56
  throw new Error(lib.prompts.projectAddPrompt.errors.cannotAddFeature(template.value.type, template.disabled));
57
57
  }
@@ -13,7 +13,7 @@ export async function selectProjectTemplatePrompt(promptOptions, projectTemplate
13
13
  if (template instanceof Separator || !template.value) {
14
14
  return;
15
15
  }
16
- if (promptOptions.features?.includes(template.value.type)) {
16
+ if (promptOptions.features?.includes(template.value.cliSelector || template.value.type)) {
17
17
  if (template.disabled) {
18
18
  throw new Error(`Cannot create project with template '${template.value.type}'. Reasons: ${template.disabled}`);
19
19
  }
@@ -0,0 +1,32 @@
1
+ import { TextContentResponse, Tool } from '../../types.js';
2
+ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { z } from 'zod';
4
+ declare const inputSchemaZodObject: z.ZodObject<{
5
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
+ dest: z.ZodOptional<z.ZodString>;
7
+ functionsFolder: z.ZodOptional<z.ZodString>;
8
+ filename: z.ZodOptional<z.ZodString>;
9
+ endpointMethod: z.ZodOptional<z.ZodEnum<["DELETE", "GET", "PATCH", "POST", "PUT"]>>;
10
+ endpointPath: z.ZodOptional<z.ZodString>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ absoluteCurrentWorkingDirectory: string;
13
+ dest?: string | undefined;
14
+ functionsFolder?: string | undefined;
15
+ filename?: string | undefined;
16
+ endpointMethod?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT" | undefined;
17
+ endpointPath?: string | undefined;
18
+ }, {
19
+ absoluteCurrentWorkingDirectory: string;
20
+ dest?: string | undefined;
21
+ functionsFolder?: string | undefined;
22
+ filename?: string | undefined;
23
+ endpointMethod?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT" | undefined;
24
+ endpointPath?: string | undefined;
25
+ }>;
26
+ export type HsCreateFunctionInputSchema = z.infer<typeof inputSchemaZodObject>;
27
+ export declare class HsCreateFunctionTool extends Tool<HsCreateFunctionInputSchema> {
28
+ constructor(mcpServer: McpServer);
29
+ handler({ dest, functionsFolder, filename, endpointMethod, endpointPath, absoluteCurrentWorkingDirectory, }: HsCreateFunctionInputSchema): Promise<TextContentResponse>;
30
+ register(): RegisteredTool;
31
+ }
32
+ export {};
@@ -0,0 +1,96 @@
1
+ import { Tool } from '../../types.js';
2
+ import { z } from 'zod';
3
+ import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
4
+ import { runCommandInDir } from '../../utils/project.js';
5
+ import { formatTextContents, formatTextContent } from '../../utils/content.js';
6
+ import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
+ import { addFlag } from '../../utils/command.js';
8
+ import { HTTP_METHODS } from '../../../types/Cms.js';
9
+ const inputSchema = {
10
+ absoluteCurrentWorkingDirectory,
11
+ dest: z
12
+ .string()
13
+ .describe('The destination path where the function should be created on the current computer.')
14
+ .optional(),
15
+ functionsFolder: z
16
+ .string()
17
+ .describe('Folder name for function creation. Required for non-interactive function creation. If the user has not specified the folder name, ask them to provide it.')
18
+ .optional(),
19
+ filename: z
20
+ .string()
21
+ .describe('Function filename. Required for non-interactive function creation. If the user has not specified the filename, ask them to provide it.')
22
+ .optional(),
23
+ endpointMethod: z
24
+ .enum(HTTP_METHODS)
25
+ .describe(`HTTP method for the function endpoint. Must be one of: ${HTTP_METHODS.join(', ')}. Defaults to GET.`)
26
+ .optional(),
27
+ endpointPath: z
28
+ .string()
29
+ .describe('API endpoint path for the function. Required for non-interactive function creation. If the user has not specified the endpoint path, ask them to provide it.')
30
+ .optional(),
31
+ };
32
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
33
+ const inputSchemaZodObject = z.object({ ...inputSchema });
34
+ const toolName = 'create-hubspot-cms-function';
35
+ export class HsCreateFunctionTool extends Tool {
36
+ constructor(mcpServer) {
37
+ super(mcpServer);
38
+ }
39
+ async handler({ dest, functionsFolder, filename, endpointMethod, endpointPath, absoluteCurrentWorkingDirectory, }) {
40
+ await trackToolUsage(toolName);
41
+ const content = [];
42
+ // Require functions folder
43
+ if (!functionsFolder) {
44
+ content.push(formatTextContent(`Ask the user to provide the folder name for the function.`));
45
+ }
46
+ // Require filename
47
+ if (!filename) {
48
+ content.push(formatTextContent(`Ask the user to provide the filename for the function.`));
49
+ }
50
+ // Require endpoint path
51
+ if (!endpointPath) {
52
+ content.push(formatTextContent(`Ask the user to provide the API endpoint path for the function.`));
53
+ }
54
+ // If we have missing required information, return the prompts
55
+ if (content.length > 0) {
56
+ return {
57
+ content,
58
+ };
59
+ }
60
+ // Build the command
61
+ let command = 'hs create function';
62
+ if (dest) {
63
+ command += ` "${dest}"`;
64
+ }
65
+ // Add function-specific flags
66
+ if (functionsFolder) {
67
+ command = addFlag(command, 'functions-folder', functionsFolder);
68
+ }
69
+ if (filename) {
70
+ command = addFlag(command, 'filename', filename);
71
+ }
72
+ if (endpointMethod) {
73
+ command = addFlag(command, 'endpoint-method', endpointMethod);
74
+ }
75
+ else {
76
+ command = addFlag(command, 'endpoint-method', 'GET');
77
+ }
78
+ if (endpointPath) {
79
+ command = addFlag(command, 'endpoint-path', endpointPath);
80
+ }
81
+ try {
82
+ const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
83
+ return formatTextContents(stdout, stderr);
84
+ }
85
+ catch (error) {
86
+ return formatTextContents(error instanceof Error ? error.message : `${error}`);
87
+ }
88
+ }
89
+ register() {
90
+ return this.mcpServer.registerTool(toolName, {
91
+ title: 'Create HubSpot CMS Serverless Function',
92
+ description: `Creates a new HubSpot CMS serverless function using the hs create function command. Functions can be created non-interactively by specifying functionsFolder, filename, and endpointPath. Supports all HTTP methods (${HTTP_METHODS.join(', ')}).`,
93
+ inputSchema,
94
+ }, this.handler);
95
+ }
96
+ }
@@ -0,0 +1,26 @@
1
+ import { TextContentResponse, Tool } from '../../types.js';
2
+ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { z } from 'zod';
4
+ declare const inputSchemaZodObject: z.ZodObject<{
5
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
+ userSuppliedName: z.ZodOptional<z.ZodString>;
7
+ dest: z.ZodOptional<z.ZodString>;
8
+ templateType: z.ZodOptional<z.ZodEnum<["page-template", "email-template", "partial", "global-partial", "blog-listing-template", "blog-post-template", "search-template", "section"]>>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ absoluteCurrentWorkingDirectory: string;
11
+ dest?: string | undefined;
12
+ templateType?: "page-template" | "email-template" | "partial" | "global-partial" | "blog-listing-template" | "blog-post-template" | "search-template" | "section" | undefined;
13
+ userSuppliedName?: string | undefined;
14
+ }, {
15
+ absoluteCurrentWorkingDirectory: string;
16
+ dest?: string | undefined;
17
+ templateType?: "page-template" | "email-template" | "partial" | "global-partial" | "blog-listing-template" | "blog-post-template" | "search-template" | "section" | undefined;
18
+ userSuppliedName?: string | undefined;
19
+ }>;
20
+ export type HsCreateTemplateInputSchema = z.infer<typeof inputSchemaZodObject>;
21
+ export declare class HsCreateTemplateTool extends Tool<HsCreateTemplateInputSchema> {
22
+ constructor(mcpServer: McpServer);
23
+ handler({ userSuppliedName, dest, templateType, absoluteCurrentWorkingDirectory, }: HsCreateTemplateInputSchema): Promise<TextContentResponse>;
24
+ register(): RegisteredTool;
25
+ }
26
+ export {};
@@ -0,0 +1,75 @@
1
+ import { Tool } from '../../types.js';
2
+ import { z } from 'zod';
3
+ import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
4
+ import { runCommandInDir } from '../../utils/project.js';
5
+ import { formatTextContents, formatTextContent } from '../../utils/content.js';
6
+ import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
+ import { addFlag } from '../../utils/command.js';
8
+ import { TEMPLATE_TYPES } from '../../../types/Cms.js';
9
+ const inputSchema = {
10
+ absoluteCurrentWorkingDirectory,
11
+ userSuppliedName: z
12
+ .string()
13
+ .describe('REQUIRED - If not specified by the user, DO NOT choose. Ask the user to specify the name of the template they want to create.')
14
+ .optional(),
15
+ dest: z
16
+ .string()
17
+ .describe('The destination path where the template should be created on the current computer.')
18
+ .optional(),
19
+ templateType: z
20
+ .enum(TEMPLATE_TYPES)
21
+ .describe(`Template type for template creation. Must be one of: ${TEMPLATE_TYPES.join(', ')}`)
22
+ .optional(),
23
+ };
24
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
25
+ const inputSchemaZodObject = z.object({ ...inputSchema });
26
+ const toolName = 'create-hubspot-cms-template';
27
+ export class HsCreateTemplateTool extends Tool {
28
+ constructor(mcpServer) {
29
+ super(mcpServer);
30
+ }
31
+ async handler({ userSuppliedName, dest, templateType, absoluteCurrentWorkingDirectory, }) {
32
+ await trackToolUsage(toolName);
33
+ const content = [];
34
+ // Always require a name
35
+ if (!userSuppliedName) {
36
+ content.push(formatTextContent(`Ask the user to specify the name of the template they want to create.`));
37
+ }
38
+ // Require template type
39
+ if (!templateType) {
40
+ content.push(formatTextContent(`Ask the user what template type they want to create. Options are: ${TEMPLATE_TYPES.join(', ')}`));
41
+ }
42
+ // If we have missing required information, return the prompts
43
+ if (content.length > 0) {
44
+ return {
45
+ content,
46
+ };
47
+ }
48
+ // Build the command
49
+ let command = 'hs create template';
50
+ if (userSuppliedName) {
51
+ command += ` "${userSuppliedName}"`;
52
+ }
53
+ if (dest) {
54
+ command += ` "${dest}"`;
55
+ }
56
+ // Add template type flag
57
+ if (templateType) {
58
+ command = addFlag(command, 'template-type', templateType);
59
+ }
60
+ try {
61
+ const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
62
+ return formatTextContents(stdout, stderr);
63
+ }
64
+ catch (error) {
65
+ return formatTextContents(error instanceof Error ? error.message : `${error}`);
66
+ }
67
+ }
68
+ register() {
69
+ return this.mcpServer.registerTool(toolName, {
70
+ title: 'Create HubSpot CMS Template',
71
+ description: `Creates a new HubSpot CMS template using the hs create template command. Templates can be created non-interactively by specifying templateType. Supports all template types including: ${TEMPLATE_TYPES.join(', ')}.`,
72
+ inputSchema,
73
+ }, this.handler);
74
+ }
75
+ }
@@ -0,0 +1,32 @@
1
+ import { TextContentResponse, Tool } from '../../types.js';
2
+ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { z } from 'zod';
4
+ declare const inputSchemaZodObject: z.ZodObject<{
5
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
+ endpoint: z.ZodString;
7
+ account: z.ZodOptional<z.ZodString>;
8
+ latest: z.ZodOptional<z.ZodBoolean>;
9
+ compact: z.ZodOptional<z.ZodBoolean>;
10
+ limit: z.ZodOptional<z.ZodNumber>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ endpoint: string;
13
+ absoluteCurrentWorkingDirectory: string;
14
+ account?: string | undefined;
15
+ limit?: number | undefined;
16
+ compact?: boolean | undefined;
17
+ latest?: boolean | undefined;
18
+ }, {
19
+ endpoint: string;
20
+ absoluteCurrentWorkingDirectory: string;
21
+ account?: string | undefined;
22
+ limit?: number | undefined;
23
+ compact?: boolean | undefined;
24
+ latest?: boolean | undefined;
25
+ }>;
26
+ export type HsFunctionLogsInputSchema = z.infer<typeof inputSchemaZodObject>;
27
+ export declare class HsFunctionLogsTool extends Tool<HsFunctionLogsInputSchema> {
28
+ constructor(mcpServer: McpServer);
29
+ handler({ endpoint, account, latest, compact, limit, absoluteCurrentWorkingDirectory, }: HsFunctionLogsInputSchema): Promise<TextContentResponse>;
30
+ register(): RegisteredTool;
31
+ }
32
+ export {};
@@ -0,0 +1,76 @@
1
+ import { Tool } from '../../types.js';
2
+ import { z } from 'zod';
3
+ import { addFlag } from '../../utils/command.js';
4
+ import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
5
+ import { runCommandInDir } from '../../utils/project.js';
6
+ import { formatTextContents } from '../../utils/content.js';
7
+ import { trackToolUsage } from '../../utils/toolUsageTracking.js';
8
+ const inputSchema = {
9
+ absoluteCurrentWorkingDirectory,
10
+ endpoint: z
11
+ .string()
12
+ .describe('The function endpoint/path to get logs for. Required. Example: "my-function" or "api/my-endpoint" (leading slash will be automatically removed)'),
13
+ account: z
14
+ .string()
15
+ .describe('The HubSpot account id or name from the HubSpot config file to use for the operation.')
16
+ .optional(),
17
+ latest: z
18
+ .boolean()
19
+ .describe('Get only the latest log entry for the function.')
20
+ .optional(),
21
+ compact: z.boolean().describe('Display logs in compact format.').optional(),
22
+ limit: z
23
+ .number()
24
+ .describe('Maximum number of log entries to retrieve.')
25
+ .optional(),
26
+ };
27
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28
+ const inputSchemaZodObject = z.object({ ...inputSchema });
29
+ const toolName = 'get-hubspot-cms-serverless-function-logs';
30
+ export class HsFunctionLogsTool extends Tool {
31
+ constructor(mcpServer) {
32
+ super(mcpServer);
33
+ }
34
+ async handler({ endpoint, account, latest, compact, limit, absoluteCurrentWorkingDirectory, }) {
35
+ await trackToolUsage(toolName);
36
+ // Ensure endpoint doesn't start with '/'
37
+ const normalizedEndpoint = endpoint.startsWith('/')
38
+ ? endpoint.slice(1)
39
+ : endpoint;
40
+ let command = `hs logs ${normalizedEndpoint}`;
41
+ if (latest) {
42
+ command = addFlag(command, 'latest', latest);
43
+ }
44
+ if (compact) {
45
+ command = addFlag(command, 'compact', compact);
46
+ }
47
+ if (limit) {
48
+ command = addFlag(command, 'limit', limit);
49
+ }
50
+ if (account) {
51
+ command = addFlag(command, 'account', account);
52
+ }
53
+ try {
54
+ const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
55
+ return formatTextContents(stdout, stderr);
56
+ }
57
+ catch (error) {
58
+ const errorMessage = error instanceof Error ? error.message : String(error);
59
+ return {
60
+ content: [
61
+ {
62
+ type: 'text',
63
+ text: `Error executing hs logs command: ${errorMessage}`,
64
+ },
65
+ ],
66
+ };
67
+ }
68
+ }
69
+ register() {
70
+ return this.mcpServer.registerTool(toolName, {
71
+ title: 'Get HubSpot CMS serverless function logs for an endpoint',
72
+ description: 'Retrieve logs for HubSpot CMS serverless functions. Use this tool to help debug issues with serverless functions by reading the production logs. Supports various options like latest, compact, and limiting results. Use after listing functions with list-hubspot-cms-serverless-functions to get the endpoint path.',
73
+ inputSchema,
74
+ }, this.handler);
75
+ }
76
+ }
@@ -0,0 +1,23 @@
1
+ import { TextContentResponse, Tool } from '../../types.js';
2
+ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { z } from 'zod';
4
+ declare const inputSchemaZodObject: z.ZodObject<{
5
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
+ account: z.ZodOptional<z.ZodString>;
7
+ json: z.ZodOptional<z.ZodBoolean>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ absoluteCurrentWorkingDirectory: string;
10
+ account?: string | undefined;
11
+ json?: boolean | undefined;
12
+ }, {
13
+ absoluteCurrentWorkingDirectory: string;
14
+ account?: string | undefined;
15
+ json?: boolean | undefined;
16
+ }>;
17
+ export type HsListFunctionsInputSchema = z.infer<typeof inputSchemaZodObject>;
18
+ export declare class HsListFunctionsTool extends Tool<HsListFunctionsInputSchema> {
19
+ constructor(mcpServer: McpServer);
20
+ handler({ account, json, absoluteCurrentWorkingDirectory, }: HsListFunctionsInputSchema): Promise<TextContentResponse>;
21
+ register(): RegisteredTool;
22
+ }
23
+ export {};
@@ -0,0 +1,58 @@
1
+ import { Tool } from '../../types.js';
2
+ import { z } from 'zod';
3
+ import { addFlag } from '../../utils/command.js';
4
+ import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
5
+ import { runCommandInDir } from '../../utils/project.js';
6
+ import { formatTextContents } from '../../utils/content.js';
7
+ import { trackToolUsage } from '../../utils/toolUsageTracking.js';
8
+ const inputSchema = {
9
+ absoluteCurrentWorkingDirectory,
10
+ account: z
11
+ .string()
12
+ .describe('The HubSpot account id or name from the HubSpot config file to use for the operation.')
13
+ .optional(),
14
+ json: z
15
+ .boolean()
16
+ .describe('Return raw JSON output instead of formatted table. Useful for programmatic access.')
17
+ .optional(),
18
+ };
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
+ const inputSchemaZodObject = z.object({ ...inputSchema });
21
+ const toolName = 'list-hubspot-cms-serverless-functions';
22
+ export class HsListFunctionsTool extends Tool {
23
+ constructor(mcpServer) {
24
+ super(mcpServer);
25
+ }
26
+ async handler({ account, json, absoluteCurrentWorkingDirectory, }) {
27
+ await trackToolUsage(toolName);
28
+ let command = 'hs function list';
29
+ if (json) {
30
+ command += ' --json';
31
+ }
32
+ if (account) {
33
+ command = addFlag(command, 'account', account);
34
+ }
35
+ try {
36
+ const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
37
+ return formatTextContents(stdout, stderr);
38
+ }
39
+ catch (error) {
40
+ const errorMessage = error instanceof Error ? error.message : String(error);
41
+ return {
42
+ content: [
43
+ {
44
+ type: 'text',
45
+ text: `Error executing hs function list command: ${errorMessage}`,
46
+ },
47
+ ],
48
+ };
49
+ }
50
+ }
51
+ register() {
52
+ return this.mcpServer.registerTool(toolName, {
53
+ title: 'List HubSpot CMS Serverless Functions',
54
+ description: 'Get a list of all serverless functions deployed in a HubSpot portal/account. Shows function routes, HTTP methods, secrets, and timestamps.',
55
+ inputSchema,
56
+ }, this.handler);
57
+ }
58
+ }