@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
package/dist/bin/index.js CHANGED
@@ -1,242 +1,667 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || (function () {
20
- var ownKeys = function(o) {
21
- ownKeys = Object.getOwnPropertyNames || function (o) {
22
- var ar = [];
23
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
- return ar;
25
- };
26
- return ownKeys(o);
27
- };
28
- return function (mod) {
29
- if (mod && mod.__esModule) return mod;
30
- var result = {};
31
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
- __setModuleDefault(result, mod);
33
- return result;
34
- };
35
- })();
36
- Object.defineProperty(exports, "__esModule", { value: true });
37
- const fs = __importStar(require("node:fs"));
38
- const path = __importStar(require("node:path"));
39
- const commander_1 = require("commander");
40
- const auth_guard_1 = require("../lib/auth-guard");
41
- const oauth_freshness_1 = require("../lib/oauth-freshness");
42
- const logger_1 = require("../lib/logger");
43
- const exit_codes_1 = require("../lib/exit-codes");
44
- const errors_1 = require("../lib/errors");
45
- const en_1 = require("../lang/en");
46
- const hidden_input_1 = require("../lib/hidden-input");
47
- const config_1 = require("../lib/config");
48
- const constants_1 = require("../lib/constants");
49
- const oauth_refresh_1 = require("../services/oauth-refresh");
50
- const ui_1 = require("../lib/ui");
51
- const container_1 = require("../container");
52
- const command_registry_1 = require("../lib/command-registry");
53
- const definitions_1 = require("../commands/definitions");
54
- const update_notifier_1 = require("../lib/update-notifier");
55
- const skill_1 = require("../services/skill");
56
- const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8'));
57
- const version = pkg.version;
58
- // Version update check — async, non-blocking. Cached at ~/.brevo/update-check.json (24h TTL).
59
- // Skipped in CI, non-TTY, or when --no-update-notifier / BREVO_NO_UPDATE_NOTIFIER=1 is set.
60
- const updateCheck = (0, update_notifier_1.startUpdateCheck)({ pkg, argv: process.argv });
61
- // For long interactive flows (`app init`, `app create`), surface the banner
62
- // before the command runs so the user sees it up front instead of after a
63
- // multi-prompt sequence.
64
- const showBannerEarly = (0, update_notifier_1.shouldShowBannerBefore)(process.argv);
65
- if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0') {
66
- (0, logger_1.logWarn)(en_1.messages.TLS_VERIFICATION_DISABLED);
67
- }
68
- const program = new commander_1.Command();
69
- program
70
- .name('brevo')
71
- .description('Brevo Developer CLI create, manage, and test OAuth integrations')
72
- .version(version)
73
- .option('--debug', 'Enable debug logging')
74
- .configureHelp({
75
- formatHelp: (_cmd, helper) => {
76
- const version = helper.commandDescription(_cmd);
77
- return [
78
- `Usage: brevo [options] [command]`,
79
- ``,
80
- version,
81
- ``,
82
- `Options:`,
83
- ` -V, --version output the version number`,
84
- ` -h, --help display help for command`,
85
- ``,
86
- `Commands:`,
87
- ` brevo login [--browser] [--json] Authenticate with your Brevo account`,
88
- ` brevo logout [--json] Clear stored credentials`,
89
- ` brevo whoami [--json] Show current authenticated user`,
90
- ``,
91
- `App commands:`,
92
- ` brevo app init Quick setup — login, create app, and scaffold`,
93
- ` brevo app create [--name] [--distribution private|public] [--redirect-uri <url>...] [--logo-uri <url>] [--json]`,
94
- ` brevo app list [--json]`,
95
- ` brevo app credentials [--app-id <id>] [--reveal-secret] [--json]`,
96
- ` brevo app update [--app-id <id>] [--name] [--redirect-uri <url>...] [--logo-uri <url>] [--json]`,
97
- ` brevo app delete [--app-id <id>] [--force] [--json]`,
98
- ` brevo app scaffold [--app-id <id>] [--json]`,
99
- ` brevo app start [feature] [--port <port>]`,
100
- ``,
101
- `Skill commands:`,
102
- ` brevo skill:cli install [--json] Install the brevo-cli Claude Code skill`,
103
- ` brevo skill:cli uninstall [--json] Remove the brevo-cli skill`,
104
- ``,
105
- `Scope commands:`,
106
- ` brevo app available-scopes [--web] [--json] List OAuth scopes supported by the IdP`,
107
- ` (--web opens the catalog in a local browser page)`,
108
- ``,
109
- `Run \`brevo <command> --help\` for details on a specific command.`,
110
- ``,
111
- `Examples:`,
112
- ` $ brevo login # authenticate interactively`,
113
- ` $ brevo app init # guided setup`,
114
- ` $ brevo app create --name "My App" --json # create app, JSON output`,
115
- ` $ brevo app list --json # list apps as JSON`,
116
- ` $ brevo app scaffold --app-id APPID # generate starter code`,
117
- ` $ brevo app start oauth --port 3000 # start OAuth test server`,
118
- ` $ brevo app available-scopes --web # browse OAuth scope catalog`,
119
- ``,
120
- `Docs: ${constants_1.BREVO_CLI_REFERENCE_URL}`,
121
- ``,
122
- ].join('\n');
123
- },
124
- })
125
- .action((_options, cmd) => {
126
- const stray = cmd.args;
127
- if (stray.length === 0) {
128
- cmd.outputHelp();
129
- return;
2
+ "use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod));var fs12=__toESM(require("node:fs")),path13=__toESM(require("node:path")),import_commander2=require("commander");var fs=__toESM(require("node:fs")),path=__toESM(require("node:path")),os=__toESM(require("node:os"));var EXIT_CODES={SUCCESS:0,ERROR:1,ABORTED:2,AUTH_FAILURE:3,NETWORK_ERROR:4,NOT_FOUND:5};var CliError=class extends Error{constructor(message,exitCode=EXIT_CODES.ERROR){super(message);this.exitCode=exitCode;this.name="CliError"}exitCode},AbortError=class extends CliError{constructor(){super("Aborted.",EXIT_CODES.ABORTED),this.name="AbortError"}},AuthExpiredError=class extends CliError{constructor(){super("Your session has expired. Run `brevo login` to sign in again.",EXIT_CODES.AUTH_FAILURE),this.name="AuthExpiredError"}};var ApiError=class extends CliError{constructor(message,statusCode,errorCode,apiCode){super(message,statusToExitCode(statusCode));this.statusCode=statusCode;this.errorCode=errorCode;this.apiCode=apiCode;this.name="ApiError"}statusCode;errorCode;apiCode};function statusToExitCode(status){return status===0?EXIT_CODES.NETWORK_ERROR:status===401?EXIT_CODES.AUTH_FAILURE:status===403?EXIT_CODES.ERROR:status===404?EXIT_CODES.NOT_FOUND:EXIT_CODES.ERROR}var strippedUrlSuffix;function getStrippedUrlSuffix(parsed){return`${parsed.pathname==="/"?"":parsed.pathname}${parsed.search}${parsed.hash}`||void 0}function stripPath(parsed){return strippedUrlSuffix=getStrippedUrlSuffix(parsed),parsed.origin}function warnIfPathStripped(){strippedUrlSuffix&&(process.stderr.write(` Warning: BREVO_API_URL path "${strippedUrlSuffix}" was stripped. Endpoints already include paths.
3
+ `),strippedUrlSuffix=void 0)}function isLocalHttpAllowed(parsed){return parsed.protocol==="http:"&&(parsed.hostname==="localhost"||parsed.hostname==="127.0.0.1"||parsed.hostname==="::1")}function resolveApiBase(){let raw=process.env.BREVO_API_URL||"https://api.brevo.com",parsed;try{parsed=new URL(raw)}catch{throw new CliError(`Invalid BREVO_API_URL: "${raw}" is not a valid URL.`)}if(parsed.protocol==="https:"||isLocalHttpAllowed(parsed))return stripPath(parsed);throw new CliError(`BREVO_API_URL must use HTTPS. Got: ${raw}
4
+ HTTP is only allowed for localhost/127.0.0.1.`)}var API_BASE=resolveApiBase();function resolveOauthProxyUrl(){let raw=process.env.BREVO_OAUTH_PROXY_URL||"https://oauth-cli.brevo.com",parsed;try{parsed=new URL(raw)}catch{throw new CliError(`Invalid BREVO_OAUTH_PROXY_URL: "${raw}" is not a valid URL.`)}if(parsed.protocol!=="https:"&&!isLocalHttpAllowed(parsed))throw new CliError(`BREVO_OAUTH_PROXY_URL must use HTTPS. Got: ${raw}
5
+ HTTP is only allowed for localhost/127.0.0.1.`);return parsed.origin}var OAUTH_PROXY_URL=resolveOauthProxyUrl();function resolveAppStoreUrl(){let raw=process.env.BREVO_APP_STORE_URL||"https://app-store-bo-be.brevo.com",parsed;try{parsed=new URL(raw)}catch{throw new CliError(`Invalid BREVO_APP_STORE_URL: "${raw}" is not a valid URL.`)}if(parsed.protocol!=="https:"&&!isLocalHttpAllowed(parsed))throw new CliError(`BREVO_APP_STORE_URL must use HTTPS. Got: ${raw}
6
+ HTTP is only allowed for localhost/127.0.0.1.`);return parsed.origin}var APP_STORE_BASE=resolveAppStoreUrl(),USER_AGENT_HEADER="User-Agent",CLI_AUTH_METHODS={API_KEY:"api_key",OAUTH:"oauth"},ENDPOINTS={ACCOUNT:"/v3/account/info",CORPORATE_SUB_ACCOUNTS:"/v3/corporate/subAccount",APP_STORE_APPS:"/v3/app-store/apps",APP_STORE_APP:appId=>`/v3/app-store/apps/${encodeURIComponent(appId)}`,CLI_INFO:"/cli/info",APP_STATE:appId=>`/v3/app-store/apps/${encodeURIComponent(appId)}/state`,APP_STORE_APP_UPLOAD:appId=>`/v3/app-store/apps/${encodeURIComponent(appId)}/upload`,APP_STORE_APP_WITHDRAW:appId=>`/v3/app-store/apps/${encodeURIComponent(appId)}/withdraw`,APP_STORE_APP_INSTALLS:appId=>`/v3/app-store/apps/${encodeURIComponent(appId)}/installs`,APP_STORE_SURFACE_POINTS:"/v3/app-store/surface-points",APP_STORE_SURFACE_POINT_LOCATIONS:"/v3/app-store/surface-points/locations",OAUTH_AUTHORIZE:"/oauth/authorize",OAUTH_TOKEN:"/oauth/token"},CLI={LOGIN:"brevo login",INIT:"brevo app init",HELP:"brevo --help",APP_CREATE:"brevo app create",APP_LIST:"brevo app list",APP_STATUS:"brevo app status",APP_SCAFFOLD:"brevo app scaffold",APP_SCAFFOLD_APP_ID:appId=>appId?`brevo app scaffold --app-id ${appId}`:"brevo app scaffold --app-id <id>",APP_CREDENTIALS:appId=>appId?`brevo app credentials --app-id ${appId}`:"brevo app credentials --app-id <id>",APP_DELETE_APP_ID:appId=>appId?`brevo app delete --app-id ${appId}`:"brevo app delete --app-id <id>",APP_CREDENTIALS_REVEAL:appId=>appId?`brevo app credentials --reveal-secret --app-id ${appId}`:"brevo app credentials --reveal-secret",APP_UPLOAD:"brevo app upload",APP_DEPLOY:accountId=>accountId?`brevo app deploy ${accountId}`:"brevo app deploy",APP_ROLLBACK:accountId=>accountId?`brevo app rollback ${accountId}`:"brevo app rollback",APP_DELETE:"brevo app delete",APP_WITHDRAW:appId=>appId?`brevo app withdraw --app-id ${appId}`:"brevo app withdraw --app-id <id>",APP_SUBMIT:appId=>appId?`brevo app submit --app-id ${appId}`:"brevo app submit --app-id <id>",APP_START:feature=>feature?`brevo app start ${feature}`:"brevo app start <feature>",APP_SCOPES:"brevo app available-scopes",SKILL_INSTALL:"brevo skill:cli install",SKILL_UNINSTALL:"brevo skill:cli uninstall"};var DEFAULT_PORT=3009,DEFAULT_REDIRECT_URI=`http://localhost:${DEFAULT_PORT}/auth/callback`,PLACEHOLDER_CLIENT_ID="YOUR_CLIENT_ID",OAUTH_BASE="https://oauth.brevo.com",OAUTH_REALM="partner",OAUTH_SCOPES_URL=`${OAUTH_BASE}/realms/${OAUTH_REALM}/scopes`,LEGACY_ALL_SCOPE="all",DEFAULT_SCOPES=["contacts:read","contacts:write","crm:read","crm:write"];var EXTENSION_TYPE_ACTION_LINK="actionLink",EXTENSION_TYPE_IFRAME="iframeExtension";var DEFAULT_LINK_TARGET="_blank",UPLOADABLE_LINK_TARGETS=[DEFAULT_LINK_TARGET],BREVO_DASHBOARD_API_KEYS_URL="https://app.brevo.com/settings/keys/api",BREVO_API_KEY_DOCS_URL="https://developers.brevo.com/docs/api-key-authentication";var BREVO_CLI_REFERENCE_URL="https://developers.brevo.com/docs/cli-reference",BREVO_OAUTH_SCOPES_DOCS_URL="https://developers.brevo.com/docs/oauth-scopes#scope-catalog";var APP_NAME_MAX_LENGTH=48,APP_NAME_REGEX=/^[a-zA-Z0-9 ._\-\u00C0-\u024F]+$/;function validateAppName(name){let trimmed=name.trim();return trimmed.length===0?"App name cannot be empty.":trimmed.length>APP_NAME_MAX_LENGTH?`App name must be at most ${APP_NAME_MAX_LENGTH} characters (got ${trimmed.length}).`:APP_NAME_REGEX.test(trimmed)?!0:"App name can only contain letters, numbers, spaces, hyphens, dots, underscores, and accented characters."}function validateYesNo(input){let val=String(input).toLowerCase().trim();return val==="y"||val==="yes"||val==="n"||val==="no"||val===""?!0:"Please enter y or n"}function validateEnum(value,allowed,flagName){if(value&&!allowed.includes(value))throw new CliError(`Invalid ${flagName} "${value}". Must be one of: ${allowed.join(", ")}.`)}function validateUrl(value,fieldName){if(value){if(/[\s,]/.test(value))throw new CliError(`Invalid ${fieldName}: "${value}" contains whitespace or a comma. Pass each URL with a separate --redirect-uri flag.`);try{let parsed=new URL(value);if(parsed.protocol!=="http:"&&parsed.protocol!=="https:")throw new Error("bad protocol")}catch{throw new CliError(`Invalid ${fieldName}: "${value}" is not a valid HTTP/HTTPS URL.`)}}}function collectUrls(value,previous=[]){return validateUrl(value,"redirect URL"),[...previous,value]}var SCOPE_TOKEN_REGEX=/^[A-Za-z0-9][A-Za-z0-9:_.-]*$/,SCOPE_SPLIT_REGEX=/[\s,]+/;function splitScopes(input){if(input==null)return[];let values=Array.isArray(input)?input:[input],out=[],seen=new Set;for(let v of values)if(typeof v=="string")for(let token of v.split(SCOPE_SPLIT_REGEX))token&&(seen.has(token)||(seen.add(token),out.push(token)));return out}function validateScopes(scopes){for(let scope of scopes)if(!SCOPE_TOKEN_REGEX.test(scope))throw new CliError(`Invalid scope: "${scope}" \u2014 scopes can only contain letters, numbers, ':', '_', '.', '-'.`)}function containsLegacyAllScope(scopes){return scopes?.includes(LEGACY_ALL_SCOPE)??!1}function isSafeUiAppUrl(parsed){return parsed.protocol==="https:"?!0:parsed.protocol==="http:"&&(parsed.hostname==="localhost"||parsed.hostname==="127.0.0.1"||parsed.hostname==="::1")}function validateUiAppUrl(value){let trimmed=value.trim();if(!trimmed)return"URL cannot be empty.";let parsed;try{parsed=new URL(trimmed)}catch{return`Invalid URL: "${trimmed}" is not a valid URL.`}return isSafeUiAppUrl(parsed)?!0:`Invalid URL: "${trimmed}" must use https:// (http:// is allowed only for localhost).`}var UI_APP_LABEL_MAX_LENGTH=48,UI_APP_MORE_INFO_MAX_LENGTH=255;function validateUiAppLabel(value){let trimmed=value.trim();return trimmed?trimmed.length>UI_APP_LABEL_MAX_LENGTH?`Label must be at most ${UI_APP_LABEL_MAX_LENGTH} characters (got ${trimmed.length}).`:!0:"Label cannot be empty."}function validateUiAppMoreInfo(value){let trimmed=value.trim();return trimmed.length>UI_APP_MORE_INFO_MAX_LENGTH?`More info must be at most ${UI_APP_MORE_INFO_MAX_LENGTH} characters (got ${trimmed.length}).`:!0}function validateSurfacePoint(point){return String(point??"").trim()?!0:"Surface point cannot be empty."}var AUTHORABLE_EXTENSION_TYPES=[EXTENSION_TYPE_ACTION_LINK,EXTENSION_TYPE_IFRAME];function validateUiAppContext(fields){let seen=new Set;for(let field of fields){let trimmed=String(field??"").trim();if(!trimmed)return"Context field names cannot be empty.";if(seen.has(trimmed))return`Duplicate context field "${trimmed}".`;seen.add(trimmed)}return!0}function asText(value){return typeof value=="string"?value:typeof value=="number"||typeof value=="boolean"||typeof value=="bigint"?String(value):""}function isPresentField(value){return value===void 0?!1:typeof value!="string"||value.trim()!==""}function validateUiApp(uiApp){if(!uiApp||typeof uiApp!="object")throw new CliError('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".');let block=uiApp,extensionType=asText(block.extension_type);if(!AUTHORABLE_EXTENSION_TYPES.includes(extensionType))throw new CliError(`Unsupported ui_app.extension_type "${extensionType}". Must be one of: ${AUTHORABLE_EXTENSION_TYPES.join(", ")}.`);rejectPreBex290Fields(block),validateSurfacePointList(block.surface_point_list);let labelCheck=validateUiAppLabel(asText(block.label));if(labelCheck!==!0)throw new CliError(`ui_app.label: ${labelCheck}`);let moreInfoCheck=validateUiAppMoreInfo(asText(block.more_info));if(moreInfoCheck!==!0)throw new CliError(`ui_app.more_info: ${moreInfoCheck}`);extensionType===EXTENSION_TYPE_IFRAME?validateIframeExtensionFields(block):validateActionLinkFields(block)}function rejectPreBex290Fields(block){if(block.heading!==void 0)throw new CliError("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.");if(block.subheading!==void 0)throw new CliError("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.");if(block.context!==void 0)throw new CliError('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"] }].')}function validateSurfacePointList(entries){if(!Array.isArray(entries)||entries.length===0)throw new CliError('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.');let names=[];for(let entry of entries){if(!entry||typeof entry!="object"||Array.isArray(entry))throw new CliError('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.');let row=entry,check=validateSurfacePoint(asText(row.surface_point_name));if(check!==!0)throw new CliError(`ui_app.surface_point_list: ${check}`);let name=asText(row.surface_point_name).trim();if(names.push(name),row.context!==void 0){if(!Array.isArray(row.context))throw new CliError(`ui_app.surface_point_list["${name}"].context must be an array of field names, e.g. ["recordId"].`);let contextCheck=validateUiAppContext(row.context.map(asText));if(contextCheck!==!0)throw new CliError(`ui_app.surface_point_list["${name}"].context: ${contextCheck}`)}}if(new Set(names).size!==names.length)throw new CliError("ui_app.surface_point_list contains duplicate extension points.")}function validateActionLinkFields(block){let urlCheck=validateUiAppUrl(asText(block.redirect_link));if(urlCheck!==!0)throw new CliError(`ui_app.redirect_link: ${urlCheck}`);if(block.link_target!==void 0&&!UPLOADABLE_LINK_TARGETS.includes(asText(block.link_target)))throw new CliError(`Invalid ui_app.link_target "${asText(block.link_target)}". Must be one of: ${UPLOADABLE_LINK_TARGETS.join(", ")}.`);if(isPresentField(block.modal_iframe_url))throw new CliError(`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.`)}function validateIframeExtensionFields(block){let urlCheck=validateUiAppUrl(asText(block.modal_iframe_url));if(urlCheck!==!0)throw new CliError(`ui_app.modal_iframe_url: ${urlCheck}`);if(isPresentField(block.redirect_link))throw new CliError(`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.`);if(isPresentField(block.link_target))throw new CliError(`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.`)}function parsePositiveInt(value,flagName){let n=Number.parseInt(value,10);if(!Number.isFinite(n)||n<=0)throw new CliError(`Invalid ${flagName}: "${value}" is not a positive integer.`);return n}function parseAppId(value){let trimmed=value.trim();if(trimmed.length===0)throw new CliError("Invalid --app-id: value cannot be empty.");return trimmed}function isUiAppConfigShape(config){return!!config?.ui_app}function isUiAppRecordShape(app){return app?app.ui_app?!0:!app.client_id&&!app.redirect_uris?.length:!1}function getConfigDir(){return process.env.BREVO_CONFIG_HOME||path.join(os.homedir(),".brevo")}function getCredentialsPath(){return path.join(getConfigDir(),"credentials.json")}function ensureDir(){fs.mkdirSync(getConfigDir(),{recursive:!0,mode:448})}var APP_NAME_CACHE_TTL_MS=600*1e3;function sanitizeAppNames(value){if(!value||typeof value!="object")return;let out={};for(let[key,raw]of Object.entries(value))if(typeof raw=="string"&&raw.trim())out[key]={name:raw,savedAt:0};else if(raw&&typeof raw=="object"){let entry=raw;typeof entry.name=="string"&&entry.name.trim()&&typeof entry.savedAt=="number"&&Number.isFinite(entry.savedAt)&&(out[key]={name:entry.name,savedAt:entry.savedAt})}return Object.keys(out).length>0?out:void 0}function sanitizeApps(apps){let sanitized={};for(let[key,value]of Object.entries(apps))if(value&&typeof value=="object"){let entry=value;typeof entry.clientId=="string"&&typeof entry.clientSecret=="string"&&(sanitized[key]={clientId:entry.clientId,clientSecret:entry.clientSecret})}return sanitized}function readCredentials(){try{let parsed=JSON.parse(fs.readFileSync(getCredentialsPath(),"utf-8"));if(parsed.profiles){let profileName=typeof parsed.activeProfile=="string"&&parsed.activeProfile||"default",firstKey=Object.keys(parsed.profiles)[0],profile=parsed.profiles[profileName]??(firstKey?parsed.profiles[firstKey]:void 0),migrated={auth:typeof profile?.apiKey=="string"&&profile.apiKey?{kind:"api-key",apiKey:profile.apiKey}:void 0,accountEmail:profile?.accountEmail,organizationId:profile?.organizationId,userId:profile?.userId,apps:sanitizeApps(parsed.apps??{})};try{writeCredentials(migrated)}catch{}return migrated}if(!parsed.auth&&typeof parsed.apiKey=="string"&&parsed.apiKey){let migrated={auth:{kind:"api-key",apiKey:parsed.apiKey},accountEmail:parsed.accountEmail,organizationId:parsed.organizationId,userId:parsed.userId,apps:sanitizeApps(parsed.apps??{})};try{writeCredentials(migrated)}catch{}return migrated}return{auth:sanitizeAuth(parsed.auth),accountEmail:parsed.accountEmail,organizationId:parsed.organizationId,userId:parsed.userId,apps:sanitizeApps(parsed.apps??{}),appNames:sanitizeAppNames(parsed.appNames)}}catch{return{apps:{}}}}function sanitizeAuth(raw){if(!raw||typeof raw!="object")return;let v=raw;if(v.kind==="api-key"&&typeof v.apiKey=="string"&&v.apiKey)return{kind:"api-key",apiKey:v.apiKey};if(v.kind==="oauth"&&typeof v.accessToken=="string"&&v.accessToken&&typeof v.refreshToken=="string"&&v.refreshToken&&typeof v.tokenType=="string"&&v.tokenType&&typeof v.expiresAt=="number"&&Number.isFinite(v.expiresAt))return{kind:"oauth",accessToken:v.accessToken,refreshToken:v.refreshToken,expiresAt:v.expiresAt,tokenType:v.tokenType,scope:typeof v.scope=="string"?v.scope:void 0}}function writeCredentials(creds){ensureDir();let filePath=getCredentialsPath();fs.writeFileSync(filePath,JSON.stringify(creds,null,2),{mode:384});try{fs.chmodSync(filePath,384)}catch{}}function getAuthCred(){return process.env.BREVO_API_KEY?{kind:"api-key",apiKey:process.env.BREVO_API_KEY}:readCredentials().auth}function getEmail(){return readCredentials().accountEmail}function getOrganizationId(){return readCredentials().organizationId}function getUserId(){return readCredentials().userId}function saveCredentials(apiKey,account){let creds=readCredentials();creds.auth={kind:"api-key",apiKey},creds.accountEmail=account.email,creds.organizationId=account.organizationId,creds.userId=account.userId,writeCredentials(creds)}function saveOauthCredentials(tokens,account){let creds=readCredentials();creds.auth={kind:"oauth",accessToken:tokens.accessToken,refreshToken:tokens.refreshToken,expiresAt:Date.now()+tokens.expiresIn*1e3,tokenType:tokens.tokenType,scope:tokens.scope},account?(creds.accountEmail=account.email,creds.organizationId=account.organizationId,creds.userId=account.userId):(delete creds.accountEmail,delete creds.organizationId,delete creds.userId),writeCredentials(creds)}function updateOauthTokens(tokens){let creds=readCredentials();creds.auth={kind:"oauth",accessToken:tokens.accessToken,refreshToken:tokens.refreshToken,expiresAt:Date.now()+tokens.expiresIn*1e3,tokenType:tokens.tokenType,scope:tokens.scope},writeCredentials(creds)}function clearCredentials(){let creds=readCredentials();delete creds.auth,delete creds.accountEmail,delete creds.organizationId,delete creds.userId,writeCredentials(creds)}function deleteCredentialsFile(){try{fs.unlinkSync(getCredentialsPath())}catch(error){if(typeof error=="object"&&error!==null&&"code"in error&&error.code==="ENOENT")return;throw error}}function hasAppCredentials(){return Object.keys(readCredentials().apps).length>0}function countAppCredentials(){return Object.keys(readCredentials().apps).length}function isAuthenticated(){return!!getAuthCred()}function saveAppCredentials(appId,cred){let creds=readCredentials();creds.apps[appId]=cred,writeCredentials(creds)}function clearAppsCache(){let creds=readCredentials();creds.apps={},delete creds.appNames,writeCredentials(creds)}function getAppCredentials(appId){return readCredentials().apps[appId]}function deleteAppCredentials(appId){if(!appId)return;let creds=readCredentials();appId in creds.apps&&(delete creds.apps[appId],writeCredentials(creds))}function saveAppName(appId,name){if(!appId||!name)return;let creds=readCredentials();creds.appNames={...creds.appNames,[appId]:{name,savedAt:Date.now()}},writeCredentials(creds)}function getAppNames(){let creds=readCredentials(),cache=creds.appNames??{},now=Date.now(),fresh={},result={},pruned=!1;for(let[id,entry]of Object.entries(cache))now-entry.savedAt<APP_NAME_CACHE_TTL_MS?(fresh[id]=entry,result[id]=entry.name):pruned=!0;if(pruned){creds.appNames=Object.keys(fresh).length>0?fresh:void 0;try{writeCredentials(creds)}catch{}}return result}function deleteAppName(appId){if(!appId)return;let creds=readCredentials();if(!creds.appNames||!(appId in creds.appNames))return;let{[appId]:_removed,...rest}=creds.appNames;creds.appNames=Object.keys(rest).length>0?rest:void 0,writeCredentials(creds)}var PROJECT_CONFIG_FILE="app-config.json";function readProjectConfig(){return readProjectConfigAt(process.cwd())}function readNormalizedAppId(raw){let rawAppId=raw.appId;if(typeof rawAppId=="string")return rawAppId.trim()||void 0;if(typeof rawAppId=="number"&&Number.isFinite(rawAppId))return String(rawAppId)}function buildAuthOverride(rawAuth){if(!rawAuth||typeof rawAuth!="object")return;let auth=rawAuth,override,scopes=auth.scopes;if((Array.isArray(scopes)||typeof scopes=="string")&&(override={...auth,scopes:splitScopes(scopes)}),"redirectUrls"in auth){override=override??{...auth};let legacyRedirects=auth.redirectUrls;!Array.isArray(override.redirectUris)&&Array.isArray(legacyRedirects)&&(override.redirectUris=legacyRedirects),delete override.redirectUrls}return override&&"type"in override?delete override.type:"type"in auth&&(override={...auth},delete override.type),override}function readDistributionType(rawRecord,rawAuth){let newDistributionType=rawRecord.distribution_type;if(typeof newDistributionType=="string"&&newDistributionType.trim())return newDistributionType.trim();let legacyAuthType=rawAuth&&typeof rawAuth=="object"?rawAuth.type:void 0;if(typeof legacyAuthType=="string"&&legacyAuthType.trim()&&legacyAuthType!=="none")return legacyAuthType.trim();let legacyDistribution=rawRecord.distribution;return typeof legacyDistribution=="string"&&legacyDistribution.trim()?legacyDistribution.trim():"private"}function readProjectConfigAt(dir){try{let raw=JSON.parse(fs.readFileSync(path.resolve(dir,PROJECT_CONFIG_FILE),"utf-8"));if(!raw||typeof raw!="object")return null;let rawRecord=raw,appId=readNormalizedAppId(rawRecord);if(!appId)return null;let rawAuth=rawRecord.auth,authOverride=buildAuthOverride(rawAuth),distributionType=readDistributionType(rawRecord,rawAuth),{distribution:_legacyDistribution,permittedUrls:_permittedUrls,support:_support,...rawWithoutLegacyDistribution}=rawRecord,rawUiApp=rawWithoutLegacyDistribution.ui_app;return"ui_app"in rawWithoutLegacyDistribution&&(!rawUiApp||typeof rawUiApp!="object")&&delete rawWithoutLegacyDistribution.ui_app,{...rawWithoutLegacyDistribution,appId,distribution_type:distributionType,...authOverride?{auth:authOverride}:{}}}catch{return null}}function hasLocalApp(){let cfg=readProjectConfig();return cfg?.appId!=null&&cfg.appId!==""}function findEnclosingProjectDir(){let dir=path.dirname(process.cwd());for(;;){if(readProjectConfigAt(dir))return dir;let parent=path.dirname(dir);if(parent===dir)return null;dir=parent}}function isUiAppConfig(config){return isUiAppConfigShape(config)}function writeProjectConfig(config){let configPath=path.resolve(process.cwd(),PROJECT_CONFIG_FILE);fs.writeFileSync(configPath,JSON.stringify(config,null,2)+`
7
+ `,"utf-8")}function isNonEmptyString(value){return typeof value=="string"&&value.trim()!==""}function backfillProjectConfigFromServer(appId,server){let configPath=path.resolve(process.cwd(),PROJECT_CONFIG_FILE),raw;try{raw=JSON.parse(fs.readFileSync(configPath,"utf-8"))}catch{return[]}if(!raw||typeof raw!="object")return[];let normalized=readProjectConfig();if(normalized?.appId!==appId)return[];let rawRecord=raw,backfilled=[],next={...normalized};!isNonEmptyString(rawRecord.version)&&isNonEmptyString(server.version)&&(next.version=server.version,backfilled.push("version"));let rawAuth=rawRecord.auth,legacyAuthType=rawAuth&&typeof rawAuth=="object"?rawAuth.type:void 0;return isNonEmptyString(rawRecord.distribution_type)||isNonEmptyString(rawRecord.distribution)||isNonEmptyString(legacyAuthType)&&legacyAuthType!=="none"||(next.distribution_type=server.distribution_type??normalized.distribution_type,backfilled.push("distribution_type")),backfilled.length===0?[]:(writeProjectConfig(next),backfilled)}function scaffoldFileCount(done,written,total){return written===total?`${done} (${total} files)`:written===0?`already in place (${total} files, nothing rewritten)`:`${done} (${written} of ${total} files written)`}var coreMessages={UPDATE_AVAILABLE:(current,latest)=>`Update available: ${current} \u2192 ${latest}`,UPDATE_RUN:name=>`Run: npm install -g ${name}`,UPDATE_RUN_YARN:name=>`Or: yarn global add ${name}`,UPDATE_RUN_BREW:"Or: brew upgrade brevo",FORCE_UPDATE_REQUIRED:(current,latest)=>`Update required: v${current} is no longer supported (latest v${latest}).`,FORCE_UPDATE_HINT:"Update to continue using the Brevo CLI:",CLI_VERSION_NOTICE_FALLBACK:"A newer version of the Brevo CLI is available.",AUTH_WELCOME:"Welcome to Brevo CLI",AUTH_PROMPT_METHOD:"How would you like to authenticate?",AUTH_PROMPT_API_KEY:"Paste your API key:",AUTH_SUCCESS:email=>`Authenticated as ${email}`,AUTH_INVALID_KEY:"Invalid API key. Please check and try again.",AUTH_HINT:(keysUrl,docsUrl)=>`
8
+ To authenticate, you need a Brevo API key.
9
+ Create one at: ${keysUrl}
10
+ Docs: ${docsUrl}
11
+ `,AUTH_SAVED:path14=>`Credentials saved to ${path14}`,AUTH_NEXT:`Next: ${CLI.APP_CREATE}`,AUTH_CREATE_APP_PROMPT:"Would you like to create an app?",AUTH_NOT_LOGGED_IN:"Not currently authenticated.",AUTH_LOGGED_OUT:"Credentials cleared.",AUTH_LOGGED_OUT_WITH_APPS:count=>`Credentials cleared, including cached credentials for ${count} app${count===1?"":"s"}.`,AUTH_LOGOUT_APP_WARNING:"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.",AUTH_LOGOUT_CONFIRM:"Proceed with logout?",AUTH_EXPIRED:"Your API key is invalid or expired.",AUTH_EXPIRED_PROMPT:"Enter a new API key:",AUTH_GET_KEY_URL:"Create an API key at: https://app.brevo.com/settings/keys/api",AUTH_BROWSER_OPENING:"Opening your browser to log you in...",AUTH_BROWSER_FALLBACK_URL:url=>`If your browser didn't open automatically, open this URL to log in:
12
+ ${url}`,AUTH_BROWSER_WAITING:"Waiting for login to complete (Ctrl+C to cancel)...",AUTH_BROWSER_TOKENS_RECEIVED:path14=>`Login complete. Credentials saved to ${path14}. Verifying account...`,AUTH_BROWSER_TIMEOUT:"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.",AUTH_BROWSER_CANCELLED:"Login cancelled.",AUTH_BROWSER_BAD_PAYLOAD:"Unexpected response from the login service. Please try again.",AUTH_BROWSER_NON_INTERACTIVE:"Browser login needs an interactive terminal. Set BREVO_API_KEY to authenticate non-interactively.",WHOAMI_AUTHENTICATED:(email,company)=>`Authenticated as ${email} (${company})`,WHOAMI_NOT_AUTHENTICATED:`Not authenticated. Run: ${CLI.LOGIN}`,WHOAMI_CREDENTIAL_MISMATCH:fields=>`Local credentials mismatch with API for: ${fields.join(", ")}. Run \`${CLI.LOGIN}\` to re-authenticate.`,PREVIEW_FEATURE_UNAVAILABLE:"That command is not available yet. It is part of a Brevo feature that has not been released.",APP_CREATE_NAME_PROMPT:"App name:",APP_CREATE_TYPE_PROMPT:"Distribution type?",APP_CREATE_APP_TYPE_PROMPT:"What type of app are you building?",APP_CREATE_APP_TYPE_OAUTH:"OAuth app (Authorize against Brevo and call the API on a user\u2019s behalf)",APP_CREATE_APP_TYPE_UI:"UI app (Render inside Brevo \u2014 opens your app from a record)",APP_CREATE_SUCCESS:"App created.",APP_CREATE_NAME_TAKEN:"That name is already taken. Try a different name.",APP_CREATE_SESSION_EXPIRED:"Your session expired while you were answering. Your answers are still here.",APP_CREATE_RELOGIN_CONFIRM:"Log in again and create the app?",APP_CREATE_PUBLIC_REJECTED:serverMessage=>`Public apps can't be created from the CLI yet \u2014 Brevo rejected this request.
13
+
14
+ Do this: re-run with \`--distribution private\`
15
+ Note: \`distribution_type\` is fixed at creation \u2014 \`${CLI.APP_UPLOAD}\` can't change it later
16
+ Brevo said: ${serverMessage}`,APP_CREATE_REDIRECT_PROMPT:"OAuth callback URL \u2014 where users are sent after authorizing your app:",APP_CREATE_REDIRECT_HINT:cmd=>`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.`,APP_CREATE_REDIRECT_ANOTHER:"Add another redirect URL?",APP_CREATE_REDIRECT_EMPTY:"Redirect URL cannot be empty",APP_CREATE_REDIRECT_INVALID:"Invalid format. Must start with http:// or https://",APP_CREATE_LOGO_PROMPT:"App logo URL (optional \u2014 leave blank to skip):",APP_CREATE_LOGO_INVALID:"Invalid format. Must be a valid https:// URL (e.g. https://example.com/logo.png).",APP_CREATE_PORT_IN_USE:(port,available)=>`Port ${port} is in use. Defaulting to port ${available}.`,APP_CREATE_PORT_SCAN_FAILED:port=>`Warning: Could not find a free port near ${port}. Defaulting to ${port} \u2014 it may conflict with a running process.`,APP_CREATE_LIMIT_REACHED:"You have reached the maximum number of OAuth apps allowed for your account. To make room, delete an existing app: brevo app delete",APP_CREATE_BOX_TITLE:"App created",APP_CREATE_BOX_SCOPES_LABEL:"Default scopes:",APP_CREATE_BOX_SCOPE_HINT:`You can add more scopes later by editing \`auth.scopes\` in app-config.json and running \`${CLI.APP_UPLOAD}\`.`,APP_SCAFFOLD_FEATURE_CONFIRM:label=>label?`Scaffold the ${label}?`:"Do you want to scaffold a feature?",APP_CREATE_BASE_SUCCESS:(written,total)=>`Project structure ${scaffoldFileCount("created",written,total)}`,APP_CREATE_BASE_ONLY_NEXT:cdDir=>[...cdDir?[`1. cd ${cdDir}`]:[],`${cdDir?2:1}. ${CLI.APP_SCAFFOLD} (add a feature \u2014 e.g. the OAuth test server)`],APP_CREATE_JSON_SCAFFOLD_DIR_EXISTS:dir=>`Skipped scaffold: directory already exists (${dir}). cd into it and run \`${CLI.APP_SCAFFOLD}\` to add a feature.`,APP_CREATE_DIR_EXISTS_SKIPPED:dir=>`Skipped scaffolding: directory already exists (${dir}). cd into it and run \`${CLI.APP_SCAFFOLD}\` to add a feature.`,APP_CREATE_ALREADY_LINKED:name=>`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.`,APP_CREATE_DIR_UNRESOLVED:"Could not resolve the output directory for scaffolding.",APP_CREATE_UI_NEXT:cdDir=>[...cdDir?[`1. cd ${cdDir}`]:[],`${cdDir?2:1}. ${CLI.APP_UPLOAD} (validate and save your configuration)`,`${cdDir?3:2}. ${CLI.APP_DEPLOY()} (make it available in an account)`],APP_LIST_EMPTY:`No apps found. Create one with: ${CLI.APP_CREATE}`,APP_LIST_HEADER:"Your apps:",APP_TYPE_OAUTH:"OAuth app",APP_TYPE_UI:"UI app",APP_SELECT_NON_INTERACTIVE:command=>`Cannot show the app picker in non-interactive mode. Name the app instead:
17
+
18
+ ${command}
19
+
20
+ \`${CLI.APP_LIST}\` shows the IDs.`,APP_CREDENTIALS_REVEAL_CONFIRM:"Are you sure you want to reveal the client secret?",APP_CREDENTIALS_SELECT:"Select an app:",CLIENT_SECRET_HIDDEN_HUMAN:`[hidden \u2014 run \`${CLI.APP_CREDENTIALS_REVEAL()}\`]`,CLIENT_SECRET_HIDDEN_JSON:"[hidden]",CLIENT_SECRET_NOT_AVAILABLE:"[not available]",APP_CREDENTIALS_CONFIG_BACKFILLED:fields=>`Backfilled ${fields.join(", ")} into app-config.json.`,APP_UPDATE_REMOVED:`\`brevo app update\` has been removed \u2014 use \`${CLI.APP_UPLOAD}\` instead.
21
+
22
+ \`${CLI.APP_UPLOAD}\` pushes the whole of app-config.json and takes only
23
+ --yes and --json. None of the old edit flags exist any more:
24
+
25
+ --name --redirect-uri --scope --logo-uri --app-id
26
+
27
+ To change the app's name, redirect URLs, scopes or logo, edit
28
+ app-config.json, then run:
29
+
30
+ ${CLI.APP_UPLOAD}
31
+
32
+ No app-config.json here? Set up a project for an app you already
33
+ have (\`${CLI.APP_LIST}\` shows their IDs), then edit and upload:
34
+
35
+ ${CLI.APP_SCAFFOLD_APP_ID()}
36
+ ${CLI.APP_UPLOAD}
37
+
38
+ Docs: ${BREVO_CLI_REFERENCE_URL}`,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.`,APP_UPLOAD_INVALID_JSON:`app-config.json contains invalid JSON. Fix the file, or run \`${CLI.APP_SCAFFOLD}\` to regenerate it.`,APP_UPLOAD_MISSING_APP_ID:`app-config.json is missing "appId". Fix the file, or run \`${CLI.APP_SCAFFOLD}\` to regenerate it.`,APP_UPLOAD_NO_REDIRECT_URLS:"app-config.json has no redirect URLs configured.",APP_UPLOAD_INVALID_REDIRECT_URL:url=>`Invalid redirect URL "${url}". Must be a valid http:// or https:// URL.`,APP_UPLOAD_INVALID_REDIRECT_PROTOCOL:url=>`Invalid redirect URL "${url}". Must use http:// or https://.`,APP_UPLOAD_SUMMARY:"Upload summary:",APP_UPLOAD_CONFIRM:"Proceed with upload?",APP_UPLOAD_CANCELLED:"Upload cancelled.",APP_UPLOAD_SUCCESS:"App uploaded.",APP_UPLOAD_UP_TO_DATE:version2=>`Already up to date at version ${version2}.`,APP_UPLOAD_NO_REDIRECT_URLS_OAUTH:"app-config.json has no redirect URLs configured. OAuth apps need at least one \u2014 add it to `auth.redirectUris`.",APP_UPLOAD_UI_APP_SUMMARY:"UI app:",APP_UPLOAD_UI_LINK_TARGET_NOTE:"(added on upload; not a field in app-config.json)",APP_UPLOAD_UI_APP_AUTH_EMPTY_REQUIRED:"This is a UI app (app-config.json has a `ui_app` block), so it uses no OAuth \u2014 set `auth` to `{}`.",APP_UPLOAD_UI_APP_AUTH_HAS_OAUTH_FIELDS:"UI apps don't use OAuth \u2014 remove `scopes` and `redirectUris` from `auth` and keep it empty (`{}`).",APP_DEPLOY_MISSING_CLIENT_ID:`Could not determine your Brevo account's organization ID.
39
+
40
+ Run \`${CLI.LOGIN}\` to re-authenticate.`,APP_UPLOAD_DISTRIBUTION_IMMUTABLE:(current,next)=>`distribution_type cannot be changed via upload \u2014 this app is "${current}" on Brevo, but app-config.json says "${next}".
41
+ Edit \`distribution_type\` in app-config.json back to "${current}", or create a new ${next} app with \`${CLI.APP_CREATE}\`.`,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.
42
+ Replace 'all' with the specific scopes your integration uses (run \`${CLI.APP_SCOPES}\` to see the catalog),
43
+ migrate by editing \`auth.scopes\` and running \`${CLI.APP_UPLOAD}\`, then re-run \`${CLI.APP_START("oauth")}\`.`,LEGACY_ALL_SCOPE_LIST_TAG:" (legacy 'all' \u2014 deprecated)",LEGACY_ALL_SCOPE_SCAFFOLD_SUBSTITUTED:writtenScopes=>`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}\`.`,LEGACY_ALL_SCOPE_UPDATE_MIGRATING:"Migrating from legacy 'all' scope \u2014 'all' will be removed.",LEGACY_ALL_SCOPE_DEPRECATED_BLOCK:`This app currently has the legacy 'all' OAuth scope, which is being deprecated.
44
+ Replace 'all' with the specific scopes your integration uses in app-config.json's \`auth.scopes\`.
45
+ Run \`${CLI.APP_SCOPES}\` to see the catalog, then run \`${CLI.APP_UPLOAD}\` to migrate.`,APP_DELETE_SELECT:"Select an app to delete:",APP_DELETE_CONFIRM:(name,id)=>`Delete app "${name}" (${id})? This cannot be undone.`,APP_DELETE_SUCCESS:id=>`App ${id} deleted.`,APP_DELETE_CANCELLED:"Delete cancelled.",APP_DELETE_FOLDER_CONFIRM:dir=>`Delete the local project folder at ${dir}?`,APP_DELETE_FOLDER_SUCCESS:dir=>`Project folder deleted: ${dir}`,APP_DELETE_FOLDER_FAILED:dir=>`Could not delete folder ${dir}. Remove it manually.`,APP_SCAFFOLD_DIR_PROMPT:"Output directory:",APP_SCAFFOLD_DIR_EXISTS:"Directory already exists. What would you like to do?",APP_SCAFFOLD_FEATURE_TYPE_PROMPT:"What feature do you want to scaffold?",APP_SCAFFOLD_FEATURE_EXISTS:"This feature already has files in this project. What would you like to do?",APP_SCAFFOLD_FEATURE_EXISTS_OVERWRITE:"Overwrite existing files",APP_SCAFFOLD_FEATURE_EXISTS_MERGE:"Merge (keep existing, add missing)",APP_SCAFFOLD_FEATURE_EXISTS_CANCEL:"Cancel",APP_SCAFFOLD_NO_CONFIG:`No app-config.json found in this directory, so there is no app to scaffold a feature into.
46
+
47
+ - cd into an existing project folder and try again, or
48
+ - run \`${CLI.APP_SCAFFOLD_APP_ID()}\` to set this directory up for an app you already have (\`${CLI.APP_LIST}\` shows their IDs), or
49
+ - run \`${CLI.APP_CREATE}\` to create a new app here.`,APP_SCAFFOLD_APP_ID_MISMATCH:(localAppId,requestedAppId)=>`This directory is already linked to app ${localAppId}, so it can't be set up for app ${requestedAppId}.
50
+
51
+ 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.`,APP_SCAFFOLD_TARGET_LINKED_ELSEWHERE:(targetDir,localAppId,requestedAppId)=>`${targetDir} is already a project for app ${localAppId}, so it can't be set up for app ${requestedAppId}.
52
+
53
+ - cd ${targetDir} and run \`${CLI.APP_SCAFFOLD}\` to work on app ${localAppId}, or
54
+ - run \`${CLI.APP_SCAFFOLD}\` again and choose a different directory for app ${requestedAppId}.`,APP_SCAFFOLD_BOOTSTRAP_INTRO:appId=>`Setting up a project for app ${appId}...`,APP_SCAFFOLD_BOOTSTRAP_OFFER:"No app-config.json in this directory, so there is no app to add a feature to.",APP_SCAFFOLD_BOOTSTRAP_CONFIRM:"Set up a project for an app you already have?",APP_SCAFFOLD_BOOTSTRAP_DECLINED:`Nothing to do here yet.
55
+
56
+ - run \`${CLI.APP_CREATE}\` to create a new app in this directory, or
57
+ - cd into an existing project folder and run \`${CLI.APP_SCAFFOLD}\` there.`,APP_SCAFFOLD_SELECT:"Which app do you want to set a project up for?",APP_SCAFFOLD_INSIDE_PROJECT:projectDir=>`This directory is inside the Brevo app project at ${projectDir}, so setting it up as a second project would nest one inside the other.
58
+
59
+ - cd ${projectDir} and run \`${CLI.APP_SCAFFOLD}\` there to work on that app, or
60
+ - cd to a directory outside it to set up a different app.`,APP_SCAFFOLD_BOOTSTRAP_UNRECOVERABLE:appId=>`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.
61
+
62
+ 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.
63
+
64
+ - If you still have this app's project folder, cd into it and run \`${CLI.APP_UPLOAD}\` from there, or
65
+ - run \`${CLI.APP_CREATE}\` and choose "UI app" to author the configuration again.`,APP_SCAFFOLD_DIFF_INTRO:name=>`App "${name}" is linked here, but its local config differs from the server:`,APP_SCAFFOLD_DIFF_LINE:(field,local,server)=>` ${field}: ${local} \u2192 ${server}`,APP_SCAFFOLD_DIFF_CONFIRM:"Scaffolding will update app-config.json to match the server. Continue?",APP_SCAFFOLD_CANCELLED:"Scaffold cancelled.",APP_SCAFFOLD_BASE_IN_SYNC:"app-config.json already matches the server \u2014 left as it is.",APP_SCAFFOLD_JSON_DIFF_CANCELLED:"app-config.json differs from the server and --json cannot prompt for confirmation. Re-run without --json to review and confirm the update.",APP_SCAFFOLD_SERVER_READBACK_FAILED:appId=>`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.`,APP_SCAFFOLD_SUCCESS:(written,total)=>`Feature ${scaffoldFileCount("scaffolded",written,total)}`,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()}\`.`,APP_SCAFFOLD_TARGET_IS_CWD:"Scaffolding into the current directory.",APP_SCAFFOLD_CREATING_DIR:dir=>`Creating ${dir} and moving into it...`,APP_SCAFFOLD_USING_EXISTING_DIR:dir=>`Moving into ${dir}...`,APP_SCAFFOLD_NEXT_STEPS_TITLE:"Next steps",APP_SCAFFOLD_NEXT_STEPS_LINES:cdDir=>[...cdDir?[`1. cd ${cdDir}`]:[],`${cdDir?2:1}. yarn --cwd src/oauth`," (or: npm --prefix src/oauth install)",`${cdDir?3:2}. ${CLI.APP_START("oauth")}`],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}\`.`,APP_START_FEATURE_NOT_FOUND:entryFile=>`Feature entry file not found.
66
+
67
+ Expected: ${entryFile}
68
+ Current directory: ${process.cwd()}
69
+
70
+ Make sure you are inside your scaffolded project directory.
71
+ Run \`cd <your-project-folder>\` and try again.`,APP_START_NO_DEPS:featureDir=>`Dependencies not installed. Run \`yarn --cwd ${featureDir}\` (or \`npm --prefix ${featureDir} install\`) first.`,APP_START_STOPPED:"Process stopped.",APP_START_MISSING_FEATURE:available=>`Missing feature name.
72
+
73
+ Available features:
74
+ ${available}
75
+
76
+ Usage: ${CLI.APP_START()}`,APP_START_UNKNOWN_FEATURE:(feature,available)=>`Unknown feature "${feature}". Available features: ${available}`,APP_START_PORT_IN_USE:port=>`Port ${port} is already in use.
77
+
78
+ Either stop the process using port ${port}, use a different port with \`--port <port>\`,
79
+ or update your redirect URL by editing \`auth.redirectUris\` in app-config.json and running \`${CLI.APP_UPLOAD}\`.`,APP_START_CUSTOM_PORT_IN_USE:port=>`Port ${port} is already in use.
80
+
81
+ Stop the process using port ${port}, or pick another port with \`--port <port>\`
82
+ and update your redirect URL by editing \`auth.redirectUris\` in app-config.json and running \`${CLI.APP_UPLOAD}\`.`,APP_START_EXITED:(feature,code)=>`${feature} exited with code ${code}`,APP_START_FAILED:(feature,error)=>`Failed to start ${feature}: ${error}`,APP_START_REDIRECT_NOT_REGISTERED:port=>`Port ${port} isn't registered as a redirect URL for this app.`,APP_START_REDIRECT_REGISTER_PROMPT:url=>`Register ${url}? You can delete it later if you want.`,APP_START_REDIRECT_REGISTERED:url=>`Added ${url} to app-config.json and uploaded the new config.`,APP_START_REDIRECT_UPLOAD_FAILED:url=>`${url} was saved to app-config.json but the upload failed. Fix the issue and run \`${CLI.APP_UPLOAD}\` to finish registering it.`,APP_START_REDIRECT_DECLINED:url=>`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.`,APP_START_REDIRECT_NON_INTERACTIVE:(port,url)=>`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.`,AUTH_LOGOUT_NON_INTERACTIVE:"Cannot prompt for confirmation in non-interactive mode. Use --force to skip.",ERR_NETWORK:"Cannot reach Brevo API.",ERR_RATE_LIMITED:retryAfter=>`Rate limited. Retrying in ${retryAfter} seconds...`,ERR_REGISTRY:"Operation failed due to a registry error. Please try again.",ERR_UI_APP_NOT_ENABLED:`UI apps aren't enabled for this Brevo account yet.
83
+
84
+ Why: UI apps (action links) are still rolling out, and are enabled per account.
85
+ Do this: build an OAuth app instead, or ask Brevo to enable UI apps for this account.`,ERR_AUTH_GATEWAY:"API is behind an authentication gateway (e.g. Cloudflare Access). Sign in via your browser first, or check your API base URL.",TLS_VERIFICATION_DISABLED:"TLS certificate verification is disabled (NODE_TLS_REJECT_UNAUTHORIZED=0). This is insecure \u2014 API keys and tokens can be intercepted on the network.",INIT_WELCOME:"Brevo CLI \u2014 Quick Setup",INIT_ALREADY_LOGGED_IN:"Already authenticated.",INIT_VERIFY_UNAVAILABLE:"Couldn't verify your credentials right now \u2014 continuing with the stored session.",INIT_STEP_LOGIN:" Step 1: Authenticate with your Brevo account",INIT_STEP_CREATE:" Step 2: Create your first app",INIT_APPS_EXIST:count=>`You have ${count} app${count===1?"":"s"} already.`,INIT_APP_LINKED:name=>`App "${name}" is linked to this project (app-config.json).`,INIT_APP_ACTION:"What would you like to do?",INIT_DONE:`All set! Run \`${CLI.APP_START("oauth")}\` to test your OAuth flow, or \`${CLI.HELP}\` to see all commands.`,INIT_DONE_UI_APP:`All set! Follow the next steps above, or run \`${CLI.HELP}\` to see all commands.`,SKILL_INSTALL_SUCCESS:(name,version2,dir)=>`Installed ${name}@${version2} \u2192 ${dir}`,SKILL_INSTALL_CLAUDE_ONLY:"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.",SKILL_INSTALL_ALREADY:(name,version2)=>`${name}@${version2} is already up to date.`,SKILL_UNINSTALL_SUCCESS:(name,dir)=>`Uninstalled ${name} from ${dir}`,SKILL_UNINSTALL_NONE:"No Brevo skills installed.",SKILL_AUTOREFRESHED:(name,oldVer,newVer)=>`\u21BB refreshed ${name} skill (v${oldVer} \u2192 v${newVer})`,SKILL_AUTOREFRESH_FAILED:(name,err)=>`\u26A0 failed to refresh ${name} skill: ${err}`,APP_SCOPES_EMPTY:"The IdP returned an empty scope list.",APP_SCOPES_USAGE_HINT:`Add a scope to an app by editing \`auth.scopes\` in app-config.json and running \`${CLI.APP_UPLOAD}\`.`,APP_SCOPES_DOCS_HINT:`Full CLI reference: ${BREVO_CLI_REFERENCE_URL}`,APP_SCOPES_CATALOG_DOCS_HINT:`Scope catalog docs: ${BREVO_OAUTH_SCOPES_DOCS_URL}`,APP_SCOPES_WEB_LISTENING:url=>`Open in browser: ${url} (Ctrl+C to stop)`,APP_SCOPES_WEB_TITLE:"Brevo OAuth scopes",APP_SCOPES_WEB_INTRO:(count,sourceUrl)=>`${count} scope${count===1?"":"s"} from ${sourceUrl}`,APP_SCOPES_WEB_SEARCH_PLACEHOLDER:"Filter scopes\u2026",APP_SCOPES_WEB_EMPTY:"The IdP returned an empty scope list.",APP_SCOPES_WEB_FOOTER:"Served locally by the Brevo CLI. Press Ctrl+C in the terminal to stop.",APP_SCOPES_WEB_REFRESH:"Refresh",APP_SCOPES_WEB_REFRESHING:"Refreshing\u2026",APP_SCOPES_WEB_REFRESH_FAILED:`Refresh failed. Please restart \`${CLI.APP_SCOPES} --web\` to retry.`,APP_SCOPES_WEB_ENDPOINTS_LABEL:"API endpoints",APP_SCOPES_WEB_NO_ENDPOINTS:"No API endpoints listed for this scope.",APP_SCOPES_WEB_COPY:"Copy",APP_SCOPES_WEB_COPIED:"Copied!",APP_SCOPES_WEB_COPY_CATEGORY_ARIA:"Copy {category} scopes",APP_SCOPES_WEB_SELECT_SCOPE_ARIA:"Select {scope}",APP_SCOPES_WEB_COPY_SELECTED:"Copy selected",APP_SCOPES_WEB_SELECTED_PLACEHOLDER:"Tick scopes to build a comma-separated list for app-config.json's `auth.scopes`",APP_SCOPES_WEB_LEGACY_BADGE:"deprecated",APP_SCOPES_WEB_LEGACY_TITLE:"Legacy 'all' scope \u2014 replace with the granular scopes your integration uses.",APP_SCOPES_WEB_DOCS_LINK:"Full CLI reference",APP_SCOPES_WEB_CATALOG_DOCS_CTA:"Read the scope catalog docs",OAUTH_METADATA_MISSING_SCOPES:"IdP scopes response did not include a scopes array.",OAUTH_METADATA_FETCH_FAILED:(url,status)=>`Failed to fetch OAuth scopes from ${url} (HTTP ${status}).`,ABORTED:"Aborted."},messages={...coreMessages};var REMOVED_COMMANDS=[{group:"app",name:"update",message:messages.APP_UPDATE_REMOVED}];function removedCommandsIn(group){return REMOVED_COMMANDS.filter(c=>c.group===group)}function isRemovedCommand(name,parentName){return REMOVED_COMMANDS.some(c=>c.name===name&&(c.group===void 0||c.group===parentName))}var UNAUTHENTICATED_COMMANDS=new Set(["login","help","init","whoami","logout","available-scopes"]),UNAUTHENTICATED_GROUPS=new Set(["skill:cli"]);function commandRequiresAuth(thisCommand,actionCommand){let commandName=actionCommand.name(),parentName=actionCommand.parent?.name();return actionCommand===thisCommand||commandName===thisCommand.name()||isRemovedCommand(commandName,parentName)?!1:!(UNAUTHENTICATED_COMMANDS.has(commandName)||parentName&&UNAUTHENTICATED_GROUPS.has(parentName)||process.argv.includes("--help")||process.argv.includes("-h")||process.argv.includes("--version")||process.argv.includes("-V")||process.argv.length<=2)}function installAuthGuard(program2){program2.hook("preAction",(thisCommand,actionCommand)=>{if(commandRequiresAuth(thisCommand,actionCommand)&&!isAuthenticated())throw new CliError(`Not authenticated. Run: ${CLI.LOGIN}`)})}var OAUTH_REFRESH_SKEW_MS=6e4;function shouldRefreshOauth(auth,now,skewMs=OAUTH_REFRESH_SKEW_MS){return auth?.kind!=="oauth"||!auth.refreshToken||!Number.isFinite(auth.expiresAt)?!1:auth.expiresAt-skewMs<=now}async function ensureFreshOauthToken(deps){let auth=deps.getAuthCred(),now=deps.now?deps.now():Date.now();if(!shouldRefreshOauth(auth,now,deps.skewMs))return!1;try{return deps.persist(await deps.refresh(auth.refreshToken)),!0}catch(err){if(deps.isTerminal?.(err))throw deps.onTerminal?.(),new AuthExpiredError;return deps.onError?.(err),!1}}function installProactiveOauthRefresh(program2,deps){program2.hook("preAction",async(thisCommand,actionCommand)=>{commandRequiresAuth(thisCommand,actionCommand)&&await ensureFreshOauthToken(deps)})}var isDebug=()=>process.env.BREVO_DEBUG==="1"||process.argv.includes("--debug"),isTTY=()=>process.stdout.isTTY===!0,useColor=()=>process.env.NO_COLOR===void 0&&(process.env.FORCE_COLOR!==void 0||isTTY());function color(code,text){return useColor()?`\x1B[${code}m${text}\x1B[0m`:text}var COLOR_RED="31",SENSITIVE_KEYS=new Set(["api-key","api_key","apikey","access_token","refresh_token","client_secret","token","password","secret","authorization"]);function redactSensitiveFields(data){if(data==null||typeof data!="object")return data;if(Array.isArray(data))return data.map(redactSensitiveFields);let redacted={};for(let[key,value]of Object.entries(data))SENSITIVE_KEYS.has(key.toLowerCase())?redacted[key]="[REDACTED]":typeof value=="object"&&value!==null?redacted[key]=redactSensitiveFields(value):redacted[key]=value;return redacted}function logHttp(method,path14){if(isDebug()){let line=`\u2192 ${method} ${path14}`;process.stderr.write(` ${color("90",line)}
86
+ `)}}function logHttpResponse(status,path14){if(isDebug()){let code=status>=200&&status<300?"32":"31",line=`\u2190 ${status} ${path14}`;process.stderr.write(` ${color(code,line)}
87
+ `)}}function logDebug(context,data){if(isDebug()){let safe=redactSensitiveFields(data),line=`[debug] ${context}: ${JSON.stringify(safe)}`;process.stderr.write(` ${color("90",line)}
88
+ `)}}function formatError(error){if(error instanceof Error)return error.stack??error.message;if(typeof error=="string")return error;if(typeof error=="number"||typeof error=="boolean"||typeof error=="bigint"||typeof error=="symbol")return String(error);try{return JSON.stringify(error)??"undefined"}catch{return"[unserializable error]"}}function logError(message,error){process.stderr.write(`
89
+ ${color("31","\u2717")} ${message}
90
+ `),isDebug()&&error?process.stderr.write(` ${color("90",formatError(error))}
91
+ `):error&&process.stderr.write(` ${color("90","Run with --debug for full details")}
92
+ `)}function logSuccess(message){process.stdout.write(`
93
+ ${color("32","\u2713")} ${message}
94
+ `)}function logInfo(message){process.stdout.write(` ${message}
95
+ `)}function logWarn(message){process.stdout.write(` ${color("33","\u26A0")} ${message}
96
+ `)}var readline=__toESM(require("node:readline"));function readHiddenInput(prompt){return new Promise((resolve11,reject)=>{let rl=readline.createInterface({input:process.stdin,output:process.stdout}),answered=!1,stdout=process.stdout;process.stdout.write(` ${prompt}`);let prompting=!0;rl._writeToOutput=function(str){prompting||(str.includes(`
97
+ `)||str.includes("\r")?stdout.write(`
98
+ `):stdout.write("*"))},process.nextTick(()=>{prompting=!1}),rl.question("",answer=>{answered=!0,rl.close(),resolve11(answer.trim())}),rl.on("close",()=>{answered||reject(new Error("readline was closed"))})})}var RefreshError=class extends Error{constructor(message,unauthorized,cause){super(message);this.unauthorized=unauthorized;this.cause=cause;this.name="RefreshError"}unauthorized;cause};async function refreshAccessToken(refreshToken,proxyUrl){let res;try{res=await fetch(`${proxyUrl}/refresh`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({refresh_token:refreshToken}),signal:AbortSignal.timeout(15e3)})}catch(err){throw new RefreshError("Could not reach the Brevo login service.",!1,err)}if(!res.ok)throw new RefreshError(`Token refresh failed (${res.status}).`,res.status===401);let body;try{body=await res.json()}catch(err){throw new RefreshError("Malformed refresh response from login service.",!1,err)}if(!body.access_token||typeof body.access_token!="string"||typeof body.expires_in!="number"||!Number.isFinite(body.expires_in)||body.expires_in<=0||!body.token_type||typeof body.token_type!="string")throw new RefreshError("Malformed refresh response from login service.",!1);let resolvedRefreshToken=refreshToken;if(body.refresh_token!==void 0){if(typeof body.refresh_token!="string"||body.refresh_token.length===0)throw new RefreshError("Malformed refresh response from login service.",!1);resolvedRefreshToken=body.refresh_token}return{accessToken:body.access_token,refreshToken:resolvedRefreshToken,expiresIn:body.expires_in,tokenType:body.token_type,scope:body.scope}}var SPINNER_FRAMES=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],activeSpinner=null;function stopActiveSpinner(){activeSpinner&&(activeSpinner.stop(),activeSpinner=null)}function createSpinner(text,options){stopActiveSpinner();let isTTY2=process.stdout.isTTY===!0;if(options?.silent||!isTTY2)return options?.silent||process.stderr.write(` ${text}
99
+ `),{update(t){options?.silent||process.stderr.write(` ${t}
100
+ `)},stop(t){t&&!options?.silent&&process.stderr.write(` ${t}
101
+ `)}};let frame=0,currentText=text,interval=setInterval(()=>{let spinner2=SPINNER_FRAMES[frame%SPINNER_FRAMES.length];process.stdout.write(`\r \x1B[36m${spinner2}\x1B[0m ${currentText}`),frame++},80),spinner={update(t){currentText=t},stop(finalText){clearInterval(interval),process.stdout.write("\r\x1B[K"),finalText&&process.stdout.write(` ${finalText}
102
+ `),activeSpinner===spinner&&(activeSpinner=null)}};return activeSpinner=spinner,spinner}var BOX_CHROME_COLUMNS=6,BOX_MIN_CONTENT_COLUMNS=24;function boxContentBudget(){let columns=process.stdout.columns,usable=(typeof columns=="number"&&columns>0?columns:80)-BOX_CHROME_COLUMNS;return Math.max(usable,BOX_MIN_CONTENT_COLUMNS)}function printBox(title,lines){let budget=boxContentBudget(),titleRows=wrapToWidth(title,budget),bodyRows=lines.flatMap(line=>wrapToWidth(line,budget)),maxLen=Math.max(...[...titleRows,...bodyRows].map(displayWidth)),border="\u2500".repeat(maxLen+2);process.stdout.write(`
103
+ \u250C${border}\u2510
104
+ `);for(let row of titleRows)process.stdout.write(` \u2502 \x1B[1m${row}${" ".repeat(maxLen-displayWidth(row))}\x1B[0m \u2502
105
+ `);process.stdout.write(` \u251C${border}\u2524
106
+ `);for(let row of bodyRows)process.stdout.write(` \u2502 ${row}${" ".repeat(maxLen-displayWidth(row))} \u2502
107
+ `);process.stdout.write(` \u2514${border}\u2518
108
+
109
+ `)}var SGR=/\x1b\[[0-9;]*m/,SGR_GLOBAL=/\x1b\[[0-9;]*m/g,SGR_RESET="\x1B[0m";function stripAnsi(str){return str.replace(SGR_GLOBAL,"")}function displayWidth(str){return stripAnsi(str).length}function openSgr(row){let codes=row.match(SGR_GLOBAL),last=codes?.[codes.length-1];return!last||last===SGR_RESET||last==="\x1B[m"?"":last}function takeRow(line,width){let row="",visible=0,i=0,breakAt=-1,breakFrom=-1;for(;i<line.length&&visible<width;){let escape=SGR.exec(line.slice(i));if(escape?.index===0){row+=escape[0],i+=escape[0].length;continue}line[i]===" "&&visible>0&&(breakAt=row.length,breakFrom=i),row+=line[i],visible+=1,i+=1}return i>=line.length?{row,rest:""}:breakAt>0&&displayWidth(row.slice(0,breakAt))>=width/2?{row:row.slice(0,breakAt),rest:line.slice(breakFrom+1)}:{row,rest:line.slice(i)}}function wrapToWidth(line,width){if(displayWidth(line)<=width)return[line];let leading=/^ */.exec(line)?.[0].length??0,indent=" ".repeat(Math.max(Math.min(leading+2,width-BOX_MIN_CONTENT_COLUMNS),0)),rows=[],rest=line,carried="";for(;rest;){let first=rows.length===0,{row,rest:remaining}=takeRow(rest,width-(first?0:indent.length)),full=first?row:`${indent}${carried}${row}`;carried=openSgr(full),rows.push(carried?`${full}${SGR_RESET}`:full),rest=remaining}return rows}var OUTPUT_GUTTER=" ";function indentChoices(choices){return choices.map(choice=>{if(!choice||typeof choice!="object")return choice;let candidate=choice;return candidate.type==="separator"||typeof candidate.name!="string"?choice:{...candidate,name:`${OUTPUT_GUTTER}${candidate.name}`}})}var fs2=__toESM(require("node:fs")),path2=__toESM(require("node:path"));function readCliVersion(){try{let pkgPath=path2.resolve(__dirname,"..","..","package.json"),pkg2=JSON.parse(fs2.readFileSync(pkgPath,"utf-8"));return typeof pkg2.version=="string"?pkg2.version:"0.0.0"}catch{return"0.0.0"}}var CLI_VERSION=readCliVersion();var OS_BY_PLATFORM={darwin:"macos",win32:"windows",linux:"linux"};function getCliOs(){return OS_BY_PLATFORM[process.platform]??"other"}function sanitizeHeaderValue(value,fallback){return value.replace(/[^\x20-\x7E]/g,"")||fallback}var SAFE_CLI_VERSION=sanitizeHeaderValue(CLI_VERSION,"0.0.0");function getAuthMethod(authHeader){if(authHeader&&"api-key"in authHeader)return CLI_AUTH_METHODS.API_KEY;if(authHeader&&"Authorization"in authHeader)return CLI_AUTH_METHODS.OAUTH}function getCliUserAgent(authHeader){let method=getAuthMethod(authHeader),comment=method?`${getCliOs()}; auth=${method}`:getCliOs();return`brevo-cli/${SAFE_CLI_VERSION} (${comment})`}function buildCliHeaders(authHeader){return{[USER_AGENT_HEADER]:getCliUserAgent(authHeader)}}var MAX_RETRIES=3,DEFAULT_RETRY_AFTER_SECONDS=5,MAX_RETRY_AFTER_SECONDS=300,apiCodeMessages={APP_LIMIT_REACHED:messages.APP_CREATE_LIMIT_REACHED,REGISTRY_ERROR:messages.ERR_REGISTRY,ui_app_not_enabled:messages.ERR_UI_APP_NOT_ENABLED};function resolveErrorMessage(apiCode,fallback){return apiCode&&apiCode in apiCodeMessages?apiCodeMessages[apiCode]:fallback}function parseRetryAfter(header){if(!header)return DEFAULT_RETRY_AFTER_SECONDS;let parsed=Number.parseInt(header,10);return!Number.isFinite(parsed)||parsed<=0?DEFAULT_RETRY_AFTER_SECONDS:Math.min(parsed,MAX_RETRY_AFTER_SECONDS)}function sanitizeErrorMessage(msg){return msg.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g,"").replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g,"").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g,"")}function looksLikeHtml(s){let lower=s.toLowerCase();return lower.includes("<!doctype html")||lower.includes("<html")}function parseResponseData(text,status){let data={};try{let parsed=text?JSON.parse(text):{};parsed!==null&&typeof parsed=="object"&&(data=parsed)}catch{if(looksLikeHtml(text))throw new ApiError(messages.ERR_AUTH_GATEWAY,status,"AUTH_GATEWAY");data={message:text}}if(typeof data.message=="string"&&looksLikeHtml(data.message))throw new ApiError(messages.ERR_AUTH_GATEWAY,status,"AUTH_GATEWAY");return data}function throwResponseError(data,status){let apiCode=typeof data.code=="string"?data.code:void 0,rawFallback=typeof data.message=="string"&&data.message||typeof data.error=="string"&&data.error||""||`Request failed with status ${status}`,fallback=sanitizeErrorMessage(rawFallback),message=resolveErrorMessage(apiCode,fallback);throw new ApiError(message,status,mapErrorCode(status,apiCode),apiCode)}function mapErrorCode(status,apiCode){if(apiCode==="APP_LIMIT_REACHED")return"APP_LIMIT_REACHED";if(apiCode==="REGISTRY_ERROR")return"REGISTRY_ERROR";switch(status){case 401:return"AUTH_INVALID";case 403:return"ACCESS_DENIED";case 404:return"APP_NOT_FOUND";case 429:return"RATE_LIMITED";default:return}}var ApiClient=class{constructor(deps){this.deps=deps}deps;onAuthFailure;ensureFresh;setOnAuthFailure(handler){this.onAuthFailure=handler}setEnsureFresh(handler){this.ensureFresh=handler}get(path14){return this.request({method:"GET",path:path14})}post(path14,body){return this.request({method:"POST",path:path14,body})}patch(path14,body){return this.request({method:"PATCH",path:path14,body})}put(path14,body){return this.request({method:"PUT",path:path14,body})}delete(path14,body){return this.request({method:"DELETE",path:path14,body})}getWithKey(path14,apiKey){return this.request({method:"GET",path:path14,skipAuth:!0,authHeader:{"api-key":apiKey}})}getWithBearer(path14,accessToken,tokenType="Bearer"){return this.request({method:"GET",path:path14,skipAuth:!0,authHeader:{Authorization:`${tokenType} ${accessToken}`}})}buildHeaders(opts){let authHeader=opts.authHeader??(opts.skipAuth?void 0:this.deps.getAuthHeader());return{"Content-Type":"application/json",Accept:"application/json",...buildCliHeaders(authHeader),...opts.headers,...authHeader}}async performFetch(url,opts,headers){try{return await fetch(url,{method:opts.method,headers,body:opts.body?JSON.stringify(opts.body):void 0,signal:AbortSignal.timeout(3e4)})}catch(err){let apiErr=new ApiError(messages.ERR_NETWORK,0,"NETWORK_ERROR");throw apiErr.cause=err,apiErr}}async request(opts,isRetry=!1,retryCount=0){this.ensureFresh&&!opts.skipAuth&&!opts.authHeader&&await this.ensureFresh();let url=`${this.deps.baseUrl}${opts.path}`,headers=this.buildHeaders(opts);logHttp(opts.method,opts.path),opts.body!==void 0&&logDebug(`request ${opts.method} ${opts.path}`,opts.body);let response=await this.performFetch(url,opts,headers);if(logHttpResponse(response.status,opts.path),response.status===401&&!isRetry&&!opts.skipAuth){if(this.onAuthFailure)return await this.onAuthFailure(),this.request(opts,!0,retryCount);throw new ApiError(messages.AUTH_EXPIRED,401,"AUTH_EXPIRED")}if(response.status===429){if(retryCount>=MAX_RETRIES)throw new ApiError("Rate limited \u2014 max retries exceeded.",429,"RATE_LIMITED");let retryAfter=parseRetryAfter(response.headers.get("retry-after"));return process.stderr.write(` ${messages.ERR_RATE_LIMITED(retryAfter)}
110
+ `),await new Promise(r=>setTimeout(r,retryAfter*1e3)),this.request(opts,isRetry,retryCount+1)}if(response.status===502&&retryCount<1)return await new Promise(r=>setTimeout(r,2e3)),this.request(opts,isRetry,retryCount+1);let text=await response.text(),data=parseResponseData(text,response.status);return logDebug(`response ${opts.method} ${opts.path}`,data),response.ok||throwResponseError(data,response.status),data}};var SUB_ACCOUNT_PAGE_SIZE=50,SUB_ACCOUNT_MAX_PAGES=40;function createAccountService(client2){return{validateApiKey(apiKey){return client2.getWithKey(ENDPOINTS.ACCOUNT,apiKey)},getAccount(){return client2.get(ENDPOINTS.ACCOUNT)},async fetchSubAccounts(){let collected=[];for(let page=0;page<SUB_ACCOUNT_MAX_PAGES;page+=1){let response=await client2.get(`${ENDPOINTS.CORPORATE_SUB_ACCOUNTS}?offset=${collected.length}&limit=${SUB_ACCOUNT_PAGE_SIZE}`),batch=response?.subAccounts??[];if(collected.push(...batch),batch.length===0||collected.length>=(response?.count??0))break}return collected}}}var import_inquirer=__toESM(require("inquirer"));function normalizeAppId(raw){let{app_id}=raw;if(typeof app_id=="string"){let trimmed=app_id.trim();if(trimmed.length===0)throw new CliError("Invalid app_id in API response: expected non-empty string, received empty string.");return{...raw,app_id:trimmed}}if(typeof app_id=="number"&&Number.isFinite(app_id))return{...raw,app_id:String(app_id)};let received=describeAppId(app_id);throw new CliError(`Invalid app_id in API response: expected string or finite number, received ${received}.`)}function describeAppId(value){return value===null?"null":typeof value=="number"?Number.isNaN(value)?"number (NaN)":Number.isFinite(value)?"number":`number (${value>0?"Infinity":"-Infinity"})`:typeof value}function firstNonEmptyString(...candidates){for(let candidate of candidates)if(typeof candidate=="string"&&candidate.trim())return candidate.trim()}function pick(key,value){return value===void 0?{}:{[key]:value}}function flattenCreateAuth(raw){let{auth,...rest}=raw;return auth?{...rest,...pick("client_id",rest.client_id??auth.client_id),...pick("client_secret",rest.client_secret??auth.client_secret),...pick("redirect_uris",rest.redirect_uris??auth.redirect_uris),...pick("scopes",rest.scopes??auth.scopes)}:rest}function rethrowNotFound(err,appId){throw err instanceof ApiError&&err.statusCode===404?new CliError(`App ${appId} not found.`,err.exitCode):err}function toNumericIdentifier(value){let trimmed=value?.trim();return trimmed&&/^\d+$/.test(trimmed)?Number(trimmed):void 0}function buildInstallPayload(accountId,name){return{...pick("client_id",toNumericIdentifier(getOrganizationId())),...pick("deploy_client_id",toNumericIdentifier(accountId)),name,is_developer:!0}}function logEmptyAndThrow(){throw logInfo(`
111
+ ${messages.APP_LIST_EMPTY}
112
+ `),new CliError(messages.APP_LIST_EMPTY,EXIT_CODES.ERROR)}function mergeCachedCredentials(app,local){let diffs=[];return local&&(!app.client_id&&local.clientId?app.client_id=local.clientId:local.clientId&&local.clientId!==app.client_id&&diffs.push("client_id"),!app.client_secret&&local.clientSecret?app.client_secret=local.clientSecret:local.clientSecret&&local.clientSecret!==app.client_secret&&diffs.push("client_secret")),diffs}function createAppService(client2){async function fetchAppsList(){return(await client2.get(ENDPOINTS.APP_STORE_APPS)||[]).map(normalizeAppId)}return{fetchAppsList,async fetchSurfacePointLocations(){let res=await client2.get(ENDPOINTS.APP_STORE_SURFACE_POINT_LOCATIONS),raw=Array.isArray(res)?res:res?.locations??[],locations=new Set;for(let entry of raw){if(typeof entry!="string")continue;let name=entry.trim();name&&locations.add(name)}return[...locations]},async fetchSurfacePoints(locations){let filter=(locations??[]).map(l=>String(l).trim()).filter(Boolean),query=filter.length?`?location=${encodeURIComponent(filter.join(","))}`:"",res=await client2.get(`${ENDPOINTS.APP_STORE_SURFACE_POINTS}${query}`),rows=Array.isArray(res)?res:res?.surface_points??[],seen=new Set,normalized=[];for(let row of rows){if(!row||typeof row!="object")continue;let name=firstNonEmptyString(row.extension_point_name,row.extension_point);if(!name||seen.has(name))continue;seen.add(name);let{extension_point:_legacyName,location:legacyLocation,place:legacyPlace,kind:legacyKind,supported_extension_types:legacySupportedTypes,...rest}=row;normalized.push({...rest,extension_point_name:name,...pick("location_name",firstNonEmptyString(row.location_name,legacyLocation)),...pick("section_name",firstNonEmptyString(row.section_name,legacyPlace)),...pick("component_type",firstNonEmptyString(row.component_type,legacyKind)),...row.extension_type_list??legacySupportedTypes?{extension_type_list:row.extension_type_list??legacySupportedTypes}:{}})}return normalized},async fetchApp(appId){let app;try{app=await client2.get(ENDPOINTS.APP_STORE_APP(appId))}catch(err){rethrowNotFound(err,appId)}return app?normalizeAppId(app):null},async fetchAppState(appId){let res;try{res=await client2.get(ENDPOINTS.APP_STATE(appId))}catch(err){rethrowNotFound(err,appId)}return res},async pickApp(promptMessage,formatChoice){let spinner=createSpinner("Loading apps..."),apps=await fetchAppsList();spinner.stop(),apps.length===0&&logEmptyAndThrow();let{selectedApp}=await import_inquirer.default.prompt([{type:"rawlist",name:"selectedApp",message:promptMessage,choices:apps.map(a=>{let appName=a.name||"App "+a.app_id;return{name:formatChoice?formatChoice(a):`${appName} (App ID: ${a.app_id}, Client ID: ${a.client_id})`,value:a.app_id}})}]);return selectedApp},async resolveAppCredentials(appId,opts){let raw;try{raw=await client2.get(ENDPOINTS.APP_STORE_APP(appId))}catch(err){if(opts?.tolerateMissing&&err instanceof ApiError&&err.statusCode===404)return null;rethrowNotFound(err,appId)}if(!raw)return null;let app=normalizeAppId(raw),diffs=mergeCachedCredentials(app,getAppCredentials(appId));return{app,diffs}},syncAppCredentials(appId,app){let existing=getAppCredentials(appId),clientId=app.client_id||existing?.clientId,clientSecret=app.client_secret||existing?.clientSecret;clientId&&clientSecret&&saveAppCredentials(appId,{clientId,clientSecret})},async createApp(payload){let raw=await client2.post(ENDPOINTS.APP_STORE_APPS,payload);return flattenCreateAuth(normalizeAppId(raw))},async uploadApp(appId,payload){return client2.post(ENDPOINTS.APP_STORE_APP_UPLOAD(appId),payload)},async deleteApp(appId){try{await client2.delete(ENDPOINTS.APP_STORE_APP(appId))}catch(err){rethrowNotFound(err,appId)}},async deployApp(appId,accountId,name){try{await client2.post(ENDPOINTS.APP_STORE_APP_INSTALLS(appId),buildInstallPayload(accountId,name))}catch(err){rethrowNotFound(err,appId)}},async rollbackApp(appId,accountId,name){await client2.delete(ENDPOINTS.APP_STORE_APP_INSTALLS(appId),buildInstallPayload(accountId,name))},async withdrawApp(appId){try{await client2.post(ENDPOINTS.APP_STORE_APP_WITHDRAW(appId))}catch(err){rethrowNotFound(err,appId)}}}}function buildAuthHeader(){let auth=getAuthCred();if(auth)return auth.kind==="api-key"?{"api-key":auth.apiKey}:{Authorization:`${auth.tokenType} ${auth.accessToken}`}}var client=new ApiClient({baseUrl:API_BASE,getAuthHeader:buildAuthHeader}),accountService=createAccountService(client),appService=createAppService(client);var FEATURE_STAGE={"account-install":"preview","review-lifecycle":"preview","ui-app-type":"preview","public-distribution":"preview"};function isFeatureAvailable(feature){return FEATURE_STAGE[feature]==="ga"||!1}function assertFeatureAvailable(feature){if(!isFeatureAvailable(feature))throw new CliError(messages.PREVIEW_FEATURE_UNAVAILABLE)}function previewFeatureOf(def){if(def.requires)return def.requires in FEATURE_STAGE?def.requires:void 0}function registerCommand(parent,def){let gatedBehind=previewFeatureOf(def),gateHides=!!gatedBehind&&!isFeatureAvailable(gatedBehind),hidden=def.hidden===!0||gateHides,cmd=parent.command(def.name,{hidden}).description(def.description);if(def.arguments)for(let arg of def.arguments)cmd.argument(arg.name,arg.description);if(def.options)for(let opt of def.options)opt.parser?cmd.option(opt.flags,opt.description,opt.parser):cmd.option(opt.flags,opt.description);def.examples?.length&&cmd.addHelpText("after",`
113
+ Examples:
114
+ `+def.examples.map(e=>` $ ${e}`).join(`
115
+ `)+`
116
+ `),cmd.action((...actionArgs)=>{gatedBehind&&assertFeatureAvailable(gatedBehind);let opts=actionArgs.at(-2),positionalArgs=actionArgs.slice(0,-2);return def.handler(opts,...positionalArgs)})}function registerRemovedCommand(parent,removed){let cmd=parent.command(removed.name,{hidden:!0}).allowUnknownOption(!0).allowExcessArguments(!0).helpOption(!1).argument("[args...]").action(()=>{throw new CliError(removed.message)});cmd.help=()=>{throw new CliError(removed.message)}}function registerSubcommandGroup(parent,group){let groupCmd=parent.command(group.name).description(group.description);for(let def of group.commands)registerCommand(groupCmd,def);for(let removed of removedCommandsIn(group.name))registerRemovedCommand(groupCmd,removed)}function registerAll(program2,commands,groups){for(let cmd of commands)registerCommand(program2,cmd);for(let removed of removedCommandsIn())registerRemovedCommand(program2,removed);for(let group of groups)registerSubcommandGroup(program2,group)}var import_commander=require("commander");function distributionValues(){return isFeatureAvailable("public-distribution")?"private|public":"private"}function createDescription(){return isFeatureAvailable("ui-app-type")?"Create a new app (OAuth, or a UI app via the prompts)":"Create a new OAuth app"}function formatRootHelp(description){return["Usage: brevo [options] [command]","",description,"","Options:"," -V, --version output the version number"," -h, --help display help for command","","Commands:"," brevo login [--browser] [--json] Authenticate with your Brevo account"," brevo logout [--json] Clear stored credentials"," brevo whoami [--json] Show current authenticated user","","App commands:"," brevo app init Quick setup \u2014 login, create app, and scaffold",` brevo app create [--name] [--distribution ${distributionValues()}]`," [--redirect-uri <url>...] [--logo-uri <url>] [--json]",` ${createDescription()}`," brevo app list [--json] List all apps in your account"," brevo app credentials [--app-id <id>] [--reveal-secret] [--json]"," Show an app's client ID and secret"," brevo app scaffold [--app-id <id>] [--json] Add a feature (e.g. OAuth server) here"," brevo app start [feature] [--port <port>] Run a scaffolded feature locally"," brevo app upload [--yes] [--json] Push app-config.json to Brevo"," brevo app delete [--app-id <id>] [--force] [--json]"," Delete an app","","Skill commands:"," brevo skill:cli install [--json] Install the brevo-cli Claude Code skill"," brevo skill:cli uninstall [--json] Remove the brevo-cli skill","","Scope commands:"," brevo app available-scopes [--web] [--json] List OAuth scopes supported by the IdP"," (--web opens the catalog in a browser)","","Run `brevo <command> --help` for details on a specific command.","","Examples:"," $ brevo login # authenticate interactively"," $ brevo app init # guided setup",' $ brevo app create --name "My App" --json # create app, JSON output'," $ brevo app list --json # list apps as JSON"," $ brevo app scaffold --app-id APPID # generate starter code"," $ brevo app start oauth --port 3000 # start OAuth test server"," $ brevo app available-scopes --web # browse OAuth scope catalog","",`Docs: ${BREVO_CLI_REFERENCE_URL}`,""].join(`
117
+ `)}function createHelpFormatter(root){return(cmd,helper)=>cmd!==root?import_commander.Help.prototype.formatHelp.call(helper,cmd,helper):formatRootHelp(helper.commandDescription(cmd))}function isAbortError(err){if(!(err instanceof Error))return!1;if((err.name?.toLowerCase()??"")==="exitprompterror")return!0;let msg=err.message.toLowerCase();return msg.includes("readline was closed")||msg.includes("user force closed")||msg.includes("exitprompterror")||msg.includes("prompt was closed")}async function withAbortHandler(fn){try{return await fn()}catch(err){throw isAbortError(err)?new AbortError:err}}function withCommandHandler(fn){return opts=>withAbortHandler(()=>fn(opts))}var emitted=!1;function jsonOutput(data){emitted=!0,process.stdout.write(JSON.stringify(data)+`
118
+ `)}function hasEmittedJson(){return emitted}function buildJsonError(err){let envelope={error:{name:err instanceof Error?err.name:"Error",message:err instanceof Error?err.message:String(err),exitCode:err instanceof CliError?err.exitCode:EXIT_CODES.ERROR}};return err instanceof ApiError&&(err.errorCode&&(envelope.error.code=err.errorCode),envelope.error.statusCode=err.statusCode),envelope}function emitJsonError(err,argv=process.argv){argv.includes("--json")&&(hasEmittedJson()||jsonOutput(buildJsonError(err)))}var import_inquirer2=__toESM(require("inquirer"));function assertAppSelectionAllowed(command,jsonMode){if(jsonMode||!process.stdin.isTTY)throw new CliError(messages.APP_SELECT_NON_INTERACTIVE(command))}async function promptAppSelection(promptMessage){let listSpinner=createSpinner("Fetching apps..."),apps;try{apps=await appService.fetchAppsList()}finally{listSpinner.stop()}if(apps.length===0)throw logInfo(`
119
+ ${messages.APP_LIST_EMPTY}
120
+ `),new CliError(messages.APP_LIST_EMPTY);let{selectedApp}=await import_inquirer2.default.prompt([{type:"rawlist",name:"selectedApp",message:promptMessage,choices:apps.map(a=>({name:`${a.name||"App "+a.app_id} (App ID: ${a.app_id}, Client ID: ${a.client_id})`,value:a.app_id}))}]),appId=selectedApp,matched=apps.find(a=>a.app_id===appId);return{appId,appLabel:matched?.name||matched?.client_id||appId}}var import_node_child_process=require("node:child_process");function openBrowser(url){process.platform==="darwin"?(0,import_node_child_process.execFile)("open",[url]):process.platform==="win32"?(0,import_node_child_process.execFile)("cmd",["/c","start","",url]):(0,import_node_child_process.execFile)("xdg-open",[url])}var oauthAppType={id:"oauth",label:messages.APP_TYPE_OAUTH,availability:"ga",detectConfig:config=>!isUiAppConfigShape(config),detectRecord:app=>!isUiAppRecordShape(app),recoverableFromRecord:app=>!!app,validateConfig:()=>{},wireOnlyKeys:[]};var UI_APP_WIRE_ONLY_KEYS=["link_target","version","extension_point_name"],uiAppType={id:"ui",label:messages.APP_TYPE_UI,availability:"preview",detectConfig:isUiAppConfigShape,detectRecord:isUiAppRecordShape,recoverableFromRecord:app=>!!app?.ui_app,validateConfig:config=>{validateUiApp(config.ui_app)},wireOnlyKeys:UI_APP_WIRE_ONLY_KEYS};var APP_TYPES={oauth:oauthAppType,ui:uiAppType},POSITIVELY_DETECTED=[uiAppType];function resolveFromConfig(config){return POSITIVELY_DETECTED.find(type=>type.detectConfig(config))??oauthAppType}function resolveFromRecord(app){return POSITIVELY_DETECTED.find(type=>type.detectRecord(app))??oauthAppType}function appTypeById(id){return APP_TYPES[id]}var import_inquirer8=__toESM(require("inquirer"));var import_inquirer6=__toESM(require("inquirer"));var fs5=__toESM(require("node:fs")),path5=__toESM(require("node:path")),import_inquirer5=__toESM(require("inquirer"));var net=__toESM(require("node:net"));function tryBind(port,host){return new Promise((resolve11,reject)=>{let server=net.createServer();server.once("error",err=>{err.code==="EADDRINUSE"?resolve11(!1):reject(err)}),server.once("listening",()=>{server.close(()=>resolve11(!0))});try{server.listen(port,host)}catch(err){reject(err)}})}async function isPortAvailable(port){return!(!await tryBind(port,"0.0.0.0")||!await tryBind(port,"127.0.0.1"))}var MAX_SCAN=20;async function findAvailablePort(startPort){for(let port=startPort;port<startPort+MAX_SCAN;port++)if(await isPortAvailable(port))return port;return null}var fs4=__toESM(require("node:fs")),path4=__toESM(require("node:path")),import_inquirer3=__toESM(require("inquirer"));var fs3=__toESM(require("node:fs")),path3=__toESM(require("node:path")),TEMPLATES_DIR=path3.resolve(__dirname,"files");function loadTemplate(relativePath){return fs3.readFileSync(path3.join(TEMPLATES_DIR,relativePath),"utf-8")}function applyVars(template,vars){let result=template;for(let[key,value]of Object.entries(vars))result=result.replaceAll(key,value);return result}var IF_OPEN_RE=/^\s*\{\{#if (public|private|oauth|ui_app)\}\}\s*$/,IF_CLOSE_RE=/^\s*\{\{\/if\}\}\s*$/;function applyConditionals(template,flags){let activeFlags=typeof flags=="string"?new Set([flags]):flags,lines=template.split(`
121
+ `),out=[],stack=[],active=()=>stack.length===0||stack.at(-1)===!0;for(let line of lines){let open=IF_OPEN_RE.exec(line);if(open){stack.push(active()&&activeFlags.has(open[1]));continue}if(IF_CLOSE_RE.test(line)){if(stack.length===0)throw new Error("applyConditionals: unmatched {{/if}}");stack.pop();continue}active()&&out.push(line)}if(stack.length>0)throw new Error("applyConditionals: unclosed {{#if}}");return out.join(`
122
+ `)}var BASE_TEMPLATE_MANIFEST=[{outputPath:"app-config.json",templatePath:"app-config.json.tmpl"},{outputPath:".gitignore",templatePath:"gitignore.tmpl"},{outputPath:"AGENTS.md",templatePath:"AGENTS.md.tmpl"},{outputPath:"CLAUDE.md",templatePath:"CLAUDE.md.tmpl"},{outputPath:"README.md",templatePath:"README.md.tmpl"}],FEATURE_TEMPLATE_MANIFESTS={oauth:[{outputPath:"src/oauth/server.js",templatePath:"src/oauth/server.js.tmpl"},{outputPath:"src/oauth/handler.js",templatePath:"src/oauth/handler.js.tmpl"},{outputPath:"src/oauth/token-store.js",templatePath:"src/oauth/token-store.js.tmpl"},{outputPath:"src/oauth/.env.example",templatePath:"src/oauth/.env.example.tmpl"},{outputPath:"src/oauth/.env.local",templatePath:"src/oauth/.env.local.tmpl"},{outputPath:"src/oauth/package.json",templatePath:"src/oauth/package.json.tmpl"}]},FEATURE_LABELS={oauth:"Test OAuth App"};function resolveTemplateFlags(vars){let distribution=vars["{{DISTRIBUTION}}"]==="public"?"public":"private",isUiApp=!!vars["{{UI_APP_JSON}}"];return new Set([distribution,isUiApp?"ui_app":"oauth"])}function loadManifest(manifest,vars){let flags=resolveTemplateFlags(vars);return manifest.map(entry=>({name:entry.outputPath,content:applyVars(applyConditionals(loadTemplate(entry.templatePath),flags),vars)}))}function loadBaseTemplates(vars){return loadManifest(BASE_TEMPLATE_MANIFEST,vars)}function loadFeatureTemplates(featureType,vars){return loadManifest(FEATURE_TEMPLATE_MANIFESTS[featureType],vars)}var oauthServerJsTemplate=loadTemplate("src/oauth/server.js.tmpl"),oauthHandlerTemplate=loadTemplate("src/oauth/handler.js.tmpl"),tokenStoreJsTemplate=loadTemplate("src/oauth/token-store.js.tmpl"),envExampleTemplate=loadTemplate("src/oauth/.env.example.tmpl"),envLocalTemplate=loadTemplate("src/oauth/.env.local.tmpl"),gitignoreTemplate=loadTemplate("gitignore.tmpl"),packageJsonTemplate=loadTemplate("src/oauth/package.json.tmpl"),appConfigTemplate=loadTemplate("app-config.json.tmpl"),agentsMdTemplate=loadTemplate("AGENTS.md.tmpl"),claudeMdTemplate=loadTemplate("CLAUDE.md.tmpl"),readmeTemplate=loadTemplate("README.md.tmpl");function formatFileTree(filePaths){let tree={};for(let fp of filePaths){let parts=fp.split("/"),node=tree;for(let part of parts)node[part]=node[part]||{},node=node[part]}let lines=[];function render(node,prefix){let entries=Object.keys(node).sort((a,b)=>{let aIsDir=Object.keys(node[a]??{}).length>0,bIsDir=Object.keys(node[b]??{}).length>0;return aIsDir!==bIsDir?aIsDir?-1:1:a.localeCompare(b)});entries.forEach((name,i)=>{let isLast=i===entries.length-1,connector=isLast?"\u2514\u2500\u2500 ":"\u251C\u2500\u2500 ",children=node[name]??{},isDir=Object.keys(children).length>0;lines.push(`${prefix}${connector}${name}${isDir?"/":""}`),isDir&&render(children,prefix+(isLast?" ":"\u2502 "))})}return render(tree," "),lines.join(`
123
+ `)}function printFileTree(filePaths){for(let line of formatFileTree(filePaths).split(`
124
+ `))logInfo(line)}function computeSlug(name){return(name||"my-app").toLowerCase().replaceAll(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")||"my-app"}async function fetchAppContext(appId,silent,uiApp,fallbackApp){let spinner=createSpinner("Fetching app details...",{silent}),result;try{result=await appService.resolveAppCredentials(appId,{tolerateMissing:!!fallbackApp})}finally{spinner.stop()}let appDetails=result?.app??null;result?(result.diffs.length>0&&logWarn(`Local credentials for app ${appId} differ from server (${result.diffs.join(", ")}). Updating local cache.`),appService.syncAppCredentials(appId,result.app)):fallbackApp&&(appDetails=fallbackApp,silent||logWarn(messages.APP_SCAFFOLD_SERVER_READBACK_FAILED(appId)));let serverRedirectUrls=appDetails?.redirect_uris??[],redirectUris=serverRedirectUrls.length>0?serverRedirectUrls:[DEFAULT_REDIRECT_URI],localhostUri=redirectUris.find(url=>url.startsWith("http://localhost")||url.startsWith("http://127.0.0.1"));return{appDetails,clientId:appDetails?.client_id||PLACEHOLDER_CLIENT_ID,clientSecret:appDetails?.client_secret||"YOUR_CLIENT_SECRET",redirectUris,redirectUri:localhostUri||DEFAULT_REDIRECT_URI,...uiApp?{uiApp}:{}}}async function resolveProjectDirectory(defaultDir,jsonMode=!1,validateTarget){let outputDir=jsonMode?defaultDir:(await import_inquirer3.default.prompt([{type:"input",name:"outputDir",message:messages.APP_SCAFFOLD_DIR_PROMPT,default:defaultDir}])).outputDir,targetDir=path4.resolve(outputDir),existed=fs4.existsSync(targetDir);if(validateTarget?.(targetDir),!existed)return{targetDir,mergeOnly:!1,chooseAgain:!1,existed:!1};if(jsonMode)return{targetDir,unresolved:!0};let{action}=await import_inquirer3.default.prompt([{type:"list",name:"action",message:messages.APP_SCAFFOLD_DIR_EXISTS,choices:indentChoices([{name:"Overwrite existing files",value:"overwrite"},{name:"Merge (keep existing, add missing)",value:"merge"},{name:"Choose a different path",value:"new"}])}]);return action==="new"?{targetDir,mergeOnly:!1,chooseAgain:!0,existed:!0}:{targetDir,mergeOnly:action==="merge",chooseAgain:!1,existed:!0}}function applyProjectDirectory(decision,jsonMode=!1){if(decision.unresolved||decision.chooseAgain)return;let{targetDir,existed}=decision;existed?jsonMode||(targetDir===process.cwd()?logInfo(messages.APP_SCAFFOLD_TARGET_IS_CWD):logInfo(messages.APP_SCAFFOLD_USING_EXISTING_DIR(path4.relative(process.cwd(),targetDir)))):(jsonMode||logInfo(messages.APP_SCAFFOLD_CREATING_DIR(path4.relative(process.cwd(),targetDir))),fs4.mkdirSync(targetDir,{recursive:!0})),process.chdir(targetDir)}function diffLocalConfig(localConfig,ctx){let diffs=[],serverName=ctx.appDetails?.name;serverName&&localConfig.appName!==serverName&&diffs.push({field:"appName",local:localConfig.appName||"(none)",server:serverName});let serverDistribution=ctx.appDetails?.distribution_type??"private";if(localConfig.distribution_type!==serverDistribution&&diffs.push({field:"distribution_type",local:localConfig.distribution_type,server:serverDistribution}),!isUiAppConfig(localConfig)){let localRedirects=[...localConfig.auth?.redirectUris??[]].sort((a,b)=>a.localeCompare(b)),serverRedirects=[...ctx.redirectUris].sort((a,b)=>a.localeCompare(b));JSON.stringify(localRedirects)!==JSON.stringify(serverRedirects)&&diffs.push({field:"redirectUris",local:localRedirects.join(", ")||"(none)",server:serverRedirects.join(", ")||"(none)"})}if(!isUiAppConfig(localConfig)){let localScopes=[...localConfig.auth?.scopes??[]].sort((a,b)=>a.localeCompare(b)),serverScopes=[...ctx.appDetails?.scopes??[]].filter(s=>s!==LEGACY_ALL_SCOPE).sort((a,b)=>a.localeCompare(b));JSON.stringify(localScopes)!==JSON.stringify(serverScopes)&&diffs.push({field:"scopes",local:localScopes.join(", ")||"(none)",server:serverScopes.join(", ")||"(none)"})}let localLogo=localConfig.logoUri??"",serverLogo=ctx.appDetails?.logo_uri??"";localLogo!==serverLogo&&diffs.push({field:"logoUri",local:localLogo||"(none)",server:serverLogo||"(none)"});let localVersion=localConfig.version??"",serverVersion=ctx.appDetails?.version??"";return localVersion!==serverVersion&&diffs.push({field:"version",local:localVersion||"(none)",server:serverVersion||"(none)"}),diffs}function writeScaffoldFiles(files,targetDir,mergeOnly){let written=0;for(let file of files){let filePath=path4.join(targetDir,file.name);if(fs4.mkdirSync(path4.dirname(filePath),{recursive:!0}),mergeOnly&&fs4.existsSync(filePath))continue;let writeOptions=file.name.endsWith(".env.local")?{mode:384}:{};fs4.writeFileSync(filePath,file.content,{encoding:"utf-8",...writeOptions}),written++}return written}function renderUiAppJson(uiApp){return uiApp?JSON.stringify(uiApp,null,2).replaceAll(`
125
+ `,`
126
+ `):""}function buildTemplateVars(appId,ctx,targetDir){let appName=(ctx.appDetails?.name||path4.basename(targetDir)).replaceAll(/["\\\n\r\t]/g,"").trim()||"my-app",remoteScopes=ctx.appDetails?.scopes,legacyAllSubstituted=!ctx.uiApp&&containsLegacyAllScope(remoteScopes),granularScopes=(remoteScopes??[]).filter(s=>s!==LEGACY_ALL_SCOPE),scopes;ctx.uiApp?scopes=[]:scopes=granularScopes.length>0?granularScopes:[...DEFAULT_SCOPES];let slug=computeSlug(ctx.appDetails?.name);return{vars:{"{{APP_NAME}}":appName,"{{APP_SLUG}}":slug,"{{APP_ID}}":String(appId),"{{CLIENT_ID}}":ctx.clientId,"{{CLIENT_SECRET}}":ctx.clientSecret,"{{REDIRECT_URI}}":ctx.redirectUri,"{{REDIRECT_URLS_JSON}}":JSON.stringify(ctx.redirectUris),"{{SCOPES_JSON}}":JSON.stringify(scopes),"{{DISTRIBUTION}}":ctx.appDetails?.distribution_type??"private","{{LOGO_URI}}":ctx.appDetails?.logo_uri??"","{{APP_VERSION}}":ctx.appDetails?.version??"","{{OAUTH_BASE}}":OAUTH_BASE,"{{OAUTH_REALM}}":OAUTH_REALM,"{{UI_APP_JSON}}":renderUiAppJson(ctx.uiApp)},scopes,legacyAllSubstituted}}function runBaseScaffold(appId,ctx,targetDir,mergeOnly){let{vars,scopes,legacyAllSubstituted}=buildTemplateVars(appId,ctx,targetDir),files=loadBaseTemplates(vars);return{written:writeScaffoldFiles(files,targetDir,mergeOnly),legacyAllSubstituted,scopes,files}}async function resolveFeatureConflict(featureType,appId,ctx,targetDir,opts){if(opts.overwrite)return"overwrite";let{vars}=buildTemplateVars(appId,ctx,targetDir);if(!loadFeatureTemplates(featureType,vars).some(f=>fs4.existsSync(path4.join(targetDir,f.name)))||opts.jsonMode)return"merge";let{action}=await import_inquirer3.default.prompt([{type:"list",name:"action",message:messages.APP_SCAFFOLD_FEATURE_EXISTS,choices:indentChoices([{name:messages.APP_SCAFFOLD_FEATURE_EXISTS_OVERWRITE,value:"overwrite"},{name:messages.APP_SCAFFOLD_FEATURE_EXISTS_MERGE,value:"merge"},{name:messages.APP_SCAFFOLD_FEATURE_EXISTS_CANCEL,value:"cancel"}])}]);return action}function runFeatureScaffold(featureType,appId,ctx,targetDir,mergeOnly){let{vars}=buildTemplateVars(appId,ctx,targetDir);featureType==="oauth"&&fs4.mkdirSync(path4.join(targetDir,"src","oauth"),{recursive:!0});let files=loadFeatureTemplates(featureType,vars);return{written:writeScaffoldFiles(files,targetDir,mergeOnly),files}}function reportBaseScaffoldSuccess(result){logSuccess(messages.APP_CREATE_BASE_SUCCESS(result.written,result.files.length)),result.legacyAllSubstituted&&logWarn(messages.LEGACY_ALL_SCOPE_SCAFFOLD_SUBSTITUTED(result.scopes.join(", "))),printFileTree(result.files.map(f=>f.name))}function reportScaffoldSuccess(result){logSuccess(messages.APP_SCAFFOLD_SUCCESS(result.written,result.files.length)),result.legacyAllSubstituted&&logWarn(messages.LEGACY_ALL_SCOPE_SCAFFOLD_SUBSTITUTED(result.scopes.join(", "))),printFileTree(result.files.map(f=>f.name)),printBox(messages.APP_SCAFFOLD_NEXT_STEPS_TITLE,messages.APP_SCAFFOLD_NEXT_STEPS_LINES(result.cdDir)),logInfo(messages.APP_SCAFFOLD_SCOPES_TIP)}function computeCdHint(originalCwd,targetDir){return path4.relative(originalCwd,targetDir)||void 0}var import_inquirer4=__toESM(require("inquirer"));function featureTypes(){return Object.keys(FEATURE_TEMPLATE_MANIFESTS)}function soleFeatureType(){let types=featureTypes();return types.length===1?types[0]:void 0}var FALLBACK_FEATURE="oauth";function soleFeatureLabel(){let only=soleFeatureType();return only?FEATURE_LABELS[only]:void 0}async function promptFeatureType(interactive){let types=featureTypes(),only=soleFeatureType();if(only)return only;if(!interactive)return types[0]??FALLBACK_FEATURE;let{featureType}=await import_inquirer4.default.prompt([{type:"list",name:"featureType",message:messages.APP_SCAFFOLD_FEATURE_TYPE_PROMPT,choices:indentChoices(types.map(type=>({name:FEATURE_LABELS[type],value:type})))}]);return featureType}async function promptScaffoldFeature(){let{scaffoldRaw}=await import_inquirer4.default.prompt([{type:"input",name:"scaffoldRaw",message:messages.APP_SCAFFOLD_FEATURE_CONFIRM(soleFeatureLabel())+" (Y/n)",default:"y",validate:validateYesNo}]),val=String(scaffoldRaw).toLowerCase().trim();return val===""||val.startsWith("y")}async function finishProject(params){let{appId,ctx,targetDir,cdDir,isUiApp}=params;if(isUiApp)return printBox(messages.APP_SCAFFOLD_NEXT_STEPS_TITLE,messages.APP_CREATE_UI_NEXT(cdDir)),{cancelled:!1,feature:null,written:0};if(!(params.offerFeature&&await promptScaffoldFeature()))return logInfo(messages.APP_SCAFFOLD_SCOPES_TIP),printBox(messages.APP_SCAFFOLD_NEXT_STEPS_TITLE,messages.APP_CREATE_BASE_ONLY_NEXT(cdDir)),{cancelled:!1,feature:null,written:0};let feature=await promptFeatureType(!0),mergeOnly;if(params.onConflict==="ask"){let choice=await resolveFeatureConflict(feature,appId,ctx,targetDir,{jsonMode:!!params.jsonMode,overwrite:!!params.overwriteFlag});if(choice==="cancel")return logInfo(messages.APP_SCAFFOLD_CANCELLED),{cancelled:!0};mergeOnly=choice==="merge"}else mergeOnly=params.onConflict==="merge";let feat=runFeatureScaffold(feature,appId,ctx,targetDir,mergeOnly);return reportScaffoldSuccess({written:feat.written,legacyAllSubstituted:!1,scopes:params.baseScopes,files:feat.files,targetDir,cdDir}),{cancelled:!1,feature,written:feat.written}}function formatPlacementLines(uiApp){return(uiApp.surface_point_list??[]).map(entry=>{let context=entry.context?.length?` (context: ${entry.context.join(", ")})`:"";return`${entry.surface_point_name}${context}`})}function validateHttpUrl(trimmed,invalidMessage){try{let parsed=new URL(trimmed);return parsed.protocol!=="http:"&&parsed.protocol!=="https:"?invalidMessage:!0}catch{return invalidMessage}}var validateRedirectUrl=input=>{let trimmed=input.trim();return trimmed?validateHttpUrl(trimmed,messages.APP_CREATE_REDIRECT_INVALID):messages.APP_CREATE_REDIRECT_EMPTY},validateLogoUrl=input=>{let trimmed=input.trim();return trimmed?validateHttpUrl(trimmed,messages.APP_CREATE_LOGO_INVALID):!0};function guardAgainstLinkedApp(){if(!hasLocalApp())return;let projectConfig=readProjectConfig(),linkedName=projectConfig?.appName||String(projectConfig?.appId??"");throw new CliError(messages.APP_CREATE_ALREADY_LINKED(linkedName))}async function resolveAppName(nameFlag){if(nameFlag){let nameCheck=validateAppName(nameFlag);if(nameCheck!==!0)throw new CliError(nameCheck);return nameFlag}return(await import_inquirer5.default.prompt([{type:"input",name:"name",message:messages.APP_CREATE_NAME_PROMPT,validate:validateAppName}])).name}async function resolveAppType(interactive){if(!interactive)return"oauth";let choices=[{name:messages.APP_CREATE_APP_TYPE_OAUTH,value:"oauth"}];return(await import_inquirer5.default.prompt([{type:"list",name:"appType",message:messages.APP_CREATE_APP_TYPE_PROMPT,choices:indentChoices(choices)}])).appType}function assertDistributionFlag(distributionFlag){validateEnum(distributionFlag,["private","public"],"--distribution"),distributionFlag==="public"&&assertFeatureAvailable("public-distribution")}async function resolveDistribution(distributionFlag,interactive){if(distributionFlag)return distributionFlag;if(!interactive)return"private";let choices=[{name:"Private (Used exclusively by your organisation)",value:"private"}];return(await import_inquirer5.default.prompt([{type:"list",name:"distribution",message:messages.APP_CREATE_TYPE_PROMPT,choices:indentChoices(choices)}])).distribution}async function promptAddAnotherRedirect(){let{anotherRaw}=await import_inquirer5.default.prompt([{type:"input",name:"anotherRaw",message:messages.APP_CREATE_REDIRECT_ANOTHER+" (y/N)",default:"n",validate:validateYesNo}]);return String(anotherRaw).toLowerCase().trim().startsWith("y")}async function promptRedirectUrls(quiet){let availablePort=await findAvailablePort(DEFAULT_PORT),defaultRedirect=availablePort==null||availablePort===DEFAULT_PORT?DEFAULT_REDIRECT_URI:`http://localhost:${availablePort}/auth/callback`;quiet||(availablePort==null?logInfo(messages.APP_CREATE_PORT_SCAN_FAILED(DEFAULT_PORT)):availablePort!==DEFAULT_PORT&&logInfo(messages.APP_CREATE_PORT_IN_USE(DEFAULT_PORT,availablePort)),logInfo(messages.APP_CREATE_REDIRECT_HINT(CLI.APP_START("oauth"))));let redirectUris=[],{redirectUrl:firstUrl}=await import_inquirer5.default.prompt([{type:"input",name:"redirectUrl",message:messages.APP_CREATE_REDIRECT_PROMPT,default:defaultRedirect,validate:validateRedirectUrl}]);for(redirectUris.push(firstUrl.trim());await promptAddAnotherRedirect();){let{nextUrl}=await import_inquirer5.default.prompt([{type:"input",name:"nextUrl",message:messages.APP_CREATE_REDIRECT_PROMPT,validate:validateRedirectUrl}]);redirectUris.push(nextUrl.trim())}return redirectUris}async function resolveRedirectUrls(redirectUriFlag,quiet){let flagUrls=redirectUriFlag??[];return flagUrls.length>0?flagUrls:process.stdin.isTTY?promptRedirectUrls(quiet):[DEFAULT_REDIRECT_URI]}async function resolveLogoUri(logoUriFlag,jsonMode){if(logoUriFlag||!process.stdin.isTTY||jsonMode)return logoUriFlag;let{logoUrl}=await import_inquirer5.default.prompt([{type:"input",name:"logoUrl",message:messages.APP_CREATE_LOGO_PROMPT,validate:validateLogoUrl}]);return String(logoUrl??"").trim()||void 0}async function resolveCreateDirectory(appName,interactive){let slug=computeSlug(appName);if(!interactive){let targetDir=path5.resolve(`./${slug}`);return fs5.existsSync(targetDir)?{targetDir,skipped:!0}:{targetDir,mergeOnly:!1,skipped:!1,existed:!1}}let dir=await resolveProjectDirectory(`./${slug}`);for(;!dir.unresolved&&dir.chooseAgain;)dir=await resolveProjectDirectory(`./${slug}`);if(dir.unresolved)throw new CliError(messages.APP_CREATE_DIR_UNRESOLVED);return{targetDir:dir.targetDir,mergeOnly:dir.mergeOnly,skipped:!1,existed:dir.existed}}function applyCreateDirectory(dir,jsonMode){dir.skipped||applyProjectDirectory({targetDir:dir.targetDir,mergeOnly:dir.mergeOnly,chooseAgain:!1,existed:dir.existed},jsonMode)}function buildCreatePayload(inputs){let isUiApp=!!inputs.uiApp;return{name:inputs.appName,distribution_type:inputs.distribution,...isUiApp?{ui_app:inputs.uiApp}:{auth:{scopes:[...DEFAULT_SCOPES],redirect_uris:inputs.redirectUris}},...inputs.logoUri?{logo_uri:inputs.logoUri}:{}}}async function retryCreateWithNewName(inputs){logError(messages.APP_CREATE_NAME_TAKEN);let retry=await import_inquirer5.default.prompt([{type:"input",name:"name",message:messages.APP_CREATE_NAME_PROMPT,validate:validateAppName}]),retrySpinner=createSpinner("Creating app...");try{let result=await appService.createApp(buildCreatePayload({...inputs,appName:retry.name}));return retrySpinner.stop(),{result,appName:retry.name}}catch(retryErr){throw retrySpinner.stop(),retryErr}}async function retryCreateAfterLogin(inputs){logWarn(messages.APP_CREATE_SESSION_EXPIRED);let{relogin}=await import_inquirer5.default.prompt([{type:"confirm",name:"relogin",message:messages.APP_CREATE_RELOGIN_CONFIRM,default:!0}]);if(!relogin)throw new AuthExpiredError;if(await loginCommand({suppressNextSteps:!0}),!isAuthenticated())throw new AuthExpiredError;let spinner=createSpinner("Creating app...");try{return{result:await appService.createApp(buildCreatePayload(inputs)),appName:inputs.appName}}finally{spinner.stop()}}function isPublicDistributionRefusal(err,distribution){return err instanceof ApiError&&err.statusCode===400&&distribution==="public"&&/distribution_type/i.test(err.message)}async function createAppWithRetry(inputs,jsonMode,interactive){let spinner=createSpinner("Creating app...",{silent:jsonMode});try{let result=await appService.createApp(buildCreatePayload(inputs));return spinner.stop(),{result,appName:inputs.appName}}catch(err){if(spinner.stop(),err instanceof ApiError&&err.errorCode==="APP_LIMIT_REACHED")throw jsonMode&&jsonOutput({error:"APP_LIMIT_REACHED",message:messages.APP_CREATE_LIMIT_REACHED}),new CliError(messages.APP_CREATE_LIMIT_REACHED);if(isPublicDistributionRefusal(err,inputs.distribution))throw new CliError(messages.APP_CREATE_PUBLIC_REJECTED(err.message));if(err instanceof ApiError&&err.statusCode===409)return retryCreateWithNewName(inputs);if(err instanceof AuthExpiredError&&interactive)return retryCreateAfterLogin(inputs);throw err}}function renderCreatedApp(result,appName,logoUri){let boxLines=[`App name: ${appName}`,`App ID: ${result.app_id}`,`Client ID: ${result.client_id}`,`Client secret: ${messages.CLIENT_SECRET_HIDDEN_HUMAN}`,...(result.redirect_uris??[]).map((uri,i)=>`Redirect URL ${i+1}: ${uri}`),...logoUri?[`Logo URL: ${logoUri}`]:[],...result.version?[`App version: ${result.version}`]:[],`${messages.APP_CREATE_BOX_SCOPES_LABEL} ${[...DEFAULT_SCOPES].join(", ")}`,"",messages.APP_CREATE_BOX_SCOPE_HINT];printBox(messages.APP_CREATE_BOX_TITLE,boxLines)}var createCommand=withCommandHandler(async options=>{let jsonMode=!!options.json,originalCwd=process.cwd();guardAgainstLinkedApp(),assertDistributionFlag(options.distribution);let interactive=!jsonMode&&!!process.stdin.isTTY,appName=await resolveAppName(options.name),logoUri=await resolveLogoUri(options.logoUri,jsonMode),distribution=await resolveDistribution(options.distribution,interactive),appType=await resolveAppType(interactive),redirectUris=[],uiApp;redirectUris=await resolveRedirectUrls(options.redirectUri,jsonMode);let dir=await resolveCreateDirectory(appName,interactive),inputs={appName,distribution,redirectUris,logoUri,uiApp},{result,appName:finalAppName}=await createAppWithRetry(inputs,jsonMode,interactive);applyCreateDirectory(dir,jsonMode),result.client_id&&result.client_secret&&saveAppCredentials(result.app_id,{clientId:result.client_id,clientSecret:result.client_secret}),finalAppName&&saveAppName(result.app_id,finalAppName);let jsonBase={appId:result.app_id,appName:finalAppName,clientId:result.client_id,clientSecret:messages.CLIENT_SECRET_HIDDEN_JSON,appType,...uiApp?{uiApp}:{redirectUri:result.redirect_uris},...logoUri?{logoUri}:{},...result.version?{version:result.version}:{}},renderBox2=()=>renderCreatedApp(result,finalAppName,logoUri);if(dir.skipped){if(jsonMode){jsonOutput({...jsonBase,directory:dir.targetDir,scaffoldSkipped:messages.APP_CREATE_JSON_SCAFFOLD_DIR_EXISTS(dir.targetDir)});return}renderBox2(),logInfo(messages.APP_CREATE_DIR_EXISTS_SKIPPED(dir.targetDir));return}let fallbackApp={...result,client_id:result.client_id??"",redirect_uris:result.redirect_uris??null},ctx=await fetchAppContext(result.app_id,jsonMode,uiApp,fallbackApp),base=runBaseScaffold(result.app_id,ctx,dir.targetDir,dir.mergeOnly);if(jsonMode){jsonOutput({...jsonBase,directory:dir.targetDir,scaffolded:base.written});return}renderBox2(),reportBaseScaffoldSuccess(base),await finishProject({appId:result.app_id,ctx,targetDir:dir.targetDir,baseScopes:base.scopes,cdDir:computeCdHint(originalCwd,dir.targetDir),isUiApp:!!uiApp,offerFeature:interactive,onConflict:dir.mergeOnly?"merge":"overwrite"})});var http=__toESM(require("node:http")),import_node_crypto=require("node:crypto");var MAX_BODY_BYTES=16*1024,DEFAULT_TIMEOUT_MS=3e5;function normalizeTokens(raw){return typeof raw.access_token!="string"||!raw.access_token||typeof raw.refresh_token!="string"||!raw.refresh_token||typeof raw.expires_in!="number"||!Number.isFinite(raw.expires_in)||raw.expires_in<=0||typeof raw.token_type!="string"||!raw.token_type?null:{accessToken:raw.access_token,refreshToken:raw.refresh_token,expiresIn:raw.expires_in,tokenType:raw.token_type,scope:typeof raw.scope=="string"?raw.scope:void 0}}async function runBrowserLoginFlow(opts){let proxyOrigin=new URL(opts.proxyUrl).origin,timeoutMs=opts.timeoutMs??DEFAULT_TIMEOUT_MS,openBrowser2=opts.openBrowser??(()=>{});return new Promise((resolve11,reject)=>{let settled=!1,claimSettlement=()=>settled?!1:(settled=!0,!0),server=http.createServer((req,res)=>{let pathname=new URL(req.url??"/","http://127.0.0.1").pathname,origin=req.headers.origin;if(logDebug("loopback request",{method:req.method,url:req.url,pathname,origin}),req.method==="OPTIONS"&&pathname==="/callback"){if(origin!==proxyOrigin){logDebug("loopback OPTIONS rejected: origin mismatch",{origin,expected:proxyOrigin}),res.writeHead(403).end();return}res.writeHead(204,{"Access-Control-Allow-Origin":proxyOrigin,"Access-Control-Allow-Methods":"POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type","Access-Control-Max-Age":"600"}).end();return}if(req.method==="POST"&&pathname==="/callback"){if(origin!==proxyOrigin){logDebug("loopback POST rejected: origin mismatch",{origin,expected:proxyOrigin}),res.writeHead(403).end();return}let bytes=0,chunks=[];req.on("data",chunk=>{if(bytes+=chunk.length,bytes>MAX_BODY_BYTES){logDebug("loopback POST rejected: body too large",{bytes,max:MAX_BODY_BYTES}),res.writeHead(413,{Connection:"close"}).end(),req.destroy();return}chunks.push(chunk)}),req.on("end",()=>{let parsed=null;try{parsed=JSON.parse(Buffer.concat(chunks).toString("utf-8"))}catch{parsed=null}let tokens=parsed?normalizeTokens(parsed):null;if(!tokens){logDebug("loopback POST rejected: bad payload shape",{hasParsed:parsed!==null,keys:parsed?Object.keys(parsed):null}),res.writeHead(400,{"Access-Control-Allow-Origin":proxyOrigin,"Content-Type":"text/plain"}).end("Bad payload");return}logDebug("loopback POST accepted",{hasScope:tokens.scope!==void 0}),res.writeHead(204,{"Access-Control-Allow-Origin":proxyOrigin}).end(),claimSettlement()&&(server.close(),resolve11(tokens))});return}if(req.method==="GET"&&(pathname==="/"||pathname==="/callback")){res.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),res.end('<!doctype html><meta charset="utf-8"><title>Brevo CLI login</title><p>Waiting for login to complete \u2014 you can close this tab once the CLI confirms success.</p>');return}logDebug("loopback request not matched",{method:req.method,pathname}),res.writeHead(404).end()});server.on("error",err=>{logDebug("loopback server error",{message:err.message}),claimSettlement()&&reject(err)}),server.listen(0,"127.0.0.1",()=>{let port=server.address().port,attemptToken=(0,import_node_crypto.randomUUID)(),loginUrl=`${opts.proxyUrl}/login?port=${port}&t=${attemptToken}`;logDebug("loopback listening",{host:"127.0.0.1",port,proxyOrigin}),opts.onWaiting?.(loginUrl);try{openBrowser2(loginUrl)}catch{}});let timer=setTimeout(()=>{claimSettlement()&&(server.close(),reject(new CliError(messages.AUTH_BROWSER_TIMEOUT)))},timeoutMs);timer.unref?.(),server.on("close",()=>clearTimeout(timer))})}function wipeAppsCacheIfAccountChanged(newOrganizationId){let previousOrganizationId=getOrganizationId();previousOrganizationId&&previousOrganizationId!==newOrganizationId&&clearAppsCache()}async function promptApiKey(){let{key}=await import_inquirer6.default.prompt([{type:"password",name:"key",message:messages.AUTH_PROMPT_API_KEY,mask:"*",validate:input=>input.trim().length>0||"API key cannot be empty"}]);return key}async function resolveLoginMethod(forceBrowser,apiKey){if(forceBrowser){if(!process.stdin.isTTY)throw new CliError(messages.AUTH_BROWSER_NON_INTERACTIVE);return"browser"}if(apiKey)return"api-key";if(!process.stdin.isTTY)throw new CliError(messages.AUTH_BROWSER_NON_INTERACTIVE);let{chosen}=await import_inquirer6.default.prompt([{type:"list",name:"chosen",message:messages.AUTH_PROMPT_METHOD,choices:indentChoices([{name:"Browser (sign in through your browser)",value:"browser"},{name:"API key (paste from your Brevo dashboard)",value:"api-key"}]),default:"browser"}]);return chosen}async function retryApiKeyValidation(quiet){let retryKey=await promptApiKey(),retrySpinner=createSpinner("Validating API key...",{silent:quiet});try{let account=await accountService.validateApiKey(retryKey);return retrySpinner.stop(),{account,apiKey:retryKey}}catch(retryErr){throw retrySpinner.stop(),retryErr instanceof ApiError&&retryErr.statusCode===401?new CliError(messages.AUTH_INVALID_KEY,EXIT_CODES.AUTH_FAILURE):retryErr}}async function validateApiKeyWithRetry(apiKey,quiet){let spinner=createSpinner("Validating API key...",{silent:quiet});try{let account=await accountService.validateApiKey(apiKey);return spinner.stop(),{account,apiKey}}catch(err){if(spinner.stop(),!(err instanceof ApiError&&err.statusCode===401)||(logError(messages.AUTH_INVALID_KEY),quiet||logInfo(` ${messages.AUTH_GET_KEY_URL}`),!process.stdin.isTTY))throw err;return retryApiKeyValidation(quiet)}}async function loginWithApiKey(envApiKey,quiet){let apiKey=envApiKey;if(apiKey||(openBrowser(BREVO_DASHBOARD_API_KEYS_URL),quiet||process.stdout.write(messages.AUTH_HINT(BREVO_DASHBOARD_API_KEYS_URL,BREVO_API_KEY_DOCS_URL)),apiKey=await promptApiKey()),!apiKey)throw new CliError("No API key provided.");let validated=await validateApiKeyWithRetry(apiKey,quiet);if(!validated.account)throw new CliError("Authentication failed.");return wipeAppsCacheIfAccountChanged(validated.account.organization_id),saveCredentials(validated.apiKey,{email:validated.account.email,organizationId:validated.account.organization_id,userId:validated.account.user_id}),validated.account}async function loginWithBrowser(quiet){quiet||logInfo(` ${messages.AUTH_BROWSER_OPENING}`);let tokens=await runBrowserLoginFlow({proxyUrl:OAUTH_PROXY_URL,openBrowser,onWaiting:url=>{quiet||(logInfo(` ${messages.AUTH_BROWSER_FALLBACK_URL(url)}`),logInfo(` ${messages.AUTH_BROWSER_WAITING}`))}}),tokensToStore={accessToken:tokens.accessToken,refreshToken:tokens.refreshToken,expiresIn:tokens.expiresIn,tokenType:tokens.tokenType,scope:tokens.scope};saveOauthCredentials(tokensToStore),quiet||logSuccess(messages.AUTH_BROWSER_TOKENS_RECEIVED(getCredentialsPath()));let spinner=createSpinner("Finishing login...",{silent:quiet}),account;try{account=await client.getWithBearer(ENDPOINTS.ACCOUNT,tokens.accessToken,tokens.tokenType)}catch(err){throw err instanceof ApiError&&err.statusCode===401&&clearCredentials(),err}finally{spinner.stop()}if(!account)throw new CliError("Authentication failed.");return wipeAppsCacheIfAccountChanged(account.organization_id),saveOauthCredentials(tokensToStore,{email:account.email,organizationId:account.organization_id,userId:account.user_id}),account}async function showNextSteps(){let apps=[],appsSpinner=createSpinner("Checking your apps...");try{apps=await appService.fetchAppsList()}catch{}finally{appsSpinner.stop()}if(apps.length>0){printBox("What's next?",[CLI.APP_CREATE,CLI.APP_LIST,CLI.APP_SCAFFOLD,CLI.APP_CREDENTIALS()]);return}if(!process.stdin.isTTY){logInfo(`
127
+ ${messages.AUTH_NEXT}
128
+ `);return}process.stdout.write(`
129
+ `);let{shouldCreate}=await import_inquirer6.default.prompt([{type:"confirm",name:"shouldCreate",message:messages.AUTH_CREATE_APP_PROMPT,default:!0}]);if(shouldCreate){process.stdout.write(`
130
+ `),await createCommand({});return}logInfo(`
131
+ ${messages.AUTH_NEXT}
132
+ `)}var loginCommand=withCommandHandler(async options=>{let quiet=!!options.json;quiet||(process.stdout.write(`
133
+ ${messages.AUTH_WELCOME}
134
+ `),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
135
+ `));let apiKey=process.env.BREVO_API_KEY,account=await resolveLoginMethod(options.browser,apiKey)==="api-key"?await loginWithApiKey(apiKey,quiet):await loginWithBrowser(quiet);if(options.json){jsonOutput({authenticated:!0,email:account.email,company:account.companyName});return}logSuccess(messages.AUTH_SUCCESS(account.email)),logInfo(messages.AUTH_SAVED(getCredentialsPath())),!options.suppressNextSteps&&await showNextSteps()});var import_inquirer7=__toESM(require("inquirer"));function stripKeysDeep(value,keys){return Array.isArray(value)?value.map(entry=>stripKeysDeep(entry,keys)):value&&typeof value=="object"?Object.fromEntries(Object.entries(value).filter(([key])=>!keys.includes(key)).map(([k,v])=>[k,stripKeysDeep(v,keys)])):value}function stripUiAppWireOnlyKeys(uiApp){return stripKeysDeep(uiApp,appTypeById("ui").wireOnlyKeys)}function stripUiAppWireOnlyKeysFrom(value){return stripKeysDeep(value,appTypeById("ui").wireOnlyKeys)}async function resolveBaseRefresh(localConfig,ctx,jsonMode){let diffs=diffLocalConfig(localConfig,ctx);if(diffs.length===0)return{cancelled:!1,refreshBase:!1};if(jsonMode)return{cancelled:!0,reason:messages.APP_SCAFFOLD_JSON_DIFF_CANCELLED,diffs};logInfo(messages.APP_SCAFFOLD_DIFF_INTRO(localConfig.appName||localConfig.appId));for(let diff of diffs)logInfo(messages.APP_SCAFFOLD_DIFF_LINE(diff.field,diff.local,diff.server));let{confirmed}=await import_inquirer7.default.prompt([{type:"confirm",name:"confirmed",message:messages.APP_SCAFFOLD_DIFF_CONFIRM,default:!0}]);return confirmed?{cancelled:!1,refreshBase:!0}:{cancelled:!0}}async function resolveScaffoldPlan(localConfig,jsonMode){let appId=localConfig.appId,ctx=await fetchAppContext(appId,jsonMode,localConfig.ui_app),refresh=await resolveBaseRefresh(localConfig,ctx,jsonMode);return refresh.cancelled?refresh:{cancelled:!1,appId,ctx,refreshBase:refresh.refreshBase}}async function resolveBootstrapPlan(appId,jsonMode){jsonMode||logInfo(messages.APP_SCAFFOLD_BOOTSTRAP_INTRO(appId));let probe=await fetchAppContext(appId,jsonMode),record=probe.appDetails,appType=resolveFromRecord(record);if(record&&!appType.recoverableFromRecord(record))throw new CliError(messages.APP_SCAFFOLD_BOOTSTRAP_UNRECOVERABLE(appId));let serverUiApp=record?.ui_app?stripUiAppWireOnlyKeys(record.ui_app):void 0,ctx=serverUiApp?{...probe,uiApp:serverUiApp}:probe;return{cancelled:!1,appId,ctx,refreshBase:!0}}async function resolveBootstrapAppId(requestedAppId,jsonMode){if(requestedAppId)return requestedAppId;if(jsonMode||!process.stdin.isTTY)throw new CliError(messages.APP_SCAFFOLD_NO_CONFIG);logInfo(messages.APP_SCAFFOLD_BOOTSTRAP_OFFER);let{useExisting}=await import_inquirer7.default.prompt([{type:"confirm",name:"useExisting",message:messages.APP_SCAFFOLD_BOOTSTRAP_CONFIRM,default:!0}]);if(!useExisting)return;let{appId}=await promptAppSelection(messages.APP_SCAFFOLD_SELECT);return appId}async function resolveBootstrapDirectory(ctx,jsonMode,appId){if(jsonMode||!process.stdin.isTTY)return;let refuseIfLinkedElsewhere=targetDir=>{let targetConfig=readProjectConfigAt(targetDir);if(targetConfig&&targetConfig.appId!==appId)throw new CliError(messages.APP_SCAFFOLD_TARGET_LINKED_ELSEWHERE(targetDir,targetConfig.appId,appId))},defaultDir=`./${computeSlug(ctx.appDetails?.name)}`,dir=await resolveProjectDirectory(defaultDir,!1,refuseIfLinkedElsewhere);for(;!dir.unresolved&&dir.chooseAgain;)dir=await resolveProjectDirectory(defaultDir,!1,refuseIfLinkedElsewhere);if(dir.unresolved)throw new CliError(messages.APP_CREATE_DIR_UNRESOLVED);return applyProjectDirectory(dir),{targetDir:dir.targetDir,mergeOnly:dir.mergeOnly}}var BOOTSTRAP_DECLINED=Symbol("bootstrap-declined");async function resolveBootstrapTarget(localConfig,requestedAppId,jsonMode){if(localConfig){if(requestedAppId&&localConfig.appId!==requestedAppId)throw new CliError(messages.APP_SCAFFOLD_APP_ID_MISMATCH(localConfig.appId,requestedAppId));return}let enclosingProject=findEnclosingProjectDir();if(enclosingProject)throw new CliError(messages.APP_SCAFFOLD_INSIDE_PROJECT(enclosingProject));return await resolveBootstrapAppId(requestedAppId,jsonMode)||BOOTSTRAP_DECLINED}function reportPlanCancelled(plan,jsonMode){if(jsonMode){jsonOutput({cancelled:!0,...plan.reason?{reason:plan.reason}:{},...plan.diffs?{diffs:plan.diffs}:{}});return}logInfo(messages.APP_SCAFFOLD_CANCELLED)}async function resolveScaffoldLayout(localConfig,appId,ctx,planRefreshBase,jsonMode){let originalCwd=process.cwd(),bootstrapDir=localConfig?void 0:await resolveBootstrapDirectory(ctx,jsonMode,appId),targetDir=bootstrapDir?.targetDir??process.cwd(),cdDir=bootstrapDir?computeCdHint(originalCwd,targetDir):void 0,baseMergeOnly=bootstrapDir?.mergeOnly??!1,refreshBase=planRefreshBase;if(bootstrapDir){let targetConfig=readProjectConfigAt(targetDir);if(targetConfig){let refresh=await resolveBaseRefresh(targetConfig,ctx,jsonMode);if(refresh.cancelled)return null;refreshBase=refresh.refreshBase,refresh.refreshBase&&(baseMergeOnly=!1)}}return{targetDir,cdDir,refreshBase,baseMergeOnly}}function finishUiAppScaffold(appId,ctx,layout,jsonMode){let{targetDir,cdDir,refreshBase,baseMergeOnly}=layout,base=refreshBase?runBaseScaffold(appId,ctx,targetDir,baseMergeOnly):null;if(jsonMode){jsonOutput({scaffolded:base?.written??0,directory:targetDir,features:[],reason:messages.APP_SCAFFOLD_NO_FEATURES_FOR_UI_APP});return}base&&(logSuccess(messages.APP_CREATE_BASE_SUCCESS(base.written,base.files.length)),printFileTree(base.files.map(f=>f.name))),logInfo(messages.APP_SCAFFOLD_NO_FEATURES_FOR_UI_APP),cdDir&&printBox(messages.APP_SCAFFOLD_NEXT_STEPS_TITLE,messages.APP_CREATE_UI_NEXT(cdDir))}async function finishInteractiveBootstrap(appId,ctx,layout,jsonMode,overwrite){let{targetDir,cdDir,refreshBase,baseMergeOnly}=layout,bootstrapBase=refreshBase?runBaseScaffold(appId,ctx,targetDir,baseMergeOnly):null;bootstrapBase?reportBaseScaffoldSuccess(bootstrapBase):logInfo(messages.APP_SCAFFOLD_BASE_IN_SYNC),await finishProject({appId,ctx,targetDir,baseScopes:bootstrapBase?.scopes??[],cdDir,isUiApp:!1,offerFeature:!0,onConflict:"ask",jsonMode,overwriteFlag:overwrite})}async function finishFeatureScaffold(appId,ctx,layout,jsonMode,overwrite){let{targetDir,cdDir,refreshBase,baseMergeOnly}=layout,feature=await promptFeatureType(!jsonMode),conflict=await resolveFeatureConflict(feature,appId,ctx,targetDir,{jsonMode,overwrite});if(conflict==="cancel"){logInfo(messages.APP_SCAFFOLD_CANCELLED);return}let featureMergeOnly=conflict==="merge",base=refreshBase?runBaseScaffold(appId,ctx,targetDir,baseMergeOnly):null,feat=runFeatureScaffold(feature,appId,ctx,targetDir,featureMergeOnly),written=(base?.written??0)+feat.written,files=[...base?.files??[],...feat.files];if(jsonMode){jsonOutput({scaffolded:written,directory:targetDir});return}reportScaffoldSuccess({written,legacyAllSubstituted:base?.legacyAllSubstituted??!1,scopes:base?.scopes??[],files,targetDir,cdDir})}var scaffoldCommand=withCommandHandler(async options=>{let jsonMode=!!options.json,overwrite=!!options.overwrite,requestedAppId=options.appId?.trim()||void 0,localConfig=readProjectConfig(),bootstrapAppId=await resolveBootstrapTarget(localConfig,requestedAppId,jsonMode);if(bootstrapAppId===BOOTSTRAP_DECLINED){logInfo(messages.APP_SCAFFOLD_BOOTSTRAP_DECLINED);return}let plan=localConfig?await resolveScaffoldPlan(localConfig,jsonMode):await resolveBootstrapPlan(bootstrapAppId,jsonMode);if(plan.cancelled){reportPlanCancelled(plan,jsonMode);return}let{appId,ctx}=plan,layout=await resolveScaffoldLayout(localConfig,appId,ctx,plan.refreshBase,jsonMode);if(!layout){logInfo(messages.APP_SCAFFOLD_CANCELLED);return}if(localConfig?isUiAppConfig(localConfig):!!ctx.uiApp){finishUiAppScaffold(appId,ctx,layout,jsonMode);return}if(!localConfig&&!jsonMode&&!!process.stdin.isTTY){await finishInteractiveBootstrap(appId,ctx,layout,jsonMode,overwrite);return}await finishFeatureScaffold(appId,ctx,layout,jsonMode,overwrite)});function isAuthRejection(err){return err instanceof AuthExpiredError?!0:!(err instanceof ApiError)||err.errorCode==="AUTH_GATEWAY"?!1:err.statusCode===401||err.statusCode===403}async function ensureLoggedIn(){if(isAuthenticated()){let spinner=createSpinner("Verifying credentials...");try{await accountService.getAccount(),spinner.stop(),logSuccess(messages.INIT_ALREADY_LOGGED_IN);return}catch(err){if(spinner.stop(),!isAuthRejection(err)){logDebug("init credential probe inconclusive",{reason:err instanceof Error?err.message:String(err)}),logWarn(messages.INIT_VERIFY_UNAVAILABLE);return}logWarn(messages.AUTH_EXPIRED)}}if(logInfo(messages.INIT_STEP_LOGIN),await loginCommand({suppressNextSteps:!0}),!isAuthenticated())throw new CliError("Login failed.")}async function appExistsOnServer(appId){if(!appId)return!1;let spinner=createSpinner("Verifying app...");try{return await appService.fetchApp(appId)!==null}catch{return!1}finally{spinner.stop()}}async function promptLinkedAppAction(configAppId,linkedName){logSuccess(messages.INIT_APP_LINKED(linkedName));let{action}=await import_inquirer8.default.prompt([{type:"list",name:"action",message:messages.INIT_APP_ACTION,choices:indentChoices([{name:"Scaffold this app",value:"scaffold"},{name:"Create a new app",value:"create"},{name:"Skip \u2014 I'm all set",value:"skip"}])}]);return action}function initDoneMessage(){return isUiAppConfig(readProjectConfig())?messages.INIT_DONE_UI_APP:messages.INIT_DONE}var initCommand=withCommandHandler(async _options=>{process.stdout.write(`
136
+ ${messages.INIT_WELCOME}
137
+ `),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
138
+
139
+ `),await ensureLoggedIn(),process.stdout.write(`
140
+ `);let projectConfig=readProjectConfig(),configAppId=typeof projectConfig?.appId=="string"?projectConfig.appId.trim():"",linkedName=projectConfig?.appName||configAppId;if(configAppId&&await appExistsOnServer(configAppId)){let action=await promptLinkedAppAction(configAppId,linkedName);if(action==="skip"){logInfo(`
141
+ ${initDoneMessage()}
142
+ `);return}if(action==="scaffold"){process.stdout.write(`
143
+ `),await scaffoldCommand({}),logInfo(`
144
+ ${initDoneMessage()}
145
+ `);return}}else configAppId?(logWarn(`App "${linkedName}" (from app-config.json) no longer exists on the server. It may have been deleted.`),logInfo(messages.INIT_STEP_CREATE)):logInfo(messages.INIT_STEP_CREATE);process.stdout.write(`
146
+ `),await createCommand({}),logInfo(`
147
+ ${initDoneMessage()}
148
+ `)});var import_inquirer9=__toESM(require("inquirer"));var logoutCommand=withCommandHandler(async options=>{if(!isAuthenticated()){options.json?jsonOutput({loggedOut:!1,reason:"not_authenticated"}):logInfo(`
149
+ ${messages.AUTH_NOT_LOGGED_IN}
150
+ `);return}if(!options.force&&!options.json&&hasAppCredentials()){if(!process.stdin.isTTY)throw new CliError(messages.AUTH_LOGOUT_NON_INTERACTIVE);logWarn(messages.AUTH_LOGOUT_APP_WARNING);let{confirmed}=await import_inquirer9.default.prompt([{type:"confirm",name:"confirmed",message:messages.AUTH_LOGOUT_CONFIRM,default:!1}]);if(!confirmed){logInfo(`
151
+ ${messages.ABORTED}
152
+ `);return}}let appCount=countAppCredentials();deleteCredentialsFile(),options.json?jsonOutput({loggedOut:!0,appsCleared:appCount}):appCount>0?logSuccess(messages.AUTH_LOGGED_OUT_WITH_APPS(appCount)):logSuccess(messages.AUTH_LOGGED_OUT)});function findCredentialMismatches(account){let mismatched=[],storedEmail=getEmail(),storedOrgId=getOrganizationId(),storedUserId=getUserId();return storedEmail&&storedEmail!==account.email&&mismatched.push("email"),storedOrgId&&storedOrgId!==account.organization_id&&mismatched.push("organization"),storedUserId!=null&&storedUserId!==account.user_id&&mismatched.push("user id"),mismatched}function renderAccount(account){let authKind=getAuthCred()?.kind,kindLabel="";authKind==="oauth"?kindLabel=" (browser login)":authKind==="api-key"&&(kindLabel=" (API key)"),logInfo(`
153
+ ${messages.WHOAMI_AUTHENTICATED(account.email,account.companyName||"N/A")}${kindLabel}
154
+ Organization: ${account.organization_id}
155
+ User ID: ${account.user_id}
156
+ `)}var whoamiCommand=withCommandHandler(async options=>{if(!isAuthenticated())throw options.json&&jsonOutput({authenticated:!1,reason:"no_key"}),new CliError(messages.WHOAMI_NOT_AUTHENTICATED);let spinner=createSpinner("Checking credentials...",{silent:options.json});try{let account=await accountService.getAccount();spinner.stop();let mismatched=findCredentialMismatches(account);if(mismatched.length>0)throw options.json&&jsonOutput({authenticated:!1,reason:"credential_mismatch",mismatchedFields:mismatched}),new CliError(messages.WHOAMI_CREDENTIAL_MISMATCH(mismatched));options.json?jsonOutput({authenticated:!0,authKind:getAuthCred()?.kind,email:account.email,company:account.companyName||"",organizationId:account.organization_id,userId:account.user_id}):renderAccount(account)}catch(err){throw spinner.stop(),err instanceof CliError?err:(options.json&&jsonOutput({authenticated:!1,reason:"expired"}),new CliError(messages.AUTH_EXPIRED))}});var listCommand=withCommandHandler(async options=>{let spinner=createSpinner("Fetching apps...",{silent:options.json}),apps;try{apps=await appService.fetchAppsList()}finally{spinner.stop()}let merged=mergeCachedNames(apps||[]);if(options.json){jsonOutput(merged.map(toJsonApp));return}if(merged.length===0){logInfo(`
157
+ ${messages.APP_LIST_EMPTY}
158
+ `);return}logInfo(`
159
+ ${messages.APP_LIST_HEADER}
160
+ `);for(let app of merged)printApp(app)});function mergeCachedNames(apps){let cachedNames=getAppNames();return apps.map(app=>{let cached=cachedNames[app.app_id];return cached?cached===app.name?(deleteAppName(app.app_id),app):{...app,name:cached}:app})}function toJsonApp({client_secret:_secret,...rest}){return{...rest,...containsLegacyAllScope(rest.scopes)?{legacy_all_scope:!0}:{}}}function printApp(app){let name=app.name||"\u2014",appType=resolveFromRecord(app),isUiApp=appType.id==="ui";process.stdout.write(` ${name} (App ID: ${app.app_id})
161
+ `),process.stdout.write(` Type: ${appType.label}
162
+ `),isUiApp||printOAuthIdentity(app),process.stdout.write(` Logo URL: ${app.logo_uri||"(none)"}
163
+ `),process.stdout.write(` Version: ${app.version||"(none)"}
164
+ `),isUiApp||printScopes(app),app.ui_app&&printUiApp(app.ui_app),process.stdout.write(`
165
+ `)}function printOAuthIdentity(app){process.stdout.write(` Client ID: ${app.client_id}
166
+ `);let redirectUris=app.redirect_uris??[];if(redirectUris.length===0){process.stdout.write(` Redirect URLs: (none)
167
+ `);return}redirectUris.forEach((uri,i)=>{process.stdout.write(` Redirect URL ${i+1}: ${uri}
168
+ `)})}function printScopes(app){let scopes=app.scopes??[],legacyTag=containsLegacyAllScope(scopes)?messages.LEGACY_ALL_SCOPE_LIST_TAG:"";process.stdout.write(` Scopes: ${scopes.length>0?scopes.join(", "):"(none)"}${legacyTag}
169
+ `)}function printUiApp(uiApp){process.stdout.write(` Extension: ${uiApp.extension_type}
170
+ `),formatPlacementLines(uiApp).forEach((line,i)=>{process.stdout.write(` ${i===0?"Placement: ":" "}${line}
171
+ `)}),uiApp.label&&process.stdout.write(` Label: ${uiApp.label}
172
+ `),uiApp.more_info&&process.stdout.write(` More info: ${uiApp.more_info}
173
+ `),uiApp.redirect_link&&process.stdout.write(` Link: ${uiApp.redirect_link}
174
+ `),uiApp.modal_iframe_url&&process.stdout.write(` Modal URL: ${uiApp.modal_iframe_url}
175
+ `)}var import_inquirer10=__toESM(require("inquirer"));async function resolveSecretReveal(revealRequested,app){if(!revealRequested)return{display:messages.CLIENT_SECRET_HIDDEN_HUMAN,revealed:!1};if(!process.stdin.isTTY)return logInfo(" Secret not revealed in non-interactive mode. Use --reveal-secret in a terminal."),{display:messages.CLIENT_SECRET_HIDDEN_HUMAN,revealed:!1};let{confirmed}=await import_inquirer10.default.prompt([{type:"confirm",name:"confirmed",message:messages.APP_CREDENTIALS_REVEAL_CONFIRM,default:!1}]);return confirmed?{display:app.client_secret||messages.CLIENT_SECRET_NOT_AVAILABLE,revealed:!0}:{display:messages.CLIENT_SECRET_HIDDEN_HUMAN,revealed:!1}}function printCredentialsHuman(app,appId,secretDisplay){logInfo(`
176
+ App name: ${app.name||"\u2014"}`),logInfo(` App ID: ${appId}`),logInfo(` Client ID: ${app.client_id}`),logInfo(` Client secret: ${secretDisplay}`),app.scopes&&app.scopes.length>0?logInfo(` Scopes: ${app.scopes.join(", ")}`):logInfo(" Scopes: (none)");let redirectUris=app.redirect_uris??[];redirectUris.length>0?redirectUris.forEach((uri,i)=>{logInfo(` Redirect URL ${i+1}: ${uri}`)}):logInfo(" Redirect URLs: (none)"),process.stdout.write(`
177
+ `)}async function reconcileLocalCache(appId,app,diffs,jsonMode){if(diffs.length===0){appService.syncAppCredentials(appId,app);return}if(logWarn(`Local credentials for app ${appId} differ from server (${diffs.join(", ")}).`),!process.stdin.isTTY||jsonMode){appService.syncAppCredentials(appId,app);return}let{shouldUpdate}=await import_inquirer10.default.prompt([{type:"confirm",name:"shouldUpdate",message:"Update local credentials to match the server?",default:!0}]);shouldUpdate&&(appService.syncAppCredentials(appId,app),logInfo(` Local credentials updated.
178
+ `))}var credentialsCommand=withCommandHandler(async options=>{options.appId||assertAppSelectionAllowed(CLI.APP_CREDENTIALS(),options.json);let appId=options.appId??await appService.pickApp(messages.APP_CREDENTIALS_SELECT),spinner=createSpinner("Fetching credentials...",{silent:options.json}),result=await appService.resolveAppCredentials(appId);if(spinner.stop(),!result)throw new CliError(`App ${appId} not found.`);let{app,diffs}=result;app.name&&saveAppName(appId,app.name);let{display:secretDisplay,revealed:revealConfirmed}=await resolveSecretReveal(options.revealSecret,app);options.json?jsonOutput({appName:app.name||null,appId,clientId:app.client_id,clientSecret:revealConfirmed?app.client_secret??messages.CLIENT_SECRET_NOT_AVAILABLE:messages.CLIENT_SECRET_HIDDEN_JSON,scopes:app.scopes||[],redirectUris:app.redirect_uris??[]}):printCredentialsHuman(app,appId,secretDisplay),await reconcileLocalCache(appId,app,diffs,options.json);let backfilled=backfillProjectConfigFromServer(appId,{version:app.version,distribution_type:app.distribution_type});backfilled.length>0&&!options.json&&logInfo(` ${messages.APP_CREDENTIALS_CONFIG_BACKFILLED(backfilled)}
179
+ `)});var fs6=__toESM(require("node:fs")),path6=__toESM(require("node:path")),import_inquirer11=__toESM(require("inquirer"));var NON_INTERACTIVE_CONFIRM_ERROR="Cannot prompt for confirmation in non-interactive mode. Use --yes or --json to skip.";function loadUsableConfig(){let configPath=path6.resolve(process.cwd(),"app-config.json");if(!fs6.existsSync(configPath))throw new CliError(messages.APP_UPLOAD_NO_CONFIG);let raw;try{raw=JSON.parse(fs6.readFileSync(configPath,"utf-8"))}catch{throw new CliError(messages.APP_UPLOAD_INVALID_JSON)}if(!raw||typeof raw!="object"||!("appId"in raw)||!raw.appId)throw new CliError(messages.APP_UPLOAD_MISSING_APP_ID);let config=readProjectConfig();if(!config)throw new CliError(messages.APP_UPLOAD_MISSING_APP_ID);return config}function validateRedirectUrls(urls){for(let url of urls)try{let parsed=new URL(url);if(parsed.protocol!=="http:"&&parsed.protocol!=="https:")throw new CliError(messages.APP_UPLOAD_INVALID_REDIRECT_PROTOCOL(url))}catch(err){throw err instanceof CliError?err:new CliError(messages.APP_UPLOAD_INVALID_REDIRECT_URL(url))}}async function fetchExistingApp(appId,silent){let spinner=createSpinner("Fetching app...",{silent}),app;try{app=await appService.fetchApp(appId)}finally{spinner.stop()}if(!app)throw new CliError(`App ${appId} not found.`);return app}function diffLines(current,next){let currentSet=new Set(current),nextSet=new Set(next);return[...next.map(v=>currentSet.has(v)?v:`${v} (new)`),...current.filter(v=>!nextSet.has(v)).map(v=>`${v} (removed)`)]}function logAligned(label,lines){lines.forEach((line,i)=>{logInfo(`${i===0?label:" "}${line}`)})}function buildDiff(config,remote){let nextScopes=config.auth?.scopes??[];return{appId:config.appId,currentName:remote.name,nextName:config.appName,currentUrls:remote.redirect_uris??[],nextUrls:config.auth?.redirectUris??[],currentLogoUri:remote.logo_uri,nextLogoUri:config.logoUri??"",currentScopes:remote.scopes??[],nextScopes,currentDistribution:remote.distribution_type,nextDistribution:config.distribution_type,currentVersion:remote.version,nextVersion:config.version||remote.version||"",migratingLegacyScopes:containsLegacyAllScope(remote.scopes??[]),currentUiApp:remote.ui_app,nextUiApp:config.ui_app}}var stripInjectedKeys=stripUiAppWireOnlyKeysFrom;function sortKeysDeep(value){return Array.isArray(value)?value.map(sortKeysDeep):value&&typeof value=="object"?Object.fromEntries(Object.entries(value).sort(([a],[b])=>a.localeCompare(b)).map(([k,v])=>[k,sortKeysDeep(v)])):value}function withoutInjectedKeys(uiApp){return stripInjectedKeys(uiApp)}function canonicalizeUiApp(uiApp){if(!uiApp)return"";let normalized=sortKeysDeep(stripInjectedKeys(uiApp)),entries=normalized.surface_point_list;return Array.isArray(entries)&&(normalized.surface_point_list=[...entries].sort((a,b)=>JSON.stringify(a).localeCompare(JSON.stringify(b)))),JSON.stringify(normalized)}function renderUploadDiff(diff){logInfo(""),logInfo(` ${messages.APP_UPLOAD_SUMMARY}`),logInfo(` App ID: ${diff.appId}`);let renamePrefix=diff.currentName&&diff.currentName!==diff.nextName?`${diff.currentName} \u2192 `:"";logInfo(` Name: ${renamePrefix}${diff.nextName}`),diff.currentDistribution&&diff.currentDistribution!==diff.nextDistribution?logInfo(` Distribution: ${diff.currentDistribution} \u2192 ${diff.nextDistribution}`):logInfo(` Distribution: ${diff.nextDistribution}`),diff.nextUiApp||logAligned(" Redirect URLs: ",diffLines(diff.currentUrls,diff.nextUrls)),diff.migratingLegacyScopes&&logInfo(` ${messages.LEGACY_ALL_SCOPE_UPDATE_MIGRATING}`),diff.nextUiApp||logAligned(" Scopes: ",diffLines(diff.currentScopes,diff.nextScopes)),diff.currentLogoUri&&diff.currentLogoUri!==diff.nextLogoUri?logInfo(` Logo URL: ${diff.currentLogoUri} \u2192 ${diff.nextLogoUri||"(none)"}`):diff.nextLogoUri&&logInfo(` Logo URL: ${diff.nextLogoUri}`),diff.currentVersion&&diff.currentVersion!==diff.nextVersion?logInfo(` Version: ${diff.currentVersion} \u2192 ${diff.nextVersion||"(unknown)"}`):diff.nextVersion&&logInfo(` Version: ${diff.nextVersion}`),diff.nextUiApp&&renderUiAppDiff(diff.nextUiApp,diff.currentUiApp),logInfo("")}function renderUiAppDiff(next,current){let changed=canonicalizeUiApp(next)!==canonicalizeUiApp(current);logInfo(` ${messages.APP_UPLOAD_UI_APP_SUMMARY}${changed?" (changed)":""}`),logInfo(` Extension type: ${next.extension_type}`),formatPlacementLines(next).forEach((line,i)=>{logInfo(` ${i===0?"Placement: ":" "}${line}`)}),logInfo(` Label: ${next.label??""}`),next.more_info&&logInfo(` More info: ${next.more_info}`),logInfo(` Redirect link: ${next.redirect_link??""}`)}function diffToJson(diff){return{current:{name:diff.currentName,redirect_uris:diff.currentUrls,scopes:diff.currentScopes,logo_uri:diff.currentLogoUri,distribution_type:diff.currentDistribution,version:diff.currentVersion,...diff.currentUiApp?{ui_app:diff.currentUiApp}:{}},next:{name:diff.nextName,...diff.nextUiApp?{}:{redirect_uris:diff.nextUrls,scopes:diff.nextScopes},logo_uri:diff.nextLogoUri,distribution_type:diff.nextDistribution,version:diff.nextVersion,...diff.nextUiApp?{ui_app:diff.nextUiApp}:{}}}}function hasNoChanges(diff){let oauthUnchanged=!!diff.nextUiApp||JSON.stringify([...diff.currentUrls].sort())===JSON.stringify([...diff.nextUrls].sort())&&JSON.stringify([...diff.currentScopes].sort())===JSON.stringify([...diff.nextScopes].sort());return diff.currentName===diff.nextName&&diff.currentDistribution===diff.nextDistribution&&oauthUnchanged&&(diff.currentLogoUri||"")===(diff.nextLogoUri||"")&&(diff.currentVersion||"")===(diff.nextVersion||"")&&canonicalizeUiApp(diff.currentUiApp)===canonicalizeUiApp(diff.nextUiApp)}async function uploadProjectConfig(config,opts={}){let isUiApp=isUiAppConfig(config),redirectUris=config.auth?.redirectUris??[],scopes=config.auth?.scopes??[],appVersion=opts.appVersion??config.version??"";appVersion||(appVersion=(await fetchExistingApp(config.appId,opts.silent)).version??"");let spinner=createSpinner("Uploading app...",{silent:opts.silent}),response;try{response=await appService.uploadApp(config.appId,{app_id:config.appId,name:config.appName,logo_uri:config.logoUri??"",version:appVersion,distribution_type:config.distribution_type,...isUiApp?{}:{auth:{scopes,redirect_uris:redirectUris}},...isUiApp&&config.ui_app?{ui_app:{...config.ui_app,...config.ui_app.extension_type===EXTENSION_TYPE_ACTION_LINK?{link_target:DEFAULT_LINK_TARGET}:{}}}:{}})}finally{spinner.stop()}let finalName=response.name??config.appName;finalName&&saveAppName(config.appId,finalName);let confirmedVersion=response.version??response.app_version??appVersion;return writeProjectConfig({...config,appName:finalName,logoUri:response.logo_uri??config.logoUri,distribution_type:response.distribution_type??config.distribution_type,version:confirmedVersion,auth:isUiApp?{}:{scopes:response.auth.scopes??scopes,redirectUris:response.auth.redirect_uris??redirectUris},...isUiApp&&(response.ui_app??config.ui_app)?{ui_app:withoutInjectedKeys(response.ui_app??config.ui_app)}:{}}),{confirmedVersion,finalName}}function validateAuthShape(config){if(isUiAppConfig(config)){if(!config.auth)throw new CliError(messages.APP_UPLOAD_UI_APP_AUTH_EMPTY_REQUIRED);if(config.auth.scopes!==void 0||config.auth.redirectUris!==void 0)throw new CliError(messages.APP_UPLOAD_UI_APP_AUTH_HAS_OAUTH_FIELDS)}}function runLocalPreflight(config){validateAuthShape(config);let redirectUris=config.auth?.redirectUris??[];if(!isUiAppConfig(config)&&redirectUris.length===0)throw new CliError(messages.APP_UPLOAD_NO_REDIRECT_URLS_OAUTH);validateRedirectUrls(redirectUris),resolveFromConfig(config).validateConfig(config);let scopes=config.auth?.scopes??[];if(validateScopes(scopes),containsLegacyAllScope(scopes))throw new CliError(messages.LEGACY_ALL_SCOPE_DEPRECATED_BLOCK)}async function confirmUpload(){if(!process.stdin.isTTY)throw new CliError(NON_INTERACTIVE_CONFIRM_ERROR);let{confirmed}=await import_inquirer11.default.prompt([{type:"confirm",name:"confirmed",message:messages.APP_UPLOAD_CONFIRM,default:!0}]);return!!confirmed}var uploadCommand=withCommandHandler(async options=>{let config=loadUsableConfig();runLocalPreflight(config);let remote=await fetchExistingApp(config.appId,options.json),diff=buildDiff(config,remote);if(diff.currentDistribution&&diff.currentDistribution!==diff.nextDistribution)throw new CliError(messages.APP_UPLOAD_DISTRIBUTION_IMMUTABLE(diff.currentDistribution,diff.nextDistribution));if(options.json||renderUploadDiff(diff),hasNoChanges(diff)){if(options.json){jsonOutput({appId:config.appId,upToDate:!0,version:diff.nextVersion,...diffToJson(diff)});return}logInfo(messages.APP_UPLOAD_UP_TO_DATE(diff.nextVersion||"unknown"));return}if(!options.json&&!options.yes&&!await confirmUpload()){logInfo(`
180
+ ${messages.APP_UPLOAD_CANCELLED}
181
+ `);return}let{confirmedVersion,finalName}=await uploadProjectConfig(config,{silent:options.json,appVersion:diff.nextVersion});if(options.json){jsonOutput({appId:config.appId,name:finalName,version:confirmedVersion,...diffToJson(diff)});return}logSuccess(messages.APP_UPLOAD_SUCCESS),logInfo(` Version: ${confirmedVersion||"(unknown)"}`),process.stdout.write(`
182
+ `)});var fs7=__toESM(require("node:fs")),path7=__toESM(require("node:path")),os2=__toESM(require("node:os")),import_inquirer12=__toESM(require("inquirer"));function isSafeToDelete(dir){let resolved=path7.resolve(dir),home=os2.homedir(),{root}=path7.parse(resolved);return!(resolved===root||resolved===home||path7.dirname(resolved)===root||!fs7.existsSync(path7.join(resolved,"app-config.json")))}async function confirmDeletion(appLabel,appId){let{confirmed}=await import_inquirer12.default.prompt([{type:"confirm",name:"confirmed",message:messages.APP_DELETE_CONFIRM(appLabel,appId),default:!1}]);return confirmed?!0:(logInfo(`
183
+ ${messages.APP_DELETE_CANCELLED}
184
+ `),!1)}function removeProjectFolder(cwd){if(!isSafeToDelete(cwd)){logWarn(messages.APP_DELETE_FOLDER_FAILED(cwd));return}try{fs7.rmSync(cwd,{recursive:!0,force:!0}),logSuccess(messages.APP_DELETE_FOLDER_SUCCESS(cwd))}catch{logWarn(messages.APP_DELETE_FOLDER_FAILED(cwd))}}async function offerLocalFolderCleanup(appId){if(readProjectConfig()?.appId!==appId)return;let cwd=process.cwd(),{deleteFolder}=await import_inquirer12.default.prompt([{type:"confirm",name:"deleteFolder",message:messages.APP_DELETE_FOLDER_CONFIRM(cwd),default:!1}]);deleteFolder&&removeProjectFolder(cwd)}var deleteCommand=withCommandHandler(async options=>{let appId=options.appId,appLabel="";if(!appId){assertAppSelectionAllowed(CLI.APP_DELETE_APP_ID(),options.json);let selection=await promptAppSelection(messages.APP_DELETE_SELECT);appId=selection.appId,appLabel=selection.appLabel}if(!options.force&&!await confirmDeletion(appLabel||appId,appId))return;let deleteSpinner=createSpinner("Deleting app...",{silent:options.json});if(await appService.deleteApp(appId),deleteSpinner.stop(),deleteAppName(appId),deleteAppCredentials(appId),options.json){jsonOutput({deleted:!0,appId});return}logSuccess(messages.APP_DELETE_SUCCESS(appId)),options.force||await offerLocalFolderCleanup(appId)});async function fetchSupportedScopes(){let response;try{response=await fetch(OAUTH_SCOPES_URL,{method:"GET"})}catch{throw new ApiError(messages.OAUTH_METADATA_FETCH_FAILED(OAUTH_SCOPES_URL,0),0,"NETWORK_ERROR")}if(!response.ok)throw new ApiError(messages.OAUTH_METADATA_FETCH_FAILED(OAUTH_SCOPES_URL,response.status),response.status);let body;try{body=await response.json()}catch{throw new CliError(messages.OAUTH_METADATA_MISSING_SCOPES)}if(!body||typeof body!="object"||!Array.isArray(body.scopes))throw new CliError(messages.OAUTH_METADATA_MISSING_SCOPES);return body.scopes.filter(s=>!!s&&typeof s=="object"&&typeof s.name=="string"&&typeof s.category=="string"&&s.is_oidc_reserved!==!0).map(s=>({name:s.name,category:s.category,apiEndpoints:Array.isArray(s.api_endpoints)?s.api_endpoints.filter(e=>typeof e=="string"):[]}))}var http2=__toESM(require("node:http"));function escapeHtml(value){return value.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}function safeJson(value){return JSON.stringify(value).replaceAll("<",String.raw`\u003c`)}var STYLES=`
185
+ :root {
186
+ color-scheme: light dark;
187
+ --bg: #ffffff;
188
+ --hero-bg: #e6f6ec;
189
+ --fg: #1b1b1b;
190
+ --muted: #696969;
191
+ --border: #e3e3e3;
192
+ --accent: #0b996e;
193
+ --card: #ffffff;
194
+ --card-shadow: 0 1px 2px rgba(28, 28, 28, .08);
195
+ --chip-bg: #e1f3e8;
196
+ --chip-fg: #00734a;
197
+ --section-header-bg: #f6fbf8;
198
+ --error: #cf1a3b;
199
+ --radius-card: 16px;
200
+ --radius-input: 16px;
201
+ --radius-chip: 20px;
202
+ }
203
+ @media (prefers-color-scheme: dark) {
204
+ :root {
205
+ --bg: #0e1613;
206
+ --hero-bg: #142119;
207
+ --fg: #f1f5f2;
208
+ --muted: #94a89c;
209
+ --border: #25382d;
210
+ --accent: #3ddc97;
211
+ --card: #16241c;
212
+ --card-shadow: 0 1px 2px rgba(0, 0, 0, .4);
213
+ --chip-bg: #1d3327;
214
+ --chip-fg: #a7e3c5;
215
+ --section-header-bg: #121f18;
216
+ --error: #ff8d9a;
217
+ }
218
+ }
219
+ * { box-sizing: border-box; }
220
+ body {
221
+ margin: 0;
222
+ padding: 0;
223
+ font: 15px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
224
+ background: var(--bg);
225
+ color: var(--fg);
226
+ -webkit-font-smoothing: antialiased;
227
+ -moz-osx-font-smoothing: grayscale;
228
+ }
229
+ .hero {
230
+ background: var(--hero-bg);
231
+ padding: 2.5rem 1.5rem 2rem;
232
+ }
233
+ .hero-inner {
234
+ max-width: 880px;
235
+ margin: 0 auto;
236
+ display: flex;
237
+ align-items: center;
238
+ justify-content: space-between;
239
+ gap: 1.5rem;
240
+ flex-wrap: wrap;
241
+ }
242
+ .hero-copy { flex: 1 1 24rem; min-width: 0; }
243
+ h1 {
244
+ font-size: 2rem;
245
+ line-height: 2.5rem;
246
+ font-weight: 600;
247
+ margin: 0 0 .5rem;
248
+ }
249
+ .intro { color: var(--muted); margin: 0; word-break: break-word; }
250
+ a.docs-cta {
251
+ display: inline-block;
252
+ flex: none;
253
+ white-space: nowrap;
254
+ padding: .5rem 1.1rem;
255
+ font-weight: 600;
256
+ font-size: .9rem;
257
+ color: #ffffff;
258
+ background: var(--accent);
259
+ border-radius: var(--radius-input);
260
+ text-decoration: none;
261
+ box-shadow: var(--card-shadow);
262
+ }
263
+ a.docs-cta:hover { opacity: .9; }
264
+ @media (prefers-color-scheme: dark) {
265
+ a.docs-cta { color: #0e1613; }
266
+ }
267
+ main {
268
+ max-width: 880px;
269
+ margin: 0 auto;
270
+ padding: 1.5rem 1.5rem 4rem;
271
+ }
272
+ .toolbar {
273
+ display: flex;
274
+ gap: .75rem;
275
+ align-items: center;
276
+ margin-bottom: 1.5rem;
277
+ }
278
+ input[type="search"] {
279
+ flex: 1;
280
+ padding: .55rem .9rem;
281
+ font: inherit;
282
+ font-size: 1rem;
283
+ border: 1px solid var(--border);
284
+ border-radius: var(--radius-input);
285
+ background: var(--card);
286
+ color: var(--fg);
287
+ }
288
+ input[type="search"]:focus {
289
+ outline: none;
290
+ border-color: var(--accent);
291
+ box-shadow: inset 0 0 0 1px var(--accent);
292
+ }
293
+ button.refresh {
294
+ padding: .5rem 1rem;
295
+ font: inherit;
296
+ font-weight: 600;
297
+ font-size: .9rem;
298
+ border: 1px solid var(--border);
299
+ border-radius: var(--radius-input);
300
+ background: var(--card);
301
+ color: var(--fg);
302
+ cursor: pointer;
303
+ box-shadow: var(--card-shadow);
304
+ }
305
+ button.refresh:hover { background: var(--chip-bg); border-color: var(--chip-bg); }
306
+ button.refresh:disabled { opacity: .55; cursor: progress; }
307
+ .refresh-error {
308
+ margin: -.75rem 0 1rem;
309
+ color: var(--error);
310
+ font-size: .85rem;
311
+ }
312
+ section {
313
+ border: 1px solid var(--border);
314
+ border-radius: var(--radius-card);
315
+ margin-bottom: 1rem;
316
+ background: var(--card);
317
+ box-shadow: var(--card-shadow);
318
+ overflow: hidden;
319
+ }
320
+ section[hidden] { display: none; }
321
+ .section-head {
322
+ display: flex;
323
+ align-items: center;
324
+ justify-content: space-between;
325
+ gap: .75rem;
326
+ padding: .55rem 1rem;
327
+ background: var(--section-header-bg);
328
+ border-bottom: 1px solid var(--border);
329
+ }
330
+ .section-head h2 {
331
+ font-size: .75rem;
332
+ font-weight: 600;
333
+ text-transform: uppercase;
334
+ letter-spacing: .06em;
335
+ color: var(--muted);
336
+ margin: 0;
337
+ }
338
+ button.copy {
339
+ font: inherit;
340
+ font-size: .75rem;
341
+ font-weight: 600;
342
+ padding: .2rem .7rem;
343
+ border: 1px solid var(--border);
344
+ border-radius: var(--radius-chip);
345
+ background: var(--card);
346
+ color: var(--accent);
347
+ cursor: pointer;
348
+ white-space: nowrap;
349
+ }
350
+ button.copy:hover { background: var(--chip-bg); border-color: var(--chip-bg); }
351
+ button.copy:disabled { opacity: .55; cursor: default; }
352
+ .selection-bar {
353
+ display: flex;
354
+ gap: .75rem;
355
+ align-items: center;
356
+ margin-bottom: 1.5rem;
357
+ }
358
+ .selection-bar input[type="text"] {
359
+ flex: 1;
360
+ padding: .55rem .9rem;
361
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
362
+ font-size: .85rem;
363
+ border: 1px solid var(--border);
364
+ border-radius: var(--radius-input);
365
+ background: var(--card);
366
+ color: var(--fg);
367
+ }
368
+ .selection-bar button.copy { padding: .5rem 1rem; font-size: .9rem; }
369
+ ul { list-style: none; margin: 0; padding: .5rem 0; }
370
+ li { padding: .4rem 1rem; }
371
+ li[hidden] { display: none; }
372
+ .scope-row { display: flex; align-items: flex-start; gap: .55rem; }
373
+ .scope-row input[type="checkbox"] {
374
+ accent-color: var(--accent);
375
+ margin-top: .25rem;
376
+ flex: none;
377
+ cursor: pointer;
378
+ }
379
+ .scope-row details { flex: 1; min-width: 0; }
380
+ .badge {
381
+ font-size: .68rem;
382
+ font-weight: 600;
383
+ text-transform: uppercase;
384
+ letter-spacing: .04em;
385
+ color: var(--error);
386
+ border: 1px solid currentColor;
387
+ border-radius: var(--radius-chip);
388
+ padding: 0 .45rem;
389
+ margin-left: .5rem;
390
+ vertical-align: middle;
391
+ }
392
+ details > summary {
393
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
394
+ font-size: .9rem;
395
+ cursor: pointer;
396
+ list-style: revert;
397
+ }
398
+ details[open] > summary { margin-bottom: .35rem; }
399
+ .endpoints {
400
+ margin: .25rem 0 .25rem 1.1rem;
401
+ padding: 0;
402
+ display: flex;
403
+ flex-wrap: wrap;
404
+ gap: .35rem;
405
+ }
406
+ .endpoints .ep {
407
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
408
+ font-size: .78rem;
409
+ background: var(--chip-bg);
410
+ color: var(--chip-fg);
411
+ padding: .15rem .55rem;
412
+ border-radius: var(--radius-chip);
413
+ }
414
+ .endpoints .none {
415
+ font-size: .8rem;
416
+ color: var(--muted);
417
+ font-style: italic;
418
+ }
419
+ .empty { color: var(--muted); padding: 2rem 0; text-align: center; }
420
+ footer {
421
+ margin-top: 3rem;
422
+ padding-top: 1.5rem;
423
+ border-top: 1px solid var(--border);
424
+ font-size: .8rem;
425
+ color: var(--muted);
426
+ }
427
+ footer a { color: var(--accent); }
428
+ `.trim(),SCRIPT=`
429
+ (function () {
430
+ var dataNode = document.getElementById('scopes-data');
431
+ var state = dataNode ? JSON.parse(dataNode.textContent || '[]') : [];
432
+
433
+ var listEl = document.getElementById('scopes-list');
434
+ var searchEl = document.querySelector('input[type="search"]');
435
+ var refreshBtn = document.getElementById('refresh-btn');
436
+ var errorEl = document.getElementById('refresh-error');
437
+ var introCountEl = document.getElementById('scopes-count');
438
+ var selectedEl = document.getElementById('selected-scopes');
439
+ var copySelectedBtn = document.getElementById('copy-selected');
440
+ var labels = JSON.parse(document.getElementById('labels').textContent);
441
+
442
+ // Selected scope names, in click order. Rendered as '"a","b"' \u2014 pastes
443
+ // straight into app-config.json's auth.scopes array.
444
+ var selected = [];
445
+
446
+ function quoted(names) {
447
+ return names.map(function (n) { return '"' + n + '"'; }).join(',');
448
+ }
449
+
450
+ function escapeHtml(s) {
451
+ return String(s).replace(/[&<>"']/g, function (c) {
452
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
453
+ });
454
+ }
455
+
456
+ function render(entries) {
457
+ if (!entries.length) {
458
+ listEl.innerHTML = '<p class="empty">' + escapeHtml(labels.empty) + '</p>';
459
+ return;
130
460
  }
131
- // Unknown top-level command — Commander dispatches stray args to the
132
- // root action when no subcommand matches. Surface a clear error so the
133
- // user knows they typed a command that doesn't exist.
134
- process.stderr.write(`error: unknown command '${stray[0]}'\n`);
135
- process.stderr.write(`See \`brevo --help\` for available commands.\n`);
136
- process.exit(exit_codes_1.EXIT_CODES.ERROR);
137
- });
138
- // Auth guard — blocks unauthenticated access (except login, logout, help)
139
- (0, auth_guard_1.installAuthGuard)(program);
140
- // Proactive OAuth refresh — replaces a near-expiry access token before the
141
- // command runs, so a short access-token TTL stays invisible and the session
142
- // lives as long as the refresh token does. Best-effort: failures are logged at
143
- // debug level and never block the command. The reactive handler below stays the
144
- // safety net and the only place credentials get cleared.
145
- (0, oauth_freshness_1.installProactiveOauthRefresh)(program, {
146
- getAuthCred: config_1.getAuthCred,
147
- refresh: (refreshToken) => (0, oauth_refresh_1.refreshAccessToken)(refreshToken, constants_1.OAUTH_PROXY_URL),
148
- persist: config_1.updateOauthTokens,
149
- onError: (err) => (0, logger_1.logDebug)('proactive oauth refresh skipped', {
150
- reason: err instanceof Error ? err.message : String(err),
151
- }),
152
- });
153
- // ──────────────── Register all commands ────────────────
154
- (0, command_registry_1.registerAll)(program, definitions_1.topLevelCommands, [definitions_1.appCommandGroup, definitions_1.skillCommandGroup]);
155
- // ──────────────── Re-auth handler ────────────────
156
- container_1.client.setOnAuthFailure(async () => {
157
- const auth = (0, config_1.getAuthCred)();
158
- // OAuth: silently refresh the access token and let the original request retry.
159
- // If the refresh itself returns 401, the refresh token is dead — clear creds
160
- // and surface a friendly "please log in again" message instead of falling
161
- // through to the api-key prompt.
162
- if (auth?.kind === 'oauth') {
163
- try {
164
- const refreshed = await (0, oauth_refresh_1.refreshAccessToken)(auth.refreshToken, constants_1.OAUTH_PROXY_URL);
165
- (0, config_1.updateOauthTokens)(refreshed);
166
- return;
461
+ var byCategory = new Map();
462
+ entries.forEach(function (e) {
463
+ var list = byCategory.get(e.category);
464
+ if (list) list.push(e);
465
+ else byCategory.set(e.category, [e]);
466
+ });
467
+ var html = '';
468
+ byCategory.forEach(function (items, cat) {
469
+ var copyAria = labels.copyCategoryAria.replace('{category}', cat);
470
+ html += '<section><div class="section-head"><h2>' + escapeHtml(cat) + '</h2>';
471
+ html +=
472
+ '<button type="button" class="copy copy-category" data-category="' + escapeHtml(cat) +
473
+ '" aria-label="' + escapeHtml(copyAria) + '">' + escapeHtml(labels.copy) + '</button></div><ul>';
474
+ items.forEach(function (e) {
475
+ var endpoints = Array.isArray(e.apiEndpoints) ? e.apiEndpoints : [];
476
+ var isLegacy = e.name === labels.legacyScope;
477
+ html += '<li><div class="scope-row">';
478
+ if (isLegacy) {
479
+ // The legacy catch-all scope is deprecated: badge it, no checkbox,
480
+ // and copy CTAs skip it so pasted lists never reintroduce it.
481
+ html += '<details><summary>' + escapeHtml(e.name) +
482
+ '<span class="badge" title="' + escapeHtml(labels.legacyTitle) + '">' +
483
+ escapeHtml(labels.legacyBadge) + '</span></summary>';
484
+ } else {
485
+ var selectAria = labels.selectScopeAria.replace('{scope}', e.name);
486
+ html += '<input type="checkbox" class="scope-check" data-scope="' + escapeHtml(e.name) +
487
+ '" aria-label="' + escapeHtml(selectAria) + '"' +
488
+ (selected.indexOf(e.name) !== -1 ? ' checked' : '') + '>';
489
+ html += '<details><summary>' + escapeHtml(e.name) + '</summary>';
167
490
  }
168
- catch (err) {
169
- if (err instanceof oauth_refresh_1.RefreshError && err.unauthorized) {
170
- (0, config_1.clearCredentials)();
171
- throw new errors_1.AuthExpiredError();
172
- }
173
- throw err;
491
+ if (endpoints.length) {
492
+ html += '<div class="endpoints" aria-label="' + escapeHtml(labels.endpointsLabel) + '">';
493
+ endpoints.forEach(function (ep) {
494
+ html += '<span class="ep">' + escapeHtml(ep) + '</span>';
495
+ });
496
+ html += '</div>';
497
+ } else {
498
+ html += '<div class="endpoints"><span class="none">' + escapeHtml(labels.noEndpoints) + '</span></div>';
174
499
  }
175
- }
176
- // API-key: prompt for a new key and re-validate.
177
- (0, ui_1.stopActiveSpinner)();
178
- (0, config_1.clearCredentials)();
179
- (0, logger_1.logWarn)(en_1.messages.AUTH_EXPIRED);
180
- (0, logger_1.logInfo)(` ${en_1.messages.AUTH_GET_KEY_URL}\n`);
181
- const newKey = await (0, hidden_input_1.readHiddenInput)(en_1.messages.AUTH_EXPIRED_PROMPT + ' ');
182
- const account = await container_1.client.getWithKey(constants_1.ENDPOINTS.ACCOUNT, newKey);
183
- (0, config_1.saveCredentials)(newKey, {
184
- email: account.email,
185
- organizationId: account.organization_id,
186
- userId: account.user_id,
500
+ html += '</details></div></li>';
501
+ });
502
+ html += '</ul></section>';
187
503
  });
188
- (0, logger_1.logSuccess)(en_1.messages.AUTH_SUCCESS(account.email));
189
- });
190
- // ──────────────── Signal handling ────────────────
191
- for (const signal of ['SIGINT', 'SIGTERM']) {
192
- process.on(signal, () => {
193
- (0, logger_1.logInfo)(`\n Received ${signal}, shutting down.\n`);
194
- process.exit(exit_codes_1.EXIT_CODES.ABORTED);
504
+ listEl.innerHTML = html;
505
+ applyFilter();
506
+ }
507
+
508
+ function copyText(text, btn) {
509
+ if (!navigator.clipboard || !navigator.clipboard.writeText) return;
510
+ navigator.clipboard.writeText(text).then(function () {
511
+ var original = btn.textContent;
512
+ btn.textContent = labels.copied;
513
+ setTimeout(function () {
514
+ btn.textContent = original;
515
+ }, 1200);
516
+ }).catch(function () { /* clipboard denied \u2014 leave the button as-is */ });
517
+ }
518
+
519
+ function updateSelection() {
520
+ selectedEl.value = quoted(selected);
521
+ copySelectedBtn.disabled = selected.length === 0;
522
+ }
523
+
524
+ function applyFilter() {
525
+ var q = searchEl ? searchEl.value.toLowerCase().trim() : '';
526
+ var sections = listEl.querySelectorAll('section');
527
+ sections.forEach(function (section) {
528
+ var items = section.querySelectorAll('li');
529
+ var anyVisible = false;
530
+ items.forEach(function (li) {
531
+ var text = li.textContent.toLowerCase();
532
+ var match = q === '' || text.indexOf(q) !== -1;
533
+ li.hidden = !match;
534
+ if (match) anyVisible = true;
535
+ });
536
+ section.hidden = !anyVisible;
195
537
  });
196
- }
197
- // ──────────────── Error handling ────────────────
198
- // Emit deferred warning if BREVO_API_URL had a path stripped
199
- (0, constants_1.warnIfPathStripped)();
200
- // Force-update gate — if the installed CLI is a full major version behind the
201
- // latest npm release, block the command (non-zero exit) so the user upgrades.
202
- // Skipped for --help/--version (so help stays reachable) and whenever the
203
- // update check itself is skipped (CI / non-TTY / opt-out). Fails open.
204
- const args = process.argv.slice(2);
205
- const isHelpOrVersion = args.includes('--help') ||
206
- args.includes('-h') ||
207
- args.includes('--version') ||
208
- args.includes('-V');
209
- const forceGate = isHelpOrVersion
210
- ? Promise.resolve()
211
- : (0, update_notifier_1.enforceMinVersion)(updateCheck, { name: pkg.name, version }).then((mustUpdate) => {
212
- if (mustUpdate)
213
- process.exit(exit_codes_1.EXIT_CODES.ERROR);
538
+ }
539
+
540
+ function setCount(n) {
541
+ if (introCountEl) introCountEl.textContent = String(n);
542
+ }
543
+
544
+ render(state);
545
+ setCount(state.length);
546
+ updateSelection();
547
+
548
+ if (searchEl) {
549
+ searchEl.addEventListener('input', applyFilter);
550
+ }
551
+
552
+ // Delegated handlers survive re-renders (refresh replaces listEl's HTML).
553
+ listEl.addEventListener('click', function (ev) {
554
+ var btn = ev.target.closest ? ev.target.closest('button.copy-category') : null;
555
+ if (!btn) return;
556
+ var cat = btn.getAttribute('data-category');
557
+ var names = state
558
+ .filter(function (e) { return e.category === cat && e.name !== labels.legacyScope; })
559
+ .map(function (e) { return e.name; });
560
+ copyText(quoted(names), btn);
561
+ });
562
+
563
+ listEl.addEventListener('change', function (ev) {
564
+ var box = ev.target.closest ? ev.target.closest('input.scope-check') : null;
565
+ if (!box) return;
566
+ var name = box.getAttribute('data-scope');
567
+ var idx = selected.indexOf(name);
568
+ if (box.checked && idx === -1) selected.push(name);
569
+ if (!box.checked && idx !== -1) selected.splice(idx, 1);
570
+ updateSelection();
571
+ });
572
+
573
+ copySelectedBtn.addEventListener('click', function () {
574
+ if (selected.length) copyText(quoted(selected), copySelectedBtn);
575
+ });
576
+
577
+ if (refreshBtn) {
578
+ var defaultLabel = refreshBtn.textContent;
579
+ refreshBtn.addEventListener('click', function () {
580
+ refreshBtn.disabled = true;
581
+ refreshBtn.textContent = labels.refreshing;
582
+ if (errorEl) errorEl.hidden = true;
583
+ fetch('/scopes.json', { headers: { Accept: 'application/json' } })
584
+ .then(function (res) {
585
+ if (!res.ok) throw new Error('HTTP ' + res.status);
586
+ return res.json();
587
+ })
588
+ .then(function (body) {
589
+ var entries = Array.isArray(body && body.scopes) ? body.scopes : [];
590
+ state = entries;
591
+ // Drop selections for scopes the IdP no longer returns.
592
+ selected = selected.filter(function (name) {
593
+ return state.some(function (e) { return e.name === name; });
594
+ });
595
+ render(state);
596
+ setCount(state.length);
597
+ updateSelection();
598
+ })
599
+ .catch(function () {
600
+ if (errorEl) errorEl.hidden = false;
601
+ })
602
+ .then(function () {
603
+ refreshBtn.disabled = false;
604
+ refreshBtn.textContent = defaultLabel;
605
+ });
214
606
  });
215
- forceGate
216
- .then(() => showBannerEarly ? (0, update_notifier_1.notifyUpdate)(updateCheck, { name: pkg.name, version }) : undefined)
217
- .then(() => program.parseAsync(process.argv))
218
- .then(async () => {
219
- if (!showBannerEarly) {
220
- await (0, update_notifier_1.notifyUpdate)(updateCheck, { name: pkg.name, version });
221
- }
222
- // Local skill catalog check — sync, no network. Silently refreshes any
223
- // installed skill that's behind the bundled catalog so the AI tool always
224
- // sees the latest primer. Opt out with BREVO_NO_SKILL_AUTOREFRESH=1.
225
- skill_1.skillService.autoRefreshOutdated();
226
- // Force exit — Node's native fetch keeps TCP connections alive which can
227
- // prevent the process from exiting when running against local servers.
228
- process.exit(0);
229
- })
230
- .catch((err) => {
231
- if (err instanceof errors_1.AbortError) {
232
- (0, logger_1.logInfo)(`\n ${en_1.messages.ABORTED}`);
233
- process.exit(exit_codes_1.EXIT_CODES.ABORTED);
234
- }
235
- if (err instanceof errors_1.CliError) {
236
- (0, logger_1.logError)(err.message);
237
- process.exit(err.exitCode);
238
- }
239
- (0, logger_1.logError)(err.message, err);
240
- process.exit(exit_codes_1.EXIT_CODES.ERROR);
241
- });
242
- //# sourceMappingURL=index.js.map
607
+ }
608
+ })();
609
+ `.trim();function renderScopesHtml(entries){let title=escapeHtml(messages.APP_SCOPES_WEB_TITLE),intro=messages.APP_SCOPES_WEB_INTRO(entries.length,OAUTH_SCOPES_URL),introWithSpan=escapeHtml(intro).replace(String(entries.length),`<span id="scopes-count">${entries.length}</span>`),searchPlaceholder=escapeHtml(messages.APP_SCOPES_WEB_SEARCH_PLACEHOLDER),refreshLabel=escapeHtml(messages.APP_SCOPES_WEB_REFRESH),refreshError=escapeHtml(messages.APP_SCOPES_WEB_REFRESH_FAILED),footer=escapeHtml(messages.APP_SCOPES_WEB_FOOTER),docsLink=escapeHtml(messages.APP_SCOPES_WEB_DOCS_LINK),catalogDocsCta=escapeHtml(messages.APP_SCOPES_WEB_CATALOG_DOCS_CTA),selectedPlaceholder=escapeHtml(messages.APP_SCOPES_WEB_SELECTED_PLACEHOLDER),copySelected=escapeHtml(messages.APP_SCOPES_WEB_COPY_SELECTED),labels={empty:messages.APP_SCOPES_WEB_EMPTY,refreshing:messages.APP_SCOPES_WEB_REFRESHING,endpointsLabel:messages.APP_SCOPES_WEB_ENDPOINTS_LABEL,noEndpoints:messages.APP_SCOPES_WEB_NO_ENDPOINTS,copy:messages.APP_SCOPES_WEB_COPY,copied:messages.APP_SCOPES_WEB_COPIED,copyCategoryAria:messages.APP_SCOPES_WEB_COPY_CATEGORY_ARIA,selectScopeAria:messages.APP_SCOPES_WEB_SELECT_SCOPE_ARIA,legacyScope:LEGACY_ALL_SCOPE,legacyBadge:messages.APP_SCOPES_WEB_LEGACY_BADGE,legacyTitle:messages.APP_SCOPES_WEB_LEGACY_TITLE};return`<!doctype html>
610
+ <html lang="en">
611
+ <head>
612
+ <meta charset="utf-8">
613
+ <meta name="viewport" content="width=device-width, initial-scale=1">
614
+ <title>${title}</title>
615
+ <style>${STYLES}</style>
616
+ </head>
617
+ <body>
618
+ <header class="hero">
619
+ <div class="hero-inner">
620
+ <div class="hero-copy">
621
+ <h1>${title}</h1>
622
+ <p class="intro">${introWithSpan}</p>
623
+ </div>
624
+ <a class="docs-cta" href="${BREVO_OAUTH_SCOPES_DOCS_URL}" target="_blank" rel="noopener noreferrer">${catalogDocsCta} \u2197</a>
625
+ </div>
626
+ </header>
627
+ <main>
628
+ <div class="toolbar">
629
+ <input type="search" placeholder="${searchPlaceholder}" autofocus>
630
+ <button id="refresh-btn" class="refresh" type="button">${refreshLabel}</button>
631
+ </div>
632
+ <p id="refresh-error" class="refresh-error" hidden>${refreshError}</p>
633
+ <div class="selection-bar">
634
+ <input id="selected-scopes" type="text" readonly placeholder="${selectedPlaceholder}" aria-label="${selectedPlaceholder}">
635
+ <button id="copy-selected" class="copy" type="button" disabled>${copySelected}</button>
636
+ </div>
637
+ <div id="scopes-list"></div>
638
+ <footer>${footer} <a href="${BREVO_CLI_REFERENCE_URL}">${docsLink}</a></footer>
639
+ </main>
640
+ <script type="application/json" id="scopes-data">${safeJson(entries)}</script>
641
+ <script type="application/json" id="labels">${safeJson(labels)}</script>
642
+ <script>${SCRIPT}</script>
643
+ </body>
644
+ </html>
645
+ `}function closeServer(server){return new Promise(resolveClose=>{server.close(()=>resolveClose())})}function startScopesWebServer(initialEntries,options={}){let html=renderScopesHtml(initialEntries),refetch=options.refetch;return new Promise((resolve11,reject)=>{let server=http2.createServer((req,res)=>{let pathname=new URL(req.url??"/","http://127.0.0.1").pathname;if(logDebug("scopes-web request",{method:req.method,pathname}),req.method==="GET"&&pathname==="/"){res.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),res.end(html);return}if(req.method==="GET"&&pathname==="/scopes.json"){let respond=entries=>{res.writeHead(200,{"Content-Type":"application/json; charset=utf-8","Cache-Control":"no-store"}),res.end(JSON.stringify({scopes:entries}))};if(!refetch){respond(initialEntries);return}refetch().then(respond).catch(err=>{logDebug("scopes-web refetch failed",{message:err.message}),res.writeHead(502,{"Content-Type":"application/json; charset=utf-8"}),res.end(JSON.stringify({error:"refetch_failed"}))});return}res.writeHead(404).end()});server.once("error",err=>{logDebug("scopes-web server error",{message:err.message}),reject(err)}),server.listen(0,"127.0.0.1",()=>{let port=server.address().port,url=`http://127.0.0.1:${port}/`;logDebug("scopes-web listening",{host:"127.0.0.1",port}),resolve11({url,close:()=>closeServer(server)})})})}function waitForShutdownSignal(){return new Promise(resolve11=>{let handler=()=>{process.off("SIGINT",handler),process.off("SIGTERM",handler),resolve11()};process.once("SIGINT",handler),process.once("SIGTERM",handler)})}function groupByCategory(entries){let byCategory=new Map;for(let entry of entries){let list=byCategory.get(entry.category);list?list.push(entry.name):byCategory.set(entry.category,[entry.name])}return byCategory}function printScopesByCategory(entries){let byCategory=groupByCategory(entries),first=!0;for(let[category,names]of byCategory){first||logInfo(""),first=!1,logInfo(`${category}:`);for(let name of names)logInfo(` ${name}`)}logInfo(""),logInfo(messages.APP_SCOPES_USAGE_HINT),logInfo(messages.APP_SCOPES_CATALOG_DOCS_HINT),logInfo(messages.APP_SCOPES_DOCS_HINT)}async function runWebMode(entries){let server=await startScopesWebServer(entries,{refetch:fetchSupportedScopes});logInfo(""),logInfo(messages.APP_SCOPES_WEB_LISTENING(server.url));try{openBrowser(server.url)}catch(err){logDebug("openBrowser failed",{message:err.message})}await waitForShutdownSignal(),await server.close()}var scopesCommand=withCommandHandler(async options=>{let entries=await fetchSupportedScopes();if(options.json){jsonOutput({scopes:entries.map(e=>e.name)});return}entries.length===0?logInfo(messages.APP_SCOPES_EMPTY):printScopesByCategory(entries),options.web&&await runWebMode(entries)});var fs8=__toESM(require("node:fs")),path8=__toESM(require("node:path")),import_node_child_process2=require("node:child_process"),import_inquirer13=__toESM(require("inquirer"));var FEATURES={oauth:{entry:"src/oauth/server.js",description:"Local OAuth test server"}};function findMatchingLocalRedirect(redirectUris,port){return redirectUris.find(url=>{try{let parsed=new URL(url);return(parsed.hostname==="localhost"||parsed.hostname==="127.0.0.1")&&parsed.port===String(port)}catch{return!1}})}async function ensureRedirectRegistered(config,port){let redirectUris=config.auth?.redirectUris??[],existing=findMatchingLocalRedirect(redirectUris,port);if(existing)return existing;let newRedirectUrl=`http://localhost:${port}/auth/callback`;if(!process.stdin.isTTY)throw new CliError(messages.APP_START_REDIRECT_NON_INTERACTIVE(port,newRedirectUrl));logInfo(` ${messages.APP_START_REDIRECT_NOT_REGISTERED(port)}`);let{shouldRegister}=await import_inquirer13.default.prompt([{type:"confirm",name:"shouldRegister",message:messages.APP_START_REDIRECT_REGISTER_PROMPT(newRedirectUrl),default:!0}]);if(!shouldRegister){logWarn(messages.APP_START_REDIRECT_DECLINED(newRedirectUrl));return}let updatedConfig={...config,auth:{...config.auth,redirectUris:[...redirectUris,newRedirectUrl]}};writeProjectConfig(updatedConfig);try{await uploadProjectConfig(updatedConfig)}catch(err){throw logWarn(messages.APP_START_REDIRECT_UPLOAD_FAILED(newRedirectUrl)),err}return logSuccess(messages.APP_START_REDIRECT_REGISTERED(newRedirectUrl)),newRedirectUrl}function resolveFeatureEntry(feature){if(!feature){let available=Object.entries(FEATURES).map(([name,f])=>` ${name} ${f.description}`).join(`
646
+ `);throw new CliError(messages.APP_START_MISSING_FEATURE(available))}let featureConfig=FEATURES[feature];if(!featureConfig){let available=Object.keys(FEATURES).join(", ");throw new CliError(messages.APP_START_UNKNOWN_FEATURE(feature,available))}let entryFile=path8.resolve(featureConfig.entry);if(!fs8.existsSync(entryFile))throw new CliError(messages.APP_START_FEATURE_NOT_FOUND(featureConfig.entry));let featureDir=path8.dirname(featureConfig.entry);if(!fs8.existsSync(path8.resolve(featureDir,"node_modules")))throw new CliError(messages.APP_START_NO_DEPS(featureDir));return entryFile}function resolvePort(config,optionsPort){if(optionsPort)return optionsPort;let redirectUrl=config?.auth?.redirectUris?.[0];if(!redirectUrl)return DEFAULT_PORT;try{let parsed=new URL(redirectUrl);return parsed.port?Number(parsed.port):DEFAULT_PORT}catch{return DEFAULT_PORT}}function runChildProcess(entryFile,childEnv,feature){let child=(0,import_node_child_process2.spawn)(process.execPath,[entryFile],{stdio:"inherit",env:childEnv}),onSignal=signal=>{child.kill(signal)};return process.prependListener("SIGINT",onSignal),process.prependListener("SIGTERM",onSignal),new Promise((resolve11,reject)=>{child.on("close",code=>{process.removeListener("SIGINT",onSignal),process.removeListener("SIGTERM",onSignal),code&&code!==0?reject(new CliError(messages.APP_START_EXITED(feature,code))):(logInfo(`
647
+ ${messages.APP_START_STOPPED}
648
+ `),resolve11())}),child.on("error",err=>{process.removeListener("SIGINT",onSignal),process.removeListener("SIGTERM",onSignal),reject(new CliError(messages.APP_START_FAILED(feature,err.message)))})})}var startCommand=withCommandHandler(async options=>{let{feature}=options,entryFile=resolveFeatureEntry(feature),config=readProjectConfig();if(containsLegacyAllScope(config?.auth?.scopes))throw new CliError(messages.LEGACY_ALL_SCOPE_START_BLOCK);let port=resolvePort(config,options.port);if(!await isPortAvailable(port))throw new CliError(options.port?messages.APP_START_CUSTOM_PORT_IN_USE(port):messages.APP_START_PORT_IN_USE(port));let redirectUri=config?.appId?await ensureRedirectRegistered(config,port):void 0;logInfo(`
649
+ Starting ${feature}...`);let childEnv={...process.env,PORT:String(port)};redirectUri&&(childEnv.REDIRECT_URI=redirectUri),await runChildProcess(entryFile,childEnv,feature)});var fs9=__toESM(require("node:fs")),path10=__toESM(require("node:path")),os3=__toESM(require("node:os"));var path9=__toESM(require("node:path"));var SKILL_CATALOG=[{name:"brevo-cli",description:"Agent primer for the Brevo Developer CLI \u2014 decision tree, hard rules, and command map for the `brevo` binary.",version:CLI_VERSION,subdir:"",files:["SKILL.md"]}],SKILLS_BUNDLE_DIR=path9.resolve(__dirname,"..","..","agent-context");function getSkill(name){return SKILL_CATALOG.find(s=>s.name===name)}function getClaudeSkillsRoot(){let override=process.env.BREVO_CLAUDE_HOME;return override?.trim()?path10.join(override,"skills"):path10.join(os3.homedir(),".claude","skills")}function getSkillTargetDir(name){return path10.join(getClaudeSkillsRoot(),name)}var MARKER_FILE=".brevo-skill.json";function readMarker(skillDir){try{let raw=JSON.parse(fs9.readFileSync(path10.join(skillDir,MARKER_FILE),"utf-8"));return raw&&typeof raw=="object"&&typeof raw.name=="string"&&typeof raw.version=="string"&&raw.source==="brevo-cli"?{name:raw.name,version:raw.version,installedAt:typeof raw.installedAt=="string"?raw.installedAt:"",source:"brevo-cli"}:null}catch{return null}}function writeMarker(skillDir,entry){let marker={name:entry.name,version:entry.version,installedAt:new Date().toISOString(),source:"brevo-cli"};fs9.writeFileSync(path10.join(skillDir,MARKER_FILE),JSON.stringify(marker,null,2)+`
650
+ `,"utf-8")}function copySkillFiles(entry,targetDir){fs9.mkdirSync(targetDir,{recursive:!0});for(let relative2 of entry.files){let src=path10.join(SKILLS_BUNDLE_DIR,entry.subdir,relative2),dest=path10.join(targetDir,relative2);fs9.mkdirSync(path10.dirname(dest),{recursive:!0}),fs9.copyFileSync(src,dest)}}var skillService={getOutdatedSkills(){return SKILL_CATALOG.flatMap(entry=>{let marker=readMarker(getSkillTargetDir(entry.name));return marker?marker.version===entry.version?[]:[{name:entry.name,installedVersion:marker.version,latestVersion:entry.version}]:[]})},isInstalled(name){let entry=getSkill(name);return entry?readMarker(getSkillTargetDir(entry.name))!==null:!1},install(name,options={}){let entry=getSkill(name);if(!entry)throw new CliError(unknownSkillMessage(name));let targetDir=getSkillTargetDir(entry.name),marker=readMarker(targetDir);return marker&&!options.force?{name:entry.name,version:marker.version,status:"already-installed",path:targetDir}:(copySkillFiles(entry,targetDir),writeMarker(targetDir,entry),{name:entry.name,version:entry.version,status:marker?"overwritten":"installed",path:targetDir})},installAll(options={}){return SKILL_CATALOG.map(entry=>this.install(entry.name,options))},uninstallAll(){return SKILL_CATALOG.flatMap(entry=>{let targetDir=getSkillTargetDir(entry.name);return readMarker(targetDir)?(fs9.rmSync(targetDir,{recursive:!0,force:!0}),[{name:entry.name,path:targetDir}]):[]})},autoRefreshOutdated(opts={}){if(shouldSkipAutoRefresh(opts))return;let outdated;try{outdated=this.getOutdatedSkills()}catch{return}if(outdated.length===0)return;let output=opts.output??process.stderr;for(let s of outdated)try{this.install(s.name,{force:!0}),output.write(` ${messages.SKILL_AUTOREFRESHED(s.name,s.installedVersion,s.latestVersion)}
651
+ `)}catch(err){let msg=err instanceof Error?err.message:String(err);output.write(` ${messages.SKILL_AUTOREFRESH_FAILED(s.name,msg)}
652
+ `)}}};function shouldSkipAutoRefresh(opts={}){let env=opts.env??process.env,argv=opts.argv??process.argv;return!!(env.CI==="true"||env.CI==="1"||argv.includes("--json")||argv.length>2&&argv[2]==="skill:cli"||env.BREVO_NO_SKILL_AUTOREFRESH==="1"||env.BREVO_NO_SKILL_AUTOREFRESH==="true")}function unknownSkillMessage(name){let available=SKILL_CATALOG.map(s=>s.name).join(", ");return`Unknown skill "${name}". Available: ${available}`}var installCommand=withCommandHandler(async options=>{let results=skillService.installAll();if(options.json){jsonOutput(results);return}let installedFresh=!1;for(let r of results)r.status==="already-installed"?logInfo(`
653
+ ${messages.SKILL_INSTALL_ALREADY(r.name,r.version)}`):(logSuccess(messages.SKILL_INSTALL_SUCCESS(r.name,r.version,r.path)),installedFresh=!0);installedFresh&&logInfo(`
654
+ ${messages.SKILL_INSTALL_CLAUDE_ONLY}`)});var uninstallCommand=withCommandHandler(async options=>{let results=skillService.uninstallAll();if(options.json){jsonOutput(results.map(r=>({uninstalled:!0,...r})));return}if(results.length===0){logInfo(`
655
+ ${messages.SKILL_UNINSTALL_NONE}`);return}for(let r of results)logSuccess(messages.SKILL_UNINSTALL_SUCCESS(r.name,r.path))});var topLevelCommands=[{name:"login",description:"Authenticate with your Brevo account",options:[{flags:"--browser",description:"Force browser-based login"},{flags:"--json",description:"Output as JSON"}],examples:["brevo login","brevo login --browser","BREVO_API_KEY=xkeysib-... brevo login"],handler:opts=>loginCommand({browser:!!opts.browser,json:!!opts.json})},{name:"logout",description:"Clear stored credentials",options:[{flags:"--force",description:"Skip confirmation (for CI)"},{flags:"--json",description:"Output as JSON"}],handler:opts=>logoutCommand({force:!!opts.force,json:!!opts.json})},{name:"whoami",description:"Show current authenticated user",options:[{flags:"--json",description:"Output as JSON"}],handler:opts=>whoamiCommand({json:!!opts.json})}],appCommandGroup={name:"app",description:"Manage OAuth applications",commands:[{name:"init",description:"Quick setup \u2014 login, create app, and scaffold in one go",examples:["brevo app init"],handler:()=>initCommand({})},{name:"create",description:createDescription(),examples:["brevo app create",'brevo app create --name "My App" --distribution private',...isFeatureAvailable("public-distribution")?['brevo app create --name "My App" --distribution public']:[],'brevo app create --name "My App" --distribution private --redirect-uri http://localhost:3009/auth/callback','brevo app create --name "My App" --distribution private --redirect-uri http://localhost:3009/auth/callback --redirect-uri https://myapp.com/callback --json','brevo app create --name "My App" --distribution private --logo-uri https://example.com/logo.png'],options:[{flags:"--name <name>",description:"App name"},{flags:"--distribution <type>",description:`Distribution type (${distributionValues()})`},{flags:"--redirect-uri <url>",description:"Redirect URI (repeatable, OAuth apps only)",parser:collectUrls},{flags:"--logo-uri <url>",description:"App logo URL (http or https)",parser:v=>(validateUrl(v,"logo URL"),v)},{flags:"--json",description:"Output as JSON"}],handler:opts=>createCommand({name:opts.name,distribution:opts.distribution,redirectUri:opts.redirectUri,logoUri:opts.logoUri,json:!!opts.json})},{name:"list",description:"List all apps in your account",examples:["brevo app list","brevo app list --json"],options:[{flags:"--json",description:"Output as JSON"}],handler:opts=>listCommand({json:!!opts.json})},{name:"credentials",description:"Show client ID and secret for an app",examples:["brevo app credentials --app-id 42","brevo app credentials --app-id 42 --reveal-secret --json"],options:[{flags:"--app-id <id>",description:"App ID",parser:v=>parseAppId(v)},{flags:"--reveal-secret",description:"Show the client secret"},{flags:"--json",description:"Output as JSON"}],handler:opts=>credentialsCommand({appId:opts.appId,revealSecret:!!opts.revealSecret,json:!!opts.json})},{name:"upload",description:"Push app-config.json to Brevo, validated and synced with the server",examples:["brevo app upload","brevo app upload --yes","brevo app upload --json"],options:[{flags:"--yes",description:"Skip confirmation prompt"},{flags:"--json",description:"Output as JSON"}],handler:opts=>uploadCommand({yes:!!opts.yes,json:!!opts.json})},{name:"delete",description:"Delete an app",examples:["brevo app delete --app-id 42","brevo app delete --app-id 42 --force"],options:[{flags:"--app-id <id>",description:"App ID",parser:v=>parseAppId(v)},{flags:"--force",description:"Skip confirmation (for CI)"},{flags:"--json",description:"Output as JSON"}],handler:opts=>deleteCommand({appId:opts.appId,force:!!opts.force,json:!!opts.json})},{name:"scaffold",description:"Add a feature to the app in this directory, or set an empty directory up for an existing app",examples:["brevo app scaffold","brevo app scaffold --app-id 42","brevo app scaffold --overwrite","brevo app scaffold --json","brevo app scaffold --app-id 42 --json"],options:[{flags:"--app-id <id>",description:"Set an empty directory up for an app you already have",parser:v=>parseAppId(v)},{flags:"--overwrite",description:"Overwrite existing feature files instead of merging (skips the prompt)"},{flags:"--json",description:"Output as JSON"}],handler:opts=>scaffoldCommand({appId:opts.appId,json:!!opts.json,overwrite:!!opts.overwrite})},{name:"available-scopes",description:"List OAuth scopes supported by the IdP",examples:["brevo app available-scopes","brevo app available-scopes --web","brevo app available-scopes --json"],options:[{flags:"--json",description:"Output as JSON"},{flags:"--web",description:"Open the scope catalog in a local browser page"}],handler:opts=>scopesCommand({json:!!opts.json,web:!!opts.web})},{name:"start",description:"Run a scaffolded feature locally",arguments:[{name:"[feature]",description:"Feature to start (e.g. oauth)"}],examples:["brevo app start oauth","brevo app start oauth --port 3000"],options:[{flags:"--port <port>",description:"Server port (default: 3009)",parser:v=>parsePositiveInt(v,"--port")}],handler:(opts,feature)=>startCommand({feature,port:opts.port})}]},skillCommandGroup={name:"skill:cli",description:"Install the brevo-cli Claude Code skill (Claude only)",commands:[{name:"install",description:"Install the brevo-cli skill into ~/.claude/skills/ (Claude only \u2014 other AI tools should read agent-context/AGENTS.md instead)",examples:["brevo skill:cli install","brevo skill:cli install --json"],options:[{flags:"--json",description:"Output as JSON"}],handler:opts=>installCommand({json:!!opts.json})},{name:"uninstall",description:"Remove the brevo-cli skill from ~/.claude/skills/ (Claude only)",examples:["brevo skill:cli uninstall","brevo skill:cli uninstall --json"],options:[{flags:"--json",description:"Output as JSON"}],handler:opts=>uninstallCommand({json:!!opts.json})}]};var fs10=__toESM(require("node:fs")),os4=__toESM(require("node:os")),path11=__toESM(require("node:path"));var REGISTRY_URL=name=>`https://registry.npmjs.org/${encodeURIComponent(name).replace("%40","@")}/latest`,TTL_MS=720*60*1e3,FETCH_TIMEOUT_MS=2e3,NOTIFY_WAIT_MS=1500,CACHE_FILE="update-check.json";function getCachePath(override,env=process.env){if(override)return override;let dir=env.BREVO_CONFIG_HOME||path11.join(os4.homedir(),".brevo");return path11.join(dir,CACHE_FILE)}function shouldShowBannerBefore(argv){let args2=argv.slice(2);return args2.length===0||args2.includes("--help")||args2.includes("-h")||args2.includes("--version")||args2.includes("-V")?!0:args2[0]==="app"&&(args2[1]==="init"||args2[1]==="create")}function shouldSkipCheck(opts){let env=opts.env??process.env,argv=opts.argv??process.argv,isTTY2=opts.isTTY??!!process.stdout.isTTY;return!!(env.CI==="true"||env.CI==="1"||!isTTY2||env.NO_UPDATE_NOTIFIER==="1"||env.NO_UPDATE_NOTIFIER==="true"||env.BREVO_NO_UPDATE_NOTIFIER==="1"||env.BREVO_NO_UPDATE_NOTIFIER==="true"||argv.includes("--no-update-notifier"))}function parseVersion(v){let match=/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v.trim());if(!(!match?.[1]||!match?.[2]||!match?.[3]))return{major:Number.parseInt(match[1],10),minor:Number.parseInt(match[2],10),patch:Number.parseInt(match[3],10),prerelease:match[4]??""}}function comparePrereleaseIdentifiers(ai,bi){if(ai===bi)return 0;let aNum=/^\d+$/.test(ai),bNum=/^\d+$/.test(bi);if(aNum&&bNum){let diff=Number.parseInt(ai,10)-Number.parseInt(bi,10);return diff===0?0:diff>0?1:-1}return aNum?-1:bNum||ai>bi?1:-1}function comparePrerelease(a,b){if(a===b)return 0;let aParts=a.split("."),bParts=b.split("."),len=Math.min(aParts.length,bParts.length);for(let i=0;i<len;i++){let cmp=comparePrereleaseIdentifiers(aParts[i]??"",bParts[i]??"");if(cmp!==0)return cmp}return aParts.length===bParts.length?0:aParts.length>bParts.length?1:-1}function compareVersions(current,latest){let c=parseVersion(current),l=parseVersion(latest);return!c||!l?0:l.major!==c.major?l.major-c.major:l.minor!==c.minor?l.minor-c.minor:l.patch!==c.patch?l.patch-c.patch:c.prerelease&&!l.prerelease?1:!c.prerelease&&l.prerelease?-1:comparePrerelease(l.prerelease,c.prerelease)}function isNewer(current,latest){return compareVersions(current,latest)>0}function isMajorBehind(current,latest){let c=parseVersion(current),l=parseVersion(latest);return!c||!l?!1:l.major>c.major}function readCache(cachePath){try{let raw=JSON.parse(fs10.readFileSync(cachePath,"utf-8"));if(raw&&typeof raw=="object"&&typeof raw.latest=="string"&&typeof raw.lastChecked=="number"&&Number.isFinite(raw.lastChecked))return{latest:raw.latest,lastChecked:raw.lastChecked}}catch{}}function writeCache(cachePath,cache){try{fs10.mkdirSync(path11.dirname(cachePath),{recursive:!0,mode:448}),fs10.writeFileSync(cachePath,JSON.stringify(cache,null,2),"utf-8")}catch{}}async function fetchLatestVersion(name,opts){let fetchImpl=opts?.fetchImpl??fetch,timeoutMs=opts?.fetchTimeoutMs??FETCH_TIMEOUT_MS,controller=new AbortController,timer=setTimeout(()=>controller.abort(),timeoutMs);try{let res=await fetchImpl(REGISTRY_URL(name),{signal:controller.signal,headers:{Accept:"application/json"}});if(!res.ok)return;let json=await res.json();return typeof json.version=="string"?json.version:void 0}catch{return}finally{clearTimeout(timer)}}function renderBox(lines){let inner=Math.max(...lines.map(l=>l.length))+4,top="\u256D"+"\u2500".repeat(inner)+"\u256E",bot="\u2570"+"\u2500".repeat(inner)+"\u256F",pad=s=>" "+s+" ".repeat(inner-s.length-2);return["",` ${top}`,...lines.map(l=>` \u2502${pad(l)}\u2502`),` ${bot}`,""].join(`
656
+ `)}function withNotice(box,serverMessage){let line=serverMessage??messages.CLI_VERSION_NOTICE_FALLBACK;return`
657
+ ${color(COLOR_RED,line)}
658
+ ${box}`}function formatBanner(current,latest,name,serverMessage){return withNotice(renderBox([messages.UPDATE_AVAILABLE(current,latest),messages.UPDATE_RUN(name),messages.UPDATE_RUN_YARN(name),messages.UPDATE_RUN_BREW]),serverMessage)}function formatForceUpdateBanner(current,latest,name,serverMessage){return withNotice(renderBox([messages.FORCE_UPDATE_REQUIRED(current,latest),messages.FORCE_UPDATE_HINT,messages.UPDATE_RUN(name),messages.UPDATE_RUN_YARN(name),messages.UPDATE_RUN_BREW]),serverMessage)}function formatBlockedBanner(current,latest,name,serverMessage){return latest?formatForceUpdateBanner(current,latest,name,serverMessage):withNotice(renderBox([messages.FORCE_UPDATE_HINT,messages.UPDATE_RUN(name),messages.UPDATE_RUN_YARN(name),messages.UPDATE_RUN_BREW]),serverMessage)}function startUpdateCheck(opts){if(shouldSkipCheck(opts))return{pending:Promise.resolve()};let cachePath=getCachePath(opts.cachePath,opts.env),now=opts.now?opts.now():Date.now(),ttl=opts.ttlMs??TTL_MS,cache=readCache(cachePath);if(!(!cache||now-cache.lastChecked>ttl))return{cachedLatest:cache?.latest,pending:Promise.resolve()};let handle={cachedLatest:cache?.latest,pending:Promise.resolve()};return handle.pending=(async()=>{let latest=await fetchLatestVersion(opts.pkg.name,opts);latest&&(handle.cachedLatest=latest,writeCache(cachePath,{latest,lastChecked:now}))})(),handle}async function notifyUpdate(handle,pkg2,output=process.stderr,waitMs=NOTIFY_WAIT_MS){handle.notified||(await Promise.race([handle.pending,new Promise(resolve11=>setTimeout(resolve11,waitMs).unref?.())]),handle.cachedLatest&&isNewer(pkg2.version,handle.cachedLatest)&&(handle.notified=!0,output.write(formatBanner(pkg2.version,handle.cachedLatest,pkg2.name,handle.notice)+`
659
+ `)))}async function enforceMinVersion(handle,pkg2,output=process.stderr,waitMs=NOTIFY_WAIT_MS){return await Promise.race([handle.pending,new Promise(resolve11=>setTimeout(resolve11,waitMs).unref?.())]),handle.cachedLatest&&isMajorBehind(pkg2.version,handle.cachedLatest)?(output.write(formatForceUpdateBanner(pkg2.version,handle.cachedLatest,pkg2.name,handle.notice)+`
660
+ `),!0):!1}var fs11=__toESM(require("node:fs")),os5=__toESM(require("node:os")),path12=__toESM(require("node:path"));var CLI_INFO_TIMEOUT_MS=1500,MAX_NOTICE_MESSAGE_LEN=200,CLI_INFO_CACHE_TTL_MS=900*1e3,CLI_INFO_CACHE_FILE="cli-info-cache.json";function getCliInfoCachePath(env){let dir=env.BREVO_CONFIG_HOME||path12.join(os5.homedir(),".brevo");return path12.join(dir,CLI_INFO_CACHE_FILE)}function readCliInfoCache(cachePath){try{let raw=JSON.parse(fs11.readFileSync(cachePath,"utf-8"));if(raw&&typeof raw=="object"&&typeof raw.cliVersion=="string"&&typeof raw.baseUrl=="string"&&typeof raw.lastChecked=="number"&&Number.isFinite(raw.lastChecked)&&raw.info&&typeof raw.info=="object"&&typeof raw.info.isBlocked=="boolean")return{cliVersion:raw.cliVersion,baseUrl:raw.baseUrl,lastChecked:raw.lastChecked,info:{isBlocked:raw.info.isBlocked,upgradeMessage:typeof raw.info.upgradeMessage=="string"?raw.info.upgradeMessage:void 0}}}catch{}}function writeCliInfoCache(cachePath,cache){try{fs11.mkdirSync(path12.dirname(cachePath),{recursive:!0,mode:448}),fs11.writeFileSync(cachePath,JSON.stringify(cache,null,2),"utf-8")}catch{}}function sanitizeNoticeMessage(raw){if(typeof raw!="string"||!raw||looksLikeHtml(raw))return;let oneLine=sanitizeErrorMessage(raw).replace(/\s+/g," ").trim();if(oneLine)return oneLine.length>MAX_NOTICE_MESSAGE_LEN?oneLine.slice(0,MAX_NOTICE_MESSAGE_LEN):oneLine}function buildUrl(baseUrl,query){let params=new URLSearchParams({cli_version:query.cliVersion,reason:query.reason});return`${baseUrl}${ENDPOINTS.CLI_INFO}?${params.toString()}`}async function fetchCliInfo(query,opts={}){let env=opts.env??process.env,cachePath=opts.cachePath??getCliInfoCachePath(env),now=opts.now?opts.now():Date.now(),ttlMs=opts.ttlMs??CLI_INFO_CACHE_TTL_MS,baseUrl=opts.baseUrl??APP_STORE_BASE,cached=readCliInfoCache(cachePath);if(cached&&cached.cliVersion===query.cliVersion&&cached.baseUrl===baseUrl&&now-cached.lastChecked<=ttlMs)return cached.info;let fetchImpl=opts.fetchImpl??fetch,controller=new AbortController,timer=setTimeout(()=>controller.abort(),opts.timeoutMs??CLI_INFO_TIMEOUT_MS);try{let res=await fetchImpl(buildUrl(baseUrl,query),{method:"GET",signal:controller.signal,headers:{Accept:"application/json"}});if(!res.ok)return;let body=await res.json();if(!body||typeof body!="object")return;let info={upgradeMessage:sanitizeNoticeMessage(body.upgrade_message),isBlocked:body.is_blocked===!0};return writeCliInfoCache(cachePath,{cliVersion:query.cliVersion,baseUrl,info,lastChecked:now}),info}catch{return}finally{clearTimeout(timer)}}var pkg=JSON.parse(fs12.readFileSync(path13.resolve(__dirname,"../../package.json"),"utf-8")),version=pkg.version,updateCheck=startUpdateCheck({pkg,argv:process.argv}),showBannerEarly=shouldShowBannerBefore(process.argv);process.env.NODE_TLS_REJECT_UNAUTHORIZED==="0"&&logWarn(messages.TLS_VERIFICATION_DISABLED);var program=new import_commander2.Command;program.name("brevo").description("Brevo Developer CLI \u2014 create, manage, and test OAuth integrations").version(version).option("--debug","Enable debug logging").configureHelp({formatHelp:createHelpFormatter(program)}).action((_options,cmd)=>{let stray=cmd.args;if(stray.length===0){cmd.outputHelp();return}process.stderr.write(`error: unknown command '${stray[0]}'
661
+ `),process.stderr.write("See `brevo --help` for available commands.\n"),process.exit(EXIT_CODES.ERROR)});installAuthGuard(program);var oauthFreshnessDeps={getAuthCred,refresh:refreshToken=>refreshAccessToken(refreshToken,OAUTH_PROXY_URL),persist:updateOauthTokens,isTerminal:err=>err instanceof RefreshError&&err.unauthorized,onTerminal:clearCredentials,onError:err=>logDebug("proactive oauth refresh skipped",{reason:err instanceof Error?err.message:String(err)})};installProactiveOauthRefresh(program,oauthFreshnessDeps);client.setEnsureFresh(async()=>{await ensureFreshOauthToken(oauthFreshnessDeps)});registerAll(program,topLevelCommands,[appCommandGroup,skillCommandGroup]);client.setOnAuthFailure(async()=>{let auth=getAuthCred();if(auth?.kind==="oauth")try{let refreshed=await refreshAccessToken(auth.refreshToken,OAUTH_PROXY_URL);updateOauthTokens(refreshed);return}catch(err){throw err instanceof RefreshError&&err.unauthorized?(clearCredentials(),new AuthExpiredError):err}stopActiveSpinner(),clearCredentials(),logWarn(messages.AUTH_EXPIRED),logInfo(` ${messages.AUTH_GET_KEY_URL}
662
+ `);let newKey=await readHiddenInput(messages.AUTH_EXPIRED_PROMPT+" "),account=await client.getWithKey(ENDPOINTS.ACCOUNT,newKey);saveCredentials(newKey,{email:account.email,organizationId:account.organization_id,userId:account.user_id}),logSuccess(messages.AUTH_SUCCESS(account.email))});for(let signal of["SIGINT","SIGTERM"])process.on(signal,()=>{logInfo(`
663
+ Received ${signal}, shutting down.
664
+ `),process.exit(EXIT_CODES.ABORTED)});warnIfPathStripped();var args=new Set(process.argv.slice(2)),isHelpOrVersion=args.has("--help")||args.has("-h")||args.has("--version")||args.has("-V");async function applyCliInfo(){let info=await fetchCliInfo({cliVersion:version,reason:"startup"});info&&(updateCheck.notice=info.upgradeMessage,!isHelpOrVersion&&info.isBlocked&&(process.stderr.write(formatBlockedBanner(version,updateCheck.cachedLatest,pkg.name,info.upgradeMessage)+`
665
+ `),process.exit(EXIT_CODES.ERROR)))}async function forceGate(){if(isHelpOrVersion)return;await enforceMinVersion(updateCheck,{name:pkg.name,version})&&process.exit(EXIT_CODES.ERROR)}applyCliInfo().then(forceGate).then(()=>showBannerEarly?notifyUpdate(updateCheck,{name:pkg.name,version}):void 0).then(()=>program.parseAsync(process.argv)).then(async()=>{await notifyUpdate(updateCheck,{name:pkg.name,version}),skillService.autoRefreshOutdated(),process.exit(0)}).catch(async err=>{emitJsonError(err),err instanceof AbortError&&(logInfo(`
666
+ ${messages.ABORTED}`),process.exit(EXIT_CODES.ABORTED));let exitCode=err instanceof CliError?err.exitCode:EXIT_CODES.ERROR;err instanceof CliError?logError(err.message):logError(err.message,err),await notifyUpdate(updateCheck,{name:pkg.name,version}),process.exit(exitCode)});
667
+ //# sourceMappingURL=index.js.map