@getbrevo/cli 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +209 -0
- package/README.md +8 -5
- package/agent-context/AGENTS.md +59 -15
- package/agent-context/SKILL.md +70 -14
- package/dist/{templates → bin}/files/AGENTS.md.tmpl +11 -1
- package/dist/{templates → bin}/files/CLAUDE.md.tmpl +8 -0
- package/dist/{templates → bin}/files/README.md.tmpl +11 -14
- package/dist/bin/files/app-config.json.tmpl +16 -0
- package/dist/{templates → bin}/files/src/oauth/.env.example.tmpl +8 -0
- package/dist/{templates → bin}/files/src/oauth/.env.local.tmpl +2 -0
- package/dist/{templates → bin}/files/src/oauth/handler.js.tmpl +49 -7
- package/dist/{templates → bin}/files/src/oauth/server.js.tmpl +20 -14
- package/dist/{templates → bin}/files/src/oauth/token-store.js.tmpl +19 -0
- package/dist/bin/index.js +660 -235
- package/dist/bin/index.js.map +7 -1
- package/package.json +6 -4
- package/dist/api/client.d.ts +0 -25
- package/dist/api/client.d.ts.map +0 -1
- package/dist/api/client.js +0 -205
- package/dist/api/client.js.map +0 -1
- package/dist/bin/index.d.ts +0 -3
- package/dist/bin/index.d.ts.map +0 -1
- package/dist/commands/app/create.d.ts +0 -8
- package/dist/commands/app/create.d.ts.map +0 -1
- package/dist/commands/app/create.js +0 -325
- package/dist/commands/app/create.js.map +0 -1
- package/dist/commands/app/credentials.d.ts +0 -6
- package/dist/commands/app/credentials.d.ts.map +0 -1
- package/dist/commands/app/credentials.js +0 -114
- package/dist/commands/app/credentials.js.map +0 -1
- package/dist/commands/app/delete.d.ts +0 -6
- package/dist/commands/app/delete.d.ts.map +0 -1
- package/dist/commands/app/delete.js +0 -167
- package/dist/commands/app/delete.js.map +0 -1
- package/dist/commands/app/list.d.ts +0 -4
- package/dist/commands/app/list.d.ts.map +0 -1
- package/dist/commands/app/list.js +0 -71
- package/dist/commands/app/list.js.map +0 -1
- package/dist/commands/app/scaffold.d.ts +0 -5
- package/dist/commands/app/scaffold.d.ts.map +0 -1
- package/dist/commands/app/scaffold.js +0 -213
- package/dist/commands/app/scaffold.js.map +0 -1
- package/dist/commands/app/scopes.d.ts +0 -2
- package/dist/commands/app/scopes.d.ts.map +0 -1
- package/dist/commands/app/scopes.js +0 -81
- package/dist/commands/app/scopes.js.map +0 -1
- package/dist/commands/app/start.d.ts +0 -5
- package/dist/commands/app/start.d.ts.map +0 -1
- package/dist/commands/app/start.js +0 -220
- package/dist/commands/app/start.js.map +0 -1
- package/dist/commands/app/update.d.ts +0 -2
- package/dist/commands/app/update.d.ts.map +0 -1
- package/dist/commands/app/update.js +0 -444
- package/dist/commands/app/update.js.map +0 -1
- package/dist/commands/definitions.d.ts +0 -5
- package/dist/commands/definitions.d.ts.map +0 -1
- package/dist/commands/definitions.js +0 -262
- package/dist/commands/definitions.js.map +0 -1
- package/dist/commands/init.d.ts +0 -2
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -138
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/login.d.ts +0 -6
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js +0 -251
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts +0 -5
- package/dist/commands/logout.d.ts.map +0 -1
- package/dist/commands/logout.js +0 -54
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/skill/install.d.ts +0 -4
- package/dist/commands/skill/install.d.ts.map +0 -1
- package/dist/commands/skill/install.js +0 -29
- package/dist/commands/skill/install.js.map +0 -1
- package/dist/commands/skill/uninstall.d.ts +0 -4
- package/dist/commands/skill/uninstall.d.ts.map +0 -1
- package/dist/commands/skill/uninstall.js +0 -23
- package/dist/commands/skill/uninstall.js.map +0 -1
- package/dist/commands/whoami.d.ts +0 -4
- package/dist/commands/whoami.d.ts.map +0 -1
- package/dist/commands/whoami.js +0 -85
- package/dist/commands/whoami.js.map +0 -1
- package/dist/container.d.ts +0 -7
- package/dist/container.d.ts.map +0 -1
- package/dist/container.js +0 -28
- package/dist/container.js.map +0 -1
- package/dist/lang/en.d.ts +0 -162
- package/dist/lang/en.d.ts.map +0 -1
- package/dist/lang/en.js +0 -189
- package/dist/lang/en.js.map +0 -1
- package/dist/lib/abort.d.ts +0 -3
- package/dist/lib/abort.d.ts.map +0 -1
- package/dist/lib/abort.js +0 -30
- package/dist/lib/abort.js.map +0 -1
- package/dist/lib/auth-guard.d.ts +0 -13
- package/dist/lib/auth-guard.d.ts.map +0 -1
- package/dist/lib/auth-guard.js +0 -60
- package/dist/lib/auth-guard.js.map +0 -1
- package/dist/lib/browser.d.ts +0 -2
- package/dist/lib/browser.d.ts.map +0 -1
- package/dist/lib/browser.js +0 -16
- package/dist/lib/browser.js.map +0 -1
- package/dist/lib/cli-version.d.ts +0 -2
- package/dist/lib/cli-version.d.ts.map +0 -1
- package/dist/lib/cli-version.js +0 -59
- package/dist/lib/cli-version.js.map +0 -1
- package/dist/lib/command-handler.d.ts +0 -10
- package/dist/lib/command-handler.d.ts.map +0 -1
- package/dist/lib/command-handler.js +0 -16
- package/dist/lib/command-handler.js.map +0 -1
- package/dist/lib/command-registry.d.ts +0 -28
- package/dist/lib/command-registry.d.ts.map +0 -1
- package/dist/lib/command-registry.js +0 -54
- package/dist/lib/command-registry.js.map +0 -1
- package/dist/lib/config.d.ts +0 -83
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js +0 -430
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/constants.d.ts +0 -48
- package/dist/lib/constants.d.ts.map +0 -1
- package/dist/lib/constants.js +0 -121
- package/dist/lib/constants.js.map +0 -1
- package/dist/lib/errors.d.ts +0 -30
- package/dist/lib/errors.d.ts.map +0 -1
- package/dist/lib/errors.js +0 -66
- package/dist/lib/errors.js.map +0 -1
- package/dist/lib/exit-codes.d.ts +0 -9
- package/dist/lib/exit-codes.d.ts.map +0 -1
- package/dist/lib/exit-codes.js +0 -12
- package/dist/lib/exit-codes.js.map +0 -1
- package/dist/lib/hidden-input.d.ts +0 -2
- package/dist/lib/hidden-input.d.ts.map +0 -1
- package/dist/lib/hidden-input.js +0 -78
- package/dist/lib/hidden-input.js.map +0 -1
- package/dist/lib/json-output.d.ts +0 -6
- package/dist/lib/json-output.d.ts.map +0 -1
- package/dist/lib/json-output.js +0 -11
- package/dist/lib/json-output.js.map +0 -1
- package/dist/lib/logger.d.ts +0 -11
- package/dist/lib/logger.d.ts.map +0 -1
- package/dist/lib/logger.js +0 -110
- package/dist/lib/logger.js.map +0 -1
- package/dist/lib/oauth-freshness.d.ts +0 -30
- package/dist/lib/oauth-freshness.d.ts.map +0 -1
- package/dist/lib/oauth-freshness.js +0 -61
- package/dist/lib/oauth-freshness.js.map +0 -1
- package/dist/lib/port.d.ts +0 -25
- package/dist/lib/port.d.ts.map +0 -1
- package/dist/lib/port.js +0 -103
- package/dist/lib/port.js.map +0 -1
- package/dist/lib/telemetry.d.ts +0 -9
- package/dist/lib/telemetry.d.ts.map +0 -1
- package/dist/lib/telemetry.js +0 -57
- package/dist/lib/telemetry.js.map +0 -1
- package/dist/lib/ui.d.ts +0 -14
- package/dist/lib/ui.d.ts.map +0 -1
- package/dist/lib/ui.js +0 -75
- package/dist/lib/ui.js.map +0 -1
- package/dist/lib/update-notifier.d.ts +0 -37
- package/dist/lib/update-notifier.d.ts.map +0 -1
- package/dist/lib/update-notifier.js +0 -298
- package/dist/lib/update-notifier.js.map +0 -1
- package/dist/lib/validators.d.ts +0 -55
- package/dist/lib/validators.d.ts.map +0 -1
- package/dist/lib/validators.js +0 -166
- package/dist/lib/validators.js.map +0 -1
- package/dist/services/account.d.ts +0 -8
- package/dist/services/account.d.ts.map +0 -1
- package/dist/services/account.js +0 -15
- package/dist/services/account.js.map +0 -1
- package/dist/services/app.d.ts +0 -38
- package/dist/services/app.d.ts.map +0 -1
- package/dist/services/app.js +0 -145
- package/dist/services/app.js.map +0 -1
- package/dist/services/browser-auth.d.ts +0 -15
- package/dist/services/browser-auth.d.ts.map +0 -1
- package/dist/services/browser-auth.js +0 -188
- package/dist/services/browser-auth.js.map +0 -1
- package/dist/services/normalize-app-id.d.ts +0 -12
- package/dist/services/normalize-app-id.d.ts.map +0 -1
- package/dist/services/normalize-app-id.js +0 -38
- package/dist/services/normalize-app-id.js.map +0 -1
- package/dist/services/oauth-metadata.d.ts +0 -7
- package/dist/services/oauth-metadata.d.ts.map +0 -1
- package/dist/services/oauth-metadata.js +0 -43
- package/dist/services/oauth-metadata.js.map +0 -1
- package/dist/services/oauth-refresh.d.ts +0 -14
- package/dist/services/oauth-refresh.d.ts.map +0 -1
- package/dist/services/oauth-refresh.js +0 -70
- package/dist/services/oauth-refresh.js.map +0 -1
- package/dist/services/scopes-html.d.ts +0 -3
- package/dist/services/scopes-html.d.ts.map +0 -1
- package/dist/services/scopes-html.js +0 -516
- package/dist/services/scopes-html.js.map +0 -1
- package/dist/services/scopes-web.d.ts +0 -12
- package/dist/services/scopes-web.d.ts.map +0 -1
- package/dist/services/scopes-web.js +0 -95
- package/dist/services/scopes-web.js.map +0 -1
- package/dist/services/skill.d.ts +0 -48
- package/dist/services/skill.d.ts.map +0 -1
- package/dist/services/skill.js +0 -223
- package/dist/services/skill.js.map +0 -1
- package/dist/skills/index.d.ts +0 -47
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -61
- package/dist/skills/index.js.map +0 -1
- package/dist/templates/files/app-config.json.tmpl +0 -25
- package/dist/templates/index.d.ts +0 -36
- package/dist/templates/index.d.ts.map +0 -1
- package/dist/templates/index.js +0 -105
- package/dist/templates/index.js.map +0 -1
- package/dist/types.d.ts +0 -32
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- /package/dist/{templates → bin}/files/gitignore.tmpl +0 -0
- /package/dist/{templates → bin}/files/src/oauth/package.json.tmpl +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,214 @@
|
|
|
1
1
|
# @getbrevo/cli
|
|
2
2
|
|
|
3
|
+
## 2.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e6d72f5: `brevo app update` is replaced by `brevo app upload`, `brevo app scaffold` can set up a
|
|
8
|
+
directory for an app you already have, `app-config.json` moves to a new shape,
|
|
9
|
+
`brevo <command> --help` prints that command's own usage, and `--json` now produces output
|
|
10
|
+
on failure paths.
|
|
11
|
+
|
|
12
|
+
## `brevo app update` → `brevo app upload` (BEX-250)
|
|
13
|
+
|
|
14
|
+
`upload` takes only `--yes` and `--json`. The edit flags (`--app-id`, `--name`,
|
|
15
|
+
`--redirect-uri`, `--scope`, `--logo-uri`) are gone — edit `app-config.json`, then upload.
|
|
16
|
+
|
|
17
|
+
It always fetches the app's remote state and shows a local-vs-server diff before pushing,
|
|
18
|
+
including under `--yes` (skips the prompt only) and `--json`. No differences means exit `0`,
|
|
19
|
+
"already up to date", and no network push. Otherwise the whole local file is pushed and the
|
|
20
|
+
server-confirmed state written back. `distribution_type` is immutable after `app create`; a
|
|
21
|
+
changed value is refused locally.
|
|
22
|
+
|
|
23
|
+
`app update` stays registered but hidden, and answers with a message naming its replacement
|
|
24
|
+
(reachable with any removed flag, stray args, `--help`, or `brevo app help update`, and with
|
|
25
|
+
no login). Nothing is forwarded or uploaded; it exits `1`.
|
|
26
|
+
|
|
27
|
+
**Migrating:** `brevo app scaffold --app-id <id>`, edit `app-config.json`, `brevo app upload`.
|
|
28
|
+
|
|
29
|
+
## `brevo app scaffold` gains a bootstrap mode
|
|
30
|
+
|
|
31
|
+
`scaffold` now picks its mode from whether the current directory holds an `app-config.json`.
|
|
32
|
+
With one, it adds a feature to the linked app. Without one it _bootstraps_ — sets the
|
|
33
|
+
directory up for an app that already exists — the successor to `brevo app update --app-id`.
|
|
34
|
+
- `--app-id <id>` fetches the app, writes `app-config.json` plus the base files, then
|
|
35
|
+
continues into the feature flow.
|
|
36
|
+
- Interactively with no config and no flag, it asks _"Set up a project for an app you already
|
|
37
|
+
have?"_ (default yes) and shows an app picker, so recovery no longer needs the app's ID.
|
|
38
|
+
Declining exits `0`. Under `--json` or off a TTY the usual no-config error is raised, so
|
|
39
|
+
scripts are unaffected.
|
|
40
|
+
- A bootstrap also asks for an output directory (defaulting to the app name as a slug),
|
|
41
|
+
creates it, and reports `cd <dir>` as the first next step; answer `.` to stay put. The
|
|
42
|
+
project is written and shown _before_ the feature question, so declining still leaves a
|
|
43
|
+
usable project.
|
|
44
|
+
|
|
45
|
+
Bootstrapping refuses three cases before any network call, each of which previously produced
|
|
46
|
+
a silently wrong project: a directory **inside** an existing app project (which used to nest
|
|
47
|
+
a second config, after which `upload` pushed the wrong app); `--app-id` naming a **different**
|
|
48
|
+
app than the directory is linked to; and an output directory that is **already another app's
|
|
49
|
+
project** (where answering _Merge_ left that app's config in place while writing this app's
|
|
50
|
+
credentials beside it).
|
|
51
|
+
|
|
52
|
+
`scaffold` also asks before reusing existing feature files — **Overwrite / Merge / Cancel**
|
|
53
|
+
(default Merge) — and a new `--overwrite` flag forces a full overwrite without prompting.
|
|
54
|
+
|
|
55
|
+
**Fixed:** bootstrapping into a directory that already holds a project for the _same_ app
|
|
56
|
+
wrote nothing and reported success. It is now treated as the refresh it is: drift is listed,
|
|
57
|
+
you confirm, the file is rewritten.
|
|
58
|
+
|
|
59
|
+
**Fixed:** `brevo app scaffold --json` could block on a prompt and hang CI. It now never
|
|
60
|
+
prompts — each case reports `{ "cancelled": true, "reason": "...", "diffs": [...] }`.
|
|
61
|
+
|
|
62
|
+
## `brevo app create`
|
|
63
|
+
|
|
64
|
+
Creation is split from feature scaffolding. `create` writes only the base project
|
|
65
|
+
(`app-config.json`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `README.md`); the OAuth
|
|
66
|
+
test-server code is now a feature, scaffolded when _"Scaffold the Test OAuth App?"_
|
|
67
|
+
(default yes) is accepted. Non-interactive runs stay base-only — add the code later with
|
|
68
|
+
`brevo app scaffold`.
|
|
69
|
+
|
|
70
|
+
`create` now hard-errors if `app-config.json` already exists, resolves its target directory
|
|
71
|
+
before creating the app, says where files are landing, and adds a `cd <dir>` step to
|
|
72
|
+
_Next steps_ when they land elsewhere. The logo URL is asked right after the app name;
|
|
73
|
+
`--logo-uri` and non-interactive runs are unchanged. Neither command asks _"What feature do
|
|
74
|
+
you want to scaffold?"_ any more — the CLI ships one feature; the picker returns
|
|
75
|
+
automatically if a second is added.
|
|
76
|
+
|
|
77
|
+
Fixed in `create`:
|
|
78
|
+
- `Client ID: undefined` — the create response nests OAuth fields under `auth`, which
|
|
79
|
+
dropped `Redirect URL n:` lines and omitted `clientId` / `redirectUri` from `--json`. The
|
|
80
|
+
response is now flattened in one place, tolerating both shapes.
|
|
81
|
+
- A failed read-back no longer destroys a successful create. A `404` on that one read now
|
|
82
|
+
falls back to the create response and completes with a warning instead of exiting non-zero
|
|
83
|
+
while the app sits on the server.
|
|
84
|
+
- No stray directory is left behind when create fails — the directory _decision_ still comes
|
|
85
|
+
first, but nothing is written until there is provably an app to write it for.
|
|
86
|
+
- The file count no longer reads `(0 files)` above a file tree; it reports `created (5 files)`,
|
|
87
|
+
`already in place (5 files, nothing rewritten)`, or `created (2 of 5 files written)`.
|
|
88
|
+
- Choosing Overwrite/Merge on _"Directory already exists"_ now says `Moving into <dir>...`
|
|
89
|
+
rather than contradicting the answer with `Creating <dir> and moving into it...`.
|
|
90
|
+
|
|
91
|
+
## `app-config.json` shape
|
|
92
|
+
|
|
93
|
+
Backward compatible on read, migrated on the next write-back (`upload`, `app start`'s URL
|
|
94
|
+
registration, `credentials`, or `scaffold`):
|
|
95
|
+
- `auth.redirectUrls` → **`auth.redirectUris`**, matching the wire key `redirect_uris`. The
|
|
96
|
+
old key is still read; the new one wins if both are present.
|
|
97
|
+
- A legacy top-level `distribution`, and the interim `auth.type`, become **`distribution_type`**.
|
|
98
|
+
- New read-only top-level **`version`**, tracking the app-store API's version field.
|
|
99
|
+
- `cliVersion`, `permittedUrls`, `support` and `auth.type` are dropped — nothing read them.
|
|
100
|
+
|
|
101
|
+
**Downgrade caveat:** older releases read only `redirectUrls`, so a migrated file reads to
|
|
102
|
+
them as having no redirect URLs. That is loud and harmless in `app update`, but **`app start`
|
|
103
|
+
costs you something**: seeing an empty list it offers to register
|
|
104
|
+
`http://localhost:<port>/auth/callback` and, on yes, pushes that single URL as the app's whole
|
|
105
|
+
`redirect_uris` — dropping every real one, silently. Upgrade the lagging CLI, or keep both
|
|
106
|
+
keys as a stopgap; if it already happened, restore the list and `brevo app upload`.
|
|
107
|
+
|
|
108
|
+
`brevo app credentials` backfills a legacy config toward the current shape when its `appId`
|
|
109
|
+
matches the app being inspected, filling a missing `version` / `distribution_type` from the
|
|
110
|
+
server without overwriting existing values — so projects that are never uploaded converge too.
|
|
111
|
+
|
|
112
|
+
## Help, `--json`, and debugging
|
|
113
|
+
- **`brevo <command> --help` prints that command's own usage, arguments, flags and examples.**
|
|
114
|
+
Subcommands previously rendered the root help screen, which never named the flags being
|
|
115
|
+
looked for. The root screen is unchanged.
|
|
116
|
+
- **`--json` now applies on failure.** Commands other than `whoami` and `logout` exited
|
|
117
|
+
non-zero with nothing on stdout. Failures now write one `{"error": {...}}` document carrying
|
|
118
|
+
`name`, `message`, `exitCode`, plus `statusCode` and (when classified) `code`. The
|
|
119
|
+
human-readable message still goes to stderr, so stdout stays exactly one parseable document.
|
|
120
|
+
- **`brevo app list` shows a `Version:` line** and includes `version` in `--json`. It is also
|
|
121
|
+
hardened against `redirect_uris: null` / `scopes: null` from the list endpoint, which used to
|
|
122
|
+
end the listing part-way with `Cannot read properties of null (reading 'length')`; the same
|
|
123
|
+
dereference is fixed in `app credentials`.
|
|
124
|
+
- **`--debug` logs the request body** before the request goes out, alongside the response —
|
|
125
|
+
same `<METHOD> <path>`, same redaction. Nothing changes without `--debug`.
|
|
126
|
+
|
|
127
|
+
## Output
|
|
128
|
+
- **Boxed output wraps instead of shredding itself.** `printBox` sized itself to its longest
|
|
129
|
+
line and let the terminal wrap the overflow without the frame. Lines are now wrapped before
|
|
130
|
+
the frame is drawn — on a word where possible, mid-string where not — with continuation rows
|
|
131
|
+
indented under their label and colour carried across. Piped output falls back to 80 columns.
|
|
132
|
+
- **Selection prompts are indented into the CLI's output gutter**; `list` and `checkbox`
|
|
133
|
+
options used to render two columns left of every other line. Numbered app pickers are
|
|
134
|
+
unchanged.
|
|
135
|
+
- **Fixed:** the first row of a printed file tree was indented two columns deeper than the rest.
|
|
136
|
+
- **The logo-URL prompt fits 80 columns** — `App logo URL (optional — leave blank to skip):`,
|
|
137
|
+
with the example URL moved to the validation error.
|
|
138
|
+
|
|
139
|
+
## Request payloads
|
|
140
|
+
|
|
141
|
+
`POST /v3/app-store/apps` now carries OAuth fields in the same `auth: { scopes, redirect_uris }`
|
|
142
|
+
block the upload endpoint takes, and upload's version field is renamed `app_version` →
|
|
143
|
+
`version`. `cli_version` and `source: 'cli'` are no longer sent in either body — both were
|
|
144
|
+
outside the declared contract, upload rejects unknown keys with a `400`, and the caller is
|
|
145
|
+
identified by a structured `User-Agent` (`brevo-cli/<version> (<os>; auth=<method>)`).
|
|
146
|
+
|
|
147
|
+
## An expired session is reported before the prompts (BEX-341 follow-up)
|
|
148
|
+
|
|
149
|
+
`brevo app create` could ask every question and only then report ``Your session has expired.
|
|
150
|
+
Run `brevo login` ``, discarding all of it: a refresh token the login service _refuses_ was
|
|
151
|
+
treated like a transient network failure and swallowed.
|
|
152
|
+
|
|
153
|
+
A refused token is now terminal — stored credentials are cleared and the expiry is reported
|
|
154
|
+
from the pre-command hook, before the first prompt. Failures that say nothing about the
|
|
155
|
+
session (timeout, `5xx`, unwritable credentials file) are still swallowed and never block a
|
|
156
|
+
command. The token is also re-checked before each authenticated request rather than once at
|
|
157
|
+
startup, and if a session dies mid-flow `app create` offers to log you back in and re-sends
|
|
158
|
+
the answers you already gave (interactive terminals only). Exit codes and error text are
|
|
159
|
+
unchanged.
|
|
160
|
+
|
|
161
|
+
## The legacy `all` OAuth scope is refused on upload (BEX-214)
|
|
162
|
+
|
|
163
|
+
`brevo app upload` now stops when `auth.scopes` still contains the deprecated catch-all `all`
|
|
164
|
+
and explains the migration: replace it with the specific scopes your integration uses, list
|
|
165
|
+
them with `brevo app available-scopes`, then upload. `brevo app start oauth` refuses the same
|
|
166
|
+
config with the same guidance, `brevo app list` tags an affected app, and `brevo app scaffold`
|
|
167
|
+
writes granular scopes.
|
|
168
|
+
|
|
169
|
+
The refusal is local — no round trip, nothing pushed. An app already holding `all` on Brevo is
|
|
170
|
+
untouched until you upload a replacement list; the upload diff then labels it as a migration.
|
|
171
|
+
|
|
172
|
+
## Other fixes
|
|
173
|
+
- **`auth.scopes` written as a single string is read correctly.** A hand-edited
|
|
174
|
+
`"scopes": "contacts:read, contacts:write"` was documented as normalized on read and was
|
|
175
|
+
not — the splitter was only reached for arrays, so the upload validator iterated the string
|
|
176
|
+
one character at a time and rejected the run with `Invalid scope: ":"`. Strings are now split
|
|
177
|
+
on commas and whitespace, exactly as over-long array entries always were.
|
|
178
|
+
- **`app credentials` / `app delete` no longer open an app picker they cannot draw.** Their
|
|
179
|
+
`--app-id` fallback was not gated on the run being interactive, so under `--json` or off a
|
|
180
|
+
TTY it printed its choice list to **stdout** and then aborted with
|
|
181
|
+
`ERR_USE_AFTER_CLOSE: readline was closed`. Both now refuse up front, before any network
|
|
182
|
+
call, naming the exact command to run and exiting `1`; `--json` gets the usual error
|
|
183
|
+
envelope. Interactive runs are unchanged. `app delete` is the consequential one — a scripted
|
|
184
|
+
delete that appeared to rely on the picker was never selecting anything.
|
|
185
|
+
|
|
186
|
+
- 67390da: Take the update banner's message from the Brevo app-store service, and let it block a CLI version that can no longer be supported.
|
|
187
|
+
|
|
188
|
+
Whether an update banner appears is unchanged: the CLI still checks the npm registry, compares against its own version, and applies the same soft-notice and force-update rules. What changed is the first line of that banner, which now comes from `GET /cli/info` and is rendered in red above the box. The box's own contents are untouched.
|
|
189
|
+
|
|
190
|
+
That endpoint is called on the app-store service **directly**, not through the v3 API gateway, and needs no API key — so the notice still renders while logged out, mid-`login`, or with expired credentials, which is exactly when a stale CLI is most likely to be the real problem. `BREVO_APP_STORE_URL` overrides the base URL for non-production testing.
|
|
191
|
+
|
|
192
|
+
The response may also carry `"is_blocked": true`. When it does, every command except `--help` / `--version` prints the banner to stderr and exits `1` without running, letting a broken CLI version be stopped without shipping a new release. This is deliberately **not** silenced by `BREVO_NO_UPDATE_NOTIFIER=1`, `--no-update-notifier`, CI, or a non-TTY — those suppress a _notice_, and a suppressed banner must never mean a suppressed block.
|
|
193
|
+
|
|
194
|
+
Because a block has to prevent a command rather than report on it afterwards, the call now runs once before the command instead of after it. The result is cached at `~/.brevo/cli-info-cache.json` for **15 minutes**, keyed to the running CLI version, so a healthy fleet of invocations doesn't call the app-store service on every single command — revised wording and a new block reach users within minutes rather than after the old 12h npm-style TTL, and an upgrade always gets a live check regardless of the cache's age. A failed call never overwrites a good cache entry.
|
|
195
|
+
|
|
196
|
+
It fails open throughout. A timeout, a non-2xx, HTML from a gateway, or an unparseable body all leave the CLI behaving exactly as before — the banner falls back to local wording and nothing is blocked. Only a literal `true` blocks, so an outage can never lock anyone out. The endpoint is fetched outside the authenticated API client, so a 401 from it cannot reach the re-auth handler or clear stored credentials, and the returned text is control-character stripped, flattened to one line, and clamped before display.
|
|
197
|
+
|
|
198
|
+
Also show the update notice when a command fails. Previously the banner only printed on the success path, so anyone whose command errored — including the auth errors most likely to be fixed by upgrading — never saw that a newer CLI existed. The banner now prints after the error message on stderr, keeping the command's own exit code, and `notifyUpdate` is idempotent so runs that already showed it up front (`--help`, `app init`, `app create`) don't print it twice. A Ctrl-C abort still exits immediately without waiting on the check.
|
|
199
|
+
|
|
200
|
+
The npm registry check backing the soft-notice/force-update gate (`~/.brevo/update-check.json`) moves from a 24h TTL to **12h**, so a newly-published version reaches users about twice as fast.
|
|
201
|
+
|
|
202
|
+
### Patch Changes
|
|
203
|
+
|
|
204
|
+
- bcfc3c1: Fix two ways the update banner could show local wording when the app-store service had wording of its own.
|
|
205
|
+
|
|
206
|
+
**`--help` and `--version` never asked.** They are exempt from the `is_blocked` gate so a blocked CLI can still say what version it is — but that was implemented by skipping the whole `/cli/info` call, which discarded the message along with the verdict. Both still render an update banner, so the two commands people most often run to check their version were the only ones that could never explain why the version mattered. The exemption now covers the block alone: they fetch the wording, show it, and still exit `0` no matter what `is_blocked` says.
|
|
207
|
+
|
|
208
|
+
**The 15-minute cache ignored which service the answer came from.** The entry was keyed on `cliVersion` and `lastChecked` only, but the base URL is overridable per-invocation via `BREVO_APP_STORE_URL`, so two runs against different environments shared one entry — whichever was hit first answered for both until the TTL expired. The symptom was badly misleading: pointing at staging rendered the message production had returned moments earlier and vice versa, so the CLI and a direct `curl` disagreed, and which environment looked broken depended only on the order the two were run in. `baseUrl` is now part of the key. An entry written before the field existed fails the shape check and is treated as a miss, so an upgrade re-fetches once rather than serving a stale answer.
|
|
209
|
+
|
|
210
|
+
Neither changes what blocks, what fails open, or any exit code.
|
|
211
|
+
|
|
3
212
|
## 2.0.2
|
|
4
213
|
|
|
5
214
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Command-line tool to create, manage, and test [Brevo](https://www.brevo.com/) OA
|
|
|
5
5
|
> 📖 Full command and option documentation: **[Brevo CLI reference](https://developers.brevo.com/docs/cli-reference)**
|
|
6
6
|
|
|
7
7
|
> [!WARNING]
|
|
8
|
-
> **Upgrade to
|
|
8
|
+
> **Upgrade to the latest released version.** All versions from **1.1.1** up to (but not including) **2.0.0** should be migrated to at least **2.1.0**. The `2.0.0` release introduced **breaking changes**, so some CLI commands may not work as expected on older versions. **`2.1.0` and above** also carry further `app-config.json` migrations (e.g. legacy `auth.redirectUrls` → `auth.redirectUris`) that are applied automatically the next time the CLI writes your config (`brevo app upload`, `brevo app start`, …).
|
|
9
9
|
>
|
|
10
10
|
> Upgrade with `npm install -g @getbrevo/cli@latest` (or `yarn global add @getbrevo/cli@latest`, or `brew upgrade brevo`), then confirm with `brevo --version`.
|
|
11
11
|
|
|
@@ -93,16 +93,19 @@ Run `brevo --help` or `brevo <command> --help` for full command and option lists
|
|
|
93
93
|
| `brevo logout` | Clear stored credentials (`--force` to skip confirmation) |
|
|
94
94
|
| `brevo whoami` | Show the authenticated user |
|
|
95
95
|
| `brevo app init` | Guided setup — login, create app, and scaffold in one go |
|
|
96
|
-
| `brevo app create` | Create an OAuth app (`--name`, `--distribution private
|
|
97
|
-
| `brevo app list` | List
|
|
96
|
+
| `brevo app create` | Create an OAuth app (`--name`, `--distribution private`, repeatable `--redirect-uri`, `--logo-uri`) |
|
|
97
|
+
| `brevo app list` | List apps in your account |
|
|
98
98
|
| `brevo app credentials` | Show client ID and secret (`--app-id`, `--reveal-secret`) |
|
|
99
|
-
| `brevo app
|
|
99
|
+
| `brevo app upload` | Push `app-config.json` to Brevo after showing a local-vs-server diff (`--yes`) |
|
|
100
100
|
| `brevo app delete` | Delete an app (`--app-id`, `--force`) |
|
|
101
|
-
| `brevo app scaffold` |
|
|
101
|
+
| `brevo app scaffold` | Add a feature to the app in the current directory, or set an empty directory up for an app you already have — picked interactively, or named with `--app-id` (`--overwrite`, `--json`) |
|
|
102
102
|
| `brevo app start` | Run a scaffolded feature locally (e.g. `brevo app start oauth --port 3000`) |
|
|
103
|
+
| `brevo app available-scopes` | List the OAuth scopes the IdP supports (`--web` opens the catalog in a browser) |
|
|
103
104
|
|
|
104
105
|
Most commands require a successful `brevo login` first, except authentication/help flows (`brevo login`, `brevo logout`, `brevo app init`, `--help`). Every command accepts `--json` for machine-readable output.
|
|
105
106
|
|
|
107
|
+
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.
|
|
108
|
+
|
|
106
109
|
### Browser login
|
|
107
110
|
|
|
108
111
|
`brevo login` defaults to a browser-based sign-in. The CLI starts a temporary loopback server, opens your browser to the Brevo CLI login service, and stores the returned tokens in `~/.brevo/credentials.json`. Access tokens refresh automatically on expiry.
|
package/agent-context/AGENTS.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
This project uses the [Brevo Developer CLI](https://www.npmjs.com/package/@getbrevo/cli) (`brevo` binary) to manage Brevo OAuth applications.
|
|
5
5
|
|
|
6
|
+
## `brevo --help` is the source of truth
|
|
7
|
+
|
|
8
|
+
Brevo features that haven't been released are **not built into the published CLI at all** — not as hidden commands, not behind a flag or an env var. `brevo --help` and `brevo app --help` list everything the binary can do. Treat that as the complete surface and build from it.
|
|
9
|
+
|
|
10
|
+
What this means in practice:
|
|
11
|
+
|
|
12
|
+
- **If a command isn't in `--help`, it doesn't exist here.** Invoking it gives Commander's `unknown command` and exit `1`. That is a final answer, not a transient failure and not a permissions problem — there is no flag, config edit, account setting, or environment variable that reveals it. Don't retry, don't hunt for an alternative route, and don't tell the user to request access. Say the feature isn't available in this CLI and offer the nearest thing that works.
|
|
13
|
+
- **Don't act on a command you remember rather than one you can see.** Your training data may include commands from a newer or unreleased build of this CLI. `--help` in the current session is the only reliable source, and the table below documents only what a published build ships.
|
|
14
|
+
- **A flag can be rejected even when its command exists.** `--distribution` is a real flag, but a value tied to an unreleased feature is refused with *"That command is not available yet. It is part of a Brevo feature that has not been released."* Same rule: it's final. Use `--distribution private`.
|
|
15
|
+
|
|
16
|
+
The Brevo API enforces the same boundaries independently, so nothing is gained by routing around the CLI — a hand-rolled `curl` hits the platform's own refusal.
|
|
17
|
+
|
|
6
18
|
## AI agents — start here
|
|
7
19
|
|
|
8
20
|
Pick the path that matches your tooling:
|
|
@@ -50,7 +62,7 @@ Don't fall back to raw HTTP against `api.brevo.com` — the `brevo` binary is th
|
|
|
50
62
|
|
|
51
63
|
## When to use it
|
|
52
64
|
|
|
53
|
-
- Create, list,
|
|
65
|
+
- Create, list, upload changes to, or delete Brevo OAuth apps
|
|
54
66
|
- Scaffold a starter OAuth integration
|
|
55
67
|
- Run a local OAuth test server (`brevo app start oauth`)
|
|
56
68
|
- Inspect or rotate app credentials
|
|
@@ -63,12 +75,12 @@ Don't fall back to raw HTTP against `api.brevo.com` — the `brevo` binary is th
|
|
|
63
75
|
| `brevo logout` | Clear stored credentials (`--force`, `--json`) |
|
|
64
76
|
| `brevo whoami` | Show the authenticated account (`--json`) |
|
|
65
77
|
| `brevo app init` | Guided setup (login, create, scaffold) |
|
|
66
|
-
| `brevo app list` | List
|
|
67
|
-
| `brevo app create` | Create an app (`--name`, `--distribution`, `--redirect-uri`, `--logo-uri`, `--json`).
|
|
68
|
-
| `brevo app
|
|
69
|
-
| `brevo app credentials` | Show client ID / secret (`--app-id`, `--reveal-secret`, `--json`) |
|
|
70
|
-
| `brevo app delete` | Delete an app (`--app-id`, `--force`, `--json`) |
|
|
71
|
-
| `brevo app scaffold` |
|
|
78
|
+
| `brevo app list` | List apps (`--json`). Each row names its type. |
|
|
79
|
+
| `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. **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 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. |
|
|
81
|
+
| `brevo app credentials` | Show client ID / secret (`--app-id`, `--reveal-secret`, `--json`). **`--app-id` is required when scripting** — see the picker note below. Also backfills a missing top-level `version` / `distribution_type` into cwd's `app-config.json` when its `appId` matches (fill-only-when-missing, silent). |
|
|
82
|
+
| `brevo app delete` | Delete an app (`--app-id`, `--force`, `--json`). **`--app-id` is required when scripting** — see the picker note below. |
|
|
83
|
+
| `brevo app scaffold` | Add a feature to the app in the current directory (`--app-id`, `--overwrite`, `--json`). Requires an `app-config.json` in cwd **unless `--app-id <id>` is passed or you accept its bootstrap offer**; reads the linked app from it. `--app-id` in a directory with no config fetches that app and writes `app-config.json` + the base files first — the only way to get a config for an app that already exists, and the migration path off the removed `brevo app update --app-id`. Interactively, omitting `--app-id` in a config-less directory prints *"No app-config.json in this directory…"*, asks **"Set up a project for an app you already have?"** (default yes) and on yes shows an app picker; answering no exits `0` with the remaining routes on screen. Every interactive bootstrap (picker or `--app-id`) then asks `Output directory:` defaulted to `./<slugified app name>`, creates it and `cd`s into it — answer `.` to stay in the current directory — and the *Next steps* box opens with `cd <dir>`. Under `--json` or off a TTY there is no offer — it errors, listing the three ways out. It refuses, before any network call: if the directory is already linked to a different app — either the one the command ran in or the one it was pointed at (naming the app it is already linked to is a no-op), if the directory is **inside** an existing app project (a nested second config would make a later `brevo app upload` from there push the wrong app). Diffs the local config against the server and, on drift, updates `app-config.json` to match (on consent) before writing the feature files — including when a bootstrap is pointed at a directory that already holds a project, where answering **Merge** to the directory question does *not* skip that refresh. When feature files already exist, prompts Overwrite / Merge / Cancel (default Merge — existing files kept); `--overwrite` forces overwrite and skips the prompt. The scaffolded OAuth flow is the confidential-client flow: the token exchange is authenticated with the `CLIENT_SECRET` written into the generated `.env.local`. |
|
|
72
84
|
| `brevo app start oauth` | Run the scaffolded OAuth test server (`--port`) |
|
|
73
85
|
| `brevo app available-scopes` | List OAuth scopes supported by the IdP (`--json`, `--web`) |
|
|
74
86
|
| `brevo skill:cli install` | Install the brevo-cli Claude Code skill (Claude-only; auto-refreshes on every `brevo` run) |
|
|
@@ -78,30 +90,61 @@ Run `brevo --help` or `brevo <command> --help` for the full set.
|
|
|
78
90
|
|
|
79
91
|
## Conventions
|
|
80
92
|
|
|
81
|
-
- **Every command supports `--json`** — prefer this when parsing output programmatically.
|
|
82
|
-
- **`app-config.json
|
|
93
|
+
- **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.
|
|
94
|
+
- **`app-config.json` describes an OAuth app.** It carries `auth.scopes` / `auth.redirectUris` alongside the top-level `appId` / `appName` / `logoUri` / `version` / `distribution_type`. Write only the keys documented here — `brevo app upload` validates the whole file and rejects anything it doesn't recognise.
|
|
95
|
+
- **`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.
|
|
96
|
+
- **`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).
|
|
97
|
+
- **`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": "..." }`.
|
|
98
|
+
- **`app-config.json`** in the working directory pins the linked app — `brevo app upload` and `brevo app start` read from it. `upload` is the *only* command that pushes config changes, and it has no `--app-id` override (it always resolves the app from cwd's `app-config.json`, hard-erroring if that file is missing/invalid/lacks `appId`); `brevo app start` accepts `--app-id` to target a different app. The top-level `logoUri` string is pushed as `logo_uri`; leave it empty to keep the API value untouched. The top-level `version` string is round-tripped as `version` on the wire — `upload` sends the local value (falling back to the server's current value if locally absent) and writes back whatever the server confirms. `brevo app credentials` additionally backfills a missing top-level `version` / `distribution_type` into cwd's `app-config.json` when its `appId` matches the inspected app — fill-only-when-missing (never overwrites an existing local value), silent in all modes — so legacy projects that are never `upload`ed still converge to the current shape.
|
|
99
|
+
- **Commands that pick an app interactively refuse to do so when scripted.** `brevo app credentials` and `brevo app delete` fall back to an interactive app picker when `--app-id` is absent. Under `--json` **or** off a TTY that picker is refused up front — before any network call — with a `CliError` naming the exact command to run (`brevo app credentials --app-id <id>`) and exiting `1`. **Always pass `--app-id` when scripting these.** The refusal exists because the picker renders its choice list to stdout, which would otherwise corrupt the single-JSON-document contract below and leak app ids into whatever is parsing it. `brevo app delete` is the one that matters most: it is destructive, so a script that relied on the picker was never doing what its author thought.
|
|
100
|
+
- **There is no `brevo app update`.** It was removed and replaced by `brevo app upload`, with no shim and no flag-for-flag equivalent — change an app's name, redirect URLs, scopes or logo by editing `app-config.json`, then run `brevo app upload`. That is what to replace it with wherever you find it: a user's script, a CI job, a README, or your own recollection. Invoking it — with any of the old flags (`--name`, `--redirect-uri`, `--scope`, `--logo-uri`, `--app-id`), with `--help`, or as `brevo app help update` — prints a message naming `upload` and exits `1` **without uploading anything**, so a `1` from `brevo app update` means the command is gone, not that an upload failed. It is absent from every help screen, and needs no login to reach.
|
|
83
101
|
- **Credentials** live at `~/.brevo/credentials.json`. Never commit this file or any `.env.local`.
|
|
84
102
|
- **Non-interactive auth:** `BREVO_API_KEY=xkeysib-... brevo login`. The legacy `--api-key` flag was removed because it leaks into shell history.
|
|
85
|
-
- **Skip prompts:** `--force` for delete
|
|
103
|
+
- **Skip prompts:** `--force` for `app delete` and `logout`; `--yes` for `app upload`.
|
|
86
104
|
- **Forced update:** when the installed CLI is a full **major** version behind the latest npm release, every command except `--help`/`--version` prints a blocking update banner to stderr and exits `1` without running. Update with `npm install -g @getbrevo/cli` (or `yarn global add`). The gate honors the same opt-outs as the soft update notice (`BREVO_NO_UPDATE_NOTIFIER=1`, `--no-update-notifier`, CI, non-TTY), so it never fires in those contexts.
|
|
105
|
+
- **Update notice wording:** the update/force-update banners take their first line from the app-store service (`GET /cli/info`). It is called directly, not through the v3 API gateway, and needs no API key — so it works while logged out or with expired credentials. It runs once per invocation, **before** the command, and the response is cached at `~/.brevo/cli-info-cache.json` for **15 minutes**, keyed to the installed `cliVersion` — so reworded text or a new block reaches the CLI within minutes rather than after the old 12h npm-style TTL. Whether an update banner appears is still decided from the npm registry, and if the call fails the banner still appears with local wording (a failed call never overwrites a good cache entry).
|
|
106
|
+
- **Soft update notice on failures:** the non-blocking update banner also prints after a command *fails*, not just after it succeeds — so stderr may hold the error message followed by the update box. Exit codes are unchanged (still the command's own), the box never appears twice in one run, and a Ctrl-C abort skips it. Parse stderr accordingly, or set `BREVO_NO_UPDATE_NOTIFIER=1` / pass `--no-update-notifier`.
|
|
107
|
+
- **Server-side block:** `GET /cli/info` may answer `"is_blocked": true`, in which case every command except `--help` / `--version` prints a banner to stderr and exits `1` **without running** — independently of the npm major-version gate. Unlike the update notice, this is **not** suppressed by `BREVO_NO_UPDATE_NOTIFIER=1`, `--no-update-notifier`, CI, or a non-TTY: a hidden banner must never mean a hidden block. It fails open — a timeout, a non-2xx, or an unparseable body lets the command run, and only a literal `true` blocks. So a `brevo` call that suddenly exits `1` with an update banner may mean either a major-version gap or a server-side block; both are cleared by upgrading. Because the verdict is cached for 15 minutes, a lifted block can take up to 15 minutes to clear on a machine that already cached the blocked answer — delete `~/.brevo/cli-info-cache.json` for an immediate re-check. Note `--help` / `--version` are exempt from the block only, not from the lookup: they still fetch `/cli/info` and still show the server's wording on their update banner, they just never exit non-zero because of it.
|
|
87
108
|
- **Exit codes:** `0` success · `1` general error · `2` aborted · `3` auth · `4` network · `5` not found.
|
|
88
109
|
|
|
110
|
+
## JSON errors
|
|
111
|
+
|
|
112
|
+
Under `--json`, a failing command writes **one** JSON document to stdout describing the failure; the human-readable message still goes to stderr. The `error` key is the discriminator — no success payload has one:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{ "error": { "name": "CliError", "message": "Not authenticated. Run: brevo login", "exitCode": 1 } }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Field | Always present | Meaning |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| `name` | yes | Error class — `CliError`, `ApiError`, `AuthExpiredError`, `AbortError` |
|
|
121
|
+
| `message` | yes | Same text written to stderr |
|
|
122
|
+
| `exitCode` | yes | Matches the process exit code |
|
|
123
|
+
| `statusCode` | `ApiError` only | HTTP status behind the failure |
|
|
124
|
+
| `code` | `ApiError`, when classified | `AUTH_INVALID`, `AUTH_EXPIRED`, `ACCESS_DENIED`, `APP_NOT_FOUND`, `REDIRECT_INVALID`, `PORT_IN_USE`, `NETWORK_ERROR`, `RATE_LIMITED`, `APP_LIMIT_REACHED`, `REGISTRY_ERROR`, `AUTH_GATEWAY` |
|
|
125
|
+
|
|
126
|
+
Stdout is always **exactly one** parseable document. Commands that already describe their own failure keep their shape rather than emitting this envelope — `brevo whoami --json` returns `{"authenticated": false, "reason": "no_key"}` (exit `1`). Check for `error` first, then fall back to the command's own shape.
|
|
127
|
+
|
|
128
|
+
## Command help
|
|
129
|
+
|
|
130
|
+
`brevo --help` prints the grouped overview of every command. `brevo <command> --help` prints that command's own usage line, arguments, flags, and examples — e.g. `brevo app scaffold --help` documents `--app-id` / `--overwrite` / `--json`. Use it to confirm a flag exists on the installed version rather than assuming from this file.
|
|
131
|
+
|
|
89
132
|
## Scopes
|
|
90
133
|
|
|
91
134
|
- New apps created via `brevo app create` default to `contacts:read`, `contacts:write`, `crm:read`, `crm:write`. The CLI prints these on success.
|
|
92
|
-
-
|
|
135
|
+
- To change scopes, redirect URLs, name, logo, or version, edit the corresponding field in `app-config.json` directly and run `brevo app upload` — there is no `--scope`/`--redirect-uri`/`--name`/`--logo-uri` flag on `upload`. Same normalization (comma/whitespace-split, de-duped) is applied to `auth.scopes` when read from `app-config.json`. `distribution_type` cannot be changed this way — it is immutable after `app create`, and `upload` errors if the local value drifts from the server.
|
|
93
136
|
- `brevo app available-scopes [--json] [--web]` lists the OAuth scopes the IdP currently supports. It reads a **public** catalog and works **without `brevo login`** (no API key needed). Text output groups names by category (e.g. `account`, `data_crm`, `messaging`); `--json` returns a flat `{ scopes: string[] }` of names. OIDC-reserved scopes (`openid`, `profile`, `email`, `offline_access`) and magic wildcards are excluded. The CLI validates scope **format** locally (must match `[A-Za-z0-9][A-Za-z0-9:_.-]*`) but does **not** validate that a scope is recognized by the IdP — server returns 400 on unknown scopes.
|
|
94
137
|
- Passing `--web` to `brevo app available-scopes` additionally starts a short-lived loopback HTTP server on `127.0.0.1:<ephemeral>` rendering the same catalog as a styled HTML page and opens the user's browser. It stays in the foreground until Ctrl+C. Without `--web` the command exits after printing the list — TTY detection no longer triggers the browser. `--json` always suppresses the browser (`--json` returns before `--web` is evaluated).
|
|
95
138
|
|
|
96
139
|
### Legacy `'all'` scope deprecation
|
|
97
140
|
|
|
98
|
-
The legacy catch-all `'all'` OAuth scope is deprecated. The CLI **blocks** `brevo app
|
|
141
|
+
The legacy catch-all `'all'` OAuth scope is deprecated. The CLI **blocks** `brevo app upload` and `brevo app start oauth` when scopes still contain `'all'` (no escape hatch, no silent rewrite); the only mutating path that proceeds is editing `auth.scopes` in `app-config.json` and running `upload`. To handle a legacy app:
|
|
99
142
|
|
|
100
143
|
1. **Detect** `'all'` in `auth.scopes` of a local `app-config.json`, or on a remote app via `brevo app list --json` — affected apps carry `"legacy_all_scope": true` (text output appends `(legacy 'all' — deprecated)` to the scopes line).
|
|
101
144
|
2. **Prompt the user to pick granular scopes** — use `brevo app available-scopes --json` for the catalog, or fall back to the four defaults (`contacts:read`, `contacts:write`, `crm:read`, `crm:write`).
|
|
102
|
-
3. **Migrate**
|
|
145
|
+
3. **Migrate** by editing `auth.scopes` in `app-config.json` to replace `'all'` with the granular scopes your integration uses, then run `brevo app upload` (the summary shows a "Migrating from legacy 'all' scope" line and `all (removed)`).
|
|
103
146
|
|
|
104
|
-
`
|
|
147
|
+
Writing `app-config.json` for an app whose remote scopes contain `'all'` never propagates it: the file keeps the app's remaining granular scopes (or the four default scopes when `'all'` was the only scope), with a one-line substitution notice (suppressed under `--json`). This happens when `brevo app create` writes the base config and when `brevo app scaffold` rewrites it after a detected server drift. Note the substitution is local-only — the remote app still needs the `app-config.json` edit + `brevo app upload` migration above.
|
|
105
148
|
|
|
106
149
|
## Environment variables
|
|
107
150
|
|
|
@@ -110,10 +153,11 @@ The legacy catch-all `'all'` OAuth scope is deprecated. The CLI **blocks** `brev
|
|
|
110
153
|
| `BREVO_API_KEY` | Non-interactive login |
|
|
111
154
|
| `BREVO_API_URL` | Override API base (HTTPS required, except `localhost`) |
|
|
112
155
|
| `BREVO_OAUTH_PROXY_URL` | Override OAuth proxy used by browser login |
|
|
156
|
+
| `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`) |
|
|
113
157
|
| `BREVO_CONFIG_HOME` | Override credentials directory (default `~/.brevo/`) |
|
|
114
158
|
| `BREVO_CLAUDE_HOME` | Override Claude Code home used by `skill:cli` (default `~/.claude/`) |
|
|
115
159
|
| `BREVO_NO_SKILL_AUTOREFRESH` | Set to `1` to suppress automatic skill refresh on `brevo` runs |
|
|
116
|
-
| `BREVO_NO_UPDATE_NOTIFIER` | Set to `1` to suppress the npm update-available notice **and** the blocking major-version force-update gate |
|
|
160
|
+
| `BREVO_NO_UPDATE_NOTIFIER` | Set to `1` to suppress the npm update-available notice **and** the blocking major-version force-update gate. Does **not** suppress a server-side `is_blocked` block. |
|
|
117
161
|
| `BREVO_DEBUG=1` or `--debug` | Verbose HTTP and error logging |
|
|
118
162
|
|
|
119
163
|
## Safety
|
package/agent-context/SKILL.md
CHANGED
|
@@ -7,6 +7,18 @@ description: Use when working in a project that uses the Brevo Developer CLI (th
|
|
|
7
7
|
|
|
8
8
|
This project uses the Brevo Developer CLI to create and manage Brevo OAuth applications. Treat the `brevo` binary as the canonical entry point — don't shell out to `curl https://api.brevo.com/...` for things the CLI already covers.
|
|
9
9
|
|
|
10
|
+
## `brevo --help` is the source of truth
|
|
11
|
+
|
|
12
|
+
Brevo features that haven't been released are **not built into the published CLI at all** — not as hidden commands, not behind a flag or an env var. `brevo --help` and `brevo app --help` list everything the binary can do. Treat that as the complete surface and build from it.
|
|
13
|
+
|
|
14
|
+
What this means in practice:
|
|
15
|
+
|
|
16
|
+
- **If a command isn't in `--help`, it doesn't exist here.** Invoking it gives Commander's `unknown command` and exit `1`. That is a final answer, not a transient failure and not a permissions problem — there is no flag, config edit, account setting, or environment variable that reveals it. Don't retry, don't hunt for an alternative route, and don't tell the user to get access. Say the feature isn't available in this CLI and offer the nearest thing that works.
|
|
17
|
+
- **Don't act on a command you remember rather than one you can see.** Your recollection of the Brevo CLI may include commands from a newer or unreleased build. `--help` in the current session is the only reliable source.
|
|
18
|
+
- **A flag can be rejected even when its command exists.** `--distribution` is a real flag, but a value tied to an unreleased feature is refused with *"That command is not available yet. It is part of a Brevo feature that has not been released."* Same rule: it's final. Use `--distribution private`.
|
|
19
|
+
|
|
20
|
+
The Brevo API enforces the same boundaries independently, so nothing is gained by trying to route around the CLI — a hand-rolled `curl` hits the platform's own refusal.
|
|
21
|
+
|
|
10
22
|
> **Reading this from the repo rather than `~/.claude/skills/brevo-cli/`?**
|
|
11
23
|
>
|
|
12
24
|
> - **If you're Claude Code** (CLI or desktop app — any agent that reads `~/.claude/skills/`): install it as a skill with `brevo skill:cli install` so your tooling loads it natively and keeps it auto-refreshed. The repo copy is bundled with the npm package; the installed copy is what Claude actually consumes. Note: the Claude Desktop **chat** app does not read `~/.claude/skills/`, so the installed skill won't load there — only Claude Code surfaces pick it up.
|
|
@@ -40,54 +52,98 @@ Don't fall back to raw HTTP against `api.brevo.com` — the `brevo` binary is th
|
|
|
40
52
|
- "Authenticate" → `brevo login` (or `BREVO_API_KEY=xkeysib-... brevo login` for CI)
|
|
41
53
|
- "Who am I logged in as?" → `brevo whoami --json`
|
|
42
54
|
- "Show / pick an app" → `brevo app list --json`
|
|
43
|
-
- "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`).
|
|
44
|
-
- "Update app metadata" → `brevo app
|
|
45
|
-
- "Get client credentials" → `brevo app credentials --app-id <id> --json` (add `--reveal-secret` to print the secret)
|
|
46
|
-
- "
|
|
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
|
+
- "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). **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.
|
|
57
|
+
- "Get client credentials" → `brevo app credentials --app-id <id> --json` (add `--reveal-secret` to print the secret). **`--app-id` is not optional here** — without it the command wants an interactive app picker, and under `--json`/off a TTY it refuses with exit `1` rather than prompting.
|
|
58
|
+
- "Set up a project for an app that already exists" → `brevo app scaffold --app-id <id>` (`brevo app list` gives the IDs). Fetches the app and writes `app-config.json` + the base files, then adds a feature as below. Interactively it asks which directory to write into first (default `./<slugified app name>`); under `--json`/off a TTY it uses the current one. **This is the only way to get an `app-config.json` for an existing app** — `brevo app create` makes a *new* app, and `brevo app upload` only ever reads the project in the current directory. It refuses if the directory is already linked to a *different* app; pointing it at the app the directory is already linked to is a no-op. **Interactively you can omit `--app-id`**: plain `brevo app scaffold` in a directory with no `app-config.json` says so, asks *"Set up a project for an app you already have?"* (default yes), and on yes lists the account's apps so you can pick one — no need to look the ID up first. **Every interactive bootstrap (picker or `--app-id`) then asks `Output directory:`, defaulted to `./<the app's name slugified>`** — the same question `brevo app create` asks — creates that directory and `cd`s into it, so the files don't land in whatever folder you happened to be standing in; answer `.` to use the current directory instead. It then writes the project (`app-config.json` + the base files), shows what it wrote, and asks *"Scaffold the Test OAuth App?"* (default yes) — declining is normal and leaves the project in place, exit `0`. The *Next steps* box opens with `cd <dir>`, because the CLI can only move its own process, never your shell. **Under `--json` or off a TTY there is no directory question and the files go into the current directory**, so scripted `brevo app scaffold --app-id <id>` runs are unchanged — `mkdir` and `cd` yourself first if you want them somewhere specific. Answering **no** is a normal outcome, not an error: it exits `0` after printing the remaining routes (`brevo app create` here, or `cd` into an existing project). **Always pass `--app-id` when scripting** — the offer needs a terminal, so under `--json` or off a TTY the command errors instead of prompting. **If the directory you point it at already holds a project, the bootstrap is a refresh, not a fresh write**: the config found there is diffed against the server and rewritten only on consent (*"…will update app-config.json to match the server. Continue?"*, default yes) — answering **Merge** at the directory question does not suppress that, because merging keeps the file that exists and so would skip the very file a bootstrap is for. No drift means `app-config.json` is left alone with a one-line notice, and the feature still gets added. A directory holding a project for a **different** app is refused outright, naming both apps. Two refusals apply to both forms, before any write: that different-app case, and the directory must not be **inside** an existing project (a nested second `app-config.json` would make a later `brevo app upload` push the wrong app).
|
|
59
|
+
- "Add a feature (e.g. the OAuth test server) to an existing project" → `brevo app scaffold` (run **inside** the project directory; it reads the linked app from `app-config.json`, so `--app-id` is only needed to bootstrap a directory that has none). Not needed right after `app create` if you already accepted the feature prompt there. If feature files already exist it prompts Overwrite / Merge / Cancel (default Merge); pass `--overwrite` to force a full overwrite without prompting. **The scaffolded OAuth flow is the confidential-client flow:** `/auth/callback` authenticates the token exchange with the `CLIENT_SECRET` written into the scaffolded `.env.local`.
|
|
47
60
|
- "Run the OAuth test server" → `brevo app start oauth --port 3009` (must be inside the scaffolded directory)
|
|
48
|
-
- "Delete an app" → `brevo app delete --app-id <id> --force`
|
|
61
|
+
- "Delete an app" → `brevo app delete --app-id <id> --force`. **`--app-id` is not optional here either** — omitting it means an interactive picker, which under `--json`/off a TTY is refused with exit `1`. Never script a delete without naming the app.
|
|
49
62
|
- "List supported OAuth scopes" → `brevo app available-scopes --json`
|
|
50
63
|
- "Sign out" → `brevo logout --force`
|
|
51
64
|
|
|
52
65
|
## Hard rules
|
|
53
66
|
|
|
54
|
-
1. **Always pass `--json`** when you intend to parse output. Every command supports it.
|
|
67
|
+
1. **Always pass `--json`** when you intend to parse output. Every command supports it, **on success and on failure alike** — a failing `--json` run writes a single `{"error": {...}}` document to stdout (see *JSON errors* below), so you can read the reason instead of only seeing a non-zero exit.
|
|
55
68
|
2. **Never print, log, or commit** API keys (`xkeysib-…`), client secrets, refresh tokens, or contents of `~/.brevo/credentials.json` / `.env.local`. Redact before sharing diagnostics.
|
|
56
69
|
3. **Don't use `--api-key`** — the flag was removed. Use the `BREVO_API_KEY` env var.
|
|
57
|
-
4.
|
|
70
|
+
4. **`brevo app create` refuses to run inside an already-linked directory** (`app-config.json` present) — `cd` elsewhere or use `brevo app scaffold` there instead. **`brevo app scaffold` requires an `app-config.json` in the current directory unless you pass `--app-id` or answer its bootstrap offer** (it adds a feature to an already-created project); with none present and no `--app-id` it offers to set the directory up for an existing app when interactive, and otherwise errors, listing the three ways out (`cd` into a project, `--app-id` to set this directory up for an app you already have, or `brevo app create` for a new one). With `--app-id <id>` in a directory that has no config it fetches that app, asks (interactively only) which directory to write into — default `./<slugified app name>`, `.` to stay put — writes `app-config.json` + the base files there, and continues; that is the migration path off the removed `brevo app update --app-id`, and off a TTY or under `--json` it writes into the current directory with no prompt. If the directory it is told to write into already holds a project for the **same** app, it diffs that config against the server and rewrites it only on consent — the directory prompt's **Merge** answer does not silently skip the refresh, and no drift leaves `app-config.json` untouched with a notice. It refuses (before any network call) if the directory is already linked to a *different* app — whether that is the directory the command ran in or the one it was pointed at — or if the directory is **inside** an existing app project (a nested second `app-config.json` would make a later `brevo app upload` from there push the wrong app — `cd` to the project root or outside it). It reads the linked app from that config, diffs the config against the server, and if fields drifted it tells you and (on consent) rewrites `app-config.json` 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; Cancel aborts). Pass `--overwrite` to force a full overwrite and skip that prompt (works interactively and under `--json`). **Under `--json` it never prompts**: a config diff comes back as `{ "cancelled": true, "reason": "...", "diffs": [...] }`; otherwise it scaffolds the feature (merging existing files unless `--overwrite` is passed) and returns `{ "scaffolded": <n>, "directory": "..." }`.
|
|
58
71
|
5. **Prefer flag-driven over interactive** in agent contexts: `--name`, `--app-id`, `--force`, `--yes` so the command doesn't block on prompts.
|
|
72
|
+
6. **Write only the `app-config.json` keys this file documents.** `brevo app upload` validates the whole file and rejects keys it doesn't recognise, so an invented block fails at upload rather than doing anything useful.
|
|
59
73
|
|
|
60
74
|
## Locating the linked app
|
|
61
75
|
|
|
62
|
-
If `app-config.json` exists in the working directory, it pins the app — `brevo app
|
|
76
|
+
If `app-config.json` exists in the working directory, it pins the app — `brevo app upload` and `brevo app start` use it automatically. `brevo app start` accepts an `--app-id` override to target a different app; `upload` does **not** — it only ever reads cwd's `app-config.json`, hard-erroring if that file is missing, invalid, or lacks `appId`.
|
|
77
|
+
|
|
78
|
+
`app-config.json` carries an optional top-level `logoUri` string. When set, `brevo app upload` pushes it as `logo_uri`; when empty / absent, the field is left untouched on the API.
|
|
79
|
+
|
|
80
|
+
`app-config.json` also carries a top-level `version` string, shown by `brevo app create`/`brevo app list`. `brevo app upload` sends it on the wire as `version` (falling back to the server's current value if locally absent) and writes back whatever version the server confirms after a successful upload.
|
|
63
81
|
|
|
64
|
-
`app-config.json`
|
|
82
|
+
`brevo app credentials` also backfills a legacy `app-config.json` toward the current shape: when the file exists in cwd and its `appId` matches the app being inspected, any missing top-level `version` / `distribution_type` is filled in from the server (fill-only-when-missing — an existing local value is never overwritten). This runs silently in all modes; human output prints a one-line note when something was written. It's how projects that are never `upload`ed still converge.
|
|
65
83
|
|
|
66
84
|
## Scopes
|
|
67
85
|
|
|
68
|
-
- New apps created via `brevo app create` default to `contacts:read`, `contacts:write`, `crm:read`, `crm:write`. The CLI prints the default set on success and points to `brevo app
|
|
69
|
-
-
|
|
86
|
+
- New apps created via `brevo app create` default to `contacts:read`, `contacts:write`, `crm:read`, `crm:write`. The CLI prints the default set on success and points to editing `app-config.json` + `brevo app upload` for changes.
|
|
87
|
+
- To add, remove, or change scopes: edit `auth.scopes` in `app-config.json` directly, then run `brevo app upload`. Comma- or whitespace-separated values are normalized on read, whether they sit in a single array entry (`["crm:read", "crm:write, campaigns:read"]`) or replace the array entirely (`"crm:write, campaigns:read"`) — either becomes two scopes. To see what's currently set, run `brevo app credentials --app-id <id> --json`.
|
|
70
88
|
- `brevo app available-scopes [--json] [--web]` lists the OAuth scopes the IdP currently supports. It reads a **public** catalog and works **without `brevo login`** (no API key needed). Text output groups names by category (e.g. `account`, `data_crm`, `messaging`); `--json` returns a flat `{ scopes: string[] }` of names. OIDC-reserved scopes (`openid`, `profile`, `email`, `offline_access`) and magic wildcards are excluded. The CLI validates scope **format** locally (must match `[A-Za-z0-9][A-Za-z0-9:_.-]*`) but does **not** validate that a scope is recognized by the IdP — use `app available-scopes` to confirm spelling before passing an unfamiliar scope.
|
|
71
89
|
- Passing `--web` to `brevo app available-scopes` **also opens a browser** to a styled local page (loopback `http://127.0.0.1:<port>/`) and stays running until Ctrl+C. Without `--web` the command exits after printing the list — TTY detection no longer triggers the browser. `--json` always suppresses the browser, so agent invocations using `--json` behave the same regardless of `--web`.
|
|
72
90
|
|
|
73
91
|
### Legacy `'all'` scope deprecation
|
|
74
92
|
|
|
75
|
-
The legacy catch-all `'all'` OAuth scope is deprecated. The CLI **blocks** `brevo app
|
|
93
|
+
The legacy catch-all `'all'` OAuth scope is deprecated. The CLI **blocks** `brevo app upload` and `brevo app start oauth` when scopes still contain `'all'` (no escape hatch, no silent rewrite); the only mutating path that proceeds is editing `auth.scopes` in `app-config.json` and running `upload`. To handle a legacy app:
|
|
76
94
|
|
|
77
95
|
1. **Detect** `'all'` in `auth.scopes` of a local `app-config.json`, or on a remote app via `brevo app list --json` — affected apps carry `"legacy_all_scope": true` (text output appends `(legacy 'all' — deprecated)` to the scopes line).
|
|
78
96
|
2. **Prompt the user to pick granular scopes** — use `brevo app available-scopes --json` for the catalog, or fall back to the four defaults (`contacts:read`, `contacts:write`, `crm:read`, `crm:write`).
|
|
79
|
-
3. **Migrate**
|
|
97
|
+
3. **Migrate** by editing `auth.scopes` in `app-config.json` to replace `'all'` with the granular scopes your integration uses, then run `brevo app upload` (the summary shows a "Migrating from legacy 'all' scope" line and `all (removed)`).
|
|
80
98
|
|
|
81
|
-
`
|
|
99
|
+
Writing `app-config.json` for an app whose remote scopes contain `'all'` never propagates it: the file keeps the app's remaining granular scopes (or the four default scopes when `'all'` was the only scope), with a one-line substitution notice (suppressed under `--json`). This applies when `brevo app create` writes the base config, and when `brevo app scaffold` rewrites it after a detected server drift. Note the substitution is local-only — the remote app still needs the `app-config.json` edit + `brevo app upload` migration above.
|
|
82
100
|
|
|
83
101
|
## Exit codes
|
|
84
102
|
|
|
85
103
|
`0` success · `1` general error · `2` aborted · `3` auth failure · `4` network · `5` not found.
|
|
86
104
|
|
|
105
|
+
## JSON errors
|
|
106
|
+
|
|
107
|
+
Under `--json`, a command that fails writes **one** JSON document to stdout describing the failure, and the human-readable message still goes to stderr. The `error` key is the discriminator — no success payload has one:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{ "error": { "name": "CliError", "message": "Not authenticated. Run: brevo login", "exitCode": 1 } }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`name` is the error class (`CliError`, `ApiError`, `AuthExpiredError`, `AbortError`), `message` is the same text printed to stderr, and `exitCode` matches the process exit code. An `ApiError` adds `statusCode` (the HTTP status) and, when the API classified the failure, `code` — one of `AUTH_INVALID`, `AUTH_EXPIRED`, `ACCESS_DENIED`, `APP_NOT_FOUND`, `REDIRECT_INVALID`, `PORT_IN_USE`, `NETWORK_ERROR`, `RATE_LIMITED`, `APP_LIMIT_REACHED`, `REGISTRY_ERROR`, `AUTH_GATEWAY`:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{ "error": { "name": "ApiError", "message": "App not found", "exitCode": 5, "code": "APP_NOT_FOUND", "statusCode": 404 } }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Two things to rely on: stdout is always **exactly one** parseable document, and commands that describe their own failure keep doing so instead of emitting this envelope — `brevo whoami --json` still returns `{"authenticated": false, "reason": "no_key"}` (exit `1`). Check for `error` first, then fall back to the command's own shape.
|
|
120
|
+
|
|
121
|
+
## Command help
|
|
122
|
+
|
|
123
|
+
`brevo --help` prints the grouped overview of every command. `brevo <command> --help` prints that command's own usage line, arguments, flags, and examples — e.g. `brevo app scaffold --help` documents `--app-id` / `--overwrite` / `--json`. When you need to confirm a flag exists on the version actually installed, read it from there rather than assuming from this file.
|
|
124
|
+
|
|
87
125
|
## Forced update
|
|
88
126
|
|
|
89
127
|
When the installed CLI is a full **major** version behind the latest npm release, every command except `--help` / `--version` prints a blocking update banner to stderr and exits `1` **without running** — so a `brevo` call that suddenly exits `1` with an update banner means the CLI must be upgraded (`npm install -g @getbrevo/cli` or `yarn global add @getbrevo/cli`) before it will work. The gate honors the same opt-outs as the soft update notice (`BREVO_NO_UPDATE_NOTIFIER=1`, `--no-update-notifier`, CI, non-TTY), so it never fires in those contexts.
|
|
90
128
|
|
|
129
|
+
## Update notice wording
|
|
130
|
+
|
|
131
|
+
The update banner's first line comes from the app-store service (`GET /cli/info`). It is called directly rather than through the v3 API gateway and requires no API key, so it works while logged out or with expired credentials. It runs once per invocation, **before** the command, and the response is cached at `~/.brevo/cli-info-cache.json` for **15 minutes**, keyed to the installed `cliVersion` — so reworded text or a new block reaches the CLI within minutes rather than after the old 12h npm-style TTL, without a network call on every single command. Whether an update banner appears is still decided from the npm registry, exactly as before, and a failed call just means the banner uses local wording (a failed call never overwrites a good cache entry). `BREVO_APP_STORE_URL` overrides that service's base URL for non-production testing.
|
|
132
|
+
|
|
133
|
+
## Server-side block
|
|
134
|
+
|
|
135
|
+
The same response may carry `"is_blocked": true`. When it does, every command except `--help` / `--version` prints a banner to stderr and exits `1` **without running** — a separate mechanism from the major-version gate above, and one the server can turn on without shipping a new CLI.
|
|
136
|
+
|
|
137
|
+
Those two are exempt from the **block**, not from the lookup: they still call `/cli/info` and still render the server's wording on their update banner. They simply never exit non-zero because of it, so a blocked CLI can always still report what version it is.
|
|
138
|
+
|
|
139
|
+
Two things to know when you hit this:
|
|
140
|
+
|
|
141
|
+
- **The notice opt-outs do not apply.** `BREVO_NO_UPDATE_NOTIFIER=1`, `--no-update-notifier`, CI, and a non-TTY all suppress the *notice*; none of them suppress a block. Don't suggest them as a workaround — the only fix is upgrading the CLI.
|
|
142
|
+
- **It fails open.** A timeout, a non-2xx, or an unparseable body lets the command run normally; only a literal `true` blocks. So a network problem never manifests as a block.
|
|
143
|
+
- **A block can persist up to 15 minutes after the server lifts it, and vice versa**, because the whole response is cached. If you need a verdict that reflects the server's *current* state, delete `~/.brevo/cli-info-cache.json` (or bump the version) before checking.
|
|
144
|
+
|
|
145
|
+
The soft (non-blocking) notice also prints after a command **fails**, not just after it succeeds — so stderr can hold the error message followed by the update box. This does not change exit codes: a failing command still exits with its own code, and the box is never printed twice in one run. A Ctrl-C abort skips it. If you parse stderr strictly, suppress the notice with `BREVO_NO_UPDATE_NOTIFIER=1` or `--no-update-notifier`.
|
|
146
|
+
|
|
91
147
|
## Before sharing or committing output
|
|
92
148
|
|
|
93
149
|
* [ ] No `xkeysib-…` API keys, client secrets, refresh tokens, or contents of `~/.brevo/credentials.json` / `.env.local` in messages, logs, or diffs.
|