@hubspot/cli 7.7.3-experimental.0 → 7.7.5-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 (44) hide show
  1. package/bin/cli.js +2 -0
  2. package/commands/account/auth.d.ts +2 -0
  3. package/commands/account/auth.js +15 -7
  4. package/commands/auth.d.ts +2 -0
  5. package/commands/auth.js +17 -8
  6. package/commands/hubdb/list.d.ts +4 -0
  7. package/commands/hubdb/list.js +83 -0
  8. package/commands/hubdb.js +2 -0
  9. package/commands/mcp/setup.d.ts +13 -0
  10. package/commands/mcp/setup.js +248 -0
  11. package/commands/mcp/start.d.ts +10 -0
  12. package/commands/mcp/start.js +66 -0
  13. package/commands/mcp.d.ts +10 -0
  14. package/commands/mcp.js +20 -0
  15. package/commands/project/listBuilds.js +2 -5
  16. package/lang/en.d.ts +26 -4
  17. package/lang/en.js +28 -4
  18. package/lib/prompts/personalAccessKeyPrompt.js +35 -24
  19. package/lib/prompts/projectAddPrompt.js +1 -1
  20. package/lib/prompts/promptUtils.d.ts +2 -1
  21. package/lib/prompts/promptUtils.js +2 -1
  22. package/mcp-server/server.d.ts +1 -0
  23. package/mcp-server/server.js +18 -0
  24. package/mcp-server/tools/index.d.ts +2 -0
  25. package/mcp-server/tools/index.js +17 -0
  26. package/mcp-server/tools/project/AddFeatureToProject.d.ts +6 -0
  27. package/mcp-server/tools/project/AddFeatureToProject.js +100 -0
  28. package/mcp-server/tools/project/CreateProjectTool.d.ts +6 -0
  29. package/mcp-server/tools/project/CreateProjectTool.js +119 -0
  30. package/mcp-server/tools/project/DeployProject.d.ts +6 -0
  31. package/mcp-server/tools/project/DeployProject.js +50 -0
  32. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +6 -0
  33. package/mcp-server/tools/project/GuidedWalkthroughTool.js +54 -0
  34. package/mcp-server/tools/project/UploadProjectTools.d.ts +5 -0
  35. package/mcp-server/tools/project/UploadProjectTools.js +26 -0
  36. package/mcp-server/tools/project/constants.d.ts +3 -0
  37. package/mcp-server/tools/project/constants.js +13 -0
  38. package/mcp-server/types.d.ts +8 -0
  39. package/mcp-server/types.js +7 -0
  40. package/mcp-server/utils/command.d.ts +3 -0
  41. package/mcp-server/utils/command.js +16 -0
  42. package/mcp-server/utils/project.d.ts +5 -0
  43. package/mcp-server/utils/project.js +17 -0
  44. package/package.json +4 -2
package/bin/cli.js CHANGED
@@ -44,6 +44,7 @@ const feedback_1 = __importDefault(require("../commands/feedback"));
44
44
  const doctor_1 = __importDefault(require("../commands/doctor"));
45
45
  const completion_1 = __importDefault(require("../commands/completion"));
46
46
  const app_1 = __importDefault(require("../commands/app"));
47
+ const mcp_1 = __importDefault(require("../commands/mcp"));
47
48
  function getTerminalWidth() {
48
49
  const width = yargs_1.default.terminalWidth();
49
50
  if (width >= 100)
@@ -122,6 +123,7 @@ const argv = yargs_1.default
122
123
  .command(doctor_1.default)
123
124
  .command(completion_1.default)
124
125
  .command(app_1.default)
126
+ .command(mcp_1.default)
125
127
  .help()
126
128
  .alias('h', 'help')
127
129
  .recommendCommands()
@@ -1,6 +1,8 @@
1
1
  import { CommonArgs, ConfigArgs, YargsCommandModule } from '../../types/Yargs';
2
2
  type AccountAuthArgs = CommonArgs & ConfigArgs & {
3
3
  disableTracking?: boolean;
4
+ } & {
5
+ personalAccessKey?: string;
4
6
  };
5
7
  declare const accountAuthCommand: YargsCommandModule<unknown, AccountAuthArgs>;
6
8
  export default accountAuthCommand;
@@ -26,12 +26,14 @@ const TRACKING_STATUS = {
26
26
  COMPLETE: 'complete',
27
27
  };
28
28
  const authType = auth_1.PERSONAL_ACCESS_KEY_AUTH_METHOD.value;
29
- async function updateConfigWithNewAccount(env, configAlreadyExists, accountId) {
29
+ async function updateConfigWithNewAccount(env, configAlreadyExists, providedPersonalAccessKey, accountId) {
30
30
  try {
31
- const { personalAccessKey } = await (0, personalAccessKeyPrompt_1.personalAccessKeyPrompt)({
32
- env,
33
- account: accountId,
34
- });
31
+ const { personalAccessKey } = providedPersonalAccessKey
32
+ ? { personalAccessKey: providedPersonalAccessKey }
33
+ : await (0, personalAccessKeyPrompt_1.personalAccessKeyPrompt)({
34
+ env,
35
+ account: accountId,
36
+ });
35
37
  const token = await (0, personalAccessKey_1.getAccessToken)(personalAccessKey, env);
36
38
  const defaultAccountName = token.hubName
37
39
  ? (0, text_1.toKebabCase)(token.hubName)
@@ -96,7 +98,7 @@ async function handleConfigMigration() {
96
98
  const describe = en_1.commands.account.subcommands.auth.describe;
97
99
  const command = 'auth';
98
100
  async function handler(args) {
99
- const { providedAccountId, disableTracking } = args;
101
+ const { providedAccountId, disableTracking, personalAccessKey: providedPersonalAccessKey, } = args;
100
102
  if (!disableTracking) {
101
103
  (0, usageTracking_1.trackCommandUsage)('account-auth', {}, providedAccountId);
102
104
  await (0, usageTracking_1.trackAuthAction)('account-auth', authType, TRACKING_STATUS.STARTED);
@@ -112,7 +114,7 @@ async function handler(args) {
112
114
  }
113
115
  (0, config_1.loadConfig)('');
114
116
  (0, process_1.handleExit)(config_1.deleteEmptyConfigFile);
115
- const updatedConfig = await updateConfigWithNewAccount(args.qa ? environments_1.ENVIRONMENTS.QA : environments_1.ENVIRONMENTS.PROD, configAlreadyExists, providedAccountId);
117
+ const updatedConfig = await updateConfigWithNewAccount(args.qa ? environments_1.ENVIRONMENTS.QA : environments_1.ENVIRONMENTS.PROD, configAlreadyExists, providedPersonalAccessKey, providedAccountId);
116
118
  if (!updatedConfig) {
117
119
  if (!disableTracking) {
118
120
  await (0, usageTracking_1.trackAuthAction)('account-auth', authType, TRACKING_STATUS.ERROR);
@@ -151,6 +153,12 @@ function accountAuthBuilder(yargs) {
151
153
  hidden: true,
152
154
  default: false,
153
155
  },
156
+ 'personal-access-key': {
157
+ describe: en_1.commands.account.subcommands.auth.options.personalAccessKey,
158
+ type: 'string',
159
+ hidden: false,
160
+ alias: 'pak',
161
+ },
154
162
  });
155
163
  return yargs;
156
164
  }
@@ -1,6 +1,8 @@
1
1
  import { AccountArgs, CommonArgs, ConfigArgs, TestingArgs, YargsCommandModule } from '../types/Yargs';
2
2
  type AuthArgs = CommonArgs & ConfigArgs & TestingArgs & AccountArgs & {
3
3
  authType?: string;
4
+ } & {
5
+ personalAccessKey?: string;
4
6
  };
5
7
  declare const authCommand: YargsCommandModule<unknown, AuthArgs>;
6
8
  export default authCommand;
package/commands/auth.js CHANGED
@@ -20,6 +20,7 @@ const oauth_1 = require("../lib/oauth");
20
20
  const exitCodes_1 = require("../lib/enums/exitCodes");
21
21
  const ui_1 = require("../lib/ui");
22
22
  const index_1 = require("../lib/errorHandlers/index");
23
+ const en_1 = require("../lang/en");
23
24
  const TRACKING_STATUS = {
24
25
  STARTED: 'started',
25
26
  ERROR: 'error',
@@ -33,7 +34,7 @@ const SUPPORTED_AUTHENTICATION_PROTOCOLS_TEXT = (0, text_1.commaSeparatedValues)
33
34
  const command = 'auth';
34
35
  const describe = (0, lang_1.i18n)('commands.auth.describe');
35
36
  async function handler(args) {
36
- const { authType: authTypeFlagValue, config: configFlagValue, qa, providedAccountId, } = args;
37
+ const { authType: authTypeFlagValue, config: configFlagValue, qa, providedAccountId, personalAccessKey: providedPersonalAccessKey, } = args;
37
38
  const authType = (authTypeFlagValue && authTypeFlagValue.toLowerCase()) ||
38
39
  auth_1.PERSONAL_ACCESS_KEY_AUTH_METHOD.value;
39
40
  (0, commonOpts_1.setLogLevel)(args);
@@ -68,14 +69,16 @@ async function handler(args) {
68
69
  successAuthMethod = auth_1.OAUTH_AUTH_METHOD.name;
69
70
  break;
70
71
  case auth_1.PERSONAL_ACCESS_KEY_AUTH_METHOD.value:
71
- configData = await (0, personalAccessKeyPrompt_1.personalAccessKeyPrompt)({
72
- env,
73
- account: providedAccountId,
74
- });
72
+ const { personalAccessKey } = providedPersonalAccessKey
73
+ ? { personalAccessKey: providedPersonalAccessKey }
74
+ : await (0, personalAccessKeyPrompt_1.personalAccessKeyPrompt)({
75
+ env,
76
+ account: providedAccountId,
77
+ });
75
78
  try {
76
- token = await (0, personalAccessKey_1.getAccessToken)(configData.personalAccessKey, env);
79
+ token = await (0, personalAccessKey_1.getAccessToken)(personalAccessKey, env);
77
80
  defaultName = token.hubName ? (0, text_1.toKebabCase)(token.hubName) : undefined;
78
- updatedConfig = await (0, personalAccessKey_1.updateConfigWithAccessToken)(token, configData.personalAccessKey, env);
81
+ updatedConfig = await (0, personalAccessKey_1.updateConfigWithAccessToken)(token, personalAccessKey, env);
79
82
  }
80
83
  catch (e) {
81
84
  (0, index_1.logError)(e);
@@ -108,7 +111,7 @@ async function handler(args) {
108
111
  await (0, usageTracking_1.trackAuthAction)('auth', authType, TRACKING_STATUS.ERROR, providedAccountId);
109
112
  process.exit(exitCodes_1.EXIT_CODES.ERROR);
110
113
  }
111
- const nameFromConfigData = 'name' in configData ? configData.name : undefined;
114
+ const nameFromConfigData = configData && 'name' in configData ? configData.name : undefined;
112
115
  const accountName = (updatedConfig && updatedConfig.name) || validName || nameFromConfigData;
113
116
  await (0, setAsDefaultAccountPrompt_1.setAsDefaultAccountPrompt)(accountName);
114
117
  logger_1.logger.success((0, lang_1.i18n)('commands.auth.success.configFileUpdated', {
@@ -141,6 +144,12 @@ function authBuilder(yargs) {
141
144
  type: 'string',
142
145
  alias: 'a',
143
146
  },
147
+ 'personal-access-key': {
148
+ describe: en_1.commands.auth.options.personalAccessKey.describe,
149
+ type: 'string',
150
+ hidden: false,
151
+ alias: 'pak',
152
+ },
144
153
  });
145
154
  return yargs;
146
155
  }
@@ -0,0 +1,4 @@
1
+ import { CommonArgs, ConfigArgs, AccountArgs, EnvironmentArgs, YargsCommandModule } from '../../types/Yargs';
2
+ type HubdbListArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs;
3
+ declare const hubdbListCommand: YargsCommandModule<unknown, HubdbListArgs>;
4
+ export default hubdbListCommand;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const hubdb_1 = require("@hubspot/local-dev-lib/api/hubdb");
4
+ const config_1 = require("@hubspot/local-dev-lib/config");
5
+ const urls_1 = require("@hubspot/local-dev-lib/urls");
6
+ const exitCodes_1 = require("../../lib/enums/exitCodes");
7
+ const logger_1 = require("../../lib/ui/logger");
8
+ const index_1 = require("../../lib/errorHandlers/index");
9
+ const en_1 = require("../../lang/en");
10
+ const yargsUtils_1 = require("../../lib/yargsUtils");
11
+ const usageTracking_1 = require("../../lib/usageTracking");
12
+ const table_1 = require("../../lib/ui/table");
13
+ const command = ['list', 'ls'];
14
+ const describe = en_1.commands.hubdb.subcommands.list.describe;
15
+ async function getTableData(accountId) {
16
+ try {
17
+ const response = await (0, hubdb_1.fetchTables)(accountId);
18
+ return response.data;
19
+ }
20
+ catch (err) {
21
+ (0, index_1.logError)(err);
22
+ process.exit(exitCodes_1.EXIT_CODES.ERROR);
23
+ }
24
+ }
25
+ // stripping the types and unnecessary fields so this data can be turned into a UI table
26
+ function mapTablesToUI(tables) {
27
+ return tables.map(({ id, label, name, columnCount, rowCount }) => [
28
+ `${id}`,
29
+ label,
30
+ name,
31
+ `${columnCount || 0}`,
32
+ `${rowCount}`,
33
+ ]);
34
+ }
35
+ async function handler(args) {
36
+ const { derivedAccountId } = args;
37
+ (0, usageTracking_1.trackCommandUsage)('hubdb-list', {}, derivedAccountId);
38
+ const { results: tables, total } = await getTableData(derivedAccountId);
39
+ const tableUIData = mapTablesToUI(tables);
40
+ tableUIData.unshift((0, table_1.getTableHeader)([
41
+ en_1.commands.hubdb.subcommands.list.labels.id,
42
+ en_1.commands.hubdb.subcommands.list.labels.label,
43
+ en_1.commands.hubdb.subcommands.list.labels.name,
44
+ en_1.commands.hubdb.subcommands.list.labels.columns,
45
+ en_1.commands.hubdb.subcommands.list.labels.rows,
46
+ ]));
47
+ logger_1.uiLogger.success(en_1.commands.hubdb.subcommands.list.success(derivedAccountId));
48
+ logger_1.uiLogger.log(' ');
49
+ // link devs to the hubdb page in hubspot for easy access
50
+ // TODO: This is hacky, we should make a util like getBaseUrl()
51
+ const baseUrl = (0, urls_1.getHubSpotWebsiteOrigin)((0, config_1.getEnv)());
52
+ logger_1.uiLogger.log(en_1.commands.hubdb.subcommands.list.viewTablesLink(baseUrl, derivedAccountId));
53
+ // don't bother showing an empty list of tables
54
+ if (tables.length > 0) {
55
+ // if truncated is 0, it will be interpreted as falsy
56
+ const truncated = total - tables.length;
57
+ logger_1.uiLogger.log(en_1.commands.hubdb.subcommands.list.tablesDisplayed(tables.length, total, truncated));
58
+ logger_1.uiLogger.log('--------------------------------');
59
+ logger_1.uiLogger.log(en_1.commands.hubdb.subcommands.list.tables);
60
+ logger_1.uiLogger.log((0, table_1.getTableContents)(tableUIData, { border: { bodyLeft: ' ' } }));
61
+ }
62
+ else {
63
+ logger_1.uiLogger.log(en_1.commands.hubdb.subcommands.list.noTables(derivedAccountId));
64
+ }
65
+ process.exit(exitCodes_1.EXIT_CODES.SUCCESS);
66
+ }
67
+ function hubdbListBuilder(yargs) {
68
+ yargs.example([['$0 hubdb list']]);
69
+ return yargs;
70
+ }
71
+ const builder = (0, yargsUtils_1.makeYargsBuilder)(hubdbListBuilder, command, describe, {
72
+ useGlobalOptions: true,
73
+ useConfigOptions: true,
74
+ useAccountOptions: true,
75
+ useEnvironmentOptions: true,
76
+ });
77
+ const hubdbListCommand = {
78
+ command,
79
+ describe,
80
+ handler,
81
+ builder,
82
+ };
83
+ exports.default = hubdbListCommand;
package/commands/hubdb.js CHANGED
@@ -8,6 +8,7 @@ const create_1 = __importDefault(require("./hubdb/create"));
8
8
  const fetch_1 = __importDefault(require("./hubdb/fetch"));
9
9
  const delete_1 = __importDefault(require("./hubdb/delete"));
10
10
  const clear_1 = __importDefault(require("./hubdb/clear"));
11
+ const list_1 = __importDefault(require("./hubdb/list"));
11
12
  const lang_1 = require("../lib/lang");
12
13
  const yargsUtils_1 = require("../lib/yargsUtils");
13
14
  exports.command = 'hubdb';
@@ -18,6 +19,7 @@ function hubdbBuilder(yargs) {
18
19
  .command(create_1.default)
19
20
  .command(fetch_1.default)
20
21
  .command(delete_1.default)
22
+ .command(list_1.default)
21
23
  .demandCommand(1, '');
22
24
  return yargs;
23
25
  }
@@ -0,0 +1,13 @@
1
+ import { ArgumentsCamelCase, Argv } from 'yargs';
2
+ interface MCPSetupArgs {
3
+ targets?: string[];
4
+ }
5
+ declare function builder(yargs: Argv): Argv;
6
+ declare function handler(argv: ArgumentsCamelCase<MCPSetupArgs>): Promise<void>;
7
+ declare const _default: {
8
+ command: string[];
9
+ describe: string;
10
+ builder: typeof builder;
11
+ handler: typeof handler;
12
+ };
13
+ export default _default;
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const logger_1 = require("@hubspot/local-dev-lib/logger");
7
+ const child_process_1 = require("child_process");
8
+ const util_1 = require("util");
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const os_1 = __importDefault(require("os"));
12
+ const SpinniesManager_1 = __importDefault(require("../../lib/ui/SpinniesManager"));
13
+ const exitCodes_1 = require("../../lib/enums/exitCodes");
14
+ const promptUtils_1 = require("../../lib/prompts/promptUtils");
15
+ const chalk_1 = __importDefault(require("chalk"));
16
+ const command = ['setup', 'update'];
17
+ const describe = 'Add or update MCP server configuration to claude and cursor';
18
+ const mcpServerPath = path_1.default.join(os_1.default.homedir(), 'src', 'hubspot-cli', 'dist', 'mcp-server', 'server.js');
19
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
20
+ const supportedTools = [
21
+ { name: 'Claude', value: 'claude' },
22
+ { name: 'Claude Desktop', value: 'claude-desktop' },
23
+ { name: 'Cursor', value: 'cursor' },
24
+ ];
25
+ function builder(yargs) {
26
+ yargs.option('targets', {
27
+ describe: 'Target application to configure',
28
+ type: 'array',
29
+ choices: [...supportedTools.map(tool => tool.value)],
30
+ });
31
+ return yargs;
32
+ }
33
+ async function handler(argv) {
34
+ await addMcpServerToConfig(argv.targets);
35
+ }
36
+ exports.default = { command, describe, builder, handler };
37
+ async function addMcpServerToConfig(targets) {
38
+ try {
39
+ let derivedTargets = [];
40
+ if (!targets || targets.length === 0) {
41
+ const { selectedTargets } = await (0, promptUtils_1.promptUser)({
42
+ name: 'selectedTargets',
43
+ type: 'checkbox',
44
+ message: '[--targets] Which tools would you like to add the HubSpot CLI MCP server to?',
45
+ choices: supportedTools,
46
+ validate: (choices) => {
47
+ return choices.length === 0 ? 'Must choose at least one tool' : true;
48
+ },
49
+ });
50
+ derivedTargets = selectedTargets;
51
+ }
52
+ else {
53
+ derivedTargets = targets;
54
+ }
55
+ SpinniesManager_1.default.init();
56
+ if (derivedTargets.includes('claude-desktop')) {
57
+ await runSetupFunction(setupClaudeDesktop);
58
+ }
59
+ if (derivedTargets.includes('claude')) {
60
+ await runSetupFunction(setupClaudeCode);
61
+ }
62
+ if (derivedTargets.includes('cursor')) {
63
+ await runSetupFunction(setupCursor);
64
+ }
65
+ logger_1.logger.info(`You can now use the HubSpot CLI tools in ${derivedTargets.join(', ')}. ${chalk_1.default.bold('You may need to restart these tools to apply the changes')}.`);
66
+ }
67
+ catch (error) {
68
+ SpinniesManager_1.default.fail('mcpSetup', {
69
+ text: 'Failed to configure HubSpot CLI MCP server.',
70
+ });
71
+ logger_1.logger.error(error);
72
+ }
73
+ }
74
+ async function runSetupFunction(func) {
75
+ const result = await func();
76
+ if (!result) {
77
+ process.exit(exitCodes_1.EXIT_CODES.ERROR);
78
+ }
79
+ }
80
+ async function setupClaudeDesktop() {
81
+ try {
82
+ const configPath = getClaudeDesktopConfigPath();
83
+ SpinniesManager_1.default.add('claudeDesktop', {
84
+ text: 'Configuring Claude Desktop...',
85
+ });
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ let config = {};
88
+ // Read existing config if it exists
89
+ if (fs_1.default.existsSync(configPath)) {
90
+ try {
91
+ const configContent = fs_1.default.readFileSync(configPath, 'utf8');
92
+ config = JSON.parse(configContent);
93
+ logger_1.logger.debug(`Found existing Claude Desktop config at ${configPath}`);
94
+ }
95
+ catch (error) {
96
+ logger_1.logger.debug(`Could not parse existing Claude Desktop config, creating new one: ${error}`);
97
+ }
98
+ }
99
+ else {
100
+ // Create config directory if it doesn't exist
101
+ const configDir = path_1.default.dirname(configPath);
102
+ fs_1.default.mkdirSync(configDir, { recursive: true });
103
+ logger_1.logger.debug(`Created Claude Desktop config directory at ${configDir}`);
104
+ }
105
+ // Initialize mcpServers if it doesn't exist
106
+ if (!config.mcpServers) {
107
+ config.mcpServers = {};
108
+ }
109
+ // Add or update HubSpot CLI MCP server
110
+ config.mcpServers['hubspot-cli-mcp'] = {
111
+ // TODO: Before merging, change these to `hs mcp start` and do a test publish
112
+ command: 'node',
113
+ args: [mcpServerPath],
114
+ };
115
+ // Write the updated config
116
+ fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
117
+ SpinniesManager_1.default.succeed('claudeDesktop', {
118
+ text: 'Configured Claude Desktop',
119
+ });
120
+ return true;
121
+ }
122
+ catch (error) {
123
+ SpinniesManager_1.default.fail('claudeDesktop', {
124
+ text: 'Failed to configure Claude Desktop',
125
+ });
126
+ logger_1.logger.debug(`Failed to configure Claude Desktop: ${error}`);
127
+ return false;
128
+ }
129
+ }
130
+ async function setupClaudeCode() {
131
+ try {
132
+ SpinniesManager_1.default.add('claudeCode', {
133
+ text: 'Configuring Claude Code...',
134
+ });
135
+ try {
136
+ // Check if claude command is available
137
+ await execAsync('claude --version');
138
+ // Run claude mcp add command
139
+ const mcpConfig = JSON.stringify({
140
+ type: 'stdio',
141
+ // TODO: Before merging, change these to `hs mcp start` and do a test publish
142
+ command: 'node',
143
+ args: [mcpServerPath],
144
+ });
145
+ const { stdout } = await execAsync('claude mcp list');
146
+ if (stdout.includes('hubspot-cli-mcp')) {
147
+ SpinniesManager_1.default.update('claudeCode', {
148
+ text: 'CLI mcp server already installed, reinstalling',
149
+ });
150
+ try {
151
+ await execAsync('claude mcp remove "hubspot-cli-mcp" --scope user');
152
+ }
153
+ catch (error) {
154
+ // @ts-ignore
155
+ logger_1.logger.warn(error.stderr);
156
+ }
157
+ }
158
+ await execAsync(`claude mcp add-json "hubspot-cli-mcp" '${mcpConfig}' --scope user`);
159
+ SpinniesManager_1.default.succeed('claudeCode', {
160
+ text: 'Configured Claude Code',
161
+ });
162
+ return true;
163
+ }
164
+ catch (error) {
165
+ if (error instanceof Error &&
166
+ error.message.includes('claude: command not found')) {
167
+ SpinniesManager_1.default.fail('claudeCode', {
168
+ text: 'Claude Code CLI not found - skipping configuration',
169
+ });
170
+ logger_1.logger.info(' Install Claude Code CLI to enable this configuration');
171
+ }
172
+ else {
173
+ SpinniesManager_1.default.fail('claudeCode', {
174
+ text: 'Claude Code CLI not working - skipping configuration',
175
+ });
176
+ logger_1.logger.error(`Error: ${error}`);
177
+ }
178
+ return false;
179
+ }
180
+ }
181
+ catch (error) {
182
+ SpinniesManager_1.default.fail('claudeCode', {
183
+ text: 'Failed to configure Claude Code',
184
+ });
185
+ logger_1.logger.error(`Failed to configure Claude Code: ${error}`);
186
+ return false;
187
+ }
188
+ }
189
+ async function setupCursor() {
190
+ try {
191
+ SpinniesManager_1.default.add('cursor', {
192
+ text: 'Configuring Cursor...',
193
+ });
194
+ const cursorConfigPath = path_1.default.join(os_1.default.homedir(), '.cursor', 'mcp.json');
195
+ if (!fs_1.default.existsSync(cursorConfigPath)) {
196
+ SpinniesManager_1.default.succeed('cursor', {
197
+ text: 'No .cursor/mcp.json file found - skipping Cursor configuration',
198
+ });
199
+ logger_1.logger.info(' Create a .cursor/mcp.json file in your project to enable Cursor MCP support');
200
+ return false;
201
+ }
202
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
203
+ let config = {};
204
+ // Read existing config
205
+ try {
206
+ const configContent = fs_1.default.readFileSync(cursorConfigPath, 'utf8');
207
+ config = JSON.parse(configContent);
208
+ logger_1.logger.debug(`Found existing Cursor config at ${cursorConfigPath}`);
209
+ }
210
+ catch (error) {
211
+ logger_1.logger.warn(`Could not parse existing Cursor config, creating new one: ${error}`);
212
+ }
213
+ // Initialize mcpServers if it doesn't exist
214
+ if (!config.mcpServers) {
215
+ config.mcpServers = {};
216
+ }
217
+ // Add or update HubSpot CLI MCP server
218
+ config.mcpServers['hubspot-cli-mcp'] = {
219
+ // TODO: Before merging, change these to `hs mcp start` and do a test publish
220
+ command: 'node',
221
+ args: [mcpServerPath],
222
+ };
223
+ // Write the updated config
224
+ fs_1.default.writeFileSync(cursorConfigPath, JSON.stringify(config, null, 2));
225
+ SpinniesManager_1.default.succeed('cursor', {
226
+ text: 'Configured Cursor',
227
+ });
228
+ return true;
229
+ }
230
+ catch (error) {
231
+ SpinniesManager_1.default.fail('cursor', {
232
+ text: 'Failed to configure Cursor',
233
+ });
234
+ logger_1.logger.error(`Failed to configure Cursor: ${error}`);
235
+ return false;
236
+ }
237
+ }
238
+ function getClaudeDesktopConfigPath() {
239
+ const platform = os_1.default.platform();
240
+ const homeDir = os_1.default.homedir();
241
+ if (platform === 'win32') {
242
+ const appData = process.env.APPDATA || path_1.default.join(homeDir, 'AppData', 'Roaming');
243
+ return path_1.default.join(appData, 'Claude', 'claude_desktop_config.json');
244
+ }
245
+ else {
246
+ return path_1.default.join(homeDir, '.config', 'claude', 'claude_desktop_config.json');
247
+ }
248
+ }
@@ -0,0 +1,10 @@
1
+ import { Argv } from 'yargs';
2
+ declare function builder(yargs: Argv): Argv;
3
+ declare function handler(): Promise<void>;
4
+ declare const _default: {
5
+ command: string;
6
+ describe: undefined;
7
+ builder: typeof builder;
8
+ handler: typeof handler;
9
+ };
10
+ export default _default;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const logger_1 = require("@hubspot/local-dev-lib/logger");
7
+ const child_process_1 = require("child_process");
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const command = 'start';
11
+ const describe = undefined; // Keep this command hidden, doesn't seem useful to expose
12
+ function builder(yargs) {
13
+ return yargs;
14
+ }
15
+ async function handler() {
16
+ await startMcpServer();
17
+ }
18
+ exports.default = { command, describe, builder, handler };
19
+ async function startMcpServer() {
20
+ try {
21
+ const serverPath = path_1.default.join(__dirname, '..', '..', 'mcp-server', 'server.js');
22
+ // Check if server file exists
23
+ if (!fs_1.default.existsSync(serverPath)) {
24
+ logger_1.logger.error(`MCP server file not found at ${serverPath}`);
25
+ return;
26
+ }
27
+ logger_1.logger.info('Starting HubSpot CLI MCP server...');
28
+ logger_1.logger.info(`Server path: ${serverPath}`);
29
+ logger_1.logger.info('The server will run in stdio mode for MCP client connections');
30
+ logger_1.logger.info('Press Ctrl+C to stop the server');
31
+ // Start the server using ts-node
32
+ const child = (0, child_process_1.spawn)('node', [serverPath], {
33
+ stdio: 'inherit',
34
+ env: {
35
+ ...process.env,
36
+ },
37
+ });
38
+ // Handle server process events
39
+ child.on('error', error => {
40
+ logger_1.logger.error(error);
41
+ logger_1.logger.error('Failed to start MCP server:', error.message);
42
+ });
43
+ child.on('close', code => {
44
+ if (code === 0) {
45
+ logger_1.logger.info('MCP server stopped gracefully');
46
+ }
47
+ else {
48
+ logger_1.logger.error(`MCP server exited with code ${code}`);
49
+ }
50
+ });
51
+ // Handle graceful shutdown
52
+ process.on('SIGINT', () => {
53
+ logger_1.logger.info('Shutting down MCP server...');
54
+ child.kill('SIGTERM');
55
+ process.exit(0);
56
+ });
57
+ process.on('SIGTERM', () => {
58
+ logger_1.logger.info('Shutting down MCP server...');
59
+ child.kill('SIGTERM');
60
+ process.exit(0);
61
+ });
62
+ }
63
+ catch (error) {
64
+ logger_1.logger.error('Error starting MCP server:', error);
65
+ }
66
+ }
@@ -0,0 +1,10 @@
1
+ import { Argv } from 'yargs';
2
+ declare function builder(yargs: Argv): Argv;
3
+ declare function handler(): void;
4
+ declare const _default: {
5
+ command: string;
6
+ describe: string;
7
+ builder: typeof builder;
8
+ handler: typeof handler;
9
+ };
10
+ export default _default;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const start_1 = __importDefault(require("./mcp/start"));
7
+ const setup_1 = __importDefault(require("./mcp/setup"));
8
+ const command = 'mcp';
9
+ const describe = 'Manage the Model Context Protocol server';
10
+ function builder(yargs) {
11
+ return yargs
12
+ .command(start_1.default)
13
+ .command(setup_1.default)
14
+ .demandCommand(1, 'You must specify a subcommand')
15
+ .help();
16
+ }
17
+ function handler() {
18
+ // This function is required by yargs but not used since we have subcommands
19
+ }
20
+ exports.default = { command, describe, builder, handler };
@@ -17,6 +17,7 @@ const lang_1 = require("../../lib/lang");
17
17
  const index_2 = require("../../lib/errorHandlers/index");
18
18
  const exitCodes_1 = require("../../lib/enums/exitCodes");
19
19
  const yargsUtils_1 = require("../../lib/yargsUtils");
20
+ const en_1 = require("../../lang/en");
20
21
  const command = 'list-builds';
21
22
  const describe = (0, ui_1.uiBetaTag)((0, lang_1.i18n)(`commands.project.subcommands.listBuilds.describe`), false);
22
23
  async function fetchAndDisplayBuilds(accountId, project, options) {
@@ -29,11 +30,7 @@ async function fetchAndDisplayBuilds(accountId, project, options) {
29
30
  }));
30
31
  }
31
32
  else {
32
- logger_1.logger.log((0, lang_1.i18n)(`commands.project.subcommands.listBuilds.logs.showingRecentBuilds`, {
33
- count: results.length,
34
- projectName: project.name,
35
- viewBuildsLink: (0, ui_1.uiLink)((0, lang_1.i18n)(`commands.project.subcommands.listBuilds.logs.viewAllBuildsLink`), (0, urls_1.getProjectDetailUrl)(project.name, accountId)),
36
- }));
33
+ logger_1.logger.log(en_1.commands.project.listBuilds.showingRecentBuilds(results.length, project.name, (0, ui_1.uiLink)(en_1.commands.project.listBuilds.viewAllBuildsLink, (0, urls_1.getProjectDetailUrl)(project.name, accountId))));
37
34
  }
38
35
  if (results.length === 0) {
39
36
  logger_1.logger.log((0, lang_1.i18n)(`commands.project.subcommands.listBuilds.errors.noBuilds`));