@getbrevo/cli 2.0.2 → 2.2.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.
- package/CHANGELOG.md +237 -0
- package/README.md +32 -6
- package/agent-context/AGENTS.md +62 -15
- package/agent-context/SKILL.md +98 -14
- package/dist/bin/files/AGENTS.md.tmpl +119 -0
- package/dist/bin/files/CLAUDE.md.tmpl +139 -0
- package/dist/bin/files/README.md.tmpl +184 -0
- package/dist/bin/files/app-config.json.tmpl +16 -0
- package/dist/{templates → bin}/files/src/oauth/.env.example.tmpl +8 -0
- package/dist/{templates → bin}/files/src/oauth/.env.local.tmpl +2 -0
- package/dist/{templates → bin}/files/src/oauth/handler.js.tmpl +49 -7
- package/dist/{templates → bin}/files/src/oauth/server.js.tmpl +20 -14
- package/dist/{templates → bin}/files/src/oauth/token-store.js.tmpl +19 -0
- package/dist/bin/index.js +680 -235
- package/dist/bin/index.js.map +7 -1
- package/package.json +6 -4
- package/dist/api/client.d.ts +0 -25
- package/dist/api/client.d.ts.map +0 -1
- package/dist/api/client.js +0 -205
- package/dist/api/client.js.map +0 -1
- package/dist/bin/index.d.ts +0 -3
- package/dist/bin/index.d.ts.map +0 -1
- package/dist/commands/app/create.d.ts +0 -8
- package/dist/commands/app/create.d.ts.map +0 -1
- package/dist/commands/app/create.js +0 -325
- package/dist/commands/app/create.js.map +0 -1
- package/dist/commands/app/credentials.d.ts +0 -6
- package/dist/commands/app/credentials.d.ts.map +0 -1
- package/dist/commands/app/credentials.js +0 -114
- package/dist/commands/app/credentials.js.map +0 -1
- package/dist/commands/app/delete.d.ts +0 -6
- package/dist/commands/app/delete.d.ts.map +0 -1
- package/dist/commands/app/delete.js +0 -167
- package/dist/commands/app/delete.js.map +0 -1
- package/dist/commands/app/list.d.ts +0 -4
- package/dist/commands/app/list.d.ts.map +0 -1
- package/dist/commands/app/list.js +0 -71
- package/dist/commands/app/list.js.map +0 -1
- package/dist/commands/app/scaffold.d.ts +0 -5
- package/dist/commands/app/scaffold.d.ts.map +0 -1
- package/dist/commands/app/scaffold.js +0 -213
- package/dist/commands/app/scaffold.js.map +0 -1
- package/dist/commands/app/scopes.d.ts +0 -2
- package/dist/commands/app/scopes.d.ts.map +0 -1
- package/dist/commands/app/scopes.js +0 -81
- package/dist/commands/app/scopes.js.map +0 -1
- package/dist/commands/app/start.d.ts +0 -5
- package/dist/commands/app/start.d.ts.map +0 -1
- package/dist/commands/app/start.js +0 -220
- package/dist/commands/app/start.js.map +0 -1
- package/dist/commands/app/update.d.ts +0 -2
- package/dist/commands/app/update.d.ts.map +0 -1
- package/dist/commands/app/update.js +0 -444
- package/dist/commands/app/update.js.map +0 -1
- package/dist/commands/definitions.d.ts +0 -5
- package/dist/commands/definitions.d.ts.map +0 -1
- package/dist/commands/definitions.js +0 -262
- package/dist/commands/definitions.js.map +0 -1
- package/dist/commands/init.d.ts +0 -2
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -138
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/login.d.ts +0 -6
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js +0 -251
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts +0 -5
- package/dist/commands/logout.d.ts.map +0 -1
- package/dist/commands/logout.js +0 -54
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/skill/install.d.ts +0 -4
- package/dist/commands/skill/install.d.ts.map +0 -1
- package/dist/commands/skill/install.js +0 -29
- package/dist/commands/skill/install.js.map +0 -1
- package/dist/commands/skill/uninstall.d.ts +0 -4
- package/dist/commands/skill/uninstall.d.ts.map +0 -1
- package/dist/commands/skill/uninstall.js +0 -23
- package/dist/commands/skill/uninstall.js.map +0 -1
- package/dist/commands/whoami.d.ts +0 -4
- package/dist/commands/whoami.d.ts.map +0 -1
- package/dist/commands/whoami.js +0 -85
- package/dist/commands/whoami.js.map +0 -1
- package/dist/container.d.ts +0 -7
- package/dist/container.d.ts.map +0 -1
- package/dist/container.js +0 -28
- package/dist/container.js.map +0 -1
- package/dist/lang/en.d.ts +0 -162
- package/dist/lang/en.d.ts.map +0 -1
- package/dist/lang/en.js +0 -189
- package/dist/lang/en.js.map +0 -1
- package/dist/lib/abort.d.ts +0 -3
- package/dist/lib/abort.d.ts.map +0 -1
- package/dist/lib/abort.js +0 -30
- package/dist/lib/abort.js.map +0 -1
- package/dist/lib/auth-guard.d.ts +0 -13
- package/dist/lib/auth-guard.d.ts.map +0 -1
- package/dist/lib/auth-guard.js +0 -60
- package/dist/lib/auth-guard.js.map +0 -1
- package/dist/lib/browser.d.ts +0 -2
- package/dist/lib/browser.d.ts.map +0 -1
- package/dist/lib/browser.js +0 -16
- package/dist/lib/browser.js.map +0 -1
- package/dist/lib/cli-version.d.ts +0 -2
- package/dist/lib/cli-version.d.ts.map +0 -1
- package/dist/lib/cli-version.js +0 -59
- package/dist/lib/cli-version.js.map +0 -1
- package/dist/lib/command-handler.d.ts +0 -10
- package/dist/lib/command-handler.d.ts.map +0 -1
- package/dist/lib/command-handler.js +0 -16
- package/dist/lib/command-handler.js.map +0 -1
- package/dist/lib/command-registry.d.ts +0 -28
- package/dist/lib/command-registry.d.ts.map +0 -1
- package/dist/lib/command-registry.js +0 -54
- package/dist/lib/command-registry.js.map +0 -1
- package/dist/lib/config.d.ts +0 -83
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js +0 -430
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/constants.d.ts +0 -48
- package/dist/lib/constants.d.ts.map +0 -1
- package/dist/lib/constants.js +0 -121
- package/dist/lib/constants.js.map +0 -1
- package/dist/lib/errors.d.ts +0 -30
- package/dist/lib/errors.d.ts.map +0 -1
- package/dist/lib/errors.js +0 -66
- package/dist/lib/errors.js.map +0 -1
- package/dist/lib/exit-codes.d.ts +0 -9
- package/dist/lib/exit-codes.d.ts.map +0 -1
- package/dist/lib/exit-codes.js +0 -12
- package/dist/lib/exit-codes.js.map +0 -1
- package/dist/lib/hidden-input.d.ts +0 -2
- package/dist/lib/hidden-input.d.ts.map +0 -1
- package/dist/lib/hidden-input.js +0 -78
- package/dist/lib/hidden-input.js.map +0 -1
- package/dist/lib/json-output.d.ts +0 -6
- package/dist/lib/json-output.d.ts.map +0 -1
- package/dist/lib/json-output.js +0 -11
- package/dist/lib/json-output.js.map +0 -1
- package/dist/lib/logger.d.ts +0 -11
- package/dist/lib/logger.d.ts.map +0 -1
- package/dist/lib/logger.js +0 -110
- package/dist/lib/logger.js.map +0 -1
- package/dist/lib/oauth-freshness.d.ts +0 -30
- package/dist/lib/oauth-freshness.d.ts.map +0 -1
- package/dist/lib/oauth-freshness.js +0 -61
- package/dist/lib/oauth-freshness.js.map +0 -1
- package/dist/lib/port.d.ts +0 -25
- package/dist/lib/port.d.ts.map +0 -1
- package/dist/lib/port.js +0 -103
- package/dist/lib/port.js.map +0 -1
- package/dist/lib/telemetry.d.ts +0 -9
- package/dist/lib/telemetry.d.ts.map +0 -1
- package/dist/lib/telemetry.js +0 -57
- package/dist/lib/telemetry.js.map +0 -1
- package/dist/lib/ui.d.ts +0 -14
- package/dist/lib/ui.d.ts.map +0 -1
- package/dist/lib/ui.js +0 -75
- package/dist/lib/ui.js.map +0 -1
- package/dist/lib/update-notifier.d.ts +0 -37
- package/dist/lib/update-notifier.d.ts.map +0 -1
- package/dist/lib/update-notifier.js +0 -298
- package/dist/lib/update-notifier.js.map +0 -1
- package/dist/lib/validators.d.ts +0 -55
- package/dist/lib/validators.d.ts.map +0 -1
- package/dist/lib/validators.js +0 -166
- package/dist/lib/validators.js.map +0 -1
- package/dist/services/account.d.ts +0 -8
- package/dist/services/account.d.ts.map +0 -1
- package/dist/services/account.js +0 -15
- package/dist/services/account.js.map +0 -1
- package/dist/services/app.d.ts +0 -38
- package/dist/services/app.d.ts.map +0 -1
- package/dist/services/app.js +0 -145
- package/dist/services/app.js.map +0 -1
- package/dist/services/browser-auth.d.ts +0 -15
- package/dist/services/browser-auth.d.ts.map +0 -1
- package/dist/services/browser-auth.js +0 -188
- package/dist/services/browser-auth.js.map +0 -1
- package/dist/services/normalize-app-id.d.ts +0 -12
- package/dist/services/normalize-app-id.d.ts.map +0 -1
- package/dist/services/normalize-app-id.js +0 -38
- package/dist/services/normalize-app-id.js.map +0 -1
- package/dist/services/oauth-metadata.d.ts +0 -7
- package/dist/services/oauth-metadata.d.ts.map +0 -1
- package/dist/services/oauth-metadata.js +0 -43
- package/dist/services/oauth-metadata.js.map +0 -1
- package/dist/services/oauth-refresh.d.ts +0 -14
- package/dist/services/oauth-refresh.d.ts.map +0 -1
- package/dist/services/oauth-refresh.js +0 -70
- package/dist/services/oauth-refresh.js.map +0 -1
- package/dist/services/scopes-html.d.ts +0 -3
- package/dist/services/scopes-html.d.ts.map +0 -1
- package/dist/services/scopes-html.js +0 -516
- package/dist/services/scopes-html.js.map +0 -1
- package/dist/services/scopes-web.d.ts +0 -12
- package/dist/services/scopes-web.d.ts.map +0 -1
- package/dist/services/scopes-web.js +0 -95
- package/dist/services/scopes-web.js.map +0 -1
- package/dist/services/skill.d.ts +0 -48
- package/dist/services/skill.d.ts.map +0 -1
- package/dist/services/skill.js +0 -223
- package/dist/services/skill.js.map +0 -1
- package/dist/skills/index.d.ts +0 -47
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -61
- package/dist/skills/index.js.map +0 -1
- package/dist/templates/files/AGENTS.md.tmpl +0 -52
- package/dist/templates/files/CLAUDE.md.tmpl +0 -59
- package/dist/templates/files/README.md.tmpl +0 -85
- package/dist/templates/files/app-config.json.tmpl +0 -25
- package/dist/templates/index.d.ts +0 -36
- package/dist/templates/index.d.ts.map +0 -1
- package/dist/templates/index.js +0 -105
- package/dist/templates/index.js.map +0 -1
- package/dist/types.d.ts +0 -32
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- /package/dist/{templates → bin}/files/gitignore.tmpl +0 -0
- /package/dist/{templates → bin}/files/src/oauth/package.json.tmpl +0 -0
package/dist/bin/index.js
CHANGED
|
@@ -1,242 +1,687 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}) :
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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"},coreEndpoints={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_STORE_APP_UPLOAD:appId=>`/v3/app-store/apps/${encodeURIComponent(appId)}/upload`,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"},ENDPOINTS={...coreEndpoints},EXAMPLE_APP_ID="3f8c1a2e-5b47-4d9c-8e10-6a2b7d4f0c93",coreCli={LOGIN:"brevo login",INIT:"brevo app init",HELP:"brevo --help",APP_CREATE:"brevo app create",APP_LIST:"brevo app list",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_INSTALL:accountId=>accountId?`brevo app install ${accountId}`:"brevo app install",APP_UNINSTALL:accountId=>accountId?`brevo app uninstall ${accountId}`:"brevo app uninstall",APP_INSTALL_APP_ID:appId=>appId?`brevo app install --app-id ${appId}`:"brevo app install --app-id <id>",APP_UNINSTALL_APP_ID:appId=>appId?`brevo app uninstall --app-id ${appId}`:"brevo app uninstall --app-id <id>",APP_DELETE:"brevo app delete",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"},CLI={...coreCli};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"],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),rejectRootCtaFields(block),validateSurfacePointList(block.surface_point_list,extensionType)}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": "contactDetails.header.menu", "context": ["recordId"] }].')}function rejectRootCtaFields(block){let moved=[["label",'"label": "Open in Acme"'],["more_info",'"more_info": "See this record in Acme"'],["redirect_link",'"redirect_link": "https://example.com/open"'],["modal_iframe_url",'"modal_iframe_url": "https://example.com/embed"']];for(let[key,hint]of moved)if(block[key]!==void 0)throw new CliError(`ui_app.${key} moved into each surface_point_list entry (each placement carries its own) \u2014 e.g. [{ "surface_point_name": "contactDetails.header.menu", ${hint} }]. Move it in app-config.json.`);if(block.link_target!==void 0)throw new CliError("ui_app.link_target moved onto each surface_point_list entry (BEX-426), and is not authored in app-config.json at all \u2014 `brevo app upload` injects it per placement. Remove it from the file.")}function validateSurfacePointList(entries,extensionType){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": "contactDetails.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": "contactDetails.header.menu", "context": ["recordId"] }. A bare string is the pre-BEX-290 shape.');let row=entry;if(row.surface_point_name===void 0)throw new CliError('ui_app.surface_point_list entries must carry "surface_point_name" (e.g. { "surface_point_name": "contactDetails.header.menu" }).'+(row.surface_point!==void 0?' "surface_point" is not a field \u2014 rename it to "surface_point_name".':""));let 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();names.push(name),validateEntryContext(row,name),validateEntrySize(row,name),validateEntryCtaFields(row,name,extensionType)}if(new Set(names).size!==names.length)throw new CliError("ui_app.surface_point_list contains duplicate extension points.")}var SIZE_AXIS_PATTERN=/^([1-9]\d*)(px|%)$/;function validateSurfacePointSize(size){if(!size||typeof size!="object"||Array.isArray(size))return'must be an object, e.g. { "width": "280px", "height": "160px" }.';let{width,height}=size;for(let[axis,value]of Object.entries({width,height})){if(value===void 0)continue;let match=typeof value=="string"?SIZE_AXIS_PATTERN.exec(value):null;if(!match)return`${axis} must be a positive integer with a px or % unit, e.g. "280px" or "50%".`;if(match[2]==="%"&&Number(match[1])>100)return`${axis} "${match[0]}" is out of range \u2014 a % axis must be between 1% and 100%.`}return!0}function validateEntryContext(row,name){if(row.context===void 0)return;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}`)}function validateEntrySize(row,name){if(row.size===void 0)return;let sizeCheck=validateSurfacePointSize(row.size);if(sizeCheck!==!0)throw new CliError(`ui_app.surface_point_list["${name}"].size: ${sizeCheck}`)}function validateEntryCtaFields(row,name,extensionType){let at=field=>`ui_app.surface_point_list["${name}"].${field}`,labelCheck=validateUiAppLabel(asText(row.label));if(labelCheck!==!0)throw new CliError(`${at("label")}: ${labelCheck}`);let moreInfoCheck=validateUiAppMoreInfo(asText(row.more_info));if(moreInfoCheck!==!0)throw new CliError(`${at("more_info")}: ${moreInfoCheck}`);if(extensionType===EXTENSION_TYPE_IFRAME){let urlCheck2=validateUiAppUrl(asText(row.modal_iframe_url));if(urlCheck2!==!0)throw new CliError(`${at("modal_iframe_url")}: ${urlCheck2}`);if(isPresentField(row.link_target))throw new CliError(`${at("link_target")} has no effect on "${EXTENSION_TYPE_IFRAME}" extensions, which embed their URL in a modal rather than navigating to it. Remove it.`);if(isPresentField(row.redirect_link))throw new CliError(`${at("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.`);return}let urlCheck=validateUiAppUrl(asText(row.redirect_link));if(urlCheck!==!0)throw new CliError(`${at("redirect_link")}: ${urlCheck}`);if(row.link_target!==void 0&&!UPLOADABLE_LINK_TARGETS.includes(asText(row.link_target)))throw new CliError(`Invalid ${at("link_target")} "${asText(row.link_target)}". Must be one of: ${UPLOADABLE_LINK_TARGETS.join(", ")}.`);if(isPresentField(row.modal_iframe_url))throw new CliError(`${at("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 parseAccountId(value){let trimmed=String(value??"").trim();if(!trimmed)throw new CliError("Invalid account ID: value cannot be empty.");if(!/^\d+$/.test(trimmed))throw new CliError(`Invalid account ID: "${trimmed}" is not a numeric Brevo account ID.`);return trimmed}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)`}function numberedSteps(cdDir,steps){let commandWidth=Math.max(...steps.map(([command])=>command.length)),offset=cdDir?1:0;return[...cdDir?[`1. cd ${cdDir}`]:[],...steps.map(([command,note],i)=>`${i+1+offset}. ${command.padEnd(commandWidth)} (${note})`)]}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:"What distribution type should this app use?",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=>numberedSteps(cdDir,[[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=>numberedSteps(cdDir,[[CLI.APP_UPLOAD,"validate and save your configuration"],[CLI.APP_INSTALL(),"make it available in an account"]]),APP_CREATE_UI_PAGES_SPINNER:"Loading record pages...",APP_CREATE_UI_POINTS_SPINNER:"Loading placements...",APP_CREATE_UI_POINTS_FETCH_FAILED:"Could not load the available placements from the Brevo API \u2014 the UI-app flow needs them to offer where your app can appear. Check your connection and try again. Creating an OAuth app does not need this and still works.",APP_CREATE_UI_POINTS_EMPTY:"The Brevo API returned no available placements for UI apps. This usually means the extension-point registry has not been seeded in this environment \u2014 try again later.",APP_CREATE_UI_POINTS_NONE_FOR_TYPE:extensionType=>`None of the available placements can host a "${extensionType}" extension. This environment's extension-point registry may predate it \u2014 try again later.`,APP_CREATE_UI_SURFACE_PROMPT:"Which record page should it appear on?",APP_CREATE_UI_PLACEMENT_PAGE_PROMPT:page=>`Where should it appear on the ${page} page?`,APP_CREATE_UI_INTEGRATION_PROMPT:"What type of integration are you adding?",APP_CREATE_UI_INTEGRATION_EXTERNAL_LINK:"Link (Opens your URL in a new tab)",APP_CREATE_UI_LABEL_PROMPT:"Label \u2014 the menu entry\u2019s text, and the card\u2019s button text:",APP_CREATE_UI_MORE_INFO_PROMPT:"More info (optional) \u2014 the menu entry\u2019s subtext, and the card\u2019s description:",APP_CREATE_UI_REDIRECT_LINK_PROMPT:"Redirect link \u2014 the destination URL (record context arrives as query params):",APP_CREATE_UI_BOX_TITLE:"UI app created",APP_CREATE_UI_BOX_LABEL_NOTE:(label,appName)=>`The menu entry is labelled "${label}". On a card that text becomes the button, and the card's title is the app name ("${appName}").`,APP_CREATE_UI_BOX_EXAMPLE_URL_LABEL:"Brevo will open, for example:",APP_CREATE_UI_BOX_EXAMPLE_URL_NOTE:"Values are placeholders. Read them as query parameters \u2014 the path is never templated.",APP_CREATE_UI_BOX_HINT:`Edit the \`ui_app\` block in app-config.json to change any of this \u2014 add more placements as extra \`surface_point_list\` entries, each with its own label and redirect link \u2014 then run \`${CLI.APP_UPLOAD}\`.`,APP_INSTALL_SELECT:"Select an app to install:",APP_INSTALL_ACCOUNT_LABEL:(accountId,companyName,self)=>{let name=companyName?.trim();return self?name?`${name} (your own account, org ID ${accountId})`:`your own account (org ID ${accountId})`:name?`${name} (account ${accountId})`:`account ${accountId}`},APP_INSTALL_SUMMARY:"Installing this configuration (as stored on the server):",APP_INSTALL_SUMMARY_NO_VERSION:"(unknown)",APP_INSTALL_CONFIG_DRIFT:`Your local app-config.json differs from the configuration above. The install uses what the server has stored \u2014 run \`${CLI.APP_UPLOAD}\` first if you meant to install your local changes.`,APP_INSTALL_CONFIRM:(name,appId,account)=>`Install app "${name}" (${appId}) into ${account}?`,APP_INSTALL_CANCELLED:"Install cancelled.",APP_INSTALL_SUCCESS:(appId,account)=>`App ${appId} installed into ${account}.`,APP_INSTALL_NOT_UI_APP:appId=>`App ${appId} is an OAuth app, and only UI apps are installed into an account. An OAuth app becomes usable when a user authorizes it, so there is nothing to install.
|
|
17
|
+
|
|
18
|
+
\`${CLI.APP_LIST}\` shows each app's type.`,APP_UNINSTALL_NOT_UI_APP:appId=>`App ${appId} is an OAuth app, and only UI apps are installed into an account, so there is nothing to uninstall.
|
|
19
|
+
|
|
20
|
+
\`${CLI.APP_LIST}\` shows each app's type.`,APP_INSTALL_SELECT_ACCOUNT:"Select the account to install into:",APP_UNINSTALL_SELECT_ACCOUNT:"Select the account to uninstall from:",APP_INSTALL_ACCOUNT_ID_REQUIRED:`This is a corporate account, so the target account can't be resolved automatically.
|
|
21
|
+
|
|
22
|
+
Pass it explicitly: ${CLI.APP_INSTALL("<account-id>")}
|
|
23
|
+
(Choosing one from a list requires an interactive terminal.)`,APP_INSTALL_NO_SUB_ACCOUNTS:`No active sub-accounts found on this corporate account.
|
|
24
|
+
|
|
25
|
+
Pass the target account explicitly: ${CLI.APP_INSTALL("<account-id>")}`,APP_INSTALL_NOT_UPLOADED:`Please first validate your configuration with \`${CLI.APP_UPLOAD}\`.`,APP_UNINSTALL_SELECT:"Select an app to uninstall:",APP_UNINSTALL_CONFIRM:(name,appId,account)=>`Uninstall app "${name}" (${appId}) from ${account}?`,APP_UNINSTALL_CANCELLED:"Uninstall cancelled.",APP_UNINSTALL_SUCCESS:(appId,account)=>`App ${appId} uninstalled from ${account}.`,APP_UNINSTALL_NOT_INSTALLED:(appId,account)=>`App ${appId} is not installed in ${account}.`,APP_INSTALL_NON_INTERACTIVE:"Cannot prompt for confirmation in non-interactive mode. Use --force or --json to skip.",APP_INSTALL_NO_UI_APPS:`You have no UI apps, and only UI apps are installed into an account.
|
|
26
|
+
|
|
27
|
+
\`${CLI.APP_LIST}\` shows each app's type; \`${CLI.APP_CREATE}\` creates a UI app.`,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:
|
|
28
|
+
|
|
29
|
+
${command}
|
|
30
|
+
|
|
31
|
+
\`${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_CREDENTIALS_UI_APP:appId=>`App ${appId} is a UI app, and UI apps have no OAuth credentials, so there is nothing to show.
|
|
32
|
+
|
|
33
|
+
\`${CLI.APP_LIST}\` shows each app's type.`,APP_UPDATE_REMOVED:`\`brevo app update\` has been removed \u2014 use \`${CLI.APP_UPLOAD}\` instead.
|
|
34
|
+
|
|
35
|
+
\`${CLI.APP_UPLOAD}\` pushes the whole of app-config.json and takes only
|
|
36
|
+
--yes and --json. None of the old edit flags exist any more:
|
|
37
|
+
|
|
38
|
+
--name --redirect-uri --scope --logo-uri --app-id
|
|
39
|
+
|
|
40
|
+
To change the app's name, redirect URLs, scopes or logo, edit
|
|
41
|
+
app-config.json, then run:
|
|
42
|
+
|
|
43
|
+
${CLI.APP_UPLOAD}
|
|
44
|
+
|
|
45
|
+
No app-config.json here? Set up a project for an app you already
|
|
46
|
+
have (\`${CLI.APP_LIST}\` shows their IDs), then edit and upload:
|
|
47
|
+
|
|
48
|
+
${CLI.APP_SCAFFOLD_APP_ID()}
|
|
49
|
+
${CLI.APP_UPLOAD}
|
|
50
|
+
|
|
51
|
+
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_CONFIRM_INSTALLED:"Proceed with upload and update every account this app is installed in?",APP_UPLOAD_INSTALLED_IMPACT:"This app may already be installed in Brevo accounts. Uploading replaces the configuration those accounts render, and the change is live as soon as the upload succeeds.",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_INSTALL_MISSING_CLIENT_ID:`Could not determine your Brevo account's organization ID.
|
|
52
|
+
|
|
53
|
+
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}".
|
|
54
|
+
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.
|
|
55
|
+
Replace 'all' with the specific scopes your integration uses (run \`${CLI.APP_SCOPES}\` to see the catalog),
|
|
56
|
+
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.
|
|
57
|
+
Replace 'all' with the specific scopes your integration uses in app-config.json's \`auth.scopes\`.
|
|
58
|
+
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_WARNING:(name,id)=>`Deleting "${name}" (${id}) will also remove it from every account where it is installed or published.
|
|
59
|
+
Installs and credentials cannot be recovered after this.`,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.
|
|
60
|
+
|
|
61
|
+
- cd into an existing project folder and try again, or
|
|
62
|
+
- run \`${CLI.APP_SCAFFOLD_APP_ID()}\` to set this directory up for an app you already have (\`${CLI.APP_LIST}\` shows their IDs), or
|
|
63
|
+
- 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}.
|
|
64
|
+
|
|
65
|
+
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}.
|
|
66
|
+
|
|
67
|
+
- cd ${targetDir} and run \`${CLI.APP_SCAFFOLD}\` to work on app ${localAppId}, or
|
|
68
|
+
- 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.
|
|
69
|
+
|
|
70
|
+
- run \`${CLI.APP_CREATE}\` to create a new app in this directory, or
|
|
71
|
+
- 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.
|
|
72
|
+
|
|
73
|
+
- cd ${projectDir} and run \`${CLI.APP_SCAFFOLD}\` there to work on that app, or
|
|
74
|
+
- 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.
|
|
75
|
+
|
|
76
|
+
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.
|
|
77
|
+
|
|
78
|
+
- If you still have this app's project folder, cd into it and run \`${CLI.APP_UPLOAD}\` from there, or
|
|
79
|
+
- 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_INSTALL()}\`.`,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.
|
|
80
|
+
|
|
81
|
+
Expected: ${entryFile}
|
|
82
|
+
Current directory: ${process.cwd()}
|
|
83
|
+
|
|
84
|
+
Make sure you are inside your scaffolded project directory.
|
|
85
|
+
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.
|
|
86
|
+
|
|
87
|
+
Available features:
|
|
88
|
+
${available}
|
|
89
|
+
|
|
90
|
+
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.
|
|
91
|
+
|
|
92
|
+
Either stop the process using port ${port}, use a different port with \`--port <port>\`,
|
|
93
|
+
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.
|
|
94
|
+
|
|
95
|
+
Stop the process using port ${port}, or pick another port with \`--port <port>\`
|
|
96
|
+
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.
|
|
97
|
+
|
|
98
|
+
Why: UI apps (action links) are still rolling out, and are enabled per account.
|
|
99
|
+
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)}
|
|
100
|
+
`)}}function logHttpResponse(status,path14){if(isDebug()){let code=status>=200&&status<300?"32":"31",line=`\u2190 ${status} ${path14}`;process.stderr.write(` ${color(code,line)}
|
|
101
|
+
`)}}function logDebug(context,data){if(isDebug()){let safe=redactSensitiveFields(data),line=`[debug] ${context}: ${JSON.stringify(safe)}`;process.stderr.write(` ${color("90",line)}
|
|
102
|
+
`)}}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(`
|
|
103
|
+
${color("31","\u2717")} ${message}
|
|
104
|
+
`),isDebug()&&error?process.stderr.write(` ${color("90",formatError(error))}
|
|
105
|
+
`):error&&process.stderr.write(` ${color("90","Run with --debug for full details")}
|
|
106
|
+
`)}function logSuccess(message){process.stdout.write(`
|
|
107
|
+
${color("32","\u2713")} ${message}
|
|
108
|
+
`)}function logInfo(message){process.stdout.write(` ${message}
|
|
109
|
+
`)}function logWarn(message){process.stdout.write(` ${color("33","\u26A0")} ${message}
|
|
110
|
+
`)}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(`
|
|
111
|
+
`)||str.includes("\r")?stdout.write(`
|
|
112
|
+
`):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}
|
|
113
|
+
`),{update(t){options?.silent||process.stderr.write(` ${t}
|
|
114
|
+
`)},stop(t){t&&!options?.silent&&process.stderr.write(` ${t}
|
|
115
|
+
`)}};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}
|
|
116
|
+
`),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(`
|
|
117
|
+
\u250C${border}\u2510
|
|
118
|
+
`);for(let row of titleRows)process.stdout.write(` \u2502 \x1B[1m${row}${" ".repeat(maxLen-displayWidth(row))}\x1B[0m \u2502
|
|
119
|
+
`);process.stdout.write(` \u251C${border}\u2524
|
|
120
|
+
`);for(let row of bodyRows)process.stdout.write(` \u2502 ${row}${" ".repeat(maxLen-displayWidth(row))} \u2502
|
|
121
|
+
`);process.stdout.write(` \u2514${border}\u2518
|
|
122
|
+
|
|
123
|
+
`)}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,IDEMPOTENT_METHODS=new Set(["GET","PUT","DELETE"]),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)}
|
|
124
|
+
`),await new Promise(r=>setTimeout(r,retryAfter*1e3)),this.request(opts,isRetry,retryCount+1)}if(response.status===502&&retryCount<1&&IDEMPOTENT_METHODS.has(opts.method))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 getCallerAccountId(){let organizationId=getOrganizationId()?.trim();if(!organizationId)throw new CliError(messages.APP_INSTALL_MISSING_CLIENT_ID);return organizationId}function buildInstallPayload(accountId,name){return{...pick("client_id",toNumericIdentifier(getOrganizationId())),...pick("deploy_client_id",toNumericIdentifier(accountId)),name,is_developer:!0}}function logEmptyAndThrow(){throw logInfo(`
|
|
125
|
+
${messages.APP_LIST_EMPTY}
|
|
126
|
+
`),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(extensionType){let type=String(extensionType??"").trim(),query=type?`?extension_type=${encodeURIComponent(type)}`:"",res=await client2.get(`${ENDPOINTS.APP_STORE_SURFACE_POINT_LOCATIONS}${query}`),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,extensionType){let filter=(locations??[]).map(l=>String(l).trim()).filter(Boolean),type=String(extensionType??"").trim(),params=new URLSearchParams;filter.length&¶ms.set("location",filter.join(",")),type&¶ms.set("extension_type",type);let query=params.size?`?${params.toString()}`:"",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,enabled_extension_types:enabledTypes,...rest}=row,typeList=enabledTypes??row.extension_type_list??legacySupportedTypes;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)),...typeList?{extension_type_list:typeList}:{}})}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 installApp(appId,accountId,name){try{await client2.post(ENDPOINTS.APP_STORE_APP_INSTALLS(appId),buildInstallPayload(accountId,name))}catch(err){rethrowNotFound(err,appId)}},async uninstallApp(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":"ga","review-lifecycle":"preview","ui-app-type":"ga","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",`
|
|
127
|
+
Examples:
|
|
128
|
+
`+def.examples.map(e=>` $ ${e}`).join(`
|
|
129
|
+
`)+`
|
|
130
|
+
`),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 gatedSection(feature,lines){return isFeatureAvailable(feature)?lines:[]}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","",...gatedSection("account-install",["App-install commands (UI apps only):"," brevo app install [account-id] [--app-id <id>] [--force] [--json]"," Install an app into an account"," brevo app uninstall [account-id] [--app-id <id>] [--force] [--json]"," Uninstall an app from an account",""]),"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(`
|
|
131
|
+
`)}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)+`
|
|
132
|
+
`)}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_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:"ga",detectConfig:isUiAppConfigShape,detectRecord:isUiAppRecordShape,recoverableFromRecord:app=>!!app?.ui_app,validateConfig:config=>{validateUiApp(config.ui_app)},wireOnlyKeys:UI_APP_WIRE_ONLY_KEYS};var MATRIX={oauth:{private:["oauth-flow","redirect-uris","scaffold-feature"],public:["oauth-flow","redirect-uris","scaffold-feature","review-lifecycle"]},ui:{private:["account-install"],public:["account-install","review-lifecycle"]}};function capabilitiesFor(type,distribution){return MATRIX[type][distribution]}function supports(type,distribution,capability){return capabilitiesFor(type,distribution).includes(capability)}function assertCapability(type,distribution,capability,message){if(!supports(type,distribution,capability))throw new CliError(message)}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_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,opts){let listSpinner=createSpinner("Fetching apps..."),apps;try{apps=await appService.fetchAppsList()}finally{listSpinner.stop()}if(apps.length===0)throw logInfo(`
|
|
133
|
+
${messages.APP_LIST_EMPTY}
|
|
134
|
+
`),new CliError(messages.APP_LIST_EMPTY);if(opts?.filter&&(apps=apps.filter(opts.filter),apps.length===0))throw new CliError(opts.emptyMessage??messages.APP_LIST_EMPTY);let{selectedApp}=await import_inquirer2.default.prompt([{type:"rawlist",name:"selectedApp",message:promptMessage,choices:apps.map(a=>({name:a.client_id?`${a.name||"App "+a.app_id} (App ID: ${a.app_id}, Client ID: ${a.client_id})`:`${a.name||"App "+a.app_id} (App ID: ${a.app_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_inquirer9=__toESM(require("inquirer"));var import_inquirer7=__toESM(require("inquirer"));var fs5=__toESM(require("node:fs")),path5=__toESM(require("node:path")),import_inquirer6=__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(`
|
|
135
|
+
`),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(`
|
|
136
|
+
`)}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(`
|
|
137
|
+
`)}function printFileTree(filePaths){for(let line of formatFileTree(filePaths).split(`
|
|
138
|
+
`))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(`
|
|
139
|
+
`,`
|
|
140
|
+
`):""}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}}var import_inquirer5=__toESM(require("inquirer"));var NONE="(none)",VALUE_ROWS=[{label:"label: ",read:e=>e.label},{label:"more info: ",read:e=>e.more_info},{label:"redirect link: ",read:e=>e.redirect_link},{label:"modal URL: ",read:e=>e.modal_iframe_url},{label:"card size: ",read:e=>formatSize(e.size)}];function formatSize(size){if(!size)return;let axes=[...size.width?[`width ${size.width}`]:[],...size.height?[`height ${size.height}`]:[]];return axes.length?axes.join(", "):void 0}function formatContext(entry){return entry.context?.length?` (context: ${entry.context.join(", ")})`:""}function formatPlacementLines(uiApp){return(uiApp.surface_point_list??[]).flatMap(entry=>[`${entry.surface_point_name}${formatContext(entry)}`,...VALUE_ROWS.flatMap(({label,read})=>{let value=read(entry);return value?[` ${label}${value}`]:[]})])}function formatPlacementDiffLines(next,current){if(!current)return formatPlacementLines(next);let currentEntries=current.surface_point_list??[],nextEntries=next.surface_point_list??[],before=new Map(currentEntries.map(entry=>[entry.surface_point_name,entry])),nextNames=new Set(nextEntries.map(entry=>entry.surface_point_name));return[...nextEntries.flatMap(entry=>{let previous=before.get(entry.surface_point_name);if(!previous){let[slot,...rest]=formatPlacementLines({surface_point_list:[entry]});return[`${slot} (new)`,...rest]}return[`${entry.surface_point_name}${diffContext(previous,entry)}`,...VALUE_ROWS.flatMap(({label,read})=>{let from=read(previous),to=read(entry);return from===to?to?[` ${label}${to}`]:[]:[` ${label}${from??NONE} \u2192 ${to??NONE}`]})]}),...currentEntries.filter(entry=>!nextNames.has(entry.surface_point_name)).map(entry=>`${entry.surface_point_name} (removed)`)]}function diffContext(previous,entry){let from=previous.context??[],to=entry.context??[];return from.join(",")===to.join(",")?formatContext(entry):` (context: ${from.length?from.join(", "):NONE} \u2192 ${to.length?to.join(", "):NONE})`}var PLACEMENT_QUESTION_PREFIX="placement:";function toUsableRows(rows){let usable=[];for(let row of rows){let segments=row.extension_point_name.split("."),[locationToken,placeToken,kindToken]=segments.length===3?segments:["","",""],location=(row.location_name??"").trim()||locationToken,section=(row.section_name??"").trim()||placeToken,component=(row.component_type??"").trim()||kindToken,slug=(row.surface_point_name??"").trim();!location||!section||!component||!slug||usable.push({...row,location_name:location,section_name:section,component_type:component,surface_point_name:slug})}return usable}function rowSupportsExtensionType(row,extensionType){if(row.status?.trim()&&row.status.trim()!=="active")return!1;let types=row.extension_type_list;return!types||types.length===0?!0:types.includes(extensionType)}async function fetchRecordPageLocations(extensionType){let spinner=createSpinner(messages.APP_CREATE_UI_PAGES_SPINNER),locations;try{locations=await appService.fetchSurfacePointLocations(extensionType)}catch{throw new CliError(messages.APP_CREATE_UI_POINTS_FETCH_FAILED)}finally{spinner.stop()}if(locations.length===0)throw new CliError(messages.APP_CREATE_UI_POINTS_EMPTY);return locations}async function readSurfacePointRows(locations,extensionType){try{return await appService.fetchSurfacePoints(locations,extensionType)}catch{return null}}async function fetchSurfacePointsForPages(locations,extensionType){let onPickedPages=rows=>toUsableRows(rows).filter(row=>locations.includes(row.location_name)),pagesCovered=rows=>new Set(rows.map(row=>row.location_name)).size,spinner=createSpinner(messages.APP_CREATE_UI_POINTS_SPINNER),usable;try{let narrowed=await readSurfacePointRows(locations,extensionType);if(usable=onPickedPages(narrowed??[]),narrowed===null||pagesCovered(usable)<locations.length){let unfiltered=await readSurfacePointRows();if(unfiltered===null&&narrowed===null)throw new CliError(messages.APP_CREATE_UI_POINTS_FETCH_FAILED);let fallback=onPickedPages(unfiltered??[]);pagesCovered(fallback)>pagesCovered(usable)&&(usable=fallback)}}finally{spinner.stop()}let hostable=usable.filter(row=>rowSupportsExtensionType(row,extensionType));if(hostable.length===0)throw new CliError(usable.length>0?messages.APP_CREATE_UI_POINTS_NONE_FOR_TYPE(extensionType):messages.APP_CREATE_UI_POINTS_EMPTY);return hostable}function placementLabel(row){return`${row.section_name} \u2014 ${row.component_type}`}async function promptSurfacePoint(locations,extensionType){let{surface}=await import_inquirer5.default.prompt([{type:"list",name:"surface",message:messages.APP_CREATE_UI_SURFACE_PROMPT,choices:indentChoices(locations.map(location=>({name:location,value:location})))}]),page=locations.find(location=>location===String(surface??"").trim()),forPage=(await fetchSurfacePointsForPages(page?[page]:[],extensionType)).filter(row=>row.location_name===page),question=`${PLACEMENT_QUESTION_PREFIX}${page}`,answer=await import_inquirer5.default.prompt([{type:"list",name:question,message:messages.APP_CREATE_UI_PLACEMENT_PAGE_PROMPT(page??""),choices:indentChoices(forPage.map(row=>({name:placementLabel(row),value:row.surface_point_name})))}]),chosen=String(answer[question]??"").trim();return forPage.filter(row=>row.surface_point_name===chosen)}async function promptIntegrationType(){let{integrationType}=await import_inquirer5.default.prompt([{type:"list",name:"integrationType",message:messages.APP_CREATE_UI_INTEGRATION_PROMPT,choices:indentChoices([{name:messages.APP_CREATE_UI_INTEGRATION_EXTERNAL_LINK,value:EXTENSION_TYPE_ACTION_LINK}])}]);return integrationType}async function resolveUiApp(){let extensionType=await promptIntegrationType(),locations=await fetchRecordPageLocations(extensionType),selectedRows=await promptSurfacePoint(locations,extensionType),{label}=await import_inquirer5.default.prompt([{type:"input",name:"label",message:messages.APP_CREATE_UI_LABEL_PROMPT,validate:validateUiAppLabel}]),{more_info}=await import_inquirer5.default.prompt([{type:"input",name:"more_info",message:messages.APP_CREATE_UI_MORE_INFO_PROMPT,validate:validateUiAppMoreInfo}]),{url}=await import_inquirer5.default.prompt([{type:"input",name:"url",message:messages.APP_CREATE_UI_REDIRECT_LINK_PROMPT,validate:validateUiAppUrl}]),uiApp={extension_type:extensionType,surface_point_list:buildSurfacePointList(selectedRows,{contextFor:row=>row.default_context_field??[],label:String(label??"").trim(),more_info:String(more_info??"").trim(),redirect_link:String(url??"").trim()})};return validateUiApp(uiApp),uiApp}function buildSurfacePointList(rows,fields){let entries=[],seen=new Set;for(let row of rows){if(seen.has(row.surface_point_name))continue;seen.add(row.surface_point_name);let context=fields.contextFor(row).map(field=>String(field).trim()).filter(Boolean);entries.push({surface_point_name:row.surface_point_name,...context.length?{context}:{},label:fields.label,...fields.more_info?{more_info:fields.more_info}:{},redirect_link:fields.redirect_link})}return entries}function buildExampleContextUrl(redirectLink,context){let url;try{url=new URL(redirectLink)}catch{return null}for(let field of context)url.searchParams.set(field,field.replaceAll(/([a-z0-9])([A-Z])/g,"$1_$2").toUpperCase());return url.toString()}function renderExampleContextUrlLines(uiApp){let withContext=uiApp.surface_point_list.find(entry=>entry.context?.length&&entry.redirect_link);if(!withContext)return[];let example=buildExampleContextUrl(withContext.redirect_link,withContext.context??[]);return example?["",`${messages.APP_CREATE_UI_BOX_EXAMPLE_URL_LABEL}`,` ${example}`,messages.APP_CREATE_UI_BOX_EXAMPLE_URL_NOTE]:[]}function renderCreatedUiApp(result,appName,uiApp,logoUri){let boxLines=[`App name: ${appName}`,`App ID: ${result.app_id}`,`Extension type: ${uiApp.extension_type}`,...formatPlacementLines(uiApp).map((line,i)=>`${i===0?"Placement: ":" "}${line}`),...logoUri?[`Logo URL: ${logoUri}`]:[],...result.version?[`App version: ${result.version}`]:[],...renderExampleContextUrlLines(uiApp),"",messages.APP_CREATE_UI_BOX_LABEL_NOTE(uiApp.surface_point_list[0]?.label??"",appName),messages.APP_CREATE_UI_BOX_HINT];printBox(messages.APP_CREATE_UI_BOX_TITLE,boxLines)}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_inquirer6.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 isFeatureAvailable("ui-app-type")&&choices.push({name:messages.APP_CREATE_APP_TYPE_UI,value:"ui"}),(await import_inquirer6.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_inquirer6.default.prompt([{type:"list",name:"distribution",message:messages.APP_CREATE_TYPE_PROMPT,choices:indentChoices(choices)}])).distribution}async function promptAddAnotherRedirect(){let{anotherRaw}=await import_inquirer6.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_inquirer6.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_inquirer6.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_inquirer6.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_inquirer6.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_inquirer6.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;appType==="ui"?uiApp=await resolveUiApp():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=()=>uiApp?renderCreatedUiApp(result,finalAppName,uiApp,logoUri):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_inquirer7.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_inquirer7.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(`
|
|
141
|
+
${messages.AUTH_NEXT}
|
|
142
|
+
`);return}process.stdout.write(`
|
|
143
|
+
`);let{shouldCreate}=await import_inquirer7.default.prompt([{type:"confirm",name:"shouldCreate",message:messages.AUTH_CREATE_APP_PROMPT,default:!0}]);if(shouldCreate){process.stdout.write(`
|
|
144
|
+
`),await createCommand({});return}logInfo(`
|
|
145
|
+
${messages.AUTH_NEXT}
|
|
146
|
+
`)}var loginCommand=withCommandHandler(async options=>{let quiet=!!options.json;quiet||(process.stdout.write(`
|
|
147
|
+
${messages.AUTH_WELCOME}
|
|
148
|
+
`),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
|
|
149
|
+
`));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_inquirer8=__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_inquirer8.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_inquirer8.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_inquirer9.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(`
|
|
150
|
+
${messages.INIT_WELCOME}
|
|
151
|
+
`),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
|
|
152
|
+
|
|
153
|
+
`),await ensureLoggedIn(),process.stdout.write(`
|
|
154
|
+
`);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(`
|
|
155
|
+
${initDoneMessage()}
|
|
156
|
+
`);return}if(action==="scaffold"){process.stdout.write(`
|
|
157
|
+
`),await scaffoldCommand({}),logInfo(`
|
|
158
|
+
${initDoneMessage()}
|
|
159
|
+
`);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(`
|
|
160
|
+
`),await createCommand({}),logInfo(`
|
|
161
|
+
${initDoneMessage()}
|
|
162
|
+
`)});var import_inquirer10=__toESM(require("inquirer"));var logoutCommand=withCommandHandler(async options=>{if(!isAuthenticated()){options.json?jsonOutput({loggedOut:!1,reason:"not_authenticated"}):logInfo(`
|
|
163
|
+
${messages.AUTH_NOT_LOGGED_IN}
|
|
164
|
+
`);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_inquirer10.default.prompt([{type:"confirm",name:"confirmed",message:messages.AUTH_LOGOUT_CONFIRM,default:!1}]);if(!confirmed){logInfo(`
|
|
165
|
+
${messages.ABORTED}
|
|
166
|
+
`);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(`
|
|
167
|
+
${messages.WHOAMI_AUTHENTICATED(account.email,account.companyName||"N/A")}${kindLabel}
|
|
168
|
+
Organization: ${account.organization_id}
|
|
169
|
+
User ID: ${account.user_id}
|
|
170
|
+
`)}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(`
|
|
171
|
+
${messages.APP_LIST_EMPTY}
|
|
172
|
+
`);return}logInfo(`
|
|
173
|
+
${messages.APP_LIST_HEADER}
|
|
174
|
+
`);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})
|
|
175
|
+
`),process.stdout.write(` Type: ${appType.label}
|
|
176
|
+
`),isUiApp||printOAuthIdentity(app),process.stdout.write(` Logo URL: ${app.logo_uri||"(none)"}
|
|
177
|
+
`),process.stdout.write(` Version: ${app.version||"(none)"}
|
|
178
|
+
`),isUiApp||printScopes(app),app.ui_app&&printUiApp(app.ui_app),process.stdout.write(`
|
|
179
|
+
`)}function printOAuthIdentity(app){process.stdout.write(` Client ID: ${app.client_id}
|
|
180
|
+
`);let redirectUris=app.redirect_uris??[];if(redirectUris.length===0){process.stdout.write(` Redirect URLs: (none)
|
|
181
|
+
`);return}redirectUris.forEach((uri,i)=>{process.stdout.write(` Redirect URL ${i+1}: ${uri}
|
|
182
|
+
`)})}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}
|
|
183
|
+
`)}function printUiApp(uiApp){process.stdout.write(` Extension: ${uiApp.extension_type}
|
|
184
|
+
`),formatPlacementLines(uiApp).forEach((line,i)=>{process.stdout.write(` ${i===0?"Placement: ":" "}${line}
|
|
185
|
+
`)})}var import_inquirer11=__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_inquirer11.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(`
|
|
186
|
+
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(`
|
|
187
|
+
`)}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_inquirer11.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.
|
|
188
|
+
`))}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,distribution=app.distribution_type==="public"?"public":"private";assertCapability(resolveFromRecord(app).id,distribution,"oauth-flow",messages.APP_CREDENTIALS_UI_APP(appId)),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)}
|
|
189
|
+
`)});var fs6=__toESM(require("node:fs")),path6=__toESM(require("node:path")),import_inquirer12=__toESM(require("inquirer"));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 canonicalizeUiApp(uiApp){if(!uiApp)return"";let normalized=sortKeysDeep(stripUiAppWireOnlyKeysFrom(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 uiAppEquals(a,b){return canonicalizeUiApp(a)===canonicalizeUiApp(b)}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 withoutInjectedKeys(uiApp){return stripInjectedKeys(uiApp)}function withInjectedLinkTargets(uiApp){return uiApp.extension_type!==EXTENSION_TYPE_ACTION_LINK||!Array.isArray(uiApp.surface_point_list)?uiApp:{...uiApp,surface_point_list:uiApp.surface_point_list.map(entry=>({...entry,link_target:entry.link_target??DEFAULT_LINK_TARGET}))}}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)":""}`);let typePrefix=current&¤t.extension_type!==next.extension_type?`${current.extension_type} \u2192 `:"";logInfo(` Extension type: ${typePrefix}${next.extension_type}`),formatPlacementDiffLines(next,current).forEach((line,i)=>{logInfo(` ${i===0?"Placement: ":" "}${line}`)})}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:withInjectedLinkTargets(config.ui_app)}:{}})}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(installed){if(!process.stdin.isTTY)throw new CliError(NON_INTERACTIVE_CONFIRM_ERROR);let{confirmed}=await import_inquirer12.default.prompt([{type:"confirm",name:"confirmed",message:installed?messages.APP_UPLOAD_CONFIRM_INSTALLED: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}let affectsInstalls=!!diff.nextUiApp;if(!options.json&&affectsInstalls&&logWarn(` ${messages.APP_UPLOAD_INSTALLED_IMPACT}
|
|
190
|
+
`),!options.json&&!options.yes&&!await confirmUpload(affectsInstalls)){logInfo(`
|
|
191
|
+
${messages.APP_UPLOAD_CANCELLED}
|
|
192
|
+
`);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(`
|
|
193
|
+
`)});var fs7=__toESM(require("node:fs")),path7=__toESM(require("node:path")),os2=__toESM(require("node:os")),import_inquirer13=__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){logWarn(`
|
|
194
|
+
${messages.APP_DELETE_WARNING(appLabel,appId)}
|
|
195
|
+
`);let{confirmed}=await import_inquirer13.default.prompt([{type:"confirm",name:"confirmed",message:messages.APP_DELETE_CONFIRM(appLabel,appId),default:!1}]);return confirmed?!0:(logInfo(`
|
|
196
|
+
${messages.APP_DELETE_CANCELLED}
|
|
197
|
+
`),!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_inquirer13.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)options.json||logWarn(`
|
|
198
|
+
${messages.APP_DELETE_WARNING(appLabel||appId,appId)}
|
|
199
|
+
`);else if(!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("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function safeJson(value){return JSON.stringify(value).replaceAll("<",String.raw`\u003c`)}var STYLES=`
|
|
200
|
+
:root {
|
|
201
|
+
color-scheme: light dark;
|
|
202
|
+
--bg: #ffffff;
|
|
203
|
+
--hero-bg: #e6f6ec;
|
|
204
|
+
--fg: #1b1b1b;
|
|
205
|
+
--muted: #696969;
|
|
206
|
+
--border: #e3e3e3;
|
|
207
|
+
--accent: #0b996e;
|
|
208
|
+
--card: #ffffff;
|
|
209
|
+
--card-shadow: 0 1px 2px rgba(28, 28, 28, .08);
|
|
210
|
+
--chip-bg: #e1f3e8;
|
|
211
|
+
--chip-fg: #00734a;
|
|
212
|
+
--section-header-bg: #f6fbf8;
|
|
213
|
+
--error: #cf1a3b;
|
|
214
|
+
--radius-card: 16px;
|
|
215
|
+
--radius-input: 16px;
|
|
216
|
+
--radius-chip: 20px;
|
|
217
|
+
}
|
|
218
|
+
@media (prefers-color-scheme: dark) {
|
|
219
|
+
:root {
|
|
220
|
+
--bg: #0e1613;
|
|
221
|
+
--hero-bg: #142119;
|
|
222
|
+
--fg: #f1f5f2;
|
|
223
|
+
--muted: #94a89c;
|
|
224
|
+
--border: #25382d;
|
|
225
|
+
--accent: #3ddc97;
|
|
226
|
+
--card: #16241c;
|
|
227
|
+
--card-shadow: 0 1px 2px rgba(0, 0, 0, .4);
|
|
228
|
+
--chip-bg: #1d3327;
|
|
229
|
+
--chip-fg: #a7e3c5;
|
|
230
|
+
--section-header-bg: #121f18;
|
|
231
|
+
--error: #ff8d9a;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
* { box-sizing: border-box; }
|
|
235
|
+
body {
|
|
236
|
+
margin: 0;
|
|
237
|
+
padding: 0;
|
|
238
|
+
font: 15px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
239
|
+
background: var(--bg);
|
|
240
|
+
color: var(--fg);
|
|
241
|
+
-webkit-font-smoothing: antialiased;
|
|
242
|
+
-moz-osx-font-smoothing: grayscale;
|
|
243
|
+
}
|
|
244
|
+
.hero {
|
|
245
|
+
background: var(--hero-bg);
|
|
246
|
+
padding: 2.5rem 1.5rem 2rem;
|
|
247
|
+
}
|
|
248
|
+
.hero-inner {
|
|
249
|
+
max-width: 880px;
|
|
250
|
+
margin: 0 auto;
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
justify-content: space-between;
|
|
254
|
+
gap: 1.5rem;
|
|
255
|
+
flex-wrap: wrap;
|
|
256
|
+
}
|
|
257
|
+
.hero-copy { flex: 1 1 24rem; min-width: 0; }
|
|
258
|
+
h1 {
|
|
259
|
+
font-size: 2rem;
|
|
260
|
+
line-height: 2.5rem;
|
|
261
|
+
font-weight: 600;
|
|
262
|
+
margin: 0 0 .5rem;
|
|
263
|
+
}
|
|
264
|
+
.intro { color: var(--muted); margin: 0; word-break: break-word; }
|
|
265
|
+
a.docs-cta {
|
|
266
|
+
display: inline-block;
|
|
267
|
+
flex: none;
|
|
268
|
+
white-space: nowrap;
|
|
269
|
+
padding: .5rem 1.1rem;
|
|
270
|
+
font-weight: 600;
|
|
271
|
+
font-size: .9rem;
|
|
272
|
+
color: #ffffff;
|
|
273
|
+
background: var(--accent);
|
|
274
|
+
border-radius: var(--radius-input);
|
|
275
|
+
text-decoration: none;
|
|
276
|
+
box-shadow: var(--card-shadow);
|
|
277
|
+
}
|
|
278
|
+
a.docs-cta:hover { opacity: .9; }
|
|
279
|
+
@media (prefers-color-scheme: dark) {
|
|
280
|
+
a.docs-cta { color: #0e1613; }
|
|
281
|
+
}
|
|
282
|
+
main {
|
|
283
|
+
max-width: 880px;
|
|
284
|
+
margin: 0 auto;
|
|
285
|
+
padding: 1.5rem 1.5rem 4rem;
|
|
286
|
+
}
|
|
287
|
+
.toolbar {
|
|
288
|
+
display: flex;
|
|
289
|
+
gap: .75rem;
|
|
290
|
+
align-items: center;
|
|
291
|
+
margin-bottom: 1.5rem;
|
|
292
|
+
}
|
|
293
|
+
input[type="search"] {
|
|
294
|
+
flex: 1;
|
|
295
|
+
padding: .55rem .9rem;
|
|
296
|
+
font: inherit;
|
|
297
|
+
font-size: 1rem;
|
|
298
|
+
border: 1px solid var(--border);
|
|
299
|
+
border-radius: var(--radius-input);
|
|
300
|
+
background: var(--card);
|
|
301
|
+
color: var(--fg);
|
|
302
|
+
}
|
|
303
|
+
input[type="search"]:focus {
|
|
304
|
+
outline: none;
|
|
305
|
+
border-color: var(--accent);
|
|
306
|
+
box-shadow: inset 0 0 0 1px var(--accent);
|
|
307
|
+
}
|
|
308
|
+
button.refresh {
|
|
309
|
+
padding: .5rem 1rem;
|
|
310
|
+
font: inherit;
|
|
311
|
+
font-weight: 600;
|
|
312
|
+
font-size: .9rem;
|
|
313
|
+
border: 1px solid var(--border);
|
|
314
|
+
border-radius: var(--radius-input);
|
|
315
|
+
background: var(--card);
|
|
316
|
+
color: var(--fg);
|
|
317
|
+
cursor: pointer;
|
|
318
|
+
box-shadow: var(--card-shadow);
|
|
319
|
+
}
|
|
320
|
+
button.refresh:hover { background: var(--chip-bg); border-color: var(--chip-bg); }
|
|
321
|
+
button.refresh:disabled { opacity: .55; cursor: progress; }
|
|
322
|
+
.refresh-error {
|
|
323
|
+
margin: -.75rem 0 1rem;
|
|
324
|
+
color: var(--error);
|
|
325
|
+
font-size: .85rem;
|
|
326
|
+
}
|
|
327
|
+
section {
|
|
328
|
+
border: 1px solid var(--border);
|
|
329
|
+
border-radius: var(--radius-card);
|
|
330
|
+
margin-bottom: 1rem;
|
|
331
|
+
background: var(--card);
|
|
332
|
+
box-shadow: var(--card-shadow);
|
|
333
|
+
overflow: hidden;
|
|
334
|
+
}
|
|
335
|
+
section[hidden] { display: none; }
|
|
336
|
+
.section-head {
|
|
337
|
+
display: flex;
|
|
338
|
+
align-items: center;
|
|
339
|
+
justify-content: space-between;
|
|
340
|
+
gap: .75rem;
|
|
341
|
+
padding: .55rem 1rem;
|
|
342
|
+
background: var(--section-header-bg);
|
|
343
|
+
border-bottom: 1px solid var(--border);
|
|
344
|
+
}
|
|
345
|
+
.section-head h2 {
|
|
346
|
+
font-size: .75rem;
|
|
347
|
+
font-weight: 600;
|
|
348
|
+
text-transform: uppercase;
|
|
349
|
+
letter-spacing: .06em;
|
|
350
|
+
color: var(--muted);
|
|
351
|
+
margin: 0;
|
|
352
|
+
}
|
|
353
|
+
button.copy {
|
|
354
|
+
font: inherit;
|
|
355
|
+
font-size: .75rem;
|
|
356
|
+
font-weight: 600;
|
|
357
|
+
padding: .2rem .7rem;
|
|
358
|
+
border: 1px solid var(--border);
|
|
359
|
+
border-radius: var(--radius-chip);
|
|
360
|
+
background: var(--card);
|
|
361
|
+
color: var(--accent);
|
|
362
|
+
cursor: pointer;
|
|
363
|
+
white-space: nowrap;
|
|
364
|
+
}
|
|
365
|
+
button.copy:hover { background: var(--chip-bg); border-color: var(--chip-bg); }
|
|
366
|
+
button.copy:disabled { opacity: .55; cursor: default; }
|
|
367
|
+
.selection-bar {
|
|
368
|
+
display: flex;
|
|
369
|
+
gap: .75rem;
|
|
370
|
+
align-items: center;
|
|
371
|
+
margin-bottom: 1.5rem;
|
|
372
|
+
}
|
|
373
|
+
.selection-bar input[type="text"] {
|
|
374
|
+
flex: 1;
|
|
375
|
+
padding: .55rem .9rem;
|
|
376
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
|
|
377
|
+
font-size: .85rem;
|
|
378
|
+
border: 1px solid var(--border);
|
|
379
|
+
border-radius: var(--radius-input);
|
|
380
|
+
background: var(--card);
|
|
381
|
+
color: var(--fg);
|
|
382
|
+
}
|
|
383
|
+
.selection-bar button.copy { padding: .5rem 1rem; font-size: .9rem; }
|
|
384
|
+
ul { list-style: none; margin: 0; padding: .5rem 0; }
|
|
385
|
+
li { padding: .4rem 1rem; }
|
|
386
|
+
li[hidden] { display: none; }
|
|
387
|
+
.scope-row { display: flex; align-items: flex-start; gap: .55rem; }
|
|
388
|
+
.scope-row input[type="checkbox"] {
|
|
389
|
+
accent-color: var(--accent);
|
|
390
|
+
margin-top: .25rem;
|
|
391
|
+
flex: none;
|
|
392
|
+
cursor: pointer;
|
|
393
|
+
}
|
|
394
|
+
.scope-row details { flex: 1; min-width: 0; }
|
|
395
|
+
.badge {
|
|
396
|
+
font-size: .68rem;
|
|
397
|
+
font-weight: 600;
|
|
398
|
+
text-transform: uppercase;
|
|
399
|
+
letter-spacing: .04em;
|
|
400
|
+
color: var(--error);
|
|
401
|
+
border: 1px solid currentColor;
|
|
402
|
+
border-radius: var(--radius-chip);
|
|
403
|
+
padding: 0 .45rem;
|
|
404
|
+
margin-left: .5rem;
|
|
405
|
+
vertical-align: middle;
|
|
406
|
+
}
|
|
407
|
+
details > summary {
|
|
408
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
|
|
409
|
+
font-size: .9rem;
|
|
410
|
+
cursor: pointer;
|
|
411
|
+
list-style: revert;
|
|
412
|
+
}
|
|
413
|
+
details[open] > summary { margin-bottom: .35rem; }
|
|
414
|
+
.endpoints {
|
|
415
|
+
margin: .25rem 0 .25rem 1.1rem;
|
|
416
|
+
padding: 0;
|
|
417
|
+
display: flex;
|
|
418
|
+
flex-wrap: wrap;
|
|
419
|
+
gap: .35rem;
|
|
420
|
+
}
|
|
421
|
+
.endpoints .ep {
|
|
422
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
|
|
423
|
+
font-size: .78rem;
|
|
424
|
+
background: var(--chip-bg);
|
|
425
|
+
color: var(--chip-fg);
|
|
426
|
+
padding: .15rem .55rem;
|
|
427
|
+
border-radius: var(--radius-chip);
|
|
428
|
+
}
|
|
429
|
+
.endpoints .none {
|
|
430
|
+
font-size: .8rem;
|
|
431
|
+
color: var(--muted);
|
|
432
|
+
font-style: italic;
|
|
433
|
+
}
|
|
434
|
+
.empty { color: var(--muted); padding: 2rem 0; text-align: center; }
|
|
435
|
+
footer {
|
|
436
|
+
margin-top: 3rem;
|
|
437
|
+
padding-top: 1.5rem;
|
|
438
|
+
border-top: 1px solid var(--border);
|
|
439
|
+
font-size: .8rem;
|
|
440
|
+
color: var(--muted);
|
|
441
|
+
}
|
|
442
|
+
footer a { color: var(--accent); }
|
|
443
|
+
`.trim(),SCRIPT=`
|
|
444
|
+
(function () {
|
|
445
|
+
var dataNode = document.getElementById('scopes-data');
|
|
446
|
+
var state = dataNode ? JSON.parse(dataNode.textContent || '[]') : [];
|
|
447
|
+
|
|
448
|
+
var listEl = document.getElementById('scopes-list');
|
|
449
|
+
var searchEl = document.querySelector('input[type="search"]');
|
|
450
|
+
var refreshBtn = document.getElementById('refresh-btn');
|
|
451
|
+
var errorEl = document.getElementById('refresh-error');
|
|
452
|
+
var introCountEl = document.getElementById('scopes-count');
|
|
453
|
+
var selectedEl = document.getElementById('selected-scopes');
|
|
454
|
+
var copySelectedBtn = document.getElementById('copy-selected');
|
|
455
|
+
var labels = JSON.parse(document.getElementById('labels').textContent);
|
|
456
|
+
|
|
457
|
+
// Selected scope names, in click order. Rendered as '"a","b"' \u2014 pastes
|
|
458
|
+
// straight into app-config.json's auth.scopes array.
|
|
459
|
+
var selected = [];
|
|
460
|
+
|
|
461
|
+
function quoted(names) {
|
|
462
|
+
return names.map(function (n) { return '"' + n + '"'; }).join(',');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function escapeHtml(s) {
|
|
466
|
+
return String(s).replace(/[&<>"']/g, function (c) {
|
|
467
|
+
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function render(entries) {
|
|
472
|
+
if (!entries.length) {
|
|
473
|
+
listEl.innerHTML = '<p class="empty">' + escapeHtml(labels.empty) + '</p>';
|
|
474
|
+
return;
|
|
130
475
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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;
|
|
476
|
+
var byCategory = new Map();
|
|
477
|
+
entries.forEach(function (e) {
|
|
478
|
+
var list = byCategory.get(e.category);
|
|
479
|
+
if (list) list.push(e);
|
|
480
|
+
else byCategory.set(e.category, [e]);
|
|
481
|
+
});
|
|
482
|
+
var html = '';
|
|
483
|
+
byCategory.forEach(function (items, cat) {
|
|
484
|
+
var copyAria = labels.copyCategoryAria.replace('{category}', cat);
|
|
485
|
+
html += '<section><div class="section-head"><h2>' + escapeHtml(cat) + '</h2>';
|
|
486
|
+
html +=
|
|
487
|
+
'<button type="button" class="copy copy-category" data-category="' + escapeHtml(cat) +
|
|
488
|
+
'" aria-label="' + escapeHtml(copyAria) + '">' + escapeHtml(labels.copy) + '</button></div><ul>';
|
|
489
|
+
items.forEach(function (e) {
|
|
490
|
+
var endpoints = Array.isArray(e.apiEndpoints) ? e.apiEndpoints : [];
|
|
491
|
+
var isLegacy = e.name === labels.legacyScope;
|
|
492
|
+
html += '<li><div class="scope-row">';
|
|
493
|
+
if (isLegacy) {
|
|
494
|
+
// The legacy catch-all scope is deprecated: badge it, no checkbox,
|
|
495
|
+
// and copy CTAs skip it so pasted lists never reintroduce it.
|
|
496
|
+
html += '<details><summary>' + escapeHtml(e.name) +
|
|
497
|
+
'<span class="badge" title="' + escapeHtml(labels.legacyTitle) + '">' +
|
|
498
|
+
escapeHtml(labels.legacyBadge) + '</span></summary>';
|
|
499
|
+
} else {
|
|
500
|
+
var selectAria = labels.selectScopeAria.replace('{scope}', e.name);
|
|
501
|
+
html += '<input type="checkbox" class="scope-check" data-scope="' + escapeHtml(e.name) +
|
|
502
|
+
'" aria-label="' + escapeHtml(selectAria) + '"' +
|
|
503
|
+
(selected.indexOf(e.name) !== -1 ? ' checked' : '') + '>';
|
|
504
|
+
html += '<details><summary>' + escapeHtml(e.name) + '</summary>';
|
|
167
505
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
506
|
+
if (endpoints.length) {
|
|
507
|
+
html += '<div class="endpoints" aria-label="' + escapeHtml(labels.endpointsLabel) + '">';
|
|
508
|
+
endpoints.forEach(function (ep) {
|
|
509
|
+
html += '<span class="ep">' + escapeHtml(ep) + '</span>';
|
|
510
|
+
});
|
|
511
|
+
html += '</div>';
|
|
512
|
+
} else {
|
|
513
|
+
html += '<div class="endpoints"><span class="none">' + escapeHtml(labels.noEndpoints) + '</span></div>';
|
|
174
514
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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,
|
|
515
|
+
html += '</details></div></li>';
|
|
516
|
+
});
|
|
517
|
+
html += '</ul></section>';
|
|
187
518
|
});
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
519
|
+
listEl.innerHTML = html;
|
|
520
|
+
applyFilter();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function copyText(text, btn) {
|
|
524
|
+
if (!navigator.clipboard || !navigator.clipboard.writeText) return;
|
|
525
|
+
navigator.clipboard.writeText(text).then(function () {
|
|
526
|
+
var original = btn.textContent;
|
|
527
|
+
btn.textContent = labels.copied;
|
|
528
|
+
setTimeout(function () {
|
|
529
|
+
btn.textContent = original;
|
|
530
|
+
}, 1200);
|
|
531
|
+
}).catch(function () { /* clipboard denied \u2014 leave the button as-is */ });
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function updateSelection() {
|
|
535
|
+
selectedEl.value = quoted(selected);
|
|
536
|
+
copySelectedBtn.disabled = selected.length === 0;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function applyFilter() {
|
|
540
|
+
var q = searchEl ? searchEl.value.toLowerCase().trim() : '';
|
|
541
|
+
var sections = listEl.querySelectorAll('section');
|
|
542
|
+
sections.forEach(function (section) {
|
|
543
|
+
var items = section.querySelectorAll('li');
|
|
544
|
+
var anyVisible = false;
|
|
545
|
+
items.forEach(function (li) {
|
|
546
|
+
var text = li.textContent.toLowerCase();
|
|
547
|
+
var match = q === '' || text.indexOf(q) !== -1;
|
|
548
|
+
li.hidden = !match;
|
|
549
|
+
if (match) anyVisible = true;
|
|
550
|
+
});
|
|
551
|
+
section.hidden = !anyVisible;
|
|
195
552
|
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function setCount(n) {
|
|
556
|
+
if (introCountEl) introCountEl.textContent = String(n);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
render(state);
|
|
560
|
+
setCount(state.length);
|
|
561
|
+
updateSelection();
|
|
562
|
+
|
|
563
|
+
if (searchEl) {
|
|
564
|
+
searchEl.addEventListener('input', applyFilter);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Delegated handlers survive re-renders (refresh replaces listEl's HTML).
|
|
568
|
+
listEl.addEventListener('click', function (ev) {
|
|
569
|
+
var btn = ev.target.closest ? ev.target.closest('button.copy-category') : null;
|
|
570
|
+
if (!btn) return;
|
|
571
|
+
var cat = btn.getAttribute('data-category');
|
|
572
|
+
var names = state
|
|
573
|
+
.filter(function (e) { return e.category === cat && e.name !== labels.legacyScope; })
|
|
574
|
+
.map(function (e) { return e.name; });
|
|
575
|
+
copyText(quoted(names), btn);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
listEl.addEventListener('change', function (ev) {
|
|
579
|
+
var box = ev.target.closest ? ev.target.closest('input.scope-check') : null;
|
|
580
|
+
if (!box) return;
|
|
581
|
+
var name = box.getAttribute('data-scope');
|
|
582
|
+
var idx = selected.indexOf(name);
|
|
583
|
+
if (box.checked && idx === -1) selected.push(name);
|
|
584
|
+
if (!box.checked && idx !== -1) selected.splice(idx, 1);
|
|
585
|
+
updateSelection();
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
copySelectedBtn.addEventListener('click', function () {
|
|
589
|
+
if (selected.length) copyText(quoted(selected), copySelectedBtn);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
if (refreshBtn) {
|
|
593
|
+
var defaultLabel = refreshBtn.textContent;
|
|
594
|
+
refreshBtn.addEventListener('click', function () {
|
|
595
|
+
refreshBtn.disabled = true;
|
|
596
|
+
refreshBtn.textContent = labels.refreshing;
|
|
597
|
+
if (errorEl) errorEl.hidden = true;
|
|
598
|
+
fetch('/scopes.json', { headers: { Accept: 'application/json' } })
|
|
599
|
+
.then(function (res) {
|
|
600
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
601
|
+
return res.json();
|
|
602
|
+
})
|
|
603
|
+
.then(function (body) {
|
|
604
|
+
var entries = Array.isArray(body && body.scopes) ? body.scopes : [];
|
|
605
|
+
state = entries;
|
|
606
|
+
// Drop selections for scopes the IdP no longer returns.
|
|
607
|
+
selected = selected.filter(function (name) {
|
|
608
|
+
return state.some(function (e) { return e.name === name; });
|
|
609
|
+
});
|
|
610
|
+
render(state);
|
|
611
|
+
setCount(state.length);
|
|
612
|
+
updateSelection();
|
|
613
|
+
})
|
|
614
|
+
.catch(function () {
|
|
615
|
+
if (errorEl) errorEl.hidden = false;
|
|
616
|
+
})
|
|
617
|
+
.then(function () {
|
|
618
|
+
refreshBtn.disabled = false;
|
|
619
|
+
refreshBtn.textContent = defaultLabel;
|
|
620
|
+
});
|
|
214
621
|
});
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
622
|
+
}
|
|
623
|
+
})();
|
|
624
|
+
`.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>
|
|
625
|
+
<html lang="en">
|
|
626
|
+
<head>
|
|
627
|
+
<meta charset="utf-8">
|
|
628
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
629
|
+
<title>${title}</title>
|
|
630
|
+
<style>${STYLES}</style>
|
|
631
|
+
</head>
|
|
632
|
+
<body>
|
|
633
|
+
<header class="hero">
|
|
634
|
+
<div class="hero-inner">
|
|
635
|
+
<div class="hero-copy">
|
|
636
|
+
<h1>${title}</h1>
|
|
637
|
+
<p class="intro">${introWithSpan}</p>
|
|
638
|
+
</div>
|
|
639
|
+
<a class="docs-cta" href="${BREVO_OAUTH_SCOPES_DOCS_URL}" target="_blank" rel="noopener noreferrer">${catalogDocsCta} \u2197</a>
|
|
640
|
+
</div>
|
|
641
|
+
</header>
|
|
642
|
+
<main>
|
|
643
|
+
<div class="toolbar">
|
|
644
|
+
<input type="search" placeholder="${searchPlaceholder}" autofocus>
|
|
645
|
+
<button id="refresh-btn" class="refresh" type="button">${refreshLabel}</button>
|
|
646
|
+
</div>
|
|
647
|
+
<p id="refresh-error" class="refresh-error" hidden>${refreshError}</p>
|
|
648
|
+
<div class="selection-bar">
|
|
649
|
+
<input id="selected-scopes" type="text" readonly placeholder="${selectedPlaceholder}" aria-label="${selectedPlaceholder}">
|
|
650
|
+
<button id="copy-selected" class="copy" type="button" disabled>${copySelected}</button>
|
|
651
|
+
</div>
|
|
652
|
+
<div id="scopes-list"></div>
|
|
653
|
+
<footer>${footer} <a href="${BREVO_CLI_REFERENCE_URL}">${docsLink}</a></footer>
|
|
654
|
+
</main>
|
|
655
|
+
<script type="application/json" id="scopes-data">${safeJson(entries)}</script>
|
|
656
|
+
<script type="application/json" id="labels">${safeJson(labels)}</script>
|
|
657
|
+
<script>${SCRIPT}</script>
|
|
658
|
+
</body>
|
|
659
|
+
</html>
|
|
660
|
+
`}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_inquirer14=__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_inquirer14.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(`
|
|
661
|
+
`);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(`
|
|
662
|
+
${messages.APP_START_STOPPED}
|
|
663
|
+
`),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(`
|
|
664
|
+
Starting ${feature}...`);let childEnv={...process.env,PORT:String(port)};redirectUri&&(childEnv.REDIRECT_URI=redirectUri),await runChildProcess(entryFile,childEnv,feature)});var import_inquirer15=__toESM(require("inquirer"));var CORPORATE_ACCOUNT_TYPE="corporate";async function promptSubAccountSelection(accountSelectPrompt){let spinner=createSpinner("Fetching sub-accounts..."),subAccounts;try{subAccounts=await accountService.fetchSubAccounts()}finally{spinner.stop()}let selectable=subAccounts.filter(sub=>sub.active!==!1&&Number.isInteger(sub.id)&&sub.id>0);if(selectable.length===0)throw new CliError(messages.APP_INSTALL_NO_SUB_ACCOUNTS);let{selectedSubAccount}=await import_inquirer15.default.prompt([{type:"rawlist",name:"selectedSubAccount",message:accountSelectPrompt,choices:selectable.map(sub=>({name:`${sub.companyName||"Account "+sub.id} (Account ID: ${sub.id})`,value:sub.id}))}]),accountId=String(selectedSubAccount),picked=selectable.find(sub=>String(sub.id)===accountId);return{accountId,companyName:picked?.companyName,self:!1}}async function resolveTargetAccountId(accountSelectPrompt,json){let spinner=createSpinner("Resolving target account...",{silent:json}),account;try{account=await accountService.getAccount()}finally{spinner.stop()}if(account?.type?.trim().toLowerCase()!==CORPORATE_ACCOUNT_TYPE)return{accountId:getCallerAccountId(),companyName:account?.companyName,self:!0};if(json||!process.stdin.isTTY)throw new CliError(messages.APP_INSTALL_ACCOUNT_ID_REQUIRED);return promptSubAccountSelection(accountSelectPrompt)}async function resolveInstallTarget(accountIdArg,options,selectPrompt,accountSelectPrompt,selectCommand){let account=accountIdArg?{accountId:parseAccountId(accountIdArg),self:!1}:await resolveTargetAccountId(accountSelectPrompt,options.json),accountFields={accountId:account.accountId,accountLabel:messages.APP_INSTALL_ACCOUNT_LABEL(account.accountId,account.companyName,account.self),...account.companyName?.trim()?{accountName:account.companyName.trim()}:{}};if(options.appId)return{appId:options.appId,appLabel:options.appId,appFromLinkedConfig:!1,...accountFields};let projectConfig=readProjectConfig();if(projectConfig)return{appId:projectConfig.appId,appLabel:projectConfig.appName||projectConfig.appId,appFromLinkedConfig:!0,...accountFields};assertAppSelectionAllowed(selectCommand,options.json);let selection=await promptAppSelection(selectPrompt,{filter:app=>resolveFromRecord(app).id==="ui",emptyMessage:messages.APP_INSTALL_NO_UI_APPS});return{appId:selection.appId,appLabel:selection.appLabel,appFromLinkedConfig:!1,...accountFields}}async function fetchInstallSnapshot(appId,opts={}){let spinner=createSpinner("Fetching app configuration...",{silent:opts.silent});try{return await appService.fetchApp(appId)}catch{return null}finally{spinner.stop()}}async function assertInstallable(appId,opts){let distributionOf=value=>value==="public"?"public":"private",projectConfig=opts.fromLinkedConfig?readProjectConfig():null;if(projectConfig){if(assertCapability(resolveFromConfig(projectConfig).id,distributionOf(projectConfig.distribution_type),"account-install",opts.notUiAppMessage),opts.requireUploaded&&!projectConfig.version?.trim())throw new CliError(messages.APP_INSTALL_NOT_UPLOADED);return}if(!appId)return;let app="serverApp"in opts?opts.serverApp:await fetchInstallSnapshot(appId);if(app&&(assertCapability(resolveFromRecord(app).id,distributionOf(app.distribution_type),"account-install",opts.notUiAppMessage),opts.requireUploaded&&!app.version?.trim()))throw new CliError(messages.APP_INSTALL_NOT_UPLOADED)}async function confirmInstallAction(confirmMessage,cancelledMessage,options){if(options.force||options.json)return!0;if(!process.stdin.isTTY)throw new CliError(messages.APP_INSTALL_NON_INTERACTIVE);let{confirmed}=await import_inquirer15.default.prompt([{type:"confirm",name:"confirmed",message:confirmMessage,default:!1}]);return confirmed?!0:(logInfo(`
|
|
665
|
+
${cancelledMessage}
|
|
666
|
+
`),!1)}var appInstallCommand=withCommandHandler(async options=>{let{appId,appLabel,accountId,accountLabel,accountName,appFromLinkedConfig}=await resolveInstallTarget(options.accountId,options,messages.APP_INSTALL_SELECT,messages.APP_INSTALL_SELECT_ACCOUNT,CLI.APP_INSTALL_APP_ID()),serverApp=await fetchInstallSnapshot(appId,{silent:options.json});if(await assertInstallable(appId,{requireUploaded:!0,notUiAppMessage:messages.APP_INSTALL_NOT_UI_APP(appId),fromLinkedConfig:appFromLinkedConfig,serverApp}),options.json||renderInstallSummary(appId,appLabel,serverApp,appFromLinkedConfig),!await confirmInstallAction(messages.APP_INSTALL_CONFIRM(appLabel,appId,accountLabel),messages.APP_INSTALL_CANCELLED,options))return;let spinner=createSpinner("Installing app...",{silent:options.json});try{await appService.installApp(appId,accountId,appLabel)}catch(err){throw spinner.stop(),err instanceof ApiError&&err.statusCode===422?new CliError(messages.APP_INSTALL_NOT_UPLOADED,err.exitCode):err}if(spinner.stop(),options.json){jsonOutput({installed:!0,appId,accountId,...accountName?{accountName}:{},...serverApp?.version?{version:serverApp.version}:{},...serverApp?.ui_app?{ui_app:serverApp.ui_app}:{}});return}logSuccess(messages.APP_INSTALL_SUCCESS(appId,accountLabel))});function renderInstallSummary(appId,appLabel,serverApp,appFromLinkedConfig){if(!serverApp)return;logInfo(""),logInfo(` ${messages.APP_INSTALL_SUMMARY}`),logInfo(` App ID: ${appId}`),logInfo(` Name: ${serverApp.name||appLabel}`),logInfo(` Version: ${serverApp.version||messages.APP_INSTALL_SUMMARY_NO_VERSION}`),serverApp.ui_app&&(logInfo(` ${messages.APP_UPLOAD_UI_APP_SUMMARY}`),logInfo(` Extension type: ${serverApp.ui_app.extension_type}`),formatPlacementLines(serverApp.ui_app).forEach((line,i)=>{logInfo(` ${i===0?"Placement: ":" "}${line}`)})),logInfo("");let localConfig=appFromLinkedConfig?readProjectConfig():null;localConfig?.ui_app&&!uiAppEquals(localConfig.ui_app,serverApp.ui_app)&&logWarn(` ${messages.APP_INSTALL_CONFIG_DRIFT}
|
|
667
|
+
`)}function reportNotInstalled(appId,account,json){if(json){jsonOutput({uninstalled:!1,appId,accountId:account.accountId,...account.accountName?{accountName:account.accountName}:{},reason:"NOT_INSTALLED",message:messages.APP_UNINSTALL_NOT_INSTALLED(appId,account.accountLabel)});return}logInfo(`
|
|
668
|
+
${messages.APP_UNINSTALL_NOT_INSTALLED(appId,account.accountLabel)}
|
|
669
|
+
`)}var appUninstallCommand=withCommandHandler(async options=>{let target=await resolveInstallTarget(options.accountId,options,messages.APP_UNINSTALL_SELECT,messages.APP_UNINSTALL_SELECT_ACCOUNT,CLI.APP_UNINSTALL_APP_ID()),{appId,appLabel,accountId,accountLabel}=target;if(await assertInstallable(appId,{requireUploaded:!1,notUiAppMessage:messages.APP_UNINSTALL_NOT_UI_APP(appId),fromLinkedConfig:target.appFromLinkedConfig}),!await confirmInstallAction(messages.APP_UNINSTALL_CONFIRM(appLabel,appId,accountLabel),messages.APP_UNINSTALL_CANCELLED,options))return;let spinner=createSpinner("Uninstalling app...",{silent:options.json});try{await appService.uninstallApp(appId,accountId,appLabel)}catch(err){if(spinner.stop(),err instanceof ApiError&&err.statusCode===404){reportNotInstalled(appId,target,options.json);return}throw err}if(spinner.stop(),options.json){jsonOutput({uninstalled:!0,appId,accountId,...target.accountName?{accountName:target.accountName}:{}});return}logSuccess(messages.APP_UNINSTALL_SUCCESS(appId,accountLabel))});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)+`
|
|
670
|
+
`,"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)}
|
|
671
|
+
`)}catch(err){let msg=err instanceof Error?err.message:String(err);output.write(` ${messages.SKILL_AUTOREFRESH_FAILED(s.name,msg)}
|
|
672
|
+
`)}}};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(`
|
|
673
|
+
${messages.SKILL_INSTALL_ALREADY(r.name,r.version)}`):(logSuccess(messages.SKILL_INSTALL_SUCCESS(r.name,r.version,r.path)),installedFresh=!0);installedFresh&&logInfo(`
|
|
674
|
+
${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(`
|
|
675
|
+
${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 ${EXAMPLE_APP_ID}`,`brevo app credentials --app-id ${EXAMPLE_APP_ID} --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 ${EXAMPLE_APP_ID}`,`brevo app delete --app-id ${EXAMPLE_APP_ID} --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 ${EXAMPLE_APP_ID}`,"brevo app scaffold --overwrite","brevo app scaffold --json",`brevo app scaffold --app-id ${EXAMPLE_APP_ID} --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})},{name:"install",requires:"account-install",description:"Install an app into a Brevo account",arguments:[{name:"[account-id]",description:"Brevo account (tenant) ID (defaults to your own account)"}],examples:["brevo app install","brevo app install 99999",`brevo app install 99999 --app-id ${EXAMPLE_APP_ID}`,"brevo app install 99999 --force --json"],options:[{flags:"--app-id <id>",description:"App ID (uses app-config.json if omitted)",parser:v=>parseAppId(v)},{flags:"--force",description:"Skip confirmation (for CI)"},{flags:"--json",description:"Output as JSON"}],handler:(opts,accountId)=>appInstallCommand({accountId,appId:opts.appId,force:!!opts.force,json:!!opts.json})},{name:"uninstall",requires:"account-install",description:"Uninstall an app from a Brevo account",arguments:[{name:"[account-id]",description:"Brevo account (tenant) ID (defaults to your own account)"}],examples:["brevo app uninstall","brevo app uninstall 99999",`brevo app uninstall 99999 --app-id ${EXAMPLE_APP_ID}`,"brevo app uninstall 99999 --force --json"],options:[{flags:"--app-id <id>",description:"App ID (uses app-config.json if omitted)",parser:v=>parseAppId(v)},{flags:"--force",description:"Skip confirmation (for CI)"},{flags:"--json",description:"Output as JSON"}],handler:(opts,accountId)=>appUninstallCommand({accountId,appId:opts.appId,force:!!opts.force,json:!!opts.json})}]},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(`
|
|
676
|
+
`)}function withNotice(box,serverMessage){let line=serverMessage??messages.CLI_VERSION_NOTICE_FALLBACK;return`
|
|
677
|
+
${color(COLOR_RED,line)}
|
|
678
|
+
${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)+`
|
|
679
|
+
`)))}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)+`
|
|
680
|
+
`),!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]}'
|
|
681
|
+
`),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}
|
|
682
|
+
`);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(`
|
|
683
|
+
Received ${signal}, shutting down.
|
|
684
|
+
`),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)+`
|
|
685
|
+
`),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(`
|
|
686
|
+
${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)});
|
|
687
|
+
//# sourceMappingURL=index.js.map
|