@getbrevo/cli 2.0.2 → 2.1.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 (217) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +8 -5
  3. package/agent-context/AGENTS.md +59 -15
  4. package/agent-context/SKILL.md +70 -14
  5. package/dist/{templates → bin}/files/AGENTS.md.tmpl +11 -1
  6. package/dist/{templates → bin}/files/CLAUDE.md.tmpl +8 -0
  7. package/dist/{templates → bin}/files/README.md.tmpl +11 -14
  8. package/dist/bin/files/app-config.json.tmpl +16 -0
  9. package/dist/{templates → bin}/files/src/oauth/.env.example.tmpl +8 -0
  10. package/dist/{templates → bin}/files/src/oauth/.env.local.tmpl +2 -0
  11. package/dist/{templates → bin}/files/src/oauth/handler.js.tmpl +49 -7
  12. package/dist/{templates → bin}/files/src/oauth/server.js.tmpl +20 -14
  13. package/dist/{templates → bin}/files/src/oauth/token-store.js.tmpl +19 -0
  14. package/dist/bin/index.js +660 -235
  15. package/dist/bin/index.js.map +7 -1
  16. package/package.json +6 -4
  17. package/dist/api/client.d.ts +0 -25
  18. package/dist/api/client.d.ts.map +0 -1
  19. package/dist/api/client.js +0 -205
  20. package/dist/api/client.js.map +0 -1
  21. package/dist/bin/index.d.ts +0 -3
  22. package/dist/bin/index.d.ts.map +0 -1
  23. package/dist/commands/app/create.d.ts +0 -8
  24. package/dist/commands/app/create.d.ts.map +0 -1
  25. package/dist/commands/app/create.js +0 -325
  26. package/dist/commands/app/create.js.map +0 -1
  27. package/dist/commands/app/credentials.d.ts +0 -6
  28. package/dist/commands/app/credentials.d.ts.map +0 -1
  29. package/dist/commands/app/credentials.js +0 -114
  30. package/dist/commands/app/credentials.js.map +0 -1
  31. package/dist/commands/app/delete.d.ts +0 -6
  32. package/dist/commands/app/delete.d.ts.map +0 -1
  33. package/dist/commands/app/delete.js +0 -167
  34. package/dist/commands/app/delete.js.map +0 -1
  35. package/dist/commands/app/list.d.ts +0 -4
  36. package/dist/commands/app/list.d.ts.map +0 -1
  37. package/dist/commands/app/list.js +0 -71
  38. package/dist/commands/app/list.js.map +0 -1
  39. package/dist/commands/app/scaffold.d.ts +0 -5
  40. package/dist/commands/app/scaffold.d.ts.map +0 -1
  41. package/dist/commands/app/scaffold.js +0 -213
  42. package/dist/commands/app/scaffold.js.map +0 -1
  43. package/dist/commands/app/scopes.d.ts +0 -2
  44. package/dist/commands/app/scopes.d.ts.map +0 -1
  45. package/dist/commands/app/scopes.js +0 -81
  46. package/dist/commands/app/scopes.js.map +0 -1
  47. package/dist/commands/app/start.d.ts +0 -5
  48. package/dist/commands/app/start.d.ts.map +0 -1
  49. package/dist/commands/app/start.js +0 -220
  50. package/dist/commands/app/start.js.map +0 -1
  51. package/dist/commands/app/update.d.ts +0 -2
  52. package/dist/commands/app/update.d.ts.map +0 -1
  53. package/dist/commands/app/update.js +0 -444
  54. package/dist/commands/app/update.js.map +0 -1
  55. package/dist/commands/definitions.d.ts +0 -5
  56. package/dist/commands/definitions.d.ts.map +0 -1
  57. package/dist/commands/definitions.js +0 -262
  58. package/dist/commands/definitions.js.map +0 -1
  59. package/dist/commands/init.d.ts +0 -2
  60. package/dist/commands/init.d.ts.map +0 -1
  61. package/dist/commands/init.js +0 -138
  62. package/dist/commands/init.js.map +0 -1
  63. package/dist/commands/login.d.ts +0 -6
  64. package/dist/commands/login.d.ts.map +0 -1
  65. package/dist/commands/login.js +0 -251
  66. package/dist/commands/login.js.map +0 -1
  67. package/dist/commands/logout.d.ts +0 -5
  68. package/dist/commands/logout.d.ts.map +0 -1
  69. package/dist/commands/logout.js +0 -54
  70. package/dist/commands/logout.js.map +0 -1
  71. package/dist/commands/skill/install.d.ts +0 -4
  72. package/dist/commands/skill/install.d.ts.map +0 -1
  73. package/dist/commands/skill/install.js +0 -29
  74. package/dist/commands/skill/install.js.map +0 -1
  75. package/dist/commands/skill/uninstall.d.ts +0 -4
  76. package/dist/commands/skill/uninstall.d.ts.map +0 -1
  77. package/dist/commands/skill/uninstall.js +0 -23
  78. package/dist/commands/skill/uninstall.js.map +0 -1
  79. package/dist/commands/whoami.d.ts +0 -4
  80. package/dist/commands/whoami.d.ts.map +0 -1
  81. package/dist/commands/whoami.js +0 -85
  82. package/dist/commands/whoami.js.map +0 -1
  83. package/dist/container.d.ts +0 -7
  84. package/dist/container.d.ts.map +0 -1
  85. package/dist/container.js +0 -28
  86. package/dist/container.js.map +0 -1
  87. package/dist/lang/en.d.ts +0 -162
  88. package/dist/lang/en.d.ts.map +0 -1
  89. package/dist/lang/en.js +0 -189
  90. package/dist/lang/en.js.map +0 -1
  91. package/dist/lib/abort.d.ts +0 -3
  92. package/dist/lib/abort.d.ts.map +0 -1
  93. package/dist/lib/abort.js +0 -30
  94. package/dist/lib/abort.js.map +0 -1
  95. package/dist/lib/auth-guard.d.ts +0 -13
  96. package/dist/lib/auth-guard.d.ts.map +0 -1
  97. package/dist/lib/auth-guard.js +0 -60
  98. package/dist/lib/auth-guard.js.map +0 -1
  99. package/dist/lib/browser.d.ts +0 -2
  100. package/dist/lib/browser.d.ts.map +0 -1
  101. package/dist/lib/browser.js +0 -16
  102. package/dist/lib/browser.js.map +0 -1
  103. package/dist/lib/cli-version.d.ts +0 -2
  104. package/dist/lib/cli-version.d.ts.map +0 -1
  105. package/dist/lib/cli-version.js +0 -59
  106. package/dist/lib/cli-version.js.map +0 -1
  107. package/dist/lib/command-handler.d.ts +0 -10
  108. package/dist/lib/command-handler.d.ts.map +0 -1
  109. package/dist/lib/command-handler.js +0 -16
  110. package/dist/lib/command-handler.js.map +0 -1
  111. package/dist/lib/command-registry.d.ts +0 -28
  112. package/dist/lib/command-registry.d.ts.map +0 -1
  113. package/dist/lib/command-registry.js +0 -54
  114. package/dist/lib/command-registry.js.map +0 -1
  115. package/dist/lib/config.d.ts +0 -83
  116. package/dist/lib/config.d.ts.map +0 -1
  117. package/dist/lib/config.js +0 -430
  118. package/dist/lib/config.js.map +0 -1
  119. package/dist/lib/constants.d.ts +0 -48
  120. package/dist/lib/constants.d.ts.map +0 -1
  121. package/dist/lib/constants.js +0 -121
  122. package/dist/lib/constants.js.map +0 -1
  123. package/dist/lib/errors.d.ts +0 -30
  124. package/dist/lib/errors.d.ts.map +0 -1
  125. package/dist/lib/errors.js +0 -66
  126. package/dist/lib/errors.js.map +0 -1
  127. package/dist/lib/exit-codes.d.ts +0 -9
  128. package/dist/lib/exit-codes.d.ts.map +0 -1
  129. package/dist/lib/exit-codes.js +0 -12
  130. package/dist/lib/exit-codes.js.map +0 -1
  131. package/dist/lib/hidden-input.d.ts +0 -2
  132. package/dist/lib/hidden-input.d.ts.map +0 -1
  133. package/dist/lib/hidden-input.js +0 -78
  134. package/dist/lib/hidden-input.js.map +0 -1
  135. package/dist/lib/json-output.d.ts +0 -6
  136. package/dist/lib/json-output.d.ts.map +0 -1
  137. package/dist/lib/json-output.js +0 -11
  138. package/dist/lib/json-output.js.map +0 -1
  139. package/dist/lib/logger.d.ts +0 -11
  140. package/dist/lib/logger.d.ts.map +0 -1
  141. package/dist/lib/logger.js +0 -110
  142. package/dist/lib/logger.js.map +0 -1
  143. package/dist/lib/oauth-freshness.d.ts +0 -30
  144. package/dist/lib/oauth-freshness.d.ts.map +0 -1
  145. package/dist/lib/oauth-freshness.js +0 -61
  146. package/dist/lib/oauth-freshness.js.map +0 -1
  147. package/dist/lib/port.d.ts +0 -25
  148. package/dist/lib/port.d.ts.map +0 -1
  149. package/dist/lib/port.js +0 -103
  150. package/dist/lib/port.js.map +0 -1
  151. package/dist/lib/telemetry.d.ts +0 -9
  152. package/dist/lib/telemetry.d.ts.map +0 -1
  153. package/dist/lib/telemetry.js +0 -57
  154. package/dist/lib/telemetry.js.map +0 -1
  155. package/dist/lib/ui.d.ts +0 -14
  156. package/dist/lib/ui.d.ts.map +0 -1
  157. package/dist/lib/ui.js +0 -75
  158. package/dist/lib/ui.js.map +0 -1
  159. package/dist/lib/update-notifier.d.ts +0 -37
  160. package/dist/lib/update-notifier.d.ts.map +0 -1
  161. package/dist/lib/update-notifier.js +0 -298
  162. package/dist/lib/update-notifier.js.map +0 -1
  163. package/dist/lib/validators.d.ts +0 -55
  164. package/dist/lib/validators.d.ts.map +0 -1
  165. package/dist/lib/validators.js +0 -166
  166. package/dist/lib/validators.js.map +0 -1
  167. package/dist/services/account.d.ts +0 -8
  168. package/dist/services/account.d.ts.map +0 -1
  169. package/dist/services/account.js +0 -15
  170. package/dist/services/account.js.map +0 -1
  171. package/dist/services/app.d.ts +0 -38
  172. package/dist/services/app.d.ts.map +0 -1
  173. package/dist/services/app.js +0 -145
  174. package/dist/services/app.js.map +0 -1
  175. package/dist/services/browser-auth.d.ts +0 -15
  176. package/dist/services/browser-auth.d.ts.map +0 -1
  177. package/dist/services/browser-auth.js +0 -188
  178. package/dist/services/browser-auth.js.map +0 -1
  179. package/dist/services/normalize-app-id.d.ts +0 -12
  180. package/dist/services/normalize-app-id.d.ts.map +0 -1
  181. package/dist/services/normalize-app-id.js +0 -38
  182. package/dist/services/normalize-app-id.js.map +0 -1
  183. package/dist/services/oauth-metadata.d.ts +0 -7
  184. package/dist/services/oauth-metadata.d.ts.map +0 -1
  185. package/dist/services/oauth-metadata.js +0 -43
  186. package/dist/services/oauth-metadata.js.map +0 -1
  187. package/dist/services/oauth-refresh.d.ts +0 -14
  188. package/dist/services/oauth-refresh.d.ts.map +0 -1
  189. package/dist/services/oauth-refresh.js +0 -70
  190. package/dist/services/oauth-refresh.js.map +0 -1
  191. package/dist/services/scopes-html.d.ts +0 -3
  192. package/dist/services/scopes-html.d.ts.map +0 -1
  193. package/dist/services/scopes-html.js +0 -516
  194. package/dist/services/scopes-html.js.map +0 -1
  195. package/dist/services/scopes-web.d.ts +0 -12
  196. package/dist/services/scopes-web.d.ts.map +0 -1
  197. package/dist/services/scopes-web.js +0 -95
  198. package/dist/services/scopes-web.js.map +0 -1
  199. package/dist/services/skill.d.ts +0 -48
  200. package/dist/services/skill.d.ts.map +0 -1
  201. package/dist/services/skill.js +0 -223
  202. package/dist/services/skill.js.map +0 -1
  203. package/dist/skills/index.d.ts +0 -47
  204. package/dist/skills/index.d.ts.map +0 -1
  205. package/dist/skills/index.js +0 -61
  206. package/dist/skills/index.js.map +0 -1
  207. package/dist/templates/files/app-config.json.tmpl +0 -25
  208. package/dist/templates/index.d.ts +0 -36
  209. package/dist/templates/index.d.ts.map +0 -1
  210. package/dist/templates/index.js +0 -105
  211. package/dist/templates/index.js.map +0 -1
  212. package/dist/types.d.ts +0 -32
  213. package/dist/types.d.ts.map +0 -1
  214. package/dist/types.js +0 -3
  215. package/dist/types.js.map +0 -1
  216. /package/dist/{templates → bin}/files/gitignore.tmpl +0 -0
  217. /package/dist/{templates → bin}/files/src/oauth/package.json.tmpl +0 -0
@@ -1 +1,7 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bin/index.ts"],"names":[],"mappings":"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"}
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/bin/index.ts", "../../src/lib/config.ts", "../../src/lib/exit-codes.ts", "../../src/lib/errors.ts", "../../src/lib/constants.ts", "../../src/lib/validators.ts", "../../src/app-types/ui/detect.ts", "../../src/lang/en.ts", "../../src/lib/removed-commands.ts", "../../src/lib/auth-guard.ts", "../../src/lib/oauth-freshness.ts", "../../src/lib/logger.ts", "../../src/lib/hidden-input.ts", "../../src/services/oauth-refresh.ts", "../../src/lib/ui.ts", "../../src/lib/cli-version.ts", "../../src/lib/telemetry.ts", "../../src/api/client.ts", "../../src/services/account.ts", "../../src/services/app.ts", "../../src/services/normalize-app-id.ts", "../../src/container.ts", "../../src/lib/preview.ts", "../../src/lib/command-registry.ts", "../../src/lib/help.ts", "../../src/lib/abort.ts", "../../src/lib/command-handler.ts", "../../src/lib/json-output.ts", "../../src/commands/app/select-app.ts", "../../src/lib/browser.ts", "../../src/app-types/oauth/index.ts", "../../src/app-types/ui/index.ts", "../../src/app-types/index.ts", "../../src/commands/init.ts", "../../src/commands/login.ts", "../../src/commands/app/create.ts", "../../src/lib/port.ts", "../../src/commands/app/project-writer.ts", "../../src/templates/index.ts", "../../src/commands/app/scaffold-prompts.ts", "../../src/commands/app/finish-project.ts", "../../src/app-types/ui/fields.ts", "../../src/services/browser-auth.ts", "../../src/commands/app/scaffold.ts", "../../src/app-types/wire.ts", "../../src/commands/logout.ts", "../../src/commands/whoami.ts", "../../src/commands/app/list.ts", "../../src/commands/app/credentials.ts", "../../src/commands/app/upload.ts", "../../src/commands/app/delete.ts", "../../src/services/oauth-metadata.ts", "../../src/services/scopes-web.ts", "../../src/services/scopes-html.ts", "../../src/commands/app/scopes.ts", "../../src/commands/app/start.ts", "../../src/services/skill.ts", "../../src/skills/index.ts", "../../src/commands/skill/install.ts", "../../src/commands/skill/uninstall.ts", "../../src/commands/definitions.ts", "../../src/lib/update-notifier.ts", "../../src/services/cli-info.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { Command } from 'commander';\nimport { installAuthGuard } from '../lib/auth-guard';\nimport { installProactiveOauthRefresh, ensureFreshOauthToken } from '../lib/oauth-freshness';\nimport { logError, logInfo, logWarn, logSuccess, logDebug } from '../lib/logger';\nimport { EXIT_CODES } from '../lib/exit-codes';\nimport { CliError, AbortError, AuthExpiredError } from '../lib/errors';\nimport { messages } from '../lang/en';\nimport { readHiddenInput } from '../lib/hidden-input';\nimport { saveCredentials, clearCredentials, getAuthCred, updateOauthTokens } from '../lib/config';\nimport { ENDPOINTS, OAUTH_PROXY_URL, warnIfPathStripped } from '../lib/constants';\nimport { refreshAccessToken, RefreshError } from '../services/oauth-refresh';\nimport { stopActiveSpinner } from '../lib/ui';\nimport { AccountResponse } from '../types';\nimport { client } from '../container';\nimport { registerAll } from '../lib/command-registry';\nimport { topLevelCommands, appCommandGroup, skillCommandGroup } from '../commands/definitions';\nimport {\n formatBlockedBanner,\n startUpdateCheck,\n notifyUpdate,\n enforceMinVersion,\n shouldShowBannerBefore,\n} from '../lib/update-notifier';\nimport { skillService } from '../services/skill';\nimport { fetchCliInfo } from '../services/cli-info';\nimport { emitJsonError } from '../lib/json-output';\nimport { createHelpFormatter } from '../lib/help';\n\nconst pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8'));\nconst version: string = pkg.version;\n\n// Version update check \u2014 async, non-blocking. Cached at ~/.brevo/update-check.json (12h TTL).\n// Skipped in CI, non-TTY, or when --no-update-notifier / BREVO_NO_UPDATE_NOTIFIER=1 is set.\n// This covers npm detection only. The banner's notice line, and whether the\n// version is blocked outright, come from GET /cli/info \u2014 see applyCliInfo below.\nconst updateCheck = startUpdateCheck({ pkg, argv: process.argv });\n// For long interactive flows (`app init`, `app create`), surface the banner\n// before the command runs so the user sees it up front instead of after a\n// multi-prompt sequence.\nconst showBannerEarly = shouldShowBannerBefore(process.argv);\n\nif (process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0') {\n logWarn(messages.TLS_VERIFICATION_DISABLED);\n}\n\nconst program = new Command();\n\nprogram\n .name('brevo')\n .description('Brevo Developer CLI \u2014 create, manage, and test OAuth integrations')\n .version(version)\n .option('--debug', 'Enable debug logging')\n .configureHelp({ formatHelp: createHelpFormatter(program) })\n .action((_options, cmd) => {\n const stray = cmd.args;\n if (stray.length === 0) {\n cmd.outputHelp();\n return;\n }\n // Unknown top-level command \u2014 Commander dispatches stray args to the\n // root action when no subcommand matches. Surface a clear error so the\n // user knows they typed a command that doesn't exist.\n process.stderr.write(`error: unknown command '${stray[0]}'\\n`);\n process.stderr.write(`See \\`brevo --help\\` for available commands.\\n`);\n process.exit(EXIT_CODES.ERROR);\n });\n\n// Auth guard \u2014 blocks unauthenticated access (except login, logout, help)\ninstallAuthGuard(program);\n\n// Proactive OAuth refresh \u2014 replaces a near-expiry access token, so a short\n// access-token TTL stays invisible and the session lives as long as the refresh\n// token does.\n//\n// A failure that says nothing about the session (network blip, 5xx, unwritable\n// file) is logged at debug level and never blocks the command. A *refused\n// refresh token* is different in kind: nothing later in the run can recover it,\n// so it clears credentials and stops the command \u2014 see `isTerminal` below.\nconst oauthFreshnessDeps = {\n getAuthCred,\n refresh: (refreshToken: string) => refreshAccessToken(refreshToken, OAUTH_PROXY_URL),\n persist: updateOauthTokens,\n // `unauthorized` is set only by a 401 from the proxy's `/refresh`, i.e. the\n // refresh token itself was rejected. Anything else \u2014 including a timeout\n // against that same endpoint \u2014 stays best-effort.\n isTerminal: (err: unknown) => err instanceof RefreshError && err.unauthorized,\n onTerminal: clearCredentials,\n onError: (err: unknown) =>\n logDebug('proactive oauth refresh skipped', {\n reason: err instanceof Error ? err.message : String(err),\n }),\n};\n\n// Once before the command body \u2014 the check that has to beat the first prompt.\ninstallProactiveOauthRefresh(program, oauthFreshnessDeps);\n// And again before each authenticated request, for a token that expires during\n// a long interactive flow. Same deps object: one policy, two trigger points.\nclient.setEnsureFresh(async () => {\n await ensureFreshOauthToken(oauthFreshnessDeps);\n});\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Register all commands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nregisterAll(program, topLevelCommands, [appCommandGroup, skillCommandGroup]);\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Re-auth handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nclient.setOnAuthFailure(async () => {\n const auth = getAuthCred();\n\n // OAuth: silently refresh the access token and let the original request retry.\n // If the refresh itself returns 401, the refresh token is dead \u2014 clear creds\n // and surface a friendly \"please log in again\" message instead of falling\n // through to the api-key prompt.\n if (auth?.kind === 'oauth') {\n try {\n const refreshed = await refreshAccessToken(auth.refreshToken, OAUTH_PROXY_URL);\n updateOauthTokens(refreshed);\n return;\n } catch (err) {\n if (err instanceof RefreshError && err.unauthorized) {\n clearCredentials();\n throw new AuthExpiredError();\n }\n throw err;\n }\n }\n\n // API-key: prompt for a new key and re-validate.\n stopActiveSpinner();\n clearCredentials();\n logWarn(messages.AUTH_EXPIRED);\n logInfo(` ${messages.AUTH_GET_KEY_URL}\\n`);\n const newKey = await readHiddenInput(messages.AUTH_EXPIRED_PROMPT + ' ');\n const account = await client.getWithKey<AccountResponse>(ENDPOINTS.ACCOUNT, newKey);\n saveCredentials(newKey, {\n email: account.email,\n organizationId: account.organization_id,\n userId: account.user_id,\n });\n logSuccess(messages.AUTH_SUCCESS(account.email));\n});\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Signal handling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfor (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.on(signal, () => {\n logInfo(`\\n Received ${signal}, shutting down.\\n`);\n process.exit(EXIT_CODES.ABORTED);\n });\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Error handling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Emit deferred warning if BREVO_API_URL had a path stripped\nwarnIfPathStripped();\n\n// Force-update gate \u2014 if the installed CLI is a full major version behind the\n// latest npm release, block the command (non-zero exit) so the user upgrades.\n// Skipped for --help/--version (so help stays reachable) and whenever the\n// update check itself is skipped (CI / non-TTY / opt-out). Fails open.\nconst args = new Set(process.argv.slice(2));\nconst isHelpOrVersion =\n args.has('--help') || args.has('-h') || args.has('--version') || args.has('-V');\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 API version gate (BEX-370) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// GET /cli/info on the app-store service, called directly \u2014 no gateway, no\n// credentials. Cached at ~/.brevo/cli-info-cache.json for 15m (see\n// services/cli-info.ts), keyed to the running cliVersion and the service it\n// came from, so a reworded message or a new block reaches users within minutes\n// rather than after the old 12h npm-style TTL, without hitting the service on\n// every single command.\n//\n// It runs *before* the command because a block has to prevent the command, not\n// report on it afterwards.\n//\n// Fails open in every direction: a timeout, a non-2xx, an unparseable body or\n// any other error leaves `info` undefined and the command proceeds. Only an\n// explicit `is_blocked: true` stops anything.\n//\n// A block is not a notice, so it deliberately ignores the notice opt-outs\n// (BREVO_NO_UPDATE_NOTIFIER, --no-update-notifier, CI, non-TTY): a suppressed\n// banner should never mean a suppressed block.\nasync function applyCliInfo(): Promise<void> {\n const info = await fetchCliInfo({ cliVersion: version, reason: 'startup' });\n if (!info) return;\n\n // Carried in memory only, for whichever banner ends up rendering.\n updateCheck.notice = info.upgradeMessage;\n\n // `--help` / `--version` are exempt from the *block* so a blocked CLI can\n // still say what it is \u2014 but not from the *wording*. Those are two different\n // concerns, and skipping the whole call to achieve the first used to discard\n // the second, which meant the two commands people most often run to check\n // their version were the only ones that could never explain why it mattered.\n if (isHelpOrVersion) return;\n if (!info.isBlocked) return;\n\n process.stderr.write(\n formatBlockedBanner(version, updateCheck.cachedLatest, pkg.name, info.upgradeMessage) + '\\n',\n );\n process.exit(EXIT_CODES.ERROR);\n}\n\n// Kept lazy rather than started at module load: it now runs behind applyCliInfo,\n// and an eagerly-created promise would sit with no rejection handler attached\n// until that resolved. Costs nothing to defer \u2014 the npm request it awaits was\n// already kicked off by startUpdateCheck above.\nasync function forceGate(): Promise<void> {\n if (isHelpOrVersion) return;\n const mustUpdate = await enforceMinVersion(updateCheck, { name: pkg.name, version });\n if (mustUpdate) process.exit(EXIT_CODES.ERROR);\n}\n\napplyCliInfo()\n .then(forceGate)\n .then(() =>\n showBannerEarly ? notifyUpdate(updateCheck, { name: pkg.name, version }) : undefined,\n )\n .then(() => program.parseAsync(process.argv))\n .then(async () => {\n // notifyUpdate is idempotent, so the early-banner path above suppresses this\n // one on its own \u2014 no need to branch on showBannerEarly.\n await notifyUpdate(updateCheck, { name: pkg.name, version });\n // Local skill catalog check \u2014 sync, no network. Silently refreshes any\n // installed skill that's behind the bundled catalog so the AI tool always\n // sees the latest primer. Opt out with BREVO_NO_SKILL_AUTOREFRESH=1.\n skillService.autoRefreshOutdated();\n // Force exit \u2014 Node's native fetch keeps TCP connections alive which can\n // prevent the process from exiting when running against local servers.\n process.exit(0);\n })\n .catch(async (err) => {\n // A deliberate Ctrl-C is not a failure \u2014 exit immediately rather than making\n // the user wait on the update check for a banner they didn't ask for.\n emitJsonError(err);\n if (err instanceof AbortError) {\n logInfo(`\\n ${messages.ABORTED}`);\n process.exit(EXIT_CODES.ABORTED);\n }\n\n const exitCode = err instanceof CliError ? err.exitCode : EXIT_CODES.ERROR;\n if (err instanceof CliError) {\n logError(err.message);\n } else {\n logError(err.message, err);\n }\n\n // A failed command is exactly when knowing about a newer CLI matters most \u2014\n // the upgrade may be the fix. The banner goes after the error so the error\n // stays the first thing the user reads, and the exit code is unchanged.\n await notifyUpdate(updateCheck, { name: pkg.name, version });\n process.exit(exitCode);\n });\n", "import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { splitScopes } from './validators';\nimport { OAuthApp, UiApp } from '../types';\nimport { isUiAppConfigShape, isUiAppRecordShape } from '../app-types/ui/detect';\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Directory \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction getConfigDir(): string {\n return process.env.BREVO_CONFIG_HOME || path.join(os.homedir(), '.brevo');\n}\n\nexport function getCredentialsPath(): string {\n return path.join(getConfigDir(), 'credentials.json');\n}\n\nfunction ensureDir(): void {\n fs.mkdirSync(getConfigDir(), { recursive: true, mode: 0o700 });\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Credentials (~/.brevo/credentials.json) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Stores sensitive secrets \u2014 API key and app client credentials.\n// File permissions: 0o600 (owner read/write only).\n\nexport interface AppCredential {\n clientId: string;\n clientSecret: string;\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Auth (discriminated union) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type AuthCred =\n | { kind: 'api-key'; apiKey: string }\n | {\n kind: 'oauth';\n accessToken: string;\n refreshToken: string;\n expiresAt: number;\n tokenType: string;\n scope?: string;\n };\n\ninterface CachedAppName {\n name: string;\n savedAt: number;\n}\n\n// Cached names patch the eventually-consistent `app list` response. Beyond this\n// window we trust the server, so any out-of-band rename (e.g. dashboard) becomes\n// visible without forcing the user to run `app credentials`.\nconst APP_NAME_CACHE_TTL_MS = 10 * 60 * 1000;\n\ninterface BrevoCredentials {\n auth?: AuthCred;\n accountEmail?: string;\n organizationId?: string;\n userId?: number;\n apps: Record<string, AppCredential>;\n appNames?: Record<string, CachedAppName>;\n}\n\nfunction sanitizeAppNames(value: unknown): Record<string, CachedAppName> | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const out: Record<string, CachedAppName> = {};\n for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {\n if (typeof raw === 'string' && raw.trim()) {\n // Legacy plain-string entry from earlier alphas \u2014 treat as expired so the\n // server response wins on the next list call.\n out[key] = { name: raw, savedAt: 0 };\n } else if (raw && typeof raw === 'object') {\n const entry = raw as Record<string, unknown>;\n if (\n typeof entry.name === 'string' &&\n entry.name.trim() &&\n typeof entry.savedAt === 'number' &&\n Number.isFinite(entry.savedAt)\n ) {\n out[key] = { name: entry.name, savedAt: entry.savedAt };\n }\n }\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nfunction sanitizeApps(apps: Record<string, unknown>): Record<string, AppCredential> {\n const sanitized: Record<string, AppCredential> = {};\n for (const [key, value] of Object.entries(apps)) {\n if (value && typeof value === 'object') {\n const entry = value as Record<string, unknown>;\n if (typeof entry.clientId === 'string' && typeof entry.clientSecret === 'string') {\n sanitized[key] = { clientId: entry.clientId, clientSecret: entry.clientSecret };\n }\n }\n }\n return sanitized;\n}\n\nfunction readCredentials(): BrevoCredentials {\n try {\n const parsed = JSON.parse(fs.readFileSync(getCredentialsPath(), 'utf-8'));\n\n // Migrate old multi-profile format\n if (parsed.profiles) {\n const profileName =\n (typeof parsed.activeProfile === 'string' && parsed.activeProfile) || 'default';\n const firstKey = Object.keys(parsed.profiles)[0];\n const profile =\n parsed.profiles[profileName] ?? (firstKey ? parsed.profiles[firstKey] : undefined);\n const migrated: BrevoCredentials = {\n auth:\n typeof profile?.apiKey === 'string' && profile.apiKey\n ? { kind: 'api-key', apiKey: profile.apiKey }\n : undefined,\n accountEmail: profile?.accountEmail,\n organizationId: profile?.organizationId,\n userId: profile?.userId,\n apps: sanitizeApps(parsed.apps ?? {}),\n };\n try {\n writeCredentials(migrated);\n } catch {\n // non-fatal\n }\n return migrated;\n }\n\n // Migrate legacy flat { apiKey } shape \u2192 { auth: { kind: 'api-key' } }\n if (!parsed.auth && typeof parsed.apiKey === 'string' && parsed.apiKey) {\n const migrated: BrevoCredentials = {\n auth: { kind: 'api-key', apiKey: parsed.apiKey },\n accountEmail: parsed.accountEmail,\n organizationId: parsed.organizationId,\n userId: parsed.userId,\n apps: sanitizeApps(parsed.apps ?? {}),\n };\n try {\n writeCredentials(migrated);\n } catch {\n // non-fatal\n }\n return migrated;\n }\n\n return {\n auth: sanitizeAuth(parsed.auth),\n accountEmail: parsed.accountEmail,\n organizationId: parsed.organizationId,\n userId: parsed.userId,\n apps: sanitizeApps(parsed.apps ?? {}),\n appNames: sanitizeAppNames(parsed.appNames),\n };\n } catch {\n return { apps: {} };\n }\n}\n\nfunction sanitizeAuth(raw: unknown): AuthCred | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const v = raw as Record<string, unknown>;\n if (v.kind === 'api-key' && typeof v.apiKey === 'string' && v.apiKey) {\n return { kind: 'api-key', apiKey: v.apiKey };\n }\n if (\n v.kind === 'oauth' &&\n typeof v.accessToken === 'string' &&\n v.accessToken &&\n typeof v.refreshToken === 'string' &&\n v.refreshToken &&\n typeof v.tokenType === 'string' &&\n v.tokenType &&\n typeof v.expiresAt === 'number' &&\n Number.isFinite(v.expiresAt)\n ) {\n return {\n kind: 'oauth',\n accessToken: v.accessToken,\n refreshToken: v.refreshToken,\n expiresAt: v.expiresAt,\n tokenType: v.tokenType,\n scope: typeof v.scope === 'string' ? v.scope : undefined,\n };\n }\n return undefined;\n}\n\nfunction writeCredentials(creds: BrevoCredentials): void {\n ensureDir();\n const filePath = getCredentialsPath();\n fs.writeFileSync(filePath, JSON.stringify(creds, null, 2), { mode: 0o600 });\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // chmod may not work on all platforms (e.g. Windows) \u2014 ignore\n }\n}\n\nexport function getApiKey(): string | undefined {\n if (process.env.BREVO_API_KEY) {\n return process.env.BREVO_API_KEY;\n }\n const auth = readCredentials().auth;\n return auth?.kind === 'api-key' ? auth.apiKey : undefined;\n}\n\nexport function getAccessToken(): string | undefined {\n const auth = readCredentials().auth;\n return auth?.kind === 'oauth' ? auth.accessToken : undefined;\n}\n\nexport function getAuthCred(): AuthCred | undefined {\n if (process.env.BREVO_API_KEY) {\n return { kind: 'api-key', apiKey: process.env.BREVO_API_KEY };\n }\n return readCredentials().auth;\n}\n\nexport function getEmail(): string | undefined {\n return readCredentials().accountEmail;\n}\n\nexport function getOrganizationId(): string | undefined {\n return readCredentials().organizationId;\n}\n\nexport function getUserId(): number | undefined {\n return readCredentials().userId;\n}\n\nexport function saveCredentials(\n apiKey: string,\n account: { email: string; organizationId: string; userId: number },\n): void {\n const creds = readCredentials();\n creds.auth = { kind: 'api-key', apiKey };\n creds.accountEmail = account.email;\n creds.organizationId = account.organizationId;\n creds.userId = account.userId;\n writeCredentials(creds);\n}\n\nexport interface OauthTokensToStore {\n accessToken: string;\n refreshToken: string;\n expiresIn: number;\n tokenType: string;\n scope?: string;\n}\n\nexport function saveOauthCredentials(\n tokens: OauthTokensToStore,\n account?: { email: string; organizationId: string; userId: number },\n): void {\n const creds = readCredentials();\n creds.auth = {\n kind: 'oauth',\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAt: Date.now() + tokens.expiresIn * 1000,\n tokenType: tokens.tokenType,\n scope: tokens.scope,\n };\n // Account omitted = fresh token write before validation. Clear any stale\n // account info from a previous login so whoami doesn't surface mismatched data.\n if (account) {\n creds.accountEmail = account.email;\n creds.organizationId = account.organizationId;\n creds.userId = account.userId;\n } else {\n delete creds.accountEmail;\n delete creds.organizationId;\n delete creds.userId;\n }\n writeCredentials(creds);\n}\n\nexport function updateOauthTokens(tokens: OauthTokensToStore): void {\n const creds = readCredentials();\n creds.auth = {\n kind: 'oauth',\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAt: Date.now() + tokens.expiresIn * 1000,\n tokenType: tokens.tokenType,\n scope: tokens.scope,\n };\n writeCredentials(creds);\n}\n\nexport function clearCredentials(): void {\n const creds = readCredentials();\n delete creds.auth;\n delete creds.accountEmail;\n delete creds.organizationId;\n delete creds.userId;\n writeCredentials(creds);\n}\n\nexport function deleteCredentialsFile(): void {\n try {\n fs.unlinkSync(getCredentialsPath());\n } catch (error: unknown) {\n if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT') {\n return;\n }\n throw error;\n }\n}\n\nexport function hasAppCredentials(): boolean {\n return Object.keys(readCredentials().apps).length > 0;\n}\n\nexport function countAppCredentials(): number {\n return Object.keys(readCredentials().apps).length;\n}\n\nexport function isAuthenticated(): boolean {\n return !!getAuthCred();\n}\n\nexport function saveAppCredentials(appId: string, cred: AppCredential): void {\n const creds = readCredentials();\n creds.apps[appId] = cred;\n writeCredentials(creds);\n}\n\n// Wipe the per-app credential and name caches without touching auth/account\n// fields. Used when re-login detects a different account \u2014 the cached\n// clientId/clientSecret values belong to apps the new account cannot see.\nexport function clearAppsCache(): void {\n const creds = readCredentials();\n creds.apps = {};\n delete creds.appNames;\n writeCredentials(creds);\n}\n\nexport function getAppCredentials(appId: string): AppCredential | undefined {\n return readCredentials().apps[appId];\n}\n\nexport function deleteAppCredentials(appId: string): void {\n if (!appId) return;\n const creds = readCredentials();\n if (!(appId in creds.apps)) return;\n delete creds.apps[appId];\n writeCredentials(creds);\n}\n\n// Locally cached app names mirror values from `app upload` and `app credentials`.\n// Server-side, the PUT endpoint and the GET-list endpoint are eventually consistent,\n// so `app list` can return a stale name immediately after an update. Merging this\n// cache on top of the list response masks the lag. Entries expire after\n// APP_NAME_CACHE_TTL_MS so they cannot indefinitely hide an out-of-band rename.\nexport function saveAppName(appId: string, name: string): void {\n if (!appId || !name) return;\n const creds = readCredentials();\n creds.appNames = {\n ...creds.appNames,\n [appId]: { name, savedAt: Date.now() },\n };\n writeCredentials(creds);\n}\n\nexport function getAppNames(): Record<string, string> {\n const creds = readCredentials();\n const cache = creds.appNames ?? {};\n const now = Date.now();\n const fresh: Record<string, CachedAppName> = {};\n const result: Record<string, string> = {};\n let pruned = false;\n for (const [id, entry] of Object.entries(cache)) {\n if (now - entry.savedAt < APP_NAME_CACHE_TTL_MS) {\n fresh[id] = entry;\n result[id] = entry.name;\n } else {\n pruned = true;\n }\n }\n if (pruned) {\n creds.appNames = Object.keys(fresh).length > 0 ? fresh : undefined;\n try {\n writeCredentials(creds);\n } catch {\n // non-fatal \u2014 we've already returned the fresh subset to the caller\n }\n }\n return result;\n}\n\nexport function deleteAppName(appId: string): void {\n if (!appId) return;\n const creds = readCredentials();\n if (!creds.appNames || !(appId in creds.appNames)) return;\n const { [appId]: _removed, ...rest } = creds.appNames;\n creds.appNames = Object.keys(rest).length > 0 ? rest : undefined;\n writeCredentials(creds);\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Local project config (app-config.json) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Written inside the scaffolded project folder by the scaffold template.\n// Read by the CLI when run from within a project directory.\n\nexport interface ProjectConfig {\n appId: string;\n appName: string;\n version?: string;\n logoUri?: string;\n createdAt?: string;\n updatedAt?: string;\n /** Distribution type of the app: 'private' or 'public' */\n distribution_type: 'private' | 'public';\n /**\n * OAuth apps carry `{ scopes, redirectUris }`. UI apps carry exactly the\n * empty object `{}` \u2014 no scopes, no redirect URIs, no jwtSecret (nothing is\n * issued for them today). Enforced in `app upload` (see validateAuthShape),\n * not here, so unrelated commands that merely read config keep working on a\n * half-edited file.\n *\n * Caution: an `auth.type` key existed twice historically \u2014 briefly as an\n * *interim distribution* carrier ('private' | 'public'), then as the UI-app\n * marker `'none'`. The read path folds distribution values into\n * `distribution_type` and drops every `type`, so dev-era configs migrate to\n * the empty shape on their next write.\n */\n auth: {\n scopes?: string[];\n // Absent for UI apps: an action link has no OAuth callback to register.\n // OAuth apps still require at least one (enforced in `app upload`).\n redirectUris?: string[];\n };\n /**\n * Present only for UI apps (BEX-290). Its presence is the discriminator\n * between the two app types \u2014 there is no separate `appType` key, matching\n * the UIApp Support Spec's config examples. Use {@link isUiAppConfig}\n * rather than testing for the key directly.\n */\n ui_app?: UiApp;\n}\n\nconst PROJECT_CONFIG_FILE = 'app-config.json';\n\nexport function readProjectConfig(): ProjectConfig | null {\n return readProjectConfigAt(process.cwd());\n}\n\n/**\n * Normalize appId at the boundary: accept strings (trimmed) and finite numeric IDs from\n * legacy configs, reject anything else. Downstream callers can treat `config.appId` as a\n * guaranteed non-empty string.\n *\n * Returning `undefined` is what makes \"has a file called app-config.json\" and \"is a\n * project\" different questions \u2014 see {@link readProjectConfigAt}.\n */\nfunction readNormalizedAppId(raw: Record<string, unknown>): string | undefined {\n const rawAppId = raw.appId;\n if (typeof rawAppId === 'string') {\n return rawAppId.trim() || undefined;\n }\n if (typeof rawAppId === 'number' && Number.isFinite(rawAppId)) {\n return String(rawAppId);\n }\n return undefined;\n}\n\n/**\n * Normalize the `auth` block, or `undefined` to leave the file's own block untouched.\n *\n * Three migrations, applied in order to one copy of the block.\n */\nfunction buildAuthOverride(rawAuth: unknown): Record<string, unknown> | undefined {\n if (!rawAuth || typeof rawAuth !== 'object') return undefined;\n const auth = rawAuth as Record<string, unknown>;\n let override: Record<string, unknown> | undefined;\n\n // Normalize auth.scopes silently \u2014 split on commas/whitespace so an entry like\n // \"crm:read, campaigns:read\" written by a user editing the JSON by hand becomes two\n // scopes. Strict charset validation is enforced later, in the upload command, so\n // unrelated commands that just happen to read config aren't broken by a malformed scope.\n //\n // A bare string is accepted as well as an array: the field is typed string[], but a\n // hand-edited file can carry `\"crm:read, crm:write\"`, and `splitScopes` handles both.\n // Gating on Array.isArray alone let the string through unnormalized, and the upload\n // validator then iterated it one character at a time and rejected `\":\"` as a scope.\n const scopes = auth.scopes;\n if (Array.isArray(scopes) || typeof scopes === 'string') {\n override = { ...auth, scopes: splitScopes(scopes as string | string[]) };\n }\n\n // Redirect URLs were renamed auth.redirectUrls \u2192 auth.redirectUris to track the wire key\n // (redirect_uris, BEX-355/366). Read the legacy key when the new one is absent and drop\n // it from the returned config, so callers that write the object back to disk (upload.ts,\n // start.ts) migrate old projects on their next write.\n //\n // Downgrade caveat, and it is NOT a loud one: releases up to 2.0.2 read only the legacy\n // key, so a migrated file reads there as an app with no redirect URLs at all. `app start\n // oauth` then offers to register `http://localhost:<port>/auth/callback` (confirm prompt,\n // default yes) and PATCHes `redirect_uris` with just that one URL \u2014 the old write path\n // replaces the list rather than merging it, so the app's real redirect URLs are dropped\n // server-side with no error. Only reachable when a new and an old CLI share one project\n // directory (a teammate or CI left behind); a downgrade caveat, not a same-machine one.\n if ('redirectUrls' in auth) {\n override = override ?? { ...auth };\n const legacyRedirects = auth.redirectUrls;\n if (!Array.isArray(override.redirectUris) && Array.isArray(legacyRedirects)) {\n override.redirectUris = legacyRedirects;\n }\n delete override.redirectUrls;\n }\n\n // Legacy auth.type is always dropped: the interim distribution carrier\n // ('private'/'public') is folded into distribution_type by `readDistributionType`, and\n // the dev-era UI-app marker 'none' is obsolete \u2014 a UI app's auth is now the empty object\n // `{}` (the `ui_app` block alone discriminates the app type). Callers that write the\n // config back to disk migrate old files on their next write.\n if (override && 'type' in override) {\n delete override.type;\n } else if ('type' in auth) {\n override = { ...auth };\n delete override.type;\n }\n\n return override;\n}\n\n/**\n * distribution_type has moved twice: originally a top-level `distribution` key (still the\n * shape of every currently-published scaffold), briefly `auth.type` (an interim design that\n * never shipped), now a top-level `distribution_type` key. Backfill from whichever legacy\n * shape is present, preferring the new key when it already exists.\n */\nfunction readDistributionType(\n rawRecord: Record<string, unknown>,\n rawAuth: unknown,\n): 'private' | 'public' {\n const newDistributionType = rawRecord.distribution_type;\n if (typeof newDistributionType === 'string' && newDistributionType.trim()) {\n return newDistributionType.trim() as 'private' | 'public';\n }\n\n const legacyAuthType =\n rawAuth && typeof rawAuth === 'object' ? (rawAuth as Record<string, unknown>).type : undefined;\n if (\n typeof legacyAuthType === 'string' &&\n legacyAuthType.trim() &&\n legacyAuthType !== 'none' // 'none' is the UI-app auth marker, not a distribution\n ) {\n return legacyAuthType.trim() as 'private' | 'public';\n }\n\n const legacyDistribution = rawRecord.distribution;\n if (typeof legacyDistribution === 'string' && legacyDistribution.trim()) {\n return legacyDistribution.trim() as 'private' | 'public';\n }\n\n return 'private';\n}\n\n/**\n * `readProjectConfig` for an arbitrary directory.\n *\n * Exists for {@link findEnclosingProjectDir}, which has to ask the same \"is this a\n * project?\" question of a directory that is not cwd. Kept as the one implementation\n * rather than a second parser so an ancestor is judged a project by exactly the rules\n * every command already applies to cwd \u2014 most importantly the appId normalization\n * in {@link readNormalizedAppId}, which is what makes \"has a file called\n * app-config.json\" and \"is a project\" different questions.\n */\nexport function readProjectConfigAt(dir: string): ProjectConfig | null {\n try {\n const raw = JSON.parse(fs.readFileSync(path.resolve(dir, PROJECT_CONFIG_FILE), 'utf-8'));\n if (!raw || typeof raw !== 'object') return null;\n const rawRecord = raw as Record<string, unknown>;\n\n const appId = readNormalizedAppId(rawRecord);\n if (!appId) return null;\n\n // `auth` normalization and the distribution backfill are independent: the backfill\n // reads the RAW auth block, never the normalized one, so neither can see the other's\n // work and the order between them does not matter.\n const rawAuth = rawRecord.auth;\n const authOverride = buildAuthOverride(rawAuth);\n const distributionType = readDistributionType(rawRecord, rawAuth);\n\n // Drop the legacy top-level `distribution` key from the returned config \u2014\n // it's already folded into distribution_type above. `permittedUrls` and\n // `support` were scaffolded into every config but never read by anything;\n // they're dropped the same way. Callers that write this object back to\n // disk (upload.ts, start.ts) then naturally migrate old projects to the\n // new shape on their next write, instead of round-tripping stray keys\n // forever.\n const {\n distribution: _legacyDistribution,\n permittedUrls: _permittedUrls,\n support: _support,\n ...rawWithoutLegacyDistribution\n } = rawRecord;\n // `ui_app` (BEX-290) is passed through structurally intact \u2014 the spread\n // above already carries it \u2014 but a non-object value is dropped so callers\n // can trust `config.ui_app` is an object whenever it is present. Field-level\n // validation is deliberately *not* done here: unrelated commands that merely\n // read the config must not fail because the block is half-written. `app\n // upload` is the enforcement point (see validateUiApp).\n const rawUiApp = rawWithoutLegacyDistribution.ui_app;\n if ('ui_app' in rawWithoutLegacyDistribution && (!rawUiApp || typeof rawUiApp !== 'object')) {\n delete rawWithoutLegacyDistribution.ui_app;\n }\n return {\n ...rawWithoutLegacyDistribution,\n appId,\n distribution_type: distributionType,\n ...(authOverride ? { auth: authOverride } : {}),\n } as ProjectConfig;\n } catch {\n return null;\n }\n}\n\nexport function hasLocalApp(): boolean {\n const cfg = readProjectConfig();\n return cfg?.appId != null && cfg.appId !== '';\n}\n\n/**\n * The nearest ANCESTOR directory of cwd that is itself a project, or null.\n *\n * Guards `brevo app scaffold`'s no-config branch. `readProjectConfig` reads cwd and\n * deliberately does not walk up \u2014 every other command wants exactly that, because it\n * keeps \"which app am I acting on\" a property of the directory you are standing in.\n * But it means a directory one level inside a project looks identical to an empty\n * directory outside one, and the two must not get the same answer: offering to\n * materialize a project into `myapp/src/` would leave a second `app-config.json`\n * nested in the first, after which `app upload` from that directory pushes the wrong\n * app with no warning.\n *\n * cwd itself is excluded. The only caller has already established that cwd holds no\n * usable config, and counting cwd would make every ordinary in-project run report\n * itself as nested.\n *\n * Stops at the filesystem root. Unreadable or appId-less ancestors are skipped rather\n * than treated as a hit, so the walk agrees with `readProjectConfig` on what counts\n * as a project \u2014 a stray malformed file cannot wedge the command.\n */\nexport function findEnclosingProjectDir(): string | null {\n let dir = path.dirname(process.cwd());\n // `path.dirname('/') === '/'`, which is how the walk terminates. Compare against the\n // previous value rather than testing for a literal separator so this holds on Windows\n // drive roots too.\n for (;;) {\n if (readProjectConfigAt(dir)) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Whether a project config describes a UI app rather than an OAuth app.\n *\n * Thin re-export: the predicate itself lives in `src/app-types/ui/detect.ts`, beside the app\n * type it describes, so the registry and every command agree by construction. Kept exported\n * here because a good number of call sites (and their test mocks) already import it from this\n * module. See that file for why the logic must not live behind this one.\n */\nexport function isUiAppConfig(config: Pick<ProjectConfig, 'ui_app'> | null | undefined): boolean {\n return isUiAppConfigShape(config);\n}\n\n/**\n * Whether a *server* app record describes a UI app. The record counterpart to\n * {@link isUiAppConfig}, and the same thin re-export \u2014 see `app-types/ui/detect.ts` for the\n * fallback it applies and why it requires both an empty client_id and no callbacks.\n */\nexport function isUiAppRecord(\n app: Pick<OAuthApp, 'ui_app' | 'client_id' | 'redirect_uris'> | null | undefined,\n): boolean {\n return isUiAppRecordShape(app);\n}\n\nexport function writeProjectConfig(config: ProjectConfig): void {\n const configPath = path.resolve(process.cwd(), PROJECT_CONFIG_FILE);\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n\nfunction isNonEmptyString(value: unknown): boolean {\n return typeof value === 'string' && value.trim() !== '';\n}\n\n/**\n * Converge a legacy `app-config.json` in the current directory toward the\n * current config shape by backfilling fields that were absent when it was\n * scaffolded \u2014 mirroring the migration `brevo app upload` already performs, but\n * from a read-only command (`brevo app credentials`) so projects that are never\n * uploaded still catch up.\n *\n * Backfill is strictly fill-when-missing: a field the file already carries (in\n * any historical shape) is never overwritten with the server's value, even when\n * they differ. Guarded by an appId match so it only ever touches the config for\n * the app the caller actually resolved.\n *\n * @returns the names of the fields written (`'version'`, `'distribution_type'`),\n * or `[]` when there is no matching local config or nothing was missing\n * (in which case the file is left byte-for-byte untouched).\n */\nexport function backfillProjectConfigFromServer(\n appId: string,\n server: { version?: string; distribution_type?: 'public' | 'private' },\n): string[] {\n const configPath = path.resolve(process.cwd(), PROJECT_CONFIG_FILE);\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));\n } catch {\n return [];\n }\n if (!raw || typeof raw !== 'object') return [];\n\n const normalized = readProjectConfig();\n if (normalized?.appId !== appId) return [];\n\n const rawRecord = raw as Record<string, unknown>;\n const backfilled: string[] = [];\n const next: ProjectConfig = { ...normalized };\n\n // `version` is optional: only backfill when the file lacks a usable value and\n // the server actually returned one.\n if (!isNonEmptyString(rawRecord.version) && isNonEmptyString(server.version)) {\n next.version = server.version;\n backfilled.push('version');\n }\n\n // `distribution_type` is required in the current shape. It is \"missing\" only\n // when none of its historical carriers is present on disk (new top-level key,\n // the oldest top-level `distribution`, or the interim `auth.type`). When\n // missing, prefer the server's value, falling back to the normalized default.\n const rawAuth = rawRecord.auth;\n const legacyAuthType =\n rawAuth && typeof rawAuth === 'object' ? (rawAuth as Record<string, unknown>).type : undefined;\n const hasDistribution =\n isNonEmptyString(rawRecord.distribution_type) ||\n isNonEmptyString(rawRecord.distribution) ||\n // auth.type carried the distribution only in its interim shape \u2014 'none'\n // was the dev-era UI-app auth marker and says nothing about distribution.\n (isNonEmptyString(legacyAuthType) && legacyAuthType !== 'none');\n if (!hasDistribution) {\n next.distribution_type = server.distribution_type ?? normalized.distribution_type;\n backfilled.push('distribution_type');\n }\n\n if (backfilled.length === 0) return [];\n writeProjectConfig(next);\n return backfilled;\n}\n", "export const EXIT_CODES = {\n SUCCESS: 0,\n ERROR: 1,\n ABORTED: 2,\n AUTH_FAILURE: 3,\n NETWORK_ERROR: 4,\n NOT_FOUND: 5,\n} as const;\n", "import { EXIT_CODES } from './exit-codes';\n\nexport class CliError extends Error {\n constructor(\n message: string,\n public readonly exitCode: number = EXIT_CODES.ERROR,\n ) {\n super(message);\n this.name = 'CliError';\n }\n}\n\nexport class AbortError extends CliError {\n constructor() {\n super('Aborted.', EXIT_CODES.ABORTED);\n this.name = 'AbortError';\n }\n}\n\nexport class AuthExpiredError extends CliError {\n constructor() {\n super('Your session has expired. Run `brevo login` to sign in again.', EXIT_CODES.AUTH_FAILURE);\n this.name = 'AuthExpiredError';\n }\n}\n\nexport enum ErrorCode {\n AUTH_INVALID = 'AUTH_INVALID',\n AUTH_EXPIRED = 'AUTH_EXPIRED',\n ACCESS_DENIED = 'ACCESS_DENIED',\n APP_NOT_FOUND = 'APP_NOT_FOUND',\n REDIRECT_INVALID = 'REDIRECT_INVALID',\n PORT_IN_USE = 'PORT_IN_USE',\n NETWORK_ERROR = 'NETWORK_ERROR',\n RATE_LIMITED = 'RATE_LIMITED',\n APP_LIMIT_REACHED = 'APP_LIMIT_REACHED',\n REGISTRY_ERROR = 'REGISTRY_ERROR',\n AUTH_GATEWAY = 'AUTH_GATEWAY',\n}\n\nexport class ApiError extends CliError {\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly errorCode?: ErrorCode,\n public readonly apiCode?: string,\n ) {\n super(message, statusToExitCode(statusCode));\n this.name = 'ApiError';\n }\n}\n\nfunction statusToExitCode(status: number): number {\n if (status === 0) return EXIT_CODES.NETWORK_ERROR;\n if (status === 401) return EXIT_CODES.AUTH_FAILURE;\n if (status === 403) return EXIT_CODES.ERROR;\n if (status === 404) return EXIT_CODES.NOT_FOUND;\n return EXIT_CODES.ERROR;\n}\n", "import { CliError } from './errors';\n\n// Track whether URL suffix parts were stripped for deferred warning (avoid side effects at import time)\nlet strippedUrlSuffix: string | undefined;\n\nfunction getStrippedUrlSuffix(parsed: URL): string | undefined {\n const pathname = parsed.pathname === '/' ? '' : parsed.pathname;\n const suffix = `${pathname}${parsed.search}${parsed.hash}`;\n return suffix || undefined;\n}\n\nfunction stripPath(parsed: URL): string {\n // Use origin only \u2014 endpoints already include full paths (e.g. /v3/account).\n // Prevents double-path issues when BREVO_API_URL includes a path like /v3.\n strippedUrlSuffix = getStrippedUrlSuffix(parsed);\n return parsed.origin;\n}\n\nexport function warnIfPathStripped(): void {\n if (strippedUrlSuffix) {\n process.stderr.write(\n ` Warning: BREVO_API_URL path \"${strippedUrlSuffix}\" was stripped. Endpoints already include paths.\\n`,\n );\n strippedUrlSuffix = undefined;\n }\n}\n\nfunction isLocalHttpAllowed(parsed: URL): boolean {\n return (\n parsed.protocol === 'http:' &&\n (parsed.hostname === 'localhost' ||\n parsed.hostname === '127.0.0.1' ||\n parsed.hostname === '::1')\n );\n}\n\nfunction resolveApiBase(): string {\n const raw = process.env.BREVO_API_URL || 'https://api.brevo.com';\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new CliError(`Invalid BREVO_API_URL: \"${raw}\" is not a valid URL.`);\n }\n if (parsed.protocol === 'https:') return stripPath(parsed);\n if (isLocalHttpAllowed(parsed)) return stripPath(parsed);\n throw new CliError(\n `BREVO_API_URL must use HTTPS. Got: ${raw}\\n HTTP is only allowed for localhost/127.0.0.1.`,\n );\n}\n\nexport const API_BASE = resolveApiBase();\n\nfunction resolveOauthProxyUrl(): string {\n const raw = process.env.BREVO_OAUTH_PROXY_URL || 'https://oauth-cli.brevo.com';\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new CliError(`Invalid BREVO_OAUTH_PROXY_URL: \"${raw}\" is not a valid URL.`);\n }\n if (parsed.protocol !== 'https:' && !isLocalHttpAllowed(parsed)) {\n throw new CliError(\n `BREVO_OAUTH_PROXY_URL must use HTTPS. Got: ${raw}\\n HTTP is only allowed for localhost/127.0.0.1.`,\n );\n }\n return parsed.origin;\n}\n\nexport const OAUTH_PROXY_URL = resolveOauthProxyUrl();\n\n/**\n * Base URL of the app-store service.\n *\n * The update-notice endpoint is called here **directly**, not through the v3\n * API gateway: the gateway enforces authentication on every path it fronts, and\n * this notice has to render while the user is logged out, mid-`brevo login`, or\n * holding expired credentials \u2014 exactly when telling them their CLI is stale\n * matters most. The service serves it unauthenticated.\n *\n * Override with BREVO_APP_STORE_URL to point at a non-production environment.\n */\nfunction resolveAppStoreUrl(): string {\n const raw = process.env.BREVO_APP_STORE_URL || 'https://app-store-bo-be.brevo.com';\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new CliError(`Invalid BREVO_APP_STORE_URL: \"${raw}\" is not a valid URL.`);\n }\n if (parsed.protocol !== 'https:' && !isLocalHttpAllowed(parsed)) {\n throw new CliError(\n `BREVO_APP_STORE_URL must use HTTPS. Got: ${raw}\\n HTTP is only allowed for localhost/127.0.0.1.`,\n );\n }\n return parsed.origin;\n}\n\nexport const APP_STORE_BASE = resolveAppStoreUrl();\n\n// Header identifying the CLI on every API request. The backend parses it to\n// emit product-tracking events (e.g. \"CLI installed\") on the `cli` Kafka topic.\n// Format: `brevo-cli/<version> (<os>[; auth=<method>])` \u2014 see lib/telemetry.ts.\nexport const USER_AGENT_HEADER = 'User-Agent';\n\n// Auth-method markers carried in the User-Agent comment \u2014 part of the same\n// backend contract as USER_AGENT_HEADER, so they live here rather than inline.\nexport const CLI_AUTH_METHODS = {\n API_KEY: 'api_key',\n OAUTH: 'oauth',\n} as const;\n\nexport const ENDPOINTS = {\n ACCOUNT: '/v3/account/info',\n // Sub-accounts of a master (corporate) account \u2014 `{ count, subAccounts: [...] }`.\n // `offset` and `limit` are both required (there is no \"return everything\" call),\n // so `count` is the paging terminator. `app deploy` / `app rollback` read this to\n // resolve a deploy target when no <account-id> was given.\n CORPORATE_SUB_ACCOUNTS: '/v3/corporate/subAccount',\n APP_STORE_APPS: '/v3/app-store/apps',\n APP_STORE_APP: (appId: string) => `/v3/app-store/apps/${encodeURIComponent(appId)}`,\n // Served by the app-store service directly (APP_STORE_BASE), not via the v3\n // gateway \u2014 see resolveAppStoreUrl above and services/cli-info.ts.\n CLI_INFO: '/cli/info',\n APP_STATE: (appId: string) => `/v3/app-store/apps/${encodeURIComponent(appId)}/state`,\n APP_STORE_APP_UPLOAD: (appId: string) => `/v3/app-store/apps/${encodeURIComponent(appId)}/upload`,\n APP_STORE_APP_WITHDRAW: (appId: string) =>\n `/v3/app-store/apps/${encodeURIComponent(appId)}/withdraw`,\n // Per-account availability for UI apps (BEX-290). Until an in-product\n // enable/disable surface ships, this endpoint *is* the install mechanism for\n // an action link: POST to install into an account, DELETE to remove.\n //\n // Both verbs take the same body \u2014 `client_id` (the *caller's* organization ID,\n // which the server uses to resolve the app, and which it falls back to as the\n // install target), `deploy_client_id` (the numeric account being deployed to),\n // `name`, `is_developer`. Note the `deploy` / `rollback` *commands* are named\n // for the partner-facing verb, not the resource; the resource is an install.\n //\n // DELETE resolves the install from this body rather than from an installation ID\n // (BEX-364) \u2014 the developer never sees one \u2014 so it answers 404 both for an unknown\n // app and for an install that isn't there. `app rollback` reads either as\n // \"not deployed\"; see the comment on its catch block.\n APP_STORE_APP_INSTALLS: (appId: string) =>\n `/v3/app-store/apps/${encodeURIComponent(appId)}/installs`,\n APP_STORE_SURFACE_POINTS: '/v3/app-store/surface-points',\n // The registry's distinct `location_name` values, and nothing else:\n // `{ locations: [\"companyDetails\", \u2026], count: 3 }`. `app create` reads this for the\n // record-page prompt and then narrows the row read with `?location=<csv>`, rather than\n // pulling the whole registry to derive the same handful of strings client-side.\n APP_STORE_SURFACE_POINT_LOCATIONS: '/v3/app-store/surface-points/locations',\n OAUTH_AUTHORIZE: '/oauth/authorize',\n OAUTH_TOKEN: '/oauth/token',\n} as const;\n\nexport const CLI = {\n LOGIN: 'brevo login',\n INIT: 'brevo app init',\n HELP: 'brevo --help',\n APP_CREATE: 'brevo app create',\n APP_LIST: 'brevo app list',\n APP_STATUS: 'brevo app status',\n APP_SCAFFOLD: 'brevo app scaffold',\n // The bootstrap form, kept separate from the bare `APP_SCAFFOLD` above rather than\n // folding both into one function: the bare string is quoted in a dozen messages that\n // mean \"run it here\", and only the migration copy means \"link that app into here\".\n APP_SCAFFOLD_APP_ID: (appId?: string) =>\n appId ? `brevo app scaffold --app-id ${appId}` : 'brevo app scaffold --app-id <id>',\n APP_CREDENTIALS: (appId?: string) =>\n appId ? `brevo app credentials --app-id ${appId}` : 'brevo app credentials --app-id <id>',\n APP_DELETE_APP_ID: (appId?: string) =>\n appId ? `brevo app delete --app-id ${appId}` : 'brevo app delete --app-id <id>',\n APP_CREDENTIALS_REVEAL: (appId?: string) =>\n appId\n ? `brevo app credentials --reveal-secret --app-id ${appId}`\n : 'brevo app credentials --reveal-secret',\n APP_UPLOAD: 'brevo app upload',\n // The account ID is optional \u2014 omitted, both commands resolve the target from the\n // authenticated account. The no-argument form is the one to show in guidance copy.\n APP_DEPLOY: (accountId?: string) =>\n accountId ? `brevo app deploy ${accountId}` : 'brevo app deploy',\n APP_ROLLBACK: (accountId?: string) =>\n accountId ? `brevo app rollback ${accountId}` : 'brevo app rollback',\n APP_DELETE: 'brevo app delete',\n APP_WITHDRAW: (appId?: string) =>\n appId ? `brevo app withdraw --app-id ${appId}` : 'brevo app withdraw --app-id <id>',\n APP_SUBMIT: (appId?: string) =>\n appId ? `brevo app submit --app-id ${appId}` : 'brevo app submit --app-id <id>',\n APP_START: (feature?: string) =>\n feature ? `brevo app start ${feature}` : 'brevo app start <feature>',\n APP_SCOPES: 'brevo app available-scopes',\n SKILL_INSTALL: 'brevo skill:cli install',\n SKILL_UNINSTALL: 'brevo skill:cli uninstall',\n} as const;\n\nexport const DEFAULT_APP_FOLDER = 'my-app';\nexport const DEFAULT_PORT = 3009;\nexport const DEFAULT_REDIRECT_URI = `http://localhost:${DEFAULT_PORT}/auth/callback`;\nexport const PLACEHOLDER_CLIENT_ID = 'YOUR_CLIENT_ID';\nexport const OAUTH_BASE = 'https://oauth.brevo.com';\nexport const OAUTH_REALM = 'partner';\nexport const OAUTH_SCOPES_URL = `${OAUTH_BASE}/realms/${OAUTH_REALM}/scopes`;\n\n// Legacy catch-all OAuth scope being deprecated (BEX-214). Single source of\n// truth for the spelling \u2014 every detection path goes through\n// `containsLegacyAllScope` in lib/validators.\nexport const LEGACY_ALL_SCOPE = 'all';\n\nexport const DEFAULT_SCOPES: readonly string[] = [\n 'contacts:read',\n 'contacts:write',\n 'crm:read',\n 'crm:write',\n] as const;\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 UI apps (BEX-290) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A UI app has no OAuth block at all \u2014 its config carries an empty\n// `auth: {}`, so there is no per-type default scope set (BEX-290 follow-up).\n\n/**\n * Extension-point slot grammar: `<location>.<place>.<kind>` (BEX-350).\n *\n * The CLI holds no list of valid slot names. It used to mirror the platform's\n * twelve-row `extension_points` registry here so `app upload` could pre-flight a\n * hand-edited `surface_point_list` offline, but that mirror could only ever be a\n * lagging copy: a slot the platform had and the mirror didn't failed an upload the\n * server would have accepted, and a slot the platform had dropped still passed.\n *\n * The registry is now the only authority, read at the two points that need it:\n * `app create` prompts from it (`GET /v3/app-store/surface-points/locations` for the\n * record pages, then `GET /v3/app-store/surface-points?location=<csv>` for the\n * placements on the pages that were picked), and the upload\n * endpoint checks every authored name against `extension_points` in one indexed\n * read, answering 400 with the offending names (`checkExtensionPoints`, BEX-361).\n * Neither the UI kit's exact-match rendering nor the platform's silent drop of an\n * unregistered name has changed \u2014 a bad slot is still invisible in production \u2014\n * but the partner is now told by the layer that actually knows.\n */\n\n/**\n * The `kind` segment. Both extension types render on both kinds \u2014 a widget slot gets a\n * card, an action slot a menu entry \u2014 so kind is a placement choice, not a consequence\n * of the extension type.\n */\nexport const EXTENSION_KIND_ACTION = 'action';\nexport const EXTENSION_KIND_WIDGET = 'widget';\n\n/**\n * Friendly labels for the placement prompt \u2014 partners think in page regions, the wire\n * wants the `place` segment (served as `section_name`).\n *\n * These are CLI-OWNED and stay that way. An earlier version of this comment claimed they\n * mirror `extension_points.surface_point_name`, \"the platform's own display text\" \u2014 that\n * is false: the seeded values of that column are kebab-case slugs\n * (`contact-details-header-menu`), so rendering them to a partner would be worse than\n * these. The registry exposes no display-name column, so until it does, this map is the\n * only source of partner-facing placement labels.\n */\nexport const EXTENSION_PLACE_LABELS: Readonly<Record<string, string>> = {\n headerMenu: 'Header \"More\" (\u2022\u2022\u2022) menu',\n overviewMain: 'Main column',\n overviewSidebar: 'Sidebar',\n overviewAttributes: 'Attributes panel',\n} as const;\n\n/**\n * There is deliberately no friendly-name map for record pages, and no default page.\n *\n * `app create`'s record-page prompt shows the registry's own `location_name` values\n * verbatim (`contactDetails`, \u2026) and pre-selects none of them. A local `contact \u2192\n * contactDetails` map used to sit here: it gave every page a second, CLI-owned name that\n * had to be kept in step with the platform, needed a strip-`Details` guess for any page\n * the map didn't know, and let the prompt show something the API never said. Do not\n * reintroduce it. `EXTENSION_PLACE_LABELS` above is not a counter-example: it labels a\n * fixed segment of the BEX-350 grammar, not a server-supplied identifier.\n */\n\n/**\n * `extension_type` values, camelCase per BEX-350 \u2014 the same casing as the\n * extension-point grammar. Source of truth is `FEATURE_TYPES` in the\n * extensibility UI kit (integrations-common-frontend\n * `shared/constants/global.ts`); the kit routes on an exact match against these\n * strings.\n *\n * The pre-BEX-350 snake_case spellings (`action_link`, `iframe_extension`,\n * `legacy_component`) are deliberately NOT accepted. The kit still maps them for\n * backward compatibility, but that map is slated for removal once every producer\n * emits camelCase \u2014 and the CLI is a producer, so it only ever writes canonical\n * values. UI apps aren't live yet, so there is no authored config to migrate.\n */\nexport const EXTENSION_TYPE_ACTION_LINK = 'actionLink';\nexport const EXTENSION_TYPE_IFRAME = 'iframeExtension';\nexport const EXTENSION_TYPE_LEGACY = 'legacyComponent';\n\nexport const LINK_TARGETS: readonly string[] = ['_blank', '_self'] as const;\n// NOT authored into app-config.json (BEX-290) \u2014 `brevo app upload` injects this value\n// into an actionLink's upload payload, and `brevo app create` never writes it. Sent\n// explicitly rather than left to a default, so the payload is unambiguous.\nexport const DEFAULT_LINK_TARGET = '_blank';\n\n/**\n * The only link_target uploads accept today. `_self` is refused server-side even though\n * both the platform and the UI kit handle it, so the CLI writes `_blank` unconditionally\n * rather than prompting for a choice one of whose options would 400.\n *\n * To restore the choice: widen this back to LINK_TARGETS and re-add the prompt, in step\n * with the server relaxing its own check.\n */\nexport const UPLOADABLE_LINK_TARGETS: readonly string[] = [DEFAULT_LINK_TARGET] as const;\n\nexport const BREVO_DASHBOARD_API_KEYS_URL = 'https://app.brevo.com/settings/keys/api';\nexport const BREVO_API_KEY_DOCS_URL = 'https://developers.brevo.com/docs/api-key-authentication';\nexport const BREVO_STATUS_URL = 'https://status.brevo.com';\nexport const BREVO_DOCS_URL = 'https://developers.brevo.com';\nexport const BREVO_CLI_REFERENCE_URL = 'https://developers.brevo.com/docs/cli-reference';\nexport const BREVO_OAUTH_SCOPES_DOCS_URL =\n 'https://developers.brevo.com/docs/oauth-scopes#scope-catalog';\n", "import { CliError } from './errors';\nimport {\n LEGACY_ALL_SCOPE,\n EXTENSION_TYPE_ACTION_LINK,\n EXTENSION_TYPE_IFRAME,\n UPLOADABLE_LINK_TARGETS,\n} from './constants';\n\nconst APP_NAME_MAX_LENGTH = 48;\nconst APP_NAME_REGEX = /^[a-zA-Z0-9 ._\\-\\u00C0-\\u024F]+$/;\n\n/**\n * Validate an app name: alphanumeric, spaces, hyphens, dots, underscores,\n * and accented/extended Latin characters only. Max 48 characters.\n * Returns true if valid, or an error string for inquirer prompts.\n */\nexport function validateAppName(name: string): true | string {\n const trimmed = name.trim();\n if (trimmed.length === 0) return 'App name cannot be empty.';\n if (trimmed.length > APP_NAME_MAX_LENGTH) {\n return `App name must be at most ${APP_NAME_MAX_LENGTH} characters (got ${trimmed.length}).`;\n }\n if (!APP_NAME_REGEX.test(trimmed)) {\n return 'App name can only contain letters, numbers, spaces, hyphens, dots, underscores, and accented characters.';\n }\n return true;\n}\n\n/**\n * Validate a free-text y/n answer, empty meaning \"take the default\".\n *\n * Shared rather than local to one command because two commands ask this shape of\n * question in two files (`app create`'s \"add another redirect URL?\" and the\n * scaffold-a-feature confirm both `app create` and `app scaffold` use), and they\n * must accept exactly the same spellings.\n */\nexport function validateYesNo(input: string): true | string {\n const val = String(input).toLowerCase().trim();\n if (val === 'y' || val === 'yes' || val === 'n' || val === 'no' || val === '') {\n return true;\n }\n return 'Please enter y or n';\n}\n\n/**\n * Validate that a value is one of the allowed options.\n * Throws CliError with a user-friendly message if invalid.\n */\nexport function validateEnum(\n value: string | undefined,\n allowed: readonly string[],\n flagName: string,\n): void {\n if (value && !allowed.includes(value)) {\n throw new CliError(`Invalid ${flagName} \"${value}\". Must be one of: ${allowed.join(', ')}.`);\n }\n}\n\n/**\n * Validate that a string is a valid HTTP/HTTPS URL.\n * Throws CliError if the format is invalid.\n */\nexport function validateUrl(value: string | undefined, fieldName: string): void {\n if (!value) return;\n // Reject whitespace and commas early \u2014 Node's URL parser silently percent-encodes\n // spaces, so \"http://a/cb, http://b/cb\" parses as a single valid URL. That has let\n // comma-separated values sneak into redirect_uris as one corrupted entry.\n if (/[\\s,]/.test(value)) {\n throw new CliError(\n `Invalid ${fieldName}: \"${value}\" contains whitespace or a comma. Pass each URL with a separate --redirect-uri flag.`,\n );\n }\n try {\n const parsed = new URL(value);\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error('bad protocol');\n }\n } catch {\n throw new CliError(`Invalid ${fieldName}: \"${value}\" is not a valid HTTP/HTTPS URL.`);\n }\n}\n\n/**\n * Commander.js collect function for repeatable --redirect-uri flags.\n * Validates each URL and accumulates into an array.\n */\nexport function collectUrls(value: string, previous: string[] = []): string[] {\n validateUrl(value, 'redirect URL');\n return [...previous, value];\n}\n\n// OAuth scope tokens are split on commas and whitespace at every boundary\n// (app-config.json reads and --scope flag values) so a user can write either\n// \"crm:read crm:write\" or \"crm:read, crm:write\" or one --scope per token and\n// the CLI behaves the same. RFC 6749 \u00A73.3 already mandates space-separation in\n// authorization requests, so the split is consistent with the protocol; the\n// comma is a convenience for users editing JSON arrays.\nconst SCOPE_TOKEN_REGEX = /^[A-Za-z0-9][A-Za-z0-9:_.-]*$/;\nconst SCOPE_SPLIT_REGEX = /[\\s,]+/;\n\n/**\n * Split a scope string or array of strings into individual scope tokens.\n * Handles embedded commas/whitespace, trims, drops empties, dedupes.\n * Does NOT validate token format \u2014 use `validateScopes` for that.\n */\nexport function splitScopes(input: string | string[] | undefined | null): string[] {\n if (input == null) return [];\n const values = Array.isArray(input) ? input : [input];\n const out: string[] = [];\n const seen = new Set<string>();\n for (const v of values) {\n if (typeof v !== 'string') continue;\n for (const token of v.split(SCOPE_SPLIT_REGEX)) {\n if (!token) continue;\n if (seen.has(token)) continue;\n seen.add(token);\n out.push(token);\n }\n }\n return out;\n}\n\n/**\n * Validate that each scope is a well-formed token.\n * Throws CliError with a user-facing message if any scope is invalid.\n */\nexport function validateScopes(scopes: string[]): void {\n for (const scope of scopes) {\n if (!SCOPE_TOKEN_REGEX.test(scope)) {\n throw new CliError(\n `Invalid scope: \"${scope}\" \u2014 scopes can only contain letters, numbers, ':', '_', '.', '-'.`,\n );\n }\n }\n}\n\n/**\n * Commander.js collect function for repeatable --scope flags.\n * Splits each value on commas/whitespace, validates the resulting tokens,\n * and accumulates into an array (deduplicated against previous values).\n */\nexport function collectScopes(value: string, previous: string[] = []): string[] {\n const tokens = splitScopes(value);\n if (tokens.length === 0) {\n throw new CliError('Invalid scope: value cannot be empty.');\n }\n validateScopes(tokens);\n const out = [...previous];\n for (const t of tokens) {\n if (!out.includes(t)) out.push(t);\n }\n return out;\n}\n\n/**\n * Returns true iff the scope list contains the deprecated legacy 'all' scope.\n * Every code path that warns or blocks on the legacy scope calls this helper \u2014\n * no scattered string literals (BEX-214).\n */\n// Accepts null as well as undefined: the app-store read path returns `scopes: null`\n// for an app with no OAuth block, and both mean \"no scopes\" here.\nexport function containsLegacyAllScope(scopes: string[] | null | undefined): boolean {\n return scopes?.includes(LEGACY_ALL_SCOPE) ?? false;\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 UI apps (BEX-290) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Whether an HTTP(S) URL is safe as a UI-app destination.\n *\n * The extensibility UI kit drops any non-http(s) `redirect_link` outright\n * (`isHttpUrl` in its shared utils), so anything else would be authored and then\n * silently ignored. On top of that, Brevo opens this URL from an authenticated\n * CRM page, so plain http would downgrade the session \u2014 https is required, with\n * the loopback exemption the rest of the CLI grants so partners can point at a\n * local dev server while building.\n */\nfunction isSafeUiAppUrl(parsed: URL): boolean {\n if (parsed.protocol === 'https:') return true;\n return (\n parsed.protocol === 'http:' &&\n (parsed.hostname === 'localhost' ||\n parsed.hostname === '127.0.0.1' ||\n parsed.hostname === '::1')\n );\n}\n\n/**\n * Validate a UI-app destination URL. Returns `true` or an error string, so it can\n * back an inquirer `validate` directly as well as the upload-time check.\n */\nexport function validateUiAppUrl(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) return 'URL cannot be empty.';\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n return `Invalid URL: \"${trimmed}\" is not a valid URL.`;\n }\n if (!isSafeUiAppUrl(parsed)) {\n return `Invalid URL: \"${trimmed}\" must use https:// (http:// is allowed only for localhost).`;\n }\n return true;\n}\n\n// Length ceilings for the two authored text fields. Both are enforced server-side, so\n// without a local check the partner gets an opaque 400 from the upload endpoint \u2014 exactly\n// the failure mode this pre-flight exists to prevent. `label` shares the app-name ceiling\n// because it occupies the same one-line menu row.\nconst UI_APP_LABEL_MAX_LENGTH = 48;\nconst UI_APP_MORE_INFO_MAX_LENGTH = 255;\n\n/**\n * Validate a UI-app `label` \u2014 the menu entry's text on an `.action` slot, the card's CTA\n * button text on a `.widget` slot. Returns `true` or an error string, so it can back an\n * inquirer `validate` directly.\n */\nexport function validateUiAppLabel(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) return 'Label cannot be empty.';\n if (trimmed.length > UI_APP_LABEL_MAX_LENGTH) {\n return `Label must be at most ${UI_APP_LABEL_MAX_LENGTH} characters (got ${trimmed.length}).`;\n }\n return true;\n}\n\n/**\n * Validate a UI-app `more_info` \u2014 the menu entry's `subText`, the card's description.\n * Optional, so blank passes; only the length ceiling is enforced.\n */\nexport function validateUiAppMoreInfo(value: string): true | string {\n const trimmed = value.trim();\n if (trimmed.length > UI_APP_MORE_INFO_MAX_LENGTH) {\n return `More info must be at most ${UI_APP_MORE_INFO_MAX_LENGTH} characters (got ${trimmed.length}).`;\n }\n return true;\n}\n\n/**\n * Validate the SHAPE of a `surface_point_list` entry's slot name \u2014 present and\n * non-blank. Whether the name is registered is deliberately NOT checked here.\n *\n * The registry is the platform's, and only the platform can answer against it\n * without lagging. `app upload` sends the block and the upload endpoint checks\n * every name against `extension_points` in one read, answering 400 with the\n * offending names (`checkExtensionPoints`, BEX-361). `app create` cannot author\n * an unregistered name in the first place: every entry it writes is built from a\n * row the registry just returned.\n *\n * The failure this guards against is still real \u2014 the platform DROPS an\n * unregistered name, so a near-miss renders nothing with a 200 \u2014 but a local\n * allow-list was the wrong place to catch it. It could only ever be a stale\n * copy, and it failed in both directions: rejecting a slot the platform had added,\n * and passing one the platform had removed.\n *\n * Note the value is the registry's `surface_point_name` slug\n * (`contact-details-header-menu`), not the dotted grammar name \u2014 see\n * `SurfacePointEntry.surface_point_name`. Shape-only means the two are indistinguishable\n * here; only the registry can tell them apart, which is another reason not to try.\n */\nexport function validateSurfacePoint(point: string): true | string {\n const trimmed = String(point ?? '').trim();\n if (!trimmed) return 'Surface point cannot be empty.';\n return true;\n}\n\n/**\n * The extension types the CLI can author. `legacyComponent` is absent by design: it is\n * the pre-extensibility interpreter path, driven by the UI kit's own config registry\n * rather than by a snapshot, and never partner-authored.\n *\n * The pre-BEX-350 snake_case spellings are absent too \u2014 the CLI only ever writes\n * canonical camelCase, even though the server still accepts the old spellings inbound.\n */\nconst AUTHORABLE_EXTENSION_TYPES: readonly string[] = [\n EXTENSION_TYPE_ACTION_LINK,\n EXTENSION_TYPE_IFRAME,\n] as const;\n\n/**\n * Validate one placement's `context` list: field names must be non-empty and unique.\n *\n * Shape only. Whether the platform actually ALLOWS a given name is not checkable locally \u2014\n * the allow-list lives on that extension-point registry row \u2014 so it is checked server-side\n * at upload, where the 400 enumerates the allowed set. `brevo app create` never produces a\n * disallowed name anyway: it seeds each entry from the row's own `default_context_field`,\n * which the registry keeps inside the row's allow-list.\n */\nexport function validateUiAppContext(fields: readonly string[]): true | string {\n const seen = new Set<string>();\n for (const field of fields) {\n const trimmed = String(field ?? '').trim();\n if (!trimmed) return 'Context field names cannot be empty.';\n if (seen.has(trimmed)) return `Duplicate context field \"${trimmed}\".`;\n seen.add(trimmed);\n }\n return true;\n}\n\n/**\n * `String()` for a field read out of a hand-edited `app-config.json`, type-narrowed first.\n *\n * Every field of `ui_app` arrives as `unknown`, and a bare `String(value)` on an object or\n * array yields Object's default stringification \u2014 `'[object Object]'`. That is a non-blank\n * string, so it sails through the \"cannot be empty\" checks below and lets an object reach\n * the wire under a field the server expects to be text. Anything that is not a primitive is\n * therefore treated as absent, so the field's own message fires instead.\n *\n * Narrowed by listing what IS accepted rather than excluding objects, because TypeScript\n * does not subtract from `unknown`: after `if (typeof value === 'object') return ''` the\n * else-branch is still `unknown`, so `String(value)` there is the very call this exists to\n * avoid. A JSON document holds no symbols or functions, so nothing reachable is lost.\n */\nfunction asText(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n return '';\n}\n\n/**\n * Is a field that must be ABSENT actually present?\n *\n * Distinct from `asText(value).trim() !== ''` on purpose: these are the \"cannot be combined\n * with\" checks, where a non-string value is still something the partner wrote and still\n * travels to the wire, so it must be reported rather than read as an empty string and\n * waved through. Only a genuinely blank string counts as absent.\n */\nfunction isPresentField(value: unknown): boolean {\n if (value === undefined) return false;\n return typeof value !== 'string' || value.trim() !== '';\n}\n\n/**\n * Fully validate a `ui_app` block before it is sent to the server.\n *\n * The block is the app snapshot the platform stores, verbatim.\n * Every field below is optional on the wire \u2014 the backend degrades a malformed or\n * absent snapshot to \"not yet migrated\" rather than erroring \u2014 which means the\n * serving path will NOT tell a partner their extension is unrenderable. This\n * pre-flight is therefore deliberately stricter than the wire: it requires the fields\n * each type actually needs to render, and rejects combinations the consumers silently\n * discard.\n *\n * Throws CliError on the first problem found.\n */\nexport function validateUiApp(uiApp: unknown): void {\n if (!uiApp || typeof uiApp !== 'object') {\n throw new CliError(\n 'app-config.json has an invalid \"ui_app\" block \u2014 expected an object. Fix the file, or recreate the app with `brevo app create` and choose \"UI app\".',\n );\n }\n const block = uiApp as Record<string, unknown>;\n const extensionType = asText(block.extension_type);\n\n if (!AUTHORABLE_EXTENSION_TYPES.includes(extensionType)) {\n throw new CliError(\n `Unsupported ui_app.extension_type \"${extensionType}\". Must be one of: ${AUTHORABLE_EXTENSION_TYPES.join(', ')}.`,\n );\n }\n\n rejectPreBex290Fields(block);\n\n validateSurfacePointList(block.surface_point_list);\n\n const labelCheck = validateUiAppLabel(asText(block.label));\n if (labelCheck !== true) throw new CliError(`ui_app.label: ${labelCheck}`);\n\n const moreInfoCheck = validateUiAppMoreInfo(asText(block.more_info));\n if (moreInfoCheck !== true) throw new CliError(`ui_app.more_info: ${moreInfoCheck}`);\n\n if (extensionType === EXTENSION_TYPE_IFRAME) {\n validateIframeExtensionFields(block);\n } else {\n validateActionLinkFields(block);\n }\n}\n\n/**\n * Refuse the pre-BEX-290 field names with a migration hint rather than a mystery.\n *\n * These are a local diagnostic and claim nothing about how the upload endpoint treats an\n * unmigrated block. They earn their place on the message alone: `label`, `more_info` and a\n * per-placement `context` are the only names any consumer reads, so a config written by an\n * earlier build of this branch is wrong whatever the server does with it, and the generic\n * \"label cannot be empty\" the checks below pre-empt points at the wrong thing \u2014 the label\n * IS there, under its old name.\n *\n * No read-path alias, deliberately, for the same reason the snake_case rename didn't get\n * one: UI apps aren't live, so there is no partner config in the wild to migrate. These\n * files only exist on developer machines, and a two-line rename is cheaper to explain than\n * an alias map that has to be removed later.\n */\nfunction rejectPreBex290Fields(block: Record<string, unknown>): void {\n if (block.heading !== undefined) {\n throw new CliError(\n \"ui_app.heading was renamed to ui_app.label (it is the menu entry's text and the card's CTA). Rename the field in app-config.json.\",\n );\n }\n if (block.subheading !== undefined) {\n throw new CliError(\n \"ui_app.subheading was renamed to ui_app.more_info (it is the menu entry's second line and the card's description). Rename the field in app-config.json.\",\n );\n }\n if (block.context !== undefined) {\n throw new CliError(\n 'ui_app.context is no longer a top-level field \u2014 record context is now per placement. Move each field list into the matching `surface_point_list` entry, e.g. [{ \"surface_point_name\": \"contact-details-header-menu\", \"context\": [\"recordId\"] }].',\n );\n }\n}\n\n/**\n * Validate the slot list. Both extension types render on both kinds \u2014 a widget slot gets\n * a card, an action slot a menu entry \u2014 so the rules are that the list is non-empty, every\n * entry is an object naming a slot, no slot repeats, and each entry's `context` (when\n * present) is a well-formed list of field names.\n *\n * Shape only: whether a name is registered, and whether its context is within that slot's\n * allow-list, are both the upload endpoint's answer to give (see `validateSurfacePoint`).\n */\nfunction validateSurfacePointList(entries: unknown): void {\n if (!Array.isArray(entries) || entries.length === 0) {\n throw new CliError(\n 'ui_app.surface_point_list must list at least one placement (e.g. [{ \"surface_point_name\": \"contact-details-header-menu\", \"context\": [\"recordId\"] }]). An empty list makes the platform fall back to its default widget slots, which is unlikely to be where you want the app.',\n );\n }\n const names: string[] = [];\n for (const entry of entries) {\n if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {\n throw new CliError(\n 'ui_app.surface_point_list entries must be objects, e.g. { \"surface_point_name\": \"contact-details-header-menu\", \"context\": [\"recordId\"] }. A bare string is the pre-BEX-290 shape.',\n );\n }\n const row = entry as Record<string, unknown>;\n const check = validateSurfacePoint(asText(row.surface_point_name));\n if (check !== true) throw new CliError(`ui_app.surface_point_list: ${check}`);\n const name = asText(row.surface_point_name).trim();\n names.push(name);\n\n if (row.context !== undefined) {\n if (!Array.isArray(row.context)) {\n throw new CliError(\n `ui_app.surface_point_list[\"${name}\"].context must be an array of field names, e.g. [\"recordId\"].`,\n );\n }\n const contextCheck = validateUiAppContext(row.context.map(asText));\n if (contextCheck !== true) {\n throw new CliError(`ui_app.surface_point_list[\"${name}\"].context: ${contextCheck}`);\n }\n }\n }\n if (new Set(names).size !== names.length) {\n throw new CliError('ui_app.surface_point_list contains duplicate extension points.');\n }\n}\n\nfunction validateActionLinkFields(block: Record<string, unknown>): void {\n const urlCheck = validateUiAppUrl(asText(block.redirect_link));\n if (urlCheck !== true) throw new CliError(`ui_app.redirect_link: ${urlCheck}`);\n\n // _self is refused server-side for now, so accepting it here would only move the\n // failure to upload time. See UPLOADABLE_LINK_TARGETS.\n if (\n block.link_target !== undefined &&\n !UPLOADABLE_LINK_TARGETS.includes(asText(block.link_target))\n ) {\n throw new CliError(\n `Invalid ui_app.link_target \"${asText(block.link_target)}\". Must be one of: ${UPLOADABLE_LINK_TARGETS.join(', ')}.`,\n );\n }\n\n // The UI kit keeps `modal_iframe_url` only for an `iframeExtension` item, so one\n // carried by an actionLink is dropped without a word. Reject rather than let a\n // partner ship a URL that will never open.\n if (isPresentField(block.modal_iframe_url)) {\n throw new CliError(\n `ui_app.modal_iframe_url is only used by \"${EXTENSION_TYPE_IFRAME}\" extensions and is ignored for \"${EXTENSION_TYPE_ACTION_LINK}\". Remove it, or use redirect_link instead.`,\n );\n }\n}\n\nfunction validateIframeExtensionFields(block: Record<string, unknown>): void {\n const urlCheck = validateUiAppUrl(asText(block.modal_iframe_url));\n if (urlCheck !== true) throw new CliError(`ui_app.modal_iframe_url: ${urlCheck}`);\n\n // Refused because the two delivery paths disagree about which URL wins: the\n // widget-card path pairs strictly by extension_type and opens the modal, while the\n // header-menu path routes on redirect_link first and never opens it. The same app would\n // behave differently depending on the slot it rendered on.\n if (isPresentField(block.redirect_link)) {\n throw new CliError(\n `ui_app.redirect_link cannot be combined with \"${EXTENSION_TYPE_IFRAME}\": a menu entry would follow the redirect instead of opening the modal, while a card would open the modal. Remove it, or use \"${EXTENSION_TYPE_ACTION_LINK}\" instead.`,\n );\n }\n\n // link_target only governs where a redirect_link opens; a modal embeds its URL.\n if (isPresentField(block.link_target)) {\n throw new CliError(\n `ui_app.link_target has no effect on \"${EXTENSION_TYPE_IFRAME}\" extensions, which embed their URL in a modal rather than navigating to it. Remove it.`,\n );\n }\n}\n\n/**\n * Parse and validate an explicit `[account-id]` argument for `app deploy` /\n * `app rollback`. Only reached when the positional was actually given \u2014 an omitted\n * one is resolved from the authenticated account instead, never routed through here.\n * Brevo account IDs are numeric; accept a trimmed digit string.\n */\nexport function parseAccountId(value: string): string {\n const trimmed = String(value ?? '').trim();\n if (!trimmed) {\n throw new CliError('Invalid account ID: value cannot be empty.');\n }\n if (!/^\\d+$/.test(trimmed)) {\n throw new CliError(`Invalid account ID: \"${trimmed}\" is not a numeric Brevo account ID.`);\n }\n return trimmed;\n}\n\n/**\n * Validate that a value is a positive integer.\n * Throws CliError if the value is not a valid positive integer.\n */\nexport function parsePositiveInt(value: string, flagName: string): number {\n const n = Number.parseInt(value, 10);\n if (!Number.isFinite(n) || n <= 0) {\n throw new CliError(`Invalid ${flagName}: \"${value}\" is not a positive integer.`);\n }\n return n;\n}\n\n/**\n * Parse and validate a `--app-id` flag value.\n * Accepts any non-empty trimmed string (numeric or UUID).\n */\nexport function parseAppId(value: string): string {\n const trimmed = value.trim();\n if (trimmed.length === 0) {\n throw new CliError('Invalid --app-id: value cannot be empty.');\n }\n return trimmed;\n}\n", "/**\n * The UI-app discriminators.\n *\n * A LEAF module: it imports nothing at runtime, only erased `import type`s. That is a\n * deliberate constraint, not an accident of size.\n *\n * - `lib/config.ts` re-exports these as `isUiAppConfig` / `isUiAppRecord`, so it can depend\n * on `app-types` without `app-types` depending back on it.\n * - Half the command test suites mock `lib/config` with only the handful of functions they\n * use. When the app-type registry read its predicates from there, every one of those\n * mocks silently made detection `undefined` \u2014 `app submit`'s whole suite failed on\n * `isUiAppRecord is not a function`. Keeping the predicates out of a mocked module means\n * type detection can't be accidentally stubbed out by a test that wasn't thinking about\n * it.\n */\n// `import type` only \u2014 erased at compile time, so importing from `lib/config` here creates no\n// runtime dependency and therefore no cycle when `lib/config` re-exports these.\nimport type { ProjectConfig } from '../../lib/config';\nimport type { OAuthApp } from '../../types';\n\n/** The app-record fields detection needs. Partial: a server record may omit any of them. */\nexport type AppRecordLike = Partial<Pick<OAuthApp, 'ui_app' | 'client_id' | 'redirect_uris'>>;\n\n/** The config fields detection needs. */\nexport type AppConfigLike = Pick<ProjectConfig, 'ui_app'>;\n\n/**\n * Whether a project config describes a UI app rather than an OAuth app.\n *\n * The presence of the `ui_app` block is the discriminator \u2014 there is no separate `appType`\n * key, matching the wire contract. Every branch that needs to distinguish the app types goes\n * through here (or the registry that wraps it), so the discriminator can change in one place\n * if the backend later requires an explicit type field.\n */\nexport function isUiAppConfigShape(config: AppConfigLike | null | undefined): boolean {\n return !!config?.ui_app;\n}\n\n/**\n * Whether a *server* app record describes a UI app. The record counterpart to\n * {@link isUiAppConfigShape}.\n *\n * The echoed `ui_app` block is the reliable signal, but it is not always there:\n * `GET /v3/app-store/apps` (the list) returns no `ui_app` key at all today, and even the\n * single-app read only carries it on server builds that echo it back. So this falls back to\n * the absence of *every* piece of OAuth material \u2014 a UI app sends no `auth` block, so the\n * server has no client_id and no callbacks to return, and answers `client_id: \"\"` with\n * `redirect_uris: null`.\n *\n * Deliberately requires BOTH to be empty. A record with a client_id but no callbacks is a\n * half-configured OAuth app, not a UI app, and must keep rendering as one.\n *\n * The fallback can go once the list echoes `ui_app` (RELEASE-CHECKLIST.md \u2192 Before UI-apps GA).\n */\nexport function isUiAppRecordShape(app: AppRecordLike | null | undefined): boolean {\n if (!app) return false;\n if (app.ui_app) return true;\n return !app.client_id && !app.redirect_uris?.length;\n}\n", "import { CLI, BREVO_CLI_REFERENCE_URL, BREVO_OAUTH_SCOPES_DOCS_URL } from '../lib/constants';\nimport { previewMessages } from './preview-messages';\n\n/**\n * How a scaffold report describes what it just did, given the files it wrote and the\n * files the project ends up with.\n *\n * They are equal on a fresh scaffold and diverge on a merge, which keeps existing files\n * and writes only the missing ones \u2014 so \"wrote 0\" and \"the project has 5\" are both true\n * and only the pair is informative. Saying just the first printed `(0 files)` above a\n * five-file tree; saying just the second would claim to have written files it left alone.\n */\nfunction scaffoldFileCount(done: string, written: number, total: number): string {\n if (written === total) return `${done} (${total} files)`;\n if (written === 0) return `already in place (${total} files, nothing rewritten)`;\n return `${done} (${written} of ${total} files written)`;\n}\n\nconst coreMessages = {\n // Update notifier\n UPDATE_AVAILABLE: (current: string, latest: string): string =>\n `Update available: ${current} \u2192 ${latest}`,\n UPDATE_RUN: (name: string): string => `Run: npm install -g ${name}`,\n UPDATE_RUN_YARN: (name: string): string => `Or: yarn global add ${name}`,\n // Homebrew formula is `brevo` (tap getbrevo/tap), not the npm package name.\n UPDATE_RUN_BREW: 'Or: brew upgrade brevo',\n\n // Force update (major-version behind \u2014 blocks the command)\n FORCE_UPDATE_REQUIRED: (current: string, latest: string): string =>\n `Update required: v${current} is no longer supported (latest v${latest}).`,\n FORCE_UPDATE_HINT: 'Update to continue using the Brevo CLI:',\n\n // Shown above the update box, never inside it. Replaced by the API's wording\n // when /cli/info returns a usable upgrade_message; must stand on its own when\n // it does not.\n CLI_VERSION_NOTICE_FALLBACK: 'A newer version of the Brevo CLI is available.',\n\n // Auth\n AUTH_WELCOME: 'Welcome to Brevo CLI',\n AUTH_PROMPT_METHOD: 'How would you like to authenticate?',\n AUTH_PROMPT_API_KEY: 'Paste your API key:',\n AUTH_SUCCESS: (email: string) => `Authenticated as ${email}`,\n AUTH_INVALID_KEY: 'Invalid API key. Please check and try again.',\n AUTH_HINT: (keysUrl: string, docsUrl: string) =>\n `\\n To authenticate, you need a Brevo API key.\\n Create one at: ${keysUrl}\\n Docs: ${docsUrl}\\n`,\n AUTH_SAVED: (path: string) => `Credentials saved to ${path}`,\n AUTH_NEXT: `Next: ${CLI.APP_CREATE}`,\n AUTH_CREATE_APP_PROMPT: 'Would you like to create an app?',\n AUTH_NOT_LOGGED_IN: 'Not currently authenticated.',\n AUTH_LOGGED_OUT: 'Credentials cleared.',\n AUTH_LOGGED_OUT_WITH_APPS: (count: number) =>\n `Credentials cleared, including cached credentials for ${count} app${count === 1 ? '' : 's'}.`,\n AUTH_LOGOUT_APP_WARNING:\n 'You have cached app credentials (clientId/clientSecret) that cannot be recovered after logout.\\n Run `brevo app credentials --reveal-secret` to view them before proceeding.',\n AUTH_LOGOUT_CONFIRM: 'Proceed with logout?',\n AUTH_EXPIRED: 'Your API key is invalid or expired.',\n AUTH_EXPIRED_PROMPT: 'Enter a new API key:',\n AUTH_GET_KEY_URL: 'Create an API key at: https://app.brevo.com/settings/keys/api',\n AUTH_BROWSER_OPENING: 'Opening your browser to log you in...',\n AUTH_BROWSER_FALLBACK_URL: (url: string) =>\n `If your browser didn't open automatically, open this URL to log in:\\n ${url}`,\n AUTH_BROWSER_WAITING: 'Waiting for login to complete (Ctrl+C to cancel)...',\n AUTH_BROWSER_TOKENS_RECEIVED: (path: string) =>\n `Login complete. Credentials saved to ${path}. Verifying account...`,\n AUTH_BROWSER_TIMEOUT:\n 'Login timed out before we received a response from the browser.\\n If you were completing 2FA, close the browser tab and run `brevo login` again.\\n For non-interactive use, set BREVO_API_KEY instead.',\n AUTH_BROWSER_CANCELLED: 'Login cancelled.',\n AUTH_BROWSER_BAD_PAYLOAD: 'Unexpected response from the login service. Please try again.',\n AUTH_BROWSER_NON_INTERACTIVE:\n 'Browser login needs an interactive terminal. Set BREVO_API_KEY to authenticate non-interactively.',\n\n // Whoami\n WHOAMI_AUTHENTICATED: (email: string, company: string) =>\n `Authenticated as ${email} (${company})`,\n WHOAMI_NOT_AUTHENTICATED: `Not authenticated. Run: ${CLI.LOGIN}`,\n WHOAMI_CREDENTIAL_MISMATCH: (fields: string[]) =>\n `Local credentials mismatch with API for: ${fields.join(', ')}. Run \\`${CLI.LOGIN}\\` to re-authenticate.`,\n\n // Pre-GA gate (BEX-405). One message for every gated command, prompt choice and\n // flag value \u2014 see `src/lib/preview.ts` for why this one is shared while the\n // capability refusals each keep their own wording.\n //\n // Deliberately says nothing about the internal-account escape hatch or the env\n // var: an end user cannot use either, so naming them would only invite an attempt.\n // Both are documented where the people who need them will look \u2014 the agent docs\n // and the README.\n PREVIEW_FEATURE_UNAVAILABLE:\n 'That command is not available yet. It is part of a Brevo feature that has not been released.',\n\n // App create\n APP_CREATE_NAME_PROMPT: 'App name:',\n APP_CREATE_TYPE_PROMPT: 'Distribution type?',\n APP_CREATE_APP_TYPE_PROMPT: 'What type of app are you building?',\n APP_CREATE_APP_TYPE_OAUTH:\n 'OAuth app (Authorize against Brevo and call the API on a user\u2019s behalf)',\n APP_CREATE_APP_TYPE_UI: 'UI app (Render inside Brevo \u2014 opens your app from a record)',\n APP_CREATE_SUCCESS: 'App created.',\n APP_CREATE_NAME_TAKEN: 'That name is already taken. Try a different name.',\n // Shown only after every prompt has been answered \u2014 hence the reassurance:\n // the point of the re-login offer is that nothing typed so far is lost.\n APP_CREATE_SESSION_EXPIRED:\n 'Your session expired while you were answering. Your answers are still here.',\n APP_CREATE_RELOGIN_CONFIRM: 'Log in again and create the app?',\n // The platform refuses a public-app create from the CLI (BEX-355). Three short\n // labelled lines rather than one paragraph: the fix, the caveat that stops the\n // user thinking they can flip it later, and the server's own sentence.\n //\n // `Brevo said:` is deliberate \u2014 the raw text is quoted rather than swallowed, so\n // if the platform ever rewords the rejection (and `isPublicDistributionRefusal`\n // stops recognising it) nothing was hidden in the meantime. The labels align at\n // one column so the three lines scan as a list.\n APP_CREATE_PUBLIC_REJECTED: (serverMessage: string): string =>\n \"Public apps can't be created from the CLI yet \u2014 Brevo rejected this request.\\n\\n\" +\n ' Do this: re-run with `--distribution private`\\n' +\n ` Note: \\`distribution_type\\` is fixed at creation \u2014 \\`${CLI.APP_UPLOAD}\\` can't change it later\\n` +\n ` Brevo said: ${serverMessage}`,\n APP_CREATE_REDIRECT_PROMPT:\n 'OAuth callback URL \u2014 where users are sent after authorizing your app:',\n APP_CREATE_REDIRECT_HINT: (cmd: string) =>\n `Tip: The default below is a local test-server callback URL, used when you run \\`${cmd}\\`. Keep it to test your app locally, then add your production callback URL when you go live.`,\n APP_CREATE_REDIRECT_ANOTHER: 'Add another redirect URL?',\n APP_CREATE_REDIRECT_EMPTY: 'Redirect URL cannot be empty',\n APP_CREATE_REDIRECT_INVALID: 'Invalid format. Must start with http:// or https://',\n // Kept under 80 columns *including* inquirer's `? ` prefix. The example URL this used\n // to carry pushed it to 83, and inquirer wraps a prompt without indenting the\n // continuation, so `skip):` landed alone and flush-left on any 80-column terminal \u2014\n // which is the standard default, and this is now the second question in the flow.\n // The example lives in `APP_CREATE_LOGO_INVALID` instead, which is exactly when a\n // user needs to be shown the format.\n APP_CREATE_LOGO_PROMPT: 'App logo URL (optional \u2014 leave blank to skip):',\n APP_CREATE_LOGO_INVALID:\n 'Invalid format. Must be a valid https:// URL (e.g. https://example.com/logo.png).',\n APP_CREATE_PORT_IN_USE: (port: number, available: number) =>\n `Port ${port} is in use. Defaulting to port ${available}.`,\n APP_CREATE_PORT_SCAN_FAILED: (port: number) =>\n `Warning: Could not find a free port near ${port}. Defaulting to ${port} \u2014 it may conflict with a running process.`,\n APP_CREATE_LIMIT_REACHED:\n 'You have reached the maximum number of OAuth apps allowed for your account. To make room, delete an existing app: brevo app delete',\n APP_CREATE_BOX_TITLE: 'App created',\n APP_CREATE_BOX_SCOPES_LABEL: 'Default scopes:',\n APP_CREATE_BOX_SCOPE_HINT: `You can add more scopes later by editing \\`auth.scopes\\` in app-config.json and running \\`${CLI.APP_UPLOAD}\\`.`,\n // Names the feature while there is only one of them, because the picker that used\n // to name it is no longer asked (a list of one is not a question). With a second\n // feature in the manifest the label goes away and the picker returns \u2014 see\n // `promptFeatureType`.\n APP_SCAFFOLD_FEATURE_CONFIRM: (label?: string) =>\n label ? `Scaffold the ${label}?` : 'Do you want to scaffold a feature?',\n // `written` counts files actually put on disk, `total` the files the project has \u2014\n // and they differ whenever a merge kept what was already there. Reporting only\n // `written` printed \"created (0 files)\" directly above a five-file tree, which read\n // as a failure. See `scaffoldFileCount`, which both scaffold reports share.\n APP_CREATE_BASE_SUCCESS: (written: number, total: number) =>\n `Project structure ${scaffoldFileCount('created', written, total)}`,\n APP_CREATE_BASE_ONLY_NEXT: (cdDir?: string): string[] => [\n ...(cdDir ? [`1. cd ${cdDir}`] : []),\n `${cdDir ? 2 : 1}. ${CLI.APP_SCAFFOLD} (add a feature \u2014 e.g. the OAuth test server)`,\n ],\n APP_CREATE_JSON_SCAFFOLD_DIR_EXISTS: (dir: string) =>\n `Skipped scaffold: directory already exists (${dir}). cd into it and run \\`${CLI.APP_SCAFFOLD}\\` to add a feature.`,\n APP_CREATE_DIR_EXISTS_SKIPPED: (dir: string) =>\n `Skipped scaffolding: directory already exists (${dir}). cd into it and run \\`${CLI.APP_SCAFFOLD}\\` to add a feature.`,\n APP_CREATE_ALREADY_LINKED: (name: string) =>\n `App \"${name}\" is already linked in this directory (app-config.json found). Move to a different directory to create a new app, or run \\`${CLI.APP_SCAFFOLD}\\` here to add a feature to this project.`,\n APP_CREATE_DIR_UNRESOLVED: 'Could not resolve the output directory for scaffolding.',\n APP_CREATE_UI_NEXT: (cdDir?: string): string[] => [\n ...(cdDir ? [`1. cd ${cdDir}`] : []),\n `${cdDir ? 2 : 1}. ${CLI.APP_UPLOAD} (validate and save your configuration)`,\n `${cdDir ? 3 : 2}. ${CLI.APP_DEPLOY()} (make it available in an account)`,\n ],\n\n // App list\n APP_LIST_EMPTY: `No apps found. Create one with: ${CLI.APP_CREATE}`,\n // Not \"Your OAuth apps\" \u2014 the listing can contain UI apps too (BEX-290), and\n // each row names its own type.\n APP_LIST_HEADER: 'Your apps:',\n\n // App type, as named on a rendered row. The presence of the `ui_app` block is\n // the discriminator (see isUiAppRecord) \u2014 there is no app-type field.\n APP_TYPE_OAUTH: 'OAuth app',\n APP_TYPE_UI: 'UI app',\n\n // Raised instead of opening the app picker when there is no terminal to draw\n // it on. The picker writes its choice list to stdout, so under --json it\n // would corrupt the single-document contract, and off a TTY inquirer aborts\n // with a raw ERR_USE_AFTER_CLOSE readline stack instead of anything readable.\n APP_SELECT_NON_INTERACTIVE: (command: string) =>\n `Cannot show the app picker in non-interactive mode. Name the app instead:\\n\\n ${command}\\n\\n \\`${CLI.APP_LIST}\\` shows the IDs.`,\n\n // App credentials\n APP_CREDENTIALS_REVEAL_CONFIRM: 'Are you sure you want to reveal the client secret?',\n APP_CREDENTIALS_SELECT: 'Select an app:',\n CLIENT_SECRET_HIDDEN_HUMAN: `[hidden \u2014 run \\`${CLI.APP_CREDENTIALS_REVEAL()}\\`]`,\n CLIENT_SECRET_HIDDEN_JSON: '[hidden]',\n CLIENT_SECRET_NOT_AVAILABLE: '[not available]',\n APP_CREDENTIALS_CONFIG_BACKFILLED: (fields: string[]) =>\n `Backfilled ${fields.join(', ')} into app-config.json.`,\n\n // App update \u2014 removed (BEX-250), kept only as a signpost to `app upload`.\n //\n // Names every flag `update` used to take, because the reason a script or a\n // half-remembered habit lands here is usually one of them, and the answer is the\n // same for all of them: there is no flag any more, edit the file. `distribution_type`\n // is deliberately not offered as editable \u2014 it is immutable after `app create`, and\n // `APP_UPLOAD_DISTRIBUTION_IMMUTABLE` is what says so if anyone tries.\n // The second paragraph covers `update --app-id`, which had no successor until\n // `scaffold --app-id` was restored: `upload` reads the linked project only, so a\n // user who drove `update` by ID from any directory would otherwise read \"edit\n // app-config.json\" while having no such file and no way to get one.\n //\n // Layout: `logError` prints ` \u2717 ` in front of the first line, so the body is\n // indented 4 and the runnable commands 6 \u2014 the block hangs under the headline\n // instead of dangling two columns left of it, which is what a 2-space body does\n // to a message this long. The dead flags get their own line rather than sitting\n // mid-sentence: someone who arrived here by typing one is scanning for it, not\n // reading. Written a line per line (like `APP_CREATE_PUBLIC_REJECTED`) so the\n // wrapping is visible in the source \u2014 this is the longest message in the file\n // and the one where a stray long line is most likely to go unnoticed.\n APP_UPDATE_REMOVED:\n `\\`brevo app update\\` has been removed \u2014 use \\`${CLI.APP_UPLOAD}\\` instead.\\n\\n` +\n ` \\`${CLI.APP_UPLOAD}\\` pushes the whole of app-config.json and takes only\\n` +\n ' --yes and --json. None of the old edit flags exist any more:\\n\\n' +\n ' --name --redirect-uri --scope --logo-uri --app-id\\n\\n' +\n \" To change the app's name, redirect URLs, scopes or logo, edit\\n\" +\n ' app-config.json, then run:\\n\\n' +\n ` ${CLI.APP_UPLOAD}\\n\\n` +\n ' No app-config.json here? Set up a project for an app you already\\n' +\n ` have (\\`${CLI.APP_LIST}\\` shows their IDs), then edit and upload:\\n\\n` +\n ` ${CLI.APP_SCAFFOLD_APP_ID()}\\n` +\n ` ${CLI.APP_UPLOAD}\\n\\n` +\n ` Docs: ${BREVO_CLI_REFERENCE_URL}`,\n\n // App upload\n APP_UPLOAD_NO_CONFIG: `No app-config.json found in this directory. Run \\`${CLI.APP_UPLOAD}\\` from the project directory that has your app's app-config.json, or run \\`${CLI.APP_CREATE}\\` / \\`${CLI.APP_SCAFFOLD}\\` to set one up.`,\n APP_UPLOAD_INVALID_JSON: `app-config.json contains invalid JSON. Fix the file, or run \\`${CLI.APP_SCAFFOLD}\\` to regenerate it.`,\n APP_UPLOAD_MISSING_APP_ID: `app-config.json is missing \"appId\". Fix the file, or run \\`${CLI.APP_SCAFFOLD}\\` to regenerate it.`,\n APP_UPLOAD_NO_REDIRECT_URLS: 'app-config.json has no redirect URLs configured.',\n APP_UPLOAD_INVALID_REDIRECT_URL: (url: string) =>\n `Invalid redirect URL \"${url}\". Must be a valid http:// or https:// URL.`,\n APP_UPLOAD_INVALID_REDIRECT_PROTOCOL: (url: string) =>\n `Invalid redirect URL \"${url}\". Must use http:// or https://.`,\n APP_UPLOAD_SUMMARY: 'Upload summary:',\n APP_UPLOAD_CONFIRM: 'Proceed with upload?',\n APP_UPLOAD_CANCELLED: 'Upload cancelled.',\n APP_UPLOAD_SUCCESS: 'App uploaded.',\n APP_UPLOAD_UP_TO_DATE: (version: string) => `Already up to date at version ${version}.`,\n // UI apps have no OAuth callback, so the redirect-URL requirement is\n // OAuth-only \u2014 this message names the app type to make that explicit.\n APP_UPLOAD_NO_REDIRECT_URLS_OAUTH:\n 'app-config.json has no redirect URLs configured. OAuth apps need at least one \u2014 add it to `auth.redirectUris`.',\n APP_UPLOAD_UI_APP_SUMMARY: 'UI app:',\n // app-config.json does not carry link_target \u2014 upload injects it \u2014 so the diff row\n // says where the value comes from rather than implying there is a field to edit.\n APP_UPLOAD_UI_LINK_TARGET_NOTE: '(added on upload; not a field in app-config.json)',\n // Auth-shape mismatches are hard errors, not silent ignores \u2014 the CLI is the\n // only layer that will ever tell the partner (see validateAuthShape).\n APP_UPLOAD_UI_APP_AUTH_EMPTY_REQUIRED:\n 'This is a UI app (app-config.json has a `ui_app` block), so it uses no OAuth \u2014 set `auth` to `{}`.',\n APP_UPLOAD_UI_APP_AUTH_HAS_OAUTH_FIELDS:\n \"UI apps don't use OAuth \u2014 remove `scopes` and `redirectUris` from `auth` and keep it empty (`{}`).\",\n // Both verbs identify the calling account by its organization ID, which is only\n // cached by a successful login. Numeric and UUID values are both forwarded as-is;\n // only an absent or blank one lands here, meaning the credentials predate the field\n // or were written by a partial login \u2014 re-authenticating rewrites them.\n APP_DEPLOY_MISSING_CLIENT_ID: `Could not determine your Brevo account's organization ID.\\n\\n Run \\`${CLI.LOGIN}\\` to re-authenticate.`,\n APP_UPLOAD_DISTRIBUTION_IMMUTABLE: (current: string, next: string) =>\n `distribution_type cannot be changed via upload \u2014 this app is \"${current}\" on Brevo, but app-config.json says \"${next}\".\\n Edit \\`distribution_type\\` in app-config.json back to \"${current}\", or create a new ${next} app with \\`${CLI.APP_CREATE}\\`.`,\n LEGACY_ALL_SCOPE_START_BLOCK: `This app's auth.scopes in app-config.json still contains the legacy 'all' OAuth scope, which is being deprecated.\\n Replace 'all' with the specific scopes your integration uses (run \\`${CLI.APP_SCOPES}\\` to see the catalog),\\n migrate by editing \\`auth.scopes\\` and running \\`${CLI.APP_UPLOAD}\\`, then re-run \\`${CLI.APP_START('oauth')}\\`.`,\n LEGACY_ALL_SCOPE_LIST_TAG: ` (legacy 'all' \u2014 deprecated)`,\n LEGACY_ALL_SCOPE_SCAFFOLD_SUBSTITUTED: (writtenScopes: string): string =>\n `This app still has the legacy 'all' OAuth scope (deprecated). Wrote ${writtenScopes} to app-config.json instead of 'all'. Migrate the app by editing \\`auth.scopes\\` and running \\`${CLI.APP_UPLOAD}\\`.`,\n LEGACY_ALL_SCOPE_UPDATE_MIGRATING: `Migrating from legacy 'all' scope \u2014 'all' will be removed.`,\n // Deliberately in core rather than `preview-messages`: the legacy-scope deprecation\n // (BEX-214) is GA, and `app upload` \u2014 the only reader \u2014 ships in every build. It lived\n // in `preview-messages` between BEX-405 and this fix, which meant a public build read\n // the key as `undefined` and `new CliError(undefined)` rendered as a bare `\u2717` with no\n // text. The build now refuses that class of leak; see `scripts/build.mjs`.\n LEGACY_ALL_SCOPE_DEPRECATED_BLOCK: `This app currently has the legacy 'all' OAuth scope, which is being deprecated.\\n Replace 'all' with the specific scopes your integration uses in app-config.json's \\`auth.scopes\\`.\\n Run \\`${CLI.APP_SCOPES}\\` to see the catalog, then run \\`${CLI.APP_UPLOAD}\\` to migrate.`,\n\n // App delete\n APP_DELETE_SELECT: 'Select an app to delete:',\n APP_DELETE_CONFIRM: (name: string, id: string) =>\n `Delete app \"${name}\" (${id})? This cannot be undone.`,\n APP_DELETE_SUCCESS: (id: string) => `App ${id} deleted.`,\n APP_DELETE_CANCELLED: 'Delete cancelled.',\n APP_DELETE_FOLDER_CONFIRM: (dir: string) => `Delete the local project folder at ${dir}?`,\n APP_DELETE_FOLDER_SUCCESS: (dir: string) => `Project folder deleted: ${dir}`,\n APP_DELETE_FOLDER_FAILED: (dir: string) => `Could not delete folder ${dir}. Remove it manually.`,\n\n // App scaffold\n APP_SCAFFOLD_DIR_PROMPT: 'Output directory:',\n APP_SCAFFOLD_DIR_EXISTS: 'Directory already exists. What would you like to do?',\n APP_SCAFFOLD_FEATURE_TYPE_PROMPT: 'What feature do you want to scaffold?',\n APP_SCAFFOLD_FEATURE_EXISTS:\n 'This feature already has files in this project. What would you like to do?',\n APP_SCAFFOLD_FEATURE_EXISTS_OVERWRITE: 'Overwrite existing files',\n APP_SCAFFOLD_FEATURE_EXISTS_MERGE: 'Merge (keep existing, add missing)',\n APP_SCAFFOLD_FEATURE_EXISTS_CANCEL: 'Cancel',\n // Three ways out, in the order they're likely to apply: the user is in the wrong\n // directory, the user has an app but no project for it, or the user has no app.\n // The middle one is the migration path off `brevo app update --app-id` and is the\n // reason `--app-id` exists on this command at all \u2014 leaving it out of this message\n // strands exactly the users who need it, since it appears in no help screen they\n // would think to read.\n APP_SCAFFOLD_NO_CONFIG: `No app-config.json found in this directory, so there is no app to scaffold a feature into.\\n\\n - cd into an existing project folder and try again, or\\n - run \\`${CLI.APP_SCAFFOLD_APP_ID()}\\` to set this directory up for an app you already have (\\`${CLI.APP_LIST}\\` shows their IDs), or\\n - run \\`${CLI.APP_CREATE}\\` to create a new app here.`,\n // Refuses rather than prompting: the two configs describe different apps, and every\n // resolution (overwrite, merge, pick another directory) is a decision the user is\n // better placed to make in their own shell than through a prompt that has to\n // summarise what would be lost.\n APP_SCAFFOLD_APP_ID_MISMATCH: (localAppId: string, requestedAppId: string) =>\n `This directory is already linked to app ${localAppId}, so it can't be set up for app ${requestedAppId}.\\n\\n Run \\`${CLI.APP_SCAFFOLD}\\` (no --app-id) to work on app ${localAppId}, or cd into an empty directory and run \\`${CLI.APP_SCAFFOLD_APP_ID(requestedAppId)}\\` there.`,\n // The same refusal for the directory a bootstrap was *pointed at*, which is a\n // different directory from the one the command ran in \u2014 hence its own message\n // rather than a reuse of the one above, whose \"This directory\" would name the\n // wrong place. Reached when the answer to \"Output directory:\" turns out to hold\n // another app's project: without this, answering \"Merge\" there would leave that\n // app's app-config.json in place while writing this app's credentials into\n // src/oauth/.env.local beside it.\n APP_SCAFFOLD_TARGET_LINKED_ELSEWHERE: (\n targetDir: string,\n localAppId: string,\n requestedAppId: string,\n ) =>\n `${targetDir} is already a project for app ${localAppId}, so it can't be set up for app ${requestedAppId}.\\n\\n - cd ${targetDir} and run \\`${CLI.APP_SCAFFOLD}\\` to work on app ${localAppId}, or\\n - run \\`${CLI.APP_SCAFFOLD}\\` again and choose a different directory for app ${requestedAppId}.`,\n // Deliberately doesn't say *which* directory: interactively the next prompt is\n // \"Output directory:\", so naming one here would promise a location the user is\n // about to be asked to choose. Under --json/non-TTY there is no prompt and the\n // answer is the current directory, but that run prints no messages either.\n APP_SCAFFOLD_BOOTSTRAP_INTRO: (appId: string) => `Setting up a project for app ${appId}...`,\n // Said before the confirm, not merged into it: the picker that follows lists the\n // account's apps, and a user who typed `scaffold` in the wrong directory needs to\n // know why they are suddenly being shown that list. The confirm is what makes the\n // list opt-in rather than something the command drops them into.\n APP_SCAFFOLD_BOOTSTRAP_OFFER:\n 'No app-config.json in this directory, so there is no app to add a feature to.',\n APP_SCAFFOLD_BOOTSTRAP_CONFIRM: 'Set up a project for an app you already have?',\n // Declining is a normal answer, not a failure \u2014 but the user still has an empty\n // directory, so the two remaining routes go on screen instead of exiting silently.\n APP_SCAFFOLD_BOOTSTRAP_DECLINED: `Nothing to do here yet.\\n\\n - run \\`${CLI.APP_CREATE}\\` to create a new app in this directory, or\\n - cd into an existing project folder and run \\`${CLI.APP_SCAFFOLD}\\` there.`,\n APP_SCAFFOLD_SELECT: 'Which app do you want to set a project up for?',\n // Refuses rather than bootstrapping a nested project. `readProjectConfig` reads cwd and\n // does not walk up, so this is the only thing standing between a mistyped `cd` and a\n // second app-config.json inside an existing project \u2014 after which `app upload` from that\n // directory pushes the wrong app and says nothing.\n APP_SCAFFOLD_INSIDE_PROJECT: (projectDir: string) =>\n `This directory is inside the Brevo app project at ${projectDir}, so setting it up as a second project would nest one inside the other.\\n\\n - cd ${projectDir} and run \\`${CLI.APP_SCAFFOLD}\\` there to work on that app, or\\n - cd to a directory outside it to set up a different app.`,\n // A UI app's whole configuration is its `ui_app` block, so a record that comes back\n // without one has nothing to bootstrap from. Says so explicitly rather than writing a\n // config without the block: that config would read as a valid OAuth app (the block's\n // presence is the type discriminator), and the next upload would push `auth` where\n // `ui_app` belonged.\n //\n // This is an EDGE CASE, not the ordinary post-create state. An earlier version of this\n // message asserted the platform stores the block \"only once you run `app upload`\" \u2014\n // that is false, and was corrected after reading app-store-bo-be: the CLI create\n // handler's `persistCreateResultTx` inserts an `app_versions` row at version `0.0.1`\n // *inside the create transaction*, with `Snapshot.UIApp` set from the request's block\n // (`http_cli_create_app.go`), and `GET /cli/apps/{id}` serves the block straight back\n // off the latest snapshot (`http_cli_get_app.go`). So a UI app created through this\n // CLI is recoverable immediately, with no upload. What reaches this message is a\n // record whose snapshot genuinely carries no UI block \u2014 an app predating that handler,\n // or one created through another path \u2014 which is why the copy no longer explains the\n // absence by blaming a missing upload.\n APP_SCAFFOLD_BOOTSTRAP_UNRECOVERABLE: (appId: string) =>\n `App ${appId} is a UI app, but the platform returned no \\`ui_app\\` configuration for it, so there is nothing to set this directory up from.\\n\\n A UI app's configuration lives in the \\`ui_app\\` block of app-config.json. Normally the platform holds a copy from the moment the app is created, so this usually means the app was created outside this CLI, or before the platform stored the block.\\n\\n - If you still have this app's project folder, cd into it and run \\`${CLI.APP_UPLOAD}\\` from there, or\\n - run \\`${CLI.APP_CREATE}\\` and choose \"UI app\" to author the configuration again.`,\n APP_SCAFFOLD_DIFF_INTRO: (name: string) =>\n `App \"${name}\" is linked here, but its local config differs from the server:`,\n APP_SCAFFOLD_DIFF_LINE: (field: string, local: string, server: string) =>\n ` ${field}: ${local} \u2192 ${server}`,\n APP_SCAFFOLD_DIFF_CONFIRM:\n 'Scaffolding will update app-config.json to match the server. Continue?',\n APP_SCAFFOLD_CANCELLED: 'Scaffold cancelled.',\n // The no-drift outcome of a bootstrap pointed at a directory that was already a\n // project. Said rather than silently skipped: a bootstrap normally reports the files\n // it wrote, so writing none needs a reason on screen \u2014 otherwise it looks like the\n // same \"did nothing\" this branch exists to stop being silent about.\n APP_SCAFFOLD_BASE_IN_SYNC: 'app-config.json already matches the server \u2014 left as it is.',\n APP_SCAFFOLD_JSON_DIFF_CANCELLED:\n 'app-config.json differs from the server and --json cannot prompt for confirmation. Re-run without --json to review and confirm the update.',\n // Shown when `app create`'s read-back of the app it just created comes back\n // empty or 404. The app exists \u2014 the server issued its ID \u2014 so this is a\n // read-path problem, not a missing app, and the create response has everything\n // the scaffold needs except `scopes` (which falls back to the defaults).\n APP_SCAFFOLD_SERVER_READBACK_FAILED: (appId: string) =>\n `App ${appId} was created but could not be read back from the server. Scaffolding from the create response instead \u2014 run \\`${CLI.APP_SCAFFOLD}\\` later to refresh app-config.json from the server.`,\n APP_SCAFFOLD_SUCCESS: (written: number, total: number) =>\n `Feature ${scaffoldFileCount('scaffolded', written, total)}`,\n APP_SCAFFOLD_NO_FEATURES_FOR_UI_APP: `This is a UI app \u2014 there are no features to scaffold (an action link has no local server to run). Edit the \\`ui_app\\` block in app-config.json, then run \\`${CLI.APP_UPLOAD}\\` and \\`${CLI.APP_DEPLOY()}\\`.`,\n APP_SCAFFOLD_TARGET_IS_CWD: 'Scaffolding into the current directory.',\n APP_SCAFFOLD_CREATING_DIR: (dir: string) => `Creating ${dir} and moving into it...`,\n // The directory was already there and the user has just said how to handle it \u2014\n // reporting \"Creating\" then would contradict the prompt they answered one line up.\n APP_SCAFFOLD_USING_EXISTING_DIR: (dir: string) => `Moving into ${dir}...`,\n APP_SCAFFOLD_NEXT_STEPS_TITLE: 'Next steps',\n // `cdDir` is the path (relative to the shell the user actually typed the\n // command in) they need to `cd` into. It's undefined when scaffolding\n // landed in that same directory, since process.chdir() inside the CLI\n // only moves the CLI's own process, never the user's shell.\n APP_SCAFFOLD_NEXT_STEPS_LINES: (cdDir?: string) => [\n ...(cdDir ? [`1. cd ${cdDir}`] : []),\n `${cdDir ? 2 : 1}. yarn --cwd src/oauth`,\n ` (or: npm --prefix src/oauth install)`,\n `${cdDir ? 3 : 2}. ${CLI.APP_START('oauth')}`,\n ],\n APP_SCAFFOLD_SCOPES_TIP: `Tip: list available scopes with \\`${CLI.APP_SCOPES}\\`. Update scopes by editing \\`auth.scopes\\` in app-config.json and running \\`${CLI.APP_UPLOAD}\\`.`,\n\n // App start\n APP_START_FEATURE_NOT_FOUND: (entryFile: string) =>\n `Feature entry file not found.\\n\\n Expected: ${entryFile}\\n Current directory: ${process.cwd()}\\n\\n Make sure you are inside your scaffolded project directory.\\n Run \\`cd <your-project-folder>\\` and try again.`,\n APP_START_NO_DEPS: (featureDir: string) =>\n `Dependencies not installed. Run \\`yarn --cwd ${featureDir}\\` (or \\`npm --prefix ${featureDir} install\\`) first.`,\n APP_START_STOPPED: 'Process stopped.',\n APP_START_MISSING_FEATURE: (available: string) =>\n `Missing feature name.\\n\\nAvailable features:\\n${available}\\n\\nUsage: ${CLI.APP_START()}`,\n APP_START_UNKNOWN_FEATURE: (feature: string, available: string) =>\n `Unknown feature \"${feature}\". Available features: ${available}`,\n APP_START_PORT_IN_USE: (port: number) =>\n `Port ${port} is already in use.\\n\\n Either stop the process using port ${port}, use a different port with \\`--port <port>\\`,\\n or update your redirect URL by editing \\`auth.redirectUris\\` in app-config.json and running \\`${CLI.APP_UPLOAD}\\`.`,\n APP_START_CUSTOM_PORT_IN_USE: (port: number) =>\n `Port ${port} is already in use.\\n\\n Stop the process using port ${port}, or pick another port with \\`--port <port>\\`\\n and update your redirect URL by editing \\`auth.redirectUris\\` in app-config.json and running \\`${CLI.APP_UPLOAD}\\`.`,\n APP_START_EXITED: (feature: string, code: number) => `${feature} exited with code ${code}`,\n APP_START_FAILED: (feature: string, error: string) => `Failed to start ${feature}: ${error}`,\n APP_START_REDIRECT_NOT_REGISTERED: (port: number) =>\n `Port ${port} isn't registered as a redirect URL for this app.`,\n APP_START_REDIRECT_REGISTER_PROMPT: (url: string) =>\n `Register ${url}? You can delete it later if you want.`,\n APP_START_REDIRECT_REGISTERED: (url: string) =>\n `Added ${url} to app-config.json and uploaded the new config.`,\n APP_START_REDIRECT_UPLOAD_FAILED: (url: string) =>\n `${url} was saved to app-config.json but the upload failed. Fix the issue and run \\`${CLI.APP_UPLOAD}\\` to finish registering it.`,\n APP_START_REDIRECT_DECLINED: (url: string) =>\n `Continuing without registering. The OAuth callback at ${url} will fail until you register it. Add it to \\`auth.redirectUris\\` in app-config.json and run \\`${CLI.APP_UPLOAD}\\` to register later.`,\n APP_START_REDIRECT_NON_INTERACTIVE: (port: number, url: string) =>\n `Port ${port} is not registered as a redirect URL for this app, and we can't prompt in non-interactive mode. Add \\`${url}\\` to \\`auth.redirectUris\\` in app-config.json and run \\`${CLI.APP_UPLOAD}\\` first, or re-run interactively.`,\n\n AUTH_LOGOUT_NON_INTERACTIVE:\n 'Cannot prompt for confirmation in non-interactive mode. Use --force to skip.',\n\n // Errors\n ERR_NETWORK: 'Cannot reach Brevo API.',\n ERR_RATE_LIMITED: (retryAfter: number) => `Rate limited. Retrying in ${retryAfter} seconds...`,\n ERR_REGISTRY: 'Operation failed due to a registry error. Please try again.',\n // Replaces the platform's `ui_app is not enabled for this account` (403,\n // `ui_app_not_enabled`) \u2014 a wire key is not something a partner can act on. Mapped\n // by API *code* rather than by copy, which is why this one does not quote the\n // server back the way `APP_CREATE_PUBLIC_REJECTED` does: the code is the stable\n // identifier, so there is no mismatch to leave an escape valve for, and the\n // server's sentence says nothing this line doesn't.\n //\n // Keyed centrally in `apiCodeMessages` because the same gate guards two commands:\n // `app create` (authoring a `ui_app` block) and `app upload` (pushing one). The\n // allowance is per account and shares the public-apps flag, so an enabled account\n // never sees this. `--json` consumers still get `code: \"ui_app_not_enabled\"`.\n ERR_UI_APP_NOT_ENABLED:\n \"UI apps aren't enabled for this Brevo account yet.\\n\\n\" +\n ' Why: UI apps (action links) are still rolling out, and are enabled per account.\\n' +\n ' Do this: build an OAuth app instead, or ask Brevo to enable UI apps for this account.',\n ERR_AUTH_GATEWAY:\n 'API is behind an authentication gateway (e.g. Cloudflare Access). Sign in via your browser first, or check your API base URL.',\n\n // Security warnings\n TLS_VERIFICATION_DISABLED:\n 'TLS certificate verification is disabled (NODE_TLS_REJECT_UNAUTHORIZED=0). This is insecure \u2014 API keys and tokens can be intercepted on the network.',\n\n // Init\n INIT_WELCOME: 'Brevo CLI \u2014 Quick Setup',\n INIT_ALREADY_LOGGED_IN: 'Already authenticated.',\n INIT_VERIFY_UNAVAILABLE:\n \"Couldn't verify your credentials right now \u2014 continuing with the stored session.\",\n INIT_STEP_LOGIN: ' Step 1: Authenticate with your Brevo account',\n INIT_STEP_CREATE: ' Step 2: Create your first app',\n INIT_APPS_EXIST: (count: number) => `You have ${count} app${count === 1 ? '' : 's'} already.`,\n INIT_APP_LINKED: (name: string) => `App \"${name}\" is linked to this project (app-config.json).`,\n INIT_APP_ACTION: 'What would you like to do?',\n INIT_DONE: `All set! Run \\`${CLI.APP_START('oauth')}\\` to test your OAuth flow, or \\`${CLI.HELP}\\` to see all commands.`,\n // `init` ends by naming the obvious next command, and for a UI app that is not\n // `app start oauth`: an action link has no OAuth flow and no local server to run,\n // so the OAuth line pointed at a command that would fail. It also contradicted the\n // Next steps box printed directly above it, which already said upload \u2192 deploy.\n // Deliberately in core rather than `preview-messages`: `init` is not a gated command,\n // and a hand-edited `ui_app` block can reach this line in a published build.\n INIT_DONE_UI_APP: `All set! Follow the next steps above, or run \\`${CLI.HELP}\\` to see all commands.`,\n\n // Skill\n SKILL_INSTALL_SUCCESS: (name: string, version: string, dir: string) =>\n `Installed ${name}@${version} \u2192 ${dir}`,\n SKILL_INSTALL_CLAUDE_ONLY:\n 'This skill is consumed by Claude Code. Other AI tools (Claude Desktop chat, Cursor, Copilot CLI, Gemini, etc.) should reference agent-context/AGENTS.md from the @getbrevo/cli npm package instead.',\n SKILL_INSTALL_ALREADY: (name: string, version: string) =>\n `${name}@${version} is already up to date.`,\n SKILL_UNINSTALL_SUCCESS: (name: string, dir: string) => `Uninstalled ${name} from ${dir}`,\n SKILL_UNINSTALL_NONE: 'No Brevo skills installed.',\n SKILL_AUTOREFRESHED: (name: string, oldVer: string, newVer: string) =>\n `\u21BB refreshed ${name} skill (v${oldVer} \u2192 v${newVer})`,\n SKILL_AUTOREFRESH_FAILED: (name: string, err: string) =>\n `\u26A0 failed to refresh ${name} skill: ${err}`,\n\n // App scopes\n APP_SCOPES_EMPTY: 'The IdP returned an empty scope list.',\n APP_SCOPES_USAGE_HINT: `Add a scope to an app by editing \\`auth.scopes\\` in app-config.json and running \\`${CLI.APP_UPLOAD}\\`.`,\n APP_SCOPES_DOCS_HINT: `Full CLI reference: ${BREVO_CLI_REFERENCE_URL}`,\n APP_SCOPES_CATALOG_DOCS_HINT: `Scope catalog docs: ${BREVO_OAUTH_SCOPES_DOCS_URL}`,\n APP_SCOPES_WEB_LISTENING: (url: string): string => `Open in browser: ${url} (Ctrl+C to stop)`,\n APP_SCOPES_WEB_TITLE: 'Brevo OAuth scopes',\n APP_SCOPES_WEB_INTRO: (count: number, sourceUrl: string): string =>\n `${count} scope${count === 1 ? '' : 's'} from ${sourceUrl}`,\n APP_SCOPES_WEB_SEARCH_PLACEHOLDER: 'Filter scopes\u2026',\n APP_SCOPES_WEB_EMPTY: 'The IdP returned an empty scope list.',\n APP_SCOPES_WEB_FOOTER: 'Served locally by the Brevo CLI. Press Ctrl+C in the terminal to stop.',\n APP_SCOPES_WEB_REFRESH: 'Refresh',\n APP_SCOPES_WEB_REFRESHING: 'Refreshing\u2026',\n APP_SCOPES_WEB_REFRESH_FAILED: `Refresh failed. Please restart \\`${CLI.APP_SCOPES} --web\\` to retry.`,\n APP_SCOPES_WEB_ENDPOINTS_LABEL: 'API endpoints',\n APP_SCOPES_WEB_NO_ENDPOINTS: 'No API endpoints listed for this scope.',\n APP_SCOPES_WEB_COPY: 'Copy',\n APP_SCOPES_WEB_COPIED: 'Copied!',\n // {category} / {scope} are replaced client-side in the web page script.\n APP_SCOPES_WEB_COPY_CATEGORY_ARIA: 'Copy {category} scopes',\n APP_SCOPES_WEB_SELECT_SCOPE_ARIA: 'Select {scope}',\n APP_SCOPES_WEB_COPY_SELECTED: 'Copy selected',\n APP_SCOPES_WEB_SELECTED_PLACEHOLDER: `Tick scopes to build a comma-separated list for app-config.json's \\`auth.scopes\\``,\n APP_SCOPES_WEB_LEGACY_BADGE: 'deprecated',\n APP_SCOPES_WEB_LEGACY_TITLE: `Legacy 'all' scope \u2014 replace with the granular scopes your integration uses.`,\n APP_SCOPES_WEB_DOCS_LINK: 'Full CLI reference',\n APP_SCOPES_WEB_CATALOG_DOCS_CTA: 'Read the scope catalog docs',\n OAUTH_METADATA_MISSING_SCOPES: 'IdP scopes response did not include a scopes array.',\n OAUTH_METADATA_FETCH_FAILED: (url: string, status: number): string =>\n `Failed to fetch OAuth scopes from ${url} (HTTP ${status}).`,\n\n // General\n ABORTED: 'Aborted.',\n} as const;\n\n// ELIMINATION SITE \u2014 the raw global rather than `isFeatureAvailable`, so esbuild folds\n// the spread to `{}` and drops ./preview-messages entirely. `messages` is one object\n// literal, so a property can only be removed by removing the whole object it arrived in.\n// The `as typeof previewMessages` cast keeps every call site type-safe in both builds;\n// see that module for why the lie is safe.\nexport const messages = {\n ...coreMessages,\n ...(__BREVO_PREVIEW__ ? previewMessages : ({} as typeof previewMessages)),\n};\n", "/**\n * Commands that no longer exist, kept registered only to say so.\n *\n * A removed command is not the same as a command that never existed. Commander answers\n * a name it doesn't know with `unknown command 'update'` plus a \"did you mean\" guess\n * drawn from string distance \u2014 for `app update` that guess was `create`, which is both\n * wrong (the replacement is `upload`) and expensive to act on. Registering the dead name\n * back lets the CLI name its replacement instead.\n *\n * Two consumers read this table, which is why it is a table and not a special case in\n * either of them:\n *\n * - `lib/command-registry.ts` registers each entry as a hidden command whose only\n * action is to throw its message.\n * - `lib/auth-guard.ts` exempts them from the credential check. A removed command's\n * whole job is to explain itself, and answering `Not authenticated` to someone whose\n * actual problem is that the command is gone sends them to `brevo login` for nothing.\n *\n * Entries are cheap to keep and should outlive the release that removed the command by\n * a good margin \u2014 the users who need one are precisely the ones who haven't read the\n * changelog. Drop an entry only when the name is being reused for something else.\n */\nimport { messages } from '../lang/en';\n\nexport interface RemovedCommand {\n /**\n * The subcommand group it lived under (`app update` \u2192 `'app'`), or `undefined` for a\n * removed top-level command.\n */\n group?: string;\n name: string;\n /** What the user gets instead \u2014 in `lang/en.ts`, like every other user-facing string. */\n message: string;\n}\n\nexport const REMOVED_COMMANDS: readonly RemovedCommand[] = [\n // BEX-250. Replaced by `brevo app upload`, with no shim and no flag-for-flag\n // equivalent \u2014 see `messages.APP_UPDATE_REMOVED`.\n { group: 'app', name: 'update', message: messages.APP_UPDATE_REMOVED },\n];\n\n/** The removed commands belonging to a group, or the top-level ones when called bare. */\nexport function removedCommandsIn(group?: string): RemovedCommand[] {\n return REMOVED_COMMANDS.filter((c) => c.group === group);\n}\n\n/**\n * Is this invocation one of the removed commands?\n *\n * `parentName` is whatever Commander reports as the parent, which for a top-level\n * command is the program itself (`brevo`) rather than `undefined` \u2014 so an entry with no\n * `group` matches on name alone and a grouped one has to match the group too.\n */\nexport function isRemovedCommand(name: string, parentName?: string): boolean {\n return REMOVED_COMMANDS.some(\n (c) => c.name === name && (c.group === undefined || c.group === parentName),\n );\n}\n", "import { Command } from 'commander';\nimport { isAuthenticated } from './config';\nimport { CLI } from './constants';\nimport { CliError } from './errors';\nimport { isRemovedCommand } from './removed-commands';\n\n// `available-scopes` only fetches the public IdP scope catalog (no Brevo API\n// key involved), so it works before `brevo login`.\nconst UNAUTHENTICATED_COMMANDS = new Set([\n 'login',\n 'help',\n 'init',\n 'whoami',\n 'logout',\n 'available-scopes',\n]);\n// Subcommand groups whose entire subtree is local-only and never needs auth.\n// Skill management touches files under ~/.claude/ \u2014 there is nothing to call\n// against the Brevo API.\nconst UNAUTHENTICATED_GROUPS = new Set(['skill:cli']);\n\n/**\n * True when the invocation being dispatched needs stored credentials.\n *\n * Shared by the auth guard and the proactive OAuth refresh hook\n * (`lib/oauth-freshness.ts`) so the two can never gate on different command\n * sets. Arguments match the `preAction` hook signature: `thisCommand` is the\n * command the hook was registered on (the root program), `actionCommand` the\n * one about to run.\n */\nexport function commandRequiresAuth(thisCommand: Command, actionCommand: Command): boolean {\n const commandName = actionCommand.name();\n const parentName = actionCommand.parent?.name();\n\n // The root program's default action handles \"no args\" (help) and\n // \"unknown command\" (error). Skipping the auth guard there ensures\n // unknown commands surface as \"unknown command\" instead of being\n // intercepted by an auth-required check.\n if (actionCommand === thisCommand || commandName === thisCommand.name()) {\n return false;\n }\n\n // A removed command (`app update`) exists only to point at its replacement \u2014 it\n // touches nothing server-side. Guarding it would answer \"Not authenticated\" to a\n // user whose actual problem is that the command is gone, sending them through\n // `brevo login` to reach the same message.\n if (isRemovedCommand(commandName, parentName)) {\n return false;\n }\n\n // Allow login, help, and version through without auth\n return !(\n UNAUTHENTICATED_COMMANDS.has(commandName) ||\n (parentName && UNAUTHENTICATED_GROUPS.has(parentName)) ||\n process.argv.includes('--help') ||\n process.argv.includes('-h') ||\n process.argv.includes('--version') ||\n process.argv.includes('-V') ||\n process.argv.length <= 2\n );\n}\n\nexport function installAuthGuard(program: Command): void {\n program.hook('preAction', (thisCommand, actionCommand) => {\n if (!commandRequiresAuth(thisCommand, actionCommand)) {\n return;\n }\n\n if (!isAuthenticated()) {\n throw new CliError(`Not authenticated. Run: ${CLI.LOGIN}`);\n }\n });\n}\n", "import { Command } from 'commander';\nimport { commandRequiresAuth } from './auth-guard';\nimport { AuthCred, OauthTokensToStore } from './config';\nimport { AuthExpiredError } from './errors';\n\ntype OauthCred = Extract<AuthCred, { kind: 'oauth' }>;\n\n// Refresh this far ahead of `expiresAt`. Covers clock skew between the CLI host\n// and the IdP plus the round-trip of the request the token is about to be used\n// for, so a token that would expire mid-flight is replaced first.\nexport const OAUTH_REFRESH_SKEW_MS = 60_000;\n\nexport interface OauthFreshnessDeps {\n getAuthCred: () => AuthCred | undefined;\n refresh: (refreshToken: string) => Promise<OauthTokensToStore>;\n persist: (tokens: OauthTokensToStore) => void;\n now?: () => number;\n skewMs?: number;\n onError?: (err: unknown) => void;\n /**\n * Classifies a refresh failure: true when the refresh *token* was refused\n * rather than the request merely failing to land. Injected instead of\n * imported because the only thing that can answer it \u2014 `RefreshError` \u2014 lives\n * in `services/`, and `lib/` never imports `services/`. `bin/index.ts`\n * supplies the real predicate.\n *\n * Omitted, every failure counts as transient, which is exactly the\n * best-effort behaviour this module shipped with.\n */\n isTerminal?: (err: unknown) => boolean;\n /**\n * Runs once, immediately before the `AuthExpiredError`, to clear the dead\n * credentials. Separate from the throw so this module doesn't have to know\n * where credentials live.\n */\n onTerminal?: () => void;\n}\n\n// True when `auth` is an OAuth credential whose access token is expired or\n// about to be. Doubles as a type guard so callers get the narrowed credential\n// (and its `refreshToken`) without a cast.\nexport function shouldRefreshOauth(\n auth: AuthCred | undefined,\n now: number,\n skewMs: number = OAUTH_REFRESH_SKEW_MS,\n): auth is OauthCred {\n if (auth?.kind !== 'oauth') return false;\n if (!auth.refreshToken) return false;\n if (!Number.isFinite(auth.expiresAt)) return false;\n return auth.expiresAt - skewMs <= now;\n}\n\n/**\n * Proactively swap a near-expiry OAuth access token for a fresh one.\n *\n * Best-effort for a failure that says nothing about the session \u2014 a network\n * blip, a 5xx, an unwritable credentials file. Those are handed to `onError`\n * for debug logging and swallowed, so they never block the command the user\n * actually asked for.\n *\n * **A refused refresh token is not one of those.** When `isTerminal` recognises\n * the failure, the session is over and no later request can rescue it, so this\n * clears the credentials and throws `AuthExpiredError` at the caller. As a\n * `preAction` hook that lands before the command body \u2014 i.e. before a long\n * interactive flow asks its first question, rather than after its last. Without\n * it `brevo app create` collected six answers, sent them, and only then\n * discovered the session had already been unusable when it started.\n *\n * The reactive `onAuthFailure` handler in `bin/index.ts` still covers the case\n * this one cannot see: a token the server rejects while the stored `expiresAt`\n * still looks fresh.\n *\n * Returns true only when a new token was fetched and persisted.\n */\nexport async function ensureFreshOauthToken(deps: OauthFreshnessDeps): Promise<boolean> {\n const auth = deps.getAuthCred();\n const now = deps.now ? deps.now() : Date.now();\n\n if (!shouldRefreshOauth(auth, now, deps.skewMs)) return false;\n\n try {\n deps.persist(await deps.refresh(auth.refreshToken));\n return true;\n } catch (err) {\n if (deps.isTerminal?.(err)) {\n deps.onTerminal?.();\n throw new AuthExpiredError();\n }\n deps.onError?.(err);\n return false;\n }\n}\n\n// Registers the proactive refresh as a `preAction` hook, gated to exactly the\n// commands the auth guard protects \u2014 local-only commands (`login`, `logout`,\n// `skill:cli \u2026`) must keep working offline, so they never trigger a network\n// call. Register after `installAuthGuard` so an unauthenticated invocation\n// still fails fast on the guard.\n//\n// A terminal failure propagates out of the hook, which Commander turns into a\n// rejected `parseAsync` \u2014 the command body never runs. That is the point: the\n// message has to beat the first prompt.\nexport function installProactiveOauthRefresh(program: Command, deps: OauthFreshnessDeps): void {\n program.hook('preAction', async (thisCommand, actionCommand) => {\n if (!commandRequiresAuth(thisCommand, actionCommand)) return;\n await ensureFreshOauthToken(deps);\n });\n}\n", "const isDebug = (): boolean => process.env.BREVO_DEBUG === '1' || process.argv.includes('--debug');\n\nconst isTTY = (): boolean => process.stdout.isTTY === true;\n\nconst useColor = (): boolean =>\n process.env.NO_COLOR === undefined && (process.env.FORCE_COLOR !== undefined || isTTY());\n\n// Exported so other renderers (e.g. the update banner) colour text through the\n// same NO_COLOR / FORCE_COLOR / TTY rules rather than emitting raw escapes.\nexport function color(code: string, text: string): string {\n return useColor() ? `\\x1b[${code}m${text}\\x1b[0m` : text;\n}\n\nexport const COLOR_RED = '31';\n\nconst SENSITIVE_KEYS = new Set([\n 'api-key',\n 'api_key',\n 'apikey',\n 'access_token',\n 'refresh_token',\n 'client_secret',\n 'token',\n 'password',\n 'secret',\n 'authorization',\n]);\n\nfunction redactSensitiveFields(data: unknown): unknown {\n if (data === null || data === undefined) return data;\n if (typeof data !== 'object') return data;\n if (Array.isArray(data)) return data.map(redactSensitiveFields);\n\n const redacted: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n if (SENSITIVE_KEYS.has(key.toLowerCase())) {\n redacted[key] = '[REDACTED]';\n } else if (typeof value === 'object' && value !== null) {\n redacted[key] = redactSensitiveFields(value);\n } else {\n redacted[key] = value;\n }\n }\n return redacted;\n}\n\nexport function logHttp(method: string, path: string): void {\n if (isDebug()) {\n const line = `\u2192 ${method} ${path}`;\n process.stderr.write(` ${color('90', line)}\\n`);\n }\n}\n\nexport function logHttpResponse(status: number, path: string): void {\n if (isDebug()) {\n const code = status >= 200 && status < 300 ? '32' : '31';\n const line = `\u2190 ${status} ${path}`;\n process.stderr.write(` ${color(code, line)}\\n`);\n }\n}\n\nexport function logDebug(context: string, data: unknown): void {\n if (isDebug()) {\n const safe = redactSensitiveFields(data);\n const line = `[debug] ${context}: ${JSON.stringify(safe)}`;\n process.stderr.write(` ${color('90', line)}\\n`);\n }\n}\n\nfunction formatError(error: unknown): string {\n if (error instanceof Error) return error.stack ?? error.message;\n if (typeof error === 'string') return error;\n if (\n typeof error === 'number' ||\n typeof error === 'boolean' ||\n typeof error === 'bigint' ||\n typeof error === 'symbol'\n ) {\n return String(error);\n }\n try {\n // JSON.stringify returns undefined for bare `undefined` and functions.\n return JSON.stringify(error) ?? 'undefined';\n } catch {\n // Circular structures make JSON.stringify throw.\n return '[unserializable error]';\n }\n}\n\nexport function logError(message: string, error?: unknown): void {\n process.stderr.write(`\\n ${color('31', '\u2717')} ${message}\\n`);\n if (isDebug() && error) {\n process.stderr.write(` ${color('90', formatError(error))}\\n`);\n } else if (error) {\n process.stderr.write(` ${color('90', 'Run with --debug for full details')}\\n`);\n }\n}\n\nexport function logSuccess(message: string): void {\n process.stdout.write(`\\n ${color('32', '\u2713')} ${message}\\n`);\n}\n\nexport function logInfo(message: string): void {\n process.stdout.write(` ${message}\\n`);\n}\n\nexport function logWarn(message: string): void {\n process.stdout.write(` ${color('33', '\u26A0')} ${message}\\n`);\n}\n\n// `color` is not re-exported here \u2014 it is exported at its declaration above, so\n// listing it again is a redeclaration.\nexport { isDebug, isTTY, useColor };\n", "import * as readline from 'node:readline';\n\nexport function readHiddenInput(prompt: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n let answered = false;\n\n // Mute output\n const stdout = process.stdout;\n\n process.stdout.write(` ${prompt}`);\n\n // Override _writeToOutput to mask user keystrokes only\n // readline calls _writeToOutput for its own internal writes (prompt, question text)\n // as well as echoed user input \u2014 we suppress the internal writes and mask keystrokes\n let prompting = true;\n (rl as any)._writeToOutput = function (str: string) {\n if (prompting) return; // suppress readline's own prompt/question echo\n if (str.includes('\\n') || str.includes('\\r')) {\n stdout.write('\\n');\n } else {\n stdout.write('*');\n }\n };\n\n // After readline finishes its internal setup, start masking user input\n process.nextTick(() => {\n prompting = false;\n });\n\n rl.question('', (answer) => {\n answered = true;\n rl.close();\n resolve(answer.trim());\n });\n\n rl.on('close', () => {\n if (!answered) {\n reject(new Error('readline was closed'));\n }\n });\n });\n}\n", "export interface RefreshedTokens {\n accessToken: string;\n refreshToken: string;\n expiresIn: number;\n tokenType: string;\n scope?: string;\n}\n\nexport class RefreshError extends Error {\n constructor(\n message: string,\n public readonly unauthorized: boolean,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'RefreshError';\n }\n}\n\nexport async function refreshAccessToken(\n refreshToken: string,\n proxyUrl: string,\n): Promise<RefreshedTokens> {\n let res: Response;\n try {\n res = await fetch(`${proxyUrl}/refresh`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({ refresh_token: refreshToken }),\n signal: AbortSignal.timeout(15_000),\n });\n } catch (err) {\n throw new RefreshError('Could not reach the Brevo login service.', false, err);\n }\n\n if (!res.ok) {\n throw new RefreshError(`Token refresh failed (${res.status}).`, res.status === 401);\n }\n\n let body: {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n token_type?: string;\n scope?: string;\n };\n try {\n body = (await res.json()) as typeof body;\n } catch (err) {\n throw new RefreshError('Malformed refresh response from login service.', false, err);\n }\n\n if (\n !body.access_token ||\n typeof body.access_token !== 'string' ||\n typeof body.expires_in !== 'number' ||\n !Number.isFinite(body.expires_in) ||\n body.expires_in <= 0 ||\n !body.token_type ||\n typeof body.token_type !== 'string'\n ) {\n throw new RefreshError('Malformed refresh response from login service.', false);\n }\n\n // refresh_token rotation is optional (proxies often only mint a new one\n // when the old one is close to expiry). When the field is present it must\n // be a non-empty string \u2014 `??` would let an empty string slip through and\n // get persisted as a permanently-broken refresh token.\n let resolvedRefreshToken = refreshToken;\n if (body.refresh_token !== undefined) {\n if (typeof body.refresh_token !== 'string' || body.refresh_token.length === 0) {\n throw new RefreshError('Malformed refresh response from login service.', false);\n }\n resolvedRefreshToken = body.refresh_token;\n }\n\n return {\n accessToken: body.access_token,\n refreshToken: resolvedRefreshToken,\n expiresIn: body.expires_in,\n tokenType: body.token_type,\n scope: body.scope,\n };\n}\n", "import { color } from './logger';\n\nconst SPINNER_FRAMES = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F'];\n\nexport interface Spinner {\n update(text: string): void;\n stop(finalText?: string): void;\n}\n\nlet activeSpinner: Spinner | null = null;\n\n/**\n * Stop the currently active spinner (if any).\n * Used by the re-auth handler to clear the spinner before prompting.\n */\nexport function stopActiveSpinner(): void {\n if (activeSpinner) {\n activeSpinner.stop();\n activeSpinner = null;\n }\n}\n\nexport function createSpinner(text: string, options?: { silent?: boolean }): Spinner {\n stopActiveSpinner();\n const isTTY = process.stdout.isTTY === true;\n\n if (options?.silent || !isTTY) {\n if (!options?.silent) {\n process.stderr.write(` ${text}\\n`);\n }\n return {\n update(t: string) {\n if (!options?.silent) process.stderr.write(` ${t}\\n`);\n },\n stop(t?: string) {\n if (t && !options?.silent) process.stderr.write(` ${t}\\n`);\n },\n };\n }\n\n let frame = 0;\n let currentText = text;\n\n const interval = setInterval(() => {\n const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];\n process.stdout.write(`\\r \\x1b[36m${spinner}\\x1b[0m ${currentText}`);\n frame++;\n }, 80);\n\n const spinner: Spinner = {\n update(t: string) {\n currentText = t;\n },\n stop(finalText?: string) {\n clearInterval(interval);\n process.stdout.write('\\r\\x1b[K');\n if (finalText) process.stdout.write(` ${finalText}\\n`);\n if (activeSpinner === spinner) activeSpinner = null;\n },\n };\n\n activeSpinner = spinner;\n return spinner;\n}\n\n/**\n * Columns a box spends on itself: the two-space gutter, a border and a pad on each\n * side. Everything left over is the content budget.\n */\nconst BOX_CHROME_COLUMNS = 6;\n\n/**\n * Narrowest content a box will size itself down to. Below this the frame is worth\n * less than the text, so an extremely narrow window gets an overflowing box rather\n * than a column of two-letter fragments.\n */\nconst BOX_MIN_CONTENT_COLUMNS = 24;\n\n/**\n * How wide a box line may be before it has to wrap.\n *\n * A box used to size itself purely to its longest line, which meant the *terminal*\n * did the wrapping \u2014 and a terminal wraps the text without the frame, so the tail of\n * an over-long line carries no borders and every `\u2502` after it lands mid-row. One\n * long line was enough to destroy the whole box: a created-app summary carrying an\n * example URL with six query parameters came to 147 columns and shredded itself in a\n * 127-column window.\n *\n * `process.stdout.columns` is undefined when stdout is a pipe or a file \u2014 there is no\n * width to respect there, and 80 keeps the frame intact for whoever reads it later.\n */\nfunction boxContentBudget(): number {\n const columns = process.stdout.columns;\n const usable = (typeof columns === 'number' && columns > 0 ? columns : 80) - BOX_CHROME_COLUMNS;\n return Math.max(usable, BOX_MIN_CONTENT_COLUMNS);\n}\n\nexport function printBox(title: string, lines: string[]): void {\n const budget = boxContentBudget();\n const titleRows = wrapToWidth(title, budget);\n const bodyRows = lines.flatMap((line) => wrapToWidth(line, budget));\n const maxLen = Math.max(...[...titleRows, ...bodyRows].map(displayWidth));\n const border = '\u2500'.repeat(maxLen + 2);\n\n process.stdout.write(`\\n \u250C${border}\u2510\\n`);\n for (const row of titleRows) {\n process.stdout.write(` \u2502 \\x1b[1m${row}${' '.repeat(maxLen - displayWidth(row))}\\x1b[0m \u2502\\n`);\n }\n process.stdout.write(` \u251C${border}\u2524\\n`);\n for (const row of bodyRows) {\n process.stdout.write(` \u2502 ${row}${' '.repeat(maxLen - displayWidth(row))} \u2502\\n`);\n }\n process.stdout.write(` \u2514${border}\u2518\\n\\n`);\n}\n\n// eslint-disable-next-line no-control-regex\nconst SGR = /\\x1b\\[[0-9;]*m/;\n// eslint-disable-next-line no-control-regex\nconst SGR_GLOBAL = /\\x1b\\[[0-9;]*m/g;\nconst SGR_RESET = '\\x1b[0m';\n\nfunction stripAnsi(str: string): string {\n return str.replace(SGR_GLOBAL, '');\n}\n\n/** Columns a string occupies once printed \u2014 escape sequences take none. */\nfunction displayWidth(str: string): number {\n return stripAnsi(str).length;\n}\n\n/**\n * The colour left open at the end of `row`, or `''` if none is.\n *\n * Only the last code is carried, so a row that stacked two styles resumes with one\n * of them. That is deliberate: it costs a shade on a wrapped line and cannot leak a\n * colour onto the border, which is the failure worth preventing.\n */\nfunction openSgr(row: string): string {\n const codes = row.match(SGR_GLOBAL);\n const last = codes?.[codes.length - 1];\n return !last || last === SGR_RESET || last === '\\x1b[m' ? '' : last;\n}\n\n/**\n * Take one row of at most `width` display columns off `line`.\n *\n * Escape sequences are copied through uncounted, so a coloured line measures by what\n * it prints. A word break is used when one falls in the back half of the row; a URL \u2014\n * which is why this exists and has no spaces to break on \u2014 is cut at the boundary.\n */\nfunction takeRow(line: string, width: number): { row: string; rest: string } {\n let row = '';\n let visible = 0;\n let i = 0;\n let breakAt = -1; // index into `row`, just before the last space seen\n let breakFrom = -1; // the same position in `line`\n\n while (i < line.length && visible < width) {\n const escape = SGR.exec(line.slice(i));\n if (escape?.index === 0) {\n row += escape[0];\n i += escape[0].length;\n continue;\n }\n if (line[i] === ' ' && visible > 0) {\n breakAt = row.length;\n breakFrom = i;\n }\n row += line[i];\n visible += 1;\n i += 1;\n }\n\n if (i >= line.length) return { row, rest: '' };\n if (breakAt > 0 && displayWidth(row.slice(0, breakAt)) >= width / 2) {\n return { row: row.slice(0, breakAt), rest: line.slice(breakFrom + 1) };\n }\n return { row, rest: line.slice(i) };\n}\n\n/**\n * Fit `line` into rows of at most `width` display columns.\n *\n * Continuation rows are indented two columns past the line's own leading whitespace,\n * so a wrapped value reads as belonging to its label rather than starting a new one.\n * The indent is capped so it can never eat the content budget it is measured against.\n */\nfunction wrapToWidth(line: string, width: number): string[] {\n if (displayWidth(line) <= width) return [line];\n\n const leading = /^ */.exec(line)?.[0].length ?? 0;\n const indent = ' '.repeat(Math.max(Math.min(leading + 2, width - BOX_MIN_CONTENT_COLUMNS), 0));\n const rows: string[] = [];\n let rest = line;\n // Tracked across rows rather than read back off the last one, which has already had\n // its reset appended and would therefore always report nothing open.\n let carried = '';\n\n while (rest) {\n const first = rows.length === 0;\n const { row, rest: remaining } = takeRow(rest, width - (first ? 0 : indent.length));\n const full = first ? row : `${indent}${carried}${row}`;\n carried = openSgr(full);\n rows.push(carried ? `${full}${SGR_RESET}` : full);\n rest = remaining;\n }\n return rows;\n}\n\n/**\n * The two-space gutter every other line of CLI output already sits in \u2014 see\n * `logInfo`, `logWarn` and `printBox` above, which all open with two spaces.\n */\nconst OUTPUT_GUTTER = ' ';\n\n/** A choice as inquirer accepts it, narrowed to the fields this helper touches. */\nexport interface PromptChoice {\n name: string;\n value: unknown;\n [key: string]: unknown;\n}\n\n/**\n * Indent a selection prompt's choice labels into the CLI's output gutter, so the\n * options read as nested under their question instead of running flush to the\n * terminal's left edge.\n *\n * **Only the label moves.** inquirer 8 renders each row as `pointer + ' ' + name`\n * (selected) or `' ' + name`, and `listRender` is module-private \u2014 there is no hook\n * for the pointer, so `\u276F` stays in the gutter at column 0 while every label lines up\n * two columns further right. That is the intended look: the pointer reads as a margin\n * marker rather than as part of the option text. Don't try to \"fix\" the pointer by\n * subclassing `ListPrompt` \u2014 it would pin us to inquirer's private render internals\n * for two columns of alignment.\n *\n * Applies to `list` and `checkbox` prompts. Deliberately **not** applied to `rawlist`,\n * whose rows already open with ` 1) `, ` 2) ` \u2014 a number that both provides the\n * structure this adds and would be split from its own label by the indent.\n *\n * Anything that isn't a labelled choice (a separator, a bare string) is returned\n * untouched, so a caller can pass a mixed array without filtering first.\n */\nexport function indentChoices<T>(choices: readonly T[]): T[] {\n return choices.map((choice) => {\n if (!choice || typeof choice !== 'object') return choice;\n const candidate = choice as unknown as PromptChoice;\n if (candidate.type === 'separator' || typeof candidate.name !== 'string') return choice;\n return { ...candidate, name: `${OUTPUT_GUTTER}${candidate.name}` } as unknown as T;\n });\n}\n\n// Visual tone for a status. Each tone maps to an ANSI colour code and a glyph\n// so a state reads at a glance (green \u2713 approved, red \u2717 rejected, \u2026).\nexport type StatusTone = 'neutral' | 'info' | 'pending' | 'progress' | 'success' | 'warn' | 'error';\n\nconst TONE_STYLES: Record<StatusTone, { code: string; icon: string }> = {\n neutral: { code: '90', icon: '\u25CB' }, // gray\n info: { code: '36', icon: '\u25C7' }, // cyan\n pending: { code: '34', icon: '\u25D4' }, // blue\n progress: { code: '33', icon: '\u25D0' }, // yellow\n success: { code: '32', icon: '\u2713' }, // green\n warn: { code: '33', icon: '\u26A0' }, // yellow\n error: { code: '31', icon: '\u2717' }, // red\n};\n\n/**\n * Print a colourful, aligned status card:\n *\n * Title\n * \u2500\u2500\u2500\u2500\u2500\n *\n * \u2713 Label\n * Message body, indented to line up under the label.\n *\n * Colours honour NO_COLOR / FORCE_COLOR / TTY via the shared `color` helper,\n * and every line shares the same left gutter so the block stays aligned.\n */\nexport function printStatusCard(\n title: string,\n label: string,\n message: string,\n tone: StatusTone,\n): void {\n const { code, icon } = TONE_STYLES[tone];\n const rule = '\u2500'.repeat(title.length);\n // \"icon + space\" is 2 columns wide; indent the message to align under the label.\n const bodyIndent = ' ' + ' '.repeat(2);\n\n // Bold the label in the tone's own colour.\n const boldCode = `1;${code}`;\n\n let out = `\\n ${color('1', title)}\\n ${color('90', rule)}\\n\\n`;\n out += ` ${color(code, icon)} ${color(boldCode, label)}\\n`;\n for (const line of message.split('\\n')) {\n out += `${bodyIndent}${color('90', line)}\\n`;\n }\n out += '\\n';\n process.stdout.write(out);\n}\n", "import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\n/**\n * CLI version pulled from the bundled `package.json` at module-init.\n *\n * Resolved relative to this file so the same lookup works under ts-jest\n * (`src/lib/` \u2192 repo root) and the published tarball\n * (`node_modules/@getbrevo/cli/dist/lib/` \u2192 package root). Falls back to\n * `'0.0.0'` if the file is missing or malformed \u2014 safe default for any\n * consumer that uses this for telemetry or skill-version stamping.\n */\nfunction readCliVersion(): string {\n try {\n const pkgPath = path.resolve(__dirname, '..', '..', 'package.json');\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as { version?: unknown };\n return typeof pkg.version === 'string' ? pkg.version : '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\nexport const CLI_VERSION = readCliVersion();\n", "import { CLI_VERSION } from './cli-version';\nimport { USER_AGENT_HEADER, CLI_AUTH_METHODS } from './constants';\n\n/**\n * CLI identification sent as a single `User-Agent` header on every API\n * request so the backend can emit product-tracking events (taxonomy card\n * \"CLI installed\", Kafka topic `cli`) without a dedicated telemetry endpoint.\n *\n * Format: `brevo-cli/<version> (<os>)`, extended with `; auth=<method>` when\n * the request carries credentials \u2014 e.g. `brevo-cli/1.2.0 (macos; auth=oauth)`.\n *\n * Values are sanitized to printable ASCII: undici throws a TypeError on\n * header values containing control characters, which would fail every\n * request \u2014 sanitizing here removes that failure mode entirely.\n */\n\nconst OS_BY_PLATFORM: Record<string, string> = {\n darwin: 'macos',\n win32: 'windows',\n linux: 'linux',\n};\n\n// The taxonomy card only allows known values to reach Amplitude, so anything\n// outside the mapped platforms is reported as `other` rather than passed raw.\nexport function getCliOs(): string {\n return OS_BY_PLATFORM[process.platform] ?? 'other';\n}\n\nexport function sanitizeHeaderValue(value: string, fallback: string): string {\n const sanitized = value.replace(/[^\\x20-\\x7E]/g, '');\n return sanitized || fallback;\n}\n\n// CLI_VERSION is fixed at module init, so sanitize it once rather than on\n// every request. Falls back to the same default as cli-version.ts.\nconst SAFE_CLI_VERSION = sanitizeHeaderValue(CLI_VERSION, '0.0.0');\n\ntype CliAuthMethod = (typeof CLI_AUTH_METHODS)[keyof typeof CLI_AUTH_METHODS];\n\n// Derived from the auth header about to be sent rather than from stored\n// credentials, so login-time validation calls (explicit key/bearer) report\n// the right method without coupling telemetry to credential storage.\nexport function getAuthMethod(\n authHeader: Record<string, string> | undefined,\n): CliAuthMethod | undefined {\n if (authHeader && 'api-key' in authHeader) return CLI_AUTH_METHODS.API_KEY;\n if (authHeader && 'Authorization' in authHeader) return CLI_AUTH_METHODS.OAUTH;\n return undefined;\n}\n\nexport function getCliUserAgent(authHeader?: Record<string, string>): string {\n const method = getAuthMethod(authHeader);\n const comment = method ? `${getCliOs()}; auth=${method}` : getCliOs();\n return `brevo-cli/${SAFE_CLI_VERSION} (${comment})`;\n}\n\nexport function buildCliHeaders(authHeader?: Record<string, string>): Record<string, string> {\n return { [USER_AGENT_HEADER]: getCliUserAgent(authHeader) };\n}\n", "import { ApiError, ErrorCode } from '../lib/errors';\nimport { logHttp, logHttpResponse, logDebug } from '../lib/logger';\nimport { buildCliHeaders } from '../lib/telemetry';\nimport { messages } from '../lang/en';\n\ninterface RequestOptions {\n method: string;\n path: string;\n body?: unknown;\n headers?: Record<string, string>;\n skipAuth?: boolean;\n authHeader?: Record<string, string>;\n}\n\ntype AuthFailureHandler = () => Promise<void>;\ntype EnsureFreshHandler = () => Promise<void>;\n\nexport interface ApiClientDeps {\n baseUrl: string;\n getAuthHeader: () => Record<string, string> | undefined;\n}\n\nconst MAX_RETRIES = 3;\nconst DEFAULT_RETRY_AFTER_SECONDS = 5;\nconst MAX_RETRY_AFTER_SECONDS = 300;\n\nconst apiCodeMessages: Record<string, string> = {\n APP_LIMIT_REACHED: messages.APP_CREATE_LIMIT_REACHED,\n REGISTRY_ERROR: messages.ERR_REGISTRY,\n // app-store-bo-be's `gateUIApp` answers 403 `ui_app_not_enabled` when the calling\n // account lacks the public-apps flag and the request authors a `ui_app` block. It\n // guards both `app create` and `app upload`, so it is mapped here rather than in\n // either command \u2014 the code is stable, unlike the copy.\n ui_app_not_enabled: messages.ERR_UI_APP_NOT_ENABLED,\n};\n\nfunction resolveErrorMessage(apiCode: string | undefined, fallback: string): string {\n if (apiCode && apiCode in apiCodeMessages) {\n return apiCodeMessages[apiCode]!;\n }\n return fallback;\n}\n\n// Server-provided `retry-after` is untrusted input. Cap it to avoid indefinite\n// waits from a malicious or misconfigured server, and reject NaN/negatives\n// which would otherwise turn into a 0ms tight retry loop.\nexport function parseRetryAfter(header: string | null): number {\n if (!header) return DEFAULT_RETRY_AFTER_SECONDS;\n const parsed = Number.parseInt(header, 10);\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_RETRY_AFTER_SECONDS;\n return Math.min(parsed, MAX_RETRY_AFTER_SECONDS);\n}\n\n// Sanitize untrusted server-provided error strings before they reach the terminal:\n// strip ANSI escape sequences and non-printable control characters that could\n// reposition the cursor, clear the screen, or inject fake prompts.\nexport function sanitizeErrorMessage(msg: string): string {\n return (\n msg\n // ANSI CSI/OSC escape sequences\n // eslint-disable-next-line no-control-regex\n .replace(/\\x1B\\[[0-?]*[ -/]*[@-~]/g, '')\n // eslint-disable-next-line no-control-regex\n .replace(/\\x1B\\][^\\x07]*(?:\\x07|\\x1B\\\\)/g, '')\n // C0 + DEL + C1 control chars, keeping \\t \\n \\r.\n // Includes 0x80\u20130x9F so 8-bit CSI (0x9B) / OSC (0x9D) introducers\n // are stripped alongside their 7-bit ESC-prefixed equivalents.\n // eslint-disable-next-line no-control-regex\n .replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F-\\x9F]/g, '')\n );\n}\n\n// Detect HTML bodies returned by auth gateways (Cloudflare Access, SSO proxies).\n// Matches case-insensitively and runs regardless of response status, since\n// gateways frequently return HTML on 401/403 as well as 2xx.\n// Exported so the update notice can apply the same guard to /cli/info.\nexport function looksLikeHtml(s: string): boolean {\n const lower = s.toLowerCase();\n return lower.includes('<!doctype html') || lower.includes('<html');\n}\n\nfunction parseResponseData(text: string, status: number): Record<string, unknown> {\n let data: Record<string, unknown> = {};\n try {\n const parsed: unknown = text ? JSON.parse(text) : {};\n if (parsed !== null && typeof parsed === 'object') {\n data = parsed as Record<string, unknown>;\n }\n } catch {\n if (looksLikeHtml(text)) {\n throw new ApiError(messages.ERR_AUTH_GATEWAY, status, ErrorCode.AUTH_GATEWAY);\n }\n data = { message: text };\n }\n\n if (typeof data.message === 'string' && looksLikeHtml(data.message)) {\n throw new ApiError(messages.ERR_AUTH_GATEWAY, status, ErrorCode.AUTH_GATEWAY);\n }\n return data;\n}\n\nfunction throwResponseError(data: Record<string, unknown>, status: number): never {\n const apiCode = typeof data.code === 'string' ? data.code : undefined;\n // Prefer the API's human-readable text: some endpoints return `message`,\n // others (e.g. app upload's `distribution_type cannot be changed via upload`)\n // return it under `error`. Surface whichever is present before falling back\n // to the generic status line.\n const apiText =\n (typeof data.message === 'string' && data.message) ||\n (typeof data.error === 'string' && data.error) ||\n '';\n const rawFallback = apiText || `Request failed with status ${status}`;\n const fallback = sanitizeErrorMessage(rawFallback);\n const message = resolveErrorMessage(apiCode, fallback);\n throw new ApiError(message, status, mapErrorCode(status, apiCode), apiCode);\n}\n\nfunction mapErrorCode(status: number, apiCode?: string): ErrorCode | undefined {\n if (apiCode === 'APP_LIMIT_REACHED') return ErrorCode.APP_LIMIT_REACHED;\n if (apiCode === 'REGISTRY_ERROR') return ErrorCode.REGISTRY_ERROR;\n\n switch (status) {\n case 401:\n return ErrorCode.AUTH_INVALID;\n case 403:\n return ErrorCode.ACCESS_DENIED;\n case 404:\n return ErrorCode.APP_NOT_FOUND;\n case 429:\n return ErrorCode.RATE_LIMITED;\n default:\n return undefined;\n }\n}\n\nexport class ApiClient {\n private onAuthFailure?: AuthFailureHandler;\n private ensureFresh?: EnsureFreshHandler;\n\n constructor(private readonly deps: ApiClientDeps) {}\n\n setOnAuthFailure(handler: AuthFailureHandler): void {\n this.onAuthFailure = handler;\n }\n\n /**\n * Runs before every authenticated request, to renew an access token that is\n * expired or about to be.\n *\n * The same check also runs once as a `preAction` hook, and that one is not\n * redundant \u2014 it is what fails a dead session *before* an interactive command\n * starts asking questions. This one covers the other half: a token that was\n * comfortably fresh when the command started and expired somewhere in the\n * minutes of prompts before the first request went out. `brevo app create`\n * makes no API call at all until every prompt is answered, so that window is\n * as long as the user is slow.\n *\n * Cheap: it only reaches the network when the stored expiry is actually\n * within the skew, so the common case is a file read. Wired in `bin/index.ts`\n * over the same deps as the hook, so the two can't drift.\n */\n setEnsureFresh(handler: EnsureFreshHandler): void {\n this.ensureFresh = handler;\n }\n\n get<T>(path: string): Promise<T> {\n return this.request<T>({ method: 'GET', path });\n }\n\n post<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>({ method: 'POST', path, body });\n }\n\n patch<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>({ method: 'PATCH', path, body });\n }\n\n put<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>({ method: 'PUT', path, body });\n }\n\n // A body on DELETE is unusual but not disallowed, and the app-store installs\n // resource requires one (it identifies the install by account, not by path).\n delete<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>({ method: 'DELETE', path, body });\n }\n\n getWithKey<T>(path: string, apiKey: string): Promise<T> {\n return this.request<T>({\n method: 'GET',\n path,\n skipAuth: true,\n authHeader: { 'api-key': apiKey },\n });\n }\n\n getWithBearer<T>(path: string, accessToken: string, tokenType = 'Bearer'): Promise<T> {\n return this.request<T>({\n method: 'GET',\n path,\n skipAuth: true,\n authHeader: { Authorization: `${tokenType} ${accessToken}` },\n });\n }\n\n private buildHeaders(opts: RequestOptions): Record<string, string> {\n const explicitAuth: Record<string, string> | undefined = opts.authHeader;\n const authHeader = explicitAuth ?? (opts.skipAuth ? undefined : this.deps.getAuthHeader());\n\n return {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...buildCliHeaders(authHeader),\n ...opts.headers,\n ...authHeader,\n };\n }\n\n private async performFetch(\n url: string,\n opts: RequestOptions,\n headers: Record<string, string>,\n ): Promise<Response> {\n try {\n return await fetch(url, {\n method: opts.method,\n headers,\n body: opts.body ? JSON.stringify(opts.body) : undefined,\n signal: AbortSignal.timeout(30_000),\n });\n } catch (err) {\n const apiErr = new ApiError(messages.ERR_NETWORK, 0, ErrorCode.NETWORK_ERROR);\n (apiErr as Error).cause = err;\n throw apiErr;\n }\n }\n\n private async request<T>(opts: RequestOptions, isRetry = false, retryCount = 0): Promise<T> {\n // Before the headers are built, so the renewed token is the one that gets\n // sent. Skipped for requests that carry their own credential: `getWithKey`\n // and `getWithBearer` are login's own validation calls, and refreshing the\n // stored session underneath them would be answering a question they didn't\n // ask. Re-entered on the 401/429/502 retries below \u2014 harmless, since it is\n // a no-op unless the token is inside the skew, and after a long 429 backoff\n // it is the check that keeps the retry from going out stale.\n if (this.ensureFresh && !opts.skipAuth && !opts.authHeader) {\n await this.ensureFresh();\n }\n\n const url = `${this.deps.baseUrl}${opts.path}`;\n const headers = this.buildHeaders(opts);\n\n logHttp(opts.method, opts.path);\n // The request body, symmetric with the response log below and redacted by the\n // same rules. Logged before the fetch so the payload is visible even when the\n // request never comes back (timeout, network error). A bodyless method logs\n // nothing rather than a bare `undefined`.\n if (opts.body !== undefined) {\n logDebug(`request ${opts.method} ${opts.path}`, opts.body);\n }\n const response = await this.performFetch(url, opts, headers);\n logHttpResponse(response.status, opts.path);\n\n if (response.status === 401 && !isRetry && !opts.skipAuth) {\n if (this.onAuthFailure) {\n await this.onAuthFailure();\n return this.request<T>(opts, true, retryCount);\n }\n throw new ApiError(messages.AUTH_EXPIRED, 401, ErrorCode.AUTH_EXPIRED);\n }\n\n if (response.status === 429) {\n if (retryCount >= MAX_RETRIES) {\n throw new ApiError('Rate limited \u2014 max retries exceeded.', 429, ErrorCode.RATE_LIMITED);\n }\n const retryAfter = parseRetryAfter(response.headers.get('retry-after'));\n process.stderr.write(` ${messages.ERR_RATE_LIMITED(retryAfter)}\\n`);\n await new Promise((r) => setTimeout(r, retryAfter * 1000));\n return this.request<T>(opts, isRetry, retryCount + 1);\n }\n\n if (response.status === 502 && retryCount < 1) {\n await new Promise((r) => setTimeout(r, 2000));\n return this.request<T>(opts, isRetry, retryCount + 1);\n }\n\n const text = await response.text();\n const data = parseResponseData(text, response.status);\n\n logDebug(`response ${opts.method} ${opts.path}`, data);\n\n if (!response.ok) {\n throwResponseError(data, response.status);\n }\n\n return data as T;\n }\n}\n", "import { ApiClient } from '../api/client';\nimport { ENDPOINTS } from '../lib/constants';\nimport { AccountResponse, SubAccount, SubAccountsResponse } from '../types';\n\n/**\n * Conservative fixed page size. The public reference documents no `limit` cap, so\n * page in known-safe chunks rather than guessing a maximum the server may reject.\n */\nconst SUB_ACCOUNT_PAGE_SIZE = 50;\n\n/**\n * Backstop on the paging loop. `count` is the documented terminator, but it is\n * server-supplied \u2014 a wrong or drifting value must not spin the CLI forever.\n */\nconst SUB_ACCOUNT_MAX_PAGES = 40;\n\nexport function createAccountService(client: ApiClient) {\n return {\n validateApiKey(apiKey: string): Promise<AccountResponse> {\n return client.getWithKey<AccountResponse>(ENDPOINTS.ACCOUNT, apiKey);\n },\n getAccount(): Promise<AccountResponse> {\n return client.get<AccountResponse>(ENDPOINTS.ACCOUNT);\n },\n\n /**\n * Every sub-account of the authenticated master account, paged to exhaustion\n * (BEX-290). Returns rows verbatim \u2014 filtering (notably on `active`) belongs to\n * the caller that renders them, not here.\n *\n * Stops on the first empty page as well as on `count`, so a `count` larger than\n * the rows actually returned terminates instead of looping on a fixed offset.\n */\n async fetchSubAccounts(): Promise<SubAccount[]> {\n const collected: SubAccount[] = [];\n\n for (let page = 0; page < SUB_ACCOUNT_MAX_PAGES; page += 1) {\n const response = await client.get<SubAccountsResponse>(\n `${ENDPOINTS.CORPORATE_SUB_ACCOUNTS}?offset=${collected.length}&limit=${SUB_ACCOUNT_PAGE_SIZE}`,\n );\n const batch = response?.subAccounts ?? [];\n collected.push(...batch);\n\n if (batch.length === 0 || collected.length >= (response?.count ?? 0)) break;\n }\n\n return collected;\n },\n };\n}\n\nexport type AccountService = ReturnType<typeof createAccountService>;\n", "import inquirer from 'inquirer';\nimport { ApiClient } from '../api/client';\nimport { ENDPOINTS } from '../lib/constants';\nimport { ApiError, CliError } from '../lib/errors';\nimport { EXIT_CODES } from '../lib/exit-codes';\nimport { logInfo } from '../lib/logger';\nimport { createSpinner } from '../lib/ui';\nimport { messages } from '../lang/en';\nimport {\n OAuthApp,\n CreateAppResponse,\n AppStateResponse,\n RawSurfacePointRow,\n SurfacePointLocationsResponse,\n SurfacePointRow,\n SurfacePointsResponse,\n UiApp,\n UploadAppPayload,\n UploadAppResponse,\n} from '../types';\nimport {\n AppCredential,\n getAppCredentials,\n getOrganizationId,\n saveAppCredentials,\n} from '../lib/config';\nimport { normalizeAppId } from './normalize-app-id';\n\n/** First of the candidates that is a non-blank string, trimmed; `undefined` if none is. */\nfunction firstNonEmptyString(...candidates: unknown[]): string | undefined {\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim()) return candidate.trim();\n }\n return undefined;\n}\n\n/**\n * `{ key: value }` when the value is present, `{}` when it is not \u2014 so a row missing a\n * column stays missing rather than gaining an explicit `undefined`, which would survive\n * `JSON.stringify` differently and show up in the upload diff.\n */\nfunction pick<K extends string, V>(key: K, value: V | undefined): Partial<Record<K, V>> {\n return value === undefined ? {} : ({ [key]: value } as Record<K, V>);\n}\n\n/**\n * The create response exactly as it comes off the wire: OAuth fields may arrive\n * nested under `auth`, top-level, or (for a UI app) not at all.\n */\ntype RawCreateAppResponse = CreateAppResponse & {\n auth?: {\n client_id?: string;\n client_secret?: string;\n scopes?: string[];\n redirect_uris?: string[];\n };\n};\n\n/**\n * Lift the create response's `auth` block to the top level.\n *\n * The unified-payload work moved the create REQUEST's OAuth fields inside `auth`,\n * to match the upload endpoint. The platform's nested-contract handler\n * (`http_cli_create_app_public.go`) echoes that nesting straight back, so the\n * response became `{app_id, name, version, distribution_type, auth: {...}}` while\n * every caller still read the flat shape. Nothing type-errored, so it shipped:\n * `app create` printed `Client ID: undefined`, dropped its `Redirect URL n:` lines,\n * `--json` silently omitted `clientId`/`redirectUri` (JSON.stringify drops\n * undefined), and the scaffold read-back fallback lost its callbacks too.\n *\n * Normalising here rather than at the ~7 read sites keeps the fix in one place and\n * keeps `CreateAppResponse` a single shape for everything downstream. `auth` is\n * removed after lifting so there is exactly one place to read each field.\n *\n * Both shapes are tolerated because the flat handler is still live on some\n * deployments \u2014 this is a compatibility shim, not a migration, and it can go once\n * the nested contract is the only one in production (RELEASE-CHECKLIST.md).\n *\n * A present flat field wins over its nested twin. They should never disagree; if\n * they do, the shape that shipped is the one callers already expect, and quietly\n * preferring the other would be a behaviour change hiding inside a shim.\n */\nfunction flattenCreateAuth(raw: RawCreateAppResponse): CreateAppResponse {\n const { auth, ...rest } = raw;\n if (!auth) return rest;\n return {\n ...rest,\n ...pick('client_id', rest.client_id ?? auth.client_id),\n ...pick('client_secret', rest.client_secret ?? auth.client_secret),\n ...pick('redirect_uris', rest.redirect_uris ?? auth.redirect_uris),\n ...pick('scopes', rest.scopes ?? auth.scopes),\n };\n}\n\nfunction rethrowNotFound(err: unknown, appId: string): never {\n if (err instanceof ApiError && err.statusCode === 404) {\n throw new CliError(`App ${appId} not found.`, err.exitCode);\n }\n throw err;\n}\n\n/**\n * An account identifier as a number, or `undefined` when it is not one.\n *\n * Brevo identifies accounts two different ways depending on where the value came\n * from \u2014 a sub-account listing yields an integer `id`, while `organization_id` may be\n * a UUID \u2014 but **both body fields on the installs resource are Go `int64`**, and the\n * handler decodes the body *before* it consults anything else. A UUID in either field\n * is therefore a decode failure: HTTP 400, whole request lost.\n *\n * So a non-numeric identifier is dropped from the payload rather than sent. Omission\n * is not a loss of information, because the server has a better answer for each field\n * than the CLI does \u2014 see {@link buildInstallPayload}. What must never happen is\n * `Number()` on everything: `Number('550e8400-\u2026')` is `NaN`, which `JSON.stringify`\n * emits as `null`, and `null` is a decode failure too.\n */\nfunction toNumericIdentifier(value: string | undefined): number | undefined {\n const trimmed = value?.trim();\n return trimmed && /^\\d+$/.test(trimmed) ? Number(trimmed) : undefined;\n}\n\n/**\n * The caller's own account identifier, as a display/target string.\n *\n * Unlike the wire fields this keeps a UUID intact \u2014 it is what `app deploy` labels the\n * target with and what a plain account resolves to when no `[account-id]` was given.\n * Only an absent or blank value throws, since that means the credentials carry no\n * account at all and re-authenticating is the fix.\n */\nexport function getCallerAccountId(): string {\n const organizationId = getOrganizationId()?.trim();\n if (!organizationId) {\n throw new CliError(messages.APP_DEPLOY_MISSING_CLIENT_ID);\n }\n return organizationId;\n}\n\n/**\n * Body shared by both verbs on the app-store installs resource (BEX-290).\n *\n * Both identifiers are **optional on the wire, and deliberately so** \u2014 the server\n * resolves each one better than the CLI can when it is absent:\n *\n * - `client_id` is the caller, the account that owns the app, which the server\n * resolves it against via `FindIDByUUID(uuid, client_id)`. The handler reads the\n * `X-Sib-Client-Id` header *first* and only falls back to this field, and the API\n * gateway populates that header from the api-key/bearer credential the CLI already\n * sends. So this is a fallback for a header that is normally there \u2014 worth sending\n * when it is a number, never worth a 400 when it isn't.\n * - `deploy_client_id` is the target the install lands in, and the server defaults it\n * to the caller when absent. That default is exactly right for a plain account\n * deploying into itself, which is the only case that can produce a non-numeric value\n * here (an explicit `[account-id]` is numeric by `parseAccountId`, and a sub-account\n * `id` is numeric by construction).\n *\n * The two are not interchangeable: they differ whenever a corporate account deploys\n * into a sub-account, and collapsing them 404s the app lookup.\n */\nfunction buildInstallPayload(accountId: string, name: string) {\n return {\n ...pick('client_id', toNumericIdentifier(getOrganizationId())),\n ...pick('deploy_client_id', toNumericIdentifier(accountId)),\n name,\n is_developer: true,\n };\n}\n\n/**\n * Check apps exist and throw with user-facing message if empty.\n */\nfunction logEmptyAndThrow(): never {\n logInfo(`\\n ${messages.APP_LIST_EMPTY}\\n`);\n throw new CliError(messages.APP_LIST_EMPTY, EXIT_CODES.ERROR);\n}\n\n/**\n * Merge the locally cached credentials into a freshly-read app, in place: prefer the remote\n * value, fall back to the cache for a field the GET did not carry.\n *\n * Returns the names of the fields where the cache and the server both had a value and they\n * disagreed \u2014 the caller surfaces those as a staleness warning rather than silently picking\n * a side.\n */\nfunction mergeCachedCredentials(app: OAuthApp, local: AppCredential | undefined): string[] {\n const diffs: string[] = [];\n if (!local) return diffs;\n\n if (!app.client_id && local.clientId) {\n app.client_id = local.clientId;\n } else if (local.clientId && local.clientId !== app.client_id) {\n diffs.push('client_id');\n }\n\n if (!app.client_secret && local.clientSecret) {\n app.client_secret = local.clientSecret;\n } else if (local.clientSecret && local.clientSecret !== app.client_secret) {\n diffs.push('client_secret');\n }\n\n return diffs;\n}\n\nexport function createAppService(client: ApiClient) {\n async function fetchAppsList(): Promise<OAuthApp[]> {\n const apps = await client.get<OAuthApp[]>(ENDPOINTS.APP_STORE_APPS);\n return (apps || []).map(normalizeAppId);\n }\n\n return {\n fetchAppsList,\n\n /**\n * The record-page locations the registry offers, in server order (BEX-361).\n *\n * `GET .../surface-points/locations` answers with the distinct `location_name` values\n * and nothing else (`{ locations, count }`), which is exactly what `app create`'s\n * record-page prompt needs. Reading it beats pulling every row and reducing to the\n * same handful of strings: it is the registry's own answer to \"which pages exist\"\n * rather than the CLI's inference from whichever rows happened to come back, and it\n * doesn't make the page prompt wait on the full registry.\n *\n * Tolerates a bare array alongside the wrapped shape, and drops non-string, blank and\n * duplicate entries, so callers can trust every value they get.\n *\n * Errors propagate \u2014 the caller owns the actionable message, same as below.\n */\n async fetchSurfacePointLocations(): Promise<string[]> {\n const res = await client.get<SurfacePointLocationsResponse | string[] | null>(\n ENDPOINTS.APP_STORE_SURFACE_POINT_LOCATIONS,\n );\n const raw = Array.isArray(res) ? res : (res?.locations ?? []);\n const locations = new Set<string>();\n for (const entry of raw) {\n if (typeof entry !== 'string') continue;\n const name = entry.trim();\n if (name) locations.add(name);\n }\n return [...locations];\n },\n\n /**\n * Read the extension-point registry rows for UI-app slot authoring (BEX-361).\n *\n * `locations` narrows to the given `location_name` values (comma-separated on the\n * wire), which is how `app create` fetches the placements for the pages a partner\n * actually picked \u2014 the pages come from `fetchSurfacePointLocations` above. Omit it\n * for the whole registry, which the create flow only falls back to when the narrowed\n * read doesn't cover the picked pages.\n *\n * There is deliberately NO extension-type filter. Both extension types render on both\n * kinds, so filtering server-side would hide authorable placements; the create flow\n * checks each row's own `extension_type_list` instead.\n *\n * The backend route is specified (app-store-bo-be GET /cli/surface-points) but not\n * built yet; only the public /v3 mapping is assumed. See RELEASE-CHECKLIST.md \u2192 Before\n * UI-apps GA. Errors propagate \u2014 the caller owns the actionable message, since a 404\n * currently just means \"endpoint not built\".\n *\n * Normalization: rows are keyed on `extension_point_name`, falling back to the pre-BEX-361\n * `extension_point` spelling, and the three decomposed segments accept either naming\n * (see RawSurfacePointRow for why both are tolerated). Rows with no usable name are\n * dropped and duplicates deduped, so callers can trust every row's identity.\n */\n async fetchSurfacePoints(locations?: readonly string[]): Promise<SurfacePointRow[]> {\n const filter = (locations ?? []).map((l) => String(l).trim()).filter(Boolean);\n const query = filter.length ? `?location=${encodeURIComponent(filter.join(','))}` : '';\n const res = await client.get<SurfacePointsResponse | RawSurfacePointRow[] | null>(\n `${ENDPOINTS.APP_STORE_SURFACE_POINTS}${query}`,\n );\n const rows = Array.isArray(res) ? res : (res?.surface_points ?? []);\n const seen = new Set<string>();\n const normalized: SurfacePointRow[] = [];\n for (const row of rows) {\n if (!row || typeof row !== 'object') continue;\n const name = firstNonEmptyString(row.extension_point_name, row.extension_point);\n if (!name || seen.has(name)) continue;\n seen.add(name);\n const {\n extension_point: _legacyName,\n location: legacyLocation,\n place: legacyPlace,\n kind: legacyKind,\n supported_extension_types: legacySupportedTypes,\n ...rest\n } = row;\n normalized.push({\n ...rest,\n extension_point_name: name,\n ...pick('location_name', firstNonEmptyString(row.location_name, legacyLocation)),\n ...pick('section_name', firstNonEmptyString(row.section_name, legacyPlace)),\n ...pick('component_type', firstNonEmptyString(row.component_type, legacyKind)),\n ...((row.extension_type_list ?? legacySupportedTypes)\n ? { extension_type_list: row.extension_type_list ?? legacySupportedTypes }\n : {}),\n });\n }\n return normalized;\n },\n\n async fetchApp(appId: string): Promise<OAuthApp | null> {\n let app: OAuthApp;\n try {\n app = await client.get<OAuthApp>(ENDPOINTS.APP_STORE_APP(appId));\n } catch (err) {\n rethrowNotFound(err, appId);\n }\n return app ? normalizeAppId(app) : null;\n },\n\n /**\n * Canonical read of an app's review lifecycle state. This is the single\n * state-read path \u2014 `brevo app status` and, later, `submit` (BEX-251) and\n * `withdraw` (BEX-253) all go through here rather than querying separately.\n * Read-only; no mutation.\n */\n async fetchAppState(appId: string): Promise<AppStateResponse> {\n let res: AppStateResponse;\n try {\n res = await client.get<AppStateResponse>(ENDPOINTS.APP_STATE(appId));\n } catch (err) {\n rethrowNotFound(err, appId);\n }\n return res;\n },\n\n async pickApp(\n promptMessage: string,\n formatChoice?: (app: OAuthApp) => string,\n ): Promise<string> {\n const spinner = createSpinner('Loading apps...');\n const apps = await fetchAppsList();\n spinner.stop();\n if (apps.length === 0) {\n logEmptyAndThrow();\n }\n\n const { selectedApp } = await inquirer.prompt([\n {\n type: 'rawlist',\n name: 'selectedApp',\n message: promptMessage,\n choices: apps.map((a) => {\n const appName = a.name || 'App ' + a.app_id;\n return {\n name: formatChoice\n ? formatChoice(a)\n : `${appName} (App ID: ${a.app_id}, Client ID: ${a.client_id})`,\n value: a.app_id,\n };\n }),\n },\n ]);\n return selectedApp as string;\n },\n\n /**\n * Fetch app from API and merge with local cache.\n * The GET endpoint may not return client_secret (only shown at creation),\n * so we fall back to the locally cached value.\n *\n * `tolerateMissing` turns a 404 into `null` instead of the friendly not-found\n * error. Only one caller wants that: `app create`'s read-back of the app it\n * just created, where a 404 means the server cannot resolve an ID it issued\n * itself moments earlier \u2014 see `fetchAppContext`'s `fallbackApp`. Every other\n * caller reads an ID the user supplied, where 404 genuinely means \"wrong ID\"\n * and must stay fatal.\n */\n async resolveAppCredentials(\n appId: string,\n opts?: { tolerateMissing?: boolean },\n ): Promise<{ app: OAuthApp; diffs: string[] } | null> {\n let raw: OAuthApp;\n try {\n raw = await client.get<OAuthApp>(ENDPOINTS.APP_STORE_APP(appId));\n } catch (err) {\n if (opts?.tolerateMissing && err instanceof ApiError && err.statusCode === 404) {\n return null;\n }\n rethrowNotFound(err, appId);\n }\n if (!raw) return null;\n const app = normalizeAppId(raw);\n const diffs = mergeCachedCredentials(app, getAppCredentials(appId));\n return { app, diffs };\n },\n\n /**\n * Save credentials to local cache.\n * Preserves existing local values when the app has missing fields\n * (e.g. GET endpoint doesn't return client_secret).\n */\n syncAppCredentials(appId: string, app: OAuthApp): void {\n const existing = getAppCredentials(appId);\n const clientId = app.client_id || existing?.clientId;\n const clientSecret = app.client_secret || existing?.clientSecret;\n // Only write if we have at least a client ID and a non-empty secret\n if (clientId && clientSecret) {\n saveAppCredentials(appId, { clientId, clientSecret });\n }\n },\n\n // The payload goes over the wire unchanged, for the same reason `uploadApp`\n // does: a top-level key outside the declared create contract is a key the\n // server can start rejecting. `source: 'cli'` used to be stamped here and was\n // removed \u2014 the backend derives the caller from the structured `User-Agent`\n // (`brevo-cli/...`, see telemetry.ts), so nothing is lost by not sending it.\n async createApp(payload: {\n name: string;\n distribution_type: 'public' | 'private';\n // OAuth fields travel inside `auth`, the same block the upload endpoint\n // takes (unified payload structure). Omitted entirely for UI apps.\n auth?: {\n scopes: string[];\n redirect_uris: string[];\n };\n // Sent for UI apps only, under the same key the upload endpoint takes and\n // the same key the block carries in app-config.json. It is the app-type\n // discriminator on the wire as well as locally: without it, create has no\n // way to know the app has no OAuth flow, and rejects the absent `auth`\n // block with `redirect_uris is required and must not be empty`.\n ui_app?: UiApp;\n logo_uri?: string;\n }): Promise<CreateAppResponse> {\n const raw = await client.post<RawCreateAppResponse>(ENDPOINTS.APP_STORE_APPS, payload);\n return flattenCreateAuth(normalizeAppId(raw));\n },\n\n // The payload goes over the wire unchanged: the upload endpoint rejects\n // unknown top-level keys with a 400, and the CLI version already reaches\n // the server on every request via the User-Agent header (see telemetry.ts).\n async uploadApp(appId: string, payload: UploadAppPayload): Promise<UploadAppResponse> {\n return client.post<UploadAppResponse>(ENDPOINTS.APP_STORE_APP_UPLOAD(appId), payload);\n },\n\n async deleteApp(appId: string): Promise<void> {\n try {\n await client.delete(ENDPOINTS.APP_STORE_APP(appId));\n } catch (err) {\n rethrowNotFound(err, appId);\n }\n },\n\n /**\n * Make a UI app available in a single Brevo account (BEX-290) by creating an\n * install on `POST /v3/app-store/apps/{id}/installs`.\n *\n * `deploy_client_id` is the numeric account ID \u2014 sent as a number, not the\n * string `parseAccountId` returns. `is_developer` is always true: every\n * install the CLI creates is a developer install by construction.\n *\n * 404 becomes a friendly CliError: on install there is only one thing to miss,\n * the app itself. Everything else (notably the \"not yet uploaded\" rejection)\n * propagates for the command to map.\n */\n async deployApp(appId: string, accountId: string, name: string): Promise<void> {\n try {\n await client.post(\n ENDPOINTS.APP_STORE_APP_INSTALLS(appId),\n buildInstallPayload(accountId, name),\n );\n } catch (err) {\n rethrowNotFound(err, appId);\n }\n },\n\n /**\n * Withdraw a UI app's availability from a single account \u2014 deletes the\n * install created by {@link deployApp}. Same resource, same body, DELETE.\n *\n * Deliberately no `rethrowNotFound` here, unlike every other verb. The developer\n * uninstall route (BEX-364) has no `installation_id` to delete by, so it resolves\n * the install from the same details the install was created with \u2014 and answers 404\n * for *both* \"app doesn't exist\" and \"no such install\", distinguishable only by the\n * error copy. Collapsing them into \"App not found\" would report a hard failure for\n * the ordinary already-rolled-back case, so the ApiError reaches the command intact\n * and `rollback` maps any 404 to its informational not-deployed path.\n */\n async rollbackApp(appId: string, accountId: string, name: string): Promise<void> {\n await client.delete(\n ENDPOINTS.APP_STORE_APP_INSTALLS(appId),\n buildInstallPayload(accountId, name),\n );\n },\n\n async withdrawApp(appId: string): Promise<void> {\n try {\n await client.post(ENDPOINTS.APP_STORE_APP_WITHDRAW(appId));\n } catch (err) {\n // 404 becomes a friendly CliError; everything else (including 422\n // \"not submitted\") propagates unchanged for the command to handle.\n rethrowNotFound(err, appId);\n }\n },\n };\n}\n\nexport type AppService = ReturnType<typeof createAppService>;\n", "import { CliError } from '../lib/errors';\n\n/**\n * Coerce `app_id` on an API response object to a string.\n * Defensive boundary in case the server still returns a number for a legacy app.\n * Fails fast on unexpected types so invalid server responses never propagate as\n * bogus IDs (e.g. \"undefined\", \"[object Object]\") into requests or credential keys.\n */\nexport function normalizeAppId<T extends { app_id: unknown }>(\n raw: T,\n): Omit<T, 'app_id'> & { app_id: string } {\n const { app_id } = raw;\n if (typeof app_id === 'string') {\n const trimmed = app_id.trim();\n if (trimmed.length === 0) {\n throw new CliError(\n 'Invalid app_id in API response: expected non-empty string, received empty string.',\n );\n }\n return { ...raw, app_id: trimmed };\n }\n if (typeof app_id === 'number' && Number.isFinite(app_id)) {\n return { ...raw, app_id: String(app_id) };\n }\n const received = describeAppId(app_id);\n throw new CliError(\n `Invalid app_id in API response: expected string or finite number, received ${received}.`,\n );\n}\n\nfunction describeAppId(value: unknown): string {\n if (value === null) return 'null';\n if (typeof value === 'number') {\n if (Number.isNaN(value)) return 'number (NaN)';\n if (!Number.isFinite(value)) return `number (${value > 0 ? 'Infinity' : '-Infinity'})`;\n return 'number';\n }\n return typeof value;\n}\n", "import { ApiClient } from './api/client';\nimport { createAccountService, AccountService } from './services/account';\nimport { createAppService, AppService } from './services/app';\nimport { API_BASE } from './lib/constants';\nimport { getAuthCred } from './lib/config';\n\n/**\n * Application container \u2014 creates and holds all shared instances.\n * Initialized once by bin/index.ts. Commands import services from here.\n *\n * The auth-failure handler is wired in bin/index.ts so it can branch on\n * stored auth kind (oauth: refresh+retry; api-key: prompt for new key)\n * without pulling UI/prompt concerns into the container.\n */\n\nfunction buildAuthHeader(): Record<string, string> | undefined {\n const auth = getAuthCred();\n if (!auth) return undefined;\n if (auth.kind === 'api-key') return { 'api-key': auth.apiKey };\n return { Authorization: `${auth.tokenType} ${auth.accessToken}` };\n}\n\nexport const client = new ApiClient({ baseUrl: API_BASE, getAuthHeader: buildAuthHeader });\n\nexport const accountService: AccountService = createAccountService(client);\nexport const appService: AppService = createAppService(client);\n", "/**\n * The pre-GA gate (BEX-405).\n *\n * Public app distribution and UI apps are built in this repo but **not live on the\n * Brevo platform**. The published package must not expose either: not in help, not to\n * a direct invocation, and \u2014 because the guard is applied at build time \u2014 not in the\n * shipped code at all. `scripts/build.mjs` eliminates every gated branch and\n * tree-shakes the command modules only those branches referenced.\n *\n * ## Why this has no runtime escape hatch\n *\n * The first version of this gate unlocked on an `@brevo.com` / `@sendinblue.com`\n * account or an opt-in env var, mirroring the clause the agent docs used to carry.\n * Both were removed when the flag moved to build time. A compile-time guard that any\n * user can switch back on is a runtime guard wearing a costume \u2014 and worse, it has to\n * ship the surface in order to be able to reveal it, which defeats the point of\n * building it out. Internal testing is `PREVIEW=1 yarn link:dev`, which produces a\n * genuinely different artifact.\n *\n * That also means this is no longer \"a guardrail, not a security boundary\": there is\n * nothing client-side left to bypass. The Brevo API remains the real authority and\n * refuses both features per account independently (`400 invalid_parameter` on a public\n * create, `403 ui_app_not_enabled` on a UI app), so the two layers are the build and\n * the server, with nothing in between for a user to talk their way past.\n *\n * ## One table, so GA is one edit\n *\n * `FEATURE_STAGE` is the only place a feature's readiness is stated. Help filtering,\n * the runtime refusal, the command registry and the two `app create` prompts all read\n * it through `isFeatureAvailable`, so flipping a row to `'ga'` releases that feature\n * everywhere at once. See `RELEASE-CHECKLIST.md` \u2192 *Before public-apps GA* /\n * *Before UI-apps GA*.\n *\n * Two of the four names are `Capability` values from `app-types/capabilities.ts`,\n * deliberately: commands already declare `requires` in `commands/definitions.ts`, so\n * command gating falls straight out of the field that is already there. The other two\n * gate a prompt choice and a flag value, neither of which is a command.\n */\nimport { CliError } from './errors';\nimport { messages } from '../lang/en';\n\nexport type PreviewFeature =\n /** `app deploy` / `app rollback`. Also a `Capability`. */\n | 'account-install'\n /** `app submit` / `app status` / `app withdraw`. Also a `Capability`. */\n | 'review-lifecycle'\n /** The *UI app* choice in `app create`'s app-type prompt. */\n | 'ui-app-type'\n /** `app create --distribution public`. */\n | 'public-distribution';\n\nexport type FeatureStage = 'ga' | 'preview';\n\n/**\n * Readiness per feature. Flip a row to `'ga'` to release it.\n *\n * Everything not listed here is GA by construction \u2014 absence from this table is not a\n * gate, so a new command is public unless someone opts it in.\n */\nexport const FEATURE_STAGE: Readonly<Record<PreviewFeature, FeatureStage>> = {\n 'account-install': 'preview',\n 'review-lifecycle': 'preview',\n 'ui-app-type': 'preview',\n 'public-distribution': 'preview',\n} as const;\n\n/**\n * Is this feature usable in this build \u2014 either released, or built with `PREVIEW=1`?\n *\n * Reads the build global directly rather than a module-level constant, for two reasons.\n * esbuild substitutes it here, so a published build folds this to\n * `FEATURE_STAGE[feature] === 'ga'` with no runtime flag left. And under jest the read\n * happens per call, which is what lets a test flip build states without re-importing\n * every module that has already captured a constant \u2014 the bug that a `PREVIEW_BUILD`\n * export caused when this was first written.\n */\nexport function isFeatureAvailable(feature: PreviewFeature): boolean {\n return FEATURE_STAGE[feature] === 'ga' || __BREVO_PREVIEW__;\n}\n\n/**\n * Refuse a gated feature.\n *\n * One message for all four, unlike `assertCapability` in `app-types/capabilities.ts`,\n * which takes the caller's wording so each command keeps the error string it shipped\n * with. Not an inconsistency: those are existing contracts a script may match on,\n * while this is new surface with no callers to break, and it answers a different\n * question (\"this feature isn't released\") than a capability error does (\"this app\n * doesn't support that\").\n *\n * Reachable in a published build only through `app create --distribution public` \u2014\n * the flag parses before the gate sees it, so the value has to be refused rather than\n * hidden. Every gated *command* is gone from the binary and never reaches here.\n */\nexport function assertFeatureAvailable(feature: PreviewFeature): void {\n if (isFeatureAvailable(feature)) return;\n throw new CliError(messages.PREVIEW_FEATURE_UNAVAILABLE);\n}\n", "import { Command } from 'commander';\nimport type { Capability } from '../app-types/capabilities';\nimport { CliError } from './errors';\nimport { FEATURE_STAGE, assertFeatureAvailable, isFeatureAvailable } from './preview';\nimport type { PreviewFeature } from './preview';\nimport { removedCommandsIn } from './removed-commands';\nimport type { RemovedCommand } from './removed-commands';\n\nexport interface CommandOption {\n flags: string;\n description: string;\n parser?: (value: string) => unknown;\n}\n\nexport interface CommandArgument {\n name: string;\n description: string;\n}\n\nexport interface CommandDefinition {\n name: string;\n description: string;\n arguments?: CommandArgument[];\n options?: CommandOption[];\n examples?: string[];\n /**\n * The capability an app must have for this command to apply \u2014 `review-lifecycle` for the\n * public-app review commands, `account-install` for deploy/rollback. See\n * `src/app-types/capabilities.ts`.\n *\n * **Declarative metadata, NOT a runtime guard.** The registry does not enforce it, and\n * that is deliberate rather than unfinished: each gated command already throws its own\n * tested message with its own exit code, and a generic interceptor here would replace\n * them all with one string \u2014 which `CLAUDE.md` counts as a user-visible break for any\n * script matching on it. Enforcement stays in the commands, via `assertCapability`, which\n * reads the same table.\n *\n * What it is for: making the rule enumerable. `bin/index.ts` currently states it as prose\n * (\"App-review commands (public apps only):\") in a hand-aligned help block, and the agent\n * docs restate it again \u2014 three copies that can drift. This field is the source those can\n * be generated from, and `command-capabilities.test.ts` already asserts it agrees with the\n * matrix so it cannot rot in the meantime.\n */\n requires?: Capability;\n /**\n * Keep the command out of `brevo app --help` while leaving it registered and callable.\n *\n * Distinct from the pre-GA gate below, which hides *and* refuses: this one hides only.\n * The command runs exactly as it always did for anyone who types it \u2014 it just stops\n * being advertised. Used for a command we don't want to put in front of users yet but\n * still need working for QA and the smoke tests (`app withdraw`).\n *\n * Hiding is only half the job: the hand-aligned root screen in `lib/help.ts` is a\n * separate renderer that Commander's `hidden` cannot reach, so a command set hidden\n * here must also be absent from `formatRootHelp`. `preview-gate.test.ts` asserts both.\n */\n hidden?: boolean;\n handler: (opts: Record<string, unknown>, ...args: unknown[]) => void | Promise<void>;\n}\n\nexport interface SubcommandGroupDefinition {\n name: string;\n description: string;\n commands: CommandDefinition[];\n}\n\n/**\n * The pre-GA feature a command's `requires` names, if any.\n *\n * `Capability` is the wider set \u2014 `oauth-flow`, `redirect-uris` and `scaffold-feature`\n * are capabilities that no gate applies to. Only the names that also appear in\n * `FEATURE_STAGE` are gateable, so the lookup is a membership test rather than a cast.\n */\nexport function previewFeatureOf(def: CommandDefinition): PreviewFeature | undefined {\n if (!def.requires) return undefined;\n return def.requires in FEATURE_STAGE ? (def.requires as PreviewFeature) : undefined;\n}\n\n/**\n * Register a flat command on the program.\n *\n * A command gated behind an unreleased feature is registered `hidden` rather than\n * skipped. Skipping would drop it from the parser too, so invoking it would produce\n * Commander's `unknown command` \u2014 which tells the user the CLI has no such command,\n * when in fact it has one that isn't released. Registering it hidden keeps the typed\n * refusal (`assertFeatureAvailable`) and its exit code.\n *\n * Note this is a *feature* gate, not the capability gate `requires` is documented as\n * not being. The distinction is real: a capability gate depends on which app you are\n * acting on and each command answers it in its own words, while this one depends only\n * on whether the feature has shipped and is the same answer for every command. That\n * is why one interceptor is right here and wrong there.\n *\n * `def.hidden` is the other route to an unlisted command, and it is not the same thing:\n * it suppresses the help entry and nothing else. The command still parses, still runs,\n * and gains no refusal from being hidden.\n */\nfunction registerCommand(parent: Command, def: CommandDefinition): void {\n const gatedBehind = previewFeatureOf(def);\n const gateHides = Boolean(gatedBehind) && !isFeatureAvailable(gatedBehind!);\n const hidden = def.hidden === true || gateHides;\n const cmd = parent.command(def.name, { hidden }).description(def.description);\n\n if (def.arguments) {\n for (const arg of def.arguments) {\n cmd.argument(arg.name, arg.description);\n }\n }\n\n if (def.options) {\n for (const opt of def.options) {\n if (opt.parser) {\n cmd.option(opt.flags, opt.description, opt.parser);\n } else {\n cmd.option(opt.flags, opt.description);\n }\n }\n }\n\n if (def.examples?.length) {\n cmd.addHelpText(\n 'after',\n '\\nExamples:\\n' + def.examples.map((e) => ` $ ${e}`).join('\\n') + '\\n',\n );\n }\n\n cmd.action((...actionArgs) => {\n // Re-checked here rather than reusing `hidden` above: that was computed at\n // registration, and the refusal must reflect the state at invocation. Same answer\n // in practice, but the gate reads the credentials file and the env, and neither\n // belongs frozen in a module-init constant.\n if (gatedBehind) assertFeatureAvailable(gatedBehind);\n // Commander passes positional args first, then options object, then Command\n const opts = actionArgs.at(-2) as Record<string, unknown>;\n const positionalArgs = actionArgs.slice(0, -2);\n return def.handler(opts, ...positionalArgs);\n });\n}\n\n/**\n * Register a command that no longer exists, purely so it can say so.\n *\n * See `lib/removed-commands.ts` for why a removed name is worth registering at all.\n * Four settings make the message reachable however the old invocation was typed, and\n * each one is load-bearing:\n *\n * - `hidden` keeps it out of `brevo app --help`. It is not a command on offer; it is a\n * forwarding address.\n * - `allowUnknownOption` plus a variadic argument swallow the flags the command used to\n * take, so `brevo app update --name X` gets the migration message instead of\n * Commander's `unknown option '--name'` \u2014 which would bury the one thing the user\n * needs to know behind a complaint about a flag that is gone either way.\n * - `allowExcessArguments` does the same for stray operands.\n * - `helpOption(false)` sends `brevo app update --help` to the message too. Left on,\n * Commander would print a usage screen for a command that isn't there and exit `0`,\n * which is the one answer a script must not get.\n */\nfunction registerRemovedCommand(parent: Command, removed: RemovedCommand): void {\n const cmd = parent\n .command(removed.name, { hidden: true })\n .allowUnknownOption(true)\n .allowExcessArguments(true)\n .helpOption(false)\n .argument('[args...]')\n .action(() => {\n throw new CliError(removed.message);\n });\n\n // `brevo app help update` is the one route that reaches neither the action nor the\n // help option: Commander's help command calls the target's `help()` directly\n // (`_dispatchHelpCommand`, which does not skip hidden commands), printing a usage\n // screen and exiting `0`. There is no hook in front of that, so the method itself is\n // replaced \u2014 the same message, by the same route as every other invocation.\n cmd.help = () => {\n throw new CliError(removed.message);\n };\n}\n\n/**\n * Register a group of subcommands (e.g. `app create`, `app list`).\n */\nfunction registerSubcommandGroup(parent: Command, group: SubcommandGroupDefinition): void {\n const groupCmd = parent.command(group.name).description(group.description);\n for (const def of group.commands) {\n registerCommand(groupCmd, def);\n }\n for (const removed of removedCommandsIn(group.name)) {\n registerRemovedCommand(groupCmd, removed);\n }\n}\n\n/**\n * Register all commands and subcommand groups onto the program.\n */\nexport function registerAll(\n program: Command,\n commands: CommandDefinition[],\n groups: SubcommandGroupDefinition[],\n): void {\n for (const cmd of commands) {\n registerCommand(program, cmd);\n }\n for (const removed of removedCommandsIn()) {\n registerRemovedCommand(program, removed);\n }\n for (const group of groups) {\n registerSubcommandGroup(program, group);\n }\n}\n", "import { Command, Help } from 'commander';\nimport { BREVO_CLI_REFERENCE_URL } from './constants';\nimport { isFeatureAvailable } from './preview';\nimport type { PreviewFeature } from './preview';\n\n/**\n * A block of help lines that only renders when its feature has shipped.\n *\n * The gated sections here line up 1:1 with the `requires` values in\n * `commands/definitions.ts`, which is what makes `FEATURE_STAGE` the single source\n * of truth for both the help screen and the runtime refusal (BEX-405). Keeping the\n * capability name on the section rather than hardcoding a boolean is the whole\n * point: flipping the table at GA restores these sections with no edit here.\n */\nfunction gatedSection(feature: PreviewFeature, lines: string[]): string[] {\n return isFeatureAvailable(feature) ? lines : [];\n}\n\n/** Accepted values of `--distribution`, narrowed while public distribution is pre-GA. */\nexport function distributionValues(): string {\n return isFeatureAvailable('public-distribution') ? 'private|public' : 'private';\n}\n\n/** `app create`'s one-line description \u2014 mentions UI apps only when they're offered. */\nexport function createDescription(): string {\n return isFeatureAvailable('ui-app-type')\n ? 'Create a new app (OAuth, or a UI app via the prompts)'\n : 'Create a new OAuth app';\n}\n\n/**\n * The root `brevo --help` screen.\n *\n * Hand-aligned rather than generated: it groups commands by what they're for\n * (app / deployment / review / skill / scope) and shows each one's flags inline,\n * which Commander's default two-column layout can't express. The grouping\n * mirrors the capability gates in `commands/definitions.ts` \u2014 see\n * `command-capabilities.test.ts`, which is the executable copy of that rule.\n *\n * Because it is hand-aligned, the pre-GA sections have to be filtered here too \u2014\n * Commander's `hidden` flag governs its own generated output (`brevo app --help`)\n * and cannot reach this string. Both read `isFeatureAvailable`, so there is one\n * decision and two renderers, not two decisions.\n */\nfunction formatRootHelp(description: string): string {\n return [\n `Usage: brevo [options] [command]`,\n ``,\n description,\n ``,\n `Options:`,\n ` -V, --version output the version number`,\n ` -h, --help display help for command`,\n ``,\n `Commands:`,\n ` brevo login [--browser] [--json] Authenticate with your Brevo account`,\n ` brevo logout [--json] Clear stored credentials`,\n ` brevo whoami [--json] Show current authenticated user`,\n ``,\n `App commands:`,\n ` brevo app init Quick setup \u2014 login, create app, and scaffold`,\n // `--distribution` itself is GA \u2014 only the `public` VALUE is gated, so the flag\n // stays and its value list narrows. Same for the app-type prompt: a locked run\n // is OAuth-only, so the description stops advertising a choice it won't offer.\n ` brevo app create [--name] [--distribution ${distributionValues()}]`,\n ` [--redirect-uri <url>...] [--logo-uri <url>] [--json]`,\n ` ${createDescription()}`,\n ` brevo app list [--json] List all apps in your account`,\n ` brevo app credentials [--app-id <id>] [--reveal-secret] [--json]`,\n ` Show an app's client ID and secret`,\n ` brevo app scaffold [--app-id <id>] [--json] Add a feature (e.g. OAuth server) here`,\n ` brevo app start [feature] [--port <port>] Run a scaffolded feature locally`,\n ` brevo app upload [--yes] [--json] Push app-config.json to Brevo`,\n ` brevo app delete [--app-id <id>] [--force] [--json]`,\n ` Delete an app`,\n ``,\n // ELIMINATION SITE \u2014 `__BREVO_PREVIEW__` wraps the call rather than living inside a\n // helper, because an array passed as a function *argument* is still evaluated: a\n // `previewOnlySection(feature, [...])` helper left every one of these lines in the\n // published bundle as a readable string. Folding `false ? \u2026 : []` at the call site is\n // what removes the array itself.\n //\n // **The build flag is therefore the outer authority for help text, above\n // `FEATURE_STAGE`.** At GA that is a trap \u2014 flipping a row to `'ga'` is not enough,\n // since a published build still has `__BREVO_PREVIEW__ === false` and would keep\n // hiding the restored section. The wrapper must be removed by hand at the same time;\n // `RELEASE-CHECKLIST.md` lists it, alongside the identical trap in\n // `commands/preview-definitions.ts`.\n ...(__BREVO_PREVIEW__\n ? gatedSection('account-install', [\n `App-deployment commands (UI apps only):`,\n ` brevo app deploy [account-id] [--app-id <id>] [--force] [--json]`,\n ` Make an app available in an account`,\n ` brevo app rollback [account-id] [--app-id <id>] [--force] [--json]`,\n ` Roll back an app from an account`,\n ``,\n ])\n : []),\n ...(__BREVO_PREVIEW__\n ? gatedSection('review-lifecycle', [\n `App-review commands (public apps only):`,\n ` brevo app submit [--app-id <id>] [--json] Submit a public app for review`,\n ` brevo app status [--app-id <id>] [--json] Show an app's review status`,\n // `brevo app withdraw` is deliberately absent. It is registered and callable in\n // a preview build, just marked `hidden` in `commands/preview-definitions.ts` so\n // it is advertised on neither help screen. Commander's `hidden` governs its own\n // generated output and cannot reach this hand-written string, so the omission\n // has to be made here by hand \u2014 the same two-renderers/one-decision split the\n // comment above describes for the gate. Restore both together.\n ``,\n ])\n : []),\n `Skill commands:`,\n ` brevo skill:cli install [--json] Install the brevo-cli Claude Code skill`,\n ` brevo skill:cli uninstall [--json] Remove the brevo-cli skill`,\n ``,\n `Scope commands:`,\n ` brevo app available-scopes [--web] [--json] List OAuth scopes supported by the IdP`,\n ` (--web opens the catalog in a browser)`,\n ``,\n `Run \\`brevo <command> --help\\` for details on a specific command.`,\n ``,\n `Examples:`,\n ` $ brevo login # authenticate interactively`,\n ` $ brevo app init # guided setup`,\n ` $ brevo app create --name \"My App\" --json # create app, JSON output`,\n ` $ brevo app list --json # list apps as JSON`,\n ` $ brevo app scaffold --app-id APPID # generate starter code`,\n ` $ brevo app start oauth --port 3000 # start OAuth test server`,\n ` $ brevo app available-scopes --web # browse OAuth scope catalog`,\n ``,\n `Docs: ${BREVO_CLI_REFERENCE_URL}`,\n ``,\n ].join('\\n');\n}\n\n/**\n * Build the `formatHelp` callback for the root program.\n *\n * Commander copies help configuration down to every subcommand\n * (`copyInheritedSettings`), so a single `formatHelp` also runs for\n * `brevo app create --help`. Returning the root screen there would repeat the\n * whole command list and hide the subcommand's own flags \u2014 the opposite of what\n * \"Run `brevo <command> --help` for details\" promises. So only the root gets\n * the hand-aligned screen; everything else falls through to Commander's default\n * formatter, which builds its output from the command's own options and\n * arguments.\n */\nexport function createHelpFormatter(root: Command): (cmd: Command, helper: Help) => string {\n return (cmd, helper) => {\n if (cmd !== root) {\n return Help.prototype.formatHelp.call(helper, cmd, helper);\n }\n return formatRootHelp(helper.commandDescription(cmd));\n };\n}\n", "import { AbortError } from './errors';\n\nexport function isAbortError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n\n // Check error name/constructor first (more reliable than message strings)\n const name = err.name?.toLowerCase() ?? '';\n if (name === 'exitprompterror') return true;\n\n const msg = err.message.toLowerCase();\n return (\n msg.includes('readline was closed') ||\n msg.includes('user force closed') ||\n msg.includes('exitprompterror') ||\n msg.includes('prompt was closed')\n );\n}\n\nexport async function withAbortHandler<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n if (isAbortError(err)) {\n throw new AbortError();\n }\n throw err;\n }\n}\n", "import { withAbortHandler } from './abort';\n\n/**\n * Wraps a command handler with abort handling (Ctrl+C during inquirer prompts).\n *\n * Usage:\n * export const myCommand = withCommandHandler(async (opts) => {\n * // command logic\n * });\n */\nexport function withCommandHandler<T extends Record<string, unknown>>(\n fn: (opts: T) => Promise<void>,\n): (opts: T) => Promise<void> {\n return (opts: T) => withAbortHandler(() => fn(opts));\n}\n", "import { CliError, ApiError, ErrorCode } from './errors';\nimport { EXIT_CODES } from './exit-codes';\n\nlet emitted = false;\n\n/**\n * Write JSON data to stdout (for --json flag output).\n * Centralizes the JSON serialization pattern used by all commands.\n */\nexport function jsonOutput(data: unknown): void {\n emitted = true;\n process.stdout.write(JSON.stringify(data) + '\\n');\n}\n\n/**\n * True once any command has written a JSON document to stdout.\n *\n * The top-level error handler in `bin/index.ts` uses this to stay a *fallback*:\n * commands that already describe their own failure in JSON (`whoami` reports\n * `{authenticated:false,reason:'no_key'}` and then throws) must not get a\n * second document appended, or `--json` stdout stops being a single parseable\n * value.\n */\nexport function hasEmittedJson(): boolean {\n return emitted;\n}\n\n/** Reset the emitted flag. Tests only \u2014 each case needs a clean slate. */\nexport function resetJsonOutputState(): void {\n emitted = false;\n}\n\nexport interface JsonErrorEnvelope {\n error: {\n name: string;\n message: string;\n exitCode: number;\n /** Present on `ApiError` only \u2014 the classified `ErrorCode`, when the API gave one. */\n code?: ErrorCode;\n /** Present on `ApiError` only \u2014 the HTTP status behind the failure. */\n statusCode?: number;\n };\n}\n\n/**\n * Shape a thrown error as the `--json` failure document.\n *\n * The `error` key is the discriminator: no success payload emits one, so a\n * script can branch on its presence without needing an `ok` flag added to\n * every existing success shape.\n */\nexport function buildJsonError(err: unknown): JsonErrorEnvelope {\n const envelope: JsonErrorEnvelope = {\n error: {\n name: err instanceof Error ? err.name : 'Error',\n message: err instanceof Error ? err.message : String(err),\n exitCode: err instanceof CliError ? err.exitCode : EXIT_CODES.ERROR,\n },\n };\n\n if (err instanceof ApiError) {\n if (err.errorCode) envelope.error.code = err.errorCode;\n envelope.error.statusCode = err.statusCode;\n }\n\n return envelope;\n}\n\n/**\n * Write the `--json` failure document to stdout, if one is owed.\n *\n * Called from the top-level error handler so that *every* command honours\n * `--json` on its failure paths, not just the handful that guard the error\n * themselves. Two conditions keep it from doing harm:\n *\n * - `--json` must actually be present, so human-facing runs are untouched.\n * - nothing may have been emitted yet, so a command that already reported its\n * own failure in JSON stays the single source of that document.\n */\nexport function emitJsonError(err: unknown, argv: string[] = process.argv): void {\n if (!argv.includes('--json')) return;\n if (hasEmittedJson()) return;\n jsonOutput(buildJsonError(err));\n}\n", "import inquirer from 'inquirer';\nimport { appService } from '../../container';\nimport { createSpinner } from '../../lib/ui';\nimport { logInfo } from '../../lib/logger';\nimport { CliError } from '../../lib/errors';\nimport { messages } from '../../lang/en';\n\n/**\n * Refuse an app picker there is no terminal to draw. Call this before any\n * picker \u2014 `promptAppSelection` here or `appService.pickApp` \u2014 on a command\n * whose app can also be named with `--app-id`.\n *\n * Both pickers render their choice list to stdout, which under `--json` breaks\n * the \"stdout is exactly one parseable document\" contract the agent docs\n * promise, and leaks app ids and client ids into what a script is trying to\n * parse. Off a TTY inquirer then aborts with a raw ERR_USE_AFTER_CLOSE readline\n * stack rather than anything a caller can act on. Refusing up front also avoids\n * the apps-list round trip, so this must run *before* the fetch.\n *\n * `command` names the caller in the message so the fix is copy-pasteable.\n */\nexport function assertAppSelectionAllowed(command: string, jsonMode?: boolean): void {\n if (jsonMode || !process.stdin.isTTY) {\n throw new CliError(messages.APP_SELECT_NON_INTERACTIVE(command));\n }\n}\n\n/**\n * Fetch the full apps list and prompt the user to pick one, returning both the\n * app id and a human label (name \u2192 client_id \u2192 id) for use in confirmation\n * copy. Shared by `app delete` and `app withdraw`.\n */\nexport async function promptAppSelection(\n promptMessage: string,\n): Promise<{ appId: string; appLabel: string }> {\n const listSpinner = createSpinner('Fetching apps...');\n let apps;\n try {\n apps = await appService.fetchAppsList();\n } finally {\n listSpinner.stop();\n }\n if (apps.length === 0) {\n logInfo(`\\n ${messages.APP_LIST_EMPTY}\\n`);\n throw new CliError(messages.APP_LIST_EMPTY);\n }\n\n const { selectedApp } = await inquirer.prompt([\n {\n type: 'rawlist',\n name: 'selectedApp',\n message: promptMessage,\n choices: apps.map((a) => ({\n name: `${a.name || 'App ' + a.app_id} (App ID: ${a.app_id}, Client ID: ${a.client_id})`,\n value: a.app_id,\n })),\n },\n ]);\n const appId = selectedApp as string;\n const matched = apps.find((a) => a.app_id === appId);\n return { appId, appLabel: matched?.name || matched?.client_id || appId };\n}\n", "import { execFile } from 'node:child_process';\n\nexport function openBrowser(url: string): void {\n if (process.platform === 'darwin') {\n execFile('open', [url]);\n } else if (process.platform === 'win32') {\n execFile('cmd', ['/c', 'start', '', url]);\n } else {\n execFile('xdg-open', [url]);\n }\n}\n", "/**\n * The OAuth integration app type \u2014 the CLI's original and default app type.\n *\n * Detection is by ELIMINATION rather than by a positive marker: `app-config.json` has no\n * `appType` key, and an OAuth app is simply one with no `ui_app` block. That asymmetry is\n * deliberate (it is the wire's own discriminator, see `CLAUDE.md`), which is why both\n * predicates here are the negation of the UI module's.\n */\nimport { messages } from '../../lang/en';\nimport type { AppTypeModule } from '../contract';\nimport { isUiAppConfigShape, isUiAppRecordShape } from '../ui/detect';\n\nexport const oauthAppType: AppTypeModule = {\n id: 'oauth',\n label: messages.APP_TYPE_OAUTH,\n availability: 'ga',\n\n detectConfig: (config) => !isUiAppConfigShape(config),\n detectRecord: (app) => !isUiAppRecordShape(app),\n\n // Always recoverable: an OAuth app's entire configuration lives on the app record itself\n // (callbacks and scopes come back on the read), not in an upload snapshot. A record with\n // neither yet still recovers \u2014 the scaffold fills in its own defaults, which is exactly\n // what `app create` wrote in the first place. Only the record's existence is required.\n recoverableFromRecord: (app) => !!app,\n\n // Nothing type-specific to check locally. The OAuth checks `app upload` runs \u2014 at least\n // one redirect URL, and scope validation \u2014 are capability-driven rather than type-driven\n // (see `capabilities.ts`), so they stay in the command where their messages and exit codes\n // are asserted.\n validateConfig: () => {},\n\n // The server echoes an OAuth app's `auth` block as it stored it, and every key in it is\n // authored. Nothing is injected or stamped, so there is nothing to normalize away.\n wireOnlyKeys: [],\n};\n", "/**\n * The UI app type (BEX-290) \u2014 an action link that renders inside a Brevo CRM record.\n *\n * `preview`: shipped in the CLI, not live on the platform. That is metadata for doc\n * generation only \u2014 the CLI deliberately has no runtime guard, see `contract.ts`.\n *\n * Note the descriptor does NOT expose this type's prompts. `./authoring` is imported directly\n * by `app create`, so `app list` doesn't pull `inquirer` and the whole registry-read flow in\n * just to print a `Type:` row.\n */\nimport { messages } from '../../lang/en';\nimport { validateUiApp } from '../../lib/validators';\nimport type { AppTypeModule } from '../contract';\nimport { isUiAppConfigShape, isUiAppRecordShape } from './detect';\n\n/**\n * Keys the platform owns on a stored `ui_app` block, none of which belong in\n * `app-config.json`:\n *\n * - `link_target` \u2014 `app upload` injects `_blank`; the server also defaults it and echoes\n * it back. Authoring it only invited a partner to edit in a `_self` that\n * 400s.\n * - `version` \u2014 the snapshot version the server manages.\n * - `extension_point_name` \u2014 the dotted slot name the platform resolves from each entry's\n * `surface_point_name` slug and stamps onto its own copy. It lives\n * INSIDE an entry, one level down, which is why the strip recurses.\n *\n * All three exist on the server's side of a comparison only. Left in, the first successful\n * upload writes them into the file this command just decided to keep them out of, and every\n * subsequent upload reports drift on fields the partner cannot edit \u2014 so \"already up to date\"\n * would never print for a UI app again.\n */\nconst UI_APP_WIRE_ONLY_KEYS: readonly string[] = [\n 'link_target',\n 'version',\n 'extension_point_name',\n] as const;\n\nexport const uiAppType: AppTypeModule = {\n id: 'ui',\n label: messages.APP_TYPE_UI,\n availability: 'preview',\n\n detectConfig: isUiAppConfigShape,\n detectRecord: isUiAppRecordShape,\n\n // Recoverable only when the server actually echoed the block, which is not a detail of\n // the config \u2014 it IS the config, and the app-type discriminator.\n //\n // **`isUiAppRecordShape` is what makes this reachable, and why it must not be reused\n // here.** That predicate's fallback (`detect.ts`) calls a record a UI app whenever it\n // carries no OAuth material \u2014 no `client_id`, no `redirect_uris` \u2014 *whether or not it has\n // a block*. So a record with neither is classified `ui` and then arrives here with\n // nothing to write. Right for labelling a row; wrong for deciding there is a config.\n //\n // Refusal, not a partial write, because the omission is invisible: a config without\n // `ui_app` doesn't read as an incomplete UI app, it reads as a perfectly valid OAuth one,\n // and the next `app upload` pushes an `auth` block where `ui_app` belonged.\n //\n // This once justified itself with a *different*, specific case \u2014 \"a UI app created but\n // never uploaded has no snapshot and comes back without it\". **That case is false**:\n // `persistCreateResultTx` writes the `app_versions` row inside the create transaction\n // with the `ui_app` in its snapshot, and `GET /cli/apps/{id}` serves it back from there,\n // so a never-uploaded UI app *is* recoverable (verified against app-store-bo-be,\n // 2026-08-13). Correcting that is not a reason to drop the guard \u2014 the classifier above\n // is, and it is independent of anything the server does with snapshots.\n recoverableFromRecord: (app) => !!app?.ui_app,\n\n validateConfig: (config) => {\n // Shape only, and only ever about the FILE \u2014 a missing label, a bare-string placement, a\n // pre-BEX-290 field name. Whether a slot is registered is the upload endpoint's call; the\n // CLI holds no copy of that registry, on purpose.\n validateUiApp(config.ui_app);\n },\n\n wireOnlyKeys: UI_APP_WIRE_ONLY_KEYS,\n};\n", "/**\n * The app-type registry.\n *\n * A static object literal, deliberately \u2014 not a plugin loader and not dynamic registration.\n * Two types (three, soon) is not a plugin problem, and a static map is what lets the compiler\n * keep checking exhaustiveness over `AppTypeId`. That checking is doing real work: it is what\n * surfaced the second unguarded null dereference when `redirect_uris` was typed as nullable.\n *\n * Order matters in `resolveFromConfig` / `resolveFromRecord`: both fall through to `oauth`,\n * because OAuth is detected by elimination (no `ui_app` block) and would otherwise match\n * everything.\n */\nimport type { ProjectConfig } from '../lib/config';\nimport type { AppRecordLike, AppTypeId, AppTypeModule } from './contract';\nimport { oauthAppType } from './oauth';\nimport { uiAppType } from './ui';\n\nexport type { AppTypeId, AppTypeModule, AppRecordLike } from './contract';\nexport {\n assertCapability,\n capabilitiesFor,\n supports,\n type Capability,\n type Distribution,\n} from './capabilities';\n\nexport const APP_TYPES: Readonly<Record<AppTypeId, AppTypeModule>> = {\n oauth: oauthAppType,\n ui: uiAppType,\n};\n\n/**\n * Every type except the fallback, in detection order. `oauth` is excluded because its\n * predicate is the negation of the others' and matches anything that reaches it.\n */\nconst POSITIVELY_DETECTED: readonly AppTypeModule[] = [uiAppType];\n\n/** Which type does this local `app-config.json` describe? Never null \u2014 OAuth is the default. */\nexport function resolveFromConfig(\n config: Pick<ProjectConfig, 'ui_app'> | null | undefined,\n): AppTypeModule {\n return POSITIVELY_DETECTED.find((type) => type.detectConfig(config)) ?? oauthAppType;\n}\n\n/**\n * Which type is this *server* record?\n *\n * Weaker than the config path and knowingly so: the list endpoint echoes no `ui_app` block\n * today, so the UI module falls back to the absence of every piece of OAuth material. See\n * `isUiAppRecord` for why that requires BOTH an empty client_id and no callbacks.\n */\nexport function resolveFromRecord(app: AppRecordLike | null | undefined): AppTypeModule {\n return POSITIVELY_DETECTED.find((type) => type.detectRecord(app)) ?? oauthAppType;\n}\n\nexport function appTypeById(id: AppTypeId): AppTypeModule {\n return APP_TYPES[id];\n}\n", "import inquirer from 'inquirer';\nimport { isAuthenticated, isUiAppConfig, readProjectConfig } from '../lib/config';\nimport { logSuccess, logInfo, logWarn, logDebug } from '../lib/logger';\nimport { createSpinner, indentChoices } from '../lib/ui';\nimport { messages } from '../lang/en';\nimport { ApiError, AuthExpiredError, CliError, ErrorCode } from '../lib/errors';\nimport { withCommandHandler } from '../lib/command-handler';\nimport { loginCommand } from './login';\nimport { createCommand } from './app/create';\nimport { scaffoldCommand } from './app/scaffold';\nimport { appService, accountService } from '../container';\n\n/**\n * Did the backend reject our credentials, or did the request just not land?\n *\n * Only the former justifies sending the user through a login. A network blip,\n * a 5xx or an unexpected throw says nothing about whether the session is valid.\n *\n * An auth gateway in front of the API (Cloudflare Access and friends) answers\n * with its own 401/403 interstitial, which the client surfaces as\n * `AUTH_GATEWAY`. That is not our credentials being refused \u2014 `brevo login`\n * cannot clear it \u2014 so it belongs with the inconclusive results, where the\n * real gateway error reaches the user on the next call.\n */\nfunction isAuthRejection(err: unknown): boolean {\n if (err instanceof AuthExpiredError) return true;\n if (!(err instanceof ApiError)) return false;\n if (err.errorCode === ErrorCode.AUTH_GATEWAY) return false;\n return err.statusCode === 401 || err.statusCode === 403;\n}\n\nasync function ensureLoggedIn(): Promise<void> {\n if (isAuthenticated()) {\n // Local creds exist \u2014 verify they still work against the backend before\n // the user invests time in the init prompts. Without this, init proceeds\n // happily and the first real API call (app create) hits a 401 mid-flow.\n const spinner = createSpinner('Verifying credentials...');\n try {\n await accountService.getAccount();\n spinner.stop();\n logSuccess(messages.INIT_ALREADY_LOGGED_IN);\n return;\n } catch (err) {\n spinner.stop();\n if (!isAuthRejection(err)) {\n // The probe is a courtesy, not a gate. Announcing \"expired\" and opening\n // a browser here would be wrong \u2014 and would fail for the same reason\n // the probe did. Carry on; the reactive 401 handler still catches\n // genuinely dead credentials later in the flow.\n logDebug('init credential probe inconclusive', {\n reason: err instanceof Error ? err.message : String(err),\n });\n logWarn(messages.INIT_VERIFY_UNAVAILABLE);\n return;\n }\n logWarn(messages.AUTH_EXPIRED);\n // Fall through to the login flow below.\n }\n }\n logInfo(messages.INIT_STEP_LOGIN);\n await loginCommand({ suppressNextSteps: true });\n if (!isAuthenticated()) {\n throw new CliError('Login failed.');\n }\n}\n\nasync function appExistsOnServer(appId: string): Promise<boolean> {\n if (!appId) return false;\n const spinner = createSpinner('Verifying app...');\n try {\n const app = await appService.fetchApp(appId);\n return app !== null;\n } catch {\n return false;\n } finally {\n spinner.stop();\n }\n}\n\nasync function promptLinkedAppAction(\n configAppId: string,\n linkedName: string,\n): Promise<'scaffold' | 'create' | 'skip'> {\n logSuccess(messages.INIT_APP_LINKED(linkedName));\n const { action } = await inquirer.prompt([\n {\n type: 'list',\n name: 'action',\n message: messages.INIT_APP_ACTION,\n choices: indentChoices([\n { name: 'Scaffold this app', value: 'scaffold' },\n { name: 'Create a new app', value: 'create' },\n { name: \"Skip \u2014 I'm all set\", value: 'skip' },\n ]),\n },\n ]);\n return action;\n}\n\n/**\n * The closing line, chosen by what the run actually left on disk.\n *\n * Read back from the project rather than threaded out of `create`/`scaffold`: both\n * `process.chdir()` into the project they wrote, so the config in cwd is theirs, and\n * neither returns anything. A run that wrote no config at all (a declined scaffold)\n * falls through to the OAuth line, which is what it was before any of this.\n */\nfunction initDoneMessage(): string {\n return isUiAppConfig(readProjectConfig()) ? messages.INIT_DONE_UI_APP : messages.INIT_DONE;\n}\n\nexport const initCommand = withCommandHandler(\n async (_options: Record<string, unknown>): Promise<void> => {\n process.stdout.write(`\\n ${messages.INIT_WELCOME}\\n`);\n process.stdout.write(' \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\\n\\n');\n\n await ensureLoggedIn();\n\n process.stdout.write('\\n');\n const projectConfig = readProjectConfig();\n const configAppId = typeof projectConfig?.appId === 'string' ? projectConfig.appId.trim() : '';\n const linkedName = projectConfig?.appName || configAppId;\n\n if (configAppId && (await appExistsOnServer(configAppId))) {\n const action = await promptLinkedAppAction(configAppId, linkedName);\n\n if (action === 'skip') {\n logInfo(`\\n ${initDoneMessage()}\\n`);\n return;\n }\n\n if (action === 'scaffold') {\n process.stdout.write('\\n');\n await scaffoldCommand({});\n logInfo(`\\n ${initDoneMessage()}\\n`);\n return;\n }\n // action === 'create' \u2014 fall through\n } else if (configAppId) {\n logWarn(\n `App \"${linkedName}\" (from app-config.json) no longer exists on the server. It may have been deleted.`,\n );\n logInfo(messages.INIT_STEP_CREATE);\n } else {\n logInfo(messages.INIT_STEP_CREATE);\n }\n\n process.stdout.write('\\n');\n await createCommand({});\n logInfo(`\\n ${initDoneMessage()}\\n`);\n },\n);\n", "import inquirer from 'inquirer';\nimport {\n saveCredentials,\n saveOauthCredentials,\n clearCredentials,\n getCredentialsPath,\n getOrganizationId,\n clearAppsCache,\n} from '../lib/config';\nimport {\n CLI,\n BREVO_DASHBOARD_API_KEYS_URL,\n BREVO_API_KEY_DOCS_URL,\n ENDPOINTS,\n OAUTH_PROXY_URL,\n} from '../lib/constants';\nimport { openBrowser } from '../lib/browser';\nimport { logSuccess, logInfo, logError } from '../lib/logger';\nimport { messages } from '../lang/en';\nimport { CliError, ApiError } from '../lib/errors';\nimport { EXIT_CODES } from '../lib/exit-codes';\nimport { withCommandHandler } from '../lib/command-handler';\nimport { createCommand } from './app/create';\nimport { appService, accountService, client } from '../container';\nimport { printBox, createSpinner, indentChoices } from '../lib/ui';\nimport { jsonOutput } from '../lib/json-output';\nimport { AccountResponse } from '../types';\nimport { runBrowserLoginFlow } from '../services/browser-auth';\n\n// On re-login, the cached per-app clientId/clientSecret values belong to apps\n// owned by the previously-authenticated organization. A new organization cannot\n// see those apps, so keeping the cache risks surfacing stale or wrong-account\n// secrets. Same organization \u2192 keep the cache to avoid an unnecessary refetch.\nfunction wipeAppsCacheIfAccountChanged(newOrganizationId: string): void {\n const previousOrganizationId = getOrganizationId();\n if (previousOrganizationId && previousOrganizationId !== newOrganizationId) {\n clearAppsCache();\n }\n}\n\nasync function promptApiKey(): Promise<string> {\n const { key } = await inquirer.prompt([\n {\n type: 'password',\n name: 'key',\n message: messages.AUTH_PROMPT_API_KEY,\n mask: '*',\n validate: (input: string) => input.trim().length > 0 || 'API key cannot be empty',\n },\n ]);\n return key;\n}\n\ntype LoginMethod = 'api-key' | 'browser';\n\n// Explicit `--browser` wins over BREVO_API_KEY so users can force the\n// browser flow on machines that have a stale env var. Browser login still\n// needs a TTY (the inquirer prompts after success), so reject early in\n// non-interactive contexts instead of hanging until timeout.\nasync function resolveLoginMethod(\n forceBrowser: boolean | undefined,\n apiKey: string | undefined,\n): Promise<LoginMethod> {\n if (forceBrowser) {\n if (!process.stdin.isTTY) {\n throw new CliError(messages.AUTH_BROWSER_NON_INTERACTIVE);\n }\n return 'browser';\n }\n if (apiKey) {\n return 'api-key';\n }\n if (!process.stdin.isTTY) {\n throw new CliError(messages.AUTH_BROWSER_NON_INTERACTIVE);\n }\n const { chosen } = await inquirer.prompt([\n {\n type: 'list',\n name: 'chosen',\n message: messages.AUTH_PROMPT_METHOD,\n choices: indentChoices([\n { name: 'Browser (sign in through your browser)', value: 'browser' },\n { name: 'API key (paste from your Brevo dashboard)', value: 'api-key' },\n ]),\n default: 'browser',\n },\n ]);\n return chosen;\n}\n\ninterface ValidatedKey {\n account: AccountResponse;\n apiKey: string;\n}\n\n// One interactive retry on a 401: the first paste is easy to fumble. A second\n// 401 (or any other error) propagates to the command handler.\nasync function retryApiKeyValidation(quiet: boolean): Promise<ValidatedKey> {\n const retryKey = await promptApiKey();\n const retrySpinner = createSpinner('Validating API key...', { silent: quiet });\n try {\n const account = await accountService.validateApiKey(retryKey);\n retrySpinner.stop();\n return { account, apiKey: retryKey };\n } catch (retryErr) {\n retrySpinner.stop();\n if (retryErr instanceof ApiError && retryErr.statusCode === 401) {\n throw new CliError(messages.AUTH_INVALID_KEY, EXIT_CODES.AUTH_FAILURE);\n }\n throw retryErr;\n }\n}\n\nasync function validateApiKeyWithRetry(apiKey: string, quiet: boolean): Promise<ValidatedKey> {\n const spinner = createSpinner('Validating API key...', { silent: quiet });\n try {\n const account = await accountService.validateApiKey(apiKey);\n spinner.stop();\n return { account, apiKey };\n } catch (err) {\n spinner.stop();\n if (!(err instanceof ApiError && err.statusCode === 401)) {\n throw err;\n }\n logError(messages.AUTH_INVALID_KEY);\n if (!quiet) logInfo(` ${messages.AUTH_GET_KEY_URL}`);\n if (!process.stdin.isTTY) {\n throw err;\n }\n return retryApiKeyValidation(quiet);\n }\n}\n\nasync function loginWithApiKey(\n envApiKey: string | undefined,\n quiet: boolean,\n): Promise<AccountResponse> {\n let apiKey = envApiKey;\n if (!apiKey) {\n openBrowser(BREVO_DASHBOARD_API_KEYS_URL);\n if (!quiet) {\n process.stdout.write(\n messages.AUTH_HINT(BREVO_DASHBOARD_API_KEYS_URL, BREVO_API_KEY_DOCS_URL),\n );\n }\n apiKey = await promptApiKey();\n }\n if (!apiKey) {\n throw new CliError('No API key provided.');\n }\n\n const validated = await validateApiKeyWithRetry(apiKey, quiet);\n if (!validated.account) {\n throw new CliError('Authentication failed.');\n }\n\n wipeAppsCacheIfAccountChanged(validated.account.organization_id);\n\n saveCredentials(validated.apiKey, {\n email: validated.account.email,\n organizationId: validated.account.organization_id,\n userId: validated.account.user_id,\n });\n return validated.account;\n}\n\nasync function loginWithBrowser(quiet: boolean): Promise<AccountResponse> {\n if (!quiet) logInfo(` ${messages.AUTH_BROWSER_OPENING}`);\n const tokens = await runBrowserLoginFlow({\n proxyUrl: OAUTH_PROXY_URL,\n openBrowser,\n onWaiting: (url) => {\n if (quiet) return;\n logInfo(` ${messages.AUTH_BROWSER_FALLBACK_URL(url)}`);\n logInfo(` ${messages.AUTH_BROWSER_WAITING}`);\n },\n });\n\n // Persist tokens before validating /v3/account so transient API failures\n // (5xx, 424, network) don't force the user to redo the OAuth dance. Only\n // a 401 unambiguously means the token itself is bad \u2014 see catch below.\n const tokensToStore = {\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresIn: tokens.expiresIn,\n tokenType: tokens.tokenType,\n scope: tokens.scope,\n };\n saveOauthCredentials(tokensToStore);\n if (!quiet) logSuccess(messages.AUTH_BROWSER_TOKENS_RECEIVED(getCredentialsPath()));\n\n const spinner = createSpinner('Finishing login...', { silent: quiet });\n let account: AccountResponse | undefined;\n try {\n account = await client.getWithBearer<AccountResponse>(\n ENDPOINTS.ACCOUNT,\n tokens.accessToken,\n tokens.tokenType,\n );\n } catch (err) {\n if (err instanceof ApiError && err.statusCode === 401) {\n clearCredentials();\n }\n throw err;\n } finally {\n spinner.stop();\n }\n\n if (!account) {\n throw new CliError('Authentication failed.');\n }\n\n wipeAppsCacheIfAccountChanged(account.organization_id);\n\n saveOauthCredentials(tokensToStore, {\n email: account.email,\n organizationId: account.organization_id,\n userId: account.user_id,\n });\n return account;\n}\n\nasync function showNextSteps(): Promise<void> {\n let apps: import('../types').OAuthApp[] = [];\n const appsSpinner = createSpinner('Checking your apps...');\n try {\n apps = await appService.fetchAppsList();\n } catch {\n // Best-effort: the next-steps box is a nicety \u2014 login already succeeded.\n } finally {\n appsSpinner.stop();\n }\n\n if (apps.length > 0) {\n printBox(\"What's next?\", [\n CLI.APP_CREATE,\n CLI.APP_LIST,\n CLI.APP_SCAFFOLD,\n CLI.APP_CREDENTIALS(),\n ]);\n return;\n }\n if (!process.stdin.isTTY) {\n logInfo(`\\n ${messages.AUTH_NEXT}\\n`);\n return;\n }\n\n process.stdout.write('\\n');\n const { shouldCreate } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'shouldCreate',\n message: messages.AUTH_CREATE_APP_PROMPT,\n default: true,\n },\n ]);\n\n if (shouldCreate) {\n process.stdout.write('\\n');\n await createCommand({});\n return;\n }\n\n logInfo(`\\n ${messages.AUTH_NEXT}\\n`);\n}\n\nexport const loginCommand = withCommandHandler(\n async (options: {\n browser?: boolean;\n json?: boolean;\n suppressNextSteps?: boolean;\n }): Promise<void> => {\n // In --json mode, stdout must be a single JSON document. Suppress the\n // welcome banner and every human-oriented log on stdout below; errors\n // still go to stderr via logError, which is fine for JSON consumers.\n const quiet = !!options.json;\n\n if (!quiet) {\n process.stdout.write(`\\n ${messages.AUTH_WELCOME}\\n`);\n process.stdout.write(' \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\\n');\n }\n\n const apiKey = process.env.BREVO_API_KEY;\n const method = await resolveLoginMethod(options.browser, apiKey);\n\n const account =\n method === 'api-key' ? await loginWithApiKey(apiKey, quiet) : await loginWithBrowser(quiet);\n\n if (options.json) {\n jsonOutput({ authenticated: true, email: account.email, company: account.companyName });\n return;\n }\n\n logSuccess(messages.AUTH_SUCCESS(account.email));\n logInfo(messages.AUTH_SAVED(getCredentialsPath()));\n\n if (options.suppressNextSteps) return;\n\n await showNextSteps();\n },\n);\n", "import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport inquirer from 'inquirer';\nimport { CLI, DEFAULT_PORT, DEFAULT_REDIRECT_URI, DEFAULT_SCOPES } from '../../lib/constants';\nimport { findAvailablePort } from '../../lib/port';\nimport { logInfo, logError, logWarn } from '../../lib/logger';\nimport { messages } from '../../lang/en';\nimport { ApiError, AuthExpiredError, CliError, ErrorCode } from '../../lib/errors';\nimport { withCommandHandler } from '../../lib/command-handler';\nimport { jsonOutput } from '../../lib/json-output';\nimport { validateEnum, validateAppName, validateYesNo } from '../../lib/validators';\nimport { assertFeatureAvailable, isFeatureAvailable } from '../../lib/preview';\nimport { printBox, createSpinner, indentChoices } from '../../lib/ui';\nimport {\n saveAppCredentials,\n saveAppName,\n hasLocalApp,\n isAuthenticated,\n readProjectConfig,\n} from '../../lib/config';\n// Cyclic on paper \u2014 `login.ts` imports `createCommand` to offer an app after a\n// successful login. Safe in practice and by construction: neither import is\n// touched at module-init time, only from inside an async handler, so the\n// binding is always resolved by the time it is read.\nimport { loginCommand } from '../login';\n// The project writer, not the `scaffold` COMMAND. `create` has never depended on that\n// command \u2014 only on the file-writing half it used to be bundled with, which is now its\n// own module. `scaffold.ts` imports the same functions; the two commands don't meet.\nimport {\n computeSlug,\n fetchAppContext,\n runBaseScaffold,\n resolveProjectDirectory,\n applyProjectDirectory,\n reportBaseScaffoldSuccess,\n computeCdHint,\n} from './project-writer';\nimport { finishProject } from './finish-project';\nimport { appService } from '../../container';\n\nimport { CreateAppResponse, OAuthApp, UiApp } from '../../types';\n// The UI-app half of this flow (registry reads, placement prompts, the summary box) lives\n// beside its app type \u2014 see `src/app-types/contract.ts` for why authoring hangs off the\n// module folder rather than off the type descriptor. Only these two entry points are public.\nimport { resolveUiApp, renderCreatedUiApp } from '../../app-types/ui/authoring';\n\nfunction validateHttpUrl(trimmed: string, invalidMessage: string): true | string {\n try {\n const parsed = new URL(trimmed);\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return invalidMessage;\n }\n return true;\n } catch {\n return invalidMessage;\n }\n}\n\nconst validateRedirectUrl = (input: string): true | string => {\n const trimmed = input.trim();\n if (!trimmed) return messages.APP_CREATE_REDIRECT_EMPTY;\n return validateHttpUrl(trimmed, messages.APP_CREATE_REDIRECT_INVALID);\n};\n\nconst validateLogoUrl = (input: string): true | string => {\n const trimmed = input.trim();\n if (!trimmed) return true;\n return validateHttpUrl(trimmed, messages.APP_CREATE_LOGO_INVALID);\n};\n\n// 0. Refuse outright if an app is already linked in this directory \u2014 no\n// confirm, no override. The user must leave the directory or run\n// `brevo app scaffold` here instead (which knows how to refresh a linked\n// project against the server).\nfunction guardAgainstLinkedApp(): void {\n if (!hasLocalApp()) return;\n const projectConfig = readProjectConfig();\n const linkedName = projectConfig?.appName || String(projectConfig?.appId ?? '');\n throw new CliError(messages.APP_CREATE_ALREADY_LINKED(linkedName));\n}\n\n// 1. App name\nasync function resolveAppName(nameFlag: string | undefined): Promise<string> {\n if (nameFlag) {\n const nameCheck = validateAppName(nameFlag);\n if (nameCheck !== true) throw new CliError(nameCheck);\n return nameFlag;\n }\n const answer = await inquirer.prompt([\n {\n type: 'input',\n name: 'name',\n message: messages.APP_CREATE_NAME_PROMPT,\n validate: validateAppName,\n },\n ]);\n return answer.name;\n}\n\n// 4. App type \u2014 OAuth integration vs UI app (BEX-290).\n// Asked last of the four opening questions: name, logo and distribution all\n// describe the app record itself and are asked of every app, so they come first.\n// The type is the branch point \u2014 it decides which of the two remaining prompt\n// paths runs (OAuth callback URLs vs UI-app placement) \u2014 so it is the last thing\n// asked before the flow splits.\n//\n// Prompt-only, deliberately: there is no `--type` flag, so a UI app can only\n// be authored from an interactive terminal. UI apps aren't live on the\n// platform yet, and a scriptable create surface would invite pipelines to pin\n// to a shape that can still change. Any non-interactive run \u2014 piped stdin or\n// `--json` \u2014 creates an OAuth app, exactly as it did before BEX-290, so\n// existing scripted `app create` calls are unaffected.\nexport type AppType = 'oauth' | 'ui';\n\nasync function resolveAppType(interactive: boolean): Promise<AppType> {\n // A UI app is only reachable through this prompt (there is no `--type` flag), so a\n // non-interactive run has nothing to resolve and creates an OAuth app, exactly as it\n // did before BEX-290.\n if (!interactive) {\n return 'oauth';\n }\n // The question is always asked; only the *choices* are gated. A build that offers one\n // app type still names it, so the flow reads the same everywhere and the user is told\n // what they are getting rather than having it applied silently.\n //\n // ELIMINATION SITE \u2014 the raw global, not `isFeatureAvailable('ui-app-type')` alone.\n // esbuild cannot fold a function call, so the helper by itself would leave this branch\n // live and keep `messages.APP_CREATE_APP_TYPE_UI` reachable. `isFeatureAvailable` is\n // still consulted, so flipping `FEATURE_STAGE` to `'ga'` releases the choice without\n // touching this line. The elimination that actually matters \u2014 the whole UI-authoring\n // layer (registry reads, placement prompts, the summary box) \u2014 hangs off the\n // `resolveUiApp` call site, which is guarded by the same global.\n const choices: Array<{ name: string; value: AppType }> = [\n { name: messages.APP_CREATE_APP_TYPE_OAUTH, value: 'oauth' },\n ];\n if (__BREVO_PREVIEW__ && isFeatureAvailable('ui-app-type')) {\n choices.push({ name: messages.APP_CREATE_APP_TYPE_UI, value: 'ui' });\n }\n const answer = await inquirer.prompt([\n {\n type: 'list',\n name: 'appType',\n message: messages.APP_CREATE_APP_TYPE_PROMPT,\n choices: indentChoices(choices),\n },\n ]);\n return answer.appType as AppType;\n}\n\n// 0b. Validate `--distribution` before anything is asked.\n//\n// Hoisted out of `resolveDistribution` because that now runs *after* the logo\n// prompt: a flag the CLI is going to reject must be rejected before the user is\n// made to answer questions, otherwise `--distribution typo` costs a logo prompt\n// first. Pure \u2014 no I/O, no prompts \u2014 so it is safe this early.\n//\n// Public distribution is pre-GA (BEX-405). The flag keeps validating against the\n// full set so `--distribution public` still fails as an *unreleased feature* rather\n// than as an unknown value \u2014 the second would be a lie, and would send the user\n// looking for a typo. `validateEnum` runs first so a genuine typo still gets the\n// \"invalid value\" error it deserves.\nfunction assertDistributionFlag(distributionFlag: string | undefined): void {\n const VALID_DISTRIBUTIONS = ['private', 'public'] as const;\n validateEnum(distributionFlag, VALID_DISTRIBUTIONS, '--distribution');\n if (distributionFlag === 'public') {\n assertFeatureAvailable('public-distribution');\n }\n}\n\n// 3. Distribution type \u2014 the flag is already validated by `assertDistributionFlag`.\nasync function resolveDistribution(\n distributionFlag: string | undefined,\n interactive: boolean,\n): Promise<string> {\n if (distributionFlag) {\n return distributionFlag;\n }\n // Load-bearing, not a tidy-up: this used to be covered by the feature check below\n // returning early, so removing that check without this one would put a prompt in\n // front of every `--json` / piped run and hang CI on a question it cannot answer.\n if (!interactive) {\n return 'private';\n }\n // Same shape as the app-type prompt: the question is always asked, only the choices\n // are gated. See the ELIMINATION SITE note there for why the raw global appears\n // alongside `isFeatureAvailable`.\n const choices: Array<{ name: string; value: string }> = [\n { name: 'Private (Used exclusively by your organisation)', value: 'private' },\n ];\n if (__BREVO_PREVIEW__ && isFeatureAvailable('public-distribution')) {\n choices.push({\n name: 'Public (Distributed to end users or marketplace listings)',\n value: 'public',\n });\n }\n const answer = await inquirer.prompt([\n {\n type: 'list',\n name: 'distribution',\n message: messages.APP_CREATE_TYPE_PROMPT,\n choices: indentChoices(choices),\n },\n ]);\n return answer.distribution;\n}\n\nasync function promptAddAnotherRedirect(): Promise<boolean> {\n const { anotherRaw } = await inquirer.prompt([\n {\n type: 'input',\n name: 'anotherRaw',\n message: messages.APP_CREATE_REDIRECT_ANOTHER + ' (y/N)',\n default: 'n',\n validate: validateYesNo,\n },\n ]);\n return String(anotherRaw).toLowerCase().trim().startsWith('y');\n}\n\nasync function promptRedirectUrls(quiet: boolean): Promise<string[]> {\n // Find an available port for the default redirect URL\n const availablePort = await findAvailablePort(DEFAULT_PORT);\n const defaultRedirect =\n availablePort == null || availablePort === DEFAULT_PORT\n ? DEFAULT_REDIRECT_URI\n : `http://localhost:${availablePort}/auth/callback`;\n if (!quiet) {\n if (availablePort == null) {\n logInfo(messages.APP_CREATE_PORT_SCAN_FAILED(DEFAULT_PORT));\n } else if (availablePort !== DEFAULT_PORT) {\n logInfo(messages.APP_CREATE_PORT_IN_USE(DEFAULT_PORT, availablePort));\n }\n logInfo(messages.APP_CREATE_REDIRECT_HINT(CLI.APP_START('oauth')));\n }\n\n const redirectUris: string[] = [];\n const { redirectUrl: firstUrl } = await inquirer.prompt([\n {\n type: 'input',\n name: 'redirectUrl',\n message: messages.APP_CREATE_REDIRECT_PROMPT,\n default: defaultRedirect,\n validate: validateRedirectUrl,\n },\n ]);\n redirectUris.push((firstUrl as string).trim());\n\n while (await promptAddAnotherRedirect()) {\n const { nextUrl } = await inquirer.prompt([\n {\n type: 'input',\n name: 'nextUrl',\n message: messages.APP_CREATE_REDIRECT_PROMPT,\n validate: validateRedirectUrl,\n },\n ]);\n redirectUris.push((nextUrl as string).trim());\n }\n return redirectUris;\n}\n\n// 5. Redirect URI(s) \u2014 already validated by collectUrls parser when passed via flag\nasync function resolveRedirectUrls(\n redirectUriFlag: string[] | undefined,\n quiet: boolean,\n): Promise<string[]> {\n const flagUrls = redirectUriFlag ?? [];\n if (flagUrls.length > 0) {\n return flagUrls;\n }\n if (process.stdin.isTTY) {\n return promptRedirectUrls(quiet);\n }\n return [DEFAULT_REDIRECT_URI];\n}\n\n// 2. Logo URL (optional) \u2014 prompt interactively when no --logo-uri flag.\n// Skipped under --json since the field is optional and --json implies scripting.\n//\n// Asked up front, right after the name, and asked identically for every app\n// type: the logo belongs to the app record rather than to either prompt path,\n// so it must not sit behind the type branch where an OAuth app answers it after\n// its callback URLs and a UI app after its placements.\nasync function resolveLogoUri(\n logoUriFlag: string | undefined,\n jsonMode: boolean,\n): Promise<string | undefined> {\n if (logoUriFlag || !process.stdin.isTTY || jsonMode) {\n return logoUriFlag;\n }\n const { logoUrl } = await inquirer.prompt([\n {\n type: 'input',\n name: 'logoUrl',\n message: messages.APP_CREATE_LOGO_PROMPT,\n validate: validateLogoUrl,\n },\n ]);\n const trimmed = String(logoUrl ?? '').trim();\n return trimmed || undefined;\n}\n\ntype CreateDirectoryResult =\n | { targetDir: string; mergeOnly: boolean; skipped: false; existed: boolean }\n | { targetDir: string; skipped: true };\n\n/**\n * Decide where the project goes \u2014 prompts only, no filesystem writes.\n *\n * Deliberately free of side effects so it can run *before* the app is created\n * (abandoning a prompt must not orphan an app on the server) while the directory\n * itself is only touched *after* the create succeeds. `applyCreateDirectory` is the\n * other half; see `applyProjectDirectory` in `./scaffold` for the full reasoning.\n */\nasync function resolveCreateDirectory(\n appName: string,\n interactive: boolean,\n): Promise<CreateDirectoryResult> {\n const slug = computeSlug(appName);\n\n if (!interactive) {\n const targetDir = path.resolve(`./${slug}`);\n if (fs.existsSync(targetDir)) {\n return { targetDir, skipped: true };\n }\n return { targetDir, mergeOnly: false, skipped: false, existed: false };\n }\n\n let dir = await resolveProjectDirectory(`./${slug}`);\n while (!dir.unresolved && dir.chooseAgain) {\n dir = await resolveProjectDirectory(`./${slug}`);\n }\n if (dir.unresolved) {\n // Unreachable in practice: `interactive` is only true when we're not in\n // --json/non-TTY mode, and resolveProjectDirectory only reports\n // `unresolved` when called with jsonMode=true (never the case here).\n // Fail loudly instead of silently guessing a directory if this ever\n // changes.\n throw new CliError(messages.APP_CREATE_DIR_UNRESOLVED);\n }\n return {\n targetDir: dir.targetDir,\n mergeOnly: dir.mergeOnly,\n skipped: false,\n existed: dir.existed,\n };\n}\n\n/** Apply a decision from `resolveCreateDirectory`. Call only after the app exists. */\nfunction applyCreateDirectory(dir: CreateDirectoryResult, jsonMode: boolean): void {\n if (dir.skipped) return;\n applyProjectDirectory(\n {\n targetDir: dir.targetDir,\n mergeOnly: dir.mergeOnly,\n chooseAgain: false,\n existed: dir.existed,\n },\n jsonMode,\n );\n}\n\ninterface CreateAppInputs {\n appName: string;\n distribution: string;\n redirectUris: string[];\n logoUri?: string;\n /** Present for UI apps only; drives scope defaults and omits redirect URIs. */\n uiApp?: UiApp;\n}\n\ninterface CreatedApp {\n result: CreateAppResponse;\n appName: string;\n}\n\nfunction buildCreatePayload(inputs: CreateAppInputs) {\n const isUiApp = !!inputs.uiApp;\n return {\n name: inputs.appName,\n distribution_type: inputs.distribution as 'public' | 'private',\n // OAuth fields travel inside the `auth` block, same as the upload payload\n // (unified structure). A UI app has no OAuth block at all (`auth: {}` in\n // its config) \u2014 the key is omitted entirely, not sent empty. Sending empty\n // arrays (or worse, the default localhost URI) would register OAuth state\n // the app type never uses.\n //\n // `ui_app` is what tells create the omission is deliberate. It is the\n // app-type discriminator on the wire exactly as it is in app-config.json\n // (`isUiAppConfig`), so create can apply the same branch the CLI does:\n // without it the endpoint reads a UI app as an OAuth app missing its\n // callbacks and answers `redirect_uris is required and must not be empty`.\n // `app upload` still sends the block and remains the platform's validation\n // authority for it \u2014 this is the same block under the same key, sent early\n // enough that the record is created with the right app type.\n ...(isUiApp\n ? { ui_app: inputs.uiApp }\n : { auth: { scopes: [...DEFAULT_SCOPES], redirect_uris: inputs.redirectUris } }),\n ...(inputs.logoUri ? { logo_uri: inputs.logoUri } : {}),\n };\n}\n\nasync function retryCreateWithNewName(inputs: CreateAppInputs): Promise<CreatedApp> {\n logError(messages.APP_CREATE_NAME_TAKEN);\n const retry = await inquirer.prompt([\n {\n type: 'input',\n name: 'name',\n message: messages.APP_CREATE_NAME_PROMPT,\n validate: validateAppName,\n },\n ]);\n const retrySpinner = createSpinner('Creating app...');\n try {\n const result = await appService.createApp(\n buildCreatePayload({ ...inputs, appName: retry.name }),\n );\n retrySpinner.stop();\n // Use the retried name for cache, JSON output, display, and scaffold prompt\n return { result, appName: retry.name };\n } catch (retryErr) {\n retrySpinner.stop();\n throw retryErr;\n }\n}\n\n/**\n * Log in again and re-send the create, rather than discarding the answers.\n *\n * A session can die between the first prompt and the POST \u2014 `app create` makes\n * no API call at all until every question is answered, so that gap is the whole\n * interactive flow. The pre-flight refresh now catches the case where the\n * session was already dead at the first prompt, and the client re-checks\n * freshness before each request; what is left for this path is a token revoked\n * (or a refresh token expired) *during* the prompts. Rare, but the old\n * behaviour \u2014 exit 1, six answers gone \u2014 was the worst possible response to it.\n *\n * Interactive callers only. Under `--json` or a pipe there is nobody to\n * complete a browser login, so the error propagates unchanged and scripts see\n * exactly the exit code they saw before.\n */\nasync function retryCreateAfterLogin(inputs: CreateAppInputs): Promise<CreatedApp> {\n logWarn(messages.APP_CREATE_SESSION_EXPIRED);\n const { relogin } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'relogin',\n message: messages.APP_CREATE_RELOGIN_CONFIRM,\n default: true,\n },\n ]);\n // A decline is the user choosing to stop, so it gets the plain expiry error\n // \u2014 same message and exit code as if this prompt had never been offered.\n if (!relogin) throw new AuthExpiredError();\n\n await loginCommand({ suppressNextSteps: true });\n if (!isAuthenticated()) throw new AuthExpiredError();\n\n const spinner = createSpinner('Creating app...');\n try {\n const result = await appService.createApp(buildCreatePayload(inputs));\n return { result, appName: inputs.appName };\n } finally {\n spinner.stop();\n }\n}\n\n/**\n * Recognise the platform's refusal to create a public app from the CLI (BEX-355):\n * `POST /v3/app-store/apps` answers `400 invalid_parameter` \u2014 *public apps cannot\n * be created with source \"cli\"; use distribution_type \"private\"* \u2014 for any create\n * that pairs a CLI caller with `distribution_type: \"public\"`. This is the API-side\n * pre-GA guard `CLAUDE.md` says belongs on the server.\n *\n * **No change to the request body can satisfy it, and the mechanism is not the\n * `User-Agent`.** The handler assigns `payload.Source = SourceCLI` before validating\n * \u2014 app-store-bo-be `http_cli_create_app.go`, and identically in\n * `http_cli_create_app_public.go` for the nested `auth`/`ui_app` contract the CLI\n * sends \u2014 deliberately *overwriting* any client-supplied value so the gate cannot be\n * bypassed by sending some other source. Dropping `source` from the body on BEX-355\n * therefore had no effect on this gate: the server puts it back.\n *\n * Deliberately a translation and not a local guard, for two reasons. First,\n * `CLAUDE.md`'s standing rule that the CLI must not mirror platform policy locally \u2014\n * a copy can only lag. Second, and concretely: **the restriction is per-account, so\n * a local guard would be wrong rather than merely stale.** The server's `allowPublic`\n * comes from the Unleash flag `app-store-bo-be-public-apps` resolved for the calling\n * client (BEX-333) and lifts the rule for the `cli` source only, failing closed on a\n * lookup error. An account with that flag enabled creates public apps from the CLI\n * successfully, and never reaches this path.\n *\n * Narrowed to the rejection that names `distribution_type`, so an unrelated 400 on a\n * public create (a bad `logo_uri`, say) keeps the server's own text rather than being\n * relabelled as the pre-GA restriction. If the server ever rewords the sentence this\n * stops matching and the raw message surfaces again \u2014 the previous behaviour, not a\n * new failure mode.\n */\nfunction isPublicDistributionRefusal(err: unknown, distribution: string): err is ApiError {\n return (\n err instanceof ApiError &&\n err.statusCode === 400 &&\n distribution === 'public' &&\n /distribution_type/i.test(err.message)\n );\n}\n\n// 5. Create the app\nasync function createAppWithRetry(\n inputs: CreateAppInputs,\n jsonMode: boolean,\n interactive: boolean,\n): Promise<CreatedApp> {\n const spinner = createSpinner('Creating app...', { silent: jsonMode });\n try {\n const result = await appService.createApp(buildCreatePayload(inputs));\n spinner.stop();\n return { result, appName: inputs.appName };\n } catch (err) {\n spinner.stop();\n if (err instanceof ApiError && err.errorCode === ErrorCode.APP_LIMIT_REACHED) {\n if (jsonMode) {\n jsonOutput({ error: 'APP_LIMIT_REACHED', message: messages.APP_CREATE_LIMIT_REACHED });\n }\n throw new CliError(messages.APP_CREATE_LIMIT_REACHED);\n }\n if (isPublicDistributionRefusal(err, inputs.distribution)) {\n throw new CliError(messages.APP_CREATE_PUBLIC_REJECTED(err.message));\n }\n if (err instanceof ApiError && err.statusCode === 409) {\n return retryCreateWithNewName(inputs);\n }\n if (err instanceof AuthExpiredError && interactive) {\n return retryCreateAfterLogin(inputs);\n }\n throw err;\n }\n}\n\nfunction renderCreatedApp(result: CreateAppResponse, appName: string, logoUri?: string): void {\n const boxLines = [\n `App name: ${appName}`,\n `App ID: ${result.app_id}`,\n `Client ID: ${result.client_id}`,\n `Client secret: ${messages.CLIENT_SECRET_HIDDEN_HUMAN}`,\n ...(result.redirect_uris ?? []).map((uri, i) => `Redirect URL ${i + 1}: ${uri}`),\n ...(logoUri ? [`Logo URL: ${logoUri}`] : []),\n ...(result.version ? [`App version: ${result.version}`] : []),\n `${messages.APP_CREATE_BOX_SCOPES_LABEL} ${[...DEFAULT_SCOPES].join(', ')}`,\n '',\n messages.APP_CREATE_BOX_SCOPE_HINT,\n ];\n printBox(messages.APP_CREATE_BOX_TITLE, boxLines);\n}\n\nexport const createCommand = withCommandHandler(\n async (options: {\n name?: string;\n distribution?: string;\n redirectUri?: string[];\n logoUri?: string;\n json?: boolean;\n }): Promise<void> => {\n const jsonMode = !!options.json;\n const originalCwd = process.cwd();\n\n guardAgainstLinkedApp();\n assertDistributionFlag(options.distribution);\n\n const interactive = !jsonMode && !!process.stdin.isTTY;\n\n // The app record first \u2014 name, logo, distribution \u2014 then the app type. All three\n // identify the app and are asked of every app regardless of type; the type is the\n // branch point, and everything below it belongs to one path or the other.\n const appName = await resolveAppName(options.name);\n const logoUri = await resolveLogoUri(options.logoUri, jsonMode);\n const distribution = await resolveDistribution(options.distribution, interactive);\n const appType = await resolveAppType(interactive);\n\n // The two app types diverge here: OAuth apps collect callback URLs, UI apps\n // collect placement + destination. Neither path runs the other's prompts.\n let redirectUris: string[] = [];\n let uiApp: UiApp | undefined;\n // Same elimination site as `resolveAppType`: this is the only call to\n // `resolveUiApp`, so guarding it on the build global is what lets the bundler drop\n // `app-types/ui/authoring.ts`. In a public build `appType` can never be `'ui'`\n // anyway \u2014 the prompt is asked, but `UI app` is not among the choices it offers \u2014\n // so this changes nothing at runtime.\n if (__BREVO_PREVIEW__ && appType === 'ui') {\n uiApp = await resolveUiApp();\n } else {\n redirectUris = await resolveRedirectUrls(options.redirectUri, jsonMode);\n }\n\n const dir = await resolveCreateDirectory(appName, interactive);\n\n const inputs: CreateAppInputs = { appName, distribution, redirectUris, logoUri, uiApp };\n const { result, appName: finalAppName } = await createAppWithRetry(\n inputs,\n jsonMode,\n interactive,\n );\n\n // The app now provably exists, so it is safe to touch the filesystem. Before\n // this line a failed create left a stray directory and a moved cwd behind.\n applyCreateDirectory(dir, jsonMode);\n\n // Cache the credentials locally. Not because this is the only copy \u2014 `GET\n // /cli/apps/{id}` is a credential-reveal endpoint and hands back `client_secret`\n // too (verified against app-store-bo-be, 2026-08-13) \u2014 but so the scaffold and\n // `app start` can read them without a round trip.\n // Guarded because a UI app has no OAuth credentials to cache: writing the pair\n // unconditionally stored `{clientId: undefined, clientSecret: undefined}` under\n // its ID, which is a cache entry that can only mislead a later read.\n if (result.client_id && result.client_secret) {\n saveAppCredentials(result.app_id, {\n clientId: result.client_id,\n clientSecret: result.client_secret,\n });\n }\n if (finalAppName) saveAppName(result.app_id, finalAppName);\n\n // Shared JSON shape for both exits below. `redirectUri` is omitted for UI\n // apps rather than emitted as an empty array, so a consumer can distinguish\n // \"no callbacks by design\" from \"callbacks not returned\".\n //\n // `--json` implies non-interactive, which implies OAuth, so `appType` is\n // always `oauth` here today and the `uiApp` branch is unreachable. Both are\n // kept so the field stays meaningful to a consumer, and so this shape doesn't\n // have to be rediscovered if UI apps ever gain a non-interactive path.\n const jsonBase = {\n appId: result.app_id,\n appName: finalAppName,\n clientId: result.client_id,\n clientSecret: messages.CLIENT_SECRET_HIDDEN_JSON,\n appType,\n ...(uiApp ? { uiApp } : { redirectUri: result.redirect_uris }),\n ...(logoUri ? { logoUri } : {}),\n ...(result.version ? { version: result.version } : {}),\n };\n\n // `uiApp` is always undefined in a public build, but an unguarded reference to\n // `renderCreatedUiApp` still keeps its module in the bundle \u2014 hence the global here\n // as well. Both call sites have to be guarded or neither elimination happens.\n const renderBox = (): void =>\n __BREVO_PREVIEW__ && uiApp\n ? renderCreatedUiApp(result, finalAppName, uiApp, logoUri)\n : renderCreatedApp(result, finalAppName, logoUri);\n\n if (dir.skipped) {\n if (jsonMode) {\n jsonOutput({\n ...jsonBase,\n directory: dir.targetDir,\n scaffoldSkipped: messages.APP_CREATE_JSON_SCAFFOLD_DIR_EXISTS(dir.targetDir),\n });\n return;\n }\n renderBox();\n logInfo(messages.APP_CREATE_DIR_EXISTS_SKIPPED(dir.targetDir));\n return;\n }\n\n // Pass the freshly collected `ui_app` block explicitly.\n //\n // NOT because the server lacks it \u2014 it does have it. This comment used to claim\n // the server \"only learns about it on `app upload`\", which was true before create\n // accepted the block and is false now: `persistCreateResultTx` writes an\n // `app_versions` row *inside the create transaction* whose snapshot carries the\n // `ui_app`, and `GET /cli/apps/{id}` serves it straight back from that snapshot\n // (`applyLatestVersionFields`). Verified against app-store-bo-be, 2026-08-13.\n //\n // It is passed because a re-read is the wrong source, not an impossible one:\n // `fetchAppContext` deliberately ignores `appDetails.ui_app` so stale or\n // unexpected server data can't reclassify an app the user just told us the type\n // of. The local answer is the authoritative one here; see that parameter's own\n // comment in `./scaffold`.\n //\n // `result` is passed as the fallback so a read-back that 404s can't destroy a\n // successful create: the app is already on the server at this point, and the\n // create response carries every field the scaffold reads off `appDetails`\n // (name, distribution_type, logo_uri, version) plus the credentials. Without\n // it, `GET /v3/app-store/apps/{id}` answering `id not found` for an ID the\n // create just issued aborted the command and left an orphan app behind.\n //\n // Widened to `OAuthApp` explicitly rather than passed raw: the create response\n // declares its OAuth fields optional (a UI app has none), while `OAuthApp` wants\n // `client_id` present and spells \"no callbacks\" as `null`. The empty string falls\n // through to the scaffold's own placeholder, which is what a UI app should get.\n const fallbackApp: OAuthApp = {\n ...result,\n client_id: result.client_id ?? '',\n redirect_uris: result.redirect_uris ?? null,\n };\n const ctx = await fetchAppContext(result.app_id, jsonMode, uiApp, fallbackApp);\n\n // Always write the basic project structure (app-config.json + meta files).\n const base = runBaseScaffold(result.app_id, ctx, dir.targetDir, dir.mergeOnly);\n\n // --json never scaffolds a feature \u2014 emit the base result as a single blob.\n if (jsonMode) {\n jsonOutput({\n ...jsonBase,\n directory: dir.targetDir,\n scaffolded: base.written,\n });\n return;\n }\n\n // Show the created-app box and the base files that were just written,\n // before asking about features.\n renderBox();\n reportBaseScaffoldSuccess(base);\n\n // Everything from here \u2014 the UI-app sign-off, the feature offer, the feature\n // write and its report \u2014 is the same tail `app scaffold`'s bootstrap runs, and\n // lives with it in `./project-writer`. `create` used to carry its own near-copy.\n //\n // `onConflict: 'merge'` rather than `'ask'`: the directory question was already\n // answered above, for this exact directory, before the app was created. Asking\n // again here would be the same question twice in one run.\n await finishProject({\n appId: result.app_id,\n ctx,\n targetDir: dir.targetDir,\n baseScopes: base.scopes,\n cdDir: computeCdHint(originalCwd, dir.targetDir),\n isUiApp: !!uiApp,\n // A piped run stays base-only, exactly as before: no question is asked and the\n // feature is left to a follow-up `brevo app scaffold`.\n offerFeature: interactive,\n onConflict: dir.mergeOnly ? 'merge' : 'overwrite',\n });\n },\n);\n", "import * as net from 'node:net';\n\n/**\n * Try to bind a fresh listener to `port` on `host`. Resolves true if the\n * bind succeeds (port free), false on EADDRINUSE, rejects on other errors.\n */\nfunction tryBind(port: number, host: string): Promise<boolean> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n resolve(false);\n } else {\n reject(err);\n }\n });\n server.once('listening', () => {\n server.close(() => resolve(true));\n });\n try {\n server.listen(port, host);\n } catch (err) {\n reject(err);\n }\n });\n}\n\n/**\n * Check whether a TCP port is available.\n * Resolves true if the port is free, false if in use.\n * Rejects on unexpected errors (e.g. EACCES, invalid port).\n *\n * Probes 0.0.0.0 (IPv4 wildcard) and 127.0.0.1 (loopback) sequentially.\n * A single-host probe misses real conflicts: a loopback-only probe doesn't\n * catch wildcard owners (e.g. Mockoon on `*:3009`), and a wildcard-only\n * probe doesn't catch loopback-bound owners. Reporting \"in use\" if either\n * probe sees EADDRINUSE matches what an OAuth server bound to 127.0.0.1\n * would actually hit at start time or runtime.\n *\n * Probes run sequentially (not Promise.all) because parallel binds to\n * 0.0.0.0:N and 127.0.0.1:N race against each other \u2014 whichever the\n * kernel grants first triggers EADDRINUSE on the other, even when the\n * port is genuinely free. Sequential probes with proper close-between\n * avoid the self-collision.\n */\nexport async function isPortAvailable(port: number): Promise<boolean> {\n if (!(await tryBind(port, '0.0.0.0'))) return false;\n if (!(await tryBind(port, '127.0.0.1'))) return false;\n return true;\n}\n\n/** Maximum number of ports to scan before giving up. */\nconst MAX_SCAN = 20;\n\n/**\n * Find the first available port starting from `startPort`.\n * Scans up to MAX_SCAN ports. Returns `null` if no free port is found.\n */\nexport async function findAvailablePort(startPort: number): Promise<number | null> {\n for (let port = startPort; port < startPort + MAX_SCAN; port++) {\n if (await isPortAvailable(port)) return port;\n }\n return null;\n}\n", "/**\n * The project writer \u2014 everything that puts a Brevo app project on disk.\n *\n * Split out of `scaffold.ts` because two commands write projects, not one. `app create`\n * imports the whole of this module; `app scaffold` imports it too and adds the plan\n * resolution (drift diff, bootstrap, app picker) that is its own. Nothing here knows\n * which command is running, and nothing here prompts for an *app* \u2014 the caller has\n * already decided that and hands over an `AppContext`.\n *\n * The two used to live in one 935-line `scaffold.ts`, which made `create.ts` read as\n * though it depended on the `scaffold` COMMAND. It never did: it depends on these\n * functions. The dependency runs one way \u2014 `scaffold.ts` imports from here, never the\n * reverse \u2014 so this file can be read without the command in view.\n */\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport inquirer from 'inquirer';\nimport {\n DEFAULT_REDIRECT_URI,\n PLACEHOLDER_CLIENT_ID,\n OAUTH_BASE,\n OAUTH_REALM,\n DEFAULT_SCOPES,\n LEGACY_ALL_SCOPE,\n} from '../../lib/constants';\nimport { logSuccess, logInfo, logWarn } from '../../lib/logger';\nimport { createSpinner, printBox, indentChoices } from '../../lib/ui';\nimport { messages } from '../../lang/en';\nimport { appService } from '../../container';\nimport { loadBaseTemplates, loadFeatureTemplates, FeatureType } from '../../templates';\nimport { containsLegacyAllScope } from '../../lib/validators';\nimport { ProjectConfig, isUiAppConfig } from '../../lib/config';\nimport { OAuthApp, UiApp } from '../../types';\n\ninterface TreeNode {\n [key: string]: TreeNode;\n}\n\nexport function formatFileTree(filePaths: string[]): string {\n // Build tree structure\n const tree: TreeNode = {};\n for (const fp of filePaths) {\n const parts = fp.split('/');\n let node = tree;\n for (const part of parts) {\n node[part] = node[part] || {};\n node = node[part];\n }\n }\n\n const lines: string[] = [];\n function render(node: TreeNode, prefix: string): void {\n const entries = Object.keys(node).sort((a, b) => {\n // Directories (non-empty children) first, then files\n const aIsDir = Object.keys(node[a] ?? {}).length > 0;\n const bIsDir = Object.keys(node[b] ?? {}).length > 0;\n if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;\n return a.localeCompare(b);\n });\n entries.forEach((name, i) => {\n const isLast = i === entries.length - 1;\n const connector = isLast ? '\u2514\u2500\u2500 ' : '\u251C\u2500\u2500 ';\n const children = node[name] ?? {};\n const isDir = Object.keys(children).length > 0;\n lines.push(`${prefix}${connector}${name}${isDir ? '/' : ''}`);\n if (isDir) {\n render(children, prefix + (isLast ? ' ' : '\u2502 '));\n }\n });\n }\n\n // Two columns, not four: the tree is printed a line at a time through `logInfo`,\n // which adds the CLI's own two-space gutter to each of them. Handing `logInfo` the\n // whole tree as one string is what used to indent the first row two columns deeper\n // than its siblings \u2014 the gutter landed on the string, so only the first line got it.\n render(tree, ' ');\n return lines.join('\\n');\n}\n\n/**\n * Print a file tree with every row in the output gutter. See `formatFileTree` for why\n * the rows have to be written one at a time.\n */\nexport function printFileTree(filePaths: string[]): void {\n for (const line of formatFileTree(filePaths).split('\\n')) logInfo(line);\n}\n\nexport interface AppContext {\n appDetails: Awaited<ReturnType<typeof appService.resolveAppCredentials>> extends infer R\n ? R extends { app: infer A }\n ? A\n : null\n : null;\n clientId: string;\n clientSecret: string;\n redirectUris: string[];\n redirectUri: string;\n /**\n * The UI-app block to write into app-config.json (BEX-290). Unlike every other\n * field here it is not necessarily server-sourced: `app create` collects it\n * from prompts before the server knows about it, and `app scaffold` carries the\n * *local* block forward so a refresh never destroys hand-edited values.\n * Absent for OAuth apps.\n */\n uiApp?: UiApp;\n}\n\nexport function computeSlug(name: string | undefined): string {\n return (\n (name || 'my-app')\n .toLowerCase()\n .replaceAll(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '') || 'my-app'\n );\n}\n\nexport async function fetchAppContext(\n appId: string,\n silent?: boolean,\n // The UI-app block to scaffold, supplied by the caller \u2014 deliberately NOT read\n // from the server response. Both callers already know the app type locally\n // (`app create` from the type it just prompted for, `app scaffold` from the\n // local `app-config.json`), and falling back to `appDetails.ui_app` would let\n // stale or unexpected server data reclassify an app the user explicitly created\n // as OAuth. Absent here means \"OAuth app\", authoritatively.\n uiApp?: UiApp,\n // The app object to fall back on when the server can't return one. Supplied by\n // `app create` only, and it is the create response itself: at that point the app\n // provably exists (the server just issued its ID), so a 404 on the read-back is\n // the server contradicting itself, not a missing app. Without this the read-back\n // threw and took the whole create with it \u2014 the app stayed on the server while\n // the user got `App <id> not found.` and no project directory (BEX-290).\n fallbackApp?: OAuthApp,\n): Promise<AppContext> {\n const spinner = createSpinner('Fetching app details...', { silent });\n let result: Awaited<ReturnType<typeof appService.resolveAppCredentials>>;\n try {\n result = await appService.resolveAppCredentials(appId, {\n tolerateMissing: Boolean(fallbackApp),\n });\n } finally {\n // In `finally` so a propagating error stops the spinner too \u2014 otherwise the\n // frame keeps printing over the error output.\n spinner.stop();\n }\n let appDetails = result?.app ?? null;\n if (result) {\n if (result.diffs.length > 0) {\n logWarn(\n `Local credentials for app ${appId} differ from server (${result.diffs.join(', ')}). Updating local cache.`,\n );\n }\n appService.syncAppCredentials(appId, result.app);\n } else if (fallbackApp) {\n appDetails = fallbackApp;\n // Suppressed under --json: logWarn writes to stdout, which would corrupt the\n // single JSON blob the command emits.\n if (!silent) logWarn(messages.APP_SCAFFOLD_SERVER_READBACK_FAILED(appId));\n }\n const serverRedirectUrls = appDetails?.redirect_uris ?? [];\n const redirectUris = serverRedirectUrls.length > 0 ? serverRedirectUrls : [DEFAULT_REDIRECT_URI];\n const localhostUri = redirectUris.find(\n (url: string) => url.startsWith('http://localhost') || url.startsWith('http://127.0.0.1'),\n );\n return {\n appDetails,\n clientId: appDetails?.client_id || PLACEHOLDER_CLIENT_ID,\n clientSecret: appDetails?.client_secret || 'YOUR_CLIENT_SECRET',\n redirectUris,\n redirectUri: localhostUri || DEFAULT_REDIRECT_URI,\n ...(uiApp ? { uiApp } : {}),\n };\n}\n\n// `unresolved: true` signals a directory conflict that couldn't be resolved\n// without a prompt (only reachable when jsonMode is true). The other branch\n// deliberately leaves `unresolved` unset (rather than `false`) so existing\n// callers/tests that compare against `{ targetDir, mergeOnly, chooseAgain }`\n// via `toEqual` keep working unmodified (`toEqual` ignores undefined keys).\nexport type ResolveProjectDirectoryResult =\n | {\n targetDir: string;\n mergeOnly: boolean;\n chooseAgain: boolean;\n /**\n * Whether `targetDir` was already on disk when the decision was taken.\n * `applyProjectDirectory` needs it to know whether to `mkdir`, and it cannot\n * re-test with `existsSync` because by then the answer may be \"yes, because\n * we just made it\".\n */\n existed: boolean;\n unresolved?: false;\n }\n | { targetDir: string; unresolved: true };\n\nexport async function resolveProjectDirectory(\n defaultDir: string,\n jsonMode = false,\n /**\n * Optional refusal hook, run as soon as a target directory is known and **before**\n * the Overwrite / Merge / Choose-a-different-path question. Throw from it to reject\n * the directory outright.\n *\n * It runs before that prompt on purpose: a directory we are going to refuse must not\n * first be the subject of a question about how to write into it. It also runs on every\n * pass of the caller's `chooseAgain` loop, so a second answer is validated like the\n * first. `app scaffold`'s bootstrap uses it to refuse a directory that already belongs\n * to a *different* app \u2014 a case its `--app-id` guard cannot see, because that one\n * compares against the current directory and this target is somewhere else.\n */\n validateTarget?: (targetDir: string) => void,\n): Promise<ResolveProjectDirectoryResult> {\n // --json must never block on a prompt: skip the \"Output directory:\" input\n // and use the default directly.\n const outputDir = jsonMode\n ? defaultDir\n : ((\n await inquirer.prompt([\n {\n type: 'input',\n name: 'outputDir',\n message: messages.APP_SCAFFOLD_DIR_PROMPT,\n default: defaultDir,\n },\n ])\n ).outputDir as string);\n const targetDir = path.resolve(outputDir);\n const existed = fs.existsSync(targetDir);\n\n // Before the conflict question, not after \u2014 see the parameter's own note. A\n // directory that does not exist cannot hold a project, but the hook is called for it\n // anyway so the caller owns that judgement rather than inferring it from `existed`.\n validateTarget?.(targetDir);\n\n if (!existed) {\n return { targetDir, mergeOnly: false, chooseAgain: false, existed: false };\n }\n\n // --json can't ask \"Overwrite / Merge / Choose a different path\" either \u2014\n // report this as unresolved instead of guessing on the user's behalf.\n if (jsonMode) {\n return { targetDir, unresolved: true };\n }\n\n const { action } = await inquirer.prompt([\n {\n type: 'list',\n name: 'action',\n message: messages.APP_SCAFFOLD_DIR_EXISTS,\n choices: indentChoices([\n { name: 'Overwrite existing files', value: 'overwrite' },\n { name: 'Merge (keep existing, add missing)', value: 'merge' },\n { name: 'Choose a different path', value: 'new' },\n ]),\n },\n ]);\n if (action === 'new') {\n return { targetDir, mergeOnly: false, chooseAgain: true, existed: true };\n }\n return { targetDir, mergeOnly: action === 'merge', chooseAgain: false, existed: true };\n}\n\n/**\n * Perform the directory decision taken by `resolveProjectDirectory` \u2014 create it if\n * needed, announce it, and move into it.\n *\n * Split out from the resolve step because the two halves have opposite ordering\n * constraints in `app create`. The *decision* has to happen before the app is\n * registered, so that abandoning a prompt (Ctrl-C at \"Output directory:\") cannot\n * leave an app stranded on the server with no project. The *mutation* has to happen\n * after, because until the create returns there may be no app to build a project\n * for \u2014 and a create can fail for reasons no amount of local validation predicts (a\n * plan quota `403`, a dropped connection, an unmapped `400`). Doing both up front\n * meant every one of those failures left a directory behind and the process `chdir`'d\n * into it, so the user's next command ran somewhere they had not chosen.\n *\n * A no-op for the two results that describe no directory to apply: an unresolved\n * `--json` conflict, and `chooseAgain`, which is the caller being asked to loop.\n */\nexport function applyProjectDirectory(\n decision: ResolveProjectDirectoryResult,\n jsonMode = false,\n): void {\n if (decision.unresolved || decision.chooseAgain) return;\n\n const { targetDir, existed } = decision;\n if (!existed) {\n if (!jsonMode) {\n logInfo(messages.APP_SCAFFOLD_CREATING_DIR(path.relative(process.cwd(), targetDir)));\n }\n fs.mkdirSync(targetDir, { recursive: true });\n } else if (!jsonMode) {\n if (targetDir === process.cwd()) {\n logInfo(messages.APP_SCAFFOLD_TARGET_IS_CWD);\n } else {\n // Not \"Creating\": the user answered \"Directory already exists\u2026\" one line up,\n // so claiming to create it contradicts the question they were just asked.\n logInfo(messages.APP_SCAFFOLD_USING_EXISTING_DIR(path.relative(process.cwd(), targetDir)));\n }\n }\n process.chdir(targetDir);\n}\n\nexport interface ConfigDiff {\n field: string;\n local: string;\n server: string;\n}\n\nexport function diffLocalConfig(localConfig: ProjectConfig, ctx: AppContext): ConfigDiff[] {\n const diffs: ConfigDiff[] = [];\n\n const serverName = ctx.appDetails?.name;\n if (serverName && localConfig.appName !== serverName) {\n diffs.push({ field: 'appName', local: localConfig.appName || '(none)', server: serverName });\n }\n\n const serverDistribution = ctx.appDetails?.distribution_type ?? 'private';\n if (localConfig.distribution_type !== serverDistribution) {\n diffs.push({\n field: 'distribution_type',\n local: localConfig.distribution_type,\n server: serverDistribution,\n });\n }\n\n // UI apps have no OAuth callback, and `ctx.redirectUris` falls back to the\n // default localhost URI when the server returns none \u2014 comparing the two would\n // report a permanent phantom diff on every UI-app scaffold. Skip it entirely.\n if (!isUiAppConfig(localConfig)) {\n const localRedirects = [...(localConfig.auth?.redirectUris ?? [])].sort((a, b) =>\n a.localeCompare(b),\n );\n const serverRedirects = [...ctx.redirectUris].sort((a, b) => a.localeCompare(b));\n if (JSON.stringify(localRedirects) !== JSON.stringify(serverRedirects)) {\n diffs.push({\n field: 'redirectUris',\n local: localRedirects.join(', ') || '(none)',\n server: serverRedirects.join(', ') || '(none)',\n });\n }\n }\n\n // Scopes are OAuth-only too: a UI app's config carries no scopes by design\n // (`auth: {}`), so comparing against whatever the server\n // reports would flag drift on every refresh.\n if (!isUiAppConfig(localConfig)) {\n const localScopes = [...(localConfig.auth?.scopes ?? [])].sort((a, b) => a.localeCompare(b));\n const serverScopes = [...(ctx.appDetails?.scopes ?? [])]\n .filter((s) => s !== LEGACY_ALL_SCOPE)\n .sort((a, b) => a.localeCompare(b));\n if (JSON.stringify(localScopes) !== JSON.stringify(serverScopes)) {\n diffs.push({\n field: 'scopes',\n local: localScopes.join(', ') || '(none)',\n server: serverScopes.join(', ') || '(none)',\n });\n }\n }\n\n const localLogo = localConfig.logoUri ?? '';\n const serverLogo = ctx.appDetails?.logo_uri ?? '';\n if (localLogo !== serverLogo) {\n diffs.push({ field: 'logoUri', local: localLogo || '(none)', server: serverLogo || '(none)' });\n }\n\n const localVersion = localConfig.version ?? '';\n const serverVersion = ctx.appDetails?.version ?? '';\n if (localVersion !== serverVersion) {\n diffs.push({\n field: 'version',\n local: localVersion || '(none)',\n server: serverVersion || '(none)',\n });\n }\n\n // `ui_app` is deliberately NOT diffed. The local block is the author's source\n // of truth \u2014 the CLI writes it, the server validates it \u2014 and not every server\n // build echoes it back on reads. Diffing it would report drift against an\n // absent remote value and then a confirmed refresh would overwrite the\n // partner's hand-edited block with nothing. The local block is instead carried\n // through the refresh verbatim (see the ctx override in scaffoldCommand).\n\n return diffs;\n}\n\nfunction writeScaffoldFiles(\n files: Array<{ name: string; content: string }>,\n targetDir: string,\n mergeOnly: boolean,\n): number {\n let written = 0;\n for (const file of files) {\n const filePath = path.join(targetDir, file.name);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n if (mergeOnly && fs.existsSync(filePath)) continue;\n // Write .env.local with restricted permissions to protect secrets\n const writeOptions = file.name.endsWith('.env.local') ? { mode: 0o600 } : {};\n fs.writeFileSync(filePath, file.content, { encoding: 'utf-8', ...writeOptions });\n written++;\n }\n return written;\n}\n\ninterface TemplateVars {\n vars: Record<string, string>;\n scopes: string[];\n legacyAllSubstituted: boolean;\n}\n\n// Build the `{{...}}` substitution map shared by base and feature templates,\n// plus the resolved scopes and whether the legacy 'all' scope was substituted.\n// Render a `ui_app` block for embedding at a 2-space indent inside\n// app-config.json.tmpl. JSON.stringify only indents *nested* levels, so every\n// line after the first needs the parent's indent added to keep the emitted file\n// readable (and diff-friendly against a hand-edited one).\nfunction renderUiAppJson(uiApp: UiApp | undefined): string {\n if (!uiApp) return '';\n return JSON.stringify(uiApp, null, 2).replaceAll('\\n', '\\n ');\n}\n\nfunction buildTemplateVars(appId: string, ctx: AppContext, targetDir: string): TemplateVars {\n const rawAppName = ctx.appDetails?.name || path.basename(targetDir);\n const appName = rawAppName.replaceAll(/[\"\\\\\\n\\r\\t]/g, '').trim() || 'my-app';\n // Never propagate the deprecated legacy 'all' scope into a fresh\n // app-config.json \u2014 keep the app's granular scopes, fall back to the\n // defaults when 'all' was the only scope, and tell the user (BEX-214).\n // UI apps have no OAuth block at all (`auth: {}`), so their\n // scopes resolve to [] \u2014 the ui_app template branch never renders them.\n const remoteScopes = ctx.appDetails?.scopes;\n const legacyAllSubstituted = !ctx.uiApp && containsLegacyAllScope(remoteScopes);\n const granularScopes = (remoteScopes ?? []).filter((s) => s !== LEGACY_ALL_SCOPE);\n let scopes: string[];\n if (ctx.uiApp) {\n scopes = [];\n } else {\n scopes = granularScopes.length > 0 ? granularScopes : [...DEFAULT_SCOPES];\n }\n\n const slug = computeSlug(ctx.appDetails?.name);\n\n const vars = {\n '{{APP_NAME}}': appName,\n '{{APP_SLUG}}': slug,\n '{{APP_ID}}': String(appId),\n '{{CLIENT_ID}}': ctx.clientId,\n '{{CLIENT_SECRET}}': ctx.clientSecret,\n '{{REDIRECT_URI}}': ctx.redirectUri,\n '{{REDIRECT_URLS_JSON}}': JSON.stringify(ctx.redirectUris),\n '{{SCOPES_JSON}}': JSON.stringify(scopes),\n '{{DISTRIBUTION}}': ctx.appDetails?.distribution_type ?? 'private',\n '{{LOGO_URI}}': ctx.appDetails?.logo_uri ?? '',\n '{{APP_VERSION}}': ctx.appDetails?.version ?? '',\n '{{OAUTH_BASE}}': OAUTH_BASE,\n '{{OAUTH_REALM}}': OAUTH_REALM,\n // Empty for OAuth apps \u2014 its emptiness is what selects the `oauth`\n // conditional branch in templates (see resolveTemplateFlags).\n '{{UI_APP_JSON}}': renderUiAppJson(ctx.uiApp),\n };\n\n return { vars, scopes, legacyAllSubstituted };\n}\n\nexport interface BaseScaffoldResult {\n written: number;\n legacyAllSubstituted: boolean;\n scopes: string[];\n files: Array<{ name: string; content: string }>;\n}\n\n// Write the basic project structure (app-config.json + project meta files).\n// No prompting, no logging/jsonOutput \u2014 callers own how the result is reported.\nexport function runBaseScaffold(\n appId: string,\n ctx: AppContext,\n targetDir: string,\n mergeOnly: boolean,\n): BaseScaffoldResult {\n const { vars, scopes, legacyAllSubstituted } = buildTemplateVars(appId, ctx, targetDir);\n const files = loadBaseTemplates(vars);\n const written = writeScaffoldFiles(files, targetDir, mergeOnly);\n return { written, legacyAllSubstituted, scopes, files };\n}\n\nexport type FeatureConflictChoice = 'merge' | 'overwrite' | 'cancel';\n\n// Decide how to handle feature files that already exist on disk, before any\n// write. Precedence:\n// 1. `--overwrite` flag \u2192 overwrite, no prompt (interactive or --json)\n// 2. no existing files \u2192 merge (nothing to skip; identical to a fresh write)\n// 3. --json (no flag) \u2192 merge (non-destructive default; use --overwrite to force)\n// 4. interactive \u2192 prompt Overwrite / Merge / Cancel\nexport async function resolveFeatureConflict(\n featureType: FeatureType,\n appId: string,\n ctx: AppContext,\n targetDir: string,\n opts: { jsonMode: boolean; overwrite: boolean },\n): Promise<FeatureConflictChoice> {\n if (opts.overwrite) return 'overwrite';\n\n const { vars } = buildTemplateVars(appId, ctx, targetDir);\n const files = loadFeatureTemplates(featureType, vars);\n const anyExists = files.some((f) => fs.existsSync(path.join(targetDir, f.name)));\n if (!anyExists) return 'merge';\n\n // --json can't prompt \u2014 stay non-destructive and merge. Scripts that want to\n // regenerate feature files must pass --overwrite explicitly.\n if (opts.jsonMode) return 'merge';\n\n const { action } = await inquirer.prompt([\n {\n type: 'list',\n name: 'action',\n message: messages.APP_SCAFFOLD_FEATURE_EXISTS,\n choices: indentChoices([\n { name: messages.APP_SCAFFOLD_FEATURE_EXISTS_OVERWRITE, value: 'overwrite' },\n { name: messages.APP_SCAFFOLD_FEATURE_EXISTS_MERGE, value: 'merge' },\n { name: messages.APP_SCAFFOLD_FEATURE_EXISTS_CANCEL, value: 'cancel' },\n ]),\n },\n ]);\n return action as FeatureConflictChoice;\n}\n\nexport interface FeatureScaffoldResult {\n written: number;\n files: Array<{ name: string; content: string }>;\n}\n\n// Write a single feature's files into an existing project directory.\nexport function runFeatureScaffold(\n featureType: FeatureType,\n appId: string,\n ctx: AppContext,\n targetDir: string,\n mergeOnly: boolean,\n): FeatureScaffoldResult {\n const { vars } = buildTemplateVars(appId, ctx, targetDir);\n if (featureType === 'oauth') {\n fs.mkdirSync(path.join(targetDir, 'src', 'oauth'), { recursive: true });\n }\n const files = loadFeatureTemplates(featureType, vars);\n const written = writeScaffoldFiles(files, targetDir, mergeOnly);\n return { written, files };\n}\n\n// Report the basic project structure written by `brevo app create` \u2014 shown\n// with the created-app box, before the feature prompt. No \"Next steps\" box\n// (that belongs to a feature scaffold, or the base-only follow-up in create.ts).\nexport function reportBaseScaffoldSuccess(result: {\n written: number;\n legacyAllSubstituted: boolean;\n scopes: string[];\n files: Array<{ name: string; content: string }>;\n}): void {\n logSuccess(messages.APP_CREATE_BASE_SUCCESS(result.written, result.files.length));\n if (result.legacyAllSubstituted) {\n logWarn(messages.LEGACY_ALL_SCOPE_SCAFFOLD_SUBSTITUTED(result.scopes.join(', ')));\n }\n printFileTree(result.files.map((f) => f.name));\n}\n\nexport function reportScaffoldSuccess(result: {\n written: number;\n legacyAllSubstituted: boolean;\n scopes: string[];\n files: Array<{ name: string; content: string }>;\n targetDir: string;\n cdDir?: string;\n}): void {\n logSuccess(messages.APP_SCAFFOLD_SUCCESS(result.written, result.files.length));\n if (result.legacyAllSubstituted) {\n logWarn(messages.LEGACY_ALL_SCOPE_SCAFFOLD_SUBSTITUTED(result.scopes.join(', ')));\n }\n printFileTree(result.files.map((f) => f.name));\n\n printBox(\n messages.APP_SCAFFOLD_NEXT_STEPS_TITLE,\n messages.APP_SCAFFOLD_NEXT_STEPS_LINES(result.cdDir),\n );\n logInfo(messages.APP_SCAFFOLD_SCOPES_TIP);\n}\n\n// process.chdir() only moves the CLI's own process, never the shell the user\n// typed the command in \u2014 so the \"cd\" hint in Next steps must be relative to\n// where the command was actually invoked (captured before any chdir), not\n// the CLI's current (already-moved) cwd.\nexport function computeCdHint(originalCwd: string, targetDir: string): string | undefined {\n return path.relative(originalCwd, targetDir) || undefined;\n}\n", "import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nconst TEMPLATES_DIR = path.resolve(__dirname, 'files');\n\n/**\n * Read a .tmpl file from the templates/files directory.\n * Resolved at runtime so template files can be edited without recompiling.\n */\nfunction loadTemplate(relativePath: string): string {\n return fs.readFileSync(path.join(TEMPLATES_DIR, relativePath), 'utf-8');\n}\n\n/**\n * Replace all occurrences of the given keys in a template string.\n * Keys should include delimiters (e.g. '{{APP_NAME}}').\n */\nexport function applyVars(template: string, vars: Record<string, string>): string {\n let result = template;\n for (const [key, value] of Object.entries(vars)) {\n result = result.replaceAll(key, value);\n }\n return result;\n}\n\nexport type Distribution = 'public' | 'private';\n\n/**\n * Conditional flags a template can branch on.\n *\n * - `public` / `private` \u2014 the app's distribution type (PKCE vs confidential\n * OAuth flow).\n * - `oauth` / `ui_app` \u2014 the app *type* (BEX-290). Exactly one is always set,\n * so a template can carry OAuth-only and UI-app-only sections side by side.\n */\nexport type TemplateFlag = 'public' | 'private' | 'oauth' | 'ui_app';\n\nconst IF_OPEN_RE = /^\\s*\\{\\{#if (public|private|oauth|ui_app)\\}\\}\\s*$/;\nconst IF_CLOSE_RE = /^\\s*\\{\\{\\/if\\}\\}\\s*$/;\n\n/**\n * Strip conditional blocks from a template.\n *\n * Blocks are delimited by whole-line markers:\n *\n * {{#if public}}\n * ...lines emitted only for public apps...\n * {{/if}}\n * {{#if ui_app}}\n * ...lines emitted only for UI apps...\n * {{/if}}\n *\n * A block's body is kept only when its flag is in `flags`; otherwise the whole\n * block is dropped. Blocks may nest. The marker lines are always removed in full\n * (including their line break), so a template whose only markers wrap the\n * *matching* branch renders byte-for-byte identically to one written without any\n * markers \u2014 this is what keeps private-app scaffolds unchanged from before PKCE\n * branching was introduced, and what keeps OAuth scaffolds unchanged from before\n * UI-app branching was introduced.\n *\n * Accepts a bare `Distribution` for backwards compatibility with existing\n * callers and tests that predate the app-type flags.\n *\n * `{{DISTRIBUTION}}` and other `{{VAR}}` placeholders are left untouched here;\n * they are resolved separately by {@link applyVars}.\n */\nexport function applyConditionals(\n template: string,\n flags: Distribution | ReadonlySet<TemplateFlag>,\n): string {\n const activeFlags: ReadonlySet<TemplateFlag> =\n typeof flags === 'string' ? new Set<TemplateFlag>([flags]) : flags;\n const lines = template.split('\\n');\n const out: string[] = [];\n // Stack of block states; `keep` is false once any enclosing block excludes us.\n const stack: boolean[] = [];\n const active = (): boolean => stack.length === 0 || stack.at(-1) === true;\n\n for (const line of lines) {\n const open = IF_OPEN_RE.exec(line);\n if (open) {\n stack.push(active() && activeFlags.has(open[1] as TemplateFlag));\n continue;\n }\n if (IF_CLOSE_RE.test(line)) {\n if (stack.length === 0) {\n throw new Error('applyConditionals: unmatched {{/if}}');\n }\n stack.pop();\n continue;\n }\n if (active()) {\n out.push(line);\n }\n }\n\n if (stack.length > 0) {\n throw new Error('applyConditionals: unclosed {{#if}}');\n }\n return out.join('\\n');\n}\n\n/**\n * Scaffold file manifest \u2014 maps output file paths to their .tmpl source files.\n * Add new templates here; the scaffold command picks them up automatically.\n */\nexport interface TemplateFile {\n /** Relative output path inside the scaffolded project */\n outputPath: string;\n /** Relative path inside templates/files/ */\n templatePath: string;\n}\n\n// The basic project structure written by `brevo app create` \u2014 the linked-app\n// config plus project meta/docs. No feature-specific code lives here.\nexport const BASE_TEMPLATE_MANIFEST: TemplateFile[] = [\n { outputPath: 'app-config.json', templatePath: 'app-config.json.tmpl' },\n { outputPath: '.gitignore', templatePath: 'gitignore.tmpl' },\n { outputPath: 'AGENTS.md', templatePath: 'AGENTS.md.tmpl' },\n { outputPath: 'CLAUDE.md', templatePath: 'CLAUDE.md.tmpl' },\n { outputPath: 'README.md', templatePath: 'README.md.tmpl' },\n];\n\n// Feature code written by `brevo app scaffold` (or `create`'s optional\n// follow-up), keyed by feature type. 'oauth' is the only feature today \u2014\n// the \"Test OAuth App\" local callback server.\nexport const FEATURE_TEMPLATE_MANIFESTS: Record<'oauth', TemplateFile[]> = {\n oauth: [\n { outputPath: 'src/oauth/server.js', templatePath: 'src/oauth/server.js.tmpl' },\n { outputPath: 'src/oauth/handler.js', templatePath: 'src/oauth/handler.js.tmpl' },\n {\n outputPath: 'src/oauth/token-store.js',\n templatePath: 'src/oauth/token-store.js.tmpl',\n },\n {\n outputPath: 'src/oauth/.env.example',\n templatePath: 'src/oauth/.env.example.tmpl',\n },\n { outputPath: 'src/oauth/.env.local', templatePath: 'src/oauth/.env.local.tmpl' },\n {\n outputPath: 'src/oauth/package.json',\n templatePath: 'src/oauth/package.json.tmpl',\n },\n ],\n};\n\nexport type FeatureType = keyof typeof FEATURE_TEMPLATE_MANIFESTS;\n\n/**\n * How each feature is named on screen \u2014 in the picker `app scaffold` shows once\n * there is more than one to choose from, and in the confirm that names the single\n * one while there isn't.\n *\n * Kept beside the manifest, and typed against it, so a new feature cannot be added\n * without a label: the record above is the registry the prompts are derived from,\n * which is what lets the CLI stop asking a question that has only one answer.\n */\nexport const FEATURE_LABELS: Record<FeatureType, string> = {\n oauth: 'Test OAuth App',\n};\n\n/**\n * Derive the conditional flag set from the template vars.\n *\n * Distribution: public apps get a PKCE (RFC 7636) OAuth flow with no client\n * secret, private apps keep the confidential-client flow. `{{DISTRIBUTION}}` is\n * always set by buildTemplateVars; default to the (unchanged) private flow if\n * it's absent.\n *\n * App type: `{{UI_APP_JSON}}` is non-empty only for UI apps, so its presence is\n * the discriminator \u2014 mirroring how `ui_app` in app-config.json discriminates\n * at runtime (see `isUiAppConfig`).\n */\nfunction resolveTemplateFlags(vars: Record<string, string>): Set<TemplateFlag> {\n const distribution: Distribution = vars['{{DISTRIBUTION}}'] === 'public' ? 'public' : 'private';\n const isUiApp = !!vars['{{UI_APP_JSON}}'];\n return new Set<TemplateFlag>([distribution, isUiApp ? 'ui_app' : 'oauth']);\n}\n\nfunction loadManifest(\n manifest: TemplateFile[],\n vars: Record<string, string>,\n): Array<{ name: string; content: string }> {\n const flags = resolveTemplateFlags(vars);\n return manifest.map((entry) => ({\n name: entry.outputPath,\n content: applyVars(applyConditionals(loadTemplate(entry.templatePath), flags), vars),\n }));\n}\n\n/**\n * Load the base project templates (app-config.json + project meta files),\n * apply variable substitution, and return an array of { name, content }\n * ready to write.\n */\nexport function loadBaseTemplates(\n vars: Record<string, string>,\n): Array<{ name: string; content: string }> {\n return loadManifest(BASE_TEMPLATE_MANIFEST, vars);\n}\n\n/**\n * Load a single feature's templates, apply variable substitution, and return\n * an array of { name, content } ready to write.\n */\nexport function loadFeatureTemplates(\n featureType: FeatureType,\n vars: Record<string, string>,\n): Array<{ name: string; content: string }> {\n return loadManifest(FEATURE_TEMPLATE_MANIFESTS[featureType], vars);\n}\n\n// \u2500\u2500 Named exports (loaded at import time from .tmpl files) \u2500\u2500\n// These allow existing code / tests that import individual templates to keep working.\n\nexport const oauthServerJsTemplate = loadTemplate('src/oauth/server.js.tmpl');\nexport const oauthHandlerTemplate = loadTemplate('src/oauth/handler.js.tmpl');\nexport const tokenStoreJsTemplate = loadTemplate('src/oauth/token-store.js.tmpl');\nexport const envExampleTemplate = loadTemplate('src/oauth/.env.example.tmpl');\nexport const envLocalTemplate = loadTemplate('src/oauth/.env.local.tmpl');\nexport const gitignoreTemplate = loadTemplate('gitignore.tmpl');\nexport const packageJsonTemplate = loadTemplate('src/oauth/package.json.tmpl');\nexport const appConfigTemplate = loadTemplate('app-config.json.tmpl');\nexport const agentsMdTemplate = loadTemplate('AGENTS.md.tmpl');\nexport const claudeMdTemplate = loadTemplate('CLAUDE.md.tmpl');\nexport const readmeTemplate = loadTemplate('README.md.tmpl');\n", "import inquirer from 'inquirer';\nimport { messages } from '../../lang/en';\nimport { FeatureType, FEATURE_TEMPLATE_MANIFESTS, FEATURE_LABELS } from '../../templates';\nimport { validateYesNo } from '../../lib/validators';\nimport { indentChoices } from '../../lib/ui';\n\n/**\n * The two questions about scaffolding a feature, in their own module because both\n * `app create` and `app scaffold` ask them and `create` already imports `./scaffold`\n * \u2014 putting them there and importing back would close a cycle.\n *\n * Both are derived from `FEATURE_TEMPLATE_MANIFESTS`, which is the point: the CLI\n * asks *which* feature only when the manifest holds more than one, so adding a\n * second feature restores the picker with no change here.\n */\n\n/** Every feature that can be scaffolded, in manifest order. */\nfunction featureTypes(): FeatureType[] {\n return Object.keys(FEATURE_TEMPLATE_MANIFESTS) as FeatureType[];\n}\n\n/**\n * The one feature to scaffold when the manifest holds exactly one, undefined\n * otherwise. Undefined is what makes a prompt necessary, so callers read as \"is\n * there a choice to make?\" rather than counting entries themselves.\n */\nfunction soleFeatureType(): FeatureType | undefined {\n const types = featureTypes();\n return types.length === 1 ? types[0] : undefined;\n}\n\n// The manifest is never empty; this only keeps the return type honest where\n// TypeScript cannot prove an index access lands on something.\nconst FALLBACK_FEATURE: FeatureType = 'oauth';\n\n/**\n * The single feature's label, or undefined once there is more than one \u2014 which is\n * what lets the confirm below name the feature while there is only one to name.\n */\nexport function soleFeatureLabel(): string | undefined {\n const only = soleFeatureType();\n return only ? FEATURE_LABELS[only] : undefined;\n}\n\n/**\n * Which feature to scaffold.\n *\n * A list with one entry is not a question: it is a keystroke that can only produce\n * one answer, put to a user who has already said what kind of app this is. So the\n * picker appears only once the manifest actually holds a choice \u2014 today it never\n * does, and `app create` / `app scaffold` go straight to writing the OAuth test\n * server.\n */\nexport async function promptFeatureType(interactive: boolean): Promise<FeatureType> {\n const types = featureTypes();\n const only = soleFeatureType();\n if (only) return only;\n if (!interactive) return types[0] ?? FALLBACK_FEATURE;\n const { featureType } = await inquirer.prompt([\n {\n type: 'list',\n name: 'featureType',\n message: messages.APP_SCAFFOLD_FEATURE_TYPE_PROMPT,\n choices: indentChoices(types.map((type) => ({ name: FEATURE_LABELS[type], value: type }))),\n },\n ]);\n return featureType;\n}\n\n/**\n * Whether to scaffold a feature at all. Defaults to yes \u2014 pressing Enter opts in.\n *\n * Asked by `app create` once the project is written, and by `app scaffold`'s\n * bootstrap for the same reason: in both, the project itself is what the user came\n * for and the feature is an extra they can decline. The feature-add mode of\n * `scaffold` does not ask \u2014 there, scaffolding a feature *is* the request.\n */\nexport async function promptScaffoldFeature(): Promise<boolean> {\n const { scaffoldRaw } = await inquirer.prompt([\n {\n type: 'input',\n name: 'scaffoldRaw',\n message: messages.APP_SCAFFOLD_FEATURE_CONFIRM(soleFeatureLabel()) + ' (Y/n)',\n default: 'y',\n validate: validateYesNo,\n },\n ]);\n const val = String(scaffoldRaw).toLowerCase().trim();\n return val === '' || val.startsWith('y');\n}\n", "/**\n * The shared command tail: what `app create` and `app scaffold`'s bootstrap both do once\n * the base project is on disk and reported.\n *\n * Its own module rather than part of `./project-writer` because the two are different\n * kinds of thing. The writer is primitives \u2014 render templates, write files, report what\n * landed \u2014 and knows nothing about a flow. This is the flow: it asks questions, branches\n * on the app type, and decides what to write next. Keeping them apart means a command can\n * take the primitives without the flow, which `app scaffold`'s feature-add path does.\n */\nimport { logInfo } from '../../lib/logger';\nimport { printBox } from '../../lib/ui';\nimport { messages } from '../../lang/en';\nimport { FeatureType } from '../../templates';\nimport { promptFeatureType, promptScaffoldFeature } from './scaffold-prompts';\nimport {\n AppContext,\n runFeatureScaffold,\n resolveFeatureConflict,\n reportScaffoldSuccess,\n} from './project-writer';\n\n/**\n * Everything a command does once the base project is on disk and reported: the UI-app\n * terminal message, the feature offer, the conflict question, the feature write and its\n * report.\n *\n * `app create` and `app scaffold`'s bootstrap had this tail twice, near-identically \u2014\n * two commands whose only real difference is how they *obtain* the app (one registers\n * it, one looks it up) had diverging answers to what happens afterwards. The base write\n * itself stays with each caller: they genuinely differ there (create must not print\n * under `--json`, and bootstrap defers the write off a TTY), and forcing that half\n * through here would need more flags than it would save.\n *\n * Prompts only when `offerFeature` is set. A caller passes `false` for a run that must\n * not block \u2014 `--json`, or a piped stdin \u2014 and gets `{ feature: null }` back without a\n * question having been asked.\n */\nexport interface FinishProjectParams {\n appId: string;\n ctx: AppContext;\n targetDir: string;\n /** Scopes from the base write, carried into the feature report. */\n baseScopes: string[];\n cdDir?: string;\n /** UI apps have no scaffoldable feature \u2014 an action link has no local server. */\n isUiApp: boolean;\n /** Ask \"scaffold a feature?\" at all. False for `--json` and non-TTY runs. */\n offerFeature: boolean;\n /**\n * What to do when the feature's files are already on disk.\n *\n * `'ask'` runs the overwrite / merge / cancel question (`app scaffold`, which is\n * routinely pointed at a populated directory). `'merge'` and `'overwrite'` skip it and\n * state the answer \u2014 `app create` passes the directory decision it already took, because\n * the user has just answered that exact question about this exact directory.\n */\n onConflict: 'ask' | 'merge' | 'overwrite';\n /** Forwarded to the conflict question; only read when `onConflict` is `'ask'`. */\n jsonMode?: boolean;\n overwriteFlag?: boolean;\n}\n\nexport type FinishProjectResult =\n | { cancelled: true }\n | { cancelled: false; feature: FeatureType | null; written: number };\n\nexport async function finishProject(params: FinishProjectParams): Promise<FinishProjectResult> {\n const { appId, ctx, targetDir, cdDir, isUiApp } = params;\n\n // No feature to offer, so this is the end of the road: name what comes next\n // (upload \u2192 deploy) rather than the OAuth test server the app can't use.\n if (isUiApp) {\n printBox(messages.APP_SCAFFOLD_NEXT_STEPS_TITLE, messages.APP_CREATE_UI_NEXT(cdDir));\n return { cancelled: false, feature: null, written: 0 };\n }\n\n const wanted = params.offerFeature && (await promptScaffoldFeature());\n if (!wanted) {\n logInfo(messages.APP_SCAFFOLD_SCOPES_TIP);\n printBox(messages.APP_SCAFFOLD_NEXT_STEPS_TITLE, messages.APP_CREATE_BASE_ONLY_NEXT(cdDir));\n return { cancelled: false, feature: null, written: 0 };\n }\n\n const feature = await promptFeatureType(true);\n\n let mergeOnly: boolean;\n if (params.onConflict === 'ask') {\n const choice = await resolveFeatureConflict(feature, appId, ctx, targetDir, {\n jsonMode: !!params.jsonMode,\n overwrite: !!params.overwriteFlag,\n });\n if (choice === 'cancel') {\n logInfo(messages.APP_SCAFFOLD_CANCELLED);\n return { cancelled: true };\n }\n mergeOnly = choice === 'merge';\n } else {\n mergeOnly = params.onConflict === 'merge';\n }\n\n const feat = runFeatureScaffold(feature, appId, ctx, targetDir, mergeOnly);\n reportScaffoldSuccess({\n written: feat.written,\n // Any legacy-'all' substitution was already surfaced by the base report, which every\n // caller prints before getting here \u2014 repeating it would warn twice for one config.\n legacyAllSubstituted: false,\n scopes: params.baseScopes,\n files: feat.files,\n targetDir,\n cdDir,\n });\n return { cancelled: false, feature, written: feat.written };\n}\n", "/**\n * Value formatting shared by every `ui_app` renderer.\n *\n * Three commands print the block \u2014 `app create`'s summary box, `app upload`'s diff, and\n * `app list` \u2014 and each has its own labels and column widths, which genuinely differ\n * (`Extension:` vs `Extension type:`) and are read by tests and users. So the LABELS stay in\n * the commands; only the value formatting lives here.\n *\n * That split is where the duplication actually mattered: the placement-with-context string was\n * written out identically in all three, and each copy had to be found and edited when the\n * entry gained its own per-page `context`. One of the two bugs fixed on this branch (the\n * unguarded null dereference) survived precisely because it existed in more than one copy.\n */\nimport type { UiApp } from '../../types';\n\n/**\n * One line per placement: the slot slug, plus its own record context when it narrows one.\n *\n * Per-entry rather than a single shared context row, because two record pages can forward\n * different fields and a combined row would hide that.\n *\n * Returns lines WITHOUT any label or indent \u2014 the caller owns those, including the\n * first-row-label / continuation-row-padding pattern each renderer uses.\n */\nexport function formatPlacementLines(uiApp: Pick<UiApp, 'surface_point_list'>): string[] {\n return (uiApp.surface_point_list ?? []).map((entry) => {\n const context = entry.context?.length ? ` (context: ${entry.context.join(', ')})` : '';\n return `${entry.surface_point_name}${context}`;\n });\n}\n", "import * as http from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport { AddressInfo } from 'node:net';\nimport { logDebug } from '../lib/logger';\nimport { CliError } from '../lib/errors';\nimport { messages } from '../lang/en';\n\nexport interface OAuthTokens {\n accessToken: string;\n refreshToken: string;\n expiresIn: number;\n tokenType: string;\n scope?: string;\n}\n\nexport interface RunBrowserLoginOptions {\n proxyUrl: string;\n timeoutMs?: number;\n openBrowser?: (url: string) => void;\n onWaiting?: (loginUrl: string) => void;\n}\n\nconst MAX_BODY_BYTES = 16 * 1024;\n// 5 minutes \u2014 accommodates SSO/2FA flows where the user may need to fetch a\n// code from another device. 120s was too tight and routinely timed out mid-2FA.\nconst DEFAULT_TIMEOUT_MS = 300_000;\n\ninterface RawTokenPayload {\n access_token?: unknown;\n refresh_token?: unknown;\n expires_in?: unknown;\n token_type?: unknown;\n scope?: unknown;\n}\n\nfunction normalizeTokens(raw: RawTokenPayload): OAuthTokens | null {\n if (\n typeof raw.access_token !== 'string' ||\n !raw.access_token ||\n typeof raw.refresh_token !== 'string' ||\n !raw.refresh_token ||\n typeof raw.expires_in !== 'number' ||\n !Number.isFinite(raw.expires_in) ||\n raw.expires_in <= 0 ||\n typeof raw.token_type !== 'string' ||\n !raw.token_type\n ) {\n return null;\n }\n return {\n accessToken: raw.access_token,\n refreshToken: raw.refresh_token,\n expiresIn: raw.expires_in,\n tokenType: raw.token_type,\n scope: typeof raw.scope === 'string' ? raw.scope : undefined,\n };\n}\n\nexport async function runBrowserLoginFlow(opts: RunBrowserLoginOptions): Promise<OAuthTokens> {\n const proxyOrigin = new URL(opts.proxyUrl).origin;\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const openBrowser = opts.openBrowser ?? (() => {});\n\n return new Promise<OAuthTokens>((resolve, reject) => {\n let settled = false;\n const claimSettlement = (): boolean => {\n if (settled) return false;\n settled = true;\n return true;\n };\n\n const server = http.createServer((req, res) => {\n const pathname = new URL(req.url ?? '/', 'http://127.0.0.1').pathname;\n const origin = req.headers.origin;\n logDebug('loopback request', { method: req.method, url: req.url, pathname, origin });\n\n if (req.method === 'OPTIONS' && pathname === '/callback') {\n if (origin !== proxyOrigin) {\n logDebug('loopback OPTIONS rejected: origin mismatch', { origin, expected: proxyOrigin });\n res.writeHead(403).end();\n return;\n }\n res\n .writeHead(204, {\n 'Access-Control-Allow-Origin': proxyOrigin,\n 'Access-Control-Allow-Methods': 'POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n 'Access-Control-Max-Age': '600',\n })\n .end();\n return;\n }\n\n if (req.method === 'POST' && pathname === '/callback') {\n if (origin !== proxyOrigin) {\n logDebug('loopback POST rejected: origin mismatch', { origin, expected: proxyOrigin });\n res.writeHead(403).end();\n return;\n }\n\n let bytes = 0;\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => {\n bytes += chunk.length;\n if (bytes > MAX_BODY_BYTES) {\n logDebug('loopback POST rejected: body too large', { bytes, max: MAX_BODY_BYTES });\n res.writeHead(413, { Connection: 'close' }).end();\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on('end', () => {\n let parsed: RawTokenPayload | null = null;\n try {\n parsed = JSON.parse(Buffer.concat(chunks).toString('utf-8')) as RawTokenPayload;\n } catch {\n parsed = null;\n }\n const tokens = parsed ? normalizeTokens(parsed) : null;\n if (!tokens) {\n logDebug('loopback POST rejected: bad payload shape', {\n hasParsed: parsed !== null,\n keys: parsed ? Object.keys(parsed) : null,\n });\n res\n .writeHead(400, {\n 'Access-Control-Allow-Origin': proxyOrigin,\n 'Content-Type': 'text/plain',\n })\n .end('Bad payload');\n return;\n }\n logDebug('loopback POST accepted', { hasScope: tokens.scope !== undefined });\n res.writeHead(204, { 'Access-Control-Allow-Origin': proxyOrigin }).end();\n if (claimSettlement()) {\n server.close();\n resolve(tokens);\n }\n });\n return;\n }\n\n if (req.method === 'GET' && (pathname === '/' || pathname === '/callback')) {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(\n '<!doctype html><meta charset=\"utf-8\"><title>Brevo CLI login</title>' +\n '<p>Waiting for login to complete \u2014 you can close this tab once the CLI confirms success.</p>',\n );\n return;\n }\n\n logDebug('loopback request not matched', { method: req.method, pathname });\n res.writeHead(404).end();\n });\n\n server.on('error', (err) => {\n logDebug('loopback server error', { message: (err as Error).message });\n if (claimSettlement()) reject(err);\n });\n\n server.listen(0, '127.0.0.1', () => {\n const port = (server.address() as AddressInfo).port;\n // Unique per-attempt token. If a previous attempt timed out mid-flow,\n // the browser may still have a tab open at the proxy showing an error;\n // a unique URL forces a fresh navigation instead of reusing that tab,\n // and prevents the proxy/browser from serving a cached response.\n const attemptToken = randomUUID();\n const loginUrl = `${opts.proxyUrl}/login?port=${port}&t=${attemptToken}`;\n logDebug('loopback listening', { host: '127.0.0.1', port, proxyOrigin });\n opts.onWaiting?.(loginUrl);\n try {\n openBrowser(loginUrl);\n } catch {\n // best-effort \u2014 URL already surfaced via onWaiting\n }\n });\n\n const timer = setTimeout(() => {\n if (claimSettlement()) {\n server.close();\n reject(new CliError(messages.AUTH_BROWSER_TIMEOUT));\n }\n }, timeoutMs);\n timer.unref?.();\n\n server.on('close', () => clearTimeout(timer));\n });\n}\n", "/**\n * `brevo app scaffold` \u2014 the command.\n *\n * Two modes, chosen by whether cwd holds an `app-config.json`: with one it adds a feature\n * to the linked app, without one it *bootstraps* a directory for an app that already\n * exists. What both modes have in common \u2014 fetching the app, resolving a directory,\n * writing the files \u2014 lives in `./project-writer`, and everything both do *after* the\n * base project lands (offer the feature, resolve a conflict, write it, report) lives in\n * `./finish-project`. Both are shared with `app create`, which reaches the same tail once\n * its app exists. This file is only the part that decides WHICH app and WHETHER to write.\n */\nimport inquirer from 'inquirer';\nimport { logSuccess, logInfo } from '../../lib/logger';\nimport { printBox } from '../../lib/ui';\nimport { messages } from '../../lang/en';\nimport { withCommandHandler } from '../../lib/command-handler';\nimport { CliError } from '../../lib/errors';\nimport { jsonOutput } from '../../lib/json-output';\nimport {\n readProjectConfig,\n readProjectConfigAt,\n findEnclosingProjectDir,\n ProjectConfig,\n isUiAppConfig,\n} from '../../lib/config';\nimport { resolveFromRecord } from '../../app-types';\nimport { stripUiAppWireOnlyKeys } from '../../app-types/wire';\nimport { promptAppSelection } from './select-app';\nimport { promptFeatureType } from './scaffold-prompts';\n// The project writer. `app create` imports the same module directly \u2014 neither command\n// reaches the other, which is the point of the split.\nimport {\n AppContext,\n ConfigDiff,\n computeSlug,\n diffLocalConfig,\n fetchAppContext,\n resolveProjectDirectory,\n applyProjectDirectory,\n runBaseScaffold,\n runFeatureScaffold,\n resolveFeatureConflict,\n reportBaseScaffoldSuccess,\n reportScaffoldSuccess,\n computeCdHint,\n printFileTree,\n} from './project-writer';\nimport { finishProject } from './finish-project';\n\n// Resolve which app this project is linked to (from cwd's app-config.json),\n// and decide whether the base config has drifted from the server. Returns a\n// cancellation instead of prompting when running under --json.\ninterface ScaffoldPlanResolved {\n cancelled: false;\n appId: string;\n ctx: AppContext;\n refreshBase: boolean;\n}\n\ninterface ScaffoldPlanCancelled {\n cancelled: true;\n reason?: string;\n diffs?: ConfigDiff[];\n}\n\ntype ScaffoldPlan = ScaffoldPlanResolved | ScaffoldPlanCancelled;\n\ntype BaseRefreshDecision = { cancelled: false; refreshBase: boolean } | ScaffoldPlanCancelled;\n\n/**\n * Diff a config that is already on disk against the server, and decide whether to\n * rewrite it.\n *\n * Shared by both modes, because both can end up holding one. The feature-add path\n * always does \u2014 a local config is what puts it in that mode. A bootstrap does whenever\n * the directory it was pointed at turns out to hold a project already, which is the\n * common way to run it: someone re-running `app scaffold` over the folder they made\n * last week. That case used to skip this question entirely and take the *directory*\n * prompt's merge answer instead, which silently dropped the whole refresh \u2014 see the\n * call site.\n *\n * The outcome is a full overwrite or nothing, never a merge: merging keeps the file\n * that exists, which is precisely the file a refresh has to rewrite.\n */\nasync function resolveBaseRefresh(\n localConfig: ProjectConfig,\n ctx: AppContext,\n jsonMode: boolean,\n): Promise<BaseRefreshDecision> {\n const diffs = diffLocalConfig(localConfig, ctx);\n\n // No drift \u2192 nothing to refresh; just add the feature.\n if (diffs.length === 0) {\n return { cancelled: false, refreshBase: false };\n }\n\n // --json can't prompt for confirmation \u2014 decline and surface the diffs so a\n // script can decide how to proceed.\n if (jsonMode) {\n return { cancelled: true, reason: messages.APP_SCAFFOLD_JSON_DIFF_CANCELLED, diffs };\n }\n\n logInfo(messages.APP_SCAFFOLD_DIFF_INTRO(localConfig.appName || localConfig.appId));\n for (const diff of diffs) {\n logInfo(messages.APP_SCAFFOLD_DIFF_LINE(diff.field, diff.local, diff.server));\n }\n const { confirmed } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'confirmed',\n message: messages.APP_SCAFFOLD_DIFF_CONFIRM,\n default: true,\n },\n ]);\n if (!confirmed) return { cancelled: true };\n return { cancelled: false, refreshBase: true };\n}\n\nasync function resolveScaffoldPlan(\n localConfig: ProjectConfig,\n jsonMode: boolean,\n): Promise<ScaffoldPlan> {\n const appId = localConfig.appId;\n // Carry the local `ui_app` block into the context so that if the user consents\n // to a config refresh, `runBaseScaffold` rewrites app-config.json *with* it\n // rather than dropping it (the refresh is a full overwrite, not a merge).\n const ctx = await fetchAppContext(appId, jsonMode, localConfig.ui_app);\n const refresh = await resolveBaseRefresh(localConfig, ctx, jsonMode);\n if (refresh.cancelled) return refresh;\n return { cancelled: false, appId, ctx, refreshBase: refresh.refreshBase };\n}\n\n// Set an empty directory up for an app that already exists on the platform.\n//\n// Unlike `resolveScaffoldPlan` there is nothing local to diff against, so there is\n// no drift question to ask and `refreshBase` is unconditionally true \u2014 writing\n// app-config.json is the point of the command in this mode, not a side effect of\n// consenting to a refresh.\n//\n// This is also the one place the *server* is authoritative about the app type.\n// `fetchAppContext` deliberately ignores `appDetails.ui_app` and takes the block\n// from its caller, because its two original callers both knew the type locally and\n// stale server data must not reclassify an app. Bootstrapping knows nothing\n// locally \u2014 that is its premise \u2014 so the server's answer is the only one there is,\n// and taking it is the same choice those callers made, not an exception to it.\nasync function resolveBootstrapPlan(appId: string, jsonMode: boolean): Promise<ScaffoldPlan> {\n if (!jsonMode) logInfo(messages.APP_SCAFFOLD_BOOTSTRAP_INTRO(appId));\n const probe = await fetchAppContext(appId, jsonMode);\n const record = probe.appDetails;\n\n // Refuse before writing anything when the server cannot answer with enough to rebuild\n // a complete app-config.json.\n //\n // Reachable through the app-type classifier, not through anything the server does with\n // snapshots: `isUiAppRecordShape` calls a record a UI app whenever it carries no OAuth\n // material, block or no block, so a record with neither is classified `ui` and lands\n // here with nothing to write. See `recoverableFromRecord` in `src/app-types/ui/index.ts`.\n //\n // This used to name a different case \u2014 \"a UI app created but never uploaded\" \u2014 on the\n // belief that the read endpoint could only source `ui_app` from an upload snapshot. That\n // was wrong: create writes the snapshot in its own transaction, so such an app does come\n // back with its block.\n //\n // It has to be a refusal rather than a partial write, because the omission is invisible.\n // The presence of `ui_app` IS the app-type discriminator, so a config written without it\n // does not read as an incomplete UI app \u2014 it reads as a perfectly valid OAuth one, and\n // the next `app upload` pushes an `auth` block where `ui_app` belonged.\n //\n // Asked of the app type rather than tested inline as `!record.ui_app`, so a third type\n // answers the same question instead of needing someone to find this branch by hand.\n // Skipped entirely when there is no record at all: that is a fetch failure, which\n // `fetchAppContext` has already reported on its own terms.\n const appType = resolveFromRecord(record);\n if (record && !appType.recoverableFromRecord(record)) {\n throw new CliError(messages.APP_SCAFFOLD_BOOTSTRAP_UNRECOVERABLE(appId));\n }\n\n // Strip the keys the platform owns before the block reaches app-config.json \u2014 it\n // injects `link_target`, manages the snapshot `version`, and stamps the dotted\n // `extension_point_name` onto every entry. None is authored, and writing one into the\n // file puts a value there that the very next `app upload` rejects as an unknown key.\n // Same owner the upload diff and write-back use; see `src/app-types/wire.ts`.\n const serverUiApp = record?.ui_app ? stripUiAppWireOnlyKeys(record.ui_app) : undefined;\n const ctx = serverUiApp ? { ...probe, uiApp: serverUiApp } : probe;\n return { cancelled: false, appId, ctx, refreshBase: true };\n}\n\n/**\n * Which app should an empty directory be set up for?\n *\n * `--app-id` wins when given \u2014 it is the non-interactive entry point and the migration\n * path off `brevo app update --app-id <id>`. Without it, an interactive run picks from\n * the account's apps, because a user who has lost their project folder (fresh clone, new\n * laptop, a create that ran in CI) has the app but not necessarily its ID.\n *\n * Falls back to the no-config error whenever prompting is impossible \u2014 under `--json` or\n * off a TTY \u2014 rather than picking an app on the user's behalf. That error already names\n * `--app-id`, which is the answer for those cases.\n */\nasync function resolveBootstrapAppId(\n requestedAppId: string | undefined,\n jsonMode: boolean,\n): Promise<string | undefined> {\n if (requestedAppId) return requestedAppId;\n if (jsonMode || !process.stdin.isTTY) {\n throw new CliError(messages.APP_SCAFFOLD_NO_CONFIG);\n }\n // Asked before the picker rather than opening straight into it. Bootstrapping is\n // not what `scaffold` normally does, and the most common way to arrive here is a\n // mistyped `cd` \u2014 for that user the list of their apps is a non-sequitur, and the\n // useful answer is \"no\". Returning `undefined` for a decline keeps that a normal\n // outcome rather than an error the caller has to recognise.\n logInfo(messages.APP_SCAFFOLD_BOOTSTRAP_OFFER);\n const { useExisting } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'useExisting',\n message: messages.APP_SCAFFOLD_BOOTSTRAP_CONFIRM,\n default: true,\n },\n ]);\n if (!useExisting) return undefined;\n const { appId } = await promptAppSelection(messages.APP_SCAFFOLD_SELECT);\n return appId;\n}\n\n/**\n * Where should a bootstrap write?\n *\n * The feature-add path has no question to answer \u2014 its directory is the project it\n * was run in. A bootstrap does: it is the one `scaffold` entry point that can be\n * invoked from a directory the user never meant to fill, and the most likely such\n * directory is the folder they keep their apps *in*. Writing eleven files\n * (`app-config.json`, `README.md`, `CLAUDE.md`, `AGENTS.md`, `.gitignore`, the\n * OAuth server) straight into that folder is silent and tedious to undo, so the\n * command asks the same question `app create` asks, with the same default \u2014\n * `./<slug of the app's name>` \u2014 and the same escape: type `.` to stay put.\n *\n * Interactive only. Under `--json` or off a TTY the answer stays the current\n * directory, because `scaffold --app-id <id>` is the scripted migration path off\n * `app update --app-id` and a pipeline that already `mkdir`s and `cd`s must not\n * start finding its config one level deeper. `resolveProjectDirectory` would not\n * prompt in that mode anyway \u2014 it would take the default, which is exactly the\n * relocation we must not do.\n */\nasync function resolveBootstrapDirectory(\n ctx: AppContext,\n jsonMode: boolean,\n appId: string,\n): Promise<{ targetDir: string; mergeOnly: boolean } | undefined> {\n if (jsonMode || !process.stdin.isTTY) return undefined;\n\n // Refuse a target that is already *another* app's project, and do it before the\n // Overwrite / Merge question rather than after: writing this app's credentials into\n // that directory would leave a project whose app-config.json and src/oauth/.env.local\n // name two different apps, so there is nothing to ask about. The `--app-id` mismatch\n // guard cannot catch this \u2014 it compares against the current directory, and this target\n // is elsewhere. A target holding *this* app's project is fine and is resolved as a\n // refresh by the caller.\n const refuseIfLinkedElsewhere = (targetDir: string): void => {\n const targetConfig = readProjectConfigAt(targetDir);\n if (targetConfig && targetConfig.appId !== appId) {\n throw new CliError(\n messages.APP_SCAFFOLD_TARGET_LINKED_ELSEWHERE(targetDir, targetConfig.appId, appId),\n );\n }\n };\n\n const defaultDir = `./${computeSlug(ctx.appDetails?.name)}`;\n let dir = await resolveProjectDirectory(defaultDir, false, refuseIfLinkedElsewhere);\n while (!dir.unresolved && dir.chooseAgain) {\n dir = await resolveProjectDirectory(defaultDir, false, refuseIfLinkedElsewhere);\n }\n if (dir.unresolved) {\n // Unreachable: `unresolved` is only ever returned with jsonMode=true, which\n // returned above. Fail loudly rather than guess if that ever changes.\n throw new CliError(messages.APP_CREATE_DIR_UNRESOLVED);\n }\n // Safe to create and move into it immediately: unlike `app create`, nothing has\n // been registered on the server that a later failure could orphan \u2014 the app\n // already exists and this command only writes files.\n applyProjectDirectory(dir);\n return { targetDir: dir.targetDir, mergeOnly: dir.mergeOnly };\n}\n\n/** The user turned the bootstrap offer down \u2014 distinct from \"there is nothing to bootstrap\". */\nconst BOOTSTRAP_DECLINED = Symbol('bootstrap-declined');\n\n/**\n * Which app the run is for, before anything is fetched or written.\n *\n * Two mutually exclusive pre-flights \u2014 a run either has a local config or it doesn't \u2014 so\n * they live together even though only one applies. Returns the app to bootstrap,\n * `undefined` when there is a local config (nothing to bootstrap), or `BOOTSTRAP_DECLINED`.\n */\nasync function resolveBootstrapTarget(\n localConfig: ProjectConfig | null,\n requestedAppId: string | undefined,\n jsonMode: boolean,\n): Promise<string | undefined | typeof BOOTSTRAP_DECLINED> {\n if (localConfig) {\n // Checked before any fetch or write: pointing `--app-id` at a directory that\n // belongs to another app is a mistake worth catching for free, and a bootstrap\n // here would overwrite that app's app-config.json with a different app's.\n if (requestedAppId && localConfig.appId !== requestedAppId) {\n throw new CliError(messages.APP_SCAFFOLD_APP_ID_MISMATCH(localConfig.appId, requestedAppId));\n }\n return undefined;\n }\n\n // `readProjectConfig` reads cwd and deliberately does not walk up, which makes a\n // directory one level inside a project indistinguishable from an empty one\n // outside it. They must not get the same answer: bootstrapping into `myapp/src/`\n // would leave a second app-config.json nested in the first, after which\n // `app upload` from that directory pushes the wrong app with no warning. Checked\n // before the picker so the user is told what is wrong rather than being asked to\n // choose an app the command will not use.\n const enclosingProject = findEnclosingProjectDir();\n if (enclosingProject) {\n throw new CliError(messages.APP_SCAFFOLD_INSIDE_PROJECT(enclosingProject));\n }\n return (await resolveBootstrapAppId(requestedAppId, jsonMode)) || BOOTSTRAP_DECLINED;\n}\n\nfunction reportPlanCancelled(plan: ScaffoldPlanCancelled, jsonMode: boolean): void {\n if (jsonMode) {\n jsonOutput({\n cancelled: true,\n ...(plan.reason ? { reason: plan.reason } : {}),\n ...(plan.diffs ? { diffs: plan.diffs } : {}),\n });\n return;\n }\n logInfo(messages.APP_SCAFFOLD_CANCELLED);\n}\n\n/** Where the files land, and whether the base is rewritten when they do. */\ninterface ScaffoldLayout {\n targetDir: string;\n cdDir: string | undefined;\n refreshBase: boolean;\n baseMergeOnly: boolean;\n}\n\n/**\n * Resolve the target directory and settle the base-refresh question for it.\n *\n * `null` means the user cancelled.\n */\nasync function resolveScaffoldLayout(\n localConfig: ProjectConfig | null,\n appId: string,\n ctx: AppContext,\n planRefreshBase: boolean,\n jsonMode: boolean,\n): Promise<ScaffoldLayout | null> {\n // Captured before `resolveBootstrapDirectory` may chdir: the `cd` hint has to\n // be relative to the shell the user typed the command in, not to the directory\n // the CLI has since moved its own process into.\n const originalCwd = process.cwd();\n const bootstrapDir = localConfig\n ? undefined\n : await resolveBootstrapDirectory(ctx, jsonMode, appId);\n const targetDir = bootstrapDir?.targetDir ?? process.cwd();\n // `cd` is only worth printing when the files did not land where the user is\n // standing; `computeCdHint` returns undefined for the directory they're in.\n const cdDir = bootstrapDir ? computeCdHint(originalCwd, targetDir) : undefined;\n // Merging the *base* files is the directory decision's call, not the feature\n // prompt's \u2014 it answers \"this directory already had files in it\", which only a\n // bootstrap that was pointed at a non-empty directory can hit. The feature-add\n // path keeps its full overwrite: a consented refresh means \"make it match the\n // server\", which a merge would quietly not do.\n let baseMergeOnly = bootstrapDir?.mergeOnly ?? false;\n let refreshBase = planRefreshBase;\n\n // A bootstrap pointed at a directory that already holds a project is a *refresh*,\n // and has to be resolved as one rather than left to the directory prompt's\n // merge answer.\n //\n // Those two answer different questions. \"Merge (keep existing, add missing)\" is\n // about not clobbering the user's own files, and `writeScaffoldFiles` implements it\n // by skipping any path that exists. app-config.json always exists in this case, so\n // merging skipped the one file the bootstrap exists to write \u2014 the command fetched\n // the app, discarded every field of it, wrote nothing, and printed the success box\n // anyway. `resolveBootstrapPlan` sets `refreshBase` unconditionally *because*\n // writing that file is the whole command; the merge flag was quietly overruling it.\n //\n // So ask the question the feature-add path asks \u2014 show the drift, confirm, then\n // fully overwrite \u2014 and drop the merge flag once a refresh is agreed, since a merge\n // cannot carry one out. The directory answer still governs a target that has files\n // but no config (a fresh clone, an empty git repo): app-config.json doesn't exist\n // there, so it is written either way and the user's other files stay untouched.\n //\n // Only reachable when a directory was resolved: without one the target is cwd, and\n // cwd having no config is what selected this branch in the first place.\n // A target belonging to a *different* app was already refused inside\n // `resolveBootstrapDirectory`, before the merge question. What can still be here is\n // this same app's project, which is a refresh.\n if (bootstrapDir) {\n const targetConfig = readProjectConfigAt(targetDir);\n if (targetConfig) {\n const refresh = await resolveBaseRefresh(targetConfig, ctx, jsonMode);\n // Always the interactive decline, never the `--json` one: this branch needs a\n // resolved directory, and `resolveBootstrapDirectory` returns none under\n // `--json` or off a TTY. So there is no machine-readable cancellation to emit.\n if (refresh.cancelled) return null;\n refreshBase = refresh.refreshBase;\n // Only drop the merge flag when a refresh is actually going ahead, because\n // dropping it is what allows app-config.json to be rewritten. With no drift\n // there is nothing to rewrite, and clearing it anyway would overwrite a file\n // that already matches the server \u2014 reintroducing a write this branch exists to\n // make deliberate.\n if (refresh.refreshBase) baseMergeOnly = false;\n }\n }\n\n return { targetDir, cdDir, refreshBase, baseMergeOnly };\n}\n\n/**\n * A UI app has no scaffoldable features, so the command is just a base-config refresh.\n */\nfunction finishUiAppScaffold(\n appId: string,\n ctx: AppContext,\n layout: ScaffoldLayout,\n jsonMode: boolean,\n): void {\n const { targetDir, cdDir, refreshBase, baseMergeOnly } = layout;\n const base = refreshBase ? runBaseScaffold(appId, ctx, targetDir, baseMergeOnly) : null;\n if (jsonMode) {\n jsonOutput({\n scaffolded: base?.written ?? 0,\n directory: targetDir,\n features: [],\n reason: messages.APP_SCAFFOLD_NO_FEATURES_FOR_UI_APP,\n });\n return;\n }\n if (base) {\n logSuccess(messages.APP_CREATE_BASE_SUCCESS(base.written, base.files.length));\n printFileTree(base.files.map((f) => f.name));\n }\n logInfo(messages.APP_SCAFFOLD_NO_FEATURES_FOR_UI_APP);\n // A bootstrap that made its own directory has to say which one, and for a UI\n // app upload \u2192 deploy is the whole of what comes next.\n if (cdDir) printBox(messages.APP_SCAFFOLD_NEXT_STEPS_TITLE, messages.APP_CREATE_UI_NEXT(cdDir));\n}\n\n/**\n * An interactive bootstrap: write and report the project, then hand off to the shared tail.\n */\nasync function finishInteractiveBootstrap(\n appId: string,\n ctx: AppContext,\n layout: ScaffoldLayout,\n jsonMode: boolean,\n overwrite: boolean,\n): Promise<void> {\n const { targetDir, cdDir, refreshBase, baseMergeOnly } = layout;\n // `refreshBase` is false in exactly one case here: the target directory already\n // held a config and it already matches the server, so there is nothing to\n // rewrite. Say so instead of writing \u2014 a blind overwrite would regenerate\n // README.md/CLAUDE.md/AGENTS.md over the user's edits to report that nothing\n // needed changing. Every other bootstrap still writes unconditionally, which is\n // the point of the mode.\n const bootstrapBase = refreshBase ? runBaseScaffold(appId, ctx, targetDir, baseMergeOnly) : null;\n if (bootstrapBase) reportBaseScaffoldSuccess(bootstrapBase);\n else logInfo(messages.APP_SCAFFOLD_BASE_IN_SYNC);\n // The rest of a bootstrap is the same tail `app create` runs once its app exists \u2014\n // offer the feature, resolve a conflict, write, report \u2014 so it runs the shared one\n // in `./finish-project` rather than a second copy of it here. `'ask'` because this\n // command is routinely pointed at a directory that already has files in it; create\n // passes the answer it already has.\n await finishProject({\n appId,\n ctx,\n targetDir,\n baseScopes: bootstrapBase?.scopes ?? [],\n cdDir,\n // A bootstrapped UI app is already handled by the caller, so this is always an OAuth app.\n isUiApp: false,\n offerFeature: true,\n onConflict: 'ask',\n jsonMode,\n overwriteFlag: overwrite,\n });\n}\n\n/**\n * The feature-add path, and any bootstrap that cannot prompt (`--json`, or off a TTY).\n */\nasync function finishFeatureScaffold(\n appId: string,\n ctx: AppContext,\n layout: ScaffoldLayout,\n jsonMode: boolean,\n overwrite: boolean,\n): Promise<void> {\n const { targetDir, cdDir, refreshBase, baseMergeOnly } = layout;\n const feature = await promptFeatureType(!jsonMode);\n\n // Decide how existing feature files are handled (overwrite/merge/cancel)\n // before writing anything.\n const conflict = await resolveFeatureConflict(feature, appId, ctx, targetDir, {\n jsonMode,\n overwrite,\n });\n if (conflict === 'cancel') {\n logInfo(messages.APP_SCAFFOLD_CANCELLED);\n return;\n }\n const featureMergeOnly = conflict === 'merge';\n\n // Refresh the base config/meta files (full overwrite) only when the local\n // config drifted from the server and the user consented.\n //\n // Only the feature-add path and non-interactive bootstraps reach here: an\n // interactive bootstrap wrote and reported its base already and then returned\n // through `finishProject`, so there is no \"already written\" case left to carry.\n const base = refreshBase ? runBaseScaffold(appId, ctx, targetDir, baseMergeOnly) : null;\n\n // Feature files merge by default (never clobber hand-edited code); the user\n // (or --overwrite) can opt into a full overwrite via resolveFeatureConflict.\n const feat = runFeatureScaffold(feature, appId, ctx, targetDir, featureMergeOnly);\n\n const written = (base?.written ?? 0) + feat.written;\n const files = [...(base?.files ?? []), ...feat.files];\n\n if (jsonMode) {\n jsonOutput({ scaffolded: written, directory: targetDir });\n return;\n }\n\n // `cdDir` is set only when a bootstrap made (or was pointed at) a directory\n // other than the one the command was typed in; the feature-add path always\n // writes into the project directory, so it stays undefined there.\n reportScaffoldSuccess({\n written,\n legacyAllSubstituted: base?.legacyAllSubstituted ?? false,\n scopes: base?.scopes ?? [],\n files,\n targetDir,\n cdDir,\n });\n}\n\nexport const scaffoldCommand = withCommandHandler(\n async (options: { json?: boolean; overwrite?: boolean; appId?: string }): Promise<void> => {\n const jsonMode = !!options.json;\n const overwrite = !!options.overwrite;\n const requestedAppId = options.appId?.trim() || undefined;\n\n // Scaffolding a feature only makes sense inside an already-created project \u2014\n // unless we are setting the directory up for an app that already exists, either\n // named by `--app-id` or chosen from the picker.\n const localConfig = readProjectConfig();\n\n const bootstrapAppId = await resolveBootstrapTarget(localConfig, requestedAppId, jsonMode);\n // Declined the offer: nothing to scaffold and nothing went wrong, so exit 0\n // with the remaining routes on screen rather than raising the no-config error\n // the user has just been shown a friendlier version of.\n if (bootstrapAppId === BOOTSTRAP_DECLINED) {\n logInfo(messages.APP_SCAFFOLD_BOOTSTRAP_DECLINED);\n return;\n }\n\n const plan = localConfig\n ? await resolveScaffoldPlan(localConfig, jsonMode)\n : await resolveBootstrapPlan(bootstrapAppId!, jsonMode);\n if (plan.cancelled) {\n reportPlanCancelled(plan, jsonMode);\n return;\n }\n\n const { appId, ctx } = plan;\n\n const layout = await resolveScaffoldLayout(localConfig, appId, ctx, plan.refreshBase, jsonMode);\n if (!layout) {\n logInfo(messages.APP_SCAFFOLD_CANCELLED);\n return;\n }\n\n // UI apps have no scaffoldable features \u2014 there is no local server to run for\n // an action link. `app scaffold` degrades to a base-config refresh so the\n // command still has a use inside a UI-app project, instead of offering an\n // OAuth test server the app can't use.\n //\n // Bootstrapping has no local config to classify, so it falls back to the block\n // `resolveBootstrapPlan` read off the server; the two agree on the linked path,\n // where `ctx.uiApp` is the local block by construction.\n const isUiApp = localConfig ? isUiAppConfig(localConfig) : Boolean(ctx.uiApp);\n if (isUiApp) {\n finishUiAppScaffold(appId, ctx, layout, jsonMode);\n return;\n }\n\n // A bootstrap writes and reports the project *before* asking about the feature:\n // the project is what the command was asked for, so it should exist whatever the\n // answer is, and the user can see what they got before deciding on the extra.\n // (The feature-add mode never asks \u2014 there, the feature *is* the request \u2014 and\n // `--json` never prompts, so both keep writing the base further down.)\n // Gated on a TTY, not just on `--json`, for the same reason the directory\n // question is: it is a new blocking prompt, and a piped run must keep finishing\n // on its own. Off a TTY the base is written further down and the feature always\n // follows, exactly as before.\n const bootstrapInteractive = !localConfig && !jsonMode && !!process.stdin.isTTY;\n if (bootstrapInteractive) {\n await finishInteractiveBootstrap(appId, ctx, layout, jsonMode, overwrite);\n return;\n }\n\n await finishFeatureScaffold(appId, ctx, layout, jsonMode, overwrite);\n },\n);\n", "/**\n * Removal of the keys a type declares as wire-only \u2014 the ones the server derives, defaults\n * or stamps, which therefore exist on its side of a comparison only.\n *\n * This module exists so the rule has ONE traversal. It previously lived in `app upload` as a\n * private helper, and before that as two private helpers, which is the whole reason it is\n * here: the upload diff and the write-back each had their own, and each had to be fixed\n * separately when `link_target` started arriving on the server's echo and again when\n * `extension_point_name` turned up one level down inside a `surface_point_list` entry. The\n * scaffold's pull path (`brevo app scaffold` with no local config) is the third consumer, and\n * writing a third traversal would have been the same bug a third time.\n *\n * A note on why the strip has to recurse rather than filter top-level keys:\n * `extension_point_name` is stamped INSIDE each entry, one level below the block, while\n * `link_target` and `version` sit at the top. One list, two depths.\n */\nimport { appTypeById } from './index';\nimport type { UiApp } from '../types';\n\n/** Strip `keys` from a value at every depth, returning a fresh structure. */\nfunction stripKeysDeep(value: unknown, keys: readonly string[]): unknown {\n if (Array.isArray(value)) return value.map((entry) => stripKeysDeep(entry, keys));\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .filter(([key]) => !keys.includes(key))\n .map(([k, v]) => [k, stripKeysDeep(v, keys)]),\n );\n }\n return value;\n}\n\n/**\n * Drop every key the UI app type owns server-side from a `ui_app` block.\n *\n * The ONLY reader of `uiAppType.wireOnlyKeys`. All three consumers \u2014 the upload diff's\n * equality check, the upload write-back, and the scaffold pull's sanitize-before-write \u2014 go\n * through here, so a key added to that list cannot be honoured by one and forgotten by\n * another.\n *\n * Does not mutate its input: callers hold onto the server's raw record for other fields.\n */\nexport function stripUiAppWireOnlyKeys(uiApp: UiApp): UiApp {\n return stripKeysDeep(uiApp, appTypeById('ui').wireOnlyKeys) as UiApp;\n}\n\n/**\n * The generic form, for a value that is not a whole block \u2014 the upload diff canonicalizes\n * a possibly-undefined block and sorts it, so it needs the traversal without the `UiApp`\n * cast.\n */\nexport function stripUiAppWireOnlyKeysFrom(value: unknown): unknown {\n return stripKeysDeep(value, appTypeById('ui').wireOnlyKeys);\n}\n", "import inquirer from 'inquirer';\nimport {\n isAuthenticated,\n hasAppCredentials,\n deleteCredentialsFile,\n countAppCredentials,\n} from '../lib/config';\nimport { logSuccess, logInfo, logWarn } from '../lib/logger';\nimport { CliError } from '../lib/errors';\nimport { messages } from '../lang/en';\nimport { withCommandHandler } from '../lib/command-handler';\nimport { jsonOutput } from '../lib/json-output';\n\nexport const logoutCommand = withCommandHandler(\n async (options: { force?: boolean; json?: boolean }): Promise<void> => {\n if (!isAuthenticated()) {\n if (options.json) {\n jsonOutput({ loggedOut: false, reason: 'not_authenticated' });\n } else {\n logInfo(`\\n ${messages.AUTH_NOT_LOGGED_IN}\\n`);\n }\n return;\n }\n\n if (!options.force && !options.json && hasAppCredentials()) {\n if (!process.stdin.isTTY) {\n throw new CliError(messages.AUTH_LOGOUT_NON_INTERACTIVE);\n }\n logWarn(messages.AUTH_LOGOUT_APP_WARNING);\n const { confirmed } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'confirmed',\n message: messages.AUTH_LOGOUT_CONFIRM,\n default: false,\n },\n ]);\n\n if (!confirmed) {\n logInfo(`\\n ${messages.ABORTED}\\n`);\n return;\n }\n }\n\n const appCount = countAppCredentials();\n deleteCredentialsFile();\n\n if (options.json) {\n jsonOutput({ loggedOut: true, appsCleared: appCount });\n } else if (appCount > 0) {\n logSuccess(messages.AUTH_LOGGED_OUT_WITH_APPS(appCount));\n } else {\n logSuccess(messages.AUTH_LOGGED_OUT);\n }\n },\n);\n", "import { logInfo } from '../lib/logger';\nimport { messages } from '../lang/en';\nimport { CliError } from '../lib/errors';\nimport {\n isAuthenticated,\n getEmail,\n getOrganizationId,\n getUserId,\n getAuthCred,\n} from '../lib/config';\nimport { accountService } from '../container';\nimport { AccountResponse } from '../types';\nimport { withCommandHandler } from '../lib/command-handler';\nimport { jsonOutput } from '../lib/json-output';\nimport { createSpinner } from '../lib/ui';\n\n// Check for mismatches between stored credentials and API response\nfunction findCredentialMismatches(account: AccountResponse): string[] {\n const mismatched: string[] = [];\n const storedEmail = getEmail();\n const storedOrgId = getOrganizationId();\n const storedUserId = getUserId();\n\n if (storedEmail && storedEmail !== account.email) mismatched.push('email');\n if (storedOrgId && storedOrgId !== account.organization_id) mismatched.push('organization');\n if (storedUserId != null && storedUserId !== account.user_id) mismatched.push('user id');\n\n return mismatched;\n}\n\nfunction renderAccount(account: AccountResponse): void {\n const authKind = getAuthCred()?.kind;\n let kindLabel = '';\n if (authKind === 'oauth') kindLabel = ' (browser login)';\n else if (authKind === 'api-key') kindLabel = ' (API key)';\n logInfo(\n `\\n ${messages.WHOAMI_AUTHENTICATED(account.email, account.companyName || 'N/A')}${kindLabel}\\n` +\n ` Organization: ${account.organization_id}\\n` +\n ` User ID: ${account.user_id}\\n`,\n );\n}\n\nexport const whoamiCommand = withCommandHandler(\n async (options: { json?: boolean }): Promise<void> => {\n // No stored key at all\n if (!isAuthenticated()) {\n if (options.json) {\n jsonOutput({ authenticated: false, reason: 'no_key' });\n }\n throw new CliError(messages.WHOAMI_NOT_AUTHENTICATED);\n }\n\n // Key exists \u2014 validate it against the API\n const spinner = createSpinner('Checking credentials...', { silent: options.json });\n try {\n const account = await accountService.getAccount();\n spinner.stop();\n\n const mismatched = findCredentialMismatches(account);\n if (mismatched.length > 0) {\n if (options.json) {\n jsonOutput({\n authenticated: false,\n reason: 'credential_mismatch',\n mismatchedFields: mismatched,\n });\n }\n throw new CliError(messages.WHOAMI_CREDENTIAL_MISMATCH(mismatched));\n }\n\n if (options.json) {\n jsonOutput({\n authenticated: true,\n authKind: getAuthCred()?.kind,\n email: account.email,\n company: account.companyName || '',\n organizationId: account.organization_id,\n userId: account.user_id,\n });\n } else {\n renderAccount(account);\n }\n } catch (err) {\n spinner.stop();\n if (err instanceof CliError) throw err;\n if (options.json) {\n jsonOutput({ authenticated: false, reason: 'expired' });\n }\n throw new CliError(messages.AUTH_EXPIRED);\n }\n },\n);\n", "import { logInfo } from '../../lib/logger';\nimport { messages } from '../../lang/en';\nimport { OAuthApp, UiApp } from '../../types';\nimport { appService } from '../../container';\nimport { withCommandHandler } from '../../lib/command-handler';\nimport { jsonOutput } from '../../lib/json-output';\nimport { createSpinner } from '../../lib/ui';\nimport { getAppNames, deleteAppName } from '../../lib/config';\nimport { containsLegacyAllScope } from '../../lib/validators';\nimport { resolveFromRecord } from '../../app-types';\nimport { formatPlacementLines } from '../../app-types/ui/fields';\n\nexport const listCommand = withCommandHandler(\n async (options: { json?: boolean }): Promise<void> => {\n const spinner = createSpinner('Fetching apps...', { silent: options.json });\n let apps: OAuthApp[] | undefined;\n try {\n apps = await appService.fetchAppsList();\n } finally {\n spinner.stop();\n }\n\n const merged = mergeCachedNames(apps || []);\n\n if (options.json) {\n jsonOutput(merged.map(toJsonApp));\n return;\n }\n\n if (merged.length === 0) {\n logInfo(`\\n ${messages.APP_LIST_EMPTY}\\n`);\n return;\n }\n\n logInfo(`\\n ${messages.APP_LIST_HEADER}\\n`);\n\n for (const app of merged) {\n printApp(app);\n }\n },\n);\n\n/**\n * The app-store list endpoint can lag behind writes (eventual consistency), so a name set\n * via `brevo app upload` may not appear here for a while. Merge locally cached names to\n * mask the propagation delay. Once the server catches up (cache equals server), drop the\n * entry so any subsequent out-of-band rename (e.g. dashboard) is visible on the next list.\n */\nfunction mergeCachedNames(apps: OAuthApp[]): OAuthApp[] {\n const cachedNames = getAppNames();\n return apps.map((app) => {\n const cached = cachedNames[app.app_id];\n if (!cached) return app;\n if (cached === app.name) {\n deleteAppName(app.app_id);\n return app;\n }\n return { ...app, name: cached };\n });\n}\n\n/**\n * Strip the secret and add the machine-readable deprecation signal.\n *\n * `legacy_all_scope: true` is for scripts/agents \u2014 only present on affected apps (BEX-214).\n */\nfunction toJsonApp({ client_secret: _secret, ...rest }: OAuthApp): Record<string, unknown> {\n return {\n ...rest,\n ...(containsLegacyAllScope(rest.scopes) ? { legacy_all_scope: true } : {}),\n };\n}\n\nfunction printApp(app: OAuthApp): void {\n const name = app.name || '\u2014';\n // The type resolves itself and carries its own label, so a third app type shows up\n // here correctly without this function learning about it.\n const appType = resolveFromRecord(app);\n const isUiApp = appType.id === 'ui';\n process.stdout.write(` ${name} (App ID: ${app.app_id})\\n`);\n process.stdout.write(` Type: ${appType.label}\\n`);\n // A UI app has no OAuth material at all \u2014 no client_id, no callbacks, no\n // scopes. Printing those rows empty would read as a broken OAuth app\n // rather than a UI app, so they are skipped entirely (same reasoning as\n // the upload summary's UI-app branch).\n if (!isUiApp) printOAuthIdentity(app);\n process.stdout.write(` Logo URL: ${app.logo_uri || '(none)'}\\n`);\n process.stdout.write(` Version: ${app.version || '(none)'}\\n`);\n if (!isUiApp) printScopes(app);\n // Only when the server echoes the block. The list endpoint does not today,\n // so a UI app usually stops at the Type row \u2014 which is still the truth of\n // what the response carried, and better than inventing empty rows.\n if (app.ui_app) printUiApp(app.ui_app);\n process.stdout.write('\\n');\n}\n\nfunction printOAuthIdentity(app: OAuthApp): void {\n process.stdout.write(` Client ID: ${app.client_id}\\n`);\n const redirectUris = app.redirect_uris ?? [];\n if (redirectUris.length === 0) {\n process.stdout.write(` Redirect URLs: (none)\\n`);\n return;\n }\n redirectUris.forEach((uri, i) => {\n process.stdout.write(` Redirect URL ${i + 1}: ${uri}\\n`);\n });\n}\n\nfunction printScopes(app: OAuthApp): void {\n const scopes = app.scopes ?? [];\n const legacyTag = containsLegacyAllScope(scopes) ? messages.LEGACY_ALL_SCOPE_LIST_TAG : '';\n process.stdout.write(\n ` Scopes: ${scopes.length > 0 ? scopes.join(', ') : '(none)'}${legacyTag}\\n`,\n );\n}\n\n/**\n * The stored `ui_app` block, field for field \u2014 this is what actually renders\n * inside Brevo, so the rows mirror the upload summary's rather than collapsing\n * to a one-line \"UI app\".\n */\nfunction printUiApp(uiApp: UiApp): void {\n process.stdout.write(` Extension: ${uiApp.extension_type}\\n`);\n // One row per placement, each with its own context: the two are per-entry, so\n // a shared row would hide that two record pages can forward different fields.\n formatPlacementLines(uiApp).forEach((line, i) => {\n process.stdout.write(` ${i === 0 ? 'Placement: ' : ' '}${line}\\n`);\n });\n if (uiApp.label) process.stdout.write(` Label: ${uiApp.label}\\n`);\n if (uiApp.more_info) process.stdout.write(` More info: ${uiApp.more_info}\\n`);\n if (uiApp.redirect_link) process.stdout.write(` Link: ${uiApp.redirect_link}\\n`);\n // An iframeExtension's modal URL is the destination, so it earns a row too.\n if (uiApp.modal_iframe_url) {\n process.stdout.write(` Modal URL: ${uiApp.modal_iframe_url}\\n`);\n }\n // No link_target row: app-config.json does not carry the field (upload injects\n // `_blank`), so surfacing it only sends a partner looking for one to edit.\n}\n", "import inquirer from 'inquirer';\nimport { logInfo, logWarn } from '../../lib/logger';\nimport { messages } from '../../lang/en';\nimport { CliError } from '../../lib/errors';\nimport { withCommandHandler } from '../../lib/command-handler';\nimport { jsonOutput } from '../../lib/json-output';\nimport { appService } from '../../container';\nimport { createSpinner } from '../../lib/ui';\nimport { saveAppName, backfillProjectConfigFromServer } from '../../lib/config';\nimport { assertAppSelectionAllowed } from './select-app';\nimport { CLI } from '../../lib/constants';\n\ntype AppDetails = Awaited<ReturnType<typeof appService.resolveAppCredentials>>;\n\nasync function resolveSecretReveal(\n revealRequested: boolean | undefined,\n app: NonNullable<AppDetails>['app'],\n): Promise<{ display: string; revealed: boolean }> {\n if (!revealRequested) {\n return { display: messages.CLIENT_SECRET_HIDDEN_HUMAN, revealed: false };\n }\n if (!process.stdin.isTTY) {\n logInfo(' Secret not revealed in non-interactive mode. Use --reveal-secret in a terminal.');\n return { display: messages.CLIENT_SECRET_HIDDEN_HUMAN, revealed: false };\n }\n const { confirmed } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'confirmed',\n message: messages.APP_CREDENTIALS_REVEAL_CONFIRM,\n default: false,\n },\n ]);\n if (confirmed) {\n return {\n display: app.client_secret || messages.CLIENT_SECRET_NOT_AVAILABLE,\n revealed: true,\n };\n }\n return { display: messages.CLIENT_SECRET_HIDDEN_HUMAN, revealed: false };\n}\n\nfunction printCredentialsHuman(\n app: NonNullable<AppDetails>['app'],\n appId: string,\n secretDisplay: string,\n): void {\n logInfo(`\\n App name: ${app.name || '\u2014'}`);\n logInfo(` App ID: ${appId}`);\n logInfo(` Client ID: ${app.client_id}`);\n logInfo(` Client secret: ${secretDisplay}`);\n if (app.scopes && app.scopes.length > 0) {\n logInfo(` Scopes: ${app.scopes.join(', ')}`);\n } else {\n logInfo(` Scopes: (none)`);\n }\n // Null, not [], on any app with no OAuth block \u2014 never iterate it directly.\n const redirectUris = app.redirect_uris ?? [];\n if (redirectUris.length > 0) {\n redirectUris.forEach((uri, i) => {\n logInfo(` Redirect URL ${i + 1}: ${uri}`);\n });\n } else {\n logInfo(` Redirect URLs: (none)`);\n }\n process.stdout.write('\\n');\n}\n\nasync function reconcileLocalCache(\n appId: string,\n app: NonNullable<AppDetails>['app'],\n diffs: string[],\n jsonMode: boolean | undefined,\n): Promise<void> {\n if (diffs.length === 0) {\n // First-time save (no local cache yet) is silent\n appService.syncAppCredentials(appId, app);\n return;\n }\n\n logWarn(`Local credentials for app ${appId} differ from server (${diffs.join(', ')}).`);\n\n if (!process.stdin.isTTY || jsonMode) {\n appService.syncAppCredentials(appId, app);\n return;\n }\n\n const { shouldUpdate } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'shouldUpdate',\n message: 'Update local credentials to match the server?',\n default: true,\n },\n ]);\n if (shouldUpdate) {\n appService.syncAppCredentials(appId, app);\n logInfo(' Local credentials updated.\\n');\n }\n}\n\nexport const credentialsCommand = withCommandHandler(\n async (options: { appId?: string; revealSecret?: boolean; json?: boolean }): Promise<void> => {\n if (!options.appId) {\n assertAppSelectionAllowed(CLI.APP_CREDENTIALS(), options.json);\n }\n const appId = options.appId ?? (await appService.pickApp(messages.APP_CREDENTIALS_SELECT));\n\n const spinner = createSpinner('Fetching credentials...', { silent: options.json });\n const result = await appService.resolveAppCredentials(appId);\n spinner.stop();\n if (!result) {\n throw new CliError(`App ${appId} not found.`);\n }\n const { app, diffs } = result;\n\n if (app.name) saveAppName(appId, app.name);\n\n const { display: secretDisplay, revealed: revealConfirmed } = await resolveSecretReveal(\n options.revealSecret,\n app,\n );\n\n if (options.json) {\n jsonOutput({\n appName: app.name || null,\n appId,\n clientId: app.client_id,\n clientSecret: revealConfirmed\n ? (app.client_secret ?? messages.CLIENT_SECRET_NOT_AVAILABLE)\n : messages.CLIENT_SECRET_HIDDEN_JSON,\n scopes: app.scopes || [],\n redirectUris: app.redirect_uris ?? [],\n });\n } else {\n printCredentialsHuman(app, appId, secretDisplay);\n }\n\n await reconcileLocalCache(appId, app, diffs, options.json);\n\n // Converge a legacy app-config.json toward the current shape: backfill any\n // version/distribution_type it was scaffolded without. Only touches the\n // file when it exists in cwd and its appId matches. Silent in every mode;\n // a one-line note is printed in human mode when something was written.\n const backfilled = backfillProjectConfigFromServer(appId, {\n version: app.version,\n distribution_type: app.distribution_type,\n });\n if (backfilled.length > 0 && !options.json) {\n logInfo(` ${messages.APP_CREDENTIALS_CONFIG_BACKFILLED(backfilled)}\\n`);\n }\n },\n);\n", "import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport inquirer from 'inquirer';\nimport { logSuccess, logInfo } from '../../lib/logger';\nimport { messages } from '../../lang/en';\nimport { withCommandHandler } from '../../lib/command-handler';\nimport { jsonOutput } from '../../lib/json-output';\nimport { CliError } from '../../lib/errors';\nimport { appService } from '../../container';\nimport { createSpinner } from '../../lib/ui';\nimport {\n readProjectConfig,\n writeProjectConfig,\n saveAppName,\n isUiAppConfig,\n ProjectConfig,\n} from '../../lib/config';\nimport { validateScopes, containsLegacyAllScope } from '../../lib/validators';\nimport { DEFAULT_LINK_TARGET, EXTENSION_TYPE_ACTION_LINK } from '../../lib/constants';\nimport { OAuthApp, UiApp, UploadAppResponse } from '../../types';\nimport { resolveFromConfig } from '../../app-types';\nimport { stripUiAppWireOnlyKeysFrom } from '../../app-types/wire';\nimport { formatPlacementLines } from '../../app-types/ui/fields';\n\ninterface UploadOptions {\n yes?: boolean;\n json?: boolean;\n}\n\nconst NON_INTERACTIVE_CONFIRM_ERROR =\n 'Cannot prompt for confirmation in non-interactive mode. Use --yes or --json to skip.';\n\n// Reads + validates app-config.json from cwd. Always hard-errors on any\n// problem \u2014 upload has no --app-id flag to fall back to, so an unusable\n// config is fatal, not a \"skip this part\" condition like in the old update.ts.\nfunction loadUsableConfig(): NonNullable<ReturnType<typeof readProjectConfig>> {\n const configPath = path.resolve(process.cwd(), 'app-config.json');\n if (!fs.existsSync(configPath)) {\n throw new CliError(messages.APP_UPLOAD_NO_CONFIG);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));\n } catch {\n throw new CliError(messages.APP_UPLOAD_INVALID_JSON);\n }\n if (\n !raw ||\n typeof raw !== 'object' ||\n !('appId' in raw) ||\n !(raw as Record<string, unknown>).appId\n ) {\n throw new CliError(messages.APP_UPLOAD_MISSING_APP_ID);\n }\n const config = readProjectConfig();\n if (!config) {\n throw new CliError(messages.APP_UPLOAD_MISSING_APP_ID);\n }\n return config;\n}\n\nfunction validateRedirectUrls(urls: string[]): void {\n for (const url of urls) {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new CliError(messages.APP_UPLOAD_INVALID_REDIRECT_PROTOCOL(url));\n }\n } catch (err) {\n if (err instanceof CliError) throw err;\n throw new CliError(messages.APP_UPLOAD_INVALID_REDIRECT_URL(url));\n }\n }\n}\n\nasync function fetchExistingApp(appId: string, silent: boolean | undefined): Promise<OAuthApp> {\n const spinner = createSpinner('Fetching app...', { silent });\n let app: OAuthApp | null;\n try {\n app = await appService.fetchApp(appId);\n } finally {\n spinner.stop();\n }\n if (!app) {\n throw new CliError(`App ${appId} not found.`);\n }\n return app;\n}\n\n// Diff `current` vs `next`: next values keep their order (tagged `(new)` when\n// absent from current), values dropped from current trail with `(removed)`.\nfunction diffLines(current: string[], next: string[]): string[] {\n const currentSet = new Set(current);\n const nextSet = new Set(next);\n return [\n ...next.map((v) => (currentSet.has(v) ? v : `${v} (new)`)),\n ...current.filter((v) => !nextSet.has(v)).map((v) => `${v} (removed)`),\n ];\n}\n\nfunction logAligned(label: string, lines: string[]): void {\n lines.forEach((line, i) => {\n logInfo(`${i === 0 ? label : ' '}${line}`);\n });\n}\n\ninterface UploadDiff {\n appId: string;\n currentName?: string;\n nextName: string;\n currentUrls: string[];\n nextUrls: string[];\n currentLogoUri?: string;\n nextLogoUri: string;\n currentScopes: string[];\n nextScopes: string[];\n currentDistribution?: 'public' | 'private';\n nextDistribution: 'public' | 'private';\n currentVersion?: string;\n nextVersion: string;\n migratingLegacyScopes: boolean;\n // UI apps only (BEX-290). `currentUiApp` stays undefined on server builds that\n // accept the ui_app block on write but don't echo it back on reads \u2014 in that case\n // the block always reads as new, which is safe: re-sending an identical block is\n // idempotent, whereas skipping it could strand a local edit.\n currentUiApp?: UiApp;\n nextUiApp?: UiApp;\n}\n\nfunction buildDiff(config: NonNullable<ProjectConfig>, remote: OAuthApp): UploadDiff {\n const nextScopes = config.auth?.scopes ?? [];\n return {\n appId: config.appId,\n currentName: remote.name,\n nextName: config.appName,\n currentUrls: remote.redirect_uris ?? [],\n nextUrls: config.auth?.redirectUris ?? [],\n currentLogoUri: remote.logo_uri,\n nextLogoUri: config.logoUri ?? '',\n currentScopes: remote.scopes ?? [],\n nextScopes,\n currentDistribution: remote.distribution_type,\n nextDistribution: config.distribution_type,\n currentVersion: remote.version,\n nextVersion: config.version || remote.version || '',\n migratingLegacyScopes: containsLegacyAllScope(remote.scopes ?? []),\n currentUiApp: remote.ui_app,\n nextUiApp: config.ui_app,\n };\n}\n\n// The wire-only keys \u2014 the ones that exist on the server's side of the comparison only, and\n// so can never signal a real local edit (BEX-290) \u2014 are declared by the app type\n// (`src/app-types/ui/index.ts` \u2192 `wireOnlyKeys`) and stripped by the shared traversal in\n// `src/app-types/wire.ts`. See that module for why the rule lives in one place: this command's\n// diff and write-back each used to carry their own copy of it, and each had to be fixed\n// separately when `link_target` started arriving on the echo and again when\n// `extension_point_name` turned up one level down inside an entry. The scaffold's pull path is\n// now a third consumer, which is what moved it out of here.\nconst stripInjectedKeys = stripUiAppWireOnlyKeysFrom;\n\n/** Recursively sort object keys, so a serialized comparison is key-order-independent. */\nfunction sortKeysDeep(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortKeysDeep);\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => [k, sortKeysDeep(v)]),\n );\n }\n return value;\n}\n\n/**\n * Drop the wire-only keys above from a block, before it is written back to\n * app-config.json. They are stripped for the same reason and by the same list the diff\n * normalizes with: none is authored, they come back on (or are added around) the server's\n * echo, and writing one into the file would put a key the partner cannot usefully edit into\n * the file this command just decided to keep it out of.\n */\nfunction withoutInjectedKeys(uiApp: UiApp): UiApp {\n return stripInjectedKeys(uiApp) as UiApp;\n}\n\n// Stable serialization for equality checks. Three things vary without the block having\n// changed, and all three are normalized away here:\n//\n// 1. Key order in app-config.json depends on how the file was edited.\n// 2. `surface_point_list` ORDER is not meaningful \u2014 the server returns registry order,\n// which need not match the order the partner picked their pages in. Without sorting,\n// an authored [deal, contact] against an echoed [contact, deal] is phantom drift.\n// 3. The injected/server-managed keys above exist on one side only \u2014 stripped by\n// `stripInjectedKeys`, the single owner of that list, rather than by a filter\n// duplicated here.\nfunction canonicalizeUiApp(uiApp: UiApp | undefined): string {\n if (!uiApp) return '';\n const normalized = sortKeysDeep(stripInjectedKeys(uiApp)) as Record<string, unknown>;\n const entries = normalized.surface_point_list;\n if (Array.isArray(entries)) {\n normalized.surface_point_list = [...entries].sort((a, b) =>\n JSON.stringify(a).localeCompare(JSON.stringify(b)),\n );\n }\n return JSON.stringify(normalized);\n}\n\nfunction renderUploadDiff(diff: UploadDiff): void {\n logInfo('');\n logInfo(` ${messages.APP_UPLOAD_SUMMARY}`);\n logInfo(` App ID: ${diff.appId}`);\n const renamePrefix =\n diff.currentName && diff.currentName !== diff.nextName ? `${diff.currentName} \u2192 ` : '';\n logInfo(` Name: ${renamePrefix}${diff.nextName}`);\n if (diff.currentDistribution && diff.currentDistribution !== diff.nextDistribution) {\n logInfo(` Distribution: ${diff.currentDistribution} \u2192 ${diff.nextDistribution}`);\n } else {\n logInfo(` Distribution: ${diff.nextDistribution}`);\n }\n // Only OAuth apps have callbacks \u2014 printing an empty \"Redirect URLs:\" row for a\n // UI app would imply something is missing.\n if (!diff.nextUiApp) {\n logAligned(' Redirect URLs: ', diffLines(diff.currentUrls, diff.nextUrls));\n }\n if (diff.migratingLegacyScopes) {\n logInfo(` ${messages.LEGACY_ALL_SCOPE_UPDATE_MIGRATING}`);\n }\n // Scopes are OAuth-only as well \u2014 a UI app's auth is empty (`{}`),\n // so a scopes row would only ever render as noise.\n if (!diff.nextUiApp) {\n logAligned(' Scopes: ', diffLines(diff.currentScopes, diff.nextScopes));\n }\n if (diff.currentLogoUri && diff.currentLogoUri !== diff.nextLogoUri) {\n logInfo(` Logo URL: ${diff.currentLogoUri} \u2192 ${diff.nextLogoUri || '(none)'}`);\n } else if (diff.nextLogoUri) {\n logInfo(` Logo URL: ${diff.nextLogoUri}`);\n }\n if (diff.currentVersion && diff.currentVersion !== diff.nextVersion) {\n logInfo(` Version: ${diff.currentVersion} \u2192 ${diff.nextVersion || '(unknown)'}`);\n } else if (diff.nextVersion) {\n logInfo(` Version: ${diff.nextVersion}`);\n }\n if (diff.nextUiApp) {\n renderUiAppDiff(diff.nextUiApp, diff.currentUiApp);\n }\n logInfo('');\n}\n\n// Field-by-field so the partner can see exactly what the platform will store \u2014\n// this block drives what renders inside Brevo, so a bare \"changed\" would be\n// useless. Values that differ from the server are tagged.\nfunction renderUiAppDiff(next: UiApp, current: UiApp | undefined): void {\n const changed = canonicalizeUiApp(next) !== canonicalizeUiApp(current);\n logInfo(` ${messages.APP_UPLOAD_UI_APP_SUMMARY}${changed ? ' (changed)' : ''}`);\n logInfo(` Extension type: ${next.extension_type}`);\n // Each placement prints its own context, because the two are per-entry now: a\n // partner targeting a contact page and a deal page can be forwarded different\n // fields on each, and one shared row would hide that.\n formatPlacementLines(next).forEach((line, i) => {\n logInfo(` ${i === 0 ? 'Placement: ' : ' '}${line}`);\n });\n logInfo(` Label: ${next.label ?? ''}`);\n if (next.more_info) logInfo(` More info: ${next.more_info}`);\n logInfo(` Redirect link: ${next.redirect_link ?? ''}`);\n // No link_target row: app-config.json does not carry the field, so printing a value\n // for it only sends a partner looking for one to edit. `_blank` is still injected\n // into an actionLink's payload \u2014 see the payload build.\n}\n\nfunction diffToJson(diff: UploadDiff) {\n return {\n current: {\n name: diff.currentName,\n redirect_uris: diff.currentUrls,\n scopes: diff.currentScopes,\n logo_uri: diff.currentLogoUri,\n distribution_type: diff.currentDistribution,\n version: diff.currentVersion,\n ...(diff.currentUiApp ? { ui_app: diff.currentUiApp } : {}),\n },\n next: {\n name: diff.nextName,\n // OAuth-only keys are omitted for UI apps \u2014 their desired state has no\n // OAuth block (auth is `{}`), and emitting empty arrays\n // would misread as \"clearing the values\".\n ...(diff.nextUiApp ? {} : { redirect_uris: diff.nextUrls, scopes: diff.nextScopes }),\n logo_uri: diff.nextLogoUri,\n distribution_type: diff.nextDistribution,\n version: diff.nextVersion,\n ...(diff.nextUiApp ? { ui_app: diff.nextUiApp } : {}),\n },\n };\n}\n\nfunction hasNoChanges(diff: UploadDiff): boolean {\n // Scopes and redirect URLs are OAuth-only: a UI app's config carries neither\n // (auth is `{}`), so comparing them against whatever the\n // server still reports would flag a phantom change on every upload.\n const isUiApp = !!diff.nextUiApp;\n const oauthUnchanged =\n isUiApp ||\n (JSON.stringify([...diff.currentUrls].sort()) === JSON.stringify([...diff.nextUrls].sort()) &&\n JSON.stringify([...diff.currentScopes].sort()) ===\n JSON.stringify([...diff.nextScopes].sort()));\n return (\n diff.currentName === diff.nextName &&\n diff.currentDistribution === diff.nextDistribution &&\n oauthUnchanged &&\n (diff.currentLogoUri || '') === (diff.nextLogoUri || '') &&\n (diff.currentVersion || '') === (diff.nextVersion || '') &&\n // Without this, editing only the `ui_app` block reports \"Already up to date\"\n // and the change is never pushed.\n canonicalizeUiApp(diff.currentUiApp) === canonicalizeUiApp(diff.nextUiApp)\n );\n}\n\nexport interface ConfigUploadOutcome {\n confirmedVersion: string;\n finalName: string;\n}\n\n/**\n * Push the config's full desired state to the server \u2014 the single write path\n * for app state (BEX-366) \u2014 and persist the server's echo back into\n * app-config.json so the local copy always tracks the confirmed version.\n *\n * The upload contract needs the server's latest version as a staleness token;\n * when none is known (neither passed in nor stored in the config), the remote\n * app is fetched to obtain one.\n */\nexport async function uploadProjectConfig(\n config: NonNullable<ProjectConfig>,\n opts: { silent?: boolean; appVersion?: string } = {},\n): Promise<ConfigUploadOutcome> {\n const isUiApp = isUiAppConfig(config);\n const redirectUris = config.auth?.redirectUris ?? [];\n const scopes = config.auth?.scopes ?? [];\n\n let appVersion = opts.appVersion ?? config.version ?? '';\n if (!appVersion) {\n appVersion = (await fetchExistingApp(config.appId, opts.silent)).version ?? '';\n }\n\n const spinner = createSpinner('Uploading app...', { silent: opts.silent });\n let response: UploadAppResponse;\n try {\n response = await appService.uploadApp(config.appId, {\n app_id: config.appId,\n name: config.appName,\n logo_uri: config.logoUri ?? '',\n version: appVersion,\n distribution_type: config.distribution_type,\n // UI apps have no OAuth block \u2014 the whole `auth` key is omitted, not sent\n // with empty arrays, mirroring `auth: {}` in the config.\n // ASSUMED wire contract (server side not built yet, see\n // RELEASE-CHECKLIST.md \u2192 Before UI-apps GA): the upload endpoint must\n // tolerate an absent auth key for UI apps.\n ...(isUiApp\n ? {}\n : {\n auth: {\n scopes,\n redirect_uris: redirectUris,\n },\n }),\n // Spread rather than a fixed key so OAuth uploads keep their exact\n // historical payload shape \u2014 `ui_app` is absent, not `undefined`.\n //\n // `link_target` is injected here rather than authored into app-config.json\n // (BEX-290): there was never a choice to make, since the server refuses\n // `_self`, so a field in the file only invited a partner to edit it into a\n // value that 400s. It is still sent explicitly rather than left to the\n // server's own default, which is gated on the pre-BEX-350 spelling of\n // extension_type and therefore no longer fires for CLI-authored apps.\n //\n // Only for an `actionLink`. An `iframeExtension` embeds its URL in a modal\n // instead of navigating, has no link target to set, and `validateUiApp`\n // refuses the field in the authored file \u2014 so injecting it would send the\n // one field the CLI just told the partner not to write.\n ...(isUiApp && config.ui_app\n ? {\n ui_app: {\n ...config.ui_app,\n ...(config.ui_app.extension_type === EXTENSION_TYPE_ACTION_LINK\n ? { link_target: DEFAULT_LINK_TARGET as UiApp['link_target'] }\n : {}),\n },\n }\n : {}),\n });\n } finally {\n spinner.stop();\n }\n\n const finalName = response.name ?? config.appName;\n if (finalName) saveAppName(config.appId, finalName);\n\n // Single source of truth for the version we persist AND print, so the two can\n // never diverge. The server returns the bumped value under `version` (see\n // UploadAppResponse); fall back to `app_version` for tolerance, and only then\n // to the version we sent \u2014 so a server-confirmed bump always wins.\n const confirmedVersion = response.version ?? response.app_version ?? appVersion;\n\n writeProjectConfig({\n ...config,\n appName: finalName,\n logoUri: response.logo_uri ?? config.logoUri,\n distribution_type: response.distribution_type ?? config.distribution_type,\n version: confirmedVersion,\n // A UI app's auth block is always written back as the canonical empty\n // `{}` \u2014 never reconciled from the server's echo, which reports null\n // scopes/redirect_uris for UI-only apps anyway.\n auth: isUiApp\n ? {}\n : {\n scopes: response.auth.scopes ?? scopes,\n redirectUris: response.auth.redirect_uris ?? redirectUris,\n },\n // Prefer the server's normalized block when it echoes one back, otherwise\n // keep what we just sent \u2014 with the wire-only keys stripped either way. The\n // server defaults `link_target` and manages the block's `version`, and\n // echoes both, so passing the echo through verbatim would write back into\n // app-config.json fields the partner never authored \u2014 undoing, on the very\n // first successful upload, the decision to keep them out of the file.\n ...(isUiApp && (response.ui_app ?? config.ui_app)\n ? { ui_app: withoutInjectedKeys((response.ui_app ?? config.ui_app)!) }\n : {}),\n });\n\n return { confirmedVersion, finalName };\n}\n\n// The auth block's shape follows the app type, and a mismatch is a hard error\n// rather than a silent ignore. This one stays local because it is a statement about\n// the local file: `auth` and `ui_app` are how `app-config.json` says which app type\n// it describes, and the server never sees the contradiction \u2014 a UI app must carry\n// exactly `auth: {}` (no scopes, no redirect URIs \u2014 nothing OAuth is issued for it).\nfunction validateAuthShape(config: NonNullable<ProjectConfig>): void {\n if (!isUiAppConfig(config)) return;\n if (!config.auth) {\n throw new CliError(messages.APP_UPLOAD_UI_APP_AUTH_EMPTY_REQUIRED);\n }\n if (config.auth.scopes !== undefined || config.auth.redirectUris !== undefined) {\n throw new CliError(messages.APP_UPLOAD_UI_APP_AUTH_HAS_OAUTH_FIELDS);\n }\n}\n\n/**\n * Everything that can be judged from `app-config.json` alone, before any round trip.\n */\nfunction runLocalPreflight(config: ProjectConfig): void {\n validateAuthShape(config);\n\n // A UI app has no OAuth callback (enforced above), so the redirect\n // requirement \u2014 and validation \u2014 is OAuth-only.\n const redirectUris = config.auth?.redirectUris ?? [];\n if (!isUiAppConfig(config) && redirectUris.length === 0) {\n throw new CliError(messages.APP_UPLOAD_NO_REDIRECT_URLS_OAUTH);\n }\n validateRedirectUrls(redirectUris);\n\n // Local pre-flight on the block's SHAPE only \u2014 a missing label, a bare-string\n // placement, a pre-BEX-290 field name \u2014 so an obviously malformed file fails with a\n // precise message before a round trip. Anything that needs the extension-point\n // registry to answer (is this slot name registered, is this context field allowed on\n // it) is left to the upload endpoint, which reads the registry and 400s naming the\n // offenders. The CLI holds no copy of that registry to check against.\n //\n // Dispatched through the app type rather than branched on here: `validateConfig` is a no-op\n // for OAuth (whose checks are capability-driven, above) and runs `validateUiApp` for a UI\n // app, so a third type brings its own pre-flight without this command changing.\n resolveFromConfig(config).validateConfig(config);\n\n const scopes = config.auth?.scopes ?? [];\n validateScopes(scopes);\n if (containsLegacyAllScope(scopes)) {\n throw new CliError(messages.LEGACY_ALL_SCOPE_DEPRECATED_BLOCK);\n }\n}\n\n/**\n * Ask before pushing. Returns false when the user declined; refuses outright off a TTY,\n * where there is nobody to answer.\n */\nasync function confirmUpload(): Promise<boolean> {\n if (!process.stdin.isTTY) {\n throw new CliError(NON_INTERACTIVE_CONFIRM_ERROR);\n }\n const { confirmed } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'confirmed',\n message: messages.APP_UPLOAD_CONFIRM,\n default: true,\n },\n ]);\n return !!confirmed;\n}\n\nexport const uploadCommand = withCommandHandler(async (options: UploadOptions): Promise<void> => {\n const config = loadUsableConfig();\n\n runLocalPreflight(config);\n\n // Unconditional: --json and --yes both still fetch + diff, per BEX-250.\n const remote = await fetchExistingApp(config.appId, options.json);\n const diff = buildDiff(config, remote);\n\n // distribution_type is immutable via upload. The server (BEX-355) rejects\n // drift with a 422, but that would burn the round trip \u2014 fast-fail here\n // against the remote state we just fetched, before prompting or pushing.\n // Skipped when the server didn't report a distribution to compare against\n // (the server-side check then remains the only enforcement).\n if (diff.currentDistribution && diff.currentDistribution !== diff.nextDistribution) {\n throw new CliError(\n messages.APP_UPLOAD_DISTRIBUTION_IMMUTABLE(diff.currentDistribution, diff.nextDistribution),\n );\n }\n\n if (!options.json) {\n renderUploadDiff(diff);\n }\n\n if (hasNoChanges(diff)) {\n if (options.json) {\n jsonOutput({\n appId: config.appId,\n upToDate: true,\n version: diff.nextVersion,\n ...diffToJson(diff),\n });\n return;\n }\n logInfo(messages.APP_UPLOAD_UP_TO_DATE(diff.nextVersion || 'unknown'));\n return;\n }\n\n if (!options.json && !options.yes && !(await confirmUpload())) {\n logInfo(`\\n ${messages.APP_UPLOAD_CANCELLED}\\n`);\n return;\n }\n\n const { confirmedVersion, finalName } = await uploadProjectConfig(config, {\n silent: options.json,\n appVersion: diff.nextVersion,\n });\n\n if (options.json) {\n jsonOutput({\n appId: config.appId,\n name: finalName,\n version: confirmedVersion,\n ...diffToJson(diff),\n });\n return;\n }\n\n logSuccess(messages.APP_UPLOAD_SUCCESS);\n logInfo(` Version: ${confirmedVersion || '(unknown)'}`);\n process.stdout.write('\\n');\n});\n", "import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport inquirer from 'inquirer';\nimport { logSuccess, logInfo, logWarn } from '../../lib/logger';\nimport { messages } from '../../lang/en';\nimport { withCommandHandler } from '../../lib/command-handler';\nimport { jsonOutput } from '../../lib/json-output';\nimport { appService } from '../../container';\nimport { createSpinner } from '../../lib/ui';\nimport { deleteAppCredentials, deleteAppName, readProjectConfig } from '../../lib/config';\nimport { assertAppSelectionAllowed, promptAppSelection } from './select-app';\nimport { CLI } from '../../lib/constants';\n\nfunction isSafeToDelete(dir: string): boolean {\n const resolved = path.resolve(dir);\n const home = os.homedir();\n const { root } = path.parse(resolved);\n // Never delete filesystem root or home directory\n if (resolved === root || resolved === home) return false;\n // Never delete direct children of root (e.g. /usr, /etc, C:\\Windows)\n if (path.dirname(resolved) === root) return false;\n // Must contain app-config.json (scaffold marker)\n if (!fs.existsSync(path.join(resolved, 'app-config.json'))) return false;\n return true;\n}\n\nasync function confirmDeletion(appLabel: string, appId: string): Promise<boolean> {\n const { confirmed } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'confirmed',\n message: messages.APP_DELETE_CONFIRM(appLabel, appId),\n default: false,\n },\n ]);\n\n if (!confirmed) {\n logInfo(`\\n ${messages.APP_DELETE_CANCELLED}\\n`);\n return false;\n }\n return true;\n}\n\nfunction removeProjectFolder(cwd: string): void {\n if (!isSafeToDelete(cwd)) {\n logWarn(messages.APP_DELETE_FOLDER_FAILED(cwd));\n return;\n }\n try {\n fs.rmSync(cwd, { recursive: true, force: true });\n logSuccess(messages.APP_DELETE_FOLDER_SUCCESS(cwd));\n } catch {\n logWarn(messages.APP_DELETE_FOLDER_FAILED(cwd));\n }\n}\n\n// Offer to delete the local scaffolded project folder if it matches the deleted app\nasync function offerLocalFolderCleanup(appId: string): Promise<void> {\n const projectConfig = readProjectConfig();\n if (projectConfig?.appId !== appId) return;\n\n const cwd = process.cwd();\n const { deleteFolder } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'deleteFolder',\n message: messages.APP_DELETE_FOLDER_CONFIRM(cwd),\n default: false,\n },\n ]);\n\n if (deleteFolder) {\n removeProjectFolder(cwd);\n }\n}\n\nexport const deleteCommand = withCommandHandler(\n async (options: { appId?: string; force?: boolean; json?: boolean }): Promise<void> => {\n let appId = options.appId;\n let appLabel = '';\n\n if (!appId) {\n assertAppSelectionAllowed(CLI.APP_DELETE_APP_ID(), options.json);\n const selection = await promptAppSelection(messages.APP_DELETE_SELECT);\n appId = selection.appId;\n appLabel = selection.appLabel;\n }\n\n if (!options.force && !(await confirmDeletion(appLabel || appId, appId))) {\n return;\n }\n\n const deleteSpinner = createSpinner('Deleting app...', { silent: options.json });\n await appService.deleteApp(appId);\n deleteSpinner.stop();\n\n deleteAppName(appId);\n deleteAppCredentials(appId);\n\n if (options.json) {\n jsonOutput({ deleted: true, appId });\n return;\n }\n\n logSuccess(messages.APP_DELETE_SUCCESS(appId));\n\n if (!options.force) {\n await offerLocalFolderCleanup(appId);\n }\n },\n);\n", "import { OAUTH_SCOPES_URL } from '../lib/constants';\nimport { ApiError, CliError, ErrorCode } from '../lib/errors';\nimport { messages } from '../lang/en';\n\nexport interface ScopeEntry {\n name: string;\n category: string;\n apiEndpoints: string[];\n}\n\ninterface RawScope {\n name?: unknown;\n category?: unknown;\n api_endpoints?: unknown;\n is_oidc_reserved?: unknown;\n}\n\nexport async function fetchSupportedScopes(): Promise<ScopeEntry[]> {\n let response: Response;\n try {\n response = await fetch(OAUTH_SCOPES_URL, { method: 'GET' });\n } catch {\n throw new ApiError(\n messages.OAUTH_METADATA_FETCH_FAILED(OAUTH_SCOPES_URL, 0),\n 0,\n ErrorCode.NETWORK_ERROR,\n );\n }\n\n if (!response.ok) {\n throw new ApiError(\n messages.OAUTH_METADATA_FETCH_FAILED(OAUTH_SCOPES_URL, response.status),\n response.status,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new CliError(messages.OAUTH_METADATA_MISSING_SCOPES);\n }\n\n if (!body || typeof body !== 'object' || !Array.isArray((body as { scopes?: unknown }).scopes)) {\n throw new CliError(messages.OAUTH_METADATA_MISSING_SCOPES);\n }\n\n const rawScopes = (body as { scopes: unknown[] }).scopes as RawScope[];\n\n return rawScopes\n .filter(\n (\n s,\n ): s is {\n name: string;\n category: string;\n api_endpoints?: unknown;\n is_oidc_reserved?: unknown;\n } =>\n !!s &&\n typeof s === 'object' &&\n typeof s.name === 'string' &&\n typeof s.category === 'string' &&\n s.is_oidc_reserved !== true,\n )\n .map((s) => ({\n name: s.name,\n category: s.category,\n apiEndpoints: Array.isArray(s.api_endpoints)\n ? s.api_endpoints.filter((e): e is string => typeof e === 'string')\n : [],\n }));\n}\n", "import * as http from 'node:http';\nimport { AddressInfo } from 'node:net';\nimport type { ScopeEntry } from './oauth-metadata';\nimport { renderScopesHtml } from './scopes-html';\nimport { logDebug } from '../lib/logger';\n\nexport interface ScopesWebServer {\n url: string;\n close: () => Promise<void>;\n}\n\nexport interface ScopesWebOptions {\n /** Called by GET /scopes.json to re-fetch scopes on demand. Optional \u2014\n * when omitted, the refresh button returns the initial entries unchanged. */\n refetch?: () => Promise<ScopeEntry[]>;\n}\n\nfunction closeServer(server: http.Server): Promise<void> {\n return new Promise<void>((resolveClose) => {\n server.close(() => resolveClose());\n });\n}\n\nexport function startScopesWebServer(\n initialEntries: ScopeEntry[],\n options: ScopesWebOptions = {},\n): Promise<ScopesWebServer> {\n const html = renderScopesHtml(initialEntries);\n const refetch = options.refetch;\n\n return new Promise<ScopesWebServer>((resolve, reject) => {\n const server = http.createServer((req, res) => {\n const pathname = new URL(req.url ?? '/', 'http://127.0.0.1').pathname;\n logDebug('scopes-web request', { method: req.method, pathname });\n\n if (req.method === 'GET' && pathname === '/') {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(html);\n return;\n }\n\n if (req.method === 'GET' && pathname === '/scopes.json') {\n const respond = (entries: ScopeEntry[]): void => {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n });\n res.end(JSON.stringify({ scopes: entries }));\n };\n\n if (!refetch) {\n respond(initialEntries);\n return;\n }\n\n refetch()\n .then(respond)\n .catch((err: unknown) => {\n logDebug('scopes-web refetch failed', { message: (err as Error).message });\n res.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify({ error: 'refetch_failed' }));\n });\n return;\n }\n\n res.writeHead(404).end();\n });\n\n server.once('error', (err) => {\n logDebug('scopes-web server error', { message: (err as Error).message });\n reject(err);\n });\n\n server.listen(0, '127.0.0.1', () => {\n const port = (server.address() as AddressInfo).port;\n const url = `http://127.0.0.1:${port}/`;\n logDebug('scopes-web listening', { host: '127.0.0.1', port });\n resolve({\n url,\n close: () => closeServer(server),\n });\n });\n });\n}\n", "import {\n OAUTH_SCOPES_URL,\n BREVO_CLI_REFERENCE_URL,\n BREVO_OAUTH_SCOPES_DOCS_URL,\n LEGACY_ALL_SCOPE,\n} from '../lib/constants';\nimport { messages } from '../lang/en';\nimport type { ScopeEntry } from './oauth-metadata';\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#39;');\n}\n\nfunction safeJson(value: unknown): string {\n // Safe for embedding inside <script type=\"application/json\">: only\n // </script> can end the block, so we just neutralise '<'.\n return JSON.stringify(value).replaceAll('<', String.raw`\\u003c`);\n}\n\n// Styling matches the Brevo developers docs (developers.brevo.com) \u2014 Brevo\n// green accent, pale-green hero, and charcoal-grey neutrals. Fonts use a\n// system stack (no CDN fetch or web-font download) so the page renders the\n// same with or without network.\nconst STYLES = `\n:root {\n color-scheme: light dark;\n --bg: #ffffff;\n --hero-bg: #e6f6ec;\n --fg: #1b1b1b;\n --muted: #696969;\n --border: #e3e3e3;\n --accent: #0b996e;\n --card: #ffffff;\n --card-shadow: 0 1px 2px rgba(28, 28, 28, .08);\n --chip-bg: #e1f3e8;\n --chip-fg: #00734a;\n --section-header-bg: #f6fbf8;\n --error: #cf1a3b;\n --radius-card: 16px;\n --radius-input: 16px;\n --radius-chip: 20px;\n}\n@media (prefers-color-scheme: dark) {\n :root {\n --bg: #0e1613;\n --hero-bg: #142119;\n --fg: #f1f5f2;\n --muted: #94a89c;\n --border: #25382d;\n --accent: #3ddc97;\n --card: #16241c;\n --card-shadow: 0 1px 2px rgba(0, 0, 0, .4);\n --chip-bg: #1d3327;\n --chip-fg: #a7e3c5;\n --section-header-bg: #121f18;\n --error: #ff8d9a;\n }\n}\n* { box-sizing: border-box; }\nbody {\n margin: 0;\n padding: 0;\n font: 15px/1.55 system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n background: var(--bg);\n color: var(--fg);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.hero {\n background: var(--hero-bg);\n padding: 2.5rem 1.5rem 2rem;\n}\n.hero-inner {\n max-width: 880px;\n margin: 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1.5rem;\n flex-wrap: wrap;\n}\n.hero-copy { flex: 1 1 24rem; min-width: 0; }\nh1 {\n font-size: 2rem;\n line-height: 2.5rem;\n font-weight: 600;\n margin: 0 0 .5rem;\n}\n.intro { color: var(--muted); margin: 0; word-break: break-word; }\na.docs-cta {\n display: inline-block;\n flex: none;\n white-space: nowrap;\n padding: .5rem 1.1rem;\n font-weight: 600;\n font-size: .9rem;\n color: #ffffff;\n background: var(--accent);\n border-radius: var(--radius-input);\n text-decoration: none;\n box-shadow: var(--card-shadow);\n}\na.docs-cta:hover { opacity: .9; }\n@media (prefers-color-scheme: dark) {\n a.docs-cta { color: #0e1613; }\n}\nmain {\n max-width: 880px;\n margin: 0 auto;\n padding: 1.5rem 1.5rem 4rem;\n}\n.toolbar {\n display: flex;\n gap: .75rem;\n align-items: center;\n margin-bottom: 1.5rem;\n}\ninput[type=\"search\"] {\n flex: 1;\n padding: .55rem .9rem;\n font: inherit;\n font-size: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius-input);\n background: var(--card);\n color: var(--fg);\n}\ninput[type=\"search\"]:focus {\n outline: none;\n border-color: var(--accent);\n box-shadow: inset 0 0 0 1px var(--accent);\n}\nbutton.refresh {\n padding: .5rem 1rem;\n font: inherit;\n font-weight: 600;\n font-size: .9rem;\n border: 1px solid var(--border);\n border-radius: var(--radius-input);\n background: var(--card);\n color: var(--fg);\n cursor: pointer;\n box-shadow: var(--card-shadow);\n}\nbutton.refresh:hover { background: var(--chip-bg); border-color: var(--chip-bg); }\nbutton.refresh:disabled { opacity: .55; cursor: progress; }\n.refresh-error {\n margin: -.75rem 0 1rem;\n color: var(--error);\n font-size: .85rem;\n}\nsection {\n border: 1px solid var(--border);\n border-radius: var(--radius-card);\n margin-bottom: 1rem;\n background: var(--card);\n box-shadow: var(--card-shadow);\n overflow: hidden;\n}\nsection[hidden] { display: none; }\n.section-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: .75rem;\n padding: .55rem 1rem;\n background: var(--section-header-bg);\n border-bottom: 1px solid var(--border);\n}\n.section-head h2 {\n font-size: .75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: .06em;\n color: var(--muted);\n margin: 0;\n}\nbutton.copy {\n font: inherit;\n font-size: .75rem;\n font-weight: 600;\n padding: .2rem .7rem;\n border: 1px solid var(--border);\n border-radius: var(--radius-chip);\n background: var(--card);\n color: var(--accent);\n cursor: pointer;\n white-space: nowrap;\n}\nbutton.copy:hover { background: var(--chip-bg); border-color: var(--chip-bg); }\nbutton.copy:disabled { opacity: .55; cursor: default; }\n.selection-bar {\n display: flex;\n gap: .75rem;\n align-items: center;\n margin-bottom: 1.5rem;\n}\n.selection-bar input[type=\"text\"] {\n flex: 1;\n padding: .55rem .9rem;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Monaco, Consolas, monospace;\n font-size: .85rem;\n border: 1px solid var(--border);\n border-radius: var(--radius-input);\n background: var(--card);\n color: var(--fg);\n}\n.selection-bar button.copy { padding: .5rem 1rem; font-size: .9rem; }\nul { list-style: none; margin: 0; padding: .5rem 0; }\nli { padding: .4rem 1rem; }\nli[hidden] { display: none; }\n.scope-row { display: flex; align-items: flex-start; gap: .55rem; }\n.scope-row input[type=\"checkbox\"] {\n accent-color: var(--accent);\n margin-top: .25rem;\n flex: none;\n cursor: pointer;\n}\n.scope-row details { flex: 1; min-width: 0; }\n.badge {\n font-size: .68rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: .04em;\n color: var(--error);\n border: 1px solid currentColor;\n border-radius: var(--radius-chip);\n padding: 0 .45rem;\n margin-left: .5rem;\n vertical-align: middle;\n}\ndetails > summary {\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Monaco, Consolas, monospace;\n font-size: .9rem;\n cursor: pointer;\n list-style: revert;\n}\ndetails[open] > summary { margin-bottom: .35rem; }\n.endpoints {\n margin: .25rem 0 .25rem 1.1rem;\n padding: 0;\n display: flex;\n flex-wrap: wrap;\n gap: .35rem;\n}\n.endpoints .ep {\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Monaco, Consolas, monospace;\n font-size: .78rem;\n background: var(--chip-bg);\n color: var(--chip-fg);\n padding: .15rem .55rem;\n border-radius: var(--radius-chip);\n}\n.endpoints .none {\n font-size: .8rem;\n color: var(--muted);\n font-style: italic;\n}\n.empty { color: var(--muted); padding: 2rem 0; text-align: center; }\nfooter {\n margin-top: 3rem;\n padding-top: 1.5rem;\n border-top: 1px solid var(--border);\n font-size: .8rem;\n color: var(--muted);\n}\nfooter a { color: var(--accent); }\n`.trim();\n\nconst SCRIPT = `\n(function () {\n var dataNode = document.getElementById('scopes-data');\n var state = dataNode ? JSON.parse(dataNode.textContent || '[]') : [];\n\n var listEl = document.getElementById('scopes-list');\n var searchEl = document.querySelector('input[type=\"search\"]');\n var refreshBtn = document.getElementById('refresh-btn');\n var errorEl = document.getElementById('refresh-error');\n var introCountEl = document.getElementById('scopes-count');\n var selectedEl = document.getElementById('selected-scopes');\n var copySelectedBtn = document.getElementById('copy-selected');\n var labels = JSON.parse(document.getElementById('labels').textContent);\n\n // Selected scope names, in click order. Rendered as '\"a\",\"b\"' \u2014 pastes\n // straight into app-config.json's auth.scopes array.\n var selected = [];\n\n function quoted(names) {\n return names.map(function (n) { return '\"' + n + '\"'; }).join(',');\n }\n\n function escapeHtml(s) {\n return String(s).replace(/[&<>\"']/g, function (c) {\n return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c];\n });\n }\n\n function render(entries) {\n if (!entries.length) {\n listEl.innerHTML = '<p class=\"empty\">' + escapeHtml(labels.empty) + '</p>';\n return;\n }\n var byCategory = new Map();\n entries.forEach(function (e) {\n var list = byCategory.get(e.category);\n if (list) list.push(e);\n else byCategory.set(e.category, [e]);\n });\n var html = '';\n byCategory.forEach(function (items, cat) {\n var copyAria = labels.copyCategoryAria.replace('{category}', cat);\n html += '<section><div class=\"section-head\"><h2>' + escapeHtml(cat) + '</h2>';\n html +=\n '<button type=\"button\" class=\"copy copy-category\" data-category=\"' + escapeHtml(cat) +\n '\" aria-label=\"' + escapeHtml(copyAria) + '\">' + escapeHtml(labels.copy) + '</button></div><ul>';\n items.forEach(function (e) {\n var endpoints = Array.isArray(e.apiEndpoints) ? e.apiEndpoints : [];\n var isLegacy = e.name === labels.legacyScope;\n html += '<li><div class=\"scope-row\">';\n if (isLegacy) {\n // The legacy catch-all scope is deprecated: badge it, no checkbox,\n // and copy CTAs skip it so pasted lists never reintroduce it.\n html += '<details><summary>' + escapeHtml(e.name) +\n '<span class=\"badge\" title=\"' + escapeHtml(labels.legacyTitle) + '\">' +\n escapeHtml(labels.legacyBadge) + '</span></summary>';\n } else {\n var selectAria = labels.selectScopeAria.replace('{scope}', e.name);\n html += '<input type=\"checkbox\" class=\"scope-check\" data-scope=\"' + escapeHtml(e.name) +\n '\" aria-label=\"' + escapeHtml(selectAria) + '\"' +\n (selected.indexOf(e.name) !== -1 ? ' checked' : '') + '>';\n html += '<details><summary>' + escapeHtml(e.name) + '</summary>';\n }\n if (endpoints.length) {\n html += '<div class=\"endpoints\" aria-label=\"' + escapeHtml(labels.endpointsLabel) + '\">';\n endpoints.forEach(function (ep) {\n html += '<span class=\"ep\">' + escapeHtml(ep) + '</span>';\n });\n html += '</div>';\n } else {\n html += '<div class=\"endpoints\"><span class=\"none\">' + escapeHtml(labels.noEndpoints) + '</span></div>';\n }\n html += '</details></div></li>';\n });\n html += '</ul></section>';\n });\n listEl.innerHTML = html;\n applyFilter();\n }\n\n function copyText(text, btn) {\n if (!navigator.clipboard || !navigator.clipboard.writeText) return;\n navigator.clipboard.writeText(text).then(function () {\n var original = btn.textContent;\n btn.textContent = labels.copied;\n setTimeout(function () {\n btn.textContent = original;\n }, 1200);\n }).catch(function () { /* clipboard denied \u2014 leave the button as-is */ });\n }\n\n function updateSelection() {\n selectedEl.value = quoted(selected);\n copySelectedBtn.disabled = selected.length === 0;\n }\n\n function applyFilter() {\n var q = searchEl ? searchEl.value.toLowerCase().trim() : '';\n var sections = listEl.querySelectorAll('section');\n sections.forEach(function (section) {\n var items = section.querySelectorAll('li');\n var anyVisible = false;\n items.forEach(function (li) {\n var text = li.textContent.toLowerCase();\n var match = q === '' || text.indexOf(q) !== -1;\n li.hidden = !match;\n if (match) anyVisible = true;\n });\n section.hidden = !anyVisible;\n });\n }\n\n function setCount(n) {\n if (introCountEl) introCountEl.textContent = String(n);\n }\n\n render(state);\n setCount(state.length);\n updateSelection();\n\n if (searchEl) {\n searchEl.addEventListener('input', applyFilter);\n }\n\n // Delegated handlers survive re-renders (refresh replaces listEl's HTML).\n listEl.addEventListener('click', function (ev) {\n var btn = ev.target.closest ? ev.target.closest('button.copy-category') : null;\n if (!btn) return;\n var cat = btn.getAttribute('data-category');\n var names = state\n .filter(function (e) { return e.category === cat && e.name !== labels.legacyScope; })\n .map(function (e) { return e.name; });\n copyText(quoted(names), btn);\n });\n\n listEl.addEventListener('change', function (ev) {\n var box = ev.target.closest ? ev.target.closest('input.scope-check') : null;\n if (!box) return;\n var name = box.getAttribute('data-scope');\n var idx = selected.indexOf(name);\n if (box.checked && idx === -1) selected.push(name);\n if (!box.checked && idx !== -1) selected.splice(idx, 1);\n updateSelection();\n });\n\n copySelectedBtn.addEventListener('click', function () {\n if (selected.length) copyText(quoted(selected), copySelectedBtn);\n });\n\n if (refreshBtn) {\n var defaultLabel = refreshBtn.textContent;\n refreshBtn.addEventListener('click', function () {\n refreshBtn.disabled = true;\n refreshBtn.textContent = labels.refreshing;\n if (errorEl) errorEl.hidden = true;\n fetch('/scopes.json', { headers: { Accept: 'application/json' } })\n .then(function (res) {\n if (!res.ok) throw new Error('HTTP ' + res.status);\n return res.json();\n })\n .then(function (body) {\n var entries = Array.isArray(body && body.scopes) ? body.scopes : [];\n state = entries;\n // Drop selections for scopes the IdP no longer returns.\n selected = selected.filter(function (name) {\n return state.some(function (e) { return e.name === name; });\n });\n render(state);\n setCount(state.length);\n updateSelection();\n })\n .catch(function () {\n if (errorEl) errorEl.hidden = false;\n })\n .then(function () {\n refreshBtn.disabled = false;\n refreshBtn.textContent = defaultLabel;\n });\n });\n }\n})();\n`.trim();\n\nexport function renderScopesHtml(entries: ScopeEntry[]): string {\n const title = escapeHtml(messages.APP_SCOPES_WEB_TITLE);\n const intro = messages.APP_SCOPES_WEB_INTRO(entries.length, OAUTH_SCOPES_URL);\n // The intro is rendered with a dynamic count span so refresh can update it\n // in place without re-rendering the source URL.\n const introWithSpan = escapeHtml(intro).replace(\n String(entries.length),\n `<span id=\"scopes-count\">${entries.length}</span>`,\n );\n const searchPlaceholder = escapeHtml(messages.APP_SCOPES_WEB_SEARCH_PLACEHOLDER);\n const refreshLabel = escapeHtml(messages.APP_SCOPES_WEB_REFRESH);\n const refreshError = escapeHtml(messages.APP_SCOPES_WEB_REFRESH_FAILED);\n const footer = escapeHtml(messages.APP_SCOPES_WEB_FOOTER);\n const docsLink = escapeHtml(messages.APP_SCOPES_WEB_DOCS_LINK);\n const catalogDocsCta = escapeHtml(messages.APP_SCOPES_WEB_CATALOG_DOCS_CTA);\n const selectedPlaceholder = escapeHtml(messages.APP_SCOPES_WEB_SELECTED_PLACEHOLDER);\n const copySelected = escapeHtml(messages.APP_SCOPES_WEB_COPY_SELECTED);\n\n const labels = {\n empty: messages.APP_SCOPES_WEB_EMPTY,\n refreshing: messages.APP_SCOPES_WEB_REFRESHING,\n endpointsLabel: messages.APP_SCOPES_WEB_ENDPOINTS_LABEL,\n noEndpoints: messages.APP_SCOPES_WEB_NO_ENDPOINTS,\n copy: messages.APP_SCOPES_WEB_COPY,\n copied: messages.APP_SCOPES_WEB_COPIED,\n copyCategoryAria: messages.APP_SCOPES_WEB_COPY_CATEGORY_ARIA,\n selectScopeAria: messages.APP_SCOPES_WEB_SELECT_SCOPE_ARIA,\n legacyScope: LEGACY_ALL_SCOPE,\n legacyBadge: messages.APP_SCOPES_WEB_LEGACY_BADGE,\n legacyTitle: messages.APP_SCOPES_WEB_LEGACY_TITLE,\n };\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${title}</title>\n<style>${STYLES}</style>\n</head>\n<body>\n<header class=\"hero\">\n <div class=\"hero-inner\">\n <div class=\"hero-copy\">\n <h1>${title}</h1>\n <p class=\"intro\">${introWithSpan}</p>\n </div>\n <a class=\"docs-cta\" href=\"${BREVO_OAUTH_SCOPES_DOCS_URL}\" target=\"_blank\" rel=\"noopener noreferrer\">${catalogDocsCta} \u2197</a>\n </div>\n</header>\n<main>\n <div class=\"toolbar\">\n <input type=\"search\" placeholder=\"${searchPlaceholder}\" autofocus>\n <button id=\"refresh-btn\" class=\"refresh\" type=\"button\">${refreshLabel}</button>\n </div>\n <p id=\"refresh-error\" class=\"refresh-error\" hidden>${refreshError}</p>\n <div class=\"selection-bar\">\n <input id=\"selected-scopes\" type=\"text\" readonly placeholder=\"${selectedPlaceholder}\" aria-label=\"${selectedPlaceholder}\">\n <button id=\"copy-selected\" class=\"copy\" type=\"button\" disabled>${copySelected}</button>\n </div>\n <div id=\"scopes-list\"></div>\n <footer>${footer} <a href=\"${BREVO_CLI_REFERENCE_URL}\">${docsLink}</a></footer>\n</main>\n<script type=\"application/json\" id=\"scopes-data\">${safeJson(entries)}</script>\n<script type=\"application/json\" id=\"labels\">${safeJson(labels)}</script>\n<script>${SCRIPT}</script>\n</body>\n</html>\n`;\n}\n", "import { withCommandHandler } from '../../lib/command-handler';\nimport { jsonOutput } from '../../lib/json-output';\nimport { logDebug, logInfo } from '../../lib/logger';\nimport { messages } from '../../lang/en';\nimport { fetchSupportedScopes, ScopeEntry } from '../../services/oauth-metadata';\nimport { startScopesWebServer } from '../../services/scopes-web';\nimport { openBrowser } from '../../lib/browser';\n\ninterface ScopesOptions {\n json?: boolean;\n web?: boolean;\n}\n\nfunction waitForShutdownSignal(): Promise<void> {\n return new Promise((resolve) => {\n const handler = (): void => {\n process.off('SIGINT', handler);\n process.off('SIGTERM', handler);\n resolve();\n };\n process.once('SIGINT', handler);\n process.once('SIGTERM', handler);\n });\n}\n\nfunction groupByCategory(entries: ScopeEntry[]): Map<string, string[]> {\n const byCategory = new Map<string, string[]>();\n for (const entry of entries) {\n const list = byCategory.get(entry.category);\n if (list) {\n list.push(entry.name);\n } else {\n byCategory.set(entry.category, [entry.name]);\n }\n }\n return byCategory;\n}\n\nfunction printScopesByCategory(entries: ScopeEntry[]): void {\n const byCategory = groupByCategory(entries);\n\n let first = true;\n for (const [category, names] of byCategory) {\n if (!first) logInfo('');\n first = false;\n logInfo(`${category}:`);\n for (const name of names) {\n logInfo(` ${name}`);\n }\n }\n\n logInfo('');\n logInfo(messages.APP_SCOPES_USAGE_HINT);\n logInfo(messages.APP_SCOPES_CATALOG_DOCS_HINT);\n logInfo(messages.APP_SCOPES_DOCS_HINT);\n}\n\nasync function runWebMode(entries: ScopeEntry[]): Promise<void> {\n const server = await startScopesWebServer(entries, { refetch: fetchSupportedScopes });\n logInfo('');\n logInfo(messages.APP_SCOPES_WEB_LISTENING(server.url));\n try {\n openBrowser(server.url);\n } catch (err) {\n logDebug('openBrowser failed', { message: (err as Error).message });\n }\n await waitForShutdownSignal();\n await server.close();\n}\n\nexport const scopesCommand = withCommandHandler(async (options: ScopesOptions): Promise<void> => {\n const entries = await fetchSupportedScopes();\n\n if (options.json) {\n jsonOutput({ scopes: entries.map((e) => e.name) });\n return;\n }\n\n if (entries.length === 0) {\n logInfo(messages.APP_SCOPES_EMPTY);\n } else {\n printScopesByCategory(entries);\n }\n\n if (options.web) {\n await runWebMode(entries);\n }\n});\n", "import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport inquirer from 'inquirer';\nimport { logInfo, logSuccess, logWarn } from '../../lib/logger';\nimport { CliError } from '../../lib/errors';\nimport { messages } from '../../lang/en';\nimport { withCommandHandler } from '../../lib/command-handler';\nimport { isPortAvailable } from '../../lib/port';\nimport { DEFAULT_PORT } from '../../lib/constants';\nimport { readProjectConfig, writeProjectConfig, ProjectConfig } from '../../lib/config';\nimport { containsLegacyAllScope } from '../../lib/validators';\nimport { uploadProjectConfig } from './upload';\n\n/**\n * Feature registry \u2014 maps feature names to their entry files.\n * Add new features here as they are added to the scaffold.\n */\nconst FEATURES: Record<string, { entry: string; description: string }> = {\n oauth: {\n entry: 'src/oauth/server.js',\n description: 'Local OAuth test server',\n },\n};\n\n/**\n * Returns the first redirect URL pointing at the local loopback\n * (host = localhost or 127.0.0.1) on the given port, or undefined.\n * Unparseable URLs are skipped.\n */\nfunction findMatchingLocalRedirect(redirectUris: string[], port: number): string | undefined {\n return redirectUris.find((url) => {\n try {\n const parsed = new URL(url);\n const hostMatches = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';\n return hostMatches && parsed.port === String(port);\n } catch {\n return false;\n }\n });\n}\n\n/**\n * If the resolved port has no matching localhost redirect URL on the app,\n * offer to register one. Approval writes the new URL into app-config.json\n * (the source of truth) and then syncs the server from the config via the\n * upload flow, so the next run is silent. Decline continues with a warning.\n * Non-TTY hard-fails because there's no way to confirm a remote-mutating\n * operation.\n *\n * Returns the resolved localhost redirect URL (existing or newly registered),\n * or undefined when the user declined registration.\n */\nasync function ensureRedirectRegistered(\n config: ProjectConfig,\n port: number,\n): Promise<string | undefined> {\n const redirectUris = config.auth?.redirectUris ?? [];\n const existing = findMatchingLocalRedirect(redirectUris, port);\n if (existing) return existing;\n\n const newRedirectUrl = `http://localhost:${port}/auth/callback`;\n\n if (!process.stdin.isTTY) {\n throw new CliError(messages.APP_START_REDIRECT_NON_INTERACTIVE(port, newRedirectUrl));\n }\n\n logInfo(` ${messages.APP_START_REDIRECT_NOT_REGISTERED(port)}`);\n const { shouldRegister } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'shouldRegister',\n message: messages.APP_START_REDIRECT_REGISTER_PROMPT(newRedirectUrl),\n default: true,\n },\n ]);\n\n if (!shouldRegister) {\n logWarn(messages.APP_START_REDIRECT_DECLINED(newRedirectUrl));\n return undefined;\n }\n\n // app-config.json is the source of truth: write the URL there first, then\n // sync the server from the config through the regular upload flow \u2014 the\n // single write path for app state. Never PATCH fields directly (BEX-366).\n const updatedConfig: ProjectConfig = {\n ...config,\n auth: { ...config.auth, redirectUris: [...redirectUris, newRedirectUrl] },\n };\n writeProjectConfig(updatedConfig);\n\n try {\n await uploadProjectConfig(updatedConfig);\n } catch (err) {\n logWarn(messages.APP_START_REDIRECT_UPLOAD_FAILED(newRedirectUrl));\n throw err;\n }\n\n logSuccess(messages.APP_START_REDIRECT_REGISTERED(newRedirectUrl));\n return newRedirectUrl;\n}\n\nfunction resolveFeatureEntry(feature: string | undefined): string {\n if (!feature) {\n const available = Object.entries(FEATURES)\n .map(([name, f]) => ` ${name} ${f.description}`)\n .join('\\n');\n throw new CliError(messages.APP_START_MISSING_FEATURE(available));\n }\n\n const featureConfig = FEATURES[feature];\n if (!featureConfig) {\n const available = Object.keys(FEATURES).join(', ');\n throw new CliError(messages.APP_START_UNKNOWN_FEATURE(feature, available));\n }\n\n const entryFile = path.resolve(featureConfig.entry);\n if (!fs.existsSync(entryFile)) {\n throw new CliError(messages.APP_START_FEATURE_NOT_FOUND(featureConfig.entry));\n }\n\n const featureDir = path.dirname(featureConfig.entry);\n if (!fs.existsSync(path.resolve(featureDir, 'node_modules'))) {\n throw new CliError(messages.APP_START_NO_DEPS(featureDir));\n }\n return entryFile;\n}\n\nfunction resolvePort(config: ProjectConfig | null, optionsPort?: number): number {\n if (optionsPort) return optionsPort;\n const redirectUrl = config?.auth?.redirectUris?.[0];\n if (!redirectUrl) return DEFAULT_PORT;\n try {\n const parsed = new URL(redirectUrl);\n return parsed.port ? Number(parsed.port) : DEFAULT_PORT;\n } catch {\n return DEFAULT_PORT;\n }\n}\n\nfunction runChildProcess(\n entryFile: string,\n childEnv: NodeJS.ProcessEnv,\n feature: string,\n): Promise<void> {\n const child = spawn(process.execPath, [entryFile], {\n stdio: 'inherit',\n env: childEnv,\n });\n\n const onSignal = (signal: NodeJS.Signals): void => {\n child.kill(signal);\n };\n process.prependListener('SIGINT', onSignal);\n process.prependListener('SIGTERM', onSignal);\n\n return new Promise((resolve, reject) => {\n child.on('close', (code) => {\n process.removeListener('SIGINT', onSignal);\n process.removeListener('SIGTERM', onSignal);\n if (code && code !== 0) {\n reject(new CliError(messages.APP_START_EXITED(feature, code)));\n } else {\n logInfo(`\\n ${messages.APP_START_STOPPED}\\n`);\n resolve();\n }\n });\n child.on('error', (err) => {\n process.removeListener('SIGINT', onSignal);\n process.removeListener('SIGTERM', onSignal);\n reject(new CliError(messages.APP_START_FAILED(feature, err.message)));\n });\n });\n}\n\nexport const startCommand = withCommandHandler(\n async (options: { feature?: string; port?: number }): Promise<void> => {\n const { feature } = options;\n const entryFile = resolveFeatureEntry(feature);\n\n const config = readProjectConfig();\n\n // The child OAuth server builds its authorize URL from auth.scopes \u2014\n // refuse to launch a test flow with the deprecated legacy 'all' scope\n // and point at the --scope migration path instead (BEX-214).\n if (containsLegacyAllScope(config?.auth?.scopes)) {\n throw new CliError(messages.LEGACY_ALL_SCOPE_START_BLOCK);\n }\n\n const port = resolvePort(config, options.port);\n\n if (!(await isPortAvailable(port))) {\n throw new CliError(\n options.port\n ? messages.APP_START_CUSTOM_PORT_IN_USE(port)\n : messages.APP_START_PORT_IN_USE(port),\n );\n }\n\n // If the app is linked locally, make sure the resolved port is one of\n // its registered redirect URLs \u2014 otherwise the OAuth callback will be\n // rejected by Brevo at runtime. Skipped when there's no linked app\n // (no appId means we'd have nowhere to push the update).\n const redirectUri = config?.appId ? await ensureRedirectRegistered(config, port) : undefined;\n\n logInfo(`\\n Starting ${feature}...`);\n\n const childEnv: NodeJS.ProcessEnv = { ...process.env, PORT: String(port) };\n if (redirectUri) childEnv.REDIRECT_URI = redirectUri;\n\n await runChildProcess(entryFile, childEnv, feature!);\n },\n);\n", "import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\n\nimport { messages } from '../lang/en';\nimport { CliError } from '../lib/errors';\nimport { SKILL_CATALOG, SKILLS_BUNDLE_DIR, SkillEntry, getSkill } from '../skills';\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Target directory \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Claude Code reads skills from `~/.claude/skills/<name>/`. The path is\n// overridable via BREVO_CLAUDE_HOME so tests (and future Cursor support)\n// can target a different root without monkey-patching `os.homedir`.\n\nexport function getClaudeSkillsRoot(): string {\n const override = process.env.BREVO_CLAUDE_HOME;\n if (override?.trim()) {\n return path.join(override, 'skills');\n }\n return path.join(os.homedir(), '.claude', 'skills');\n}\n\nfunction getSkillTargetDir(name: string): string {\n return path.join(getClaudeSkillsRoot(), name);\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Install marker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A `.brevo-skill.json` file inside each installed skill records the version\n// and source. It lets the auto-refresh pass decide whether a refresh is needed\n// and lets `brevo skill:cli uninstall` confirm we only delete directories the CLI\n// created \u2014 never something the user dropped in `~/.claude/skills/` manually.\n\nconst MARKER_FILE = '.brevo-skill.json';\n\ninterface SkillMarker {\n name: string;\n version: string;\n installedAt: string;\n source: 'brevo-cli';\n}\n\nfunction readMarker(skillDir: string): SkillMarker | null {\n try {\n const raw = JSON.parse(fs.readFileSync(path.join(skillDir, MARKER_FILE), 'utf-8'));\n if (\n raw &&\n typeof raw === 'object' &&\n typeof raw.name === 'string' &&\n typeof raw.version === 'string' &&\n raw.source === 'brevo-cli'\n ) {\n return {\n name: raw.name,\n version: raw.version,\n installedAt: typeof raw.installedAt === 'string' ? raw.installedAt : '',\n source: 'brevo-cli',\n };\n }\n return null;\n } catch {\n return null;\n }\n}\n\nfunction writeMarker(skillDir: string, entry: SkillEntry): void {\n const marker: SkillMarker = {\n name: entry.name,\n version: entry.version,\n installedAt: new Date().toISOString(),\n source: 'brevo-cli',\n };\n fs.writeFileSync(\n path.join(skillDir, MARKER_FILE),\n JSON.stringify(marker, null, 2) + '\\n',\n 'utf-8',\n );\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 File copy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction copySkillFiles(entry: SkillEntry, targetDir: string): void {\n fs.mkdirSync(targetDir, { recursive: true });\n for (const relative of entry.files) {\n const src = path.join(SKILLS_BUNDLE_DIR, entry.subdir, relative);\n const dest = path.join(targetDir, relative);\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.copyFileSync(src, dest);\n }\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type InstallStatus = 'installed' | 'already-installed' | 'overwritten';\n\nexport interface InstallResult {\n name: string;\n version: string;\n status: InstallStatus;\n path: string;\n}\n\nexport interface UninstallResult {\n name: string;\n path: string;\n}\n\nexport interface OutdatedSkill {\n name: string;\n installedVersion: string;\n latestVersion: string;\n}\n\nexport const skillService = {\n /**\n * Installed skills whose marker version is behind the bundled catalog.\n * Pure file read \u2014 safe to call on every CLI invocation for auto-refresh.\n */\n getOutdatedSkills(): OutdatedSkill[] {\n return SKILL_CATALOG.flatMap((entry) => {\n const marker = readMarker(getSkillTargetDir(entry.name));\n if (!marker) return [];\n if (marker.version === entry.version) return [];\n return [\n {\n name: entry.name,\n installedVersion: marker.version,\n latestVersion: entry.version,\n },\n ];\n });\n },\n\n /** True when the named skill is installed (marker present). */\n isInstalled(name: string): boolean {\n const entry = getSkill(name);\n if (!entry) return false;\n return readMarker(getSkillTargetDir(entry.name)) !== null;\n },\n\n install(name: string, options: { force?: boolean } = {}): InstallResult {\n const entry = getSkill(name);\n if (!entry) {\n throw new CliError(unknownSkillMessage(name));\n }\n\n const targetDir = getSkillTargetDir(entry.name);\n const marker = readMarker(targetDir);\n\n if (marker && !options.force) {\n return {\n name: entry.name,\n version: marker.version,\n status: 'already-installed',\n path: targetDir,\n };\n }\n\n copySkillFiles(entry, targetDir);\n writeMarker(targetDir, entry);\n\n return {\n name: entry.name,\n version: entry.version,\n status: marker ? 'overwritten' : 'installed',\n path: targetDir,\n };\n },\n\n installAll(options: { force?: boolean } = {}): InstallResult[] {\n return SKILL_CATALOG.map((entry) => this.install(entry.name, options));\n },\n\n uninstallAll(): UninstallResult[] {\n return SKILL_CATALOG.flatMap((entry) => {\n const targetDir = getSkillTargetDir(entry.name);\n if (!readMarker(targetDir)) return [];\n fs.rmSync(targetDir, { recursive: true, force: true });\n return [{ name: entry.name, path: targetDir }];\n });\n },\n\n /**\n * Refresh installed skills whose marker version drifts from the bundled\n * catalog. Pure local file ops \u2014 safe to call after every CLI invocation.\n * Errors during the refresh emit a single error line and never throw,\n * so a broken refresh can't block the user's actual command.\n */\n autoRefreshOutdated(opts: AutoRefreshOptions = {}): void {\n if (shouldSkipAutoRefresh(opts)) return;\n\n let outdated: OutdatedSkill[];\n try {\n outdated = this.getOutdatedSkills();\n } catch {\n return;\n }\n if (outdated.length === 0) return;\n\n const output = opts.output ?? process.stderr;\n for (const s of outdated) {\n try {\n this.install(s.name, { force: true });\n output.write(\n ` ${messages.SKILL_AUTOREFRESHED(s.name, s.installedVersion, s.latestVersion)}\\n`,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n output.write(` ${messages.SKILL_AUTOREFRESH_FAILED(s.name, msg)}\\n`);\n }\n }\n },\n};\n\nexport type SkillService = typeof skillService;\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Auto-refresh skip rules \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Exported as a free function for direct unit-testing without instantiating\n// the whole service. Mirrors update-notifier's `shouldSkipCheck` shape.\n\nexport interface AutoRefreshOptions {\n argv?: readonly string[];\n env?: NodeJS.ProcessEnv;\n output?: NodeJS.WriteStream;\n}\n\nexport function shouldSkipAutoRefresh(opts: AutoRefreshOptions = {}): boolean {\n const env = opts.env ?? process.env;\n const argv = opts.argv ?? process.argv;\n\n // CI runners shouldn't have surprise mutations to ~/.claude.\n if (env.CI === 'true' || env.CI === '1') return true;\n // --json callers want deterministic, machine-readable output.\n if (argv.includes('--json')) return true;\n // The user is already managing skills \u2014 let their explicit command do the work.\n if (argv.length > 2 && argv[2] === 'skill:cli') return true;\n // Dedicated opt-out for users who hand-edit their installed SKILL.md and\n // don't want it clobbered on the next `brevo` run.\n if (env.BREVO_NO_SKILL_AUTOREFRESH === '1' || env.BREVO_NO_SKILL_AUTOREFRESH === 'true') {\n return true;\n }\n return false;\n}\n\nfunction unknownSkillMessage(name: string): string {\n const available = SKILL_CATALOG.map((s) => s.name).join(', ');\n return `Unknown skill \"${name}\". Available: ${available}`;\n}\n", "import * as path from 'node:path';\n\nimport { CLI_VERSION } from '../lib/cli-version';\nexport { CLI_VERSION };\n\n/**\n * Catalog of Brevo-authored agent skills installable via `brevo skill:cli install`.\n *\n * Source content lives under `agent-context/` at the repo root \u2014 the same\n * directory the README documents for manual install (`cp .../agent-context/\n * SKILL.md ...`). Keeping a single source avoids drift between the\n * programmatic install path and the documented manual path.\n *\n * **Skill version is the CLI version.** Every published CLI release carries\n * its own bundled `agent-context/`, so installed skills auto-refresh after a\n * CLI upgrade even if `SKILL.md` itself didn't change. This eliminates the\n * \"forgot to bump skill version when editing SKILL.md\" failure mode.\n *\n * To add a new skill:\n * 1. Drop SKILL.md (+ any supporting files) under `agent-context/<subdir>/`,\n * or directly under `agent-context/` if it shares the brevo-cli layout.\n * 2. Add an entry below with `subdir` pointing at the right directory.\n */\n\nexport interface SkillEntry {\n /** Stable, kebab-case identifier for the skill catalog entry. */\n name: string;\n /** Short one-line summary. */\n description: string;\n /** Tracks the CLI version (see `CLI_VERSION` below). */\n version: string;\n /**\n * Subdirectory under `SKILLS_BUNDLE_DIR` that holds this skill's files.\n * `''` means files live directly in the bundle root (used by brevo-cli,\n * which shares `agent-context/` with the manually-installable copy).\n */\n subdir: string;\n /** Files to copy, relative to `SKILLS_BUNDLE_DIR/<subdir>/`. */\n files: string[];\n}\n\nexport const SKILL_CATALOG: readonly SkillEntry[] = [\n {\n name: 'brevo-cli',\n description:\n 'Agent primer for the Brevo Developer CLI \u2014 decision tree, hard rules, and command map for the `brevo` binary.',\n version: CLI_VERSION,\n subdir: '',\n files: ['SKILL.md'],\n },\n] as const;\n\n/**\n * Absolute path to the bundled skill source directory. Resolved relative to\n * this file so the same code works under ts-jest (`src/skills/`), built dev\n * builds (`dist/skills/`), and the published tarball\n * (`node_modules/@getbrevo/cli/dist/skills/`). The `agent-context/` directory\n * is shipped at the package root via `package.json` `files:`.\n */\nexport const SKILLS_BUNDLE_DIR = path.resolve(__dirname, '..', '..', 'agent-context');\n\nexport function getSkill(name: string): SkillEntry | undefined {\n return SKILL_CATALOG.find((s) => s.name === name);\n}\n", "import { logSuccess, logInfo } from '../../lib/logger';\nimport { messages } from '../../lang/en';\nimport { withCommandHandler } from '../../lib/command-handler';\nimport { jsonOutput } from '../../lib/json-output';\nimport { skillService } from '../../services/skill';\n\nexport const installCommand = withCommandHandler(\n async (options: { json?: boolean }): Promise<void> => {\n const results = skillService.installAll();\n\n if (options.json) {\n jsonOutput(results);\n return;\n }\n\n let installedFresh = false;\n for (const r of results) {\n if (r.status === 'already-installed') {\n logInfo(`\\n ${messages.SKILL_INSTALL_ALREADY(r.name, r.version)}`);\n } else {\n logSuccess(messages.SKILL_INSTALL_SUCCESS(r.name, r.version, r.path));\n installedFresh = true;\n }\n }\n\n if (installedFresh) {\n logInfo(`\\n ${messages.SKILL_INSTALL_CLAUDE_ONLY}`);\n }\n },\n);\n", "import { logSuccess, logInfo } from '../../lib/logger';\nimport { messages } from '../../lang/en';\nimport { withCommandHandler } from '../../lib/command-handler';\nimport { jsonOutput } from '../../lib/json-output';\nimport { skillService } from '../../services/skill';\n\nexport const uninstallCommand = withCommandHandler(\n async (options: { json?: boolean }): Promise<void> => {\n const results = skillService.uninstallAll();\n\n if (options.json) {\n jsonOutput(results.map((r) => ({ uninstalled: true, ...r })));\n return;\n }\n\n if (results.length === 0) {\n logInfo(`\\n ${messages.SKILL_UNINSTALL_NONE}`);\n return;\n }\n\n for (const r of results) {\n logSuccess(messages.SKILL_UNINSTALL_SUCCESS(r.name, r.path));\n }\n },\n);\n", "import { CommandDefinition, SubcommandGroupDefinition } from '../lib/command-registry';\nimport { parseAppId, parsePositiveInt, collectUrls, validateUrl } from '../lib/validators';\nimport { isFeatureAvailable } from '../lib/preview';\nimport { createDescription, distributionValues } from '../lib/help';\n// The gated subcommands are referenced only through this binding, and only from behind\n// `__BREVO_PREVIEW__`. That is what lets esbuild drop them \u2014 and their five handler\n// modules \u2014 from a published build. Importing any of those handlers directly here would\n// make them live references again and ship the whole surface. See ./preview-definitions.ts.\nimport { previewAppCommands } from './preview-definitions';\n\nimport { initCommand } from './init';\nimport { loginCommand } from './login';\nimport { logoutCommand } from './logout';\nimport { whoamiCommand } from './whoami';\nimport { createCommand } from './app/create';\nimport { listCommand } from './app/list';\nimport { credentialsCommand } from './app/credentials';\nimport { uploadCommand } from './app/upload';\nimport { deleteCommand } from './app/delete';\nimport { scaffoldCommand } from './app/scaffold';\nimport { scopesCommand } from './app/scopes';\nimport { startCommand } from './app/start';\nimport { installCommand as skillInstallCommand } from './skill/install';\nimport { uninstallCommand as skillUninstallCommand } from './skill/uninstall';\n\nexport const topLevelCommands: CommandDefinition[] = [\n {\n name: 'login',\n description: 'Authenticate with your Brevo account',\n options: [\n { flags: '--browser', description: 'Force browser-based login' },\n { flags: '--json', description: 'Output as JSON' },\n ],\n examples: ['brevo login', 'brevo login --browser', 'BREVO_API_KEY=xkeysib-... brevo login'],\n handler: (opts) =>\n loginCommand({\n browser: Boolean(opts.browser),\n json: Boolean(opts.json),\n }),\n },\n {\n name: 'logout',\n description: 'Clear stored credentials',\n options: [\n { flags: '--force', description: 'Skip confirmation (for CI)' },\n { flags: '--json', description: 'Output as JSON' },\n ],\n handler: (opts) => logoutCommand({ force: Boolean(opts.force), json: Boolean(opts.json) }),\n },\n {\n name: 'whoami',\n description: 'Show current authenticated user',\n options: [{ flags: '--json', description: 'Output as JSON' }],\n handler: (opts) => whoamiCommand({ json: Boolean(opts.json) }),\n },\n];\n\nexport const appCommandGroup: SubcommandGroupDefinition = {\n name: 'app',\n description: 'Manage OAuth applications',\n commands: [\n {\n name: 'init',\n description: 'Quick setup \u2014 login, create app, and scaffold in one go',\n examples: ['brevo app init'],\n handler: () => initCommand({}),\n },\n {\n name: 'create',\n description: createDescription(),\n // The `--distribution public` example is filtered out while public distribution\n // is pre-GA (BEX-405) \u2014 `brevo app create --help` must not advertise a value the\n // command will refuse. Filtered from the same table the refusal reads, so GA\n // restores it without an edit here.\n examples: [\n 'brevo app create',\n 'brevo app create --name \"My App\" --distribution private',\n ...(isFeatureAvailable('public-distribution')\n ? ['brevo app create --name \"My App\" --distribution public']\n : []),\n 'brevo app create --name \"My App\" --distribution private --redirect-uri http://localhost:3009/auth/callback',\n 'brevo app create --name \"My App\" --distribution private --redirect-uri http://localhost:3009/auth/callback --redirect-uri https://myapp.com/callback --json',\n 'brevo app create --name \"My App\" --distribution private --logo-uri https://example.com/logo.png',\n ],\n // A UI app is authored entirely through the interactive prompts (BEX-290) \u2014\n // there is deliberately no `--type` or per-field flag. Every flag below\n // applies to an OAuth app, which is what a non-interactive run always\n // creates.\n options: [\n { flags: '--name <name>', description: 'App name' },\n {\n flags: '--distribution <type>',\n description: `Distribution type (${distributionValues()})`,\n },\n {\n flags: '--redirect-uri <url>',\n description: 'Redirect URI (repeatable, OAuth apps only)',\n parser: collectUrls,\n },\n {\n flags: '--logo-uri <url>',\n description: 'App logo URL (http or https)',\n parser: (v: string) => {\n validateUrl(v, 'logo URL');\n return v;\n },\n },\n { flags: '--json', description: 'Output as JSON' },\n ],\n handler: (opts) =>\n createCommand({\n name: opts.name as string | undefined,\n distribution: opts.distribution as string | undefined,\n redirectUri: opts.redirectUri as string[] | undefined,\n logoUri: opts.logoUri as string | undefined,\n json: Boolean(opts.json),\n }),\n },\n {\n name: 'list',\n description: 'List all apps in your account',\n examples: ['brevo app list', 'brevo app list --json'],\n options: [{ flags: '--json', description: 'Output as JSON' }],\n handler: (opts) => listCommand({ json: Boolean(opts.json) }),\n },\n {\n name: 'credentials',\n description: 'Show client ID and secret for an app',\n examples: [\n 'brevo app credentials --app-id 42',\n 'brevo app credentials --app-id 42 --reveal-secret --json',\n ],\n options: [\n {\n flags: '--app-id <id>',\n description: 'App ID',\n parser: (v) => parseAppId(v),\n },\n { flags: '--reveal-secret', description: 'Show the client secret' },\n { flags: '--json', description: 'Output as JSON' },\n ],\n handler: (opts) =>\n credentialsCommand({\n appId: opts.appId as string | undefined,\n revealSecret: Boolean(opts.revealSecret),\n json: Boolean(opts.json),\n }),\n },\n {\n name: 'upload',\n description: 'Push app-config.json to Brevo, validated and synced with the server',\n examples: ['brevo app upload', 'brevo app upload --yes', 'brevo app upload --json'],\n options: [\n { flags: '--yes', description: 'Skip confirmation prompt' },\n { flags: '--json', description: 'Output as JSON' },\n ],\n handler: (opts) =>\n uploadCommand({\n yes: Boolean(opts.yes),\n json: Boolean(opts.json),\n }),\n },\n {\n name: 'delete',\n description: 'Delete an app',\n examples: ['brevo app delete --app-id 42', 'brevo app delete --app-id 42 --force'],\n options: [\n {\n flags: '--app-id <id>',\n description: 'App ID',\n parser: (v) => parseAppId(v),\n },\n { flags: '--force', description: 'Skip confirmation (for CI)' },\n { flags: '--json', description: 'Output as JSON' },\n ],\n handler: (opts) =>\n deleteCommand({\n appId: opts.appId as string | undefined,\n force: Boolean(opts.force),\n json: Boolean(opts.json),\n }),\n },\n {\n name: 'scaffold',\n // Two modes, selected by whether cwd holds an app-config.json: inside a project it\n // adds a feature to the linked app; in a directory with none it sets that directory\n // up for an app that already exists \u2014 picked interactively, or named by --app-id\n // when there is no terminal to prompt on.\n description:\n 'Add a feature to the app in this directory, or set an empty directory up for an existing app',\n examples: [\n 'brevo app scaffold',\n 'brevo app scaffold --app-id 42',\n 'brevo app scaffold --overwrite',\n 'brevo app scaffold --json',\n 'brevo app scaffold --app-id 42 --json',\n ],\n options: [\n {\n flags: '--app-id <id>',\n description: 'Set an empty directory up for an app you already have',\n parser: (v) => parseAppId(v),\n },\n {\n flags: '--overwrite',\n description: 'Overwrite existing feature files instead of merging (skips the prompt)',\n },\n { flags: '--json', description: 'Output as JSON' },\n ],\n handler: (opts) =>\n scaffoldCommand({\n appId: opts.appId as string | undefined,\n json: Boolean(opts.json),\n overwrite: Boolean(opts.overwrite),\n }),\n },\n {\n name: 'available-scopes',\n description: 'List OAuth scopes supported by the IdP',\n examples: [\n 'brevo app available-scopes',\n 'brevo app available-scopes --web',\n 'brevo app available-scopes --json',\n ],\n options: [\n { flags: '--json', description: 'Output as JSON' },\n { flags: '--web', description: 'Open the scope catalog in a local browser page' },\n ],\n handler: (opts) => scopesCommand({ json: Boolean(opts.json), web: Boolean(opts.web) }),\n },\n {\n name: 'start',\n description: 'Run a scaffolded feature locally',\n arguments: [{ name: '[feature]', description: 'Feature to start (e.g. oauth)' }],\n examples: ['brevo app start oauth', 'brevo app start oauth --port 3000'],\n options: [\n {\n flags: '--port <port>',\n description: 'Server port (default: 3009)',\n parser: (v) => parsePositiveInt(v, '--port'),\n },\n ],\n handler: (opts, feature) =>\n startCommand({\n feature: feature as string | undefined,\n port: opts.port as number | undefined,\n }),\n },\n // ELIMINATION SITE \u2014 the raw global rather than `isFeatureAvailable()` on purpose:\n // esbuild substitutes the global here, folds the ternary to `[]`, and can then drop\n // `previewAppCommands` and the five handler modules only it imports. Importing the\n // constant instead leaves a runtime ternary and ships the whole gated surface. See\n // src/globals.d.ts.\n //\n // Appended, not interleaved, so the spread is one foldable expression. Ordering in\n // `brevo app --help` is unaffected in a public build (there is nothing to order);\n // a preview build simply lists these five last.\n ...(__BREVO_PREVIEW__ ? previewAppCommands : []),\n ],\n};\n\nexport const skillCommandGroup: SubcommandGroupDefinition = {\n name: 'skill:cli',\n description: 'Install the brevo-cli Claude Code skill (Claude only)',\n commands: [\n {\n name: 'install',\n description:\n 'Install the brevo-cli skill into ~/.claude/skills/ (Claude only \u2014 other AI tools should read agent-context/AGENTS.md instead)',\n examples: ['brevo skill:cli install', 'brevo skill:cli install --json'],\n options: [{ flags: '--json', description: 'Output as JSON' }],\n handler: (opts) => skillInstallCommand({ json: Boolean(opts.json) }),\n },\n {\n name: 'uninstall',\n description: 'Remove the brevo-cli skill from ~/.claude/skills/ (Claude only)',\n examples: ['brevo skill:cli uninstall', 'brevo skill:cli uninstall --json'],\n options: [{ flags: '--json', description: 'Output as JSON' }],\n handler: (opts) => skillUninstallCommand({ json: Boolean(opts.json) }),\n },\n ],\n};\n", "import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { messages } from '../lang/en';\nimport { color, COLOR_RED } from './logger';\n\nconst REGISTRY_URL = (name: string): string =>\n `https://registry.npmjs.org/${encodeURIComponent(name).replace('%40', '@')}/latest`;\n\nconst TTL_MS = 12 * 60 * 60 * 1000;\nconst FETCH_TIMEOUT_MS = 2000;\nconst NOTIFY_WAIT_MS = 1500;\n\nconst CACHE_FILE = 'update-check.json';\n\nexport interface PkgInfo {\n name: string;\n version: string;\n}\n\nexport interface UpdateCheckCache {\n latest: string;\n lastChecked: number;\n}\n\nexport interface UpdateNotifierOptions {\n pkg: PkgInfo;\n argv?: readonly string[];\n env?: NodeJS.ProcessEnv;\n isTTY?: boolean;\n cachePath?: string;\n fetchImpl?: typeof fetch;\n now?: () => number;\n ttlMs?: number;\n fetchTimeoutMs?: number;\n}\n\nfunction getCachePath(override?: string, env: NodeJS.ProcessEnv = process.env): string {\n if (override) return override;\n const dir = env.BREVO_CONFIG_HOME || path.join(os.homedir(), '.brevo');\n return path.join(dir, CACHE_FILE);\n}\n\n// True when the banner must print before parseAsync runs, either because\n// Commander exits synchronously (bare `brevo`, --help, --version) and would\n// bypass the post-run notify, or because the command starts a long interactive\n// flow where users should see the upgrade up front.\nexport function shouldShowBannerBefore(argv: readonly string[]): boolean {\n const args = argv.slice(2);\n if (args.length === 0) return true;\n if (args.includes('--help') || args.includes('-h')) return true;\n if (args.includes('--version') || args.includes('-V')) return true;\n return args[0] === 'app' && (args[1] === 'init' || args[1] === 'create');\n}\n\nexport function shouldSkipCheck(opts: UpdateNotifierOptions): boolean {\n const env = opts.env ?? process.env;\n const argv = opts.argv ?? process.argv;\n const isTTY = opts.isTTY ?? Boolean(process.stdout.isTTY);\n\n if (env.CI === 'true' || env.CI === '1') return true;\n if (!isTTY) return true;\n if (env.NO_UPDATE_NOTIFIER === '1' || env.NO_UPDATE_NOTIFIER === 'true') return true;\n if (env.BREVO_NO_UPDATE_NOTIFIER === '1' || env.BREVO_NO_UPDATE_NOTIFIER === 'true') return true;\n if (argv.includes('--no-update-notifier')) return true;\n\n return false;\n}\n\ninterface ParsedVersion {\n major: number;\n minor: number;\n patch: number;\n prerelease: string;\n}\n\nfunction parseVersion(v: string): ParsedVersion | undefined {\n const match = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v.trim());\n if (!match?.[1] || !match?.[2] || !match?.[3]) return undefined;\n return {\n major: Number.parseInt(match[1], 10),\n minor: Number.parseInt(match[2], 10),\n patch: Number.parseInt(match[3], 10),\n prerelease: match[4] ?? '',\n };\n}\n\n// Numeric identifiers are compared numerically and always rank below\n// non-numeric ones (semver \u00A711.4).\nfunction comparePrereleaseIdentifiers(ai: string, bi: string): number {\n if (ai === bi) return 0;\n const aNum = /^\\d+$/.test(ai);\n const bNum = /^\\d+$/.test(bi);\n if (aNum && bNum) {\n const diff = Number.parseInt(ai, 10) - Number.parseInt(bi, 10);\n if (diff === 0) return 0;\n return diff > 0 ? 1 : -1;\n }\n if (aNum) return -1;\n if (bNum) return 1;\n return ai > bi ? 1 : -1;\n}\n\n// Per semver \u00A711.4: split on '.', compare identifiers; a longer prerelease\n// set outranks a shorter one when the leading identifiers match.\nfunction comparePrerelease(a: string, b: string): number {\n if (a === b) return 0;\n const aParts = a.split('.');\n const bParts = b.split('.');\n const len = Math.min(aParts.length, bParts.length);\n for (let i = 0; i < len; i++) {\n const cmp = comparePrereleaseIdentifiers(aParts[i] ?? '', bParts[i] ?? '');\n if (cmp !== 0) return cmp;\n }\n if (aParts.length === bParts.length) return 0;\n return aParts.length > bParts.length ? 1 : -1;\n}\n\nexport function compareVersions(current: string, latest: string): number {\n const c = parseVersion(current);\n const l = parseVersion(latest);\n if (!c || !l) return 0;\n if (l.major !== c.major) return l.major - c.major;\n if (l.minor !== c.minor) return l.minor - c.minor;\n if (l.patch !== c.patch) return l.patch - c.patch;\n if (c.prerelease && !l.prerelease) return 1;\n if (!c.prerelease && l.prerelease) return -1;\n return comparePrerelease(l.prerelease, c.prerelease);\n}\n\nexport function isNewer(current: string, latest: string): boolean {\n return compareVersions(current, latest) > 0;\n}\n\n// True when `latest` is at least one full major version ahead of `current`.\n// Used to gate the blocking force-update banner \u2014 a new major release is the\n// signal that the installed CLI may no longer be supported by the backend.\nexport function isMajorBehind(current: string, latest: string): boolean {\n const c = parseVersion(current);\n const l = parseVersion(latest);\n if (!c || !l) return false;\n return l.major > c.major;\n}\n\nexport function readCache(cachePath: string): UpdateCheckCache | undefined {\n try {\n const raw = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));\n if (\n raw &&\n typeof raw === 'object' &&\n typeof raw.latest === 'string' &&\n typeof raw.lastChecked === 'number' &&\n Number.isFinite(raw.lastChecked)\n ) {\n return { latest: raw.latest, lastChecked: raw.lastChecked };\n }\n } catch {\n // missing or corrupt \u2014 caller treats as no cache\n }\n return undefined;\n}\n\nexport function writeCache(cachePath: string, cache: UpdateCheckCache): void {\n try {\n fs.mkdirSync(path.dirname(cachePath), { recursive: true, mode: 0o700 });\n fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf-8');\n } catch {\n // non-fatal \u2014 banner still works from in-memory value\n }\n}\n\nexport async function fetchLatestVersion(\n name: string,\n opts?: UpdateNotifierOptions,\n): Promise<string | undefined> {\n const fetchImpl = opts?.fetchImpl ?? fetch;\n const timeoutMs = opts?.fetchTimeoutMs ?? FETCH_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetchImpl(REGISTRY_URL(name), {\n signal: controller.signal,\n headers: { Accept: 'application/json' },\n });\n if (!res.ok) return undefined;\n const json = (await res.json()) as { version?: unknown };\n return typeof json.version === 'string' ? json.version : undefined;\n } catch {\n return undefined;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Renders the lines into a bordered box, auto-sized to the longest line.\nfunction renderBox(lines: string[]): string {\n const inner = Math.max(...lines.map((l) => l.length)) + 4;\n const top = '\u256D' + '\u2500'.repeat(inner) + '\u256E';\n const bot = '\u2570' + '\u2500'.repeat(inner) + '\u256F';\n const pad = (s: string): string => ' ' + s + ' '.repeat(inner - s.length - 2);\n return ['', ` ${top}`, ...lines.map((l) => ` \u2502${pad(l)}\u2502`), ` ${bot}`, ''].join('\\n');\n}\n\n// The notice line sits *outside* the box on purpose. It is the one dynamic\n// line \u2014 server-supplied when /cli/info answered, local wording otherwise \u2014\n// while everything inside the box is fixed, locally-owned text. Keeping the two\n// apart means the box never changes width because of what a server returned.\n//\n// Colour goes through logger.color, so it is dropped under NO_COLOR or when the\n// stream is not a terminal. The message is already sanitized to a single\n// escape-free clamped line, so wrapping it in a colour code cannot let it break\n// out of the sequence.\nfunction withNotice(box: string, serverMessage?: string): string {\n const line = serverMessage ?? messages.CLI_VERSION_NOTICE_FALLBACK;\n return `\\n ${color(COLOR_RED, line)}\\n${box}`;\n}\n\nexport function formatBanner(\n current: string,\n latest: string,\n name: string,\n serverMessage?: string,\n): string {\n return withNotice(\n renderBox([\n messages.UPDATE_AVAILABLE(current, latest),\n messages.UPDATE_RUN(name),\n messages.UPDATE_RUN_YARN(name),\n messages.UPDATE_RUN_BREW,\n ]),\n serverMessage,\n );\n}\n\nexport function formatForceUpdateBanner(\n current: string,\n latest: string,\n name: string,\n serverMessage?: string,\n): string {\n return withNotice(\n renderBox([\n messages.FORCE_UPDATE_REQUIRED(current, latest),\n messages.FORCE_UPDATE_HINT,\n messages.UPDATE_RUN(name),\n messages.UPDATE_RUN_YARN(name),\n messages.UPDATE_RUN_BREW,\n ]),\n serverMessage,\n );\n}\n\n/**\n * Banner for a run the API has blocked. When the npm check knows the latest\n * version this is the ordinary force-update banner; otherwise it drops the\n * version line, since there is no honest value to put in it. Either way every\n * line inside the box is one the CLI already showed.\n */\nexport function formatBlockedBanner(\n current: string,\n latest: string | undefined,\n name: string,\n serverMessage?: string,\n): string {\n if (latest) return formatForceUpdateBanner(current, latest, name, serverMessage);\n return withNotice(\n renderBox([\n messages.FORCE_UPDATE_HINT,\n messages.UPDATE_RUN(name),\n messages.UPDATE_RUN_YARN(name),\n messages.UPDATE_RUN_BREW,\n ]),\n serverMessage,\n );\n}\n\nexport interface UpdateCheckHandle {\n cachedLatest?: string;\n pending: Promise<void>;\n /** Set once a banner has been written, so repeat calls are no-ops. */\n notified?: boolean;\n /**\n * The notice line to render above the box, set by the caller from the API\n * response. Deliberately in-memory only and never persisted: the wording is\n * fetched fresh on every run, so a reworded message reaches users immediately\n * rather than after a cache expires.\n */\n notice?: string;\n}\n\nexport function startUpdateCheck(opts: UpdateNotifierOptions): UpdateCheckHandle {\n if (shouldSkipCheck(opts)) {\n return { pending: Promise.resolve() };\n }\n\n const cachePath = getCachePath(opts.cachePath, opts.env);\n const now = opts.now ? opts.now() : Date.now();\n const ttl = opts.ttlMs ?? TTL_MS;\n const cache = readCache(cachePath);\n\n const stale = !cache || now - cache.lastChecked > ttl;\n if (!stale) {\n return { cachedLatest: cache?.latest, pending: Promise.resolve() };\n }\n\n const handle: UpdateCheckHandle = {\n cachedLatest: cache?.latest,\n pending: Promise.resolve(),\n };\n handle.pending = (async () => {\n const latest = await fetchLatestVersion(opts.pkg.name, opts);\n if (latest) {\n // Prefer the freshly fetched version so first-run users (no cache)\n // and stale-cache users see the banner without waiting another run.\n handle.cachedLatest = latest;\n writeCache(cachePath, { latest, lastChecked: now });\n }\n })();\n\n return handle;\n}\n\n// Idempotent: safe to call from every exit path. The first call that actually\n// writes the banner marks the handle, and later calls become no-ops.\nexport async function notifyUpdate(\n handle: UpdateCheckHandle,\n pkg: PkgInfo,\n output: NodeJS.WriteStream = process.stderr,\n waitMs: number = NOTIFY_WAIT_MS,\n): Promise<void> {\n if (handle.notified) return;\n\n await Promise.race([\n handle.pending,\n new Promise<void>((resolve) => setTimeout(resolve, waitMs).unref?.()),\n ]);\n\n if (handle.cachedLatest && isNewer(pkg.version, handle.cachedLatest)) {\n handle.notified = true;\n output.write(formatBanner(pkg.version, handle.cachedLatest, pkg.name, handle.notice) + '\\n');\n }\n}\n\n// Blocking force-update gate. When the latest npm version is a full major\n// version ahead of the installed one, writes the force-update banner and\n// returns true so the caller can stop before running the command.\n//\n// Fails open: if the version check was skipped (CI / non-TTY / opt-out, in\n// which case the handle has no cachedLatest) or the fetch hasn't resolved\n// within waitMs, returns false so a slow or unreachable npm registry never\n// blocks the user.\nexport async function enforceMinVersion(\n handle: UpdateCheckHandle,\n pkg: PkgInfo,\n output: NodeJS.WriteStream = process.stderr,\n waitMs: number = NOTIFY_WAIT_MS,\n): Promise<boolean> {\n await Promise.race([\n handle.pending,\n new Promise<void>((resolve) => setTimeout(resolve, waitMs).unref?.()),\n ]);\n\n if (handle.cachedLatest && isMajorBehind(pkg.version, handle.cachedLatest)) {\n output.write(\n formatForceUpdateBanner(pkg.version, handle.cachedLatest, pkg.name, handle.notice) + '\\n',\n );\n return true;\n }\n return false;\n}\n", "import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { APP_STORE_BASE, ENDPOINTS } from '../lib/constants';\nimport { sanitizeErrorMessage, looksLikeHtml } from '../api/client';\nimport { CliInfo, CliInfoQuery, CliInfoResponse } from '../types';\n\n// Budget for the whole call. The notice is cosmetic and the banner is already\n// going to be shown, so the user waits at most this long for nicer wording\n// before the local text is used instead.\nconst CLI_INFO_TIMEOUT_MS = 1500;\n\n// `message` is server-supplied text headed for a terminal. Clamping bounds how\n// much screen a hostile or broken backend can take, on top of the control-char\n// stripping below.\nconst MAX_NOTICE_MESSAGE_LEN = 200;\n\n// The whole response (notice + block verdict) is cached for 15 minutes so a\n// healthy fleet of invocations doesn't hit the app-store service on every\n// single command. Short enough that a reworded notice or a new block reaches\n// users the same working session rather than after the old 12h npm-style TTL.\nconst CLI_INFO_CACHE_TTL_MS = 15 * 60 * 1000;\nconst CLI_INFO_CACHE_FILE = 'cli-info-cache.json';\n\ninterface CliInfoCache {\n cliVersion: string;\n // The service the entry came from. Part of the key, not just a record: the\n // base URL is overridable per-invocation via BREVO_APP_STORE_URL, so without\n // this a run against staging is served the answer production gave a moment\n // earlier (and vice versa) \u2014 which reads as \"staging works, production\n // doesn't\" while curl against both says the opposite.\n baseUrl: string;\n info: CliInfo;\n lastChecked: number;\n}\n\nfunction getCliInfoCachePath(env: NodeJS.ProcessEnv): string {\n const dir = env.BREVO_CONFIG_HOME || path.join(os.homedir(), '.brevo');\n return path.join(dir, CLI_INFO_CACHE_FILE);\n}\n\nfunction readCliInfoCache(cachePath: string): CliInfoCache | undefined {\n try {\n const raw = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));\n if (\n raw &&\n typeof raw === 'object' &&\n typeof raw.cliVersion === 'string' &&\n typeof raw.baseUrl === 'string' &&\n typeof raw.lastChecked === 'number' &&\n Number.isFinite(raw.lastChecked) &&\n raw.info &&\n typeof raw.info === 'object' &&\n typeof raw.info.isBlocked === 'boolean'\n ) {\n return {\n cliVersion: raw.cliVersion,\n baseUrl: raw.baseUrl,\n lastChecked: raw.lastChecked,\n info: {\n isBlocked: raw.info.isBlocked,\n upgradeMessage:\n typeof raw.info.upgradeMessage === 'string' ? raw.info.upgradeMessage : undefined,\n },\n };\n }\n } catch {\n // missing or corrupt \u2014 caller treats as a cache miss\n }\n return undefined;\n}\n\nfunction writeCliInfoCache(cachePath: string, cache: CliInfoCache): void {\n try {\n fs.mkdirSync(path.dirname(cachePath), { recursive: true, mode: 0o700 });\n fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf-8');\n } catch {\n // non-fatal \u2014 the next invocation just re-fetches\n }\n}\n\n/**\n * Make server-supplied notice text safe to print.\n *\n * Three separate concerns, in order: reject anything that looks like a gateway\n * HTML page (an SSO proxy in front of `/cli/info` must never be rendered as a\n * notice), strip ANSI/control sequences that could reposition the cursor or\n * fake a prompt, then flatten to a single clamped line so a payload cannot\n * break out of the notice or scroll the screen.\n *\n * Returns `undefined` when nothing usable survives, which callers treat as\n * \"fall back to local wording\".\n */\nfunction sanitizeNoticeMessage(raw: unknown): string | undefined {\n if (typeof raw !== 'string' || !raw) return undefined;\n if (looksLikeHtml(raw)) return undefined;\n const oneLine = sanitizeErrorMessage(raw).replace(/\\s+/g, ' ').trim();\n if (!oneLine) return undefined;\n return oneLine.length > MAX_NOTICE_MESSAGE_LEN\n ? oneLine.slice(0, MAX_NOTICE_MESSAGE_LEN)\n : oneLine;\n}\n\nexport interface FetchCliInfoOptions {\n baseUrl?: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n // Cache overrides \u2014 tests point these at a scratch directory / clock rather\n // than touching the real ~/.brevo cache or the system clock.\n cachePath?: string;\n env?: NodeJS.ProcessEnv;\n now?: () => number;\n ttlMs?: number;\n}\n\nfunction buildUrl(baseUrl: string, query: CliInfoQuery): string {\n const params = new URLSearchParams({\n cli_version: query.cliVersion,\n reason: query.reason,\n });\n return `${baseUrl}${ENDPOINTS.CLI_INFO}?${params.toString()}`;\n}\n\n/**\n * Fetch the display copy and block verdict for this run, from a 15-minute cache\n * when there is a fresh one.\n *\n * A standalone `fetch` against the app-store service, deliberately not\n * `client.get()`. Two reasons: the endpoint is unauthenticated and lives off the\n * v3 gateway, and routing it through `ApiClient` would attach credentials and\n * feed any 401 into `onAuthFailure` \u2014 which can clear stored credentials and\n * prompt for a new API key. A cosmetic lookup must never be able to log the\n * user out.\n *\n * The cache is keyed to `cliVersion`: a fresh install or upgrade always gets a\n * live check rather than inheriting a verdict computed for a different\n * version, even if that entry hasn't hit its TTL yet. Only a successful\n * response is written back \u2014 a failed call (timeout, non-2xx, malformed body)\n * leaves any existing cache entry alone and returns `undefined` for this run,\n * so an outage never extends the effective staleness window.\n *\n * Fails soft in every direction: timeout, non-2xx, HTML from a gateway,\n * malformed JSON, or an unrecognised `code` all return `undefined`, and the\n * caller falls back to local wording. The banner itself is decided from the npm\n * check, so this can never create or suppress a notice \u2014 only reword one.\n */\nexport async function fetchCliInfo(\n query: CliInfoQuery,\n opts: FetchCliInfoOptions = {},\n): Promise<CliInfo | undefined> {\n const env = opts.env ?? process.env;\n const cachePath = opts.cachePath ?? getCliInfoCachePath(env);\n const now = opts.now ? opts.now() : Date.now();\n const ttlMs = opts.ttlMs ?? CLI_INFO_CACHE_TTL_MS;\n\n const baseUrl = opts.baseUrl ?? APP_STORE_BASE;\n\n const cached = readCliInfoCache(cachePath);\n if (\n cached &&\n cached.cliVersion === query.cliVersion &&\n cached.baseUrl === baseUrl &&\n now - cached.lastChecked <= ttlMs\n ) {\n return cached.info;\n }\n\n const fetchImpl = opts.fetchImpl ?? fetch;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? CLI_INFO_TIMEOUT_MS);\n try {\n const res = await fetchImpl(buildUrl(baseUrl, query), {\n method: 'GET',\n signal: controller.signal,\n headers: { Accept: 'application/json' },\n });\n if (!res.ok) return undefined;\n\n const body = (await res.json()) as CliInfoResponse | null;\n if (!body || typeof body !== 'object') return undefined;\n\n const info: CliInfo = {\n // Nothing about the text is trusted beyond being usable: rejected outright\n // if it looks like HTML, then stripped of control sequences, flattened and\n // clamped. Unusable text yields undefined and the caller falls back to\n // local wording.\n upgradeMessage: sanitizeNoticeMessage(body.upgrade_message),\n // Strict equality, not truthiness: a string \"false\", a 1, or any other\n // stray value must not stop someone working.\n isBlocked: body.is_blocked === true,\n };\n\n writeCliInfoCache(cachePath, { cliVersion: query.cliVersion, baseUrl, info, lastChecked: now });\n\n return info;\n } catch {\n return undefined;\n } finally {\n clearTimeout(timer);\n }\n}\n"],
5
+ "mappings": "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K,OAAO,WAAW,CACjE,CAMA,IAAM,OAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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",
6
+ "names": ["fs", "path", "import_commander", "path", "version", "program", "program", "path", "resolve", "isTTY", "spinner", "fs", "path", "pkg", "path", "client", "client", "inquirer", "program", "import_inquirer", "inquirer", "import_inquirer", "import_inquirer", "fs", "path", "import_inquirer", "resolve", "fs", "path", "import_inquirer", "fs", "path", "inquirer", "import_inquirer", "inquirer", "inquirer", "renderBox", "openBrowser", "resolve", "inquirer", "import_inquirer", "inquirer", "inquirer", "import_inquirer", "inquirer", "import_inquirer", "inquirer", "fs", "path", "import_inquirer", "inquirer", "fs", "path", "os", "import_inquirer", "inquirer", "http", "resolve", "resolve", "fs", "path", "import_node_child_process", "import_inquirer", "inquirer", "resolve", "fs", "path", "os", "path", "relative", "fs", "os", "path", "args", "isTTY", "pkg", "resolve", "fs", "os", "path"]
7
+ }