@hubspot/cli 8.12.0 → 8.13.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 (37) hide show
  1. package/commands/config/set.js +24 -5
  2. package/commands/customObject/createSchema.js +2 -3
  3. package/commands/customObject/updateSchema.js +2 -3
  4. package/commands/hubdb/list.js +7 -8
  5. package/commands/project/listBuilds.d.ts +1 -1
  6. package/commands/project/listBuilds.js +8 -6
  7. package/commands/project/logs.js +2 -4
  8. package/commands/project/release/list.js +1 -1
  9. package/commands/sandbox/delete.js +2 -3
  10. package/lang/en.d.ts +17 -5
  11. package/lang/en.js +19 -6
  12. package/lib/app/urls.js +2 -3
  13. package/lib/configOptions.d.ts +10 -10
  14. package/lib/configOptions.js +10 -11
  15. package/lib/dependencyManagement.js +7 -7
  16. package/lib/importData.js +3 -4
  17. package/lib/links.js +6 -5
  18. package/lib/mcp/clients.d.ts +1 -1
  19. package/lib/mcp/clients.js +8 -1
  20. package/lib/mcp/setup.d.ts +3 -3
  21. package/lib/mcp/setup.js +87 -18
  22. package/lib/npm/npmCli.d.ts +1 -0
  23. package/lib/npm/npmCli.js +13 -0
  24. package/lib/projects/urls.d.ts +0 -1
  25. package/lib/projects/urls.js +6 -13
  26. package/mcp-server/tools/project/DocsSearchTool.js +7 -12
  27. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +2 -1
  28. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +7 -11
  29. package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +2 -1
  30. package/mcp-server/tools/project/GetApplicationInfoTool.js +10 -12
  31. package/mcp-server/tools/project/GetBuildLogsTool.js +9 -11
  32. package/mcp-server/tools/project/GetBuildStatusTool.js +9 -11
  33. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +2 -1
  34. package/mcp-server/tools/project/GetConfigValuesTool.js +7 -11
  35. package/mcp-server/utils/config.js +5 -0
  36. package/mcp-server/utils/toolUsageTracking.js +10 -2
  37. package/package.json +4 -4
@@ -2,6 +2,7 @@ import { promptUser } from '../../lib/prompts/promptUtils.js';
2
2
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
3
3
  import { setDefaultCmsPublishMode, setHttpTimeout, setAllowUsageTracking, setAllowAutoUpdates, setAutoOpenBrowser, } from '../../lib/configOptions.js';
4
4
  import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler.js';
5
+ import { trackCommandMetadataUsage } from '../../lib/usageTracking.js';
5
6
  import { commands } from '../../lang/en.js';
6
7
  import { makeYargsBuilder, strictEnforceBoolean, } from '../../lib/yargsUtils.js';
7
8
  import { logError } from '../../lib/errorHandlers/index.js';
@@ -30,21 +31,39 @@ async function selectOptions() {
30
31
  }
31
32
  async function handleConfigUpdate(accountId, args) {
32
33
  const { allowAutoUpdates, allowUsageTracking, defaultCmsPublishMode, httpTimeout, autoOpenBrowser, } = args;
34
+ // Emit one metadata event per setting so each field:value pair surfaces as its
35
+ // own entry in Amplitude instead of a combined string that fragments the charts.
36
+ const trackingRequests = [];
37
+ const trackAction = (action) => {
38
+ trackingRequests.push(trackCommandMetadataUsage('config-set', { action }, accountId));
39
+ };
33
40
  if (allowAutoUpdates !== undefined) {
34
- await setAllowAutoUpdates({ allowAutoUpdates, accountId });
41
+ const value = await setAllowAutoUpdates({ allowAutoUpdates, accountId });
42
+ trackAction(`allowAutoUpdates:${value}`);
35
43
  }
36
44
  if (allowUsageTracking !== undefined) {
37
- await setAllowUsageTracking({ allowUsageTracking, accountId });
45
+ const value = await setAllowUsageTracking({
46
+ allowUsageTracking,
47
+ accountId,
48
+ });
49
+ trackAction(`allowUsageTracking:${value}`);
38
50
  }
39
51
  if (autoOpenBrowser !== undefined) {
40
- await setAutoOpenBrowser({ autoOpenBrowser, accountId });
52
+ const value = await setAutoOpenBrowser({ autoOpenBrowser, accountId });
53
+ trackAction(`autoOpenBrowser:${value}`);
41
54
  }
42
55
  if (defaultCmsPublishMode !== undefined) {
43
- await setDefaultCmsPublishMode({ defaultCmsPublishMode, accountId });
56
+ const value = await setDefaultCmsPublishMode({
57
+ defaultCmsPublishMode,
58
+ accountId,
59
+ });
60
+ trackAction(`defaultCmsPublishMode:${value}`);
44
61
  }
45
62
  if (httpTimeout !== undefined) {
46
- await setHttpTimeout({ httpTimeout, accountId });
63
+ const value = await setHttpTimeout({ httpTimeout, accountId });
64
+ trackAction(`httpTimeout:${value}`);
47
65
  }
66
+ await Promise.all(trackingRequests);
48
67
  }
49
68
  async function handler(args) {
50
69
  const { derivedAccountId, allowAutoUpdates, allowUsageTracking, defaultCmsPublishMode, httpTimeout, autoOpenBrowser, exit, } = args;
@@ -1,8 +1,7 @@
1
1
  import { uiLogger } from '../../lib/ui/logger.js';
2
- import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
3
2
  import { getAbsoluteFilePath } from '@hubspot/local-dev-lib/path';
4
3
  import { createObjectSchema } from '@hubspot/local-dev-lib/api/customObjects';
5
- import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
4
+ import { getHubSpotWebsiteOriginByAccountId } from '@hubspot/local-dev-lib/urls';
6
5
  import { logError } from '../../lib/errorHandlers/index.js';
7
6
  import { checkAndConvertToJson } from '../../lib/validation.js';
8
7
  import { commands } from '../../lang/en.js';
@@ -22,7 +21,7 @@ async function handler(args) {
22
21
  }
23
22
  try {
24
23
  const { data } = await createObjectSchema(derivedAccountId, schemaJson);
25
- uiLogger.success(commands.customObject.subcommands.createSchema.success.schemaViewable(`${getHubSpotWebsiteOrigin(getConfigAccountEnvironment(derivedAccountId))}/contacts/${derivedAccountId}/objects/${data.objectTypeId}`));
24
+ uiLogger.success(commands.customObject.subcommands.createSchema.success.schemaViewable(`${getHubSpotWebsiteOriginByAccountId(derivedAccountId)}/contacts/${derivedAccountId}/objects/${data.objectTypeId}`));
26
25
  }
27
26
  catch (e) {
28
27
  logError(e, { accountId: derivedAccountId });
@@ -1,8 +1,7 @@
1
1
  import { fetchObjectSchemas, updateObjectSchema, } from '@hubspot/local-dev-lib/api/customObjects';
2
2
  import { uiLogger } from '../../lib/ui/logger.js';
3
3
  import { getAbsoluteFilePath } from '@hubspot/local-dev-lib/path';
4
- import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
5
- import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
4
+ import { getHubSpotWebsiteOriginByAccountId } from '@hubspot/local-dev-lib/urls';
6
5
  import { listPrompt } from '../../lib/prompts/promptUtils.js';
7
6
  import { logError } from '../../lib/errorHandlers/index.js';
8
7
  import { checkAndConvertToJson } from '../../lib/validation.js';
@@ -32,7 +31,7 @@ async function handler(args) {
32
31
  choices: schemaNames,
33
32
  });
34
33
  const { data } = await updateObjectSchema(derivedAccountId, name, schemaJson);
35
- uiLogger.success(commands.customObject.subcommands.updateSchema.success.viewAtUrl(`${getHubSpotWebsiteOrigin(getConfigAccountEnvironment(derivedAccountId))}/contacts/${derivedAccountId}/objects/${data.objectTypeId}`));
34
+ uiLogger.success(commands.customObject.subcommands.updateSchema.success.viewAtUrl(`${getHubSpotWebsiteOriginByAccountId(derivedAccountId)}/contacts/${derivedAccountId}/objects/${data.objectTypeId}`));
36
35
  }
37
36
  catch (e) {
38
37
  logError(e, { accountId: derivedAccountId });
@@ -2,11 +2,12 @@ import { fetchTables } from '@hubspot/local-dev-lib/api/hubdb';
2
2
  import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler.js';
3
3
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
4
4
  import { uiLogger } from '../../lib/ui/logger.js';
5
+ import { uiLine } from '../../lib/ui/index.js';
5
6
  import { logError } from '../../lib/errorHandlers/index.js';
6
7
  import { commands } from '../../lang/en.js';
7
8
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
8
9
  import { renderTable } from '../../ui/render.js';
9
- import { getBaseHubSpotUrlForAccount } from '../../lib/projects/urls.js';
10
+ import { getHubSpotWebsiteOriginByAccountId } from '@hubspot/local-dev-lib/urls';
10
11
  const command = ['list', 'ls'];
11
12
  const describe = commands.hubdb.subcommands.list.describe;
12
13
  async function getTableData(accountId) {
@@ -44,22 +45,20 @@ async function handler(args) {
44
45
  commands.hubdb.subcommands.list.labels.rows,
45
46
  ];
46
47
  uiLogger.success(commands.hubdb.subcommands.list.success(derivedAccountId));
47
- uiLogger.log(' ');
48
- // link devs to the hubdb page in hubspot for easy access
49
- const baseUrl = getBaseHubSpotUrlForAccount(derivedAccountId);
50
- uiLogger.log(commands.hubdb.subcommands.list.viewTablesLink(baseUrl, derivedAccountId));
51
48
  // don't bother showing an empty list of tables
52
49
  if (tables.length > 0) {
50
+ uiLogger.log(commands.hubdb.subcommands.list.tables);
51
+ renderTable(tableHeader, tableUIData);
53
52
  // if truncated is 0, it will be interpreted as falsy
54
53
  const truncated = total - tables.length;
54
+ uiLine();
55
55
  uiLogger.log(commands.hubdb.subcommands.list.tablesDisplayed(tables.length, total, truncated));
56
- uiLogger.log('--------------------------------');
57
- uiLogger.log(commands.hubdb.subcommands.list.tables);
58
- renderTable(tableHeader, tableUIData);
59
56
  }
60
57
  else {
61
58
  uiLogger.log(commands.hubdb.subcommands.list.noTables(derivedAccountId));
62
59
  }
60
+ // link devs to the hubdb page in hubspot for easy access
61
+ uiLogger.log(commands.hubdb.subcommands.list.viewTablesLink(getHubSpotWebsiteOriginByAccountId(derivedAccountId), derivedAccountId));
63
62
  return exit(EXIT_CODES.SUCCESS);
64
63
  }
65
64
  function hubdbListBuilder(yargs) {
@@ -1,5 +1,5 @@
1
1
  import { CommonArgs, ConfigArgs, AccountArgs, EnvironmentArgs, YargsCommandModule } from '../../types/Yargs.js';
2
- type ProjectListBuildsArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & {
2
+ export type ProjectListBuildsArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & {
3
3
  project?: string;
4
4
  limit?: number;
5
5
  };
@@ -17,12 +17,6 @@ const describe = commands.project.listBuilds.describe;
17
17
  async function fetchAndDisplayBuilds(accountId, project, options) {
18
18
  const { data: { results, paging }, } = await fetchProjectBuilds(accountId, project.name, options);
19
19
  const currentDeploy = project.deployedBuildId;
20
- if (options && options.after) {
21
- uiLogger.log(commands.project.listBuilds.showingNextBuilds(results.length, project.name));
22
- }
23
- else {
24
- uiLogger.log(commands.project.listBuilds.showingRecentBuilds(results.length, project.name, uiLink(commands.project.listBuilds.viewAllBuildsLink, getProjectDetailUrl(project.name, accountId))));
25
- }
26
20
  if (results.length === 0) {
27
21
  uiLogger.log(commands.project.listBuilds.errors.noBuilds);
28
22
  }
@@ -46,6 +40,14 @@ async function fetchAndDisplayBuilds(accountId, project, options) {
46
40
  });
47
41
  renderTable(['Build ID', 'Status', 'Completed', 'Duration', 'Details'], builds);
48
42
  }
43
+ if (options && options.after) {
44
+ if (results.length > 0) {
45
+ uiLogger.log(commands.project.listBuilds.showingNextBuilds(results.length, project.name));
46
+ }
47
+ }
48
+ else {
49
+ uiLogger.log(commands.project.listBuilds.showingRecentBuilds(results.length, project.name, uiLink(commands.project.listBuilds.viewAllBuildsLink, getProjectDetailUrl(project.name, accountId))));
50
+ }
49
51
  if (paging && paging.next) {
50
52
  await promptUser({
51
53
  name: 'more',
@@ -1,5 +1,4 @@
1
- import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
2
- import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
1
+ import { getHubSpotWebsiteOriginByAccountId } from '@hubspot/local-dev-lib/urls';
3
2
  import { logError } from '../../lib/errorHandlers/index.js';
4
3
  import { uiLine } from '../../lib/ui/index.js';
5
4
  import { projectLogsPrompt } from '../../lib/prompts/projectsLogsPrompt.js';
@@ -11,8 +10,7 @@ import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler
11
10
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
12
11
  import { renderTable } from '../../ui/render.js';
13
12
  function getPrivateAppsUrl(accountId) {
14
- const baseUrl = getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
15
- return `${baseUrl}/private-apps/${accountId}`;
13
+ return `${getHubSpotWebsiteOriginByAccountId(accountId)}/private-apps/${accountId}`;
16
14
  }
17
15
  function logPreamble() {
18
16
  if (ProjectLogsManager.isPublicFunction) {
@@ -18,7 +18,6 @@ const describe = undefined;
18
18
  const verboseDescribe = undefined;
19
19
  async function fetchAndDisplayReleases(accountId, projectName, options) {
20
20
  const { data: { results, paging }, } = await listReleases(accountId, projectName, options);
21
- uiLogger.log(commands.project.release.list.showingReleases(results.length, projectName));
22
21
  if (results.length === 0) {
23
22
  uiLogger.log(commands.project.release.list.noReleases);
24
23
  }
@@ -29,6 +28,7 @@ async function fetchAndDisplayReleases(accountId, projectName, options) {
29
28
  new Date(release.createdAt).toLocaleString(),
30
29
  ]);
31
30
  renderTable(['Release', 'Build', 'Created'], rows);
31
+ uiLogger.log(commands.project.release.list.showingReleases(results.length, projectName));
32
32
  }
33
33
  if (paging?.next?.after) {
34
34
  await promptUser({
@@ -2,8 +2,7 @@ import { uiLogger } from '../../lib/ui/logger.js';
2
2
  import { isSpecifiedError } from '@hubspot/local-dev-lib/errors/index';
3
3
  import { deleteSandbox } from '@hubspot/local-dev-lib/api/sandboxHubs';
4
4
  import { getConfigAccountEnvironment, removeAccountFromConfig, setConfigAccountAsDefault, getAllConfigAccounts, getConfigAccountIfExists, getConfigDefaultAccountIfExists, } from '@hubspot/local-dev-lib/config';
5
- import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
6
- import { getValidEnv } from '@hubspot/local-dev-lib/environment';
5
+ import { getHubSpotWebsiteOriginByAccountId } from '@hubspot/local-dev-lib/urls';
7
6
  import { logError, debugError } from '../../lib/errorHandlers/index.js';
8
7
  import { commands } from '../../lang/en.js';
9
8
  import { deleteSandboxPrompt } from '../../lib/prompts/sandboxesPrompt.js';
@@ -43,7 +42,7 @@ async function handler(args) {
43
42
  const sandboxAccountId = sandboxAccount.accountId;
44
43
  const defaultAccount = getConfigDefaultAccountIfExists();
45
44
  const isDefaultAccount = sandboxAccountId === defaultAccount?.accountId;
46
- const baseUrl = getHubSpotWebsiteOrigin(getValidEnv(getConfigAccountEnvironment(sandboxAccountId)));
45
+ const baseUrl = getHubSpotWebsiteOriginByAccountId(sandboxAccountId);
47
46
  let parentAccountId;
48
47
  const accountsList = getAllConfigAccounts() || [];
49
48
  for (const portal of accountsList) {
package/lang/en.d.ts CHANGED
@@ -1325,9 +1325,11 @@ export declare const commands: {
1325
1325
  codex: string;
1326
1326
  claudeCode: string;
1327
1327
  cursor: string;
1328
+ devin: string;
1328
1329
  gemini: string;
1329
- windsurf: string;
1330
+ opencode: string;
1330
1331
  vsCode: string;
1332
+ other: string;
1331
1333
  args: {
1332
1334
  client: string;
1333
1335
  standalone: string;
@@ -1362,15 +1364,25 @@ export declare const commands: {
1362
1364
  geminiNotFound: string;
1363
1365
  geminiInstallFailed: string;
1364
1366
  alreadyInstalled: string;
1365
- configuringWindsurf: string;
1366
- windsurfNotFound: string;
1367
- failedToConfigureWindsurf: string;
1368
- configuredWindsurf: string;
1367
+ configuringDevin: string;
1368
+ devinNotFound: string;
1369
+ failedToConfigureDevin: string;
1370
+ configuredDevin: string;
1371
+ configuringOpenCode: string;
1372
+ openCodeNotFound: string;
1373
+ openCodeInstallFailed: string;
1374
+ configuredOpenCode: string;
1369
1375
  configuringVsCode: string;
1370
1376
  failedToConfigureVsCode: string;
1371
1377
  configuredVsCode: string;
1372
1378
  vsCodeNotFound: string;
1373
1379
  };
1380
+ otherInstructions: {
1381
+ header: string;
1382
+ docsNote: string;
1383
+ commandLabel: string;
1384
+ jsonLabel: string;
1385
+ };
1374
1386
  prompts: {
1375
1387
  targets: string;
1376
1388
  targetsRequired: string;
package/lang/en.js CHANGED
@@ -1334,9 +1334,11 @@ export const commands = {
1334
1334
  codex: 'Codex CLI',
1335
1335
  claudeCode: 'Claude Code',
1336
1336
  cursor: 'Cursor',
1337
+ devin: 'Devin',
1337
1338
  gemini: 'Gemini CLI',
1338
- windsurf: 'Windsurf',
1339
+ opencode: 'OpenCode',
1339
1340
  vsCode: 'VSCode',
1341
+ other: 'Other (manual setup)',
1340
1342
  args: {
1341
1343
  client: 'Target apps to configure',
1342
1344
  standalone: 'Use npx for all of the hs commands run by the MCP server. This allows you to use the MCP server without having the CLI globally installed.',
@@ -1375,17 +1377,28 @@ export const commands = {
1375
1377
  geminiNotFound: "Gemini CLI is not installed (missing 'gemini' command). Install it and re-run hs mcp setup.",
1376
1378
  geminiInstallFailed: 'Failed to configure Gemini CLI',
1377
1379
  alreadyInstalled: 'HubSpot CLI mcp server already installed, reinstalling',
1378
- // Windsurf
1379
- configuringWindsurf: 'Configuring Windsurf...',
1380
- windsurfNotFound: 'Windsurf is not installed. Install it and re-run hs mcp setup.',
1381
- failedToConfigureWindsurf: 'Failed to configure Windsurf',
1382
- configuredWindsurf: 'Configured Windsurf',
1380
+ // Devin
1381
+ configuringDevin: 'Configuring Devin...',
1382
+ devinNotFound: 'Devin is not installed. Install it and re-run hs mcp setup.',
1383
+ failedToConfigureDevin: 'Failed to configure Devin',
1384
+ configuredDevin: 'Configured Devin',
1385
+ // OpenCode
1386
+ configuringOpenCode: 'Configuring OpenCode...',
1387
+ openCodeNotFound: "OpenCode is not installed (missing 'opencode' command). Install it and re-run hs mcp setup.",
1388
+ openCodeInstallFailed: 'Failed to configure OpenCode',
1389
+ configuredOpenCode: 'Configured OpenCode',
1383
1390
  // VS Code
1384
1391
  configuringVsCode: 'Configuring VSCode...',
1385
1392
  failedToConfigureVsCode: 'Failed to configure VSCode',
1386
1393
  configuredVsCode: 'Configured VSCode',
1387
1394
  vsCodeNotFound: "VSCode CLI is not installed (missing 'code' command). Install it and re-run hs mcp setup.",
1388
1395
  },
1396
+ otherInstructions: {
1397
+ header: 'To connect the HubSpot MCP server to another tool, add the following MCP server configuration.',
1398
+ docsNote: "Each tool handles MCP setup differently. Check your tool's documentation for how to add an MCP server using a command or JSON config.",
1399
+ commandLabel: 'Command:',
1400
+ jsonLabel: 'JSON configuration (for tools that use a config file):',
1401
+ },
1389
1402
  prompts: {
1390
1403
  targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
1391
1404
  targetsRequired: 'Must choose at least one app to configure.',
package/lib/app/urls.js CHANGED
@@ -1,5 +1,4 @@
1
- import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
2
- import { getBaseHubSpotUrlForAccount } from '../projects/urls.js';
1
+ import { getHubSpotWebsiteOrigin, getHubSpotWebsiteOriginByAccountId, } from '@hubspot/local-dev-lib/urls';
3
2
  export function getOauthAppInstallUrl({ targetAccountId, env, clientId, scopes, redirectUrls, }) {
4
3
  const websiteOrigin = getHubSpotWebsiteOrigin(env);
5
4
  return (`${websiteOrigin}/oauth/${targetAccountId}/authorize` +
@@ -16,7 +15,7 @@ export function getAppCardSetupUrl({ targetAccountId, env, appId, }) {
16
15
  return `${websiteOrigin}/integrations-settings/${targetAccountId}/installed/framework/${appId}/app-cards?tourId=get-started`;
17
16
  }
18
17
  export function getAppLogsUrl(accountId, appId, systemType) {
19
- return `${getBaseHubSpotUrlForAccount(accountId)}/developer-monitoring/${accountId}/?logType=${systemType}&appId=${appId}`;
18
+ return `${getHubSpotWebsiteOriginByAccountId(accountId)}/developer-monitoring/${accountId}/?logType=${systemType}&appId=${appId}`;
20
19
  }
21
20
  export function getAppLogDetailsUrl(accountId, appId, systemType, logId) {
22
21
  return `${getAppLogsUrl(accountId, appId, systemType)}&logId=${logId}`;
@@ -1,21 +1,21 @@
1
1
  import { CmsPublishMode } from '@hubspot/local-dev-lib/types/Files';
2
- export declare function setAllowUsageTracking({ accountId, allowUsageTracking, }: {
2
+ export declare function setAllowUsageTracking({ allowUsageTracking, }: {
3
3
  accountId: number;
4
4
  allowUsageTracking?: boolean;
5
- }): Promise<void>;
6
- export declare function setAllowAutoUpdates({ accountId, allowAutoUpdates, }: {
5
+ }): Promise<boolean>;
6
+ export declare function setAllowAutoUpdates({ allowAutoUpdates, }: {
7
7
  accountId: number;
8
8
  allowAutoUpdates?: boolean;
9
- }): Promise<void>;
10
- export declare function setDefaultCmsPublishMode({ accountId, defaultCmsPublishMode, }: {
9
+ }): Promise<boolean>;
10
+ export declare function setDefaultCmsPublishMode({ defaultCmsPublishMode, }: {
11
11
  accountId: number;
12
12
  defaultCmsPublishMode?: CmsPublishMode;
13
- }): Promise<void>;
14
- export declare function setHttpTimeout({ accountId, httpTimeout, }: {
13
+ }): Promise<CmsPublishMode>;
14
+ export declare function setHttpTimeout({ httpTimeout, }: {
15
15
  accountId: number;
16
16
  httpTimeout?: string;
17
- }): Promise<void>;
18
- export declare function setAutoOpenBrowser({ accountId, autoOpenBrowser, }: {
17
+ }): Promise<string>;
18
+ export declare function setAutoOpenBrowser({ autoOpenBrowser, }: {
19
19
  accountId: number;
20
20
  autoOpenBrowser: boolean;
21
- }): Promise<void>;
21
+ }): Promise<boolean>;
@@ -1,7 +1,6 @@
1
1
  import { updateAllowUsageTracking, updateAllowAutoUpdates, updateDefaultCmsPublishMode, updateHttpTimeout, updateAutoOpenBrowser, } from '@hubspot/local-dev-lib/config';
2
2
  import { CMS_PUBLISH_MODE } from '@hubspot/local-dev-lib/constants/files';
3
3
  import { commaSeparatedValues } from '@hubspot/local-dev-lib/text';
4
- import { trackCommandUsage } from './usageTracking.js';
5
4
  import { promptUser, listPrompt } from './prompts/promptUtils.js';
6
5
  import { lib } from '../lang/en.js';
7
6
  import { uiLogger } from './ui/logger.js';
@@ -23,8 +22,7 @@ async function enableOrDisableBooleanFieldPrompt(fieldName) {
23
22
  });
24
23
  return isEnabled;
25
24
  }
26
- export async function setAllowUsageTracking({ accountId, allowUsageTracking, }) {
27
- trackCommandUsage('config-set-allow-usage-tracking', undefined, accountId);
25
+ export async function setAllowUsageTracking({ allowUsageTracking, }) {
28
26
  let isEnabled;
29
27
  if (typeof allowUsageTracking === 'boolean') {
30
28
  isEnabled = allowUsageTracking;
@@ -34,9 +32,9 @@ export async function setAllowUsageTracking({ accountId, allowUsageTracking, })
34
32
  }
35
33
  updateAllowUsageTracking(isEnabled);
36
34
  uiLogger.success(lib.configOptions.setAllowUsageTracking.success(isEnabled.toString()));
35
+ return isEnabled;
37
36
  }
38
- export async function setAllowAutoUpdates({ accountId, allowAutoUpdates, }) {
39
- trackCommandUsage('config-set-allow-auto-updates', undefined, accountId);
37
+ export async function setAllowAutoUpdates({ allowAutoUpdates, }) {
40
38
  let isEnabled;
41
39
  if (typeof allowAutoUpdates === 'boolean') {
42
40
  isEnabled = allowAutoUpdates;
@@ -46,6 +44,7 @@ export async function setAllowAutoUpdates({ accountId, allowAutoUpdates, }) {
46
44
  }
47
45
  updateAllowAutoUpdates(isEnabled);
48
46
  uiLogger.success(lib.configOptions.setAllowAutoUpdates.success(isEnabled.toString()));
47
+ return isEnabled;
49
48
  }
50
49
  const ALL_CMS_PUBLISH_MODES = Object.values(CMS_PUBLISH_MODE);
51
50
  async function selectCmsPublishMode() {
@@ -55,8 +54,7 @@ async function selectCmsPublishMode() {
55
54
  });
56
55
  return cmsPublishMode;
57
56
  }
58
- export async function setDefaultCmsPublishMode({ accountId, defaultCmsPublishMode, }) {
59
- trackCommandUsage('config-set-default-mode', undefined, accountId);
57
+ export async function setDefaultCmsPublishMode({ defaultCmsPublishMode, }) {
60
58
  let newDefault;
61
59
  if (!defaultCmsPublishMode) {
62
60
  newDefault = await selectCmsPublishMode();
@@ -71,6 +69,7 @@ export async function setDefaultCmsPublishMode({ accountId, defaultCmsPublishMod
71
69
  }
72
70
  updateDefaultCmsPublishMode(newDefault);
73
71
  uiLogger.success(lib.configOptions.setDefaultCmsPublishMode.success(newDefault));
72
+ return newDefault;
74
73
  }
75
74
  async function enterTimeout() {
76
75
  const { timeout } = await promptUser([
@@ -90,8 +89,7 @@ async function enterTimeout() {
90
89
  ]);
91
90
  return timeout;
92
91
  }
93
- export async function setHttpTimeout({ accountId, httpTimeout, }) {
94
- trackCommandUsage('config-set-http-timeout', undefined, accountId);
92
+ export async function setHttpTimeout({ httpTimeout, }) {
95
93
  let newHttpTimeout;
96
94
  if (!httpTimeout) {
97
95
  newHttpTimeout = await enterTimeout();
@@ -101,11 +99,12 @@ export async function setHttpTimeout({ accountId, httpTimeout, }) {
101
99
  }
102
100
  updateHttpTimeout(newHttpTimeout);
103
101
  uiLogger.success(lib.configOptions.setHttpTimeout.success(newHttpTimeout));
102
+ return newHttpTimeout;
104
103
  }
105
- export async function setAutoOpenBrowser({ accountId, autoOpenBrowser, }) {
106
- trackCommandUsage('config-set-auto-open-browser', undefined, accountId);
104
+ export async function setAutoOpenBrowser({ autoOpenBrowser, }) {
107
105
  updateAutoOpenBrowser(autoOpenBrowser);
108
106
  uiLogger.success(autoOpenBrowser
109
107
  ? lib.configOptions.setAutoOpenBrowser.enabled
110
108
  : lib.configOptions.setAutoOpenBrowser.disabled);
109
+ return autoOpenBrowser;
111
110
  }
@@ -5,7 +5,7 @@ import { exec as execAsync } from 'node:child_process';
5
5
  import { getProjectConfig } from './projects/config.js';
6
6
  import { commands } from '../lang/en.js';
7
7
  import SpinniesManager from './ui/SpinniesManager.js';
8
- import { isGloballyInstalled, executeInstall, executeUpdate, DEFAULT_PACKAGE_MANAGER, } from './npm/npmCli.js';
8
+ import { isGloballyInstalled, executeInstall, executeUpdate, getNpmExecErrorOutput, DEFAULT_PACKAGE_MANAGER, } from './npm/npmCli.js';
9
9
  import { findAllPackageJsonFilesInProjectCached, safeGetPackageJsonCached, } from './npm/packageJson.js';
10
10
  import { getNpmWorkspaceDirectoryForPackageAtLocationCached } from './npm/workspaces.js';
11
11
  class NoPackageJsonFilesError extends Error {
@@ -106,9 +106,9 @@ async function installPackagesInDirectory({ directory, packages, dev = false, np
106
106
  SpinniesManager.fail(spinner, {
107
107
  text: commands.project.installDeps.installingDependenciesFailed(relativeDir),
108
108
  });
109
- throw new Error(commands.project.installDeps.installingDependenciesFailed(relativeDir), {
110
- cause: e,
111
- });
109
+ const npmErrorOutput = getNpmExecErrorOutput(e);
110
+ throw new Error(npmErrorOutput ||
111
+ commands.project.installDeps.installingDependenciesFailed(relativeDir), npmErrorOutput ? undefined : { cause: e });
112
112
  }
113
113
  }
114
114
  export async function updatePackages({ packages, installLocations, }) {
@@ -176,9 +176,9 @@ async function updatePackagesInDirectory({ directory, packages, npmWorkspaceDire
176
176
  SpinniesManager.fail(spinner, {
177
177
  text: commands.project.updateDeps.updatingDependenciesFailed(relativeDir),
178
178
  });
179
- throw new Error(commands.project.updateDeps.updatingDependenciesFailed(relativeDir), {
180
- cause: e,
181
- });
179
+ const npmErrorOutput = getNpmExecErrorOutput(e);
180
+ throw new Error(npmErrorOutput ||
181
+ commands.project.updateDeps.updatingDependenciesFailed(relativeDir), npmErrorOutput ? undefined : { cause: e });
182
182
  }
183
183
  }
184
184
  export async function getProjectPackageJsonLocations(dir, isUpdate = false) {
package/lib/importData.js CHANGED
@@ -1,16 +1,15 @@
1
- import { getConfigAccountById, getConfigAccountEnvironment, getConfigAccountIfExists, } from '@hubspot/local-dev-lib/config';
1
+ import { getConfigAccountById, getConfigAccountIfExists, } from '@hubspot/local-dev-lib/config';
2
2
  import { createImport } from '@hubspot/local-dev-lib/api/crm';
3
- import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
3
+ import { getHubSpotWebsiteOriginByAccountId } from '@hubspot/local-dev-lib/urls';
4
4
  import { importDataTestAccountSelectPrompt } from './prompts/importDataTestAccountSelectPrompt.js';
5
5
  import { lib } from '../lang/en.js';
6
6
  import { isAppDeveloperAccount, isDeveloperTestAccount, isStandardAccount, } from './accountTypes.js';
7
7
  import { uiLogger } from './ui/logger.js';
8
8
  export async function handleImportData(targetAccountId, dataFileNames, importRequest) {
9
9
  try {
10
- const baseUrl = getHubSpotWebsiteOrigin(getConfigAccountEnvironment(targetAccountId));
11
10
  const response = await createImport(targetAccountId, importRequest, dataFileNames);
12
11
  const importId = response.data.id;
13
- uiLogger.info(lib.importData.viewImportLink(baseUrl, targetAccountId, importId));
12
+ uiLogger.info(lib.importData.viewImportLink(getHubSpotWebsiteOriginByAccountId(targetAccountId), targetAccountId, importId));
14
13
  }
15
14
  catch (error) {
16
15
  uiLogger.error(lib.importData.errors.failedToImportData);
package/lib/links.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import open from 'open';
2
- import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
3
- import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
2
+ import { getHubSpotWebsiteOrigin, getHubSpotWebsiteOriginByAccountId, } from '@hubspot/local-dev-lib/urls';
4
3
  import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
5
4
  import { uiLogger } from './ui/logger.js';
6
5
  import { renderTable } from '../ui/render.js';
@@ -75,7 +74,7 @@ const COMMON_SITE_LINKS = {
75
74
  },
76
75
  };
77
76
  export function getSiteLinksAsArray(accountId) {
78
- const baseUrl = getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
77
+ const baseUrl = getHubSpotWebsiteOriginByAccountId(accountId);
79
78
  return Object.values(COMMON_SITE_LINKS)
80
79
  .sort((a, b) => (a.shortcut < b.shortcut ? -1 : 1))
81
80
  .map(l => ({ ...l, url: l.getUrl(accountId, baseUrl) }));
@@ -94,12 +93,14 @@ export function openLink(accountId, shortcut) {
94
93
  uiLogger.error(`We couldn't find a shortcut matching ${shortcut}. Type 'hs open list' to see a list of available shortcuts`);
95
94
  return;
96
95
  }
97
- const baseUrl = getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
96
+ const baseUrl = getHubSpotWebsiteOriginByAccountId(accountId);
98
97
  open(match.getUrl(accountId, baseUrl), { url: true });
99
98
  uiLogger.success(`We opened ${match.getUrl(accountId, baseUrl)} in your browser`);
100
99
  }
101
100
  export function getProductUpdatesUrl(rolloutId, accountId) {
102
- const baseUrl = getHubSpotWebsiteOrigin(accountId ? getConfigAccountEnvironment(accountId) : ENVIRONMENTS.PROD);
101
+ const baseUrl = accountId
102
+ ? getHubSpotWebsiteOriginByAccountId(accountId)
103
+ : getHubSpotWebsiteOrigin(ENVIRONMENTS.PROD);
103
104
  if (accountId) {
104
105
  return `${baseUrl}/product-updates/${accountId}/in-beta?rollout=${rolloutId}`;
105
106
  }
@@ -1,5 +1,5 @@
1
1
  export declare const MCP_SERVER_NAME = "HubSpotDev";
2
- export type McpClientId = 'codex' | 'claude' | 'cursor' | 'gemini' | 'vscode' | 'windsurf';
2
+ export type McpClientId = 'codex' | 'claude' | 'cursor' | 'devin' | 'gemini' | 'opencode' | 'vscode';
3
3
  type McpClientDetection = {
4
4
  type: 'json' | 'text';
5
5
  pathSegments: string[];
@@ -39,12 +39,19 @@ export const MCP_CLIENTS = [
39
39
  },
40
40
  },
41
41
  {
42
- id: 'windsurf',
42
+ id: 'devin',
43
43
  detection: {
44
44
  type: 'json',
45
45
  pathSegments: ['.codeium', 'windsurf', 'mcp_config.json'],
46
46
  },
47
47
  },
48
+ {
49
+ id: 'opencode',
50
+ detection: {
51
+ type: 'json',
52
+ pathSegments: ['.config', 'opencode', 'config.json'],
53
+ },
54
+ },
48
55
  ];
49
56
  export function getMcpClientPathSegments(id) {
50
57
  const client = MCP_CLIENTS.find(c => c.id === id);
@@ -1,7 +1,6 @@
1
- import { McpClientId } from './clients.js';
2
1
  export declare const supportedTools: {
3
2
  name: string;
4
- value: McpClientId;
3
+ value: string;
5
4
  }[];
6
5
  interface McpCommand {
7
6
  command: string;
@@ -17,7 +16,8 @@ export declare function configureMcpServer(options: ConfigureMcpServerOptions):
17
16
  export declare function setupVsCode(mcpCommand?: McpCommand): Promise<boolean>;
18
17
  export declare function setupClaudeCode(mcpCommand?: McpCommand): Promise<boolean>;
19
18
  export declare function setupCursor(mcpCommand?: McpCommand): boolean;
20
- export declare function setupWindsurf(mcpCommand?: McpCommand): boolean;
19
+ export declare function setupDevin(mcpCommand?: McpCommand): boolean;
21
20
  export declare function setupCodex(mcpCommand?: McpCommand): Promise<boolean>;
22
21
  export declare function setupGemini(mcpCommand?: McpCommand): Promise<boolean>;
22
+ export declare function setupOpenCode(mcpCommand?: McpCommand): Promise<boolean>;
23
23
  export {};
package/lib/mcp/setup.js CHANGED
@@ -11,23 +11,29 @@ import fs from 'fs-extra';
11
11
  import { existsSync } from 'fs';
12
12
  const mcpServerName = MCP_SERVER_NAME;
13
13
  const claudeCode = 'claude';
14
- const windsurf = 'windsurf';
15
14
  const cursor = 'cursor';
15
+ const devin = 'devin';
16
16
  const vscode = 'vscode';
17
17
  const codex = 'codex';
18
18
  const gemini = 'gemini';
19
+ const opencode = 'opencode';
20
+ const OTHER_TOOL = 'other';
19
21
  const clientLabels = {
20
22
  codex: commands.mcp.setup.codex,
21
23
  claude: commands.mcp.setup.claudeCode,
22
24
  cursor: commands.mcp.setup.cursor,
25
+ devin: commands.mcp.setup.devin,
23
26
  gemini: commands.mcp.setup.gemini,
27
+ opencode: commands.mcp.setup.opencode,
24
28
  vscode: commands.mcp.setup.vsCode,
25
- windsurf: commands.mcp.setup.windsurf,
26
29
  };
27
- export const supportedTools = MCP_CLIENTS.map(client => ({
28
- name: clientLabels[client.id],
29
- value: client.id,
30
- }));
30
+ export const supportedTools = [
31
+ ...MCP_CLIENTS.map(client => ({
32
+ name: clientLabels[client.id],
33
+ value: client.id,
34
+ })),
35
+ { name: commands.mcp.setup.other, value: OTHER_TOOL },
36
+ ];
31
37
  const defaultMcpCommand = {
32
38
  command: 'hs',
33
39
  args: ['mcp', 'start'],
@@ -42,6 +48,7 @@ export async function configureMcpServer(options) {
42
48
  type: 'checkbox',
43
49
  message: commands.mcp.setup.prompts.targets,
44
50
  choices: supportedTools,
51
+ pageSize: supportedTools.length,
45
52
  validate: (choices) => {
46
53
  return choices.length === 0
47
54
  ? commands.mcp.setup.prompts.targetsRequired
@@ -53,7 +60,8 @@ export async function configureMcpServer(options) {
53
60
  else {
54
61
  derivedTargets = targets;
55
62
  }
56
- let useStandaloneMode = standalone;
63
+ const onlyOther = derivedTargets.length === 1 && derivedTargets[0] === OTHER_TOOL;
64
+ let useStandaloneMode = onlyOther ? false : standalone;
57
65
  if (useStandaloneMode === undefined) {
58
66
  const response = await promptUser({
59
67
  name: 'useStandaloneMode',
@@ -102,8 +110,8 @@ export async function configureMcpServer(options) {
102
110
  if (derivedTargets.includes(cursor)) {
103
111
  await runSetupFunction(() => setupCursor(mcpCommand));
104
112
  }
105
- if (derivedTargets.includes(windsurf)) {
106
- await runSetupFunction(() => setupWindsurf(mcpCommand));
113
+ if (derivedTargets.includes(devin)) {
114
+ await runSetupFunction(() => setupDevin(mcpCommand));
107
115
  }
108
116
  if (derivedTargets.includes(vscode)) {
109
117
  await runSetupFunction(() => setupVsCode(mcpCommand));
@@ -114,8 +122,17 @@ export async function configureMcpServer(options) {
114
122
  if (derivedTargets.includes(gemini)) {
115
123
  await runSetupFunction(() => setupGemini(mcpCommand));
116
124
  }
117
- uiLogger.info(commands.mcp.setup.success(derivedTargets));
118
- return derivedTargets;
125
+ if (derivedTargets.includes(opencode)) {
126
+ await runSetupFunction(() => setupOpenCode(mcpCommand));
127
+ }
128
+ if (derivedTargets.includes(OTHER_TOOL)) {
129
+ logOtherToolInstructions(mcpCommand);
130
+ }
131
+ const configuredTargets = derivedTargets.filter(t => t !== OTHER_TOOL);
132
+ if (configuredTargets.length > 0) {
133
+ uiLogger.info(commands.mcp.setup.success(configuredTargets));
134
+ }
135
+ return configuredTargets;
119
136
  }
120
137
  catch (error) {
121
138
  SpinniesManager.fail('mcpSetup', {
@@ -272,14 +289,14 @@ export function setupCursor(mcpCommand = defaultMcpCommand) {
272
289
  mcpCommand: buildCommandWithAgentString(mcpCommand, cursor),
273
290
  });
274
291
  }
275
- export function setupWindsurf(mcpCommand = defaultMcpCommand) {
276
- const windsurfConfigPath = path.join(os.homedir(), ...getMcpClientPathSegments(windsurf));
292
+ export function setupDevin(mcpCommand = defaultMcpCommand) {
293
+ const devinConfigPath = path.join(os.homedir(), ...getMcpClientPathSegments(devin));
277
294
  return setupMcpConfigFile({
278
- configPath: windsurfConfigPath,
279
- configuringMessage: commands.mcp.setup.spinners.configuringWindsurf,
280
- configuredMessage: commands.mcp.setup.spinners.configuredWindsurf,
281
- failedMessage: commands.mcp.setup.spinners.failedToConfigureWindsurf,
282
- mcpCommand: buildCommandWithAgentString(mcpCommand, windsurf),
295
+ configPath: devinConfigPath,
296
+ configuringMessage: commands.mcp.setup.spinners.configuringDevin,
297
+ configuredMessage: commands.mcp.setup.spinners.configuredDevin,
298
+ failedMessage: commands.mcp.setup.spinners.failedToConfigureDevin,
299
+ mcpCommand: buildCommandWithAgentString(mcpCommand, devin),
283
300
  });
284
301
  }
285
302
  export async function setupCodex(mcpCommand = defaultMcpCommand) {
@@ -341,6 +358,58 @@ export async function setupGemini(mcpCommand = defaultMcpCommand) {
341
358
  return false;
342
359
  }
343
360
  }
361
+ export async function setupOpenCode(mcpCommand = defaultMcpCommand) {
362
+ try {
363
+ SpinniesManager.add('openCodeSpinner', {
364
+ text: commands.mcp.setup.spinners.configuringOpenCode,
365
+ });
366
+ try {
367
+ await execAsync('opencode --version');
368
+ }
369
+ catch (error) {
370
+ SpinniesManager.fail('openCodeSpinner', {
371
+ text: commands.mcp.setup.spinners.openCodeNotFound,
372
+ });
373
+ return false;
374
+ }
375
+ const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, opencode);
376
+ await execAsync(`opencode mcp add "${mcpServerName}"${buildEnvFlagString(mcpCommand)} -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
377
+ SpinniesManager.succeed('openCodeSpinner', {
378
+ text: commands.mcp.setup.spinners.configuredOpenCode,
379
+ });
380
+ return true;
381
+ }
382
+ catch (error) {
383
+ SpinniesManager.fail('openCodeSpinner', {
384
+ text: commands.mcp.setup.spinners.openCodeInstallFailed,
385
+ });
386
+ logError(error);
387
+ return false;
388
+ }
389
+ }
390
+ function logOtherToolInstructions(mcpCommand) {
391
+ const { otherInstructions } = commands.mcp.setup;
392
+ uiLogger.log('');
393
+ uiLogger.log(otherInstructions.header);
394
+ uiLogger.log(otherInstructions.docsNote);
395
+ uiLogger.log('');
396
+ uiLogger.log(` ${otherInstructions.commandLabel}`);
397
+ uiLogger.log(` ${mcpCommand.command} ${mcpCommand.args.join(' ')}`);
398
+ uiLogger.log('');
399
+ uiLogger.log(` ${otherInstructions.jsonLabel}`);
400
+ uiLogger.log('');
401
+ const jsonConfig = {
402
+ mcpServers: {
403
+ [mcpServerName]: mcpCommand,
404
+ },
405
+ };
406
+ const indented = JSON.stringify(jsonConfig, null, 2)
407
+ .split('\n')
408
+ .map(line => ` ${line}`)
409
+ .join('\n');
410
+ uiLogger.log(indented);
411
+ uiLogger.log('');
412
+ }
344
413
  function buildCommandWithAgentString(mcpCommand, agent) {
345
414
  const mcpCommandCopy = structuredClone(mcpCommand);
346
415
  mcpCommandCopy.args.push('--ai-agent', agent);
@@ -4,6 +4,7 @@ export declare function getLatestPackageVersion(packageName: string): Promise<{
4
4
  latest: string | null;
5
5
  next: string | null;
6
6
  }>;
7
+ export declare function getNpmExecErrorOutput(error: unknown): string | null;
7
8
  export declare function executeInstall(packages?: string[], flags?: string | null, options?: {
8
9
  cwd?: string;
9
10
  }): Promise<void>;
package/lib/npm/npmCli.js CHANGED
@@ -23,6 +23,19 @@ export async function getLatestPackageVersion(packageName) {
23
23
  return { latest: null, next: null };
24
24
  }
25
25
  }
26
+ function isNpmExecError(error) {
27
+ return (typeof error === 'object' &&
28
+ error !== null &&
29
+ ('stderr' in error || 'stdout' in error));
30
+ }
31
+ export function getNpmExecErrorOutput(error) {
32
+ if (!isNpmExecError(error)) {
33
+ return null;
34
+ }
35
+ const stderr = error.stderr?.trim();
36
+ const stdout = error.stdout?.trim();
37
+ return stderr || stdout || null;
38
+ }
26
39
  export async function executeInstall(packages = [], flags, options) {
27
40
  const installCommand = `${DEFAULT_PACKAGE_MANAGER} install${flags ? ` ${flags}` : ''} ${packages.join(' ')}`;
28
41
  uiLogger.debug('Running', installCommand);
@@ -1,4 +1,3 @@
1
- export declare function getBaseHubSpotUrlForAccount(accountId: number): string;
2
1
  export declare function getProjectComponentDistributionUrl(projectName: string, componentName: string, accountId: number): string;
3
2
  export declare function getDeveloperOverviewUrl(accountId: number): string;
4
3
  export declare function getProjectDetailUrl(projectName: string, accountId: number): string | undefined;
@@ -1,18 +1,12 @@
1
- import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
2
- import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
3
- export function getBaseHubSpotUrlForAccount(accountId) {
4
- return getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
5
- }
1
+ import { getHubSpotWebsiteOriginByAccountId } from '@hubspot/local-dev-lib/urls';
6
2
  function getProjectHomeUrl(accountId) {
7
- return `${getBaseHubSpotUrlForAccount(accountId)}/developer-projects/${accountId}`;
3
+ return `${getHubSpotWebsiteOriginByAccountId(accountId)}/developer-projects/${accountId}`;
8
4
  }
9
5
  export function getProjectComponentDistributionUrl(projectName, componentName, accountId) {
10
- const baseUrl = getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
11
- return `${baseUrl}/developer-projects/${accountId}/project/${projectName}/component/${componentName}/distribution`;
6
+ return `${getHubSpotWebsiteOriginByAccountId(accountId)}/developer-projects/${accountId}/project/${projectName}/component/${componentName}/distribution`;
12
7
  }
13
8
  export function getDeveloperOverviewUrl(accountId) {
14
- const baseUrl = getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
15
- return `${baseUrl}/developer-overview/${accountId}`;
9
+ return `${getHubSpotWebsiteOriginByAccountId(accountId)}/developer-overview/${accountId}`;
16
10
  }
17
11
  export function getProjectDetailUrl(projectName, accountId) {
18
12
  if (!projectName)
@@ -34,9 +28,8 @@ export function getProjectDeployDetailUrl(projectName, deployId, accountId) {
34
28
  return `${getProjectActivityUrl(projectName, accountId)}/deploy/${deployId}`;
35
29
  }
36
30
  export function getLocalDevUiUrl(accountId, showWelcomeScreen) {
37
- return `${getBaseHubSpotUrlForAccount(accountId)}/developer-projects-local-dev/${accountId}${showWelcomeScreen ? '?welcome' : ''}`;
31
+ return `${getHubSpotWebsiteOriginByAccountId(accountId)}/developer-projects-local-dev/${accountId}${showWelcomeScreen ? '?welcome' : ''}`;
38
32
  }
39
33
  export function getAccountHomeUrl(accountId) {
40
- const baseUrl = getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
41
- return `${baseUrl}/home?portalId=${accountId}`;
34
+ return `${getHubSpotWebsiteOriginByAccountId(accountId)}/home?portalId=${accountId}`;
42
35
  }
@@ -4,8 +4,8 @@ import { Tool } from '../../Tool.js';
4
4
  import { formatTextContents } from '../../utils/content.js';
5
5
  import { absoluteCurrentWorkingDirectory, docsSearchQuery, } from './constants.js';
6
6
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
7
- import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
8
7
  import { setupHubSpotConfig } from '../../utils/config.js';
8
+ import { discoverAccountTargets } from '../../../lib/accountTargetDiscovery.js';
9
9
  import { getErrorMessage } from '../../../lib/errorHandlers/index.js';
10
10
  const docsSearchLimit = z
11
11
  .number()
@@ -33,18 +33,13 @@ export class DocsSearchTool extends Tool {
33
33
  }
34
34
  async handler({ docsSearchQuery, docsSearchLimit, absoluteCurrentWorkingDirectory, }) {
35
35
  setupHubSpotConfig(absoluteCurrentWorkingDirectory);
36
- let accountId;
37
- try {
38
- accountId = getConfigDefaultAccountIfExists()?.accountId;
39
- }
40
- catch {
41
- // Config file does not exist
42
- }
43
- if (!accountId) {
44
- const authErrorMessage = `No account ID found. Call the auth-account tool to authenticate a HubSpot account.`;
45
- return formatTextContents(authErrorMessage);
46
- }
47
36
  try {
37
+ const { recommended } = await discoverAccountTargets();
38
+ const accountId = recommended?.accountId;
39
+ if (!accountId) {
40
+ const authErrorMessage = `No account ID found. Call the auth-account tool to authenticate a HubSpot account.`;
41
+ return formatTextContents(authErrorMessage);
42
+ }
48
43
  const response = await http.post(accountId, {
49
44
  url: 'dev/docs/llms/v1/docs-search',
50
45
  data: {
@@ -5,6 +5,7 @@ import { McpLogger } from '../../utils/logger.js';
5
5
  import { z } from 'zod';
6
6
  declare const inputSchemaZodObject: z.ZodObject<{
7
7
  absoluteCurrentWorkingDirectory: z.ZodString;
8
+ absoluteProjectPath: z.ZodOptional<z.ZodString>;
8
9
  appId: z.ZodString;
9
10
  startDate: z.ZodOptional<z.ZodString>;
10
11
  endDate: z.ZodOptional<z.ZodString>;
@@ -12,7 +13,7 @@ declare const inputSchemaZodObject: z.ZodObject<{
12
13
  export type GetApiUsagePatternsByAppIdInputSchema = z.infer<typeof inputSchemaZodObject>;
13
14
  export declare class GetApiUsagePatternsByAppIdTool extends Tool<GetApiUsagePatternsByAppIdInputSchema> {
14
15
  constructor(mcpServer: McpServer, logger: McpLogger);
15
- handler({ appId, startDate, endDate, absoluteCurrentWorkingDirectory, }: GetApiUsagePatternsByAppIdInputSchema): Promise<TextContentResponse>;
16
+ handler({ appId, startDate, endDate, absoluteCurrentWorkingDirectory, absoluteProjectPath, }: GetApiUsagePatternsByAppIdInputSchema): Promise<TextContentResponse>;
16
17
  register(): RegisteredTool;
17
18
  }
18
19
  export {};
@@ -3,12 +3,13 @@ import { z } from 'zod';
3
3
  import { http } from '@hubspot/local-dev-lib/http';
4
4
  import { formatTextContents } from '../../utils/content.js';
5
5
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
6
- import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
7
- import { absoluteCurrentWorkingDirectory } from './constants.js';
6
+ import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
8
7
  import { setupHubSpotConfig } from '../../utils/config.js';
8
+ import { discoverAccountTargets } from '../../../lib/accountTargetDiscovery.js';
9
9
  import { getErrorMessage } from '../../../lib/errorHandlers/index.js';
10
10
  const inputSchema = {
11
11
  absoluteCurrentWorkingDirectory,
12
+ absoluteProjectPath: absoluteProjectPath.optional(),
12
13
  appId: z
13
14
  .string()
14
15
  .describe('The numeric app ID as a string (e.g., "3003909"). Must contain only digits. Use get-apps-info to find available app IDs.'),
@@ -28,16 +29,11 @@ export class GetApiUsagePatternsByAppIdTool extends Tool {
28
29
  constructor(mcpServer, logger) {
29
30
  super(mcpServer, logger, toolName);
30
31
  }
31
- async handler({ appId, startDate, endDate, absoluteCurrentWorkingDirectory, }) {
32
- setupHubSpotConfig(absoluteCurrentWorkingDirectory);
32
+ async handler({ appId, startDate, endDate, absoluteCurrentWorkingDirectory, absoluteProjectPath, }) {
33
+ setupHubSpotConfig(absoluteProjectPath ?? absoluteCurrentWorkingDirectory);
33
34
  try {
34
- let accountId;
35
- try {
36
- accountId = getConfigDefaultAccountIfExists()?.accountId;
37
- }
38
- catch {
39
- // Config file does not exist
40
- }
35
+ const { recommended } = await discoverAccountTargets();
36
+ const accountId = recommended?.accountId;
41
37
  if (!accountId) {
42
38
  const authErrorMessage = `No account ID found. Call the auth-account tool to authenticate a HubSpot account.`;
43
39
  return formatTextContents(authErrorMessage);
@@ -5,11 +5,12 @@ import { McpLogger } from '../../utils/logger.js';
5
5
  import { z } from 'zod';
6
6
  declare const inputSchemaZodObject: z.ZodObject<{
7
7
  absoluteCurrentWorkingDirectory: z.ZodString;
8
+ absoluteProjectPath: z.ZodOptional<z.ZodString>;
8
9
  }, z.core.$strip>;
9
10
  export type GetApplicationInfoInputSchema = z.infer<typeof inputSchemaZodObject>;
10
11
  export declare class GetApplicationInfoTool extends Tool<GetApplicationInfoInputSchema> {
11
12
  constructor(mcpServer: McpServer, logger: McpLogger);
12
- handler({ absoluteCurrentWorkingDirectory, }: GetApplicationInfoInputSchema): Promise<TextContentResponse>;
13
+ handler({ absoluteCurrentWorkingDirectory, absoluteProjectPath, }: GetApplicationInfoInputSchema): Promise<TextContentResponse>;
13
14
  register(): RegisteredTool;
14
15
  }
15
16
  export {};
@@ -3,11 +3,14 @@ import { z } from 'zod';
3
3
  import { http } from '@hubspot/local-dev-lib/http';
4
4
  import { formatTextContents } from '../../utils/content.js';
5
5
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
6
- import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
7
- import { absoluteCurrentWorkingDirectory } from './constants.js';
6
+ import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
8
7
  import { setupHubSpotConfig } from '../../utils/config.js';
8
+ import { discoverAccountTargets } from '../../../lib/accountTargetDiscovery.js';
9
9
  import { getErrorMessage } from '../../../lib/errorHandlers/index.js';
10
- const inputSchema = { absoluteCurrentWorkingDirectory };
10
+ const inputSchema = {
11
+ absoluteCurrentWorkingDirectory,
12
+ absoluteProjectPath: absoluteProjectPath.optional(),
13
+ };
11
14
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
12
15
  const inputSchemaZodObject = z.object({ ...inputSchema });
13
16
  const toolName = 'get-apps-info';
@@ -15,16 +18,11 @@ export class GetApplicationInfoTool extends Tool {
15
18
  constructor(mcpServer, logger) {
16
19
  super(mcpServer, logger, toolName);
17
20
  }
18
- async handler({ absoluteCurrentWorkingDirectory, }) {
19
- setupHubSpotConfig(absoluteCurrentWorkingDirectory);
21
+ async handler({ absoluteCurrentWorkingDirectory, absoluteProjectPath, }) {
22
+ setupHubSpotConfig(absoluteProjectPath ?? absoluteCurrentWorkingDirectory);
20
23
  try {
21
- let accountId;
22
- try {
23
- accountId = getConfigDefaultAccountIfExists()?.accountId;
24
- }
25
- catch {
26
- // Config file does not exist
27
- }
24
+ const { recommended } = await discoverAccountTargets();
25
+ const accountId = recommended?.accountId;
28
26
  if (!accountId) {
29
27
  const authErrorMessage = `No account ID found. Call the auth-account tool to authenticate a HubSpot account.`;
30
28
  return formatTextContents(authErrorMessage);
@@ -5,8 +5,8 @@ import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
5
5
  import { http } from '@hubspot/local-dev-lib/http';
6
6
  import { getProjectConfig, validateProjectConfig, } from '../../../lib/projects/config.js';
7
7
  import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
8
- import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
9
8
  import { setupHubSpotConfig } from '../../utils/config.js';
9
+ import { discoverAccountTargets } from '../../../lib/accountTargetDiscovery.js';
10
10
  const TOOL_NAME = 'get-build-logs';
11
11
  const PROJECTS_LOGS_API_PATH = 'dfs/logging/v1';
12
12
  const inputSchema = {
@@ -62,20 +62,18 @@ export class GetBuildLogsTool extends Tool {
62
62
  super(mcpServer, logger, TOOL_NAME);
63
63
  }
64
64
  async handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, buildId, logLevel, }) {
65
- setupHubSpotConfig(absoluteCurrentWorkingDirectory);
65
+ setupHubSpotConfig(absoluteProjectPath);
66
66
  try {
67
- let accountId;
68
- try {
69
- accountId = getConfigDefaultAccountIfExists()?.accountId;
70
- }
71
- catch {
72
- // Config file does not exist
73
- }
67
+ const { projectConfig, projectDir } = await getProjectConfig(absoluteProjectPath);
68
+ validateProjectConfig(projectConfig, projectDir);
69
+ const { recommended } = await discoverAccountTargets({
70
+ projectDir,
71
+ projectConfig,
72
+ });
73
+ const accountId = recommended?.accountId;
74
74
  if (!accountId) {
75
75
  return formatTextContents(absoluteCurrentWorkingDirectory, 'No account ID found. Call the auth-account tool to authenticate a HubSpot account.');
76
76
  }
77
- const { projectConfig, projectDir } = await getProjectConfig(absoluteProjectPath);
78
- validateProjectConfig(projectConfig, projectDir);
79
77
  const projectName = projectConfig.name;
80
78
  const response = await http.get(accountId, {
81
79
  url: `${PROJECTS_LOGS_API_PATH}/logs/projects/${encodeURIComponent(projectName)}/builds/${buildId}`,
@@ -6,8 +6,8 @@ import { fetchProjectBuilds, getBuildStatus, } from '@hubspot/local-dev-lib/api/
6
6
  import { getProjectConfig, validateProjectConfig, } from '../../../lib/projects/config.js';
7
7
  import moment from 'moment';
8
8
  import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
9
- import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
10
9
  import { setupHubSpotConfig } from '../../utils/config.js';
10
+ import { discoverAccountTargets } from '../../../lib/accountTargetDiscovery.js';
11
11
  const TOOL_NAME = 'get-build-status';
12
12
  const inputSchema = {
13
13
  absoluteProjectPath,
@@ -107,20 +107,18 @@ export class GetBuildStatusTool extends Tool {
107
107
  super(mcpServer, logger, TOOL_NAME);
108
108
  }
109
109
  async handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, buildId, limit, }) {
110
- setupHubSpotConfig(absoluteCurrentWorkingDirectory);
110
+ setupHubSpotConfig(absoluteProjectPath);
111
111
  try {
112
- let accountId;
113
- try {
114
- accountId = getConfigDefaultAccountIfExists()?.accountId;
115
- }
116
- catch {
117
- // Config file does not exist
118
- }
112
+ const { projectConfig, projectDir } = await getProjectConfig(absoluteProjectPath);
113
+ validateProjectConfig(projectConfig, projectDir);
114
+ const { recommended } = await discoverAccountTargets({
115
+ projectDir,
116
+ projectConfig,
117
+ });
118
+ const accountId = recommended?.accountId;
119
119
  if (!accountId) {
120
120
  return formatTextContents(absoluteCurrentWorkingDirectory, 'No account ID found. Call the auth-account tool to authenticate a HubSpot account.');
121
121
  }
122
- const { projectConfig, projectDir } = await getProjectConfig(absoluteProjectPath);
123
- validateProjectConfig(projectConfig, projectDir);
124
122
  const projectName = projectConfig.name;
125
123
  let output;
126
124
  if (buildId) {
@@ -5,13 +5,14 @@ import { McpLogger } from '../../utils/logger.js';
5
5
  import { z } from 'zod';
6
6
  declare const inputSchemaZodObject: z.ZodObject<{
7
7
  absoluteCurrentWorkingDirectory: z.ZodString;
8
+ absoluteProjectPath: z.ZodOptional<z.ZodString>;
8
9
  platformVersion: z.ZodString;
9
10
  featureType: z.ZodString;
10
11
  }, z.core.$strip>;
11
12
  type InputSchemaType = z.infer<typeof inputSchemaZodObject>;
12
13
  export declare class GetConfigValuesTool extends Tool<InputSchemaType> {
13
14
  constructor(mcpServer: McpServer, logger: McpLogger);
14
- handler({ platformVersion, featureType, absoluteCurrentWorkingDirectory, }: InputSchemaType): Promise<TextContentResponse>;
15
+ handler({ platformVersion, featureType, absoluteCurrentWorkingDirectory, absoluteProjectPath, }: InputSchemaType): Promise<TextContentResponse>;
15
16
  register(): RegisteredTool;
16
17
  }
17
18
  export {};
@@ -1,14 +1,15 @@
1
1
  import { Tool } from '../../Tool.js';
2
2
  import { z } from 'zod';
3
3
  import { formatTextContents } from '../../utils/content.js';
4
- import { absoluteCurrentWorkingDirectory } from './constants.js';
4
+ import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
5
5
  import { getIntermediateRepresentationSchema } from '@hubspot/project-parsing-lib/schema';
6
6
  import { mapToInternalType } from '@hubspot/project-parsing-lib/transform';
7
7
  import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
8
- import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
9
8
  import { setupHubSpotConfig } from '../../utils/config.js';
9
+ import { discoverAccountTargets } from '../../../lib/accountTargetDiscovery.js';
10
10
  const inputSchema = {
11
11
  absoluteCurrentWorkingDirectory,
12
+ absoluteProjectPath: absoluteProjectPath.optional(),
12
13
  platformVersion: z
13
14
  .string()
14
15
  .describe('The platform version for the project. Located in the hsproject.json file.'),
@@ -25,19 +26,14 @@ export class GetConfigValuesTool extends Tool {
25
26
  constructor(mcpServer, logger) {
26
27
  super(mcpServer, logger, toolName);
27
28
  }
28
- async handler({ platformVersion, featureType, absoluteCurrentWorkingDirectory, }) {
29
- setupHubSpotConfig(absoluteCurrentWorkingDirectory);
29
+ async handler({ platformVersion, featureType, absoluteCurrentWorkingDirectory, absoluteProjectPath, }) {
30
+ setupHubSpotConfig(absoluteProjectPath ?? absoluteCurrentWorkingDirectory);
30
31
  try {
31
32
  if (isLegacyProject(platformVersion)) {
32
33
  return formatTextContents(`Can only be used on projects with a minimum platformVersion of 2025.2`);
33
34
  }
34
- let accountId;
35
- try {
36
- accountId = getConfigDefaultAccountIfExists()?.accountId;
37
- }
38
- catch {
39
- // Config file does not exist
40
- }
35
+ const { recommended } = await discoverAccountTargets();
36
+ const accountId = recommended?.accountId;
41
37
  if (!accountId) {
42
38
  const authErrorMessage = `No account ID found. Call the auth-account tool to authenticate a HubSpot account.`;
43
39
  return formatTextContents(authErrorMessage);
@@ -8,4 +8,9 @@ export function setupHubSpotConfig(absoluteCurrentWorkingDirectory) {
8
8
  if (configPath) {
9
9
  process.env.HUBSPOT_CONFIG_PATH = configPath;
10
10
  }
11
+ else {
12
+ // Clear any value set by a previous tool call so this call doesn't
13
+ // inherit a stale config path from a different project.
14
+ delete process.env.HUBSPOT_CONFIG_PATH;
15
+ }
11
16
  }
@@ -1,5 +1,6 @@
1
1
  import { EventClass, getExecutionEnvironmentMeta, } from '../../lib/usageTracking.js';
2
- import { getConfig, getConfigDefaultAccountIfExists, } from '@hubspot/local-dev-lib/config';
2
+ import { getConfig } from '@hubspot/local-dev-lib/config';
3
+ import { discoverAccountTargets } from '../../lib/accountTargetDiscovery.js';
3
4
  import { sendUsageEvent } from '../../lib/api/usageTracking.js';
4
5
  export async function trackToolUsage(toolName, meta) {
5
6
  let config;
@@ -20,7 +21,14 @@ export async function trackToolUsage(toolName, meta) {
20
21
  ...getExecutionEnvironmentMeta(),
21
22
  ...meta,
22
23
  };
23
- const accountId = getConfigDefaultAccountIfExists()?.accountId || undefined;
24
+ let accountId;
25
+ try {
26
+ const { recommended } = await discoverAccountTargets();
27
+ accountId = recommended?.accountId;
28
+ }
29
+ catch {
30
+ // Account discovery failed; continue without account ID
31
+ }
24
32
  try {
25
33
  await sendUsageEvent({
26
34
  eventName: 'cli-interaction',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "8.12.0",
3
+ "version": "8.13.0",
4
4
  "description": "The official CLI for developing on HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": "https://github.com/HubSpot/hubspot-cli",
@@ -10,10 +10,10 @@
10
10
  "!**/__tests__/**"
11
11
  ],
12
12
  "dependencies": {
13
- "@hubspot/local-dev-lib": "5.10.1",
13
+ "@hubspot/local-dev-lib": "5.10.2",
14
14
  "@hubspot/project-parsing-lib": "0.21.0",
15
15
  "@hubspot/serverless-dev-runtime": "7.0.7",
16
- "@hubspot/ui-extensions-dev-server": "2.0.13",
16
+ "@hubspot/ui-extensions-dev-server": "2.1.0",
17
17
  "@inquirer/prompts": "7.1.0",
18
18
  "@modelcontextprotocol/sdk": "1.29.0",
19
19
  "archiver": "7.0.1",
@@ -44,7 +44,7 @@
44
44
  "zod": "^4.4.3"
45
45
  },
46
46
  "devDependencies": {
47
- "@hubspot/npm-scripts": "0.3.1",
47
+ "@hubspot/npm-scripts": "^0.3.3",
48
48
  "@types/archiver": "^6.0.3",
49
49
  "@types/cli-progress": "^3.11.6",
50
50
  "@types/express": "^5.0.0",