@wabery/cli 0.11.5 → 0.13.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/README.md +60 -9
- package/dist/api-client.d.ts +5 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +24 -4
- package/dist/api-client.js.map +1 -1
- package/dist/cli.js +130 -2
- package/dist/cli.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +405 -28
- package/dist/mcp-server.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Wabery CLI and MCP Server
|
|
2
2
|
|
|
3
3
|
Wabery is a developer platform for building messaging workflows on WhatsApp,
|
|
4
|
-
Instagram, and Messenger. It provides APIs for channels, contacts,
|
|
5
|
-
conversations, messages, WhatsApp Flows, hosted functions,
|
|
6
|
-
dispatches, submissions, registration intents, and signed webhooks,
|
|
7
|
-
experience instead of wiring together a
|
|
4
|
+
Instagram, and Messenger. It provides APIs for channels, contacts, contact
|
|
5
|
+
imports, broadcasts, conversations, messages, WhatsApp Flows, hosted functions,
|
|
6
|
+
automations, dispatches, submissions, registration intents, and signed webhooks,
|
|
7
|
+
so you can build your own product experience instead of wiring together a
|
|
8
|
+
closed inbox tool.
|
|
8
9
|
|
|
9
10
|
This package ships the `wabery` command and a local MCP server for AI coding
|
|
10
11
|
agents such as Claude Code, Codex, and OpenCode.
|
|
@@ -163,11 +164,18 @@ wabery mcp
|
|
|
163
164
|
|
|
164
165
|
MCP starts in read-only mode. To allow mutation tools for a trusted local
|
|
165
166
|
session, set `WABERY_MCP_MODE=write` or generate config with `--write`.
|
|
166
|
-
|
|
167
|
+
In local stdio mode, high-risk tools also require confirmation tokens:
|
|
167
168
|
|
|
168
169
|
- `wabery_apply_config`: `confirmation_token: "apply_config"`
|
|
169
170
|
- `wabery_publish_flow`: `confirmation_token: "publish:<flow_id>"`
|
|
170
171
|
- `wabery_unenroll_contact` with `erase=true`: `confirmation_token: "erase:<contact_id>"`
|
|
172
|
+
- `wabery_send_broadcast`: `confirmation_token: "send_broadcast:<broadcast_id>"`
|
|
173
|
+
- `wabery_schedule_broadcast`: `confirmation_token: "schedule_broadcast:<broadcast_id>"`
|
|
174
|
+
- `wabery_cancel_broadcast`: `confirmation_token: "cancel_broadcast:<broadcast_id>"`
|
|
175
|
+
|
|
176
|
+
The hosted OAuth connector does not trust model-supplied confirmation strings.
|
|
177
|
+
It asks the MCP client to show a person-controlled elicitation prompt and fails
|
|
178
|
+
closed if the client cannot do so.
|
|
171
179
|
|
|
172
180
|
## Claude Code
|
|
173
181
|
|
|
@@ -289,7 +297,18 @@ Installed CLI:
|
|
|
289
297
|
- `wabery_enroll_contact`
|
|
290
298
|
- `wabery_list_contacts`
|
|
291
299
|
- `wabery_get_contact`
|
|
300
|
+
- `wabery_list_contact_imports`
|
|
301
|
+
- `wabery_get_contact_import`
|
|
292
302
|
- `wabery_unenroll_contact`
|
|
303
|
+
- `wabery_list_broadcasts`
|
|
304
|
+
- `wabery_get_broadcast`
|
|
305
|
+
- `wabery_create_broadcast`
|
|
306
|
+
- `wabery_prepare_broadcast_audience`
|
|
307
|
+
- `wabery_list_broadcast_recipients`
|
|
308
|
+
- `wabery_send_broadcast`
|
|
309
|
+
- `wabery_schedule_broadcast`
|
|
310
|
+
- `wabery_cancel_broadcast`
|
|
311
|
+
- `wabery_duplicate_broadcast`
|
|
293
312
|
- `wabery_list_conversations`
|
|
294
313
|
- `wabery_get_conversation`
|
|
295
314
|
- `wabery_list_conversation_messages`
|
|
@@ -312,6 +331,12 @@ keys. After `wabery_send_message`, use `wabery_get_message` to inspect provider
|
|
|
312
331
|
status and Meta failure details. Use `wabery_get_template` with refresh or
|
|
313
332
|
`wabery_wait_template` to track approval.
|
|
314
333
|
|
|
334
|
+
For broadcasts, create a draft, prepare exactly one audience source, wait for
|
|
335
|
+
`ready`, and show the user the frozen counts before requesting confirmation.
|
|
336
|
+
CSV files are uploaded with `wabery contacts import`; agents can inspect
|
|
337
|
+
completed imports through the contact-import MCP tools. Never infer permission
|
|
338
|
+
to send from a request to create or review a campaign.
|
|
339
|
+
|
|
315
340
|
Hosted function MCP tools let agents create project-scoped TypeScript handlers,
|
|
316
341
|
deploy source, test them, expose them as tool-callable logic, invoke them with
|
|
317
342
|
structured arguments, and read invocation logs. Write-mode function tools require
|
|
@@ -345,6 +370,17 @@ wabery contacts update "contact_id" \
|
|
|
345
370
|
--metadata '{"linked": true}'
|
|
346
371
|
```
|
|
347
372
|
|
|
373
|
+
For a larger audience, upload a CSV and wait for completion:
|
|
374
|
+
|
|
375
|
+
```sh
|
|
376
|
+
wabery contacts import contacts.csv \
|
|
377
|
+
--phone-column phone --name-column name \
|
|
378
|
+
--language-column language --tags-column tags \
|
|
379
|
+
--consent '{"categories":["MARKETING"],"source":"signup_form","method":"web_form"}'
|
|
380
|
+
wabery contacts imports
|
|
381
|
+
wabery contacts import-status "import_id"
|
|
382
|
+
```
|
|
383
|
+
|
|
348
384
|
For self-serve customer onboarding, mint a single-use WhatsApp registration link
|
|
349
385
|
and poll it with the publishable key used by your widget:
|
|
350
386
|
|
|
@@ -356,7 +392,22 @@ wabery registration-intents create \
|
|
|
356
392
|
wabery registration-intents get "intent_id"
|
|
357
393
|
```
|
|
358
394
|
|
|
359
|
-
3.
|
|
395
|
+
3. To send a broadcast, create and prepare a draft, poll until `ready`, and
|
|
396
|
+
inspect recipients before sending:
|
|
397
|
+
|
|
398
|
+
```sh
|
|
399
|
+
wabery broadcasts create --body @broadcast.json
|
|
400
|
+
wabery broadcasts prepare "broadcast_id" --body @audience.json
|
|
401
|
+
wabery broadcasts get "broadcast_id"
|
|
402
|
+
wabery broadcasts recipients "broadcast_id"
|
|
403
|
+
wabery broadcasts send "broadcast_id"
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`audience.json` must contain exactly one of `contactIds`, `importId`, or
|
|
407
|
+
`filter`. Broadcasts require a dedicated WhatsApp channel, approved templates,
|
|
408
|
+
and category-specific consent. See https://wabery.com/docs/broadcasts.
|
|
409
|
+
|
|
410
|
+
4. Generate and validate `wabery.config.json`:
|
|
360
411
|
|
|
361
412
|
```sh
|
|
362
413
|
wabery config init wabery.config.json --project-id "proj_or_agent_id"
|
|
@@ -386,7 +437,7 @@ select the project in the dashboard, create the project-scoped API key with
|
|
|
386
437
|
`wabery login`, run `config apply` to point the webhook, and run `wabery env` to
|
|
387
438
|
emit the secret for your deploy.
|
|
388
439
|
|
|
389
|
-
|
|
440
|
+
5. Publish a flow explicitly after review. Without `--wait`, this queues the
|
|
390
441
|
publish job and returns immediately; `--wait` polls until Meta reports it
|
|
391
442
|
published, or fails with the rejection reason:
|
|
392
443
|
|
|
@@ -409,7 +460,7 @@ wabery flows reconcile "flow_id" # re-sync status from Meta (re-adopts a drif
|
|
|
409
460
|
wabery flows delete "flow_id" # draft → delete on Meta, published → deprecate
|
|
410
461
|
```
|
|
411
462
|
|
|
412
|
-
|
|
463
|
+
6. Send a published flow:
|
|
413
464
|
|
|
414
465
|
```sh
|
|
415
466
|
wabery channels list
|
|
@@ -455,7 +506,7 @@ wabery messages send --channel-id "channel_id" \
|
|
|
455
506
|
--sticker-link "https://example.com/sticker.webp"
|
|
456
507
|
```
|
|
457
508
|
|
|
458
|
-
|
|
509
|
+
7. Deploy hosted logic for a workflow or Business Agent tool:
|
|
459
510
|
|
|
460
511
|
```sh
|
|
461
512
|
wabery functions create --slug quote-customer \
|
package/dist/api-client.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface WaberyClientOptions {
|
|
|
8
8
|
projectIdProvider?: () => string | undefined;
|
|
9
9
|
/** Working directory for repo-local `.wabery.json` discovery. */
|
|
10
10
|
cwd?: string;
|
|
11
|
+
/** Server-only proof that an MCP OAuth token is used by the hosted MCP service. */
|
|
12
|
+
mcpServiceProofSecret?: string;
|
|
11
13
|
}
|
|
12
14
|
export declare class WaberyApiError extends Error {
|
|
13
15
|
readonly status: number;
|
|
@@ -19,11 +21,13 @@ export declare class WaberyApiClient {
|
|
|
19
21
|
private readonly baseUrl;
|
|
20
22
|
private readonly apiVersion?;
|
|
21
23
|
private readonly projectIdProvider?;
|
|
24
|
+
private readonly mcpServiceProofSecret?;
|
|
22
25
|
/** The project this client's key is scoped to, when known. */
|
|
23
26
|
readonly projectId?: string;
|
|
24
27
|
constructor(options?: WaberyClientOptions);
|
|
25
28
|
get(path: string, query?: Record<string, string | number | undefined>): Promise<unknown>;
|
|
26
|
-
post(path: string, body?: unknown): Promise<unknown>;
|
|
29
|
+
post(path: string, body?: unknown, idempotencyKey?: string): Promise<unknown>;
|
|
30
|
+
postForm(path: string, body: FormData): Promise<unknown>;
|
|
27
31
|
put(path: string, body?: unknown): Promise<unknown>;
|
|
28
32
|
patch(path: string, body?: unknown): Promise<unknown>;
|
|
29
33
|
delete(path: string): Promise<unknown>;
|
package/dist/api-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,mBAAmB;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC7C,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qBAAa,cAAe,SAAQ,KAAK;aAGvB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,OAAO;gBAF7B,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO;CAI9B;AAED,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAA2B;IAC9D,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAS;IAChD,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAEhB,OAAO,GAAE,mBAAwB;IAkBvC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAIrE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,MAAM;IAI1D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ;IAIrC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAIhC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAIlC,MAAM,CAAC,IAAI,EAAE,MAAM;YAIX,OAAO;CAyGrB"}
|
package/dist/api-client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
1
2
|
import { resolveCredential } from "./local-config.js";
|
|
2
3
|
export class WaberyApiError extends Error {
|
|
3
4
|
status;
|
|
@@ -13,6 +14,7 @@ export class WaberyApiClient {
|
|
|
13
14
|
baseUrl;
|
|
14
15
|
apiVersion;
|
|
15
16
|
projectIdProvider;
|
|
17
|
+
mcpServiceProofSecret;
|
|
16
18
|
/** The project this client's key is scoped to, when known. */
|
|
17
19
|
projectId;
|
|
18
20
|
constructor(options = {}) {
|
|
@@ -25,13 +27,17 @@ export class WaberyApiClient {
|
|
|
25
27
|
this.apiKey = resolved.apiKey;
|
|
26
28
|
this.projectId = resolved.projectId;
|
|
27
29
|
this.projectIdProvider = options.projectIdProvider;
|
|
30
|
+
this.mcpServiceProofSecret = options.mcpServiceProofSecret;
|
|
28
31
|
this.baseUrl = normalizeBaseUrl(resolved.baseUrl ?? "https://api.wabery.com/v1");
|
|
29
32
|
this.apiVersion = options.apiVersion ?? process.env.WABERY_API_VERSION;
|
|
30
33
|
}
|
|
31
34
|
async get(path, query) {
|
|
32
35
|
return this.request("GET", path, undefined, query);
|
|
33
36
|
}
|
|
34
|
-
async post(path, body) {
|
|
37
|
+
async post(path, body, idempotencyKey) {
|
|
38
|
+
return this.request("POST", path, body, undefined, idempotencyKey);
|
|
39
|
+
}
|
|
40
|
+
async postForm(path, body) {
|
|
35
41
|
return this.request("POST", path, body);
|
|
36
42
|
}
|
|
37
43
|
async put(path, body) {
|
|
@@ -43,7 +49,7 @@ export class WaberyApiClient {
|
|
|
43
49
|
async delete(path) {
|
|
44
50
|
return this.request("DELETE", path);
|
|
45
51
|
}
|
|
46
|
-
async request(method, path, body, query) {
|
|
52
|
+
async request(method, path, body, query, idempotencyKey) {
|
|
47
53
|
const url = new URL(`${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`);
|
|
48
54
|
for (const [key, value] of Object.entries(query ?? {})) {
|
|
49
55
|
if (value !== undefined)
|
|
@@ -61,17 +67,31 @@ export class WaberyApiClient {
|
|
|
61
67
|
else {
|
|
62
68
|
headers.authorization = `Bearer ${this.apiKey}`;
|
|
63
69
|
}
|
|
70
|
+
if (this.mcpServiceProofSecret) {
|
|
71
|
+
headers["x-wabery-mcp-service-proof"] = createHmac("sha256", this.mcpServiceProofSecret)
|
|
72
|
+
.update(this.apiKey)
|
|
73
|
+
.digest("base64url");
|
|
74
|
+
}
|
|
64
75
|
if (this.apiVersion)
|
|
65
76
|
headers["wabery-version"] = this.apiVersion;
|
|
66
77
|
const projectId = this.projectIdProvider?.() ?? this.projectId;
|
|
67
78
|
if (projectId)
|
|
68
79
|
headers["x-wabery-project-id"] = projectId;
|
|
69
|
-
if (
|
|
80
|
+
if (method === "POST" && path.startsWith("/broadcasts")) {
|
|
81
|
+
headers["idempotency-key"] =
|
|
82
|
+
idempotencyKey ?? globalThis.crypto.randomUUID();
|
|
83
|
+
}
|
|
84
|
+
if (body !== undefined && !(body instanceof FormData)) {
|
|
70
85
|
headers["content-type"] = "application/json";
|
|
86
|
+
}
|
|
71
87
|
const init = {
|
|
72
88
|
method,
|
|
73
89
|
headers,
|
|
74
|
-
body: body === undefined
|
|
90
|
+
body: body === undefined
|
|
91
|
+
? undefined
|
|
92
|
+
: body instanceof FormData
|
|
93
|
+
? body
|
|
94
|
+
: JSON.stringify(body),
|
|
75
95
|
};
|
|
76
96
|
// Transient gateway errors (Cloudflare 502/503/504) and network blips are
|
|
77
97
|
// retryable — the request usually never reached the origin. Back off and
|
package/dist/api-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { createHmac } from "node:crypto";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
4
5
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
6
|
import { platform } from "node:os";
|
|
6
7
|
import { stdin } from "node:process";
|
|
@@ -112,6 +113,10 @@ async function main() {
|
|
|
112
113
|
await handleContacts(client, subcommand, rest, parsed.flags);
|
|
113
114
|
return;
|
|
114
115
|
}
|
|
116
|
+
if (command === "broadcasts") {
|
|
117
|
+
await handleBroadcasts(client, subcommand, rest, parsed.flags);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
115
120
|
if (command === "conversations") {
|
|
116
121
|
await handleConversations(client, subcommand, rest, parsed.flags);
|
|
117
122
|
return;
|
|
@@ -817,6 +822,58 @@ async function handleContacts(client, subcommand, rest, flags) {
|
|
|
817
822
|
}));
|
|
818
823
|
return;
|
|
819
824
|
}
|
|
825
|
+
if (subcommand === "import") {
|
|
826
|
+
const path = rest[0] ?? stringFlag(flags, "file");
|
|
827
|
+
const filename = requiredValue(path, "file");
|
|
828
|
+
const file = readFileSync(filename);
|
|
829
|
+
const form = new FormData();
|
|
830
|
+
form.set("file", new Blob([file], { type: "text/csv" }), filename);
|
|
831
|
+
form.set("mapping", JSON.stringify(optionalJsonFlag(flags, "mapping") ?? {
|
|
832
|
+
phone: requiredStringFlag(flags, "phone-column"),
|
|
833
|
+
name: stringFlag(flags, "name-column"),
|
|
834
|
+
preferredLanguage: stringFlag(flags, "language-column"),
|
|
835
|
+
tags: stringFlag(flags, "tags-column"),
|
|
836
|
+
metadata: {},
|
|
837
|
+
}));
|
|
838
|
+
form.set("defaults", JSON.stringify(optionalJsonFlag(flags, "defaults") ?? {}));
|
|
839
|
+
const consent = optionalJsonFlag(flags, "consent");
|
|
840
|
+
if (consent)
|
|
841
|
+
form.set("consent", JSON.stringify(consent));
|
|
842
|
+
const result = await client.postForm("/contact-imports", form);
|
|
843
|
+
await printJson(result);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (subcommand === "imports") {
|
|
847
|
+
await printJson(await client.get("/contact-imports", {
|
|
848
|
+
limit: numberFlag(flags, "limit"),
|
|
849
|
+
starting_after: stringFlag(flags, "starting-after"),
|
|
850
|
+
}));
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (subcommand === "import-status") {
|
|
854
|
+
const importId = rest[0] ?? stringFlag(flags, "import-id");
|
|
855
|
+
await printJson(await client.get(`/contact-imports/${requiredValue(importId, "import-id")}`));
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (subcommand === "consent") {
|
|
859
|
+
const contactId = rest[0] ?? stringFlag(flags, "contact-id");
|
|
860
|
+
await printJson(await client.post(`/contacts/${requiredValue(contactId, "contact-id")}/consents`, optionalJsonFlag(flags, "body") ?? {
|
|
861
|
+
categories: requiredStringFlag(flags, "categories")
|
|
862
|
+
.split(",")
|
|
863
|
+
.map((value) => value.trim().toUpperCase()),
|
|
864
|
+
source: requiredStringFlag(flags, "source"),
|
|
865
|
+
method: stringFlag(flags, "method"),
|
|
866
|
+
}));
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
if (subcommand === "suppress") {
|
|
870
|
+
const contactId = rest[0] ?? stringFlag(flags, "contact-id");
|
|
871
|
+
await printJson(await client.post(`/contacts/${requiredValue(contactId, "contact-id")}/suppressions`, optionalJsonFlag(flags, "body") ?? {
|
|
872
|
+
category: stringFlag(flags, "category") ?? null,
|
|
873
|
+
reason: stringFlag(flags, "reason"),
|
|
874
|
+
}));
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
820
877
|
if (subcommand === "update") {
|
|
821
878
|
const contactId = rest[0] ?? stringFlag(flags, "contact-id");
|
|
822
879
|
await printJson(await client.patch(`/contacts/${requiredValue(contactId, "contact-id")}`, {
|
|
@@ -831,7 +888,51 @@ async function handleContacts(client, subcommand, rest, flags) {
|
|
|
831
888
|
await printJson(await client.delete(`/contacts/${requiredValue(contactId, "contact-id")}${erase ? "?erase=true" : ""}`));
|
|
832
889
|
return;
|
|
833
890
|
}
|
|
834
|
-
throw new Error("Usage: wabery contacts list|get|enroll|update|unenroll
|
|
891
|
+
throw new Error("Usage: wabery contacts list|get|enroll|update|unenroll|import|imports|import-status|consent|suppress");
|
|
892
|
+
}
|
|
893
|
+
async function handleBroadcasts(client, subcommand, rest, flags) {
|
|
894
|
+
const idempotencyKey = stringFlag(flags, "idempotency-key");
|
|
895
|
+
if (subcommand === "list") {
|
|
896
|
+
await printJson(await client.get("/broadcasts", {
|
|
897
|
+
limit: numberFlag(flags, "limit"),
|
|
898
|
+
starting_after: stringFlag(flags, "starting-after"),
|
|
899
|
+
status: stringFlag(flags, "status"),
|
|
900
|
+
}));
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const id = rest[0] ?? stringFlag(flags, "broadcast-id");
|
|
904
|
+
if (subcommand === "get") {
|
|
905
|
+
await printJson(await client.get(`/broadcasts/${requiredValue(id, "broadcast-id")}`));
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (subcommand === "create") {
|
|
909
|
+
await printJson(await client.post("/broadcasts", requiredJsonFlag(flags, "body"), idempotencyKey));
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (subcommand === "prepare") {
|
|
913
|
+
await printJson(await client.post(`/broadcasts/${requiredValue(id, "broadcast-id")}/audience`, requiredJsonFlag(flags, "body"), idempotencyKey));
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (["send", "cancel", "duplicate"].includes(subcommand ?? "")) {
|
|
917
|
+
await printJson(await client.post(`/broadcasts/${requiredValue(id, "broadcast-id")}/${subcommand}`, undefined, idempotencyKey));
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (subcommand === "schedule") {
|
|
921
|
+
await printJson(await client.post(`/broadcasts/${requiredValue(id, "broadcast-id")}/schedule`, optionalJsonFlag(flags, "body") ?? {
|
|
922
|
+
scheduledAt: requiredStringFlag(flags, "scheduled-at"),
|
|
923
|
+
timezone: requiredStringFlag(flags, "timezone"),
|
|
924
|
+
}, idempotencyKey));
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (subcommand === "recipients") {
|
|
928
|
+
await printJson(await client.get(`/broadcasts/${requiredValue(id, "broadcast-id")}/recipients`, {
|
|
929
|
+
limit: numberFlag(flags, "limit"),
|
|
930
|
+
starting_after: stringFlag(flags, "starting-after"),
|
|
931
|
+
status: stringFlag(flags, "status"),
|
|
932
|
+
}));
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
throw new Error("Usage: wabery broadcasts list|get|create|prepare|send|schedule|cancel|duplicate|recipients [--idempotency-key <key>]");
|
|
835
936
|
}
|
|
836
937
|
async function handleConversations(client, subcommand, rest, flags) {
|
|
837
938
|
if (subcommand === "list") {
|
|
@@ -1504,7 +1605,18 @@ function booleanFlag(flags, key) {
|
|
|
1504
1605
|
}
|
|
1505
1606
|
function optionalJsonFlag(flags, key) {
|
|
1506
1607
|
const value = stringFlag(flags, key);
|
|
1507
|
-
|
|
1608
|
+
if (!value)
|
|
1609
|
+
return undefined;
|
|
1610
|
+
const json = value.startsWith("@")
|
|
1611
|
+
? readFileSync(value.slice(1), "utf8")
|
|
1612
|
+
: value;
|
|
1613
|
+
return JSON.parse(json);
|
|
1614
|
+
}
|
|
1615
|
+
function requiredJsonFlag(flags, key) {
|
|
1616
|
+
const value = optionalJsonFlag(flags, key);
|
|
1617
|
+
if (value === undefined)
|
|
1618
|
+
throw new Error(`Missing required --${key}`);
|
|
1619
|
+
return value;
|
|
1508
1620
|
}
|
|
1509
1621
|
function requiredValue(value, name) {
|
|
1510
1622
|
if (!value)
|
|
@@ -1643,6 +1755,22 @@ Projects and contacts:
|
|
|
1643
1755
|
wabery contacts unenroll <contact_id>
|
|
1644
1756
|
wabery contacts delete <contact_id> --erase
|
|
1645
1757
|
wabery contacts list
|
|
1758
|
+
wabery contacts import contacts.csv --phone-column phone [--name-column name] [--language-column language] [--tags-column tags] [--mapping @mapping.json] [--consent @consent.json]
|
|
1759
|
+
wabery contacts imports [--limit 25]
|
|
1760
|
+
wabery contacts import-status <import_id>
|
|
1761
|
+
wabery contacts consent <contact_id> --categories MARKETING,UTILITY --source checkout
|
|
1762
|
+
wabery contacts suppress <contact_id> [--category MARKETING] [--reason "Requested by customer"]
|
|
1763
|
+
|
|
1764
|
+
Broadcasts:
|
|
1765
|
+
wabery broadcasts list [--status ready]
|
|
1766
|
+
wabery broadcasts get <broadcast_id>
|
|
1767
|
+
wabery broadcasts create --body @broadcast.json
|
|
1768
|
+
wabery broadcasts prepare <broadcast_id> --body @audience.json
|
|
1769
|
+
wabery broadcasts recipients <broadcast_id> [--limit 100] [--starting-after <id>] [--status skipped]
|
|
1770
|
+
wabery broadcasts send <broadcast_id>
|
|
1771
|
+
wabery broadcasts schedule <broadcast_id> --scheduled-at 2026-08-01T10:00:00Z --timezone UTC
|
|
1772
|
+
wabery broadcasts cancel <broadcast_id>
|
|
1773
|
+
wabery broadcasts duplicate <broadcast_id>
|
|
1646
1774
|
|
|
1647
1775
|
Templates, messages, and conversations:
|
|
1648
1776
|
wabery templates list --channel-id <id>
|