@getbrevo/cli 2.2.2 → 2.3.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 +25 -0
- package/README.md +59 -6
- package/agent-context/AGENTS.md +19 -9
- package/agent-context/SKILL.md +48 -12
- package/dist/bin/files/AGENTS.md.tmpl +2 -2
- package/dist/bin/files/CLAUDE.md.tmpl +1 -1
- package/dist/bin/files/README.md.tmpl +3 -3
- package/dist/bin/files/app-config.json.tmpl +11 -6
- package/dist/bin/index.js +108 -30
- package/dist/bin/index.js.map +3 -3
- package/package.json +1 -1
package/dist/bin/index.js
CHANGED
|
@@ -3,14 +3,15 @@
|
|
|
3
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
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
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";function resolveOauthBaseUrl(){let raw=process.env.BREVO_OAUTH_BASE_URL||"https://oauth.brevo.com",parsed;try{parsed=new URL(raw)}catch{throw new CliError(`Invalid BREVO_OAUTH_BASE_URL: "${raw}" is not a valid URL.`)}if(parsed.protocol!=="https:"&&!isLocalHttpAllowed(parsed))throw new CliError(`BREVO_OAUTH_BASE_URL must use HTTPS. Got: ${raw}
|
|
7
|
-
HTTP is only allowed for localhost/127.0.0.1.`);return parsed.origin}var OAUTH_BASE=resolveOauthBaseUrl(),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)+`
|
|
8
|
-
|
|
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",DP_FUNCTIONS:"/v3/dp-functions/functions",DP_FUNCTION:id=>`/v3/dp-functions/functions/${encodeURIComponent(id)}`,DP_FUNCTION_GENERATE_STREAM:"/v3/dp-functions/generate/stream",DP_FUNCTION_CREATE:"/v3/dp-functions/functions",DP_FUNCTION_TEMPLATES:"/v3/dp-functions/functions/templates",DP_FUNCTION_CONTACTS:"/v3/dp-functions/live-data/contacts",DP_FUNCTION_EXECUTE:"/v3/dp-functions/execute",DP_FUNCTION_CREATE_FROM_TEMPLATE:"/v3/dp-functions/functions/from-template",APP_STORE_APP_FUNCTIONS:"/v3/app-store/app-functions",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",FUNCTION_LIST:"brevo function list",FUNCTION_GET:"brevo function get --id <id>",FUNCTION_ACTIVATE:"brevo function activate --id <id>",FUNCTION_DEACTIVATE:"brevo function deactivate --id <id>",FUNCTION_DELETE:"brevo function delete --id <id>",FUNCTION_INIT:"brevo function init",FUNCTION_DEPLOY:"brevo function deploy --id <draft-id>",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";function resolveOauthBaseUrl(){let raw=process.env.BREVO_OAUTH_BASE_URL||"https://oauth.brevo.com",parsed;try{parsed=new URL(raw)}catch{throw new CliError(`Invalid BREVO_OAUTH_BASE_URL: "${raw}" is not a valid URL.`)}if(parsed.protocol!=="https:"&&!isLocalHttpAllowed(parsed))throw new CliError(`BREVO_OAUTH_BASE_URL must use HTTPS. Got: ${raw}
|
|
7
|
+
HTTP is only allowed for localhost/127.0.0.1.`);return parsed.origin}var OAUTH_BASE=resolveOauthBaseUrl(),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",LEGACY_KEY_ALIASES=[["app_id","appId"],["app_name","appName"],["logo_uri","logoUri"],["app_type","appType"]],LEGACY_AUTH_KEY_ALIASES=[["redirect_uris","redirectUris"],["redirect_uris","redirectUrls"]],mixedKeyWarnings=new Set;function warnMixedKeys(filePath,keys){keys.length===0||mixedKeyWarnings.has(filePath)||(mixedKeyWarnings.add(filePath),process.stderr.write(` \u26A0 app-config.json carries both spellings of ${keys.join(", ")} with different values; using the snake_case key. The camelCase copy is dropped on the next write.
|
|
8
|
+
`))}function foldLegacyKeys(record,aliases,conflicts){let out={...record};for(let[current,legacy]of aliases){if(!(legacy in out))continue;let legacyValue=out[legacy];delete out[legacy],out[current]===void 0?out[current]=legacyValue:legacyValue!==void 0&&JSON.stringify(out[current])!==JSON.stringify(legacyValue)&&conflicts.push(`${legacy}/${current}`)}return out}function readProjectConfig(){return readProjectConfigAt(process.cwd())}function readNormalizedAppId(raw){let rawAppId=raw.app_id;if(typeof rawAppId=="string")return rawAppId.trim()||void 0;if(typeof rawAppId=="number"&&Number.isFinite(rawAppId))return String(rawAppId)}function buildAuthOverride(rawAuth,conflicts){if(!rawAuth||typeof rawAuth!="object")return;let auth=rawAuth,override,scopes=auth.scopes;return(Array.isArray(scopes)||typeof scopes=="string")&&(override={...auth,scopes:splitScopes(scopes)}),LEGACY_AUTH_KEY_ALIASES.some(([,legacy])=>legacy in auth)&&(override=foldLegacyKeys(override??auth,LEGACY_AUTH_KEY_ALIASES,conflicts)),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 filePath=path.resolve(dir,PROJECT_CONFIG_FILE),raw=JSON.parse(fs.readFileSync(filePath,"utf-8"));if(!raw||typeof raw!="object")return null;let conflicts=[],rawRecord=foldLegacyKeys(raw,LEGACY_KEY_ALIASES,conflicts),appId=readNormalizedAppId(rawRecord);if(!appId)return null;let rawAuth=rawRecord.auth,authOverride=buildAuthOverride(rawAuth,conflicts),distributionType=readDistributionType(rawRecord,rawAuth);warnMixedKeys(filePath,conflicts);let{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,app_id:appId,distribution_type:distributionType,...authOverride?{auth:authOverride}:{}}}catch{return null}}function hasLocalApp(){let cfg=readProjectConfig();return cfg?.app_id!=null&&cfg.app_id!==""}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)+`
|
|
9
|
+
`,"utf-8")}var LEGACY_TOP_LEVEL_KEYS=[...LEGACY_KEY_ALIASES.map(([,legacy])=>legacy),"distribution","permittedUrls","support"],LEGACY_AUTH_KEYS=[...LEGACY_AUTH_KEY_ALIASES.map(([,legacy])=>legacy),"type"];function hasLegacyProjectConfigKeys(dir=process.cwd()){let raw;try{raw=JSON.parse(fs.readFileSync(path.resolve(dir,PROJECT_CONFIG_FILE),"utf-8"))}catch{return!1}if(!raw||typeof raw!="object")return!1;let record=raw;if(LEGACY_TOP_LEVEL_KEYS.some(key=>key in record))return!0;let auth=record.auth;return!auth||typeof auth!="object"?!1:LEGACY_AUTH_KEYS.some(key=>key in auth)}function migrateProjectConfigKeys(){if(!hasLegacyProjectConfigKeys())return!1;let config=readProjectConfig();return config?(writeProjectConfig(config),!0):!1}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?.app_id!==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)=>`
|
|
9
10
|
To authenticate, you need a Brevo API key.
|
|
10
11
|
Create one at: ${keysUrl}
|
|
11
12
|
Docs: ${docsUrl}
|
|
12
13
|
`,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:
|
|
13
|
-
${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
|
|
14
|
+
${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_APP_TYPE_FUNCTION:"Brevo Function (Serverless function running on Brevo\u2019s infrastructure)",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.
|
|
14
15
|
|
|
15
16
|
Do this: re-run with \`--distribution private\`
|
|
16
17
|
Note: \`distribution_type\` is fixed at creation \u2014 \`${CLI.APP_UPLOAD}\` can't change it later
|
|
@@ -25,11 +26,19 @@
|
|
|
25
26
|
|
|
26
27
|
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.
|
|
27
28
|
|
|
28
|
-
\`${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:
|
|
29
|
+
\`${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_TYPE_FUNCTION:"Brevo Function",APP_SELECT_NON_INTERACTIVE:command=>`Cannot show the app picker in non-interactive mode. Name the app instead:
|
|
29
30
|
|
|
30
31
|
${command}
|
|
31
32
|
|
|
32
|
-
\`${CLI.APP_LIST}\` shows the IDs.`,
|
|
33
|
+
\`${CLI.APP_LIST}\` shows the IDs.`,FUNCTION_LIST_HEADER:"Your Brevo Functions:",FUNCTION_LIST_EMPTY:"No Brevo Functions found. You have not created any Brevo Functions yet.",FUNCTION_LIST_DRAFT_HEADER:"Your draft Brevo Functions:",FUNCTION_LIST_DRAFT_EMPTY:"No draft Brevo Functions found.",FUNCTION_GET_HEADER:"Brevo Function details:",FUNCTION_GET_NOT_FOUND:id=>`Brevo Function "${id}" not found.`,FUNCTION_SELECT_NON_INTERACTIVE:command=>`Cannot show the function picker in non-interactive mode. Name the function instead:
|
|
34
|
+
|
|
35
|
+
${command}
|
|
36
|
+
|
|
37
|
+
\`${CLI.FUNCTION_LIST}\` shows the IDs.`,FUNCTION_GET_SELECT:"Select a function:",FUNCTION_ACTIVATE_SELECT:"Select a function to activate:",FUNCTION_DEACTIVATE_SELECT:"Select a function to deactivate:",FUNCTION_DELETE_SELECT:"Select a function to delete:",FUNCTION_ACTIVATE_NOT_FOUND:id=>`Brevo Function "${id}" not found.`,FUNCTION_ACTIVATE_CARD_TITLE:"Function Activated",FUNCTION_ACTIVATE_CARD_LABEL:"Status",FUNCTION_ACTIVATE_CARD_MESSAGE:id=>`"${id}" is now active and processing data.`,FUNCTION_DEACTIVATE_NOT_FOUND:id=>`Brevo Function "${id}" not found.`,FUNCTION_DEACTIVATE_CARD_TITLE:"Function Deactivated",FUNCTION_DEACTIVATE_CARD_LABEL:"Status",FUNCTION_DEACTIVATE_CARD_MESSAGE:id=>`"${id}" is now inactive.`,FUNCTION_DELETE_CONFIRM:id=>`Are you sure you want to delete Brevo Function "${id}"? This cannot be undone.`,FUNCTION_DELETE_CANCELLED:"Deletion cancelled.",FUNCTION_DELETE_NOT_FOUND:id=>`Brevo Function "${id}" not found.`,FUNCTION_DELETE_CARD_TITLE:"Function Deleted",FUNCTION_DELETE_CARD_LABEL:"Removed",FUNCTION_DELETE_CARD_MESSAGE:id=>`"${id}" has been permanently deleted.`,FUNCTION_INIT_SELECT_APP:"Select a Brevo Function app:",FUNCTION_INIT_NO_APPS:`No Brevo Function apps found. Create one first with \`${CLI.APP_CREATE}\`.`,FUNCTION_INIT_METHOD_PROMPT:"How would you like to create your function?",FUNCTION_INIT_METHOD_AI:"Generate using AI",FUNCTION_INIT_METHOD_TEMPLATE:"Use a predefined template",FUNCTION_INIT_DESCRIPTION_PROMPT:"Describe what this function should do:",FUNCTION_INIT_DESCRIPTION_REQUIRED:"Description cannot be empty.",FUNCTION_INIT_TEMPLATE_PROMPT:"Select a template:",FUNCTION_INIT_NO_TEMPLATES:"No templates available.",FUNCTION_INIT_STAGE_ENRICHING:"Analyzing the request",FUNCTION_INIT_STAGE_PLANNING:"Contacting databases",FUNCTION_INIT_STAGE_GENERATING:"Creating the function",FUNCTION_INIT_STAGE_VALIDATING:"Testing the function",FUNCTION_INIT_GENERATING:"Generating function...",FUNCTION_INIT_ITERATE_PROMPT:"What would you like to do?",FUNCTION_INIT_ITERATE_UPDATE:"Update / iterate on the prompt",FUNCTION_INIT_ITERATE_SAVE:"Deploy",FUNCTION_INIT_ITERATE_DESCRIPTION:"Describe the changes you want:",FUNCTION_INIT_ITERATING:"Iterating on function...",FUNCTION_INIT_SAVE_SPINNER:"Creating function...",FUNCTION_INIT_GENERATION_FAILED:"Function generation failed.",FUNCTION_INIT_GENERATION_ERROR:"Failed to generate function. Please try again.",FUNCTION_INIT_ITERATE_ERROR:"Failed to update function. Please try again.",FUNCTION_INIT_PREVIEW_ERROR:"Failed to preview function results.",FUNCTION_INIT_NON_INTERACTIVE:`\`${CLI.FUNCTION_INIT}\` requires an interactive terminal. It cannot run with --json or piped input.`,FUNCTION_INIT_FETCHING_CONTACTS:"Fetching sample contacts...",FUNCTION_INIT_EXECUTING_PREVIEW:"Previewing function...",FUNCTION_INIT_PREVIEW_HEADER:"Preview results:",FUNCTION_INIT_NAME_PROMPT:"Enter a name for this function:",FUNCTION_INIT_NAME_REQUIRED:"Name cannot be empty.",FUNCTION_INIT_DEPLOY_WARNING:"This will activate the function and run it with real-time data.",FUNCTION_INIT_DEPLOY_PROMPT:"Are you sure you want to deploy?",FUNCTION_INIT_NAME_EXISTS:"A function with this name already exists. Please choose a different name.",FUNCTION_INIT_DEPLOY_CANCELLED:"Deployment cancelled.",FUNCTION_INIT_CREATING_FROM_TEMPLATE:"Deploying function...",FUNCTION_INIT_BOX_TITLE:"Function deployed",FUNCTION_INIT_BOX_ID:id=>`ID: ${id}`,FUNCTION_DEPLOY_SELECT:"Select a draft to deploy:",FUNCTION_DEPLOY_NO_DRAFTS:"No draft functions found. Create one first with `brevo function init`.",FUNCTION_DEPLOY_NON_INTERACTIVE:`Cannot show the draft picker in non-interactive mode. Pass the draft ID instead:
|
|
38
|
+
|
|
39
|
+
${CLI.FUNCTION_DEPLOY}
|
|
40
|
+
|
|
41
|
+
\`${CLI.FUNCTION_LIST} --draft\` shows the IDs.`,FUNCTION_DEPLOY_NOT_FOUND:id=>`Draft "${id}" not found.`,FUNCTION_DEPLOY_PREVIEW_HEADER:"Preview results:",FUNCTION_DEPLOY_PREVIEW_ERROR:"Failed to preview draft results.",FUNCTION_DEPLOY_NAME_PROMPT:"Enter a name for this function:",FUNCTION_DEPLOY_NAME_REQUIRED:"Name cannot be empty.",FUNCTION_DEPLOY_WARNING:"This will activate the function and run it with real-time data.",FUNCTION_DEPLOY_CONFIRM:"Are you sure you want to deploy?",FUNCTION_DEPLOY_CANCELLED:"Deployment cancelled.",FUNCTION_DEPLOY_SPINNER:"Deploying function...",FUNCTION_DEPLOY_NAME_EXISTS:"A function with this name already exists. Please choose a different name.",FUNCTION_DEPLOY_BOX_TITLE:"Function deployed",FUNCTION_DEPLOY_BOX_ID:id=>`ID: ${id}`,FUNCTION_DEPLOY_FETCHING_CONTACTS:"Fetching sample contacts...",FUNCTION_DEPLOY_EXECUTING_PREVIEW:"Previewing function...",FUNCTION_DEPLOY_LINKING:"Linking function to app...",FUNCTION_DEPLOY_LINK_ERROR:"Function deployed but failed to link to app.",FUNCTION_DEPLOY_SELECT_APP:"Select an app to link this function to:",FUNCTION_DEPLOY_NO_APPS:`No Brevo Function apps found. Create one first with \`${CLI.APP_CREATE}\`.`,FUNCTION_LABEL_DESCRIPTION:"Description:",FUNCTION_LABEL_NAME:"Name:",FUNCTION_DEFAULT_NAME:"Untitled Function",FUNCTION_PREVIEW_EXECUTE_FAILED:"Unable to deploy function.",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.
|
|
33
42
|
|
|
34
43
|
\`${CLI.APP_LIST}\` shows each app's type.`,APP_UPDATE_REMOVED:`\`brevo app update\` has been removed \u2014 use \`${CLI.APP_UPLOAD}\` instead.
|
|
35
44
|
|
|
@@ -49,7 +58,10 @@
|
|
|
49
58
|
${CLI.APP_SCAFFOLD_APP_ID()}
|
|
50
59
|
${CLI.APP_UPLOAD}
|
|
51
60
|
|
|
52
|
-
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 "
|
|
61
|
+
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 "app_id". 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_CONFIG_KEYS_MIGRATED:"app-config.json was rewritten with snake_case keys (app_id, app_name, logo_uri, app_type, auth.redirect_uris) \u2014 values unchanged. If your own scripts read this file, update them to the new key names.",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.redirect_uris`.",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 `redirect_uris` from `auth` and keep it empty (`{}`).",APP_UPLOAD_APP_TYPE_MISMATCH:(declared,detected)=>`app-config.json says \`"app_type": "${declared}"\`, but its blocks describe a ${detected} app.
|
|
62
|
+
|
|
63
|
+
\`app_type\` is a label \u2014 the \`ui_app\` / \`brevo_function\` / \`auth\` blocks are what decide the type.
|
|
64
|
+
Set \`"app_type": "${detected}"\` to match the blocks, or edit the blocks to match the label. Removing \`app_type\` also works: it is optional.`,APP_INSTALL_MISSING_CLIENT_ID:`Could not determine your Brevo account's organization ID.
|
|
53
65
|
|
|
54
66
|
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}".
|
|
55
67
|
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.
|
|
@@ -91,13 +103,15 @@ ${available}
|
|
|
91
103
|
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.
|
|
92
104
|
|
|
93
105
|
Either stop the process using port ${port}, use a different port with \`--port <port>\`,
|
|
94
|
-
or update your redirect URL by editing \`auth.
|
|
106
|
+
or update your redirect URL by editing \`auth.redirect_uris\` in app-config.json and running \`${CLI.APP_UPLOAD}\`.`,APP_START_CUSTOM_PORT_IN_USE:port=>`Port ${port} is already in use.
|
|
95
107
|
|
|
96
108
|
Stop the process using port ${port}, or pick another port with \`--port <port>\`
|
|
97
|
-
and update your redirect URL by editing \`auth.
|
|
109
|
+
and update your redirect URL by editing \`auth.redirect_uris\` 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.redirect_uris\` 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.redirect_uris\` 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.
|
|
98
110
|
|
|
99
111
|
Why: UI apps (action links) are still rolling out, and are enabled per account.
|
|
100
|
-
Do this: build an OAuth app instead, or ask Brevo to enable UI apps for this account.`,
|
|
112
|
+
Do this: build an OAuth app instead, or ask Brevo to enable UI apps for this account.`,ERR_FEATURE_NOT_ENABLED:`Brevo Functions is not enabled for this account.
|
|
113
|
+
|
|
114
|
+
Contact Brevo to enable Brevo Functions for your 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)}
|
|
101
115
|
`)}}function logHttpResponse(status,path14){if(isDebug()){let code=status>=200&&status<300?"32":"31",line=`\u2190 ${status} ${path14}`;process.stderr.write(` ${color(code,line)}
|
|
102
116
|
`)}}function logDebug(context,data){if(isDebug()){let safe=redactSensitiveFields(data),line=`[debug] ${context}: ${JSON.stringify(safe)}`;process.stderr.write(` ${color("90",line)}
|
|
103
117
|
`)}}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(`
|
|
@@ -121,24 +135,35 @@ Usage: ${CLI.APP_START()}`,APP_START_UNKNOWN_FEATURE:(feature,available)=>`Unkno
|
|
|
121
135
|
`);for(let row of bodyRows)process.stdout.write(` \u2502 ${row}${" ".repeat(maxLen-displayWidth(row))} \u2502
|
|
122
136
|
`);process.stdout.write(` \u2514${border}\u2518
|
|
123
137
|
|
|
124
|
-
`)}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
|
|
138
|
+
`)}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 TONE_STYLES={neutral:{code:"90",icon:"\u25CB"},info:{code:"36",icon:"\u25C7"},pending:{code:"34",icon:"\u25D4"},progress:{code:"33",icon:"\u25D0"},success:{code:"32",icon:"\u2713"},warn:{code:"33",icon:"\u26A0"},error:{code:"31",icon:"\u2717"}};function printStatusCard(title,label,message,tone){let{code,icon}=TONE_STYLES[tone],rule="\u2500".repeat(title.length),bodyIndent=" "+" ".repeat(2),boldCode=`1;${code}`,out=`
|
|
139
|
+
${color("1",title)}
|
|
140
|
+
${color("90",rule)}
|
|
141
|
+
|
|
142
|
+
`;out+=` ${color(code,icon)} ${color(boldCode,label)}
|
|
143
|
+
`;for(let line of message.split(`
|
|
144
|
+
`))out+=`${bodyIndent}${color("90",line)}
|
|
145
|
+
`;out+=`
|
|
146
|
+
`,process.stdout.write(out)}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,feature_not_enabled:messages.ERR_FEATURE_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}async runEnsureFresh(){this.ensureFresh&&await this.ensureFresh()}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)}
|
|
125
147
|
`),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(`
|
|
126
148
|
${messages.APP_LIST_EMPTY}
|
|
127
|
-
`),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(
|
|
149
|
+
`),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(options){let path14=options?.type?`${ENDPOINTS.APP_STORE_APPS}?type=${encodeURIComponent(options.type)}`:ENDPOINTS.APP_STORE_APPS;return(await client2.get(path14)||[]).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 extractErrorMessage(obj){if(typeof obj.message=="string")return obj.message;if(typeof obj.error=="string")return obj.error}async function performSSEFetch(deps,method,path14,body){let authHeader=deps.getAuthHeader(),headers={"Content-Type":"application/json",Accept:"text/event-stream",...buildCliHeaders(authHeader),...authHeader},url=`${deps.baseUrl}${path14}`;try{return await fetch(url,{method,headers,body:body?JSON.stringify(body):void 0,signal:AbortSignal.timeout(12e4)})}catch(err){let apiErr=new ApiError(messages.ERR_NETWORK,0,"NETWORK_ERROR");throw apiErr.cause=err,apiErr}}async function handleSSEErrorResponse(response){let errorMessage=`Request failed with status ${response.status}`;try{let text=await response.text(),data=text?JSON.parse(text):{};if(data&&typeof data=="object"){let obj=data;if(obj.code==="feature_not_enabled")throw new ApiError(messages.ERR_FEATURE_NOT_ENABLED,response.status);let msg=extractErrorMessage(obj);msg&&(errorMessage=msg)}}catch(e){if(e instanceof ApiError)throw e}throw new ApiError(errorMessage,response.status)}async function readChunk(reader){try{let{done,value}=await reader.read();return done?null:value??null}catch(err){let apiErr=new ApiError(messages.ERR_NETWORK,0,"NETWORK_ERROR");throw apiErr.cause=err,apiErr}}function processSSELine(line,state){if(line===""||line==="\r"){if(state.currentData.length>0){let event={event:state.currentEvent,data:state.currentData.join(`
|
|
150
|
+
`)};return state.currentEvent=void 0,state.currentData=[],event}return state.currentEvent=void 0,state.currentData=[],null}let stripped=line.endsWith("\r")?line.slice(0,-1):line;return stripped.startsWith("event:")?state.currentEvent=stripped.slice(6).trim():stripped.startsWith("data:")&&state.currentData.push(stripped.slice(5).trimStart()),null}function flushSSEState(state){return state.currentData.length>0?{event:state.currentEvent,data:state.currentData.join(`
|
|
151
|
+
`)}:null}async function*sseStream(deps,method,path14,body){deps.ensureFresh&&await deps.ensureFresh();let response=await performSSEFetch(deps,method,path14,body);if(response.ok||await handleSSEErrorResponse(response),!response.body)return;let reader=response.body.getReader(),decoder=new TextDecoder,buffer="",state={currentEvent:void 0,currentData:[]};try{for(;;){let chunk=await readChunk(reader);if(!chunk)break;buffer+=decoder.decode(chunk,{stream:!0});let lines=buffer.split(`
|
|
152
|
+
`);buffer=lines.pop()??"";for(let line of lines){let event=processSSELine(line,state);event&&(yield event)}}let trailing=flushSSEState(state);trailing&&(yield trailing)}finally{reader.releaseLock()}}function createFunctionService(client2){return{async fetchFunctionList(){let all=[],offset=0,last;do{let params=new URLSearchParams({limit:String(50),offset:String(offset)});last=await client2.get(`${ENDPOINTS.DP_FUNCTIONS}?${params}`);let page=last.functions??[];if(all.push(...page),offset+=50,page.length===0)break}while(last.has_more);return{...last,functions:all}},async fetchDraftFunctionList(){let all=[],offset=0,last;do{let params=new URLSearchParams({limit:String(50),offset:String(offset),draft:"true"});last=await client2.get(`${ENDPOINTS.DP_FUNCTIONS}?${params}`);let page=last.drafts??[];if(all.push(...page),offset+=50,page.length===0)break}while(last.has_more);return{...last,drafts:all}},async fetchFunction(id){return client2.get(ENDPOINTS.DP_FUNCTION(id))},async activateFunction(id){await client2.patch(ENDPOINTS.DP_FUNCTION(id),{is_active:!0})},async deactivateFunction(id){await client2.patch(ENDPOINTS.DP_FUNCTION(id),{is_active:!1})},async deleteFunction(id){await client2.delete(ENDPOINTS.DP_FUNCTION(id))},async fetchTemplates(){return(await client2.get(ENDPOINTS.DP_FUNCTION_TEMPLATES)).templates},async createFunction(payload){return client2.post(ENDPOINTS.DP_FUNCTION_CREATE,payload)},async*generateStream(sseDeps2,payload){yield*sseStream(sseDeps2,"POST",ENDPOINTS.DP_FUNCTION_GENERATE_STREAM,payload)},async*iterateStream(sseDeps2,payload){yield*sseStream(sseDeps2,"PATCH",ENDPOINTS.DP_FUNCTION_GENERATE_STREAM,payload)},async fetchContacts(){return client2.post(ENDPOINTS.DP_FUNCTION_CONTACTS,{})},async executeTemplate(payload){return client2.post(ENDPOINTS.DP_FUNCTION_EXECUTE,payload)},async createFromTemplate(payload){return client2.post(ENDPOINTS.DP_FUNCTION_CREATE_FROM_TEMPLATE,payload)},async linkFunctionToApp(payload){return client2.post(ENDPOINTS.APP_STORE_APP_FUNCTIONS,payload)}}}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),functionService=createFunctionService(client),sseDeps={baseUrl:API_BASE,getAuthHeader:buildAuthHeader,ensureFresh:()=>client.runEnsureFresh()};var FEATURE_STAGE={"account-install":"ga","review-lifecycle":"preview","ui-app-type":"ga","public-distribution":"preview","brevo-function-type":"ga"};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",`
|
|
128
153
|
Examples:
|
|
129
154
|
`+def.examples.map(e=>` $ ${e}`).join(`
|
|
130
155
|
`)+`
|
|
131
|
-
`),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(`
|
|
132
|
-
`)}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)+`
|
|
133
|
-
`)}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(`
|
|
156
|
+
`),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);if(group.aliases)for(let alias of group.aliases)groupCmd.alias(alias);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","",...gatedSection("brevo-function-type",["Function commands (alias: brevo fn):"," brevo function list [--draft] [--json] List all Brevo Functions in your account"," brevo function get [--id <id>] [--json] Show details of a Brevo Function"," brevo function activate [--id <id>] [--json] Activate a Brevo Function"," brevo function deactivate [--id <id>] [--json] Deactivate a Brevo Function"," brevo function delete [--id <id>] [--force] [--json]"," Delete a Brevo Function"," brevo function init Create a new Brevo Function (interactive)"," brevo function deploy [--id <id>] [--app-id <id>] [--json]"," Deploy a draft Brevo Function",""]),"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(`
|
|
157
|
+
`)}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,SNAKE_CASE_OVERRIDES={redirectUri:"redirect_uris",redirectUris:"redirect_uris"};function camelToSnake(key){return key.replaceAll(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase()}function isPlainObject(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function aliasKeys(obj){let out={};for(let[key,value]of Object.entries(obj)){out[key]=value;let alias=SNAKE_CASE_OVERRIDES[key]??camelToSnake(key);alias!==key&&!(alias in obj)&&!(alias in out)&&(out[alias]=value)}return out}function withSnakeCaseAliases(data){if(Array.isArray(data))return data.map(item=>isPlainObject(item)?aliasKeys(item):item);if(!isPlainObject(data))return data;let aliased=aliasKeys(data);return isPlainObject(aliased.error)&&(aliased.error=aliasKeys(aliased.error)),aliased}function jsonOutput(data){emitted=!0,process.stdout.write(JSON.stringify(withSnakeCaseAliases(data))+`
|
|
158
|
+
`)}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])}function isFunctionAppConfig(config){return!config||typeof config!="object"?!1:"brevo_function"in config&&!!config.brevo_function}function isFunctionAppRecord(app){return!app||typeof app!="object"?!1:"brevo_function"in app&&!!app.brevo_function}var functionAppType={id:"function",label:messages.APP_TYPE_FUNCTION,availability:"ga",detectConfig:config=>isFunctionAppConfig(config),detectRecord:app=>isFunctionAppRecord(app),recoverableFromRecord:app=>!!app,validateConfig:()=>{},wireOnlyKeys:[]};var oauthAppType={id:"oauth",label:messages.APP_TYPE_OAUTH,availability:"ga",detectConfig:config=>!isUiAppConfigShape(config)&&!isFunctionAppConfig(config),detectRecord:app=>!isUiAppRecordShape(app)&&!!app&&!!app.client_id,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:{private:[],public:["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,function:functionAppType},POSITIVELY_DETECTED=[functionAppType,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(`
|
|
134
159
|
${messages.APP_LIST_EMPTY}
|
|
135
|
-
`),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(`
|
|
160
|
+
`),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|brevo_function)\}\}\s*$/,IF_CLOSE_RE=/^\s*\{\{\/if\}\}\s*$/;function applyConditionals(template,flags){let activeFlags=typeof flags=="string"?new Set([flags]):flags,lines=template.split(`
|
|
136
161
|
`),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(`
|
|
137
|
-
`)}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
|
|
162
|
+
`)}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}}"],isBrevoFunction=!!vars["{{BREVO_FUNCTION_JSON}}"],appTypeFlag;return isUiApp?appTypeFlag="ui_app":isBrevoFunction?appTypeFlag="brevo_function":appTypeFlag="oauth",new Set([distribution,appTypeFlag])}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(`
|
|
138
163
|
`)}function printFileTree(filePaths){for(let line of formatFileTree(filePaths).split(`
|
|
139
|
-
`))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.
|
|
164
|
+
`))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.app_name!==serverName&&diffs.push({field:"app_name",local:localConfig.app_name||"(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?.redirect_uris??[]].sort((a,b)=>a.localeCompare(b)),serverRedirects=[...ctx.redirectUris].sort((a,b)=>a.localeCompare(b));JSON.stringify(localRedirects)!==JSON.stringify(serverRedirects)&&diffs.push({field:"redirect_uris",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.logo_uri??"",serverLogo=ctx.appDetails?.logo_uri??"";localLogo!==serverLogo&&diffs.push({field:"logo_uri",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(`
|
|
140
165
|
`,`
|
|
141
|
-
`):""}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??[],sizeFor:row=>row.default_size??void 0,label:String(label??"").trim(),more_info:String(more_info??"").trim(),redirect_link:String(url??"").trim()})};return validateUiApp(uiApp),uiApp}async function resolveUiAppNonInteractive(input){if(input.extensionType!==EXTENSION_TYPE_ACTION_LINK)throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_EXTENSION_TYPE(input.extensionType));let locations=await fetchRecordPageLocations(input.extensionType);if(!locations.includes(input.recordPage))throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_UNKNOWN_RECORD_PAGE(input.recordPage,locations));let forPage=(await fetchSurfacePointsForPages([input.recordPage],input.extensionType)).filter(row=>row.location_name===input.recordPage),matched=forPage.filter(row=>row.surface_point_name===input.placement);if(matched.length===0)throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_UNKNOWN_PLACEMENT(input.placement,input.recordPage,forPage.map(row=>row.surface_point_name)));let uiApp={extension_type:input.extensionType,surface_point_list:buildSurfacePointList(matched,{contextFor:row=>row.default_context_field??[],sizeFor:row=>row.default_size??void 0,label:input.label.trim(),more_info:input.moreInfo.trim(),redirect_link:input.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),size=sanitizeSeededSize(fields.sizeFor(row));entries.push({surface_point_name:row.surface_point_name,...context.length?{context}:{},...size?{size}:{},label:fields.label,...fields.more_info?{more_info:fields.more_info}:{},redirect_link:fields.redirect_link})}return entries}function sanitizeSeededSize(raw){if(!raw||typeof raw!="object")return;let width=typeof raw.width=="string"?raw.width.trim():"",height=typeof raw.height=="string"?raw.height.trim():"";if(!(!width&&!height))return{...width?{width}:{},...height?{height}:{}}}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}function stringField(value){return typeof value=="string"?value:""}function parseUiConfigFile(configPath){let raw;try{raw=fs5.readFileSync(configPath,"utf-8")}catch(err){throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_CONFIG_INVALID(configPath,err instanceof Error?err.message:String(err)))}let parsed;try{parsed=JSON.parse(raw)}catch(err){throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_CONFIG_INVALID(configPath,err instanceof Error?err.message:String(err)))}return{extensionType:stringField(parsed.extension_type),recordPage:stringField(parsed.record_page),placement:stringField(parsed.surface_point_name),label:stringField(parsed.label),moreInfo:stringField(parsed.more_info),url:stringField(parsed.redirect_link)}}function buildUiAppInputFromFlags(opts){let missing=[["recordPage","--record-page"],["placement","--placement"],["label","--label"],["url","--url"]].filter(([key])=>!opts[key]).map(([,flag])=>flag);if(missing.length>0)throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_MISSING_FLAGS(missing));return{extensionType:EXTENSION_TYPE_ACTION_LINK,recordPage:opts.recordPage,placement:opts.placement,label:opts.label,moreInfo:opts.moreInfo??"",url:opts.url}}function resolveUiAppNonInteractiveInput(opts){let hasConfig=!!opts.uiConfig,hasFlags=!!opts.uiApp;if(!(!hasConfig&&!hasFlags)){if(hasConfig&&hasFlags)throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_BOTH_INPUTS);if((opts.redirectUri?.length??0)>0)throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_OAUTH_FLAG("--redirect-uri"));return hasConfig?parseUiConfigFile(opts.uiConfig):buildUiAppInputFromFlags(opts)}}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}}async function resolveUiAppOrRedirectUris(appType,nonInteractiveUiAppInput,redirectUriFlag,jsonMode){if(appType!=="ui")return{redirectUris:await resolveRedirectUrls(redirectUriFlag,jsonMode),uiApp:void 0};let uiApp=nonInteractiveUiAppInput?await resolveUiAppNonInteractive(nonInteractiveUiAppInput):await resolveUiApp();return{redirectUris:[],uiApp}}function cacheAppIdentity(result,finalAppName){result.client_id&&result.client_secret&&saveAppCredentials(result.app_id,{clientId:result.client_id,clientSecret:result.client_secret}),finalAppName&&saveAppName(result.app_id,finalAppName)}function buildCreateJsonBase(result,finalAppName,appType,uiApp,logoUri){return{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}:{}}}function reportSkippedDirectory(jsonMode,jsonBase,dir,renderBox2){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))}function buildFallbackOAuthApp(result){return{...result,client_id:result.client_id??"",redirect_uris:result.redirect_uris??null}}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 nonInteractiveUiAppInput=resolveUiAppNonInteractiveInput(options),interactive=!jsonMode&&!!process.stdin.isTTY,appName=await resolveAppName(options.name),logoUri=await resolveLogoUri(options.logoUri,jsonMode),distribution=await resolveDistribution(options.distribution,interactive),appType=nonInteractiveUiAppInput?"ui":await resolveAppType(interactive),{redirectUris,uiApp}=await resolveUiAppOrRedirectUris(appType,nonInteractiveUiAppInput,options.redirectUri,jsonMode),dir=await resolveCreateDirectory(appName,interactive),inputs={appName,distribution,redirectUris,logoUri,uiApp},{result,appName:finalAppName}=await createAppWithRetry(inputs,jsonMode,interactive);applyCreateDirectory(dir,jsonMode),cacheAppIdentity(result,finalAppName);let jsonBase=buildCreateJsonBase(result,finalAppName,appType,uiApp,logoUri),renderBox2=()=>uiApp?renderCreatedUiApp(result,finalAppName,uiApp,logoUri):renderCreatedApp(result,finalAppName,logoUri);if(dir.skipped){reportSkippedDirectory(jsonMode,jsonBase,dir,renderBox2);return}let fallbackApp=buildFallbackOAuthApp(result),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(`
|
|
166
|
+
`):""}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??"","{{APP_TYPE}}":ctx.uiApp?"ui":ctx.isBrevoFunction?"function":"oauth","{{OAUTH_BASE}}":OAUTH_BASE,"{{OAUTH_REALM}}":OAUTH_REALM,"{{UI_APP_JSON}}":renderUiAppJson(ctx.uiApp),"{{BREVO_FUNCTION_JSON}}":ctx.isBrevoFunction?"{}":""},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??[],sizeFor:row=>row.default_size??void 0,label:String(label??"").trim(),more_info:String(more_info??"").trim(),redirect_link:String(url??"").trim()})};return validateUiApp(uiApp),uiApp}async function resolveUiAppNonInteractive(input){if(input.extensionType!==EXTENSION_TYPE_ACTION_LINK)throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_EXTENSION_TYPE(input.extensionType));let locations=await fetchRecordPageLocations(input.extensionType);if(!locations.includes(input.recordPage))throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_UNKNOWN_RECORD_PAGE(input.recordPage,locations));let forPage=(await fetchSurfacePointsForPages([input.recordPage],input.extensionType)).filter(row=>row.location_name===input.recordPage),matched=forPage.filter(row=>row.surface_point_name===input.placement);if(matched.length===0)throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_UNKNOWN_PLACEMENT(input.placement,input.recordPage,forPage.map(row=>row.surface_point_name)));let uiApp={extension_type:input.extensionType,surface_point_list:buildSurfacePointList(matched,{contextFor:row=>row.default_context_field??[],sizeFor:row=>row.default_size??void 0,label:input.label.trim(),more_info:input.moreInfo.trim(),redirect_link:input.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),size=sanitizeSeededSize(fields.sizeFor(row));entries.push({surface_point_name:row.surface_point_name,...context.length?{context}:{},...size?{size}:{},label:fields.label,...fields.more_info?{more_info:fields.more_info}:{},redirect_link:fields.redirect_link})}return entries}function sanitizeSeededSize(raw){if(!raw||typeof raw!="object")return;let width=typeof raw.width=="string"?raw.width.trim():"",height=typeof raw.height=="string"?raw.height.trim():"";if(!(!width&&!height))return{...width?{width}:{},...height?{height}:{}}}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?.app_name||String(projectConfig?.app_id??"");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}function stringField(value){return typeof value=="string"?value:""}function parseUiConfigFile(configPath){let raw;try{raw=fs5.readFileSync(configPath,"utf-8")}catch(err){throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_CONFIG_INVALID(configPath,err instanceof Error?err.message:String(err)))}let parsed;try{parsed=JSON.parse(raw)}catch(err){throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_CONFIG_INVALID(configPath,err instanceof Error?err.message:String(err)))}return{extensionType:stringField(parsed.extension_type),recordPage:stringField(parsed.record_page),placement:stringField(parsed.surface_point_name),label:stringField(parsed.label),moreInfo:stringField(parsed.more_info),url:stringField(parsed.redirect_link)}}function buildUiAppInputFromFlags(opts){let missing=[["recordPage","--record-page"],["placement","--placement"],["label","--label"],["url","--url"]].filter(([key])=>!opts[key]).map(([,flag])=>flag);if(missing.length>0)throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_MISSING_FLAGS(missing));return{extensionType:EXTENSION_TYPE_ACTION_LINK,recordPage:opts.recordPage,placement:opts.placement,label:opts.label,moreInfo:opts.moreInfo??"",url:opts.url}}function resolveUiAppNonInteractiveInput(opts){let hasConfig=!!opts.uiConfig,hasFlags=!!opts.uiApp;if(!(!hasConfig&&!hasFlags)){if(hasConfig&&hasFlags)throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_BOTH_INPUTS);if((opts.redirectUri?.length??0)>0)throw new CliError(messages.APP_CREATE_UI_NONINTERACTIVE_OAUTH_FLAG("--redirect-uri"));return hasConfig?parseUiConfigFile(opts.uiConfig):buildUiAppInputFromFlags(opts)}}async function resolveAppType(interactive,distribution){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"}),isFeatureAvailable("brevo-function-type")&&distribution==="private"&&choices.push({name:messages.APP_CREATE_APP_TYPE_FUNCTION,value:"function"}),(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,isFunction=inputs.appType==="function",typeBlock;return isUiApp?typeBlock={ui_app:inputs.uiApp}:isFunction?typeBlock={brevo_function:{}}:typeBlock={auth:{scopes:[...DEFAULT_SCOPES],redirect_uris:inputs.redirectUris}},{name:inputs.appName,distribution_type:inputs.distribution,...typeBlock,...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}}async function resolveUiAppOrRedirectUris(appType,nonInteractiveUiAppInput,redirectUriFlag,jsonMode){if(appType==="function")return{redirectUris:[],uiApp:void 0};if(appType!=="ui")return{redirectUris:await resolveRedirectUrls(redirectUriFlag,jsonMode),uiApp:void 0};let uiApp=nonInteractiveUiAppInput?await resolveUiAppNonInteractive(nonInteractiveUiAppInput):await resolveUiApp();return{redirectUris:[],uiApp}}function cacheAppIdentity(result,finalAppName){result.client_id&&result.client_secret&&saveAppCredentials(result.app_id,{clientId:result.client_id,clientSecret:result.client_secret}),finalAppName&&saveAppName(result.app_id,finalAppName)}function buildCreateJsonBase(result,finalAppName,appType,uiApp,logoUri){return{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}:{}}}function reportSkippedDirectory(jsonMode,jsonBase,dir,renderBox2){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))}function buildFallbackOAuthApp(result){return{...result,client_id:result.client_id??"",redirect_uris:result.redirect_uris??null}}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 nonInteractiveUiAppInput=resolveUiAppNonInteractiveInput(options),interactive=!jsonMode&&!!process.stdin.isTTY,appName=await resolveAppName(options.name),logoUri=await resolveLogoUri(options.logoUri,jsonMode),distribution=await resolveDistribution(options.distribution,interactive),appType=nonInteractiveUiAppInput?"ui":await resolveAppType(interactive,distribution),{redirectUris,uiApp}=await resolveUiAppOrRedirectUris(appType,nonInteractiveUiAppInput,options.redirectUri,jsonMode),dir=await resolveCreateDirectory(appName,interactive),inputs={appName,distribution,redirectUris,logoUri,uiApp,appType},{result,appName:finalAppName}=await createAppWithRetry(inputs,jsonMode,interactive);applyCreateDirectory(dir,jsonMode),cacheAppIdentity(result,finalAppName);let jsonBase=buildCreateJsonBase(result,finalAppName,appType,uiApp,logoUri),renderBox2=()=>uiApp?renderCreatedUiApp(result,finalAppName,uiApp,logoUri):renderCreatedApp(result,finalAppName,logoUri);if(dir.skipped){reportSkippedDirectory(jsonMode,jsonBase,dir,renderBox2);return}let fallbackApp=buildFallbackOAuthApp(result),ctx=await fetchAppContext(result.app_id,jsonMode,uiApp,fallbackApp);appType==="function"&&(ctx.isBrevoFunction=!0);let 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(`
|
|
142
167
|
${messages.AUTH_NEXT}
|
|
143
168
|
`);return}process.stdout.write(`
|
|
144
169
|
`);let{shouldCreate}=await import_inquirer7.default.prompt([{type:"confirm",name:"shouldCreate",message:messages.AUTH_CREATE_APP_PROMPT,default:!0}]);if(shouldCreate){process.stdout.write(`
|
|
@@ -147,12 +172,12 @@ Examples:
|
|
|
147
172
|
`)}var loginCommand=withCommandHandler(async options=>{let quiet=!!options.json;quiet||(process.stdout.write(`
|
|
148
173
|
${messages.AUTH_WELCOME}
|
|
149
174
|
`),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
|
|
150
|
-
`));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.
|
|
175
|
+
`));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.app_name||localConfig.app_id));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.app_id,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.app_id!==appId)throw new CliError(messages.APP_SCAFFOLD_TARGET_LINKED_ELSEWHERE(targetDir,targetConfig.app_id,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.app_id!==requestedAppId)throw new CliError(messages.APP_SCAFFOLD_APP_ID_MISMATCH(localConfig.app_id,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}let migratedKeys=!1;if(layout.refreshBase?migratedKeys=hasLegacyProjectConfigKeys():migratedKeys=migrateProjectConfigKeys(),migratedKeys&&!layout.refreshBase&&!jsonMode&&logInfo(messages.APP_CONFIG_KEYS_MIGRATED),localConfig?isUiAppConfig(localConfig):!!ctx.uiApp){finishUiAppScaffold(appId,ctx,layout,jsonMode),reportKeyMigration(layout,migratedKeys,jsonMode);return}!localConfig&&!jsonMode&&!!process.stdin.isTTY?await finishInteractiveBootstrap(appId,ctx,layout,jsonMode,overwrite):await finishFeatureScaffold(appId,ctx,layout,jsonMode,overwrite),reportKeyMigration(layout,migratedKeys,jsonMode)});function reportKeyMigration(layout,migratedKeys,jsonMode){layout.refreshBase&&migratedKeys&&!jsonMode&&logInfo(messages.APP_CONFIG_KEYS_MIGRATED)}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(`
|
|
151
176
|
${messages.INIT_WELCOME}
|
|
152
177
|
`),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
|
|
153
178
|
|
|
154
179
|
`),await ensureLoggedIn(),process.stdout.write(`
|
|
155
|
-
`);let projectConfig=readProjectConfig(),configAppId=typeof projectConfig?.
|
|
180
|
+
`);let projectConfig=readProjectConfig(),configAppId=typeof projectConfig?.app_id=="string"?projectConfig.app_id.trim():"",linkedName=projectConfig?.app_name||configAppId;if(configAppId&&await appExistsOnServer(configAppId)){let action=await promptLinkedAppAction(configAppId,linkedName);if(action==="skip"){logInfo(`
|
|
156
181
|
${initDoneMessage()}
|
|
157
182
|
`);return}if(action==="scaffold"){process.stdout.write(`
|
|
158
183
|
`),await scaffoldCommand({}),logInfo(`
|
|
@@ -187,15 +212,15 @@ Examples:
|
|
|
187
212
|
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(`
|
|
188
213
|
`)}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.
|
|
189
214
|
`))}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)}
|
|
190
|
-
`)});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}
|
|
215
|
+
`)});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)}let rawRecord=raw&&typeof raw=="object"?raw:null;if(!rawRecord||!(rawRecord.app_id??rawRecord.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.app_id,currentName:remote.name,nextName:config.app_name,currentUrls:remote.redirect_uris??[],nextUrls:config.auth?.redirect_uris??[],currentLogoUri:remote.logo_uri,nextLogoUri:config.logo_uri??"",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,isFunctionApp:isFunctionAppConfig(config)}}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}`);let isOAuthDiff=!diff.nextUiApp&&!diff.isFunctionApp;isOAuthDiff&&logAligned(" Redirect URLs: ",diffLines(diff.currentUrls,diff.nextUrls)),diff.migratingLegacyScopes&&logInfo(` ${messages.LEGACY_ALL_SCOPE_UPDATE_MIGRATING}`),isOAuthDiff&&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){let isOAuth=!diff.nextUiApp&&!diff.isFunctionApp;return{current:{name:diff.currentName,...isOAuth?{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,...isOAuth?{redirect_uris:diff.nextUrls,scopes:diff.nextScopes}:{},logo_uri:diff.nextLogoUri,distribution_type:diff.nextDistribution,version:diff.nextVersion,...diff.nextUiApp?{ui_app:diff.nextUiApp}:{},...diff.isFunctionApp?{brevo_function:{}}:{}}}}function hasNoChanges(diff){let oauthUnchanged=!!diff.nextUiApp||diff.isFunctionApp||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),isFnApp=isFunctionAppConfig(config),redirectUris=config.auth?.redirect_uris??[],scopes=config.auth?.scopes??[],appVersion=opts.appVersion??config.version??"";appVersion||(appVersion=(await fetchExistingApp(config.app_id,opts.silent)).version??"");let spinner=createSpinner("Uploading app...",{silent:opts.silent}),response;try{response=await appService.uploadApp(config.app_id,{app_id:config.app_id,name:config.app_name,logo_uri:config.logo_uri??"",version:appVersion,distribution_type:config.distribution_type,...isUiApp||isFnApp?{}:{auth:{scopes,redirect_uris:redirectUris}},...isUiApp&&config.ui_app?{ui_app:withInjectedLinkTargets(config.ui_app)}:{},...isFnApp?{brevo_function:{}}:{}})}finally{spinner.stop()}let finalName=response.name??config.app_name;finalName&&saveAppName(config.app_id,finalName);let confirmedVersion=response.version??response.app_version??appVersion;return writeProjectConfig({...config,app_name:finalName,logo_uri:response.logo_uri??config.logo_uri,distribution_type:response.distribution_type??config.distribution_type,version:confirmedVersion,...isFnApp?{}:{auth:isUiApp?{}:{scopes:response.auth?.scopes??scopes,redirect_uris: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(!isFunctionAppConfig(config)&&isUiAppConfig(config)){if(!config.auth)throw new CliError(messages.APP_UPLOAD_UI_APP_AUTH_EMPTY_REQUIRED);if(config.auth.scopes!==void 0||config.auth.redirect_uris!==void 0)throw new CliError(messages.APP_UPLOAD_UI_APP_AUTH_HAS_OAUTH_FIELDS)}}function assertAppTypeAgrees(config){let declared=config.app_type;if(!declared)return;let detected=resolveFromConfig(config).id;if(declared!==detected)throw new CliError(messages.APP_UPLOAD_APP_TYPE_MISMATCH(declared,detected))}function runLocalPreflight(config){validateAuthShape(config);let redirectUris=config.auth?.redirect_uris??[],isFunction=isFunctionAppConfig(config);if(!isUiAppConfig(config)&&!isFunction&&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);assertAppTypeAgrees(config)}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.app_id,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)){let migrated=migrateProjectConfigKeys();if(options.json){jsonOutput({appId:config.app_id,upToDate:!0,version:diff.nextVersion,...diffToJson(diff)});return}logInfo(messages.APP_UPLOAD_UP_TO_DATE(diff.nextVersion||"unknown")),migrated&&logInfo(messages.APP_CONFIG_KEYS_MIGRATED);return}let affectsInstalls=!!diff.nextUiApp;if(!options.json&&affectsInstalls&&logWarn(` ${messages.APP_UPLOAD_INSTALLED_IMPACT}
|
|
191
216
|
`),!options.json&&!options.yes&&!await confirmUpload(affectsInstalls)){logInfo(`
|
|
192
217
|
${messages.APP_UPLOAD_CANCELLED}
|
|
193
|
-
`);return}let{confirmedVersion,finalName}=await uploadProjectConfig(config,{silent:options.json,appVersion:diff.nextVersion});if(options.json){jsonOutput({appId:config.
|
|
218
|
+
`);return}let migratingKeys=hasLegacyProjectConfigKeys(),{confirmedVersion,finalName}=await uploadProjectConfig(config,{silent:options.json,appVersion:diff.nextVersion});if(options.json){jsonOutput({appId:config.app_id,name:finalName,version:confirmedVersion,...diffToJson(diff)});return}logSuccess(messages.APP_UPLOAD_SUCCESS),logInfo(` Version: ${confirmedVersion||"(unknown)"}`),migratingKeys&&logInfo(` ${messages.APP_CONFIG_KEYS_MIGRATED}`),process.stdout.write(`
|
|
194
219
|
`)});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(`
|
|
195
220
|
${messages.APP_DELETE_WARNING(appLabel,appId)}
|
|
196
221
|
`);let{confirmed}=await import_inquirer13.default.prompt([{type:"confirm",name:"confirmed",message:messages.APP_DELETE_CONFIRM(appLabel,appId),default:!1}]);return confirmed?!0:(logInfo(`
|
|
197
222
|
${messages.APP_DELETE_CANCELLED}
|
|
198
|
-
`),!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()?.
|
|
223
|
+
`),!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()?.app_id!==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(`
|
|
199
224
|
${messages.APP_DELETE_WARNING(appLabel||appId,appId)}
|
|
200
225
|
`);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=`
|
|
201
226
|
:root {
|
|
@@ -658,11 +683,11 @@ footer a { color: var(--accent); }
|
|
|
658
683
|
<script>${SCRIPT}</script>
|
|
659
684
|
</body>
|
|
660
685
|
</html>
|
|
661
|
-
`}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?.
|
|
662
|
-
`);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?.
|
|
686
|
+
`}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?.redirect_uris??[],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,redirect_uris:[...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(`
|
|
687
|
+
`);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?.redirect_uris?.[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(`
|
|
663
688
|
${messages.APP_START_STOPPED}
|
|
664
|
-
`),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?.
|
|
665
|
-
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.
|
|
689
|
+
`),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?.app_id?await ensureRedirectRegistered(config,port):void 0;logInfo(`
|
|
690
|
+
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.app_id,appLabel:projectConfig.app_name||projectConfig.app_id,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(`
|
|
666
691
|
${cancelledMessage}
|
|
667
692
|
`),!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}
|
|
668
693
|
`)}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(`
|
|
@@ -673,13 +698,66 @@ footer a { color: var(--accent); }
|
|
|
673
698
|
`)}}};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(`
|
|
674
699
|
${messages.SKILL_INSTALL_ALREADY(r.name,r.version)}`):(logSuccess(messages.SKILL_INSTALL_SUCCESS(r.name,r.version,r.path)),installedFresh=!0);installedFresh&&logInfo(`
|
|
675
700
|
${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(`
|
|
676
|
-
${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','brevo app create --name "My App" --ui-app --record-page contactDetails --placement contactDetails.header.menu --label "Open in Acme" --url https://example.com/open --json','brevo app create --name "My App" --ui-config ./ui-app.json --json'],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:"--ui-config <file>",description:"Create an actionLink UI app from a JSON file (non-interactive; see --help)"},{flags:"--ui-app",description:"Create an actionLink UI app from flags below"},{flags:"--record-page <slug>",description:"UI app record page (with --ui-app)"},{flags:"--placement <surface_point_name>",description:"UI app placement slot (with --ui-app)"},{flags:"--label <text>",description:"UI app menu/card label, max 48 chars (with --ui-app)"},{flags:"--more-info <text>",description:"UI app supporting text, max 255 chars, optional (with --ui-app)"},{flags:"--url <url>",description:"UI app destination URL (with --ui-app)"},{flags:"--json",description:"Output as JSON"}],handler:opts=>createCommand({name:opts.name,distribution:opts.distribution,redirectUri:opts.redirectUri,logoUri:opts.logoUri,uiConfig:opts.uiConfig,uiApp:!!opts.uiApp,recordPage:opts.recordPage,placement:opts.placement,label:opts.label,moreInfo:opts.moreInfo,url:opts.url,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(`
|
|
701
|
+
${messages.SKILL_UNINSTALL_NONE}`);return}for(let r of results)logSuccess(messages.SKILL_UNINSTALL_SUCCESS(r.name,r.path))});function activeBadge(){return color("32","\u25CF active")}function inactiveBadge(){return color("90","\u25CB inactive")}function truncate(text,max){return!text||text.length<=max?text||"":text.slice(0,max-1)+"\u2026"}var listFunctionCommand=withCommandHandler(async options=>options.draft?listDraftFunctions(options):listPublishedFunctions(options));async function listPublishedFunctions(options){let spinner=createSpinner("Fetching Brevo Functions...",{silent:options.json}),response;try{response=await functionService.fetchFunctionList()}finally{spinner.stop()}let functions=response.functions??[];if(options.json){jsonOutput(response);return}if(functions.length===0){logInfo(`
|
|
702
|
+
${messages.FUNCTION_LIST_EMPTY}
|
|
703
|
+
`);return}logInfo(`
|
|
704
|
+
${messages.FUNCTION_LIST_HEADER}`),process.stdout.write(` ${color("90","\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")}
|
|
705
|
+
|
|
706
|
+
`);for(let fn of functions){let idLabel=`(${fn.id})`;process.stdout.write(` ${color("1",fn.name)} ${color("90",idLabel)}
|
|
707
|
+
`),process.stdout.write(` Status: ${fn.is_active?activeBadge():inactiveBadge()}
|
|
708
|
+
`),fn.description&&process.stdout.write(` Description: ${color("90",truncate(fn.description,60))}
|
|
709
|
+
`),process.stdout.write(` Formula: ${fn.formula}
|
|
710
|
+
`),process.stdout.write(`
|
|
711
|
+
`)}let usageLabel=`${response.total} of ${response.max} functions used`;process.stdout.write(` ${color("90",usageLabel)}
|
|
712
|
+
|
|
713
|
+
`)}async function listDraftFunctions(options){let spinner=createSpinner("Fetching draft Brevo Functions...",{silent:options.json}),response;try{response=await functionService.fetchDraftFunctionList()}finally{spinner.stop()}let drafts=response.drafts??[];if(options.json){jsonOutput(response);return}if(drafts.length===0){logInfo(`
|
|
714
|
+
${messages.FUNCTION_LIST_DRAFT_EMPTY}
|
|
715
|
+
`);return}logInfo(`
|
|
716
|
+
${messages.FUNCTION_LIST_DRAFT_HEADER}
|
|
717
|
+
`);for(let fn of drafts)process.stdout.write(` ${fn.id}
|
|
718
|
+
`),process.stdout.write(` Description: ${fn.description}
|
|
719
|
+
`),process.stdout.write(` Formula: ${fn.formula}
|
|
720
|
+
`),process.stdout.write(` Created: ${fn.created_at}
|
|
721
|
+
`),process.stdout.write(` Expires: ${fn.expires_at}
|
|
722
|
+
`),process.stdout.write(`
|
|
723
|
+
`);process.stdout.write(` Total: ${response.total}
|
|
724
|
+
|
|
725
|
+
`)}var import_inquirer16=__toESM(require("inquirer"));function assertFunctionSelectionAllowed(command,jsonMode){if(jsonMode||!process.stdin.isTTY)throw new CliError(messages.FUNCTION_SELECT_NON_INTERACTIVE(command))}async function promptFunctionSelection(promptMessage){let spinner=createSpinner("Fetching functions..."),list;try{list=await functionService.fetchFunctionList()}finally{spinner.stop()}let functions=list.functions||[];if(functions.length===0)throw new CliError(messages.FUNCTION_LIST_EMPTY);let{selected}=await import_inquirer16.default.prompt([{type:"list",name:"selected",message:promptMessage,pageSize:15,choices:indentChoices(functions.map(fn=>{let status=fn.is_active?color("32","\u25CF active"):color("90","\u25CB inactive");return{name:`${fn.name} ${status}`,value:fn.id}}))}]),matched=functions.find(fn=>fn.id===selected);return{functionId:selected,functionName:matched?.name||selected}}async function resolveFunctionId(commandName,selectPrompt,options){return options.id?options.id:(assertFunctionSelectionAllowed(commandName,options.json),(await promptFunctionSelection(selectPrompt)).functionId)}async function withNotFoundHandling(fn,opts){let spinner=createSpinner(opts.spinnerText,{silent:opts.json});try{return await fn()}catch(err){if(err instanceof ApiError&&err.statusCode===404){if(spinner.stop(),opts.json){jsonOutput({error:"not_found",message:opts.notFoundMessage});return}logWarn(`
|
|
726
|
+
${opts.notFoundMessage}
|
|
727
|
+
`);return}throw err}finally{spinner.stop()}}async function executeFunctionAction(config,options){let functionId=await resolveFunctionId(config.commandName,config.messages.selectPrompt,options),SUCCESS=Symbol("success");if(await withNotFoundHandling(async()=>(await config.execute(functionId),SUCCESS),{spinnerText:config.messages.spinnerText,json:options.json,notFoundMessage:config.messages.notFound(functionId)})!==void 0){if(options.json){jsonOutput({[config.jsonSuccessKey]:!0,id:functionId});return}printStatusCard(config.messages.cardTitle,config.messages.cardLabel,config.messages.cardMessage(functionId),config.cardTone)}}function buildFunctionActionCommand(config){return withCommandHandler(async options=>{await executeFunctionAction(config,options)})}var getFunctionCommand=withCommandHandler(async options=>{let functionId=await resolveFunctionId(CLI.FUNCTION_GET,messages.FUNCTION_GET_SELECT,options),fn=await withNotFoundHandling(()=>functionService.fetchFunction(functionId),{spinnerText:"Fetching Brevo Function...",json:options.json,notFoundMessage:messages.FUNCTION_GET_NOT_FOUND(functionId)});if(!fn)return;if(options.json){jsonOutput(fn);return}let statusText=fn.is_active?color("32","\u25CF active"):color("90","\u25CB inactive");logInfo(`
|
|
728
|
+
${messages.FUNCTION_GET_HEADER}`),process.stdout.write(` ${color("90","\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")}
|
|
729
|
+
|
|
730
|
+
`),process.stdout.write(` Name: ${color("1",fn.name)}
|
|
731
|
+
`),process.stdout.write(` ID: ${fn.id}
|
|
732
|
+
`),process.stdout.write(` Status: ${statusText}
|
|
733
|
+
`),process.stdout.write(` Description: ${fn.description}
|
|
734
|
+
`),process.stdout.write(` Explanation: ${fn.explanation}
|
|
735
|
+
`),process.stdout.write(` Formula: ${fn.formula}
|
|
736
|
+
`),fn.category&&process.stdout.write(` Category: ${fn.category}
|
|
737
|
+
`),process.stdout.write(` Version: ${fn.version}
|
|
738
|
+
`),process.stdout.write(` Created: ${color("90",fn.created_at)}
|
|
739
|
+
`),process.stdout.write(` Updated: ${color("90",fn.updated_at)}
|
|
740
|
+
`),fn.last_recalculated_at&&process.stdout.write(` Recalculated: ${color("90",fn.last_recalculated_at)}
|
|
741
|
+
`),process.stdout.write(`
|
|
742
|
+
`)});var activateFunctionCommand=buildFunctionActionCommand({commandName:CLI.FUNCTION_ACTIVATE,jsonSuccessKey:"activated",cardTone:"success",execute:id=>functionService.activateFunction(id),messages:{selectPrompt:messages.FUNCTION_ACTIVATE_SELECT,notFound:messages.FUNCTION_ACTIVATE_NOT_FOUND,spinnerText:"Activating Brevo Function...",cardTitle:messages.FUNCTION_ACTIVATE_CARD_TITLE,cardLabel:messages.FUNCTION_ACTIVATE_CARD_LABEL,cardMessage:messages.FUNCTION_ACTIVATE_CARD_MESSAGE}});var deactivateFunctionCommand=buildFunctionActionCommand({commandName:CLI.FUNCTION_DEACTIVATE,jsonSuccessKey:"deactivated",cardTone:"warn",execute:id=>functionService.deactivateFunction(id),messages:{selectPrompt:messages.FUNCTION_DEACTIVATE_SELECT,notFound:messages.FUNCTION_DEACTIVATE_NOT_FOUND,spinnerText:"Deactivating Brevo Function...",cardTitle:messages.FUNCTION_DEACTIVATE_CARD_TITLE,cardLabel:messages.FUNCTION_DEACTIVATE_CARD_LABEL,cardMessage:messages.FUNCTION_DEACTIVATE_CARD_MESSAGE}});var import_inquirer17=__toESM(require("inquirer"));var DELETE_ACTION_CONFIG={commandName:CLI.FUNCTION_DELETE,jsonSuccessKey:"deleted",cardTone:"error",execute:id=>functionService.deleteFunction(id),messages:{selectPrompt:messages.FUNCTION_DELETE_SELECT,notFound:messages.FUNCTION_DELETE_NOT_FOUND,spinnerText:"Deleting Brevo Function...",cardTitle:messages.FUNCTION_DELETE_CARD_TITLE,cardLabel:messages.FUNCTION_DELETE_CARD_LABEL,cardMessage:messages.FUNCTION_DELETE_CARD_MESSAGE}},deleteFunctionCommand=withCommandHandler(async options=>{let functionId=await resolveFunctionId(CLI.FUNCTION_DELETE,messages.FUNCTION_DELETE_SELECT,options);if(!options.force&&!options.json){let{confirmed}=await import_inquirer17.default.prompt([{type:"confirm",name:"confirmed",message:messages.FUNCTION_DELETE_CONFIRM(functionId),default:!1}]);if(!confirmed){logInfo(`
|
|
743
|
+
${messages.FUNCTION_DELETE_CANCELLED}
|
|
744
|
+
`);return}}await executeFunctionAction(DELETE_ACTION_CONFIG,{id:functionId,json:options.json})});var import_inquirer20=__toESM(require("inquirer"));var PREVIEW_EXCLUDED_KEYS=new Set(["organization_id","attribute_id","__error"]);function formatCellValue(value){return value==null?"":typeof value=="string"?value:typeof value=="number"||typeof value=="boolean"?`${value}`:JSON.stringify(value)}function printResultsTable(rows){if(rows.length===0)return;let seen=new Set;for(let row of rows)for(let key of Object.keys(row))seen.add(key);let cols=[...seen].filter(k=>!PREVIEW_EXCLUDED_KEYS.has(k));if(cols.length===0)return;let widths=cols.map(col=>Math.max(col.length,...rows.map(r=>formatCellValue(r[col]).length))),gutter=" ";process.stdout.write(`
|
|
745
|
+
${cols.map((c,i)=>c.padEnd(widths[i])).join(gutter)}
|
|
746
|
+
`),process.stdout.write(` ${widths.map(w=>"-".repeat(w)).join(gutter)}
|
|
747
|
+
`);for(let row of rows)process.stdout.write(` ${cols.map((c,i)=>formatCellValue(row[c]).padEnd(widths[i])).join(gutter)}
|
|
748
|
+
`);process.stdout.write(`
|
|
749
|
+
`)}function hasPreviewErrors(rows){return rows.some(r=>"__error"in r)}function deriveAttributeId(name){return name.trim().replace(/[^a-zA-Z0-9]+/g,"_").replace(/^_|_$/g,"").toUpperCase()}var import_inquirer18=__toESM(require("inquirer"));async function selectFunctionApp(promptMessage,emptyMessage){let spinner=createSpinner("Fetching apps..."),apps;try{apps=await appService.fetchAppsList({type:"brevo_function"})}finally{spinner.stop()}if(apps.length===0)throw new CliError(emptyMessage);let{selected}=await import_inquirer18.default.prompt([{type:"list",name:"selected",message:promptMessage,pageSize:15,choices:indentChoices(apps.map(a=>({name:`${a.name||`App ${a.app_id}`} (ID: ${a.app_id})`,value:a.app_id})))}]);return selected}async function tryLinkFunctionToApp(appId,functionId,opts){let spinner=createSpinner(messages.FUNCTION_DEPLOY_LINKING,{silent:opts?.silent});try{return await functionService.linkFunctionToApp({app_id:appId,function_id:functionId}),!0}catch(err){return logDebug("linkFunctionToApp",err),opts?.silent||logInfo(` ${color("33",messages.FUNCTION_DEPLOY_LINK_ERROR)}`),!1}finally{spinner.stop()}}var import_inquirer19=__toESM(require("inquirer"));function isDuplicateNameError(err){return err instanceof ApiError&&err.statusCode===409}async function executePreview(templateArgs,msgs){let contactSpinner=createSpinner(msgs.fetchingContacts),contactData;try{contactData=await functionService.fetchContacts()}finally{contactSpinner.stop()}let previewSpinner=createSpinner(msgs.executingPreview),executeResponse;try{executeResponse=await functionService.executeTemplate({...templateArgs,contact_data:contactData.contacts})}finally{previewSpinner.stop()}let results=executeResponse.result||[];if(hasPreviewErrors(results))throw new CliError(msgs.previewFailed);logInfo(`
|
|
750
|
+
${msgs.previewHeader}`),msgs.afterHeader&&process.stdout.write(msgs.afterHeader),printResultsTable(results)}async function tryPreview(templateArgs,msgs){if(!(!templateArgs.draft_id&&!templateArgs.template_id))try{await executePreview(templateArgs,msgs)}catch(err){if(err instanceof CliError)throw err;logInfo(` ${color("33",msgs.previewError)}`)}}async function nameConfirmDeployLoop(args2){let defaultName=args2.defaultName||"",{msgs}=args2;for(;;){let{functionName}=await import_inquirer19.default.prompt([{type:"input",name:"functionName",message:msgs.namePrompt,default:defaultName||void 0,validate:v=>v.trim()?!0:msgs.nameRequired}]);logInfo(`
|
|
751
|
+
${msgs.warning}
|
|
752
|
+
`);let{confirmDeploy}=await import_inquirer19.default.prompt([{type:"confirm",name:"confirmDeploy",message:msgs.confirmPrompt,default:!1}]);if(!confirmDeploy){logInfo(msgs.cancelled);return}let spinner=createSpinner(msgs.spinner);try{let created=await args2.createFn(functionName.trim());spinner.stop(),await tryLinkFunctionToApp(args2.appId,created.id),printBox(msgs.boxTitle,[`${messages.FUNCTION_LABEL_NAME} ${created.name}`,msgs.boxId(created.id)]);return}catch(err){if(spinner.stop(),isDuplicateNameError(err)){logInfo(msgs.nameExists),defaultName=functionName.trim();continue}throw err}}}var STAGE_LABELS={enriching:{label:messages.FUNCTION_INIT_STAGE_ENRICHING,colorCode:"36"},planning_agent:{label:messages.FUNCTION_INIT_STAGE_PLANNING,colorCode:"33"},executing_agent:{label:messages.FUNCTION_INIT_STAGE_GENERATING,colorCode:"35"},validating:{label:messages.FUNCTION_INIT_STAGE_VALIDATING,colorCode:"32"}};function updateSpinnerFromEvent(parsed,spinner){let stage=parsed.value?.stage,info=stage?STAGE_LABELS[stage]:void 0;info?spinner.update(color(info.colorCode,info.label)):parsed.value?.message&&spinner.update(parsed.value.message)}function accumulateResult(result,r){r.code&&(result.code=r.code),r.name&&(result.name=r.name),r.draft_id&&(result.draftId=r.draft_id),r.session_id&&(result.sessionId=r.session_id),r.category&&(result.category=r.category),r.description&&(result.description=r.description),r.explanation&&(result.explanation=r.explanation)}async function processGenerateStream(stream,spinner){let result={code:""};for await(let event of stream){let parsed;try{parsed=JSON.parse(event.data)}catch{continue}if(parsed.error)throw new CliError(parsed.error);updateSpinnerFromEvent(parsed,spinner),parsed.result&&accumulateResult(result,parsed.result)}if(!result.code)throw new CliError(messages.FUNCTION_INIT_GENERATION_FAILED);return result}function mergeGenerateResult(base,update){return{code:update.code||base.code,name:update.name||base.name,draftId:update.draftId||base.draftId,sessionId:update.sessionId||base.sessionId,category:update.category||base.category,description:update.description||base.description,explanation:update.explanation||base.explanation}}var PREVIEW_MSGS={fetchingContacts:messages.FUNCTION_INIT_FETCHING_CONTACTS,executingPreview:messages.FUNCTION_INIT_EXECUTING_PREVIEW,previewHeader:messages.FUNCTION_INIT_PREVIEW_HEADER,previewError:messages.FUNCTION_INIT_PREVIEW_ERROR,previewFailed:messages.FUNCTION_PREVIEW_EXECUTE_FAILED},DEPLOY_MSGS={namePrompt:messages.FUNCTION_INIT_NAME_PROMPT,nameRequired:messages.FUNCTION_INIT_NAME_REQUIRED,warning:messages.FUNCTION_INIT_DEPLOY_WARNING,confirmPrompt:messages.FUNCTION_INIT_DEPLOY_PROMPT,cancelled:messages.FUNCTION_INIT_DEPLOY_CANCELLED,spinner:messages.FUNCTION_INIT_SAVE_SPINNER,nameExists:messages.FUNCTION_INIT_NAME_EXISTS,boxTitle:messages.FUNCTION_INIT_BOX_TITLE,boxId:messages.FUNCTION_INIT_BOX_ID};async function saveGeneratedFunction(args2){await nameConfirmDeployLoop({appId:args2.appId,defaultName:args2.name,msgs:DEPLOY_MSGS,createFn:name=>functionService.createFunction({source:"cli",name,code:args2.code,category:args2.category,description:args2.description,explanation:args2.explanation,app_id:args2.appId,draft_id:args2.draftId,attribute_id:deriveAttributeId(name)})})}async function runIterateRound(current,chatHistory){let{iterateDescription}=await import_inquirer20.default.prompt([{type:"input",name:"iterateDescription",message:messages.FUNCTION_INIT_ITERATE_DESCRIPTION,validate:v=>v.trim()?!0:messages.FUNCTION_INIT_DESCRIPTION_REQUIRED}]);if(!current.draftId)throw new CliError(messages.FUNCTION_INIT_GENERATION_FAILED);let iterateSpinner=createSpinner(messages.FUNCTION_INIT_ITERATING);try{let iterateStream=functionService.iterateStream(sseDeps,{draft_function_id:current.draftId,user_prompt:iterateDescription.trim(),previous_code:current.code,chat_history:chatHistory,source:"cli"}),iterateResult=await processGenerateStream(iterateStream,iterateSpinner);return iterateSpinner.stop(),chatHistory.push({role:"user",content:iterateDescription.trim()},{role:"assistant",content:iterateResult.code}),mergeGenerateResult(current,iterateResult)}catch(err){if(iterateSpinner.stop(),err instanceof ApiError||err instanceof CliError)throw err;return logInfo(` ${color("31",messages.FUNCTION_INIT_ITERATE_ERROR)}`),null}}async function aiGenerationFlow(appId){let{description}=await import_inquirer20.default.prompt([{type:"input",name:"description",message:messages.FUNCTION_INIT_DESCRIPTION_PROMPT,validate:v=>v.trim()?!0:messages.FUNCTION_INIT_DESCRIPTION_REQUIRED}]),chatHistory=[],genSpinner=createSpinner(messages.FUNCTION_INIT_GENERATING),result;try{let stream=functionService.generateStream(sseDeps,{user_prompt:description.trim(),source:"cli"});result=await processGenerateStream(stream,genSpinner)}catch(err){throw genSpinner.stop(),err instanceof ApiError||err instanceof CliError?err:new CliError(messages.FUNCTION_INIT_GENERATION_ERROR)}genSpinner.stop(),chatHistory.push({role:"user",content:description.trim()},{role:"assistant",content:result.code}),await tryPreview({draft_id:result.draftId},PREVIEW_MSGS);let current={...result};for(;;){let{action}=await import_inquirer20.default.prompt([{type:"list",name:"action",message:messages.FUNCTION_INIT_ITERATE_PROMPT,choices:indentChoices([{name:messages.FUNCTION_INIT_ITERATE_UPDATE,value:"update"},{name:messages.FUNCTION_INIT_ITERATE_SAVE,value:"save"}])}]);if(action==="save"){await saveGeneratedFunction({appId,code:current.code,draftId:current.draftId,name:current.name,category:current.category,description:current.description,explanation:current.explanation});break}let updated=await runIterateRound(current,chatHistory);updated&&(current=updated,await tryPreview({draft_id:current.draftId},PREVIEW_MSGS))}}async function templateFlow(appId){let templateSpinner=createSpinner("Fetching templates..."),templates;try{templates=await functionService.fetchTemplates()}finally{templateSpinner.stop()}if(!templates||templates.length===0)throw new CliError(messages.FUNCTION_INIT_NO_TEMPLATES);let{templateId}=await import_inquirer20.default.prompt([{type:"list",name:"templateId",message:messages.FUNCTION_INIT_TEMPLATE_PROMPT,pageSize:15,choices:indentChoices(templates.map(t=>({name:`${t.name} \u2014 ${t.description}`,value:t.id})))}]),template=templates.find(t=>t.id===templateId);await executePreview({template_id:template.id},{...PREVIEW_MSGS,afterHeader:`
|
|
753
|
+
${messages.FUNCTION_LABEL_DESCRIPTION} ${template.description}
|
|
754
|
+
`}),await nameConfirmDeployLoop({appId,defaultName:template.name,msgs:{...DEPLOY_MSGS,spinner:messages.FUNCTION_INIT_CREATING_FROM_TEMPLATE},createFn:name=>functionService.createFromTemplate({global_function_id:template.id,name,description:template.description,category:template.category||"",attribute_id:deriveAttributeId(name),source:"cli"})})}var initFunctionCommand=withCommandHandler(async options=>{if(options.json||!process.stdin.isTTY)throw new CliError(messages.FUNCTION_INIT_NON_INTERACTIVE);let appId=await selectFunctionApp(messages.FUNCTION_INIT_SELECT_APP,messages.FUNCTION_INIT_NO_APPS),{method}=await import_inquirer20.default.prompt([{type:"list",name:"method",message:messages.FUNCTION_INIT_METHOD_PROMPT,choices:indentChoices([{name:messages.FUNCTION_INIT_METHOD_AI,value:"ai"},{name:messages.FUNCTION_INIT_METHOD_TEMPLATE,value:"template"}])}]);method==="ai"?await aiGenerationFlow(appId):await templateFlow(appId)});var import_inquirer21=__toESM(require("inquirer"));function assertInteractiveTerminal(){if(!process.stdin.isTTY)throw new CliError(messages.FUNCTION_DEPLOY_NON_INTERACTIVE)}async function promptDraftFunctionSelection(){let spinner=createSpinner("Fetching drafts..."),list;try{list=await functionService.fetchDraftFunctionList()}finally{spinner.stop()}let drafts=list.drafts||[];if(drafts.length===0)throw new CliError(messages.FUNCTION_DEPLOY_NO_DRAFTS);let{selected}=await import_inquirer21.default.prompt([{type:"list",name:"selected",message:messages.FUNCTION_DEPLOY_SELECT,pageSize:15,choices:indentChoices(drafts.map(d=>({name:`${d.id} \u2014 ${d.description||"(no description)"}`,value:d.id})))}]);return drafts.find(d=>d.id===selected)}async function fetchDraftById(id){let draft=((await functionService.fetchDraftFunctionList()).drafts||[]).find(d=>d.id===id);if(!draft)throw new CliError(messages.FUNCTION_DEPLOY_NOT_FOUND(id));return draft}var PREVIEW_MSGS2={fetchingContacts:messages.FUNCTION_DEPLOY_FETCHING_CONTACTS,executingPreview:messages.FUNCTION_DEPLOY_EXECUTING_PREVIEW,previewHeader:messages.FUNCTION_DEPLOY_PREVIEW_HEADER,previewError:messages.FUNCTION_DEPLOY_PREVIEW_ERROR,previewFailed:messages.FUNCTION_PREVIEW_EXECUTE_FAILED};function deriveNameFromDescription(description){let trimmed=description.trim();if(!trimmed)return messages.FUNCTION_DEFAULT_NAME;if(trimmed.length<=50)return trimmed;let lastSpace=trimmed.lastIndexOf(" ",50);return lastSpace>0?trimmed.slice(0,lastSpace):trimmed.slice(0,50)}async function deployJsonMode(draft,appId){let name=deriveNameFromDescription(draft.description||""),deploySpinner=createSpinner(messages.FUNCTION_DEPLOY_SPINNER,{silent:!0});try{let created=await functionService.createFunction({source:"cli",name,code:draft.formula,description:draft.description,explanation:draft.explanation,draft_id:draft.id,attribute_id:deriveAttributeId(name)});deploySpinner.stop();let linked=!1;appId&&(linked=await tryLinkFunctionToApp(appId,created.id,{silent:!0})),jsonOutput({deployed:!0,id:created.id,name:created.name,version:created.version,linked,...appId?{app_id:appId}:{}})}catch(err){throw deploySpinner.stop(),err}}var deployFunctionCommand=withCommandHandler(async options=>{let draft;if(options.id){let spinner=createSpinner("Fetching draft...",{silent:options.json});try{draft=await fetchDraftById(options.id)}finally{spinner.stop()}}else assertInteractiveTerminal(),draft=await promptDraftFunctionSelection();options.json||await tryPreview({draft_id:draft.id},PREVIEW_MSGS2);let appId=options.appId;!appId&&!options.json&&(appId=await selectFunctionApp(messages.FUNCTION_DEPLOY_SELECT_APP,messages.FUNCTION_DEPLOY_NO_APPS)),options.json?await deployJsonMode(draft,appId):await nameConfirmDeployLoop({appId,msgs:{namePrompt:messages.FUNCTION_DEPLOY_NAME_PROMPT,nameRequired:messages.FUNCTION_DEPLOY_NAME_REQUIRED,warning:messages.FUNCTION_DEPLOY_WARNING,confirmPrompt:messages.FUNCTION_DEPLOY_CONFIRM,cancelled:messages.FUNCTION_DEPLOY_CANCELLED,spinner:messages.FUNCTION_DEPLOY_SPINNER,nameExists:messages.FUNCTION_DEPLOY_NAME_EXISTS,boxTitle:messages.FUNCTION_DEPLOY_BOX_TITLE,boxId:messages.FUNCTION_DEPLOY_BOX_ID},createFn:name=>functionService.createFunction({source:"cli",name,code:draft.formula,description:draft.description,explanation:draft.explanation,draft_id:draft.id,attribute_id:deriveAttributeId(name)})})});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','brevo app create --name "My App" --ui-app --record-page contactDetails --placement contactDetails.header.menu --label "Open in Acme" --url https://example.com/open --json','brevo app create --name "My App" --ui-config ./ui-app.json --json'],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:"--ui-config <file>",description:"Create an actionLink UI app from a JSON file (non-interactive; see --help)"},{flags:"--ui-app",description:"Create an actionLink UI app from flags below"},{flags:"--record-page <slug>",description:"UI app record page (with --ui-app)"},{flags:"--placement <surface_point_name>",description:"UI app placement slot (with --ui-app)"},{flags:"--label <text>",description:"UI app menu/card label, max 48 chars (with --ui-app)"},{flags:"--more-info <text>",description:"UI app supporting text, max 255 chars, optional (with --ui-app)"},{flags:"--url <url>",description:"UI app destination URL (with --ui-app)"},{flags:"--json",description:"Output as JSON"}],handler:opts=>createCommand({name:opts.name,distribution:opts.distribution,redirectUri:opts.redirectUri,logoUri:opts.logoUri,uiConfig:opts.uiConfig,uiApp:!!opts.uiApp,recordPage:opts.recordPage,placement:opts.placement,label:opts.label,moreInfo:opts.moreInfo,url:opts.url,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})}]},functionCommandGroup={name:"function",aliases:["fn"],description:"Manage Brevo Functions",commands:[{name:"list",description:"List all Brevo Functions in your account",examples:["brevo function list","brevo function list --draft","brevo function list --json"],options:[{flags:"--draft",description:"List only draft functions"},{flags:"--json",description:"Output as JSON"}],handler:opts=>listFunctionCommand({json:!!opts.json,draft:!!opts.draft})},{name:"get",description:"Show details of a Brevo Function",examples:["brevo function get","brevo function get --id fn-001","brevo function get --id fn-001 --json"],options:[{flags:"--id <id>",description:"Function ID (shows a picker if omitted)"},{flags:"--json",description:"Output as JSON"}],handler:opts=>getFunctionCommand({id:opts.id,json:!!opts.json})},{name:"activate",description:"Activate a Brevo Function",examples:["brevo function activate","brevo function activate --id fn-001","brevo function activate --id fn-001 --json"],options:[{flags:"--id <id>",description:"Function ID (shows a picker if omitted)"},{flags:"--json",description:"Output as JSON"}],handler:opts=>activateFunctionCommand({id:opts.id,json:!!opts.json})},{name:"deactivate",description:"Deactivate a Brevo Function",examples:["brevo function deactivate","brevo function deactivate --id fn-001","brevo function deactivate --id fn-001 --json"],options:[{flags:"--id <id>",description:"Function ID (shows a picker if omitted)"},{flags:"--json",description:"Output as JSON"}],handler:opts=>deactivateFunctionCommand({id:opts.id,json:!!opts.json})},{name:"delete",description:"Delete a deployed Brevo Function",examples:["brevo function delete","brevo function delete --id fn-001","brevo function delete --id fn-001 --force","brevo function delete --id fn-001 --json"],options:[{flags:"--id <id>",description:"Function ID (shows a picker if omitted)"},{flags:"--force",description:"Skip confirmation"},{flags:"--json",description:"Output as JSON"}],handler:opts=>deleteFunctionCommand({id:opts.id,force:!!opts.force,json:!!opts.json})},{name:"init",description:"Create a new Brevo Function",examples:["brevo function init","brevo fn init"],options:[{flags:"--json",description:"Output as JSON"}],handler:opts=>initFunctionCommand({json:!!opts.json})},{name:"deploy",description:"Deploy a draft Brevo Function",examples:["brevo function deploy","brevo function deploy --id draft-001","brevo function deploy --id draft-001 --app-id my-app-id --json"],options:[{flags:"--id <id>",description:"Draft ID (shows a picker if omitted)"},{flags:"--app-id <id>",description:"App to link the deployed function to",parser:v=>parseAppId(v)},{flags:"--json",description:"Output as JSON"}],handler:opts=>deployFunctionCommand({id:opts.id,appId:opts.appId,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(`
|
|
677
755
|
`)}function withNotice(box,serverMessage){let line=serverMessage??messages.CLI_VERSION_NOTICE_FALLBACK;return`
|
|
678
756
|
${color(COLOR_RED,line)}
|
|
679
757
|
${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)+`
|
|
680
758
|
`)))}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)+`
|
|
681
759
|
`),!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]}'
|
|
682
|
-
`),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,
|
|
760
|
+
`),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)});var commandGroups=[appCommandGroup,skillCommandGroup,functionCommandGroup];registerAll(program,topLevelCommands,commandGroups);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}
|
|
683
761
|
`);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(`
|
|
684
762
|
Received ${signal}, shutting down.
|
|
685
763
|
`),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)+`
|