@wabery/cli 0.1.1

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/dist/cli.js ADDED
@@ -0,0 +1,661 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { randomBytes } from "node:crypto";
4
+ import { readFile, writeFile } from "node:fs/promises";
5
+ import { createServer } from "node:http";
6
+ import { platform } from "node:os";
7
+ import { stdin } from "node:process";
8
+ import { WaberyApiClient, WaberyApiError } from "./api-client.js";
9
+ import { createExampleConfig } from "./config-example.js";
10
+ import { checkWaberyConnection } from "./diagnostics.js";
11
+ import { getConfigPath, writeLocalConfig } from "./local-config.js";
12
+ import { runMcpServer } from "./mcp-server.js";
13
+ async function main() {
14
+ const parsed = parseArgs(process.argv.slice(2));
15
+ const [command, subcommand, ...rest] = parsed.positionals;
16
+ if (!command ||
17
+ command === "help" ||
18
+ command === "--help" ||
19
+ command === "-h") {
20
+ printHelp();
21
+ return;
22
+ }
23
+ if (command === "config" &&
24
+ (subcommand === "example" || subcommand === "init")) {
25
+ await handleLocalConfig(subcommand, rest, parsed.flags);
26
+ return;
27
+ }
28
+ if (command === "login") {
29
+ await handleLogin(parsed.flags);
30
+ return;
31
+ }
32
+ if (command === "mcp-config") {
33
+ await handleMcpConfig(subcommand, parsed.flags);
34
+ return;
35
+ }
36
+ const client = new WaberyApiClient({
37
+ apiKey: stringFlag(parsed.flags, "api-key"),
38
+ baseUrl: stringFlag(parsed.flags, "base-url"),
39
+ apiVersion: stringFlag(parsed.flags, "api-version"),
40
+ });
41
+ if (command === "mcp") {
42
+ await runMcpServer(client);
43
+ return;
44
+ }
45
+ if (command === "doctor" || command === "check") {
46
+ await printJson(await checkWaberyConnection(client));
47
+ return;
48
+ }
49
+ if (command === "config") {
50
+ await handleConfig(client, subcommand, rest, parsed.flags);
51
+ return;
52
+ }
53
+ if (command === "projects") {
54
+ await handleProjects(client, subcommand, parsed.flags);
55
+ return;
56
+ }
57
+ if (command === "channels" && subcommand === "list") {
58
+ await printJson(await client.get("/channels"));
59
+ return;
60
+ }
61
+ if (command === "limits" && subcommand === "get") {
62
+ await printJson(await client.get("/limits"));
63
+ return;
64
+ }
65
+ if (command === "contacts") {
66
+ await handleContacts(client, subcommand, rest, parsed.flags);
67
+ return;
68
+ }
69
+ if (command === "conversations") {
70
+ await handleConversations(client, subcommand, rest, parsed.flags);
71
+ return;
72
+ }
73
+ if (command === "messages") {
74
+ await handleMessages(client, subcommand, parsed.flags);
75
+ return;
76
+ }
77
+ if (command === "templates") {
78
+ await handleTemplates(client, subcommand, rest, parsed.flags);
79
+ return;
80
+ }
81
+ if (command === "flows") {
82
+ await handleFlows(client, subcommand, rest, parsed.flags);
83
+ return;
84
+ }
85
+ if (command === "submissions" && subcommand === "list") {
86
+ await printJson(await client.get("/submissions", {
87
+ agent_id: stringFlag(parsed.flags, "agent-id"),
88
+ flow_id: stringFlag(parsed.flags, "flow-id"),
89
+ limit: numberFlag(parsed.flags, "limit"),
90
+ starting_after: stringFlag(parsed.flags, "starting-after"),
91
+ }));
92
+ return;
93
+ }
94
+ if (command === "dispatches") {
95
+ await handleDispatches(client, subcommand, rest, parsed.flags);
96
+ return;
97
+ }
98
+ throw new Error(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`);
99
+ }
100
+ async function handleConfig(client, subcommand, rest, flags) {
101
+ if (subcommand === "schema") {
102
+ await printJson(await client.get("/config/schema"));
103
+ return;
104
+ }
105
+ if (subcommand === "export") {
106
+ await printJson(await client.get("/config"));
107
+ return;
108
+ }
109
+ const config = await readJsonInput(rest[0] ?? stringFlag(flags, "file"));
110
+ if (subcommand === "validate") {
111
+ await printJson(await client.post("/config/validate", config));
112
+ return;
113
+ }
114
+ if (subcommand === "diff") {
115
+ await printJson(await client.post("/config/diff", config));
116
+ return;
117
+ }
118
+ if (subcommand === "apply") {
119
+ await printJson(await client.put("/config", config));
120
+ return;
121
+ }
122
+ throw new Error("Usage: wabery config schema|export|example|init|validate|diff|apply [wabery.config.json]");
123
+ }
124
+ async function handleLocalConfig(subcommand, rest, flags) {
125
+ if (subcommand === "example") {
126
+ await printJson(createExampleConfig(stringFlag(flags, "project-id")));
127
+ return;
128
+ }
129
+ const outputPath = rest[0] ?? stringFlag(flags, "file") ?? "wabery.config.json";
130
+ const config = createExampleConfig(stringFlag(flags, "project-id"));
131
+ await writeFile(outputPath, `${JSON.stringify(config, null, 2)}\n`, {
132
+ flag: booleanFlag(flags, "force") ? "w" : "wx",
133
+ });
134
+ await printJson({ object: "config_file", path: outputPath, config });
135
+ }
136
+ async function handleMcpConfig(client, flags) {
137
+ const target = client ?? stringFlag(flags, "client") ?? "claude";
138
+ const baseUrl = stringFlag(flags, "base-url") ??
139
+ process.env.WABERY_BASE_URL ??
140
+ "https://app.wabery.com";
141
+ if (stringFlag(flags, "api-key")) {
142
+ throw new Error("mcp-config does not embed raw API keys. Set WABERY_API_KEY in your shell or MCP client's secret storage instead.");
143
+ }
144
+ const apiKey = "wab_live_...";
145
+ const apiVersion = stringFlag(flags, "api-version");
146
+ const localBin = stringFlag(flags, "local-bin");
147
+ const command = localBin ? "node" : "wabery";
148
+ const args = localBin ? [localBin, "mcp"] : ["mcp"];
149
+ const env = {
150
+ WABERY_API_KEY: apiKey,
151
+ WABERY_BASE_URL: baseUrl,
152
+ ...(apiVersion ? { WABERY_API_VERSION: apiVersion } : {}),
153
+ ...(booleanFlag(flags, "write") ? { WABERY_MCP_MODE: "write" } : {}),
154
+ };
155
+ if (target === "claude") {
156
+ const config = { mcpServers: { wabery: { command, args, env } } };
157
+ if (booleanFlag(flags, "raw")) {
158
+ await printText(`${JSON.stringify(config, null, 2)}\n`);
159
+ return;
160
+ }
161
+ await printJson({
162
+ object: "mcp_config",
163
+ client: "claude",
164
+ format: "json",
165
+ config,
166
+ });
167
+ return;
168
+ }
169
+ if (target === "codex") {
170
+ const envToml = Object.entries(env)
171
+ .map(([key, value]) => `${key} = ${JSON.stringify(value)}`)
172
+ .join(", ");
173
+ const toml = `[mcp_servers.wabery]
174
+ command = ${JSON.stringify(command)}
175
+ args = ${JSON.stringify(args)}
176
+ env = { ${envToml} }
177
+ `;
178
+ if (booleanFlag(flags, "raw")) {
179
+ await printText(toml);
180
+ return;
181
+ }
182
+ await printJson({
183
+ object: "mcp_config",
184
+ client: "codex",
185
+ format: "toml",
186
+ config: toml,
187
+ });
188
+ return;
189
+ }
190
+ if (target === "opencode") {
191
+ const config = {
192
+ $schema: "https://opencode.ai/config.json",
193
+ mcp: {
194
+ wabery: {
195
+ type: "local",
196
+ command: [command, ...args],
197
+ enabled: true,
198
+ environment: env,
199
+ },
200
+ },
201
+ };
202
+ if (booleanFlag(flags, "raw")) {
203
+ await printText(`${JSON.stringify(config, null, 2)}\n`);
204
+ return;
205
+ }
206
+ await printJson({
207
+ object: "mcp_config",
208
+ client: "opencode",
209
+ format: "json",
210
+ config,
211
+ });
212
+ return;
213
+ }
214
+ throw new Error("Usage: wabery mcp-config claude|codex|opencode [--local-bin <path>]");
215
+ }
216
+ async function handleLogin(flags) {
217
+ const baseUrl = normalizeAppBaseUrl(stringFlag(flags, "base-url") ??
218
+ process.env.WABERY_BASE_URL ??
219
+ "https://app.wabery.com");
220
+ const state = randomBytes(32).toString("base64url");
221
+ const login = await waitForBrowserLogin({
222
+ baseUrl,
223
+ state,
224
+ openBrowser: booleanFlag(flags, "no-open") !== true,
225
+ });
226
+ const configPath = writeLocalConfig({
227
+ apiKey: login.apiKey,
228
+ baseUrl: login.baseUrl,
229
+ });
230
+ await printText(`Logged in to Wabery. Credentials saved to ${configPath}\n`);
231
+ }
232
+ async function waitForBrowserLogin({ baseUrl, state, openBrowser, }) {
233
+ return new Promise((resolve, reject) => {
234
+ let settled = false;
235
+ const timeout = setTimeout(() => {
236
+ finish(new Error("Timed out waiting for browser login."));
237
+ }, 5 * 60 * 1000);
238
+ const server = createServer(async (req, res) => {
239
+ const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
240
+ if (requestUrl.pathname !== "/callback") {
241
+ res.writeHead(404, { "content-type": "text/plain" });
242
+ res.end("Not found");
243
+ return;
244
+ }
245
+ if (req.method !== "POST") {
246
+ res.writeHead(405, { "content-type": "text/plain" });
247
+ res.end("Method not allowed");
248
+ return;
249
+ }
250
+ try {
251
+ const body = await readRequestBody(req);
252
+ const form = new URLSearchParams(body);
253
+ const returnedState = form.get("state");
254
+ const apiKey = form.get("api_key");
255
+ const returnedBaseUrl = form.get("base_url") ?? baseUrl;
256
+ if (returnedState !== state) {
257
+ throw new Error("Login state mismatch. Run `wabery login` again.");
258
+ }
259
+ if (!apiKey?.startsWith("wab_live_")) {
260
+ throw new Error("Login response did not include a valid API key.");
261
+ }
262
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
263
+ res.end("<!doctype html><html><body><h1>Wabery CLI authorized</h1><p>You can close this window and return to your terminal.</p></body></html>");
264
+ finish(null, {
265
+ apiKey,
266
+ baseUrl: normalizeAppBaseUrl(returnedBaseUrl),
267
+ });
268
+ }
269
+ catch (error) {
270
+ res.writeHead(400, { "content-type": "text/plain" });
271
+ res.end(error instanceof Error ? error.message : "Login failed");
272
+ finish(error instanceof Error ? error : new Error("Login failed"));
273
+ }
274
+ });
275
+ function finish(error, result) {
276
+ if (settled)
277
+ return;
278
+ settled = true;
279
+ clearTimeout(timeout);
280
+ server.close();
281
+ if (error) {
282
+ reject(error);
283
+ return;
284
+ }
285
+ resolve(result);
286
+ }
287
+ server.listen(0, "127.0.0.1", () => {
288
+ const address = server.address();
289
+ const callback = `http://127.0.0.1:${address.port}/callback`;
290
+ const loginUrl = new URL("/cli/login", baseUrl);
291
+ loginUrl.searchParams.set("callback", callback);
292
+ loginUrl.searchParams.set("state", state);
293
+ loginUrl.searchParams.set("base_url", baseUrl);
294
+ process.stdout.write(`Opening ${loginUrl.toString()}\n`);
295
+ if (!openBrowser || !openUrl(loginUrl.toString())) {
296
+ process.stdout.write(`Open this URL in your browser to continue:\n${loginUrl.toString()}\n`);
297
+ }
298
+ });
299
+ server.on("error", (error) => finish(error));
300
+ });
301
+ }
302
+ async function readRequestBody(req) {
303
+ const chunks = [];
304
+ for await (const chunk of req) {
305
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
306
+ if (Buffer.concat(chunks).length > 16 * 1024) {
307
+ throw new Error("Login response was too large.");
308
+ }
309
+ }
310
+ return Buffer.concat(chunks).toString("utf8");
311
+ }
312
+ function openUrl(url) {
313
+ const currentPlatform = platform();
314
+ const command = currentPlatform === "darwin"
315
+ ? "open"
316
+ : currentPlatform === "win32"
317
+ ? "cmd"
318
+ : "xdg-open";
319
+ const args = currentPlatform === "win32" ? ["/c", "start", "", url] : [url];
320
+ try {
321
+ const child = spawn(command, args, {
322
+ detached: true,
323
+ stdio: "ignore",
324
+ });
325
+ child.unref();
326
+ return true;
327
+ }
328
+ catch {
329
+ return false;
330
+ }
331
+ }
332
+ function normalizeAppBaseUrl(value) {
333
+ return value.replace(/\/api\/v1\/?$/, "").replace(/\/+$/, "");
334
+ }
335
+ async function handleProjects(client, subcommand, flags) {
336
+ if (subcommand === "list") {
337
+ await printJson(await client.get("/projects"));
338
+ return;
339
+ }
340
+ if (subcommand === "create") {
341
+ await printJson(await client.post("/projects", {
342
+ name: requiredStringFlag(flags, "name"),
343
+ description: stringFlag(flags, "description"),
344
+ webhook_url: stringFlag(flags, "webhook-url"),
345
+ webhook_signing_enabled: booleanFlag(flags, "webhook-signing-enabled") ?? true,
346
+ }));
347
+ return;
348
+ }
349
+ throw new Error("Usage: wabery projects list|create --name <name>");
350
+ }
351
+ async function handleContacts(client, subcommand, rest, flags) {
352
+ if (subcommand === "list") {
353
+ await printJson(await client.get("/contacts", {
354
+ limit: numberFlag(flags, "limit"),
355
+ starting_after: stringFlag(flags, "starting-after"),
356
+ }));
357
+ return;
358
+ }
359
+ if (subcommand === "get") {
360
+ const contactId = rest[0] ?? stringFlag(flags, "contact-id");
361
+ await printJson(await client.get(`/contacts/${requiredValue(contactId, "contact-id")}`));
362
+ return;
363
+ }
364
+ if (subcommand === "enroll") {
365
+ await printJson(await client.post("/contacts", {
366
+ phone: requiredStringFlag(flags, "phone"),
367
+ project_id: requiredStringFlag(flags, "project-id"),
368
+ name: stringFlag(flags, "name"),
369
+ reference_id: stringFlag(flags, "reference-id"),
370
+ metadata: optionalJsonFlag(flags, "metadata"),
371
+ opt_in: optionalJsonFlag(flags, "opt-in"),
372
+ }));
373
+ return;
374
+ }
375
+ if (subcommand === "unenroll" || subcommand === "delete") {
376
+ const contactId = rest[0] ?? stringFlag(flags, "contact-id");
377
+ const erase = booleanFlag(flags, "erase");
378
+ await printJson(await client.delete(`/contacts/${requiredValue(contactId, "contact-id")}${erase ? "?erase=true" : ""}`));
379
+ return;
380
+ }
381
+ throw new Error("Usage: wabery contacts list|get|enroll|unenroll --phone <phone> --project-id <id>");
382
+ }
383
+ async function handleConversations(client, subcommand, rest, flags) {
384
+ if (subcommand === "list") {
385
+ await printJson(await client.get("/conversations", {
386
+ contact_id: stringFlag(flags, "contact-id"),
387
+ active: stringFlag(flags, "active"),
388
+ limit: numberFlag(flags, "limit"),
389
+ starting_after: stringFlag(flags, "starting-after"),
390
+ }));
391
+ return;
392
+ }
393
+ const conversationId = rest[0] ?? stringFlag(flags, "conversation-id");
394
+ if (subcommand === "get") {
395
+ await printJson(await client.get(`/conversations/${requiredValue(conversationId, "conversation-id")}`));
396
+ return;
397
+ }
398
+ if (subcommand === "messages") {
399
+ await printJson(await client.get(`/conversations/${requiredValue(conversationId, "conversation-id")}/messages`, {
400
+ limit: numberFlag(flags, "limit"),
401
+ starting_after: stringFlag(flags, "starting-after"),
402
+ order: stringFlag(flags, "order"),
403
+ }));
404
+ return;
405
+ }
406
+ throw new Error("Usage: wabery conversations list|get|messages <conversation_id>");
407
+ }
408
+ async function handleMessages(client, subcommand, flags) {
409
+ if (subcommand === "send") {
410
+ const body = optionalJsonFlag(flags, "body") ??
411
+ {
412
+ channel_id: requiredStringFlag(flags, "channel-id"),
413
+ conversation_id: stringFlag(flags, "conversation-id"),
414
+ to: stringFlag(flags, "to"),
415
+ text: stringFlag(flags, "text"),
416
+ template: optionalJsonFlag(flags, "template"),
417
+ media: optionalJsonFlag(flags, "media"),
418
+ interactive: optionalJsonFlag(flags, "interactive"),
419
+ idempotency_key: stringFlag(flags, "idempotency-key"),
420
+ };
421
+ await printJson(await client.post("/messages", body));
422
+ return;
423
+ }
424
+ throw new Error("Usage: wabery messages send --body '<json>'");
425
+ }
426
+ async function handleTemplates(client, subcommand, rest, flags) {
427
+ if (subcommand === "list") {
428
+ await printJson(await client.get("/templates", {
429
+ channel_id: stringFlag(flags, "channel-id"),
430
+ status: stringFlag(flags, "status"),
431
+ }));
432
+ return;
433
+ }
434
+ const templateId = rest[0] ?? stringFlag(flags, "template-id");
435
+ if (subcommand === "get") {
436
+ await printJson(await client.get(`/templates/${requiredValue(templateId, "template-id")}`));
437
+ return;
438
+ }
439
+ if (subcommand === "create") {
440
+ const body = optionalJsonFlag(flags, "body") ??
441
+ {
442
+ channel_id: requiredStringFlag(flags, "channel-id"),
443
+ name: requiredStringFlag(flags, "name"),
444
+ language: requiredStringFlag(flags, "language"),
445
+ category: requiredStringFlag(flags, "category"),
446
+ components: optionalJsonFlag(flags, "components"),
447
+ };
448
+ await printJson(await client.post("/templates", body));
449
+ return;
450
+ }
451
+ throw new Error("Usage: wabery templates list|get|create");
452
+ }
453
+ async function handleFlows(client, subcommand, rest, flags) {
454
+ if (subcommand === "list") {
455
+ await printJson(await client.get("/flows"));
456
+ return;
457
+ }
458
+ const flowId = rest[0] ?? stringFlag(flags, "flow-id");
459
+ if (subcommand === "get") {
460
+ await printJson(await client.get(`/flows/${requiredValue(flowId, "flow-id")}`));
461
+ return;
462
+ }
463
+ if (subcommand === "publish") {
464
+ await printJson(await client.post(`/flows/${requiredValue(flowId, "flow-id")}/publish`));
465
+ return;
466
+ }
467
+ if (subcommand === "send") {
468
+ await printJson(await client.post(`/flows/${requiredValue(flowId, "flow-id")}/send`, {
469
+ channel_id: requiredStringFlag(flags, "channel-id"),
470
+ contact_id: stringFlag(flags, "contact-id"),
471
+ conversation_id: stringFlag(flags, "conversation-id"),
472
+ to: stringFlag(flags, "to"),
473
+ flow_cta: stringFlag(flags, "flow-cta"),
474
+ body_text: stringFlag(flags, "body-text"),
475
+ header_text: stringFlag(flags, "header-text"),
476
+ footer_text: stringFlag(flags, "footer-text"),
477
+ first_screen: stringFlag(flags, "first-screen"),
478
+ screen_data: optionalJsonFlag(flags, "screen-data"),
479
+ }));
480
+ return;
481
+ }
482
+ throw new Error("Usage: wabery flows list|get|publish|send");
483
+ }
484
+ async function handleDispatches(client, subcommand, rest, flags) {
485
+ if (subcommand === "list") {
486
+ await printJson(await client.get("/dispatches", {
487
+ flow_id: stringFlag(flags, "flow-id"),
488
+ contact_id: stringFlag(flags, "contact-id"),
489
+ status: stringFlag(flags, "status"),
490
+ limit: numberFlag(flags, "limit"),
491
+ starting_after: stringFlag(flags, "starting-after"),
492
+ }));
493
+ return;
494
+ }
495
+ if (subcommand === "get") {
496
+ const flowToken = rest[0] ?? stringFlag(flags, "flow-token");
497
+ await printJson(await client.get(`/dispatches/${requiredValue(flowToken, "flow-token")}`));
498
+ return;
499
+ }
500
+ throw new Error("Usage: wabery dispatches list|get <flow_token>");
501
+ }
502
+ function parseArgs(args) {
503
+ const positionals = [];
504
+ const flags = {};
505
+ for (let index = 0; index < args.length; index++) {
506
+ const arg = args[index];
507
+ if (!arg.startsWith("--")) {
508
+ positionals.push(arg);
509
+ continue;
510
+ }
511
+ const [rawKey, inlineValue] = arg.slice(2).split("=", 2);
512
+ if (inlineValue !== undefined) {
513
+ flags[rawKey] = inlineValue;
514
+ continue;
515
+ }
516
+ const next = args[index + 1];
517
+ if (!next || next.startsWith("--")) {
518
+ flags[rawKey] = true;
519
+ continue;
520
+ }
521
+ flags[rawKey] = next;
522
+ index++;
523
+ }
524
+ return { positionals, flags };
525
+ }
526
+ async function readJsonInput(path) {
527
+ if (!path || path === "-") {
528
+ return JSON.parse(await readStdin());
529
+ }
530
+ return JSON.parse(await readFile(path, "utf8"));
531
+ }
532
+ async function readStdin() {
533
+ const chunks = [];
534
+ for await (const chunk of stdin) {
535
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
536
+ }
537
+ return Buffer.concat(chunks).toString("utf8");
538
+ }
539
+ function stringFlag(flags, key) {
540
+ const value = flags[key];
541
+ return typeof value === "string" ? value : undefined;
542
+ }
543
+ function requiredStringFlag(flags, key) {
544
+ return requiredValue(stringFlag(flags, key), key);
545
+ }
546
+ function numberFlag(flags, key) {
547
+ const value = stringFlag(flags, key);
548
+ return value ? Number(value) : undefined;
549
+ }
550
+ function booleanFlag(flags, key) {
551
+ const value = flags[key];
552
+ if (typeof value === "boolean")
553
+ return value;
554
+ if (value === "true")
555
+ return true;
556
+ if (value === "false")
557
+ return false;
558
+ return undefined;
559
+ }
560
+ function optionalJsonFlag(flags, key) {
561
+ const value = stringFlag(flags, key);
562
+ return value ? JSON.parse(value) : undefined;
563
+ }
564
+ function requiredValue(value, name) {
565
+ if (!value)
566
+ throw new Error(`Missing required --${name}`);
567
+ return value;
568
+ }
569
+ async function printJson(data) {
570
+ process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
571
+ }
572
+ async function printText(text) {
573
+ process.stdout.write(text);
574
+ }
575
+ function shouldPrintDebugErrors() {
576
+ return process.env.WABERY_DEBUG === "1" || process.argv.includes("--debug");
577
+ }
578
+ function redactSensitive(value) {
579
+ if (Array.isArray(value))
580
+ return value.map(redactSensitive);
581
+ if (!value || typeof value !== "object")
582
+ return value;
583
+ const sensitivePattern = /(authorization|api[-_]?key|token|secret|signature|phone|from|to|email|webhook|payload|submission)/i;
584
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
585
+ key,
586
+ sensitivePattern.test(key) ? "[REDACTED]" : redactSensitive(entry),
587
+ ]));
588
+ }
589
+ function printHelp() {
590
+ process.stdout.write(`Wabery CLI and MCP server
591
+
592
+ Environment:
593
+ WABERY_API_KEY API key override. Defaults to wabery login config.
594
+ WABERY_BASE_URL Defaults to https://app.wabery.com.
595
+ WABERY_API_VERSION Optional dated API version.
596
+
597
+ Global flags:
598
+ --api-key <key> Override WABERY_API_KEY.
599
+ --base-url <url> Override WABERY_BASE_URL.
600
+ --api-version <ver> Override WABERY_API_VERSION.
601
+
602
+ Authentication:
603
+ wabery login
604
+ wabery login --no-open
605
+ Config file: ${getConfigPath()}
606
+
607
+ MCP:
608
+ wabery mcp
609
+ wabery mcp-config claude [--write]
610
+ wabery mcp-config codex [--write]
611
+ wabery mcp-config opencode [--write]
612
+ wabery mcp-config claude --local-bin /path/to/dist/cli.js
613
+
614
+ Diagnostics:
615
+ wabery doctor
616
+
617
+ Config:
618
+ wabery config schema
619
+ wabery config export
620
+ wabery config example --project-id <project_id>
621
+ wabery config init wabery.config.json --project-id <project_id>
622
+ wabery config validate wabery.config.json
623
+ wabery config diff wabery.config.json
624
+ wabery config apply wabery.config.json
625
+
626
+ Projects and contacts:
627
+ wabery projects list
628
+ wabery projects create --name "Demo"
629
+ wabery contacts enroll --project-id <id> --phone +15555550123 --name "Jane" --opt-in '{"source":"checkout"}'
630
+ wabery contacts get <contact_id>
631
+ wabery contacts unenroll <contact_id>
632
+ wabery contacts delete <contact_id> --erase
633
+ wabery contacts list
634
+
635
+ Templates, messages, and conversations:
636
+ wabery templates list --channel-id <id>
637
+ wabery templates create --body '<json>'
638
+ wabery messages send --body '<json>'
639
+ wabery conversations list
640
+ wabery conversations messages <conversation_id>
641
+ wabery limits get
642
+
643
+ Flows:
644
+ wabery flows list
645
+ wabery flows get <flow_id>
646
+ wabery flows publish <flow_id>
647
+ wabery flows send <flow_id> --channel-id <id> --contact-id <contact_id>
648
+ `);
649
+ }
650
+ main().catch((error) => {
651
+ if (error instanceof WaberyApiError) {
652
+ const body = shouldPrintDebugErrors()
653
+ ? error.body
654
+ : redactSensitive(error.body);
655
+ process.stderr.write(`Wabery API error (${error.status}): ${error.message}\n${JSON.stringify(body, null, 2)}\n`);
656
+ process.exit(1);
657
+ }
658
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
659
+ process.exit(1);
660
+ });
661
+ //# sourceMappingURL=cli.js.map