@getbrevo/cli 2.2.0 → 2.2.2

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @getbrevo/cli
2
2
 
3
+ ## 2.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 820b67e: `brevo app create` can now create an `actionLink` UI app non-interactively, either via `--ui-config <file>` (a JSON file: `{ extension_type, record_page, surface_point_name, label, more_info?, redirect_link }`) or via `--ui-app --record-page <slug> --placement <surface_point_name> --label <text> --url <url> [--more-info <text>]`. Previously a non-interactive run (`--json`, piped stdin, or a script) always created an OAuth app instead, with no error, even when a UI app was intended. Both new paths reuse the interactive wizard's own registry validation and reject an unknown `--record-page`/`--placement` with the valid options listed in the error. Only `extension_type: "actionLink"` is supported non-interactively today; `iframeExtension`/`legacyComponent` are refused with a clear message. A plain non-interactive run with neither flag still creates an OAuth app, unchanged.
8
+
9
+ ## 2.2.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 6a840d0: `brevo app create` seeds each authored placement's `size` from the slot's registry default (`default_size` on `GET /v3/app-store/surface-points`, BEX-461), the same way `context` is seeded from `default_context_field`: written explicitly into `app-config.json`, where it can be edited or removed — the entry's own value is what uploads. A slot with no declared default writes no `size` key, and a blank or malformed served default degrades to "no seed" rather than authoring a value the CLI's own validation would refuse. There is still no size prompt.
14
+
3
15
  ## 2.2.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -108,6 +108,34 @@ Most commands require a successful `brevo login` first, except authentication/he
108
108
 
109
109
  The table above is the complete command surface of a published release. Features that aren't live on the Brevo platform yet aren't built into the package — `brevo --help` always lists everything the binary can do, so there is nothing hidden behind a flag or an environment variable.
110
110
 
111
+ ### UI apps
112
+
113
+ `brevo app create`'s interactive prompt can build two kinds of app: an OAuth app, or a **UI app**
114
+ that renders directly inside a Brevo CRM record (interactive-only — there is no `--type` flag, so
115
+ `--json` and piped runs always create an OAuth app).
116
+
117
+ Today the prompt authors one integration type, an **action link** (`extension_type: "actionLink"`).
118
+ In short: it's a clickable menu entry or card CTA button that Brevo renders on a record page — no
119
+ embed, no iframe — and clicking it just opens a URL you host, with the record's data passed along
120
+ as query parameters, never in the path. Each authored placement lives in `app-config.json` under
121
+ `ui_app.surface_point_list` and carries:
122
+
123
+ - `surface_point_name` — which slot on which record page, chosen from Brevo's live registry at
124
+ create time
125
+ - `label` — the menu entry's text, or the card's CTA button
126
+ - `more_info` *(optional)* — a supporting line under the menu entry / card description
127
+ - `redirect_link` — the destination URL
128
+ - `context` *(optional)* — which record fields to pass along as query parameters, narrowed from
129
+ whatever that slot allows
130
+ - `size` *(optional)* — card sizing, e.g. `{ "width": "280px", "height": "160px" }`; seeded from
131
+ the slot's own registry default when it declares one, and freely editable afterwards
132
+
133
+ The interactive flow authors exactly one placement per run. More placements — or edits to any
134
+ field above — are hand-added as further `surface_point_list` entries in `app-config.json` and
135
+ pushed with `brevo app upload`, which validates every entry against the registry before it goes
136
+ live. See [Uploading a UI app that is already installed](#uploading-a-ui-app-that-is-already-installed)
137
+ below for what that push looks like.
138
+
111
139
  ### Uploading a UI app that is already installed
112
140
 
113
141
  A UI app's `ui_app` block is what every account it is installed in renders, and there is no
@@ -143,6 +171,7 @@ Environment overrides:
143
171
 
144
172
  - `BREVO_API_URL` — points the CLI at a different Brevo API (defaults to `https://api.brevo.com`).
145
173
  - `BREVO_OAUTH_PROXY_URL` — points the browser-login flow at a different OAuth proxy (defaults to `https://oauth-cli.brevo.com`; useful for local development or non-default environments).
174
+ - `BREVO_OAUTH_BASE_URL` — points scope lookups and scaffolded project templates at a different OAuth realm (defaults to `https://oauth.brevo.com`).
146
175
 
147
176
  ## Exit codes
148
177
 
@@ -162,6 +191,7 @@ Environment overrides:
162
191
  | `BREVO_API_KEY` | API key used for non-interactive `brevo login` | – |
163
192
  | `BREVO_API_URL` | API base URL (HTTPS required, except for `localhost`) | `https://api.brevo.com` |
164
193
  | `BREVO_OAUTH_PROXY_URL` | OAuth proxy used by browser login (HTTPS required, except for `localhost`) | `https://oauth-cli.brevo.com` |
194
+ | `BREVO_OAUTH_BASE_URL` | OAuth realm used for scope lookups and scaffolded project templates (HTTPS required, except for `localhost`) | `https://oauth.brevo.com` |
165
195
  | `BREVO_CONFIG_HOME` | Override for the credentials directory | `~/.brevo/` |
166
196
  | `BREVO_NO_SKILL_AUTOREFRESH` | Set to `1` to suppress automatic skill refresh on `brevo` runs | off |
167
197
  | `NO_COLOR` / `FORCE_COLOR` | Disable / force ANSI colour output | – |
@@ -77,7 +77,7 @@ Don't fall back to raw HTTP against `api.brevo.com` — the `brevo` binary is th
77
77
  | `brevo whoami` | Show the authenticated account (`--json`) |
78
78
  | `brevo app init` | Guided setup (login, create, scaffold) |
79
79
  | `brevo app list` | List apps (`--json`). Each row names its type. |
80
- | `brevo app create` | Create an app (`--name`, `--distribution private`, `--redirect-uri`, `--logo-uri`, `--json`). **Pass `--distribution private`** — run `brevo app create --help` for the values this build accepts and don't pass one it doesn't list. Defaults to scopes `contacts:read`, `contacts:write`, `crm:read`, `crm:write`. Interactively it asks for the name before the OAuth prompts, and asks the **app type** (OAuth vs UI app) as the last question before the flow splits — there is no `--type` flag, so a UI app is only reachable interactively and every non-interactive run creates an OAuth app. **Errors immediately if `app-config.json` already exists in the working directory** — move elsewhere or use `brevo app scaffold` there instead. Otherwise resolves (creates/`cd`s into) a target directory, creates the app, and writes the basic project structure (`app-config.json` + `.gitignore`/`AGENTS.md`/`CLAUDE.md`/`README.md`). It scaffolds a feature (OAuth test server) only when the interactive confirm (*"Scaffold the Test OAuth App?"*, default yes) is answered yes; non-interactive runs (`--json` or piped) stay base-only — add the feature afterward with `brevo app scaffold`. |
80
+ | `brevo app create` | Create an app (`--name`, `--distribution private`, `--redirect-uri`, `--logo-uri`, `--json`). **Pass `--distribution private`** — run `brevo app create --help` for the values this build accepts and don't pass one it doesn't list. Defaults to scopes `contacts:read`, `contacts:write`, `crm:read`, `crm:write`. Interactively it asks for the name before the OAuth prompts, and asks the **app type** (OAuth vs UI app) as the last question before the flow splits — there is still no `--type` flag, but a UI app is also reachable non-interactively via `--ui-app --record-page <slug> --placement <surface_point_name> --label <text> --url <url> [--more-info <text>]`, or `--ui-config <file>` (JSON: `{ extension_type, record_page, surface_point_name, label, more_info?, redirect_link }`) — both `extension_type: "actionLink"` only today. A non-interactive run with neither flag still creates an OAuth app, as before. **Errors immediately if `app-config.json` already exists in the working directory** — move elsewhere or use `brevo app scaffold` there instead. Otherwise resolves (creates/`cd`s into) a target directory, creates the app, and writes the basic project structure (`app-config.json` + `.gitignore`/`AGENTS.md`/`CLAUDE.md`/`README.md`). It scaffolds a feature (OAuth test server) only when the interactive confirm (*"Scaffold the Test OAuth App?"*, default yes) is answered yes; non-interactive runs (`--json` or piped) stay base-only — add the feature afterward with `brevo app scaffold`. |
81
81
  | `brevo app upload` | Push `app-config.json` to Brevo (`--yes`, `--json`). No edit flags — change name/redirect URLs/scopes/logo/version by editing `app-config.json` directly, then run `upload`. **`distribution_type` is immutable** — set at `app create` time; if the local value differs from the server, `upload` errors before pushing (restore the local value, or create a new app). Always fetches the remote app first and shows a local-vs-server diff (even under `--yes`/`--json`); exits 0 with no network push if nothing differs. **For a UI app the diff covers the `ui_app` block placement by placement** — every changed value as `before → after`, added placements tagged `(new)`, dropped ones trailing `(removed)`, matched by slot slug so a reordered `surface_point_list` is not a change — and the command then warns that the app may already be installed in Brevo accounts and asks *"Proceed with upload and update every account this app is installed in?"*. There is no separate publish step: a successful upload changes what every account the app is installed in renders, immediately and with no re-install. `--yes` skips the question but still prints the warning; `--json` prints neither and stays a single parseable document. |
82
82
  | `brevo app credentials` | Show client ID / secret (`--app-id`, `--reveal-secret`, `--json`). **`--app-id` is required when scripting** — see the picker note below. **OAuth apps only** — a UI app has no OAuth credentials, so the command refuses it with exit `1` and points at `brevo app list` for the app's type. Also backfills a missing top-level `version` / `distribution_type` into cwd's `app-config.json` when its `appId` matches (fill-only-when-missing, silent). |
83
83
  | `brevo app delete` | Delete an app (`--app-id`, `--force`, `--json`). **`--app-id` is required when scripting** — see the picker note below. |
@@ -94,7 +94,7 @@ Run `brevo --help` or `brevo <command> --help` for the full set.
94
94
  ## Conventions
95
95
 
96
96
  - **Every command supports `--json`** — prefer this when parsing output programmatically. It applies to failures too: a failing `--json` run writes a single `{"error": {...}}` document to stdout (see *JSON errors* below) while the human message goes to stderr.
97
- - **Two app types, one command surface.** `app-config.json` describes either an **OAuth app** — a populated `auth` block (`auth.scopes` / `auth.redirectUris`) and no `ui_app` — or a **UI app** — a `ui_app` block and an **empty** `auth: {}` (no callbacks, scopes, or credentials). The presence of `ui_app` is the discriminator; never mix the two in one file. Both types share the top-level `appId` / `appName` / `logoUri` / `version` / `distribution_type` and the same `create`/`upload`/`list`/`delete` commands; only UI apps take `install`/`uninstall`. **A UI app has one stored configuration, shared by every account it is installed in** — `brevo app upload` is therefore how you change what an installed app renders (edit `app-config.json`, upload, done), never uninstall-then-reinstall; both commands show what is about to change for that reason. The `ui_app` block holds `extension_type` at its root (`actionLink`, `iframeExtension`, `legacyComponent` — camelCase only) and a `surface_point_list` of placement entries, each carrying `surface_point_name` (the dot-notation slug from the platform's registry, e.g. `contactDetails.header.menu` — not the `<location>.<place>.<kind>` extension-point name like `contactDetails.headerMenu.action`, which is dotted too but a different string), `label`, optional `more_info`, `redirect_link` (record context arrives as query parameters), optional `context`, optional `size` (e.g. `{ "width": "280px", "height": "160px" }` — each axis a positive-integer `px` length or `1%`–`100%` of the host slot, shrink-only, both axes optional), and — `iframeExtension` only — `modal_iframe_url`. Do **not** write `link_target` or `extension_point_name` into the file: both are wire/server-stamped (`app upload` injects `link_target` itself). Write only the keys documented here — `brevo app upload` validates the whole file and rejects anything it doesn't recognise, including the pre-GA `heading`/`subheading` names (now `label`/`more_info`, per entry). A UI-app project is **configuration only** — no feature to scaffold and no `src/oauth/`, since an action link has no local server (`brevo app scaffold` inside one says so and exits `0`; `brevo app start` does not apply) — and the base docs the scaffold writes (`AGENTS.md` / `CLAUDE.md` / `README.md`) describe whichever type the app is, so a UI app's copies cover the `ui_app` block and the `upload` → `install` flow instead of an OAuth server.
97
+ - **Two app types, one command surface.** `app-config.json` describes either an **OAuth app** — a populated `auth` block (`auth.scopes` / `auth.redirectUris`) and no `ui_app` — or a **UI app** — a `ui_app` block and an **empty** `auth: {}` (no callbacks, scopes, or credentials). The presence of `ui_app` is the discriminator; never mix the two in one file. Both types share the top-level `appId` / `appName` / `logoUri` / `version` / `distribution_type` and the same `create`/`upload`/`list`/`delete` commands; only UI apps take `install`/`uninstall`. **A UI app has one stored configuration, shared by every account it is installed in** — `brevo app upload` is therefore how you change what an installed app renders (edit `app-config.json`, upload, done), never uninstall-then-reinstall; both commands show what is about to change for that reason. The `ui_app` block holds `extension_type` at its root (`actionLink`, `iframeExtension`, `legacyComponent` — camelCase only) and a `surface_point_list` of placement entries, each carrying `surface_point_name` (the dot-notation slug from the platform's registry, e.g. `contactDetails.header.menu` — not the `<location>.<place>.<kind>` extension-point name like `contactDetails.headerMenu.action`, which is dotted too but a different string), `label`, optional `more_info`, `redirect_link` (record context arrives as query parameters), optional `context`, optional `size` (e.g. `{ "width": "280px", "height": "160px" }` — each axis a positive-integer `px` length or `1%`–`100%` of the host slot, shrink-only, both axes optional; `brevo app create` seeds it from the slot's registry default when the platform declares one, same mechanism as `context` — the entry's own value is what uploads), and — `iframeExtension` only — `modal_iframe_url`. Do **not** write `link_target` or `extension_point_name` into the file: both are wire/server-stamped (`app upload` injects `link_target` itself). Write only the keys documented here — `brevo app upload` validates the whole file and rejects anything it doesn't recognise, including the pre-GA `heading`/`subheading` names (now `label`/`more_info`, per entry). A UI-app project is **configuration only** — no feature to scaffold and no `src/oauth/`, since an action link has no local server (`brevo app scaffold` inside one says so and exits `0`; `brevo app start` does not apply) — and the base docs the scaffold writes (`AGENTS.md` / `CLAUDE.md` / `README.md`) describe whichever type the app is, so a UI app's copies cover the `ui_app` block and the `upload` → `install` flow instead of an OAuth server.
98
98
  - **`brevo app create` refuses to run inside an already-linked directory.** If `app-config.json` exists in cwd, it throws immediately (no confirm, no override) — the error points at moving elsewhere or running `brevo app scaffold` there.
99
99
  - **`brevo app create` resolves its target directory before creating the app**, then writes the **basic project structure only** (`app-config.json` + `.gitignore`/`AGENTS.md`/`CLAUDE.md`/`README.md`) — the OAuth server code is a *feature*, not part of the base. Interactive mode prompts for the target directory (default `./<slugified-app-name>`, `cd`s into it) before the API call, how to handle an existing one (overwrite / merge / choose a different path), and — after the app is created — whether to scaffold a feature (*"Scaffold the Test OAuth App?"*, default **yes**). There is no follow-up "which feature?" question while the CLI ships one: a list of one is not asked, and the confirm names it instead. A second feature would bring the picker back. Non-interactive runs stay base-only: `--json` (and piped, non-TTY) create the app and write the base files but never scaffold a feature — run `brevo app scaffold` afterward for the OAuth code. Under `--json` the same default directory is used and `cd`d into if it doesn't already exist; if it already exists, both directory setup and scaffolding are skipped (the app is still created). The JSON response always includes `directory` (absolute path) alongside the app fields, plus either `scaffolded` (base file count, on success) or `scaffoldSkipped` (a message, when the directory already existed).
100
100
  - **`brevo app scaffold` adds a feature to an already-created project, or sets an empty directory up for an app that already exists.** It **requires** an `app-config.json` in cwd unless `--app-id` is passed or its bootstrap offer is accepted, and only the bootstrap mode ever creates a directory (the feature-add mode always writes into the project it was run in). **`--app-id <id>` bootstraps a project for an app that already exists**: it fetches the app, writes `app-config.json` + the base files, and then continues into the feature flow. That is the only command that produces a config for an existing app (`app create` creates a new one, `app upload` only reads the linked project), which makes it the migration path off the removed `brevo app update --app-id`. **Interactively, `--app-id` is optional**: in a config-less directory the command explains there is no app here, asks *"Set up a project for an app you already have?"* (default **yes**), and on yes runs the same app picker `app delete` uses — because a user who has lost their project folder has the app but not necessarily its ID. Declining is a normal outcome that exits `0` after printing the remaining routes; the offer is skipped entirely under `--json` or off a TTY, where the no-config error (naming all three ways out: `cd` into a project, `--app-id`, `brevo app create`) is raised instead, so scripts behave exactly as before. **An interactive bootstrap also asks where to put the project** — `Output directory:`, defaulted to `./<slugified app name>`, the same prompt (and the same overwrite / merge / choose-a-different-path follow-up on an existing directory) `app create` uses; it creates the directory, `cd`s the CLI process into it, writes and reports the project, then asks *"Scaffold the Test OAuth App?"* (default yes; declining leaves the project and exits `0`), and opens *Next steps* with `cd <dir>` since the user's shell stayed behind. Answering `.` keeps the current directory and drops that step. This too is interactive-only: under `--json` or off a TTY the files go into the current directory as they always have, which is what makes `scaffold --app-id` safe to script. In bootstrap mode the config is written from the server's copy of the app, since there is nothing local to read it from. Bootstrapping is refused, before any network call or write, in two cases: a directory already linked to a **different** app (passing the app it is already linked to changes nothing), and a directory **inside** an existing app project — `readProjectConfig` reads cwd only and never walks up, so without that check a stray `cd` would nest a second `app-config.json` inside the first and a later `app upload` from there would push the wrong app silently. The different-app check applies to the answer to `Output directory:` as well as to cwd, and there it is the only thing standing between you and a project whose `app-config.json` and `src/oauth/.env.local` name two different apps. **A target directory that already holds a project for the same app makes the bootstrap a refresh**: its config is diffed against the server and rewritten only on consent, and the directory question's **Merge** answer does not suppress that. The two answers address different things — Merge means "don't clobber my own files" and is implemented by skipping any path that already exists, which `app-config.json` always does here, so letting it govern the base write meant the command fetched the app, discarded every field, wrote nothing, and still printed its success box. No drift leaves `app-config.json` as it is with a one-line notice; the feature is still offered either way. It otherwise reads the linked app id from that config (no picker — the picker is only for the config-less bootstrap), diffs the local config against the server, and if fields drifted it shows them and asks consent to update `app-config.json` (and the other base files) to match before writing the feature files. When any feature file already exists it prompts **Overwrite / Merge / Cancel** (default **Merge** — existing, e.g. hand-edited, files are kept and only missing files added; Cancel aborts without writing). The `--overwrite` flag forces a full overwrite of feature files and skips that prompt (works interactively and under `--json`). **Under `--json` it never prompts**: a config diff comes back as `{ "cancelled": true, "reason": "...", "diffs": [...] }`; otherwise it writes the feature (merging existing files unless `--overwrite` is passed) and returns `{ "scaffolded": <n>, "directory": "..." }`.
@@ -156,6 +156,7 @@ Writing `app-config.json` for an app whose remote scopes contain `'all'` never p
156
156
  | `BREVO_API_KEY` | Non-interactive login |
157
157
  | `BREVO_API_URL` | Override API base (HTTPS required, except `localhost`) |
158
158
  | `BREVO_OAUTH_PROXY_URL` | Override OAuth proxy used by browser login |
159
+ | `BREVO_OAUTH_BASE_URL` | Override OAuth realm used for scope lookups and scaffolded project templates (HTTPS required, except `localhost`) |
159
160
  | `BREVO_APP_STORE_URL` | Override the app-store service base used for the update notice and the server-side block check (HTTPS required, except `localhost`) |
160
161
  | `BREVO_CONFIG_HOME` | Override credentials directory (default `~/.brevo/`) |
161
162
  | `BREVO_CLAUDE_HOME` | Override Claude Code home used by `skill:cli` (default `~/.claude/`) |
@@ -53,7 +53,7 @@ Don't fall back to raw HTTP against `api.brevo.com` — the `brevo` binary is th
53
53
  - "Who am I logged in as?" → `brevo whoami --json`
54
54
  - "Show / pick an app" → `brevo app list --json`
55
55
  - "Create an app" → `brevo app create --name "<name>" --distribution private --redirect-uri <url> --json` (add `--logo-uri <https://…>` to set the app logo at creation time; new apps default to scopes `contacts:read`, `contacts:write`, `crm:read`, `crm:write`). **Use `--distribution private`** — check `brevo app create --help` for the values your account accepts, and don't pass one it doesn't list. **Fails immediately if run from a directory that already has `app-config.json`** — `cd` elsewhere first, or use `brevo app scaffold` in that directory instead. Otherwise resolves (creates/`cd`s into) its target directory, creates the app, and writes the **basic project structure** (`app-config.json` + `.gitignore`/`AGENTS.md`/`CLAUDE.md`/`README.md`). It scaffolds a feature (the OAuth test server) **only** when the interactive confirm (*"Scaffold the Test OAuth App?"*, default yes) is answered yes — there is no separate "which feature?" question while the CLI ships one; **non-interactive runs (`--json` or piped) stay base-only** — run `brevo app scaffold` afterward to add the OAuth code. Under `--json`, the response's `directory` field is where it landed and `scaffolded` is the base file count; check for `scaffoldSkipped` instead of `scaffolded` if that directory already existed (both directory setup and scaffolding are skipped together in that case, but the app is still created).
56
- - "Create a UI app" (an action link that renders inside Brevo CRM records) → run `brevo app create` **interactively** and pick *UI app* at the *"What type of app are you building?"* prompt. There is **no `--type` flag and no per-field flags** — a UI app can only be authored from an interactive terminal, and every non-interactive run (`--json` or piped stdin) creates an OAuth app. The flow then asks for the integration type, one record page, one placement on it, a label, optional supporting text, and the destination URL — it authors exactly **one** placement; add more by hand as further `surface_point_list` entries in `app-config.json` (each with its own `label` and `redirect_link`), then `brevo app upload`.
56
+ - "Create a UI app" (an action link that renders inside Brevo CRM records) → run `brevo app create` **interactively** and pick *UI app* at the *"What type of app are you building?"* prompt, **or non-interactively** with `--ui-app --record-page <slug> --placement <surface_point_name> --label <text> --url <url> [--more-info <text>]`, or `--ui-config <file>` (a JSON file: `{ extension_type, record_page, surface_point_name, label, more_info?, redirect_link }`). There is still **no `--type` flag** — a plain non-interactive run with neither `--ui-config` nor `--ui-app` creates an OAuth app, exactly as before. Both non-interactive routes only support `extension_type: "actionLink"` today; an unknown `--record-page`/`--placement` fails with the valid options listed in the error. Interactively, the flow asks for the integration type, one record page, one placement on it, a label, optional supporting text, and the destination URL — every route authors exactly **one** placement; add more by hand as further `surface_point_list` entries in `app-config.json` (each with its own `label` and `redirect_link`), then `brevo app upload`.
57
57
  - "Install a UI app into an account" → `brevo app install [account-id] --app-id <id> --force --json`. **UI apps only** — an OAuth app has nothing to install (it becomes usable when a user authorizes it) and the CLI refuses with exit `1`. The `[account-id]` positional is optional: omitted, a plain account installs into itself (no prompt, so `--json`/CI works) and a corporate account picks a sub-account interactively (non-interactive corporate runs must pass it explicitly). The app must have been validated with `brevo app upload` first — installing before an upload is refused locally. Interactively, omitting `--app-id` outside a linked project opens an app picker that lists **only UI apps**; with no UI app to offer, the command errors (exit `1`) naming `brevo app create`. That picker needs a terminal: under `--json` or off a TTY, omitting `--app-id` outside a linked project is refused with exit `1` — so always pass `--app-id` when scripting from an unlinked directory. Before it acts, `install` prints the configuration it will install **as stored on the server** — app ID, name, `version`, extension type and every placement — because that is what the account will render, not whatever the local `app-config.json` now says; under `--json` the same facts come back as `version` and `ui_app` on the result. If the linked project's `ui_app` block has drifted from the stored one, it warns and names `brevo app upload`, then installs anyway (exit `0`) — the stored configuration is a legitimate thing to install, so this is a notice, not a refusal.
58
58
  - "Uninstall a UI app from an account" → `brevo app uninstall [account-id] --app-id <id> --force --json`. Same target resolution as `install`. Uninstalling an app that isn't installed is **informational, exit `0`** — not an error.
59
59
  - "Update app metadata" → edit the relevant field(s) in `app-config.json` (`appName`, `auth.redirectUris`, `auth.scopes`, `logoUri`, `version`) (older projects may still say `auth.redirectUrls` — the CLI reads it and migrates the file to `redirectUris` on its next write), then run `brevo app upload --json` (no `--app-id`/`--name`/`--redirect-uri`/`--scope`/`--logo-uri` flags exist — `upload` always pushes the whole file, resolved only from cwd's `app-config.json`). **`distribution_type` is immutable** — it's set at `app create` time and cannot be changed via `upload`; if the local value differs from the server, `upload` errors and tells you to restore it (create a new app to get a different distribution). For a **UI app**, `upload` is also the way to change what an installed app renders — there is no re-install and no publish step: the diff prints every `ui_app` placement field by field (`before → after`, plus `(new)` / `(removed)` placements), then warns that the app may already be installed in Brevo accounts and asks *"Proceed with upload and update every account this app is installed in?"*. `--yes` skips the question but still prints the warning; `--json` prints neither and stays a single parseable document. **There is no `brevo app update`** — it was removed, with no shim and no flag-for-flag equivalent; if you find it in a user's script, CI job, README, or your own recollection, replace it with the edit-then-`upload` flow above. Invoking it — with any of the old flags, with `--help`, or as `brevo app help update` — prints a message naming `brevo app upload` and exits `1` **without uploading anything**, so a `1` from `brevo app update` means the command is gone, not that an upload failed.
@@ -90,7 +90,7 @@ The `ui_app` block in `app-config.json`:
90
90
  - `more_info` — supporting text under the menu entry / a card's description. Optional.
91
91
  - `redirect_link` — the destination URL that entry opens; record context arrives as **query parameters** (the path is never templated).
92
92
  - `context` — optional narrowing of the record fields passed along; it can only narrow what the platform allows for that slot.
93
- - `size` — optional card size for the widget card this placement renders, e.g. `{ "width": "280px", "height": "160px" }`. Each axis is a CSS length string — a positive integer with an explicit `px` unit, or `1%`–`100%` of the host slot's box (shrink-only; >100% is rejected). Both axes are optional; an omitted axis (or the whole key) stays on the host slot's default.
93
+ - `size` — optional card size for the widget card this placement renders, e.g. `{ "width": "280px", "height": "160px" }`. `brevo app create` seeds it from the slot's registry default when the platform declares one (same mechanism as `context`) — edit or remove it freely, the entry's own value is what uploads. Each axis is a CSS length string — a positive integer with an explicit `px` unit, or `1%`–`100%` of the host slot's box (shrink-only; >100% is rejected). Both axes are optional; an omitted axis (or the whole key) stays on the host slot's default.
94
94
  - `modal_iframe_url` — `iframeExtension` entries only; rejected on an `actionLink`.
95
95
  - Do **not** write `link_target` or `extension_point_name` anywhere in the file — both are wire/server-stamped values (`app upload` injects `link_target: "_blank"` itself) and the CLI strips them from server echoes.
96
96
  - The old `heading`/`subheading` names are rejected with a migration hint — they are `label`/`more_info` now, and they live **per entry**, not at the `ui_app` root.