@hubspot/cli 8.9.1 → 8.10.0-beta.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 (53) hide show
  1. package/commands/account/auth.js +2 -0
  2. package/commands/app/logs.js +4 -0
  3. package/commands/getStarted.js +3 -0
  4. package/commands/init.js +2 -0
  5. package/commands/mcp/setup.js +2 -2
  6. package/commands/project/appInstallStatus.js +2 -2
  7. package/commands/project/create.js +2 -0
  8. package/commands/project/dev/unifiedFlow.js +3 -1
  9. package/commands/project/installApp.d.ts +7 -0
  10. package/commands/project/installApp.js +181 -0
  11. package/commands/project/installDeps.js +2 -0
  12. package/commands/project/upload.d.ts +2 -0
  13. package/commands/project/upload.js +26 -5
  14. package/commands/project.js +2 -0
  15. package/commands/upgrade.js +2 -0
  16. package/lang/en.d.ts +66 -0
  17. package/lang/en.js +73 -7
  18. package/lib/accountTargetDiscovery.d.ts +15 -0
  19. package/lib/accountTargetDiscovery.js +175 -0
  20. package/lib/app/install.d.ts +22 -0
  21. package/lib/app/install.js +42 -0
  22. package/lib/app/logs.js +1 -1
  23. package/lib/auth/awaitPersonalAccessKeyOverWebsocket.js +3 -2
  24. package/lib/doctor/Doctor.d.ts +1 -0
  25. package/lib/doctor/Doctor.js +26 -0
  26. package/lib/getStartedV2Actions.js +2 -1
  27. package/lib/mcp/clients.d.ts +13 -0
  28. package/lib/mcp/clients.js +55 -0
  29. package/lib/mcp/promotion.d.ts +3 -0
  30. package/lib/mcp/promotion.js +109 -0
  31. package/lib/mcp/setup.d.ts +3 -2
  32. package/lib/mcp/setup.js +17 -12
  33. package/lib/projects/installApp.d.ts +58 -0
  34. package/lib/projects/installApp.js +283 -0
  35. package/lib/projects/localDev/AppDevModeInterface.d.ts +0 -3
  36. package/lib/projects/localDev/AppDevModeInterface.js +22 -44
  37. package/lib/projects/localDev/LocalDevProcess.js +4 -2
  38. package/lib/projects/localDev/helpers/project.js +3 -2
  39. package/lib/projects/ui.d.ts +1 -0
  40. package/lib/projects/ui.js +14 -0
  41. package/lib/projects/upload.d.ts +10 -2
  42. package/lib/projects/upload.js +15 -4
  43. package/lib/prompts/projectDevTargetAccountPrompt.js +3 -2
  44. package/lib/theme/cmsDevServerProcess.js +1 -1
  45. package/lib/usageTracking.d.ts +1 -0
  46. package/lib/usageTracking.js +9 -0
  47. package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
  48. package/mcp-server/tools/project/UploadProjectTools.js +1 -1
  49. package/package.json +5 -5
  50. package/types/AccountTargets.d.ts +37 -0
  51. package/types/AccountTargets.js +12 -0
  52. package/types/LocalDev.d.ts +3 -2
  53. package/types/ProjectComponents.d.ts +2 -1
package/lang/en.js CHANGED
@@ -4,7 +4,7 @@ import { PLATFORM_VERSIONS } from '@hubspot/project-parsing-lib/constants';
4
4
  import { PERSONAL_ACCESS_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constants/auth';
5
5
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, LEGACY_PUBLIC_APP_FILE, LOCAL_DEV_DEFAULT_PORT, PROJECT_CONFIG_FILE, PROJECT_WITH_APP, } from '../lib/constants.js';
6
6
  import { ARCHIVED_HUBSPOT_CONFIG_YAML_FILE_NAME, DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME, GLOBAL_CONFIG_PATH, } from '@hubspot/local-dev-lib/constants/config';
7
- import { indent, UI_COLORS, uiAccountDescription, uiAuthCommandReference, uiBetaTag, uiCommandReference, uiLink, } from '../lib/ui/index.js';
7
+ import { indent, UI_COLORS, uiAccountDescription, uiAuthCommandReference, uiBetaTag, uiCommandReference, uiDeprecatedTag, uiLink, } from '../lib/ui/index.js';
8
8
  import { getLocalDevUiUrl, getProjectDetailUrl, getProjectSettingsUrl, } from '../lib/projects/urls.js';
9
9
  import { getProductUpdatesUrl } from '../lib/links.js';
10
10
  export const commands = {
@@ -1325,8 +1325,11 @@ export const commands = {
1325
1325
  },
1326
1326
  mcp: {
1327
1327
  describe: 'Commands for managing HubSpot MCP servers.',
1328
+ promotion: {
1329
+ activeNudge: `${chalk.bold('Tip:')} Work faster with AI coding tools. Run ${uiCommandReference('hs mcp setup')} to connect HubSpot developer context to supported clients.`,
1330
+ },
1328
1331
  setup: {
1329
- describe: 'Setup the HubSpot development MCP servers.',
1332
+ describe: `Run ${uiCommandReference('hs mcp setup')} to connect the HubSpot Dev MCP and work faster with your AI coding tools.`,
1330
1333
  installingDocSearch: 'Adding the docs-search mcp server',
1331
1334
  codex: 'Codex CLI',
1332
1335
  claudeCode: 'Claude Code',
@@ -1900,8 +1903,11 @@ export const commands = {
1900
1903
  targetRequiresPreview: `${uiCommandReference('--target')} can only be used with ${uiCommandReference('--preview')}.`,
1901
1904
  },
1902
1905
  options: {
1906
+ force: {
1907
+ describe: 'Skip confirmation prompts and force the upload.',
1908
+ },
1903
1909
  forceCreate: {
1904
- describe: 'Automatically create project if it does not exist',
1910
+ describe: uiDeprecatedTag(`Automatically create project if it does not exist. Use ${uiCommandReference('--force')} instead.`, false),
1905
1911
  },
1906
1912
  message: {
1907
1913
  describe: 'Add a message when you upload your project and create a build',
@@ -1912,6 +1918,9 @@ export const commands = {
1912
1918
  skipNpmAudit: {
1913
1919
  describe: 'Skip the npm audit security check before uploading',
1914
1920
  },
1921
+ skipAutoDeploy: {
1922
+ describe: 'Skip automatic deployment after a successful build',
1923
+ },
1915
1924
  preview: {
1916
1925
  describe: 'Preview the build on a target portal after a successful upload',
1917
1926
  },
@@ -2192,6 +2201,47 @@ export const commands = {
2192
2201
  force: 'skip confirmation prompt',
2193
2202
  },
2194
2203
  },
2204
+ installApp: {
2205
+ describe: 'Install a static auth app from the current project into the target account',
2206
+ verboseDescribe: `Install a static auth app from the current project into the target account.\n\nRun this command from within the project directory. The project must contain a privately distributed static auth app and must already be uploaded to the target account.`,
2207
+ examples: {
2208
+ default: 'Install the static auth app from the current project',
2209
+ withAccount: 'Install the app into a specific account',
2210
+ withProfile: 'Install the app into the account targeted by a profile',
2211
+ json: 'Output install result as JSON',
2212
+ },
2213
+ options: {
2214
+ force: 'Skip confirmation prompts',
2215
+ profile: 'The profile to target with this install',
2216
+ },
2217
+ errors: {
2218
+ noProjectConfig: `No project config found. Run this command from within a HubSpot project directory, or use ${uiCommandReference('hs project create')} to create a new one.`,
2219
+ unsupportedPlatformVersion: (platformVersion) => `This command is only supported for projects on platform version 2025.2 or later (detected: ${platformVersion}).`,
2220
+ failedToParseProject: 'Failed to parse the project. Check your project configuration is valid and try again.',
2221
+ noAppInProject: 'No app was found in the local project. This command requires a project that contains an app.',
2222
+ unsupportedAuthType: (authType) => `This command only supports static auth apps. Detected auth type: ${authType}.`,
2223
+ unsupportedDistribution: (distribution) => `This command only supports privately distributed apps. Detected distribution: ${distribution}.`,
2224
+ invalidAppDeveloperAccount: (accountId) => `Static auth apps can't be installed into the app developer account ${uiAccountDescription(accountId)}. Switch to a standard, sandbox, or developer test account with ${uiCommandReference('hs account use')}, or pass ${uiCommandReference('--account')}.`,
2225
+ projectNotFound: (accountId, projectName) => `The project ${chalk.bold(projectName)} does not exist in ${uiAccountDescription(accountId)}. Run ${uiCommandReference('hs project upload')} to upload your project files to HubSpot.`,
2226
+ appNotDeployed: (appName, accountId) => `The app ${chalk.bold(appName)} has not been deployed to ${uiAccountDescription(accountId)}. Upload the project with ${uiCommandReference('hs project upload')} and ensure the build succeeds, then try again.`,
2227
+ automaticInstallUnavailable: (appName, accountId) => `${chalk.bold(appName)} could not be installed automatically in ${uiAccountDescription(accountId)}.`,
2228
+ installFailed: (appName, accountId) => `Failed to install ${chalk.bold(appName)} in ${uiAccountDescription(accountId)}.`,
2229
+ },
2230
+ jsonErrors: {
2231
+ automaticInstallUnavailable: 'Automatic install is unavailable for this account.',
2232
+ installFailed: (appName, accountId) => `Failed to install ${appName} in account ${accountId}.`,
2233
+ },
2234
+ profileMessage: (profileName, accountId) => `Installing with ${chalk.bold(profileName)} profile: ${uiAccountDescription(accountId)}`,
2235
+ appDeveloperAccountNotice: (accountId) => `${uiAccountDescription(accountId)} is an app developer account, which can't have apps installed into it.`,
2236
+ selectInstallAccountPrompt: 'Select an account to install the app into',
2237
+ uploadAndDeployPrompt: (accountId) => `The app isn't deployed to ${uiAccountDescription(accountId)} yet. Upload and deploy the project now?`,
2238
+ alreadyInstalled: (appName, accountId) => `${chalk.bold(appName)} is already installed in ${uiAccountDescription(accountId)}.`,
2239
+ outdatedScopes: (appName, accountId) => `${chalk.bold(appName)} is installed in ${uiAccountDescription(accountId)} with outdated scopes.`,
2240
+ reinstallPrompt: 'Reinstall the app to grant the latest scopes?',
2241
+ installPrompt: (appName, accountId) => `Install ${chalk.bold(appName)} in ${uiAccountDescription(accountId)}?`,
2242
+ installFromBrowser: (installUrl) => `Install the app in HubSpot: ${uiLink('Open install page', installUrl)}`,
2243
+ success: (appName, accountId) => `${chalk.bold(appName)} installed in ${uiAccountDescription(accountId)}.`,
2244
+ },
2195
2245
  installStatus: {
2196
2246
  describe: 'Check whether a static auth app in the current project is installed in the target account. This command must be run from within a HubSpot project directory.',
2197
2247
  examples: {
@@ -2632,7 +2682,7 @@ export const commands = {
2632
2682
  serviceLevel: 'Service Hub tier. Options: FREE, STARTER, PROFESSIONAL, ENTERPRISE',
2633
2683
  salesLevel: 'Sales Hub tier. Options: FREE, STARTER, PROFESSIONAL, ENTERPRISE',
2634
2684
  contentLevel: 'Content Hub tier. Options: FREE, STARTER, PROFESSIONAL, ENTERPRISE',
2635
- commerceLevel: 'Commerce Hub tier. Options: FREE, PROFESSIONAL, ENTERPRISE',
2685
+ commerceLevel: 'Revenue Hub tier. Options: FREE, PROFESSIONAL, ENTERPRISE',
2636
2686
  },
2637
2687
  example: (configPath) => `Create a test account from the config file at ${configPath}`,
2638
2688
  examples: {
@@ -3293,6 +3343,11 @@ export const lib = {
3293
3343
  configMiddleWare: {
3294
3344
  invalidAccountIdEnvironmentVariable: 'Unable to parse `HUBSPOT_ACCOUNT_ID` environment variable into a number',
3295
3345
  },
3346
+ accountTargetDiscovery: {
3347
+ errors: {
3348
+ explicitAccountNotFound: (account) => `Account ${chalk.bold(String(account))} was not found in your config. Run ${uiCommandReference('hs account list')} to see available accounts.`,
3349
+ },
3350
+ },
3296
3351
  process: {
3297
3352
  exitDebug: (signal) => `Attempting to gracefully exit. Triggered by ${signal}`,
3298
3353
  },
@@ -3569,6 +3624,11 @@ export const lib = {
3569
3624
  feedbackHeader: "We'd love to hear your feedback!",
3570
3625
  feedbackMessage: `How are you liking the new projects and developer tools? \n > Run ${uiCommandReference('hs feedback')} to let us know what you think!\n`,
3571
3626
  },
3627
+ skippedHsMetaFiles: {
3628
+ warning: (files) => `The following *-hsmeta.json files will be skipped.\nThis CLI version doesn't support the features they require:\n${files.map(f => ` ${chalk.bold(f)}`).join('\n')}\nRun ${uiCommandReference('hs upgrade')} to update the CLI, which may add support for these features.`,
3629
+ uploadWarning: (files) => `The following *-hsmeta.json files will be skipped during upload.\nThis CLI version doesn't support the features they require:\n${files.map(f => ` ${chalk.bold(f)}`).join('\n')}\nRun ${uiCommandReference('hs upgrade')} to update the CLI, which may add support for these features.`,
3630
+ prompt: '[--force] Continue anyway?',
3631
+ },
3572
3632
  components: {
3573
3633
  unableToGetUidFromHsmeta: 'Unable to get UID from hsmeta',
3574
3634
  buildSuccessMessage: {
@@ -3576,9 +3636,9 @@ export const lib = {
3576
3636
  docsUrl: 'https://developers.hubspot.com/docs/apps/developer-platform/build-apps/overview',
3577
3637
  headerCreated: (projectName, projectDest) => `${chalk.bold(projectName)} was successfully created in ${projectDest}`,
3578
3638
  headerAdded: (featureText, uid, plural) => `${featureText} ${plural ? 'were' : 'was'} successfully added to ${uid}:`,
3579
- docsDetails: (docsLink) => `📖 ${docsLink} for more details about building and testing these features.`,
3580
- uploadPrompt: `🚀 Run ${uiCommandReference('hs project upload')} when you're ready to deploy.`,
3581
- devPrompt: `🧪 Run ${uiCommandReference('hs project dev')} to start local development.`,
3639
+ docsDetails: (docsLink) => `${docsLink} for more details about building and testing these features.`,
3640
+ uploadPrompt: `Run ${uiCommandReference('hs project upload')} when you're ready to deploy.`,
3641
+ devPrompt: `Run ${uiCommandReference('hs project dev')} to start local development.`,
3582
3642
  },
3583
3643
  },
3584
3644
  },
@@ -3873,6 +3933,7 @@ export const lib = {
3873
3933
  confirmUseExistingDeveloperTestAccount: (accountName) => `Continue with ${accountName}? This account isn't currently connected to the HubSpot CLI. By continuing, you'll be prompted to generate a personal access key and connect it.`,
3874
3934
  confirmLinkExistingDeveloperTestAccount: (accountName) => `${accountName} is not linked to this directory. Would you like to link it?`,
3875
3935
  noAccountId: 'No account ID found for the selected account. Please try again.',
3936
+ fetchDeveloperTestAccountsError: 'Unable to fetch developer test accounts. Please try again.',
3876
3937
  },
3877
3938
  projectLogsPrompt: {
3878
3939
  functionName: (projectName) => `[--function] Select function in ${chalk.bold(projectName)} project`,
@@ -4277,6 +4338,11 @@ export const lib = {
4277
4338
  installed: (npmVersion) => `npm v${npmVersion} is installed`,
4278
4339
  unableToDetermine: 'Unable to determine if npm is installed',
4279
4340
  },
4341
+ mcpChecks: {
4342
+ configured: (clients) => `HubSpot MCP server configured in ${clients.join(', ')}`,
4343
+ notConfigured: (clients) => `HubSpot MCP server not configured in ${clients.join(', ')}`,
4344
+ notConfiguredSecondary: `Run ${uiCommandReference('hs mcp setup')} to connect AI coding tools to HubSpot developer context`,
4345
+ },
4280
4346
  hsChecks: {
4281
4347
  notLatest: (hsVersion) => `Version ${hsVersion} outdated`,
4282
4348
  notLatestSecondary: (command, hsVersion) => `Run ${uiCommandReference(command)} to upgrade to the latest version ${hsVersion}`,
@@ -0,0 +1,15 @@
1
+ import { DiscoverAccountTargetsOptions, DiscoverAccountTargetsResult } from '../types/AccountTargets.js';
2
+ /**
3
+ * Discovers the candidate target accounts for a command, plus the recommended
4
+ * one to use when a single account is the obvious choice.
5
+ *
6
+ * Profiles take precedence: when the project defines profiles, the candidates
7
+ * are the profile accounts only. Otherwise they are ranked explicit account,
8
+ * then env config, then linked directory, then global default.
9
+ *
10
+ * Takes plain options (not yargs argv) and never prompts or exits, so both the
11
+ * CLI and MCP can call it. `recommended` is omitted when no account is the
12
+ * obvious choice (for example, several profiles). Throws when an explicit
13
+ * account is provided but not found in the config.
14
+ */
15
+ export declare function discoverAccountTargets(options?: DiscoverAccountTargetsOptions): Promise<DiscoverAccountTargetsResult>;
@@ -0,0 +1,175 @@
1
+ import path from 'path';
2
+ import { getConfigAccountIfExists, getConfigDefaultAccountIfExists, } from '@hubspot/local-dev-lib/config';
3
+ import { getHsSettingsFileIfExists } from '@hubspot/local-dev-lib/config/hsSettings';
4
+ import { ENVIRONMENT_VARIABLES } from '@hubspot/local-dev-lib/constants/config';
5
+ import { getAllHsProfiles, loadHsProfileFile, } from '@hubspot/project-parsing-lib/profiles';
6
+ import { isTestAccountOrSandbox } from './accountTypes.js';
7
+ import { isDirectoryLinked } from './link/linkUtils.js';
8
+ import { lib } from '../lang/en.js';
9
+ import { ACCOUNT_TARGET_CATEGORIES, ACCOUNT_TARGET_SELECTION_SOURCES, } from '../types/AccountTargets.js';
10
+ function categorizeAccount(account) {
11
+ if (isTestAccountOrSandbox(account)) {
12
+ return ACCOUNT_TARGET_CATEGORIES.RECOMMENDED_TESTING;
13
+ }
14
+ if (!account.accountType) {
15
+ return ACCOUNT_TARGET_CATEGORIES.UNKNOWN;
16
+ }
17
+ return ACCOUNT_TARGET_CATEGORIES.PRODUCTION_WITH_CARE;
18
+ }
19
+ function candidateFromAccount(account, source, profileName) {
20
+ return {
21
+ accountId: account.accountId,
22
+ accountName: account.name,
23
+ accountType: account.accountType,
24
+ environment: account.env || undefined,
25
+ source,
26
+ category: categorizeAccount(account),
27
+ ...(profileName ? { profileName } : {}),
28
+ };
29
+ }
30
+ function candidateFromAccountId(accountId, source, profileName) {
31
+ const account = getConfigAccountIfExists(accountId);
32
+ if (account) {
33
+ return candidateFromAccount(account, source, profileName);
34
+ }
35
+ return {
36
+ accountId,
37
+ source,
38
+ category: ACCOUNT_TARGET_CATEGORIES.UNKNOWN,
39
+ ...(profileName ? { profileName } : {}),
40
+ };
41
+ }
42
+ function dedupeByAccountId(candidates) {
43
+ const seen = new Set();
44
+ return candidates.filter(candidate => {
45
+ if (seen.has(candidate.accountId)) {
46
+ return false;
47
+ }
48
+ seen.add(candidate.accountId);
49
+ return true;
50
+ });
51
+ }
52
+ async function gatherProfileCandidates(projectDir, projectConfig) {
53
+ const projectSourceDir = path.join(projectDir, projectConfig.srcDir);
54
+ const profileNames = await getAllHsProfiles(projectSourceDir);
55
+ const candidates = [];
56
+ for (const name of profileNames) {
57
+ let profile;
58
+ try {
59
+ profile = loadHsProfileFile(projectSourceDir, name);
60
+ }
61
+ catch {
62
+ continue;
63
+ }
64
+ if (profile && profile.accountId) {
65
+ candidates.push(candidateFromAccountId(profile.accountId, ACCOUNT_TARGET_SELECTION_SOURCES.PROFILE, name));
66
+ }
67
+ }
68
+ return candidates;
69
+ }
70
+ function resolveProfileRecommendation(profileCandidates, explicitProfileName) {
71
+ if (explicitProfileName) {
72
+ return profileCandidates.find(candidate => candidate.profileName === explicitProfileName);
73
+ }
74
+ if (profileCandidates.length === 1) {
75
+ return profileCandidates[0];
76
+ }
77
+ return undefined;
78
+ }
79
+ function resolveNonProfileRecommendation({ explicitCandidate, envCandidate, linkedCandidates, linkedDefaultAccountId, defaultCandidate, }) {
80
+ if (explicitCandidate) {
81
+ return explicitCandidate;
82
+ }
83
+ if (envCandidate) {
84
+ return envCandidate;
85
+ }
86
+ if (linkedCandidates.length > 0) {
87
+ if (linkedDefaultAccountId !== undefined) {
88
+ const linkedDefault = linkedCandidates.find(candidate => candidate.accountId === linkedDefaultAccountId);
89
+ if (linkedDefault) {
90
+ return linkedDefault;
91
+ }
92
+ }
93
+ if (linkedCandidates.length === 1) {
94
+ return linkedCandidates[0];
95
+ }
96
+ return undefined;
97
+ }
98
+ return defaultCandidate;
99
+ }
100
+ /**
101
+ * Discovers the candidate target accounts for a command, plus the recommended
102
+ * one to use when a single account is the obvious choice.
103
+ *
104
+ * Profiles take precedence: when the project defines profiles, the candidates
105
+ * are the profile accounts only. Otherwise they are ranked explicit account,
106
+ * then env config, then linked directory, then global default.
107
+ *
108
+ * Takes plain options (not yargs argv) and never prompts or exits, so both the
109
+ * CLI and MCP can call it. `recommended` is omitted when no account is the
110
+ * obvious choice (for example, several profiles). Throws when an explicit
111
+ * account is provided but not found in the config.
112
+ */
113
+ export async function discoverAccountTargets(options = {}) {
114
+ const { explicitAccount, useEnv, profileName, projectDir, projectConfig } = options;
115
+ if (projectDir && projectConfig) {
116
+ const profileCandidates = await gatherProfileCandidates(projectDir, projectConfig);
117
+ if (profileCandidates.length > 0) {
118
+ return {
119
+ candidates: dedupeByAccountId(profileCandidates),
120
+ recommended: resolveProfileRecommendation(profileCandidates, profileName),
121
+ };
122
+ }
123
+ }
124
+ const envConfigActive = Boolean(useEnv) ||
125
+ process.env[ENVIRONMENT_VARIABLES.USE_ENVIRONMENT_HUBSPOT_CONFIG] ===
126
+ 'true';
127
+ const ordered = [];
128
+ let explicitCandidate;
129
+ if (explicitAccount !== undefined && explicitAccount !== '') {
130
+ const account = getConfigAccountIfExists(explicitAccount);
131
+ if (!account) {
132
+ throw new Error(lib.accountTargetDiscovery.errors.explicitAccountNotFound(explicitAccount));
133
+ }
134
+ explicitCandidate = candidateFromAccount(account, ACCOUNT_TARGET_SELECTION_SOURCES.EXPLICIT_ACCOUNT);
135
+ ordered.push(explicitCandidate);
136
+ }
137
+ let envCandidate;
138
+ if (envConfigActive) {
139
+ const envAccount = getConfigDefaultAccountIfExists();
140
+ if (envAccount) {
141
+ envCandidate = candidateFromAccount(envAccount, ACCOUNT_TARGET_SELECTION_SOURCES.ENV_CONFIG);
142
+ ordered.push(envCandidate);
143
+ }
144
+ }
145
+ const settings = getHsSettingsFileIfExists();
146
+ const directoryIsLinked = isDirectoryLinked(settings);
147
+ const linkedCandidates = [];
148
+ if (directoryIsLinked) {
149
+ for (const accountId of settings.accounts) {
150
+ const candidate = candidateFromAccountId(accountId, ACCOUNT_TARGET_SELECTION_SOURCES.LINKED_DIRECTORY);
151
+ linkedCandidates.push(candidate);
152
+ ordered.push(candidate);
153
+ }
154
+ }
155
+ let defaultCandidate;
156
+ if (!envConfigActive) {
157
+ const defaultAccount = getConfigDefaultAccountIfExists();
158
+ if (defaultAccount) {
159
+ defaultCandidate = candidateFromAccount(defaultAccount, ACCOUNT_TARGET_SELECTION_SOURCES.GLOBAL_DEFAULT);
160
+ ordered.push(defaultCandidate);
161
+ }
162
+ }
163
+ return {
164
+ candidates: dedupeByAccountId(ordered),
165
+ recommended: resolveNonProfileRecommendation({
166
+ explicitCandidate,
167
+ envCandidate,
168
+ linkedCandidates,
169
+ linkedDefaultAccountId: directoryIsLinked
170
+ ? settings.localDefaultAccount
171
+ : undefined,
172
+ defaultCandidate,
173
+ }),
174
+ };
175
+ }
@@ -0,0 +1,22 @@
1
+ import type { HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
2
+ import type { IntermediateRepresentationNodeLocalDev } from '@hubspot/project-parsing-lib/translate';
3
+ import type { AppInstallationState, AppIRNode } from '../../types/ProjectComponents.js';
4
+ export type ScopeGroup = {
5
+ id: number;
6
+ name: string;
7
+ };
8
+ type CanUseStaticAuthTestAccountInstallOptions = {
9
+ accountConfig?: HubSpotConfigAccount;
10
+ appNode?: AppIRNode | null;
11
+ parentAccountId?: number;
12
+ };
13
+ export declare function getAppNodeFromProjectNodes(projectNodes: {
14
+ [key: string]: IntermediateRepresentationNodeLocalDev;
15
+ }): AppIRNode | null;
16
+ export declare function isStaticAuthApp(appNode?: AppIRNode | null): boolean;
17
+ export declare function isOAuthApp(appNode?: AppIRNode | null): boolean;
18
+ export declare function isPrivateApp(appNode?: AppIRNode | null): boolean;
19
+ export declare function isMarketplaceApp(appNode?: AppIRNode | null): boolean;
20
+ export declare function getAppInstallationState(isInstalledWithScopeGroups: boolean, previouslyAuthorizedScopeGroups: ScopeGroup[]): AppInstallationState;
21
+ export declare function canUseStaticAuthTestAccountInstall({ accountConfig, appNode, parentAccountId, }: CanUseStaticAuthTestAccountInstallOptions): boolean;
22
+ export {};
@@ -0,0 +1,42 @@
1
+ import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, APP_INSTALLATION_STATES, } from '../constants.js';
2
+ import { isDeveloperTestAccount, isSandbox } from '../accountTypes.js';
3
+ import { isAppIRNode } from '../projects/structure.js';
4
+ export function getAppNodeFromProjectNodes(projectNodes) {
5
+ return Object.values(projectNodes).find(isAppIRNode) || null;
6
+ }
7
+ export function isStaticAuthApp(appNode) {
8
+ return appNode?.config.auth.type.toLowerCase() === APP_AUTH_TYPES.STATIC;
9
+ }
10
+ export function isOAuthApp(appNode) {
11
+ return appNode?.config.auth.type.toLowerCase() === APP_AUTH_TYPES.OAUTH;
12
+ }
13
+ export function isPrivateApp(appNode) {
14
+ return (appNode?.config.distribution?.toLowerCase() ===
15
+ APP_DISTRIBUTION_TYPES.PRIVATE);
16
+ }
17
+ export function isMarketplaceApp(appNode) {
18
+ return (appNode?.config.distribution?.toLowerCase() ===
19
+ APP_DISTRIBUTION_TYPES.MARKETPLACE);
20
+ }
21
+ export function getAppInstallationState(isInstalledWithScopeGroups, previouslyAuthorizedScopeGroups) {
22
+ if (isInstalledWithScopeGroups) {
23
+ return APP_INSTALLATION_STATES.INSTALLED;
24
+ }
25
+ if (previouslyAuthorizedScopeGroups.length > 0) {
26
+ return APP_INSTALLATION_STATES.INSTALLED_WITH_OUTDATED_SCOPES;
27
+ }
28
+ return APP_INSTALLATION_STATES.NOT_INSTALLED;
29
+ }
30
+ export function canUseStaticAuthTestAccountInstall({ accountConfig, appNode, parentAccountId, }) {
31
+ if (!accountConfig || !isStaticAuthApp(appNode)) {
32
+ return false;
33
+ }
34
+ const accountCanUseTestInstall = isDeveloperTestAccount(accountConfig) || isSandbox(accountConfig);
35
+ if (!accountCanUseTestInstall) {
36
+ return false;
37
+ }
38
+ if (parentAccountId === undefined) {
39
+ return true;
40
+ }
41
+ return accountConfig.parentAccountId === parentAccountId;
42
+ }
package/lib/app/logs.js CHANGED
@@ -46,7 +46,7 @@ export function parseSinceTime(sinceInput) {
46
46
  endTime: now.valueOf(),
47
47
  };
48
48
  }
49
- const isoTime = moment(sinceInput);
49
+ const isoTime = moment(sinceInput, moment.ISO_8601, true);
50
50
  if (isoTime.isValid()) {
51
51
  return {
52
52
  startTime: isoTime.valueOf(),
@@ -3,7 +3,7 @@ import { randomUUID } from 'crypto';
3
3
  import open from 'open';
4
4
  import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
5
5
  import CLIWebSocketServer from '../CLIWebSocketServer.js';
6
- import { ACCOUNT_AUTH_UI_MESSAGE_SEND_TYPES, ACCOUNT_AUTH_UI_MESSAGE_RECEIVE_TYPES, ACCOUNT_AUTH_WEBSOCKET_SERVER_VERSION, } from '../constants.js';
6
+ import { ACCOUNT_AUTH_UI_MESSAGE_RECEIVE_TYPES, ACCOUNT_AUTH_UI_MESSAGE_SEND_TYPES, ACCOUNT_AUTH_WEBSOCKET_SERVER_VERSION, } from '../constants.js';
7
7
  import { personalAccessKeyPrompt } from '../prompts/personalAccessKeyPrompt.js';
8
8
  import SpinniesManager from '../ui/SpinniesManager.js';
9
9
  import { uiLogger } from '../ui/logger.js';
@@ -55,7 +55,8 @@ function createMessageHandler(server, cliCallbackToken, resolveWebsocketPak) {
55
55
  }
56
56
  async function awaitKeypressOrWebsocketPak(websocketPakPromise) {
57
57
  if (!process.stdin.isTTY) {
58
- return { type: 'keypress' };
58
+ const pak = await websocketPakPromise;
59
+ return { type: 'pak', pak };
59
60
  }
60
61
  SpinniesManager.init();
61
62
  SpinniesManager.add(SPINNER_ID, {
@@ -17,6 +17,7 @@ export declare class Doctor {
17
17
  private checkIfNodeIsInstalled;
18
18
  private checkIfNpmIsInstalled;
19
19
  private checkCLIVersion;
20
+ private checkMcpReadiness;
20
21
  private checkIfNpmInstallRequired;
21
22
  private isValidJsonFile;
22
23
  private checkProjectConfigJsonFiles;
@@ -23,6 +23,8 @@ import { pkg } from '../jsonLoader.js';
23
23
  import { lib } from '../../lang/en.js';
24
24
  import { uiLink } from '../ui/index.js';
25
25
  import { isServerRunningAtUrl } from '../http.js';
26
+ import { detectConfiguredMcpClients } from '../mcp/promotion.js';
27
+ import { MCP_CLIENTS } from '../mcp/clients.js';
26
28
  import { WEBHOOKS_KEY, APP_KEY } from '@hubspot/project-parsing-lib/constants';
27
29
  import { validateProjectConfig } from '../projects/config.js';
28
30
  import { validateSourceDirectory, handleTranslate, } from '../projects/upload.js';
@@ -73,6 +75,7 @@ export class Doctor {
73
75
  this.checkIfNodeIsInstalled(),
74
76
  this.checkIfNpmIsInstalled(),
75
77
  this.checkCLIVersion(),
78
+ this.checkMcpReadiness(),
76
79
  ];
77
80
  }
78
81
  performNetworkingChecks() {
@@ -252,6 +255,29 @@ export class Doctor {
252
255
  });
253
256
  }
254
257
  }
258
+ async checkMcpReadiness() {
259
+ try {
260
+ const configuredClients = detectConfiguredMcpClients();
261
+ const allClientIds = MCP_CLIENTS.map(c => c.id);
262
+ const unconfiguredClients = allClientIds.filter(id => !configuredClients.includes(id));
263
+ if (configuredClients.length > 0) {
264
+ this.diagnosis?.addCliSection({
265
+ type: 'success',
266
+ message: lib.doctor.mcpChecks.configured(configuredClients),
267
+ });
268
+ }
269
+ if (unconfiguredClients.length > 0) {
270
+ this.diagnosis?.addCliSection({
271
+ type: 'warning',
272
+ message: lib.doctor.mcpChecks.notConfigured(unconfiguredClients),
273
+ secondaryMessaging: lib.doctor.mcpChecks.notConfiguredSecondary,
274
+ });
275
+ }
276
+ }
277
+ catch (e) {
278
+ uiLogger.debug(getErrorMessage(e));
279
+ }
280
+ }
255
281
  async checkIfNpmInstallRequired() {
256
282
  let foundError = false;
257
283
  for (const packageFile of this.diagnosticInfo?.packageFiles || []) {
@@ -73,7 +73,7 @@ async function fetchAppAfterDeploy(accountId, env) {
73
73
  async function buildProjectMetadata(projectDir, projectConfig, accountId) {
74
74
  const srcDir = path.join(projectDir, projectConfig.srcDir);
75
75
  const projectMetadata = await getProjectMetadata(srcDir);
76
- const intermediateRepresentation = await translate({
76
+ const { intermediateRepresentation } = await translate({
77
77
  projectSourceDir: srcDir,
78
78
  platformVersion: projectConfig.platformVersion,
79
79
  accountId,
@@ -111,6 +111,7 @@ export async function uploadAndDeployAction({ accountId, projectDest, }) {
111
111
  isUploadCommand: false,
112
112
  sendIR: !isLegacyProject(projectConfig.platformVersion),
113
113
  skipValidation: false,
114
+ force: true,
114
115
  });
115
116
  if (uploadError) {
116
117
  throw new ProjectUploadError(commands.getStarted.errors.uploadActionFailed, uploadError);
@@ -0,0 +1,13 @@
1
+ export declare const MCP_SERVER_NAME = "HubSpotDev";
2
+ export type McpClientId = 'codex' | 'claude' | 'cursor' | 'gemini' | 'vscode' | 'windsurf';
3
+ type McpClientDetection = {
4
+ type: 'json' | 'text';
5
+ pathSegments: string[];
6
+ };
7
+ export type McpClient = {
8
+ id: McpClientId;
9
+ detection: McpClientDetection;
10
+ };
11
+ export declare const MCP_CLIENTS: McpClient[];
12
+ export declare function getMcpClientPathSegments(id: McpClientId): string[];
13
+ export {};
@@ -0,0 +1,55 @@
1
+ export const MCP_SERVER_NAME = 'HubSpotDev';
2
+ function getVsCodeMcpConfigPathSegments() {
3
+ if (process.platform === 'darwin') {
4
+ return ['Library', 'Application Support', 'Code', 'User', 'mcp.json'];
5
+ }
6
+ if (process.platform === 'win32') {
7
+ return ['AppData', 'Roaming', 'Code', 'User', 'mcp.json'];
8
+ }
9
+ return ['.config', 'Code', 'User', 'mcp.json'];
10
+ }
11
+ export const MCP_CLIENTS = [
12
+ {
13
+ id: 'codex',
14
+ detection: {
15
+ type: 'text',
16
+ pathSegments: ['.codex', 'config.toml'],
17
+ },
18
+ },
19
+ {
20
+ id: 'claude',
21
+ detection: { type: 'json', pathSegments: ['.claude.json'] },
22
+ },
23
+ {
24
+ id: 'cursor',
25
+ detection: { type: 'json', pathSegments: ['.cursor', 'mcp.json'] },
26
+ },
27
+ {
28
+ id: 'gemini',
29
+ detection: {
30
+ type: 'json',
31
+ pathSegments: ['.gemini', 'settings.json'],
32
+ },
33
+ },
34
+ {
35
+ id: 'vscode',
36
+ detection: {
37
+ type: 'json',
38
+ pathSegments: getVsCodeMcpConfigPathSegments(),
39
+ },
40
+ },
41
+ {
42
+ id: 'windsurf',
43
+ detection: {
44
+ type: 'json',
45
+ pathSegments: ['.codeium', 'windsurf', 'mcp_config.json'],
46
+ },
47
+ },
48
+ ];
49
+ export function getMcpClientPathSegments(id) {
50
+ const client = MCP_CLIENTS.find(c => c.id === id);
51
+ if (!client) {
52
+ return [];
53
+ }
54
+ return client.detection.pathSegments;
55
+ }
@@ -0,0 +1,3 @@
1
+ export declare function detectConfiguredMcpClients(): string[];
2
+ export declare function shouldShowMcpPromotion(): Promise<boolean>;
3
+ export declare function showMcpPromotionNudge(commandName: string): Promise<void>;