@brew.new/cli 0.8.0 → 0.10.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 +18 -1
- package/dist/bin.js +1431 -227
- package/dist/bin.js.map +1 -1
- package/package.json +4 -3
package/dist/bin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createInterface } from 'readline/promises';
|
|
3
|
+
import { BrewApiError, BrewConnectionError, BrewTimeoutError, createBrewClient, GENERATE_IMAGE_DEFAULT_TIMEOUT_MS, GIF_DEFAULT_TIMEOUT_MS, AUDIT_EMAIL_DEFAULT_TIMEOUT_MS, EDIT_EMAIL_DEFAULT_TIMEOUT_MS, GENERATE_EMAIL_DEFAULT_TIMEOUT_MS, IMPORT_EMAIL_DEFAULT_TIMEOUT_MS, IMPORT_FIGMA_DEFAULT_TIMEOUT_MS, PREVIEW_EMAIL_CLIENTS_DEFAULT_TIMEOUT_MS } from '@brew.new/sdk';
|
|
3
4
|
import { CommanderError, Command } from 'commander';
|
|
4
|
-
import { BrewApiError, createBrewClient, AUDIT_EMAIL_DEFAULT_TIMEOUT_MS } from '@brew.new/sdk';
|
|
5
5
|
import { readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
|
|
6
6
|
import { homedir } from 'os';
|
|
7
7
|
import { join } from 'path';
|
|
@@ -12,6 +12,35 @@ var EXIT_RUNTIME = 1;
|
|
|
12
12
|
var EXIT_USAGE = 2;
|
|
13
13
|
var EXIT_AUTH = 3;
|
|
14
14
|
var EXIT_CONFIRM = 4;
|
|
15
|
+
var EXIT_INTERRUPTED = 130;
|
|
16
|
+
var EXIT_TERMINATED = 143;
|
|
17
|
+
function exitCodeForSignal(signal) {
|
|
18
|
+
return signal === "SIGINT" ? EXIT_INTERRUPTED : EXIT_TERMINATED;
|
|
19
|
+
}
|
|
20
|
+
var CliInterruptError = class extends Error {
|
|
21
|
+
signal;
|
|
22
|
+
constructor(signal) {
|
|
23
|
+
super(signal === "SIGINT" ? "Interrupted." : "Terminated.");
|
|
24
|
+
this.name = "CliInterruptError";
|
|
25
|
+
this.signal = signal;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var CliTimeoutError = class extends Error {
|
|
29
|
+
timeoutMs;
|
|
30
|
+
constructor(timeoutMs) {
|
|
31
|
+
super(`No complete response within ${timeoutMs}ms.`);
|
|
32
|
+
this.name = "TimeoutError";
|
|
33
|
+
this.timeoutMs = timeoutMs;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var CliConnectionError = class extends Error {
|
|
37
|
+
constructor(cause) {
|
|
38
|
+
super("The connection failed before a complete response arrived.", {
|
|
39
|
+
cause
|
|
40
|
+
});
|
|
41
|
+
this.name = "CliConnectionError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
15
44
|
var CliUsageError = class extends Error {
|
|
16
45
|
};
|
|
17
46
|
var CliAuthError = class extends Error {
|
|
@@ -88,6 +117,9 @@ var ConfirmationRequiredError = class extends Error {
|
|
|
88
117
|
}
|
|
89
118
|
};
|
|
90
119
|
function toExitCode(error) {
|
|
120
|
+
if (error instanceof CliInterruptError) {
|
|
121
|
+
return exitCodeForSignal(error.signal);
|
|
122
|
+
}
|
|
91
123
|
if (error instanceof ConfirmationRequiredError) {
|
|
92
124
|
return EXIT_CONFIRM;
|
|
93
125
|
}
|
|
@@ -103,7 +135,40 @@ function toExitCode(error) {
|
|
|
103
135
|
}
|
|
104
136
|
return EXIT_RUNTIME;
|
|
105
137
|
}
|
|
106
|
-
function toErrorEnvelope(error) {
|
|
138
|
+
function toErrorEnvelope(error, context = {}) {
|
|
139
|
+
const envelope = baseEnvelope(error, context);
|
|
140
|
+
const drain = context.drain;
|
|
141
|
+
return withReplayAdvice(
|
|
142
|
+
drain === void 0 ? envelope : { ...envelope, progress: drain },
|
|
143
|
+
error,
|
|
144
|
+
context
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
function baseEnvelope(error, context) {
|
|
148
|
+
if (error instanceof CliInterruptError) {
|
|
149
|
+
return {
|
|
150
|
+
code: "CLI_INTERRUPTED",
|
|
151
|
+
type: "cancelled",
|
|
152
|
+
message: `${error.signal === "SIGINT" ? "Interrupted" : "Terminated"} (${error.signal}) ${context.request === void 0 ? "before any request was sent." : `while waiting for ${context.request.method} ${pathOnly(context.request.path)}.`}`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (isTimeoutError(error)) {
|
|
156
|
+
return {
|
|
157
|
+
code: "CLI_TIMEOUT",
|
|
158
|
+
type: "service_unavailable",
|
|
159
|
+
message: timeoutMessage(error, context),
|
|
160
|
+
suggestion: "Retry the request. Reuse the same Idempotency-Key for a POST request."
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (error instanceof BrewConnectionError || error instanceof CliConnectionError) {
|
|
164
|
+
const cause = error.cause instanceof Error && error.cause.message !== "" ? `: ${error.cause.message}` : "";
|
|
165
|
+
return {
|
|
166
|
+
code: "CLI_CONNECTION",
|
|
167
|
+
type: "service_unavailable",
|
|
168
|
+
message: error instanceof BrewConnectionError ? error.message : `${error.message.replace(/\.$/, "")}${context.request === void 0 ? "" : ` (${context.request.method} ${pathOnly(context.request.path)})`}${cause}.`,
|
|
169
|
+
suggestion: "Check the connection and retry the request."
|
|
170
|
+
};
|
|
171
|
+
}
|
|
107
172
|
if (error instanceof BrewApiError || error instanceof CliApiError) {
|
|
108
173
|
const details = readErrorDetails(error);
|
|
109
174
|
return {
|
|
@@ -133,14 +198,6 @@ function toErrorEnvelope(error) {
|
|
|
133
198
|
message: error.message
|
|
134
199
|
};
|
|
135
200
|
}
|
|
136
|
-
if (isTimeoutError(error)) {
|
|
137
|
-
return {
|
|
138
|
-
code: "CLI_TIMEOUT",
|
|
139
|
-
type: "service_unavailable",
|
|
140
|
-
message: "The request timed out before the API responded.",
|
|
141
|
-
suggestion: "Retry the request. Reuse the same Idempotency-Key for a POST request."
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
201
|
if (error instanceof CommandAbortedError) {
|
|
145
202
|
return {
|
|
146
203
|
code: "CLI_ABORTED",
|
|
@@ -155,7 +212,60 @@ function toErrorEnvelope(error) {
|
|
|
155
212
|
}
|
|
156
213
|
return { code: "CLI_UNEXPECTED", type: "internal_error", message };
|
|
157
214
|
}
|
|
158
|
-
|
|
215
|
+
var IN_PROGRESS_CODE = "IDEMPOTENCY_IN_PROGRESS";
|
|
216
|
+
function withReplayAdvice(envelope, error, context) {
|
|
217
|
+
if (!isUnknownOutcome(envelope)) {
|
|
218
|
+
return envelope;
|
|
219
|
+
}
|
|
220
|
+
const isInProgress = envelope.code === IN_PROGRESS_CODE || error instanceof BrewTimeoutError && error.inProgress || error instanceof BrewConnectionError && error.inProgress;
|
|
221
|
+
const holdNote = isInProgress ? " The first attempt is still running on the server, which holds its key for up to 15 minutes: until then a replay answers 409 IDEMPOTENCY_IN_PROGRESS, so wait and re-run." : "";
|
|
222
|
+
if (context.request === void 0) {
|
|
223
|
+
return envelope.code === "CLI_INTERRUPTED" ? { ...envelope, suggestion: "Nothing was sent." } : envelope;
|
|
224
|
+
}
|
|
225
|
+
if (context.isRead === true || context.request.method === "GET") {
|
|
226
|
+
return envelope.code === "CLI_INTERRUPTED" ? { ...envelope, suggestion: "It only reads; nothing was changed." } : envelope;
|
|
227
|
+
}
|
|
228
|
+
const key = errorIdempotencyKey(error) ?? context.request.idempotencyKey;
|
|
229
|
+
if (context.replays !== true || key === void 0) {
|
|
230
|
+
return {
|
|
231
|
+
...envelope,
|
|
232
|
+
suggestion: `${pathOnly(context.request.path)} may already have been changed, and this route does not replay a request: check its current state before running it again.`
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const retryCommand = context.retryCommand?.(key);
|
|
236
|
+
return {
|
|
237
|
+
...envelope,
|
|
238
|
+
suggestion: `The API keeps working after the CLI disconnects, so this may still complete. Re-run with --idempotency-key ${key} to get its result instead of running it twice (the key replays for 24 hours${context.readsStdin === true ? "; pipe the same input to it again" : ""}).${holdNote}`,
|
|
239
|
+
idempotencyKey: key,
|
|
240
|
+
...retryCommand === void 0 ? {} : { retryCommand }
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function isUnknownOutcome(envelope) {
|
|
244
|
+
if (envelope.code === "CLI_INTERRUPTED" || envelope.code === "CLI_TIMEOUT" || envelope.code === "CLI_CONNECTION" || envelope.code === IN_PROGRESS_CODE) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
return envelope.status !== void 0 && envelope.status >= 500;
|
|
248
|
+
}
|
|
249
|
+
function errorIdempotencyKey(error) {
|
|
250
|
+
if (error instanceof BrewApiError || error instanceof BrewTimeoutError || error instanceof BrewConnectionError) {
|
|
251
|
+
return error.idempotencyKey;
|
|
252
|
+
}
|
|
253
|
+
return void 0;
|
|
254
|
+
}
|
|
255
|
+
function timeoutMessage(error, context) {
|
|
256
|
+
if (error instanceof BrewTimeoutError) {
|
|
257
|
+
return error.message;
|
|
258
|
+
}
|
|
259
|
+
if (error instanceof CliTimeoutError) {
|
|
260
|
+
const request = context.request;
|
|
261
|
+
return request === void 0 ? error.message : `${error.message.replace(/\.$/, "")} (${request.method} ${pathOnly(request.path)}).`;
|
|
262
|
+
}
|
|
263
|
+
return "The request timed out before the API responded.";
|
|
264
|
+
}
|
|
265
|
+
function pathOnly(path) {
|
|
266
|
+
return path.split("?")[0] ?? path;
|
|
267
|
+
}
|
|
268
|
+
function printError(ctx, error, context = {}) {
|
|
159
269
|
if (error instanceof ConfirmationRequiredError) {
|
|
160
270
|
ctx.io.stdout.write(`${JSON.stringify(error.envelope)}
|
|
161
271
|
`);
|
|
@@ -168,7 +278,7 @@ function printError(ctx, error) {
|
|
|
168
278
|
}
|
|
169
279
|
return;
|
|
170
280
|
}
|
|
171
|
-
const envelope = toErrorEnvelope(error);
|
|
281
|
+
const envelope = toErrorEnvelope(error, context);
|
|
172
282
|
if (ctx.mode === "json") {
|
|
173
283
|
ctx.io.stderr.write(`${JSON.stringify({ error: envelope })}
|
|
174
284
|
`);
|
|
@@ -181,6 +291,15 @@ function printError(ctx, error) {
|
|
|
181
291
|
if (envelope.suggestion) {
|
|
182
292
|
lines.push(envelope.suggestion);
|
|
183
293
|
}
|
|
294
|
+
if (envelope.retryCommand) {
|
|
295
|
+
lines.push(`Re-run: ${envelope.retryCommand}`);
|
|
296
|
+
}
|
|
297
|
+
if (envelope.progress) {
|
|
298
|
+
const { rowsFetched, pagesFetched, resumeCursor } = envelope.progress;
|
|
299
|
+
lines.push(
|
|
300
|
+
`Fetched ${rowsFetched} rows in ${pagesFetched} pages before stopping${resumeCursor === void 0 ? "." : `; resume with --cursor ${resumeCursor} --all.`}`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
184
303
|
if (envelope.docs) {
|
|
185
304
|
lines.push(`Docs: ${envelope.docs}`);
|
|
186
305
|
}
|
|
@@ -219,7 +338,7 @@ function apiErrorStatus(error) {
|
|
|
219
338
|
}
|
|
220
339
|
}
|
|
221
340
|
function isTimeoutError(error) {
|
|
222
|
-
return typeof error === "object" && error !== null && "name" in error && error.name === "TimeoutError";
|
|
341
|
+
return error instanceof BrewTimeoutError || typeof error === "object" && error !== null && "name" in error && error.name === "TimeoutError";
|
|
223
342
|
}
|
|
224
343
|
|
|
225
344
|
// src/lib/confirm.ts
|
|
@@ -255,22 +374,58 @@ Proceed? [y/N] `);
|
|
|
255
374
|
});
|
|
256
375
|
}
|
|
257
376
|
function buildConfirmCommand(rawArgv) {
|
|
377
|
+
return ["brew-cli", ...rerunTokens(rawArgv), "--yes"].join(" ");
|
|
378
|
+
}
|
|
379
|
+
function buildRetryCommand(rawArgv, idempotencyKey) {
|
|
380
|
+
return [
|
|
381
|
+
"brew-cli",
|
|
382
|
+
...rerunTokens(rawArgv, { dropIdempotencyKey: true }),
|
|
383
|
+
"--idempotency-key",
|
|
384
|
+
shellQuote(idempotencyKey)
|
|
385
|
+
].join(" ");
|
|
386
|
+
}
|
|
387
|
+
function rerunTokens(rawArgv, options = {}) {
|
|
258
388
|
const tokens = [];
|
|
259
389
|
for (let index = 0; index < rawArgv.length; index += 1) {
|
|
260
390
|
const token = rawArgv[index];
|
|
261
391
|
if (token === void 0) {
|
|
262
392
|
continue;
|
|
263
393
|
}
|
|
264
|
-
if (token === "--api-key") {
|
|
394
|
+
if (token === "--api-key" || options.dropIdempotencyKey === true && token === "--idempotency-key") {
|
|
265
395
|
index += 1;
|
|
266
396
|
continue;
|
|
267
397
|
}
|
|
268
|
-
if (token.startsWith("--api-key=")) {
|
|
398
|
+
if (token.startsWith("--api-key=") || options.dropIdempotencyKey === true && token.startsWith("--idempotency-key=")) {
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (token.startsWith("--header=")) {
|
|
402
|
+
if (!isCredentialHeader(token.slice("--header=".length))) {
|
|
403
|
+
tokens.push(shellQuote(token));
|
|
404
|
+
}
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (token === "--header") {
|
|
408
|
+
const values = [];
|
|
409
|
+
while (index + 1 < rawArgv.length && !(rawArgv[index + 1] ?? "").startsWith("-")) {
|
|
410
|
+
index += 1;
|
|
411
|
+
const value = rawArgv[index] ?? "";
|
|
412
|
+
if (!isCredentialHeader(value)) {
|
|
413
|
+
values.push(shellQuote(value));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (values.length > 0) {
|
|
417
|
+
tokens.push("--header", ...values);
|
|
418
|
+
}
|
|
269
419
|
continue;
|
|
270
420
|
}
|
|
271
421
|
tokens.push(shellQuote(token));
|
|
272
422
|
}
|
|
273
|
-
return
|
|
423
|
+
return tokens;
|
|
424
|
+
}
|
|
425
|
+
var CREDENTIAL_HEADER_NAME = /authorization|cookie|api-?key|auth|token|secret|passw(or)?d|session|signature|credential/i;
|
|
426
|
+
function isCredentialHeader(header) {
|
|
427
|
+
const name = /^\s*([^:\s]+)\s*:/.exec(header)?.[1];
|
|
428
|
+
return name !== void 0 && CREDENTIAL_HEADER_NAME.test(name);
|
|
274
429
|
}
|
|
275
430
|
function shellQuote(token) {
|
|
276
431
|
if (/^[A-Za-z0-9@%+=:,./_-]+$/.test(token)) {
|
|
@@ -281,7 +436,7 @@ function shellQuote(token) {
|
|
|
281
436
|
|
|
282
437
|
// src/version.ts
|
|
283
438
|
var CLI_NAME = "brew-cli";
|
|
284
|
-
var CLI_VERSION = "0.
|
|
439
|
+
var CLI_VERSION = "0.10.0" ;
|
|
285
440
|
var CONFIG_KEYS = ["apiKey", "brandId", "apiUrl"];
|
|
286
441
|
function isConfigKey(value) {
|
|
287
442
|
return CONFIG_KEYS.includes(value);
|
|
@@ -384,12 +539,18 @@ function resolveAuth(input) {
|
|
|
384
539
|
const apiKeySource = keyFromFlag ? "flag" : keyFromEnv ? "env" : "config";
|
|
385
540
|
return { apiKey, apiKeySource, brandId, apiUrl };
|
|
386
541
|
}
|
|
387
|
-
function buildSdkClient(auth) {
|
|
542
|
+
function buildSdkClient(auth, transport) {
|
|
388
543
|
return createBrewClient({
|
|
389
544
|
apiKey: auth.apiKey,
|
|
390
545
|
baseUrl: auth.apiUrl,
|
|
391
546
|
userAgent: `${CLI_NAME}/${CLI_VERSION}`,
|
|
392
|
-
...auth.brandId === void 0 ? {} : { brandId: auth.brandId }
|
|
547
|
+
...auth.brandId === void 0 ? {} : { brandId: auth.brandId },
|
|
548
|
+
// Client-wide, so every call the command makes observes the interrupt
|
|
549
|
+
// and the deadline without each command file threading a signal.
|
|
550
|
+
signal: transport.signal,
|
|
551
|
+
fetch: transport.fetch,
|
|
552
|
+
...transport.timeoutMs === void 0 ? {} : { timeoutMs: transport.timeoutMs },
|
|
553
|
+
...transport.maxRetries === void 0 ? {} : { maxRetries: transport.maxRetries }
|
|
393
554
|
});
|
|
394
555
|
}
|
|
395
556
|
function isOrgLevelPath(url) {
|
|
@@ -466,6 +627,114 @@ function formatCell(value) {
|
|
|
466
627
|
return String(value);
|
|
467
628
|
}
|
|
468
629
|
|
|
630
|
+
// src/lib/transport.ts
|
|
631
|
+
function apiPathOf(url) {
|
|
632
|
+
const index = url.pathname.indexOf("/v1/");
|
|
633
|
+
const path = index === -1 ? url.pathname : url.pathname.slice(index);
|
|
634
|
+
return `${path}${url.search}`;
|
|
635
|
+
}
|
|
636
|
+
function createTransportState() {
|
|
637
|
+
let lastRequest;
|
|
638
|
+
let attempts = 0;
|
|
639
|
+
let drain;
|
|
640
|
+
return {
|
|
641
|
+
lastRequest: () => lastRequest,
|
|
642
|
+
attempts: () => attempts,
|
|
643
|
+
record: (request) => {
|
|
644
|
+
lastRequest = request;
|
|
645
|
+
attempts += 1;
|
|
646
|
+
},
|
|
647
|
+
drain: () => drain,
|
|
648
|
+
setDrain: (progress2) => {
|
|
649
|
+
drain = progress2;
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function recordingFetch(state) {
|
|
654
|
+
return (input, init) => {
|
|
655
|
+
if (init?.signal?.aborted === true) {
|
|
656
|
+
return globalThis.fetch(input, init);
|
|
657
|
+
}
|
|
658
|
+
const url = new URL(
|
|
659
|
+
typeof input === "string" || input instanceof URL ? input : input.url
|
|
660
|
+
);
|
|
661
|
+
state.record({
|
|
662
|
+
method: (init?.method ?? "GET").toUpperCase(),
|
|
663
|
+
path: apiPathOf(url),
|
|
664
|
+
idempotencyKey: new Headers(init?.headers).get("idempotency-key") ?? void 0
|
|
665
|
+
});
|
|
666
|
+
return globalThis.fetch(input, init);
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function anySignal(signals) {
|
|
670
|
+
const present = signals.filter(
|
|
671
|
+
(signal) => signal !== void 0
|
|
672
|
+
);
|
|
673
|
+
const [only, ...rest] = present;
|
|
674
|
+
if (only !== void 0 && rest.length === 0) {
|
|
675
|
+
return { signal: only, release: () => void 0 };
|
|
676
|
+
}
|
|
677
|
+
if (typeof AbortSignal.any === "function") {
|
|
678
|
+
return { signal: AbortSignal.any(present), release: () => void 0 };
|
|
679
|
+
}
|
|
680
|
+
const controller = new AbortController();
|
|
681
|
+
const alreadyAborted = present.find((signal) => signal.aborted);
|
|
682
|
+
if (alreadyAborted !== void 0) {
|
|
683
|
+
controller.abort(alreadyAborted.reason);
|
|
684
|
+
return { signal: controller.signal, release: () => void 0 };
|
|
685
|
+
}
|
|
686
|
+
const listeners = present.map((source) => {
|
|
687
|
+
const onAbort = () => {
|
|
688
|
+
controller.abort(source.reason);
|
|
689
|
+
};
|
|
690
|
+
source.addEventListener("abort", onAbort, { once: true });
|
|
691
|
+
return { source, onAbort };
|
|
692
|
+
});
|
|
693
|
+
return {
|
|
694
|
+
signal: controller.signal,
|
|
695
|
+
release: () => {
|
|
696
|
+
for (const { source, onAbort } of listeners) {
|
|
697
|
+
source.removeEventListener("abort", onAbort);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
var DURATION = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/;
|
|
703
|
+
var UNIT_MS = { ms: 1, s: 1e3, m: 6e4, h: 36e5 };
|
|
704
|
+
var MAX_TIMEOUT_MS = 24 * 36e5;
|
|
705
|
+
function parseTimeoutFlag(value) {
|
|
706
|
+
if (value === void 0) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const match = typeof value === "string" ? DURATION.exec(value.trim()) : null;
|
|
710
|
+
const unit = match?.[2] ?? "s";
|
|
711
|
+
const ms = match === null ? Number.NaN : Math.round(Number(match[1]) * UNIT_MS[unit]);
|
|
712
|
+
if (!(ms >= 1 && ms <= MAX_TIMEOUT_MS)) {
|
|
713
|
+
throw new CliUsageError(
|
|
714
|
+
`--timeout takes a positive duration up to 24h: 90, 90s, 1500ms or 5m (a bare number is seconds); got "${String(value)}".`
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
return ms;
|
|
718
|
+
}
|
|
719
|
+
function parseMaxRetriesFlag(value) {
|
|
720
|
+
if (value === void 0) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const parsed = typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
|
|
724
|
+
if (!(Number.isInteger(parsed) && parsed >= 0 && parsed <= 10)) {
|
|
725
|
+
throw new CliUsageError(
|
|
726
|
+
`--max-retries takes a whole number from 0 to 10; got "${String(value)}".`
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
return parsed;
|
|
730
|
+
}
|
|
731
|
+
function resolveBudget(input) {
|
|
732
|
+
if (input.timeoutMs !== void 0) {
|
|
733
|
+
return { deadlineMs: input.timeoutMs, attemptMs: input.timeoutMs };
|
|
734
|
+
}
|
|
735
|
+
return { deadlineMs: input.defaultTimeoutMs, attemptMs: void 0 };
|
|
736
|
+
}
|
|
737
|
+
|
|
469
738
|
// src/lib/context.ts
|
|
470
739
|
function makeContext(input) {
|
|
471
740
|
const globals = readGlobalFlags(input.flags);
|
|
@@ -473,12 +742,38 @@ function makeContext(input) {
|
|
|
473
742
|
isJsonFlag: globals.json,
|
|
474
743
|
isTtyOut: input.io.isTtyOut
|
|
475
744
|
});
|
|
745
|
+
const budget = resolveBudget({
|
|
746
|
+
timeoutMs: globals.timeoutMs,
|
|
747
|
+
defaultTimeoutMs: input.traits?.defaultTimeoutMs
|
|
748
|
+
});
|
|
749
|
+
const deadline = new AbortController();
|
|
750
|
+
let deadlineTimer;
|
|
751
|
+
const { signal } = anySignal([input.io.signal, deadline.signal]);
|
|
752
|
+
const transport = createTransportState();
|
|
476
753
|
const cache = /* @__PURE__ */ new Map();
|
|
477
|
-
|
|
754
|
+
const ctx = {
|
|
478
755
|
io: input.io,
|
|
479
756
|
mode,
|
|
480
757
|
globals,
|
|
481
758
|
rawArgv: input.rawArgv,
|
|
759
|
+
signal,
|
|
760
|
+
budget,
|
|
761
|
+
transport,
|
|
762
|
+
traits: input.traits,
|
|
763
|
+
startDeadline: () => {
|
|
764
|
+
const ms = budget.deadlineMs;
|
|
765
|
+
if (ms === void 0 || deadlineTimer !== void 0) {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
deadlineTimer = setTimeout(() => {
|
|
769
|
+
deadline.abort(new CliTimeoutError(ms));
|
|
770
|
+
}, ms);
|
|
771
|
+
},
|
|
772
|
+
stopDeadline: () => {
|
|
773
|
+
if (deadlineTimer !== void 0) {
|
|
774
|
+
clearTimeout(deadlineTimer);
|
|
775
|
+
}
|
|
776
|
+
},
|
|
482
777
|
client: (options) => {
|
|
483
778
|
const key = options?.allowAnonymous === true ? "anonymous" : "authed";
|
|
484
779
|
const existing = cache.get(key);
|
|
@@ -490,11 +785,20 @@ function makeContext(input) {
|
|
|
490
785
|
env: input.io.env,
|
|
491
786
|
allowAnonymous: options?.allowAnonymous === true
|
|
492
787
|
});
|
|
493
|
-
const client = buildSdkClient(auth);
|
|
788
|
+
const client = buildSdkClient(auth, sdkTransportFor(ctx));
|
|
494
789
|
cache.set(key, client);
|
|
495
790
|
return client;
|
|
496
791
|
}
|
|
497
792
|
};
|
|
793
|
+
return ctx;
|
|
794
|
+
}
|
|
795
|
+
function sdkTransportFor(ctx) {
|
|
796
|
+
return {
|
|
797
|
+
signal: ctx.signal,
|
|
798
|
+
fetch: recordingFetch(ctx.transport),
|
|
799
|
+
timeoutMs: ctx.budget.attemptMs,
|
|
800
|
+
maxRetries: ctx.globals.maxRetries
|
|
801
|
+
};
|
|
498
802
|
}
|
|
499
803
|
function makeFallbackContext(io, rawArgv) {
|
|
500
804
|
const flags = {
|
|
@@ -510,7 +814,9 @@ function readGlobalFlags(flags) {
|
|
|
510
814
|
yes: flags.yes === true,
|
|
511
815
|
apiKey: readStringFlag(flags.apiKey),
|
|
512
816
|
brand: readStringFlag(flags.brand),
|
|
513
|
-
apiUrl: readStringFlag(flags.apiUrl)
|
|
817
|
+
apiUrl: readStringFlag(flags.apiUrl),
|
|
818
|
+
timeoutMs: parseTimeoutFlag(flags.timeout),
|
|
819
|
+
maxRetries: parseMaxRetriesFlag(flags.maxRetries)
|
|
514
820
|
};
|
|
515
821
|
}
|
|
516
822
|
function readStringFlag(value) {
|
|
@@ -542,6 +848,14 @@ var GLOBAL_FLAGS = [
|
|
|
542
848
|
{
|
|
543
849
|
flag: "--api-url <url>",
|
|
544
850
|
summary: "API base URL (else BREW_API_URL, else https://brew.new/api)"
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
flag: "--timeout <duration>",
|
|
854
|
+
summary: "Give up after this long, retries and the response body included (90s, 1500ms, 5m)"
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
flag: "--max-retries <n>",
|
|
858
|
+
summary: "Retries after a transient failure, 0-10 (default 2; raw requests make one attempt)"
|
|
545
859
|
}
|
|
546
860
|
];
|
|
547
861
|
function asSdkInput(value) {
|
|
@@ -653,10 +967,69 @@ var IDEMPOTENCY_FLAG = {
|
|
|
653
967
|
flag: "--idempotency-key <key>",
|
|
654
968
|
summary: "Idempotency-Key for safe retries (auto-generated otherwise)"
|
|
655
969
|
};
|
|
970
|
+
var NON_REPLAYING_IDEMPOTENCY_FLAG = {
|
|
971
|
+
flag: "--idempotency-key <key>",
|
|
972
|
+
summary: "Accepted for compatibility only: this route never replays, so a retry runs again"
|
|
973
|
+
};
|
|
656
974
|
var INPUT_FLAG = {
|
|
657
975
|
flag: "--input <json>",
|
|
658
976
|
summary: "Full JSON request body, or - to read stdin (flags override it)"
|
|
659
977
|
};
|
|
978
|
+
var LONG_RUNNING_ROUTES = [
|
|
979
|
+
{
|
|
980
|
+
method: "POST",
|
|
981
|
+
path: "/v1/emails",
|
|
982
|
+
timeoutMs: GENERATE_EMAIL_DEFAULT_TIMEOUT_MS
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
method: "PATCH",
|
|
986
|
+
path: "/v1/emails/{emailId}",
|
|
987
|
+
timeoutMs: EDIT_EMAIL_DEFAULT_TIMEOUT_MS
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
method: "POST",
|
|
991
|
+
path: "/v1/emails/audit",
|
|
992
|
+
timeoutMs: AUDIT_EMAIL_DEFAULT_TIMEOUT_MS
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
method: "POST",
|
|
996
|
+
path: "/v1/emails/{emailId}/client-previews",
|
|
997
|
+
timeoutMs: PREVIEW_EMAIL_CLIENTS_DEFAULT_TIMEOUT_MS
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
method: "POST",
|
|
1001
|
+
path: "/v1/emails/import",
|
|
1002
|
+
timeoutMs: IMPORT_EMAIL_DEFAULT_TIMEOUT_MS
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
method: "POST",
|
|
1006
|
+
path: "/v1/emails/figma",
|
|
1007
|
+
timeoutMs: IMPORT_FIGMA_DEFAULT_TIMEOUT_MS
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
method: "POST",
|
|
1011
|
+
path: "/v1/content/gif",
|
|
1012
|
+
timeoutMs: GIF_DEFAULT_TIMEOUT_MS
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
method: "POST",
|
|
1016
|
+
path: "/v1/content/generate-image",
|
|
1017
|
+
timeoutMs: GENERATE_IMAGE_DEFAULT_TIMEOUT_MS
|
|
1018
|
+
}
|
|
1019
|
+
];
|
|
1020
|
+
function longRunningTimeoutMs(input) {
|
|
1021
|
+
const bare = input.path.split("?")[0] ?? input.path;
|
|
1022
|
+
return LONG_RUNNING_ROUTES.find(
|
|
1023
|
+
(route) => route.method === input.method && matchesTemplate(route.path, bare)
|
|
1024
|
+
)?.timeoutMs;
|
|
1025
|
+
}
|
|
1026
|
+
function matchesTemplate(template, path) {
|
|
1027
|
+
const want = template.split("/");
|
|
1028
|
+
const have = path.split("/");
|
|
1029
|
+
return want.length === have.length && want.every(
|
|
1030
|
+
(segment, index) => segment.startsWith("{") && segment.endsWith("}") ? (have[index] ?? "") !== "" : segment === have[index]
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
660
1033
|
|
|
661
1034
|
// src/lib/paginate.ts
|
|
662
1035
|
async function collectAll(ctx, fetchPage) {
|
|
@@ -664,7 +1037,20 @@ async function collectAll(ctx, fetchPage) {
|
|
|
664
1037
|
let cursor;
|
|
665
1038
|
let pageCount = 0;
|
|
666
1039
|
for (; ; ) {
|
|
667
|
-
|
|
1040
|
+
let page;
|
|
1041
|
+
try {
|
|
1042
|
+
ctx.signal.throwIfAborted();
|
|
1043
|
+
page = await fetchPage(cursor);
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
if (pageCount > 0) {
|
|
1046
|
+
ctx.transport.setDrain({
|
|
1047
|
+
rowsFetched: rows.length,
|
|
1048
|
+
pagesFetched: pageCount,
|
|
1049
|
+
...cursor === void 0 ? {} : { resumeCursor: cursor }
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
throw error;
|
|
1053
|
+
}
|
|
668
1054
|
rows.push(...page.data);
|
|
669
1055
|
pageCount += 1;
|
|
670
1056
|
const next = page.pagination?.cursor ?? null;
|
|
@@ -857,6 +1243,106 @@ var analyticsCampaignsCommand = defineCommand({
|
|
|
857
1243
|
}
|
|
858
1244
|
});
|
|
859
1245
|
|
|
1246
|
+
// src/commands/analytics/event-counts.ts
|
|
1247
|
+
var ROW_ONLY_KEYS = {
|
|
1248
|
+
cursor: "A grouped count is not paged; drop `cursor`.",
|
|
1249
|
+
automationRunId: "`groupBy` and `bucket` count email events and do not combine with `automationRunId`."
|
|
1250
|
+
};
|
|
1251
|
+
var analyticsEventCountsCommand = defineCommand({
|
|
1252
|
+
path: ["analytics", "event-counts"],
|
|
1253
|
+
summary: "Count email events per field and/or period (clicks per link, events per day, unsubscribe reasons)",
|
|
1254
|
+
sdkMethod: "analytics.eventCounts",
|
|
1255
|
+
route: { method: "GET", path: "/v1/analytics/events" },
|
|
1256
|
+
commandClass: "read",
|
|
1257
|
+
flags: [
|
|
1258
|
+
{
|
|
1259
|
+
flag: "--group-by <fields>",
|
|
1260
|
+
summary: "One or two comma-separated fields: eventType, emailId, automationId, sendId, source, link, recipientDomain, unsubscribeReason"
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
flag: "--bucket <period>",
|
|
1264
|
+
summary: "Count per UTC day, week (Monday start) or month"
|
|
1265
|
+
},
|
|
1266
|
+
{ flag: "--since <datetime>", summary: "Window start (ISO-8601)" },
|
|
1267
|
+
{ flag: "--until <datetime>", summary: "Window end (ISO-8601)" },
|
|
1268
|
+
{
|
|
1269
|
+
flag: "--event-type <type>",
|
|
1270
|
+
summary: "Only one email event type (e.g. clicked)"
|
|
1271
|
+
},
|
|
1272
|
+
{ flag: "--send-id <sendId>", summary: "Only one send" },
|
|
1273
|
+
{ flag: "--email-id <emailId>", summary: "Only one design" },
|
|
1274
|
+
{
|
|
1275
|
+
flag: "--recipient <rules>",
|
|
1276
|
+
summary: "CSV of recipient rules (max 10): an address, @domain, or substring; prefix ! to exclude"
|
|
1277
|
+
},
|
|
1278
|
+
INPUT_FLAG
|
|
1279
|
+
],
|
|
1280
|
+
examples: [
|
|
1281
|
+
"brew-cli analytics event-counts --send-id snd_123 --event-type clicked --group-by link",
|
|
1282
|
+
"brew-cli analytics event-counts --group-by eventType --bucket day",
|
|
1283
|
+
"brew-cli analytics event-counts --group-by unsubscribeReason --json"
|
|
1284
|
+
],
|
|
1285
|
+
run: async ({ ctx, flags }) => {
|
|
1286
|
+
const base = await readJsonFlag(ctx, flags.input, "--input");
|
|
1287
|
+
const input = mergeInput(base, {
|
|
1288
|
+
groupBy: flagString(flags.groupBy),
|
|
1289
|
+
bucket: flagString(flags.bucket),
|
|
1290
|
+
from: flagString(flags.since),
|
|
1291
|
+
to: flagString(flags.until),
|
|
1292
|
+
eventType: flagString(flags.eventType),
|
|
1293
|
+
sendId: flagString(flags.sendId),
|
|
1294
|
+
emailId: flagString(flags.emailId),
|
|
1295
|
+
recipient: flagString(flags.recipient)
|
|
1296
|
+
});
|
|
1297
|
+
if (input.groupBy === void 0 && input.bucket === void 0) {
|
|
1298
|
+
throw new CliUsageError(
|
|
1299
|
+
"Pass --group-by, --bucket, or both (`analytics events` lists the rows)."
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
for (const [key, reason] of Object.entries(ROW_ONLY_KEYS)) {
|
|
1303
|
+
if (input[key] !== void 0) {
|
|
1304
|
+
throw new CliUsageError(reason);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
const { limit: _pageSize, ...counted } = input;
|
|
1308
|
+
const body = await ctx.client().analytics.eventCounts(
|
|
1309
|
+
asSdkInput({
|
|
1310
|
+
...counted,
|
|
1311
|
+
...counted.groupBy === void 0 ? {} : { groupBy: toFieldList(counted.groupBy) }
|
|
1312
|
+
})
|
|
1313
|
+
);
|
|
1314
|
+
return { data: body, human: renderCounts(body) };
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
function toFieldList(value) {
|
|
1318
|
+
if (typeof value !== "string") {
|
|
1319
|
+
return value;
|
|
1320
|
+
}
|
|
1321
|
+
return value.split(",").map((field) => field.trim()).filter((field) => field !== "");
|
|
1322
|
+
}
|
|
1323
|
+
function renderCounts(body) {
|
|
1324
|
+
if (body.groups.length === 0) {
|
|
1325
|
+
return `No events counted (total ${body.count}).`;
|
|
1326
|
+
}
|
|
1327
|
+
const rows = body.groups.map((group) => ({
|
|
1328
|
+
group: Object.entries(group.key ?? {}).map(([field, value]) => `${field}=${String(value)}`).join(" "),
|
|
1329
|
+
...group.bucket === void 0 ? {} : { bucket: group.bucket },
|
|
1330
|
+
count: group.count
|
|
1331
|
+
}));
|
|
1332
|
+
const table = renderTable(rows, [
|
|
1333
|
+
...rows.some((row) => row.group !== "") ? [{ key: "group", header: "GROUP" }] : [],
|
|
1334
|
+
...rows.some((row) => "bucket" in row) ? [{ key: "bucket", header: "PERIOD" }] : [],
|
|
1335
|
+
{ key: "count", header: "COUNT" }
|
|
1336
|
+
]);
|
|
1337
|
+
const footer = [
|
|
1338
|
+
`total ${body.count}`,
|
|
1339
|
+
body.otherCount > 0 ? `${body.otherCount} in unlisted groups` : "",
|
|
1340
|
+
body.truncated ? `truncated (counted from ${body.coveredFrom ?? "?"})` : ""
|
|
1341
|
+
].filter(Boolean).join(" \xB7 ");
|
|
1342
|
+
return `${table}
|
|
1343
|
+
${footer}`;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
860
1346
|
// src/commands/analytics/events.ts
|
|
861
1347
|
var analyticsEventsCommand = defineCommand({
|
|
862
1348
|
path: ["analytics", "events"],
|
|
@@ -1206,6 +1692,66 @@ var analyticsTriggerInstancesListCommand = defineCommand({
|
|
|
1206
1692
|
});
|
|
1207
1693
|
|
|
1208
1694
|
// src/lib/raw-request.ts
|
|
1695
|
+
var DEFAULT_ATTEMPT_TIMEOUT_MS = 3e4;
|
|
1696
|
+
async function rawFetch(ctx, request) {
|
|
1697
|
+
const attempt = new AbortController();
|
|
1698
|
+
const timer = setTimeout(() => {
|
|
1699
|
+
attempt.abort(new CliTimeoutError(request.timeoutMs));
|
|
1700
|
+
}, request.timeoutMs);
|
|
1701
|
+
const combined = anySignal([ctx.signal, attempt.signal]);
|
|
1702
|
+
const { signal } = combined;
|
|
1703
|
+
if (signal.aborted) {
|
|
1704
|
+
clearTimeout(timer);
|
|
1705
|
+
combined.release();
|
|
1706
|
+
throw signal.reason;
|
|
1707
|
+
}
|
|
1708
|
+
ctx.transport.record({
|
|
1709
|
+
method: request.method,
|
|
1710
|
+
path: apiPathOf(new URL(request.url)),
|
|
1711
|
+
idempotencyKey: request.headers.get("idempotency-key") ?? void 0
|
|
1712
|
+
});
|
|
1713
|
+
try {
|
|
1714
|
+
const response = await fetch(request.url, {
|
|
1715
|
+
method: request.method,
|
|
1716
|
+
headers: request.headers,
|
|
1717
|
+
signal,
|
|
1718
|
+
...request.body === void 0 ? {} : { body: request.body }
|
|
1719
|
+
});
|
|
1720
|
+
return { response, text: await readBodyText(response, signal) };
|
|
1721
|
+
} catch (error) {
|
|
1722
|
+
if (signal.aborted) {
|
|
1723
|
+
throw signal.reason;
|
|
1724
|
+
}
|
|
1725
|
+
throw new CliConnectionError(error);
|
|
1726
|
+
} finally {
|
|
1727
|
+
clearTimeout(timer);
|
|
1728
|
+
combined.release();
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
async function readBodyText(response, signal) {
|
|
1732
|
+
if (response.body === null) {
|
|
1733
|
+
return "";
|
|
1734
|
+
}
|
|
1735
|
+
const reader = response.body.getReader();
|
|
1736
|
+
const onAbort = () => {
|
|
1737
|
+
reader.cancel(signal.reason).catch(() => void 0);
|
|
1738
|
+
};
|
|
1739
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1740
|
+
const decoder = new TextDecoder();
|
|
1741
|
+
let text = "";
|
|
1742
|
+
try {
|
|
1743
|
+
for (; ; ) {
|
|
1744
|
+
const { done, value } = await reader.read();
|
|
1745
|
+
signal.throwIfAborted();
|
|
1746
|
+
if (done) {
|
|
1747
|
+
return text + decoder.decode();
|
|
1748
|
+
}
|
|
1749
|
+
text += decoder.decode(value, { stream: true });
|
|
1750
|
+
}
|
|
1751
|
+
} finally {
|
|
1752
|
+
signal.removeEventListener("abort", onAbort);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1209
1755
|
async function rawRequest(ctx, request) {
|
|
1210
1756
|
const auth = resolveAuth({
|
|
1211
1757
|
globals: ctx.globals,
|
|
@@ -1229,13 +1775,13 @@ async function rawRequest(ctx, request) {
|
|
|
1229
1775
|
url.searchParams.set(key, value);
|
|
1230
1776
|
}
|
|
1231
1777
|
}
|
|
1232
|
-
const response = await
|
|
1778
|
+
const { response, text } = await rawFetch(ctx, {
|
|
1233
1779
|
method: request.method,
|
|
1780
|
+
url: url.toString(),
|
|
1234
1781
|
headers,
|
|
1235
1782
|
...hasBody ? { body: JSON.stringify(request.body) } : {},
|
|
1236
|
-
|
|
1783
|
+
timeoutMs: ctx.budget.attemptMs ?? DEFAULT_ATTEMPT_TIMEOUT_MS
|
|
1237
1784
|
});
|
|
1238
|
-
const text = await response.text();
|
|
1239
1785
|
const parsed = tryParseJson(text);
|
|
1240
1786
|
if (!response.ok) {
|
|
1241
1787
|
throw responseToApiError(response, parsed);
|
|
@@ -1337,7 +1883,7 @@ var apiCommand = defineCommand({
|
|
|
1337
1883
|
},
|
|
1338
1884
|
{
|
|
1339
1885
|
flag: "--idempotency-key <key>",
|
|
1340
|
-
summary: "Idempotency-Key header for safe POST retries"
|
|
1886
|
+
summary: "Idempotency-Key header for safe POST retries (a POST gets one generated otherwise)"
|
|
1341
1887
|
}
|
|
1342
1888
|
],
|
|
1343
1889
|
examples: [
|
|
@@ -1369,7 +1915,7 @@ var apiCommand = defineCommand({
|
|
|
1369
1915
|
auth,
|
|
1370
1916
|
path,
|
|
1371
1917
|
hasJsonBody: body !== void 0,
|
|
1372
|
-
idempotencyKey: typeof flags.idempotencyKey === "string" ? flags.idempotencyKey : void 0
|
|
1918
|
+
idempotencyKey: typeof flags.idempotencyKey === "string" && flags.idempotencyKey !== "" ? flags.idempotencyKey : method === "POST" ? crypto.randomUUID() : void 0
|
|
1373
1919
|
});
|
|
1374
1920
|
for (const header of readHeaderFlags(flags.header)) {
|
|
1375
1921
|
const separator = header.indexOf(":");
|
|
@@ -1383,12 +1929,16 @@ var apiCommand = defineCommand({
|
|
|
1383
1929
|
header.slice(separator + 1).trim()
|
|
1384
1930
|
);
|
|
1385
1931
|
}
|
|
1386
|
-
const response = await
|
|
1932
|
+
const { response, text } = await rawFetch(ctx, {
|
|
1387
1933
|
method,
|
|
1934
|
+
url: `${auth.apiUrl}${path}`,
|
|
1388
1935
|
headers,
|
|
1389
|
-
...body === void 0 ? {} : { body }
|
|
1936
|
+
...body === void 0 ? {} : { body },
|
|
1937
|
+
// --timeout, else the SDK's own default for this route when it is a
|
|
1938
|
+
// long-running one (`api POST /v1/emails` waits as long as `emails
|
|
1939
|
+
// generate`), else the SDK's 30 s.
|
|
1940
|
+
timeoutMs: ctx.budget.attemptMs ?? longRunningTimeoutMs({ method, path: barePath }) ?? DEFAULT_ATTEMPT_TIMEOUT_MS
|
|
1390
1941
|
});
|
|
1391
|
-
const text = await response.text();
|
|
1392
1942
|
const parsed = tryParseJson(text);
|
|
1393
1943
|
if (!response.ok) {
|
|
1394
1944
|
throw responseToApiError(response, parsed);
|
|
@@ -1451,7 +2001,9 @@ var apiKeysCreateCommand = defineCommand({
|
|
|
1451
2001
|
summary: "Bind the NEW key to this brand id (omit for an organization-wide key); not the acting --brand"
|
|
1452
2002
|
},
|
|
1453
2003
|
INPUT_FLAG,
|
|
1454
|
-
|
|
2004
|
+
// The route never replays (a replay would disclose the plaintext key
|
|
2005
|
+
// again): a retry mints a second key, whatever the key says.
|
|
2006
|
+
NON_REPLAYING_IDEMPOTENCY_FLAG
|
|
1455
2007
|
],
|
|
1456
2008
|
examples: [
|
|
1457
2009
|
"brew-cli api-keys create --name CI --permissions emails domains",
|
|
@@ -1495,9 +2047,31 @@ var apiKeysListCommand = defineCommand({
|
|
|
1495
2047
|
sdkMethod: "apiKeys.list",
|
|
1496
2048
|
route: { method: "GET", path: "/v1/api-keys" },
|
|
1497
2049
|
commandClass: "read",
|
|
2050
|
+
flags: [LIMIT_FLAG, CURSOR_FLAG, ALL_FLAG],
|
|
1498
2051
|
examples: ["brew-cli api-keys list", "brew-cli api-keys list --json"],
|
|
1499
|
-
run: async ({ ctx }) => {
|
|
1500
|
-
const
|
|
2052
|
+
run: async ({ ctx, flags }) => {
|
|
2053
|
+
const limit = flagInt(flags.limit, "--limit");
|
|
2054
|
+
const cursor = flagString(flags.cursor);
|
|
2055
|
+
if (limit === void 0 && cursor === void 0 && flags.all !== true) {
|
|
2056
|
+
const result2 = await ctx.client().apiKeys.list();
|
|
2057
|
+
return { data: result2, human: renderApiKeys(result2.data) };
|
|
2058
|
+
}
|
|
2059
|
+
const page = (after) => rawRequest(ctx, {
|
|
2060
|
+
method: "GET",
|
|
2061
|
+
path: "/v1/api-keys",
|
|
2062
|
+
query: {
|
|
2063
|
+
limit: limit === void 0 ? void 0 : String(limit),
|
|
2064
|
+
cursor: after
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
if (flags.all === true) {
|
|
2068
|
+
const rows = await collectAll(ctx, (next) => page(next ?? cursor));
|
|
2069
|
+
return {
|
|
2070
|
+
data: { data: rows, pagination: { cursor: null, hasMore: false } },
|
|
2071
|
+
human: renderApiKeys(rows)
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
const result = await page(cursor);
|
|
1501
2075
|
return { data: result, human: renderApiKeys(result.data) };
|
|
1502
2076
|
}
|
|
1503
2077
|
});
|
|
@@ -1694,6 +2268,10 @@ var audiencesListCommand = defineCommand({
|
|
|
1694
2268
|
flag: "--include <tokens>",
|
|
1695
2269
|
summary: "0.6 flag: includes ride the detail read now (`audiences get --include count,build`)"
|
|
1696
2270
|
},
|
|
2271
|
+
{
|
|
2272
|
+
flag: "--search <text>",
|
|
2273
|
+
summary: "Only audiences whose name contains this text"
|
|
2274
|
+
},
|
|
1697
2275
|
LIMIT_FLAG,
|
|
1698
2276
|
CURSOR_FLAG,
|
|
1699
2277
|
ALL_FLAG,
|
|
@@ -1710,6 +2288,7 @@ var audiencesListCommand = defineCommand({
|
|
|
1710
2288
|
throw includeRidesDetailRead("audiences get");
|
|
1711
2289
|
}
|
|
1712
2290
|
const input = mergeInput(base, {
|
|
2291
|
+
search: flagString(flags.search),
|
|
1713
2292
|
limit: flagInt(flags.limit, "--limit"),
|
|
1714
2293
|
cursor: flagString(flags.cursor)
|
|
1715
2294
|
});
|
|
@@ -1748,23 +2327,41 @@ function renderAudiences(rows) {
|
|
|
1748
2327
|
// src/commands/audiences/update.ts
|
|
1749
2328
|
var audiencesUpdateCommand = defineCommand({
|
|
1750
2329
|
path: ["audiences", "update"],
|
|
1751
|
-
summary: "Update an audience segment
|
|
2330
|
+
summary: "Update an audience segment: its name, its filters, or its members by email",
|
|
1752
2331
|
sdkMethod: "audiences.update",
|
|
1753
2332
|
route: { method: "PATCH", path: "/v1/audiences/{audienceId}" },
|
|
1754
2333
|
commandClass: "write",
|
|
1755
2334
|
args: [
|
|
1756
2335
|
{ name: "audienceId", summary: "Audience id to update", isRequired: true }
|
|
1757
2336
|
],
|
|
1758
|
-
flags: [
|
|
2337
|
+
flags: [
|
|
2338
|
+
{ flag: "--name <name>", summary: "New audience name" },
|
|
2339
|
+
{
|
|
2340
|
+
flag: "--add-email <addresses...>",
|
|
2341
|
+
summary: "Add these contacts by email, repeatable (refused when the filters cannot express it exactly)"
|
|
2342
|
+
},
|
|
2343
|
+
{
|
|
2344
|
+
flag: "--remove-email <addresses...>",
|
|
2345
|
+
summary: "Remove these contacts by email, repeatable (same rule)"
|
|
2346
|
+
},
|
|
2347
|
+
INPUT_FLAG
|
|
2348
|
+
],
|
|
1759
2349
|
examples: [
|
|
1760
2350
|
'brew-cli audiences update aud_3k9sQ --name "VIP customers"',
|
|
2351
|
+
"brew-cli audiences update aud_3k9sQ --add-email ada@example.com --remove-email bo@example.com",
|
|
1761
2352
|
`brew-cli audiences update aud_3k9sQ --input '{"filters":{"filters":[{"field":"plan","operator":"equals","value":"vip"}],"logicalOperator":"and"}}'`
|
|
1762
2353
|
],
|
|
1763
2354
|
run: async ({ ctx, args, flags }) => {
|
|
1764
2355
|
const base = await readJsonFlag(ctx, flags.input, "--input");
|
|
1765
|
-
const input = mergeInput(base, {
|
|
2356
|
+
const input = mergeInput(base, {
|
|
2357
|
+
name: flagString(flags.name),
|
|
2358
|
+
addEmails: toStringArray(flags.addEmail),
|
|
2359
|
+
removeEmails: toStringArray(flags.removeEmail)
|
|
2360
|
+
});
|
|
1766
2361
|
if (Object.keys(input).length === 0) {
|
|
1767
|
-
throw new CliUsageError(
|
|
2362
|
+
throw new CliUsageError(
|
|
2363
|
+
"Nothing to update \u2014 pass --name, --add-email, --remove-email or --input."
|
|
2364
|
+
);
|
|
1768
2365
|
}
|
|
1769
2366
|
const result = await ctx.client().audiences.update(
|
|
1770
2367
|
asSdkInput({
|
|
@@ -2117,7 +2714,16 @@ var automationsListCommand = defineCommand({
|
|
|
2117
2714
|
sdkMethod: "automations.list",
|
|
2118
2715
|
route: { method: "GET", path: "/v1/automations" },
|
|
2119
2716
|
commandClass: "read",
|
|
2120
|
-
flags: [
|
|
2717
|
+
flags: [
|
|
2718
|
+
{
|
|
2719
|
+
flag: "--search <text>",
|
|
2720
|
+
summary: "Only automations whose name matches these words"
|
|
2721
|
+
},
|
|
2722
|
+
LIMIT_FLAG,
|
|
2723
|
+
CURSOR_FLAG,
|
|
2724
|
+
ALL_FLAG,
|
|
2725
|
+
INPUT_FLAG
|
|
2726
|
+
],
|
|
2121
2727
|
examples: [
|
|
2122
2728
|
"brew-cli automations list",
|
|
2123
2729
|
"brew-cli automations list --all --json"
|
|
@@ -2125,6 +2731,7 @@ var automationsListCommand = defineCommand({
|
|
|
2125
2731
|
run: async ({ ctx, flags }) => {
|
|
2126
2732
|
const base = await readJsonFlag(ctx, flags.input, "--input");
|
|
2127
2733
|
const input = mergeInput(base, {
|
|
2734
|
+
search: flagString(flags.search),
|
|
2128
2735
|
limit: flagInt(flags.limit, "--limit"),
|
|
2129
2736
|
cursor: flagString(flags.cursor)
|
|
2130
2737
|
});
|
|
@@ -2513,17 +3120,14 @@ var FORMAT_FLAG = {
|
|
|
2513
3120
|
var FORMATS = /* @__PURE__ */ new Set(["json", "ts", "zod", "jsonschema", "skill"]);
|
|
2514
3121
|
var ENFORCEMENT_MODES = /* @__PURE__ */ new Set(["off", "prune", "strict"]);
|
|
2515
3122
|
var VALIDATE_ENFORCEMENT_MODES = /* @__PURE__ */ new Set(["prune", "strict"]);
|
|
2516
|
-
function
|
|
3123
|
+
function formatOption(value) {
|
|
2517
3124
|
const format = flagString(value);
|
|
2518
|
-
if (format
|
|
2519
|
-
return "";
|
|
2520
|
-
}
|
|
2521
|
-
if (!FORMATS.has(format)) {
|
|
3125
|
+
if (format !== void 0 && !FORMATS.has(format)) {
|
|
2522
3126
|
throw new CliUsageError(
|
|
2523
3127
|
`Unknown --format '${format}' (expected json | ts | zod | jsonschema | skill).`
|
|
2524
3128
|
);
|
|
2525
3129
|
}
|
|
2526
|
-
return
|
|
3130
|
+
return format;
|
|
2527
3131
|
}
|
|
2528
3132
|
function contentHuman(body) {
|
|
2529
3133
|
return typeof body.content === "string" ? { human: body.content } : {};
|
|
@@ -2531,8 +3135,7 @@ function contentHuman(body) {
|
|
|
2531
3135
|
var automationsTriggersContractGetCommand = defineCommand({
|
|
2532
3136
|
path: ["automations", "triggers", "contract", "get"],
|
|
2533
3137
|
summary: "Read a trigger payload contract: stored when declared, derived otherwise; --format renders ts/zod/jsonschema/skill",
|
|
2534
|
-
sdkMethod:
|
|
2535
|
-
isRawTransport: true,
|
|
3138
|
+
sdkMethod: "automations.triggers.getContract",
|
|
2536
3139
|
route: {
|
|
2537
3140
|
method: "GET",
|
|
2538
3141
|
path: "/v1/automations/triggers/{triggerEventId}/contract"
|
|
@@ -2552,18 +3155,20 @@ var automationsTriggersContractGetCommand = defineCommand({
|
|
|
2552
3155
|
"brew-cli automations triggers contract get tri_signup --format skill --json | jq -r .content"
|
|
2553
3156
|
],
|
|
2554
3157
|
run: async ({ ctx, args, flags }) => {
|
|
2555
|
-
const
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
3158
|
+
const format = formatOption(flags.format);
|
|
3159
|
+
const body = await ctx.client().automations.triggers.getContract(
|
|
3160
|
+
asSdkInput({
|
|
3161
|
+
triggerEventId: args.triggerEventId ?? "",
|
|
3162
|
+
...format === void 0 ? {} : { format }
|
|
3163
|
+
})
|
|
3164
|
+
);
|
|
2559
3165
|
return { data: body, ...contentHuman(body) };
|
|
2560
3166
|
}
|
|
2561
3167
|
});
|
|
2562
3168
|
var automationsTriggersContractPutCommand = defineCommand({
|
|
2563
3169
|
path: ["automations", "triggers", "contract", "put"],
|
|
2564
3170
|
summary: "Declare (or replace) the stored payload contract for a trigger \u2014 tree-validated before any write; omitting --enforcement leaves the stored setting unchanged",
|
|
2565
|
-
sdkMethod:
|
|
2566
|
-
isRawTransport: true,
|
|
3171
|
+
sdkMethod: "automations.triggers.putContract",
|
|
2567
3172
|
route: {
|
|
2568
3173
|
method: "PUT",
|
|
2569
3174
|
path: "/v1/automations/triggers/{triggerEventId}/contract"
|
|
@@ -2615,19 +3220,19 @@ var automationsTriggersContractPutCommand = defineCommand({
|
|
|
2615
3220
|
"Empty contract body \u2014 pass --input (fields and/or name) or --enforcement <mode>."
|
|
2616
3221
|
);
|
|
2617
3222
|
}
|
|
2618
|
-
const body = await
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
3223
|
+
const body = await ctx.client().automations.triggers.putContract(
|
|
3224
|
+
asSdkInput({
|
|
3225
|
+
...requestBody,
|
|
3226
|
+
triggerEventId: args.triggerEventId ?? ""
|
|
3227
|
+
})
|
|
3228
|
+
);
|
|
2623
3229
|
return { data: body };
|
|
2624
3230
|
}
|
|
2625
3231
|
});
|
|
2626
3232
|
var automationsTriggersContractValidateCommand = defineCommand({
|
|
2627
3233
|
path: ["automations", "triggers", "contract", "validate"],
|
|
2628
3234
|
summary: "Dry-run a payload against a trigger's contract (the fire path's validator) \u2014 never fires; invalid payloads still exit 0",
|
|
2629
|
-
sdkMethod:
|
|
2630
|
-
isRawTransport: true,
|
|
3235
|
+
sdkMethod: "automations.triggers.validatePayload",
|
|
2631
3236
|
route: {
|
|
2632
3237
|
method: "POST",
|
|
2633
3238
|
path: "/v1/automations/triggers/{triggerEventId}/contract/validate"
|
|
@@ -2662,14 +3267,13 @@ var automationsTriggersContractValidateCommand = defineCommand({
|
|
|
2662
3267
|
`Unknown --enforcement '${enforcement}' (expected prune | strict).`
|
|
2663
3268
|
);
|
|
2664
3269
|
}
|
|
2665
|
-
const body = await
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
body: {
|
|
3270
|
+
const body = await ctx.client().automations.triggers.validatePayload(
|
|
3271
|
+
asSdkInput({
|
|
3272
|
+
triggerEventId: args.triggerEventId ?? "",
|
|
2669
3273
|
payload,
|
|
2670
3274
|
...enforcement !== void 0 ? { enforcement } : {}
|
|
2671
|
-
}
|
|
2672
|
-
|
|
3275
|
+
})
|
|
3276
|
+
);
|
|
2673
3277
|
return { data: body };
|
|
2674
3278
|
}
|
|
2675
3279
|
});
|
|
@@ -3038,21 +3642,36 @@ var brandGetCommand = defineCommand({
|
|
|
3038
3642
|
});
|
|
3039
3643
|
|
|
3040
3644
|
// src/commands/brand/get-images.ts
|
|
3645
|
+
var RETIRED_FILTERS = {
|
|
3646
|
+
type: "--type (type) was retired by the API: use --kind logo|brand|generated.",
|
|
3647
|
+
aspectRatio: "--aspect-ratio (aspectRatio) was retired by the API: every image carries its width and height."
|
|
3648
|
+
};
|
|
3041
3649
|
var brandGetImagesCommand = defineCommand({
|
|
3042
3650
|
path: ["brand", "get-images"],
|
|
3043
|
-
summary: "Browse or semantically search the brand's
|
|
3651
|
+
summary: "Browse or semantically search the brand's assets (logos, brand images, images made with Brew)",
|
|
3044
3652
|
sdkMethod: "brand.getImages",
|
|
3045
3653
|
route: { method: "GET", path: "/v1/brand/images" },
|
|
3046
3654
|
commandClass: "read",
|
|
3047
3655
|
flags: [
|
|
3048
3656
|
{
|
|
3049
3657
|
flag: "--query <text>",
|
|
3050
|
-
summary: "Semantic search over
|
|
3658
|
+
summary: "Semantic search over what the images show (1 credit per new search; logos are not searchable)"
|
|
3659
|
+
},
|
|
3660
|
+
{
|
|
3661
|
+
flag: "--kind <kind>",
|
|
3662
|
+
summary: "logo, brand (from the site or uploaded) or generated"
|
|
3663
|
+
},
|
|
3664
|
+
{
|
|
3665
|
+
flag: "--sort <order>",
|
|
3666
|
+
summary: "Browse order: newest (default) or oldest; ignored by --query"
|
|
3667
|
+
},
|
|
3668
|
+
{
|
|
3669
|
+
flag: "--type <type>",
|
|
3670
|
+
summary: "Retired by the API: use --kind"
|
|
3051
3671
|
},
|
|
3052
|
-
{ flag: "--type <type>", summary: "Filter by image category" },
|
|
3053
3672
|
{
|
|
3054
3673
|
flag: "--aspect-ratio <ratio>",
|
|
3055
|
-
summary: "
|
|
3674
|
+
summary: "Retired by the API: rows carry width and height instead of a shape filter"
|
|
3056
3675
|
},
|
|
3057
3676
|
LIMIT_FLAG,
|
|
3058
3677
|
CURSOR_FLAG,
|
|
@@ -3061,48 +3680,61 @@ var brandGetImagesCommand = defineCommand({
|
|
|
3061
3680
|
],
|
|
3062
3681
|
examples: [
|
|
3063
3682
|
"brew-cli brand get-images",
|
|
3064
|
-
|
|
3683
|
+
"brew-cli brand get-images --kind generated --sort oldest --all",
|
|
3684
|
+
'brew-cli brand get-images --query "team photo" --kind brand'
|
|
3065
3685
|
],
|
|
3066
3686
|
run: async ({ ctx, flags }) => {
|
|
3067
3687
|
const base = await readJsonFlag(ctx, flags.input, "--input");
|
|
3068
3688
|
const input = mergeInput(base, {
|
|
3069
3689
|
q: flagString(flags.query),
|
|
3070
|
-
|
|
3071
|
-
|
|
3690
|
+
kind: flagString(flags.kind),
|
|
3691
|
+
sort: flagString(flags.sort),
|
|
3072
3692
|
limit: flagInt(flags.limit, "--limit"),
|
|
3073
|
-
cursor: flagString(flags.cursor)
|
|
3693
|
+
cursor: flagString(flags.cursor),
|
|
3694
|
+
type: flagString(flags.type),
|
|
3695
|
+
aspectRatio: flagString(flags.aspectRatio)
|
|
3074
3696
|
});
|
|
3075
|
-
const
|
|
3697
|
+
for (const [key, message] of Object.entries(RETIRED_FILTERS)) {
|
|
3698
|
+
if (input[key] !== void 0) {
|
|
3699
|
+
throw new CliUsageError(message);
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
const page = (cursor) => ctx.client().brand.getImages(
|
|
3703
|
+
asSdkInput(
|
|
3704
|
+
cursor === void 0 ? input : { ...input, cursor }
|
|
3705
|
+
)
|
|
3706
|
+
);
|
|
3076
3707
|
if (flags.all === true) {
|
|
3077
|
-
const rows = await collectAll(
|
|
3078
|
-
ctx,
|
|
3079
|
-
(cursor) => brand.getImages(
|
|
3080
|
-
asSdkInput({
|
|
3081
|
-
...input,
|
|
3082
|
-
...cursor === void 0 ? {} : { cursor }
|
|
3083
|
-
})
|
|
3084
|
-
)
|
|
3085
|
-
);
|
|
3708
|
+
const rows = await collectAll(ctx, page);
|
|
3086
3709
|
return {
|
|
3087
3710
|
data: { data: rows, pagination: { cursor: null, hasMore: false } },
|
|
3088
|
-
human:
|
|
3711
|
+
human: renderAssets(rows)
|
|
3089
3712
|
};
|
|
3090
3713
|
}
|
|
3091
|
-
const result = await
|
|
3092
|
-
|
|
3093
|
-
);
|
|
3094
|
-
return { data: result, human: renderImages(result.data) };
|
|
3714
|
+
const result = await page(void 0);
|
|
3715
|
+
return { data: result, human: renderAssets(result.data) };
|
|
3095
3716
|
}
|
|
3096
3717
|
});
|
|
3097
|
-
function
|
|
3718
|
+
function renderAssets(rows) {
|
|
3098
3719
|
if (rows.length === 0) {
|
|
3099
|
-
return "No brand
|
|
3720
|
+
return "No brand assets found.";
|
|
3100
3721
|
}
|
|
3101
|
-
return renderTable(
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3722
|
+
return renderTable(
|
|
3723
|
+
rows.map((row) => ({
|
|
3724
|
+
assetId: row.assetId,
|
|
3725
|
+
kind: row.kind,
|
|
3726
|
+
url: row.url,
|
|
3727
|
+
size: row.width !== void 0 && row.height !== void 0 ? `${row.width}x${row.height}` : "",
|
|
3728
|
+
added: row.addedAt ?? ""
|
|
3729
|
+
})),
|
|
3730
|
+
[
|
|
3731
|
+
{ key: "assetId", header: "ASSET ID" },
|
|
3732
|
+
{ key: "kind", header: "KIND" },
|
|
3733
|
+
{ key: "url", header: "URL" },
|
|
3734
|
+
{ key: "size", header: "SIZE" },
|
|
3735
|
+
{ key: "added", header: "ADDED" }
|
|
3736
|
+
]
|
|
3737
|
+
);
|
|
3106
3738
|
}
|
|
3107
3739
|
|
|
3108
3740
|
// src/commands/brand/update.ts
|
|
@@ -3204,10 +3836,43 @@ var brandsListCommand = defineCommand({
|
|
|
3204
3836
|
sdkMethod: "brands.list",
|
|
3205
3837
|
route: { method: "GET", path: "/v1/brands" },
|
|
3206
3838
|
commandClass: "read",
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3839
|
+
flags: [
|
|
3840
|
+
{
|
|
3841
|
+
flag: "--status <status>",
|
|
3842
|
+
summary: "Only brands in one lifecycle state: extracting | completed | failed | deleting"
|
|
3843
|
+
},
|
|
3844
|
+
LIMIT_FLAG,
|
|
3845
|
+
CURSOR_FLAG,
|
|
3846
|
+
ALL_FLAG
|
|
3847
|
+
],
|
|
3848
|
+
examples: [
|
|
3849
|
+
"brew-cli brands list --json",
|
|
3850
|
+
"brew-cli brands list --status completed",
|
|
3851
|
+
"brew-cli brands list --all --json"
|
|
3852
|
+
],
|
|
3853
|
+
run: async ({ ctx, flags }) => {
|
|
3854
|
+
const input = {
|
|
3855
|
+
status: flagString(flags.status),
|
|
3856
|
+
limit: flagInt(flags.limit, "--limit"),
|
|
3857
|
+
cursor: flagString(flags.cursor)
|
|
3858
|
+
};
|
|
3859
|
+
const brands = ctx.client().brands;
|
|
3860
|
+
if (flags.all === true) {
|
|
3861
|
+
const rows = await collectAll(
|
|
3862
|
+
ctx,
|
|
3863
|
+
(cursor) => brands.list(
|
|
3864
|
+
asSdkInput({
|
|
3865
|
+
...input,
|
|
3866
|
+
...cursor === void 0 ? {} : { cursor }
|
|
3867
|
+
})
|
|
3868
|
+
)
|
|
3869
|
+
);
|
|
3870
|
+
return {
|
|
3871
|
+
data: { data: rows, pagination: { cursor: null, hasMore: false } }
|
|
3872
|
+
};
|
|
3873
|
+
}
|
|
3874
|
+
return { data: await brands.list(asSdkInput(input)) };
|
|
3875
|
+
}
|
|
3211
3876
|
});
|
|
3212
3877
|
|
|
3213
3878
|
// src/commands/chats/get.ts
|
|
@@ -3350,6 +4015,90 @@ var contactsCountCommand = defineCommand({
|
|
|
3350
4015
|
}
|
|
3351
4016
|
});
|
|
3352
4017
|
|
|
4018
|
+
// src/commands/contacts/count-by.ts
|
|
4019
|
+
var contactsCountByCommand = defineCommand({
|
|
4020
|
+
path: ["contacts", "count-by"],
|
|
4021
|
+
summary: "Count contacts per field value, email domain, or signup period (largest group first)",
|
|
4022
|
+
sdkMethod: "contacts.countBy",
|
|
4023
|
+
route: { method: "POST", path: "/v1/contacts/search" },
|
|
4024
|
+
commandClass: "read",
|
|
4025
|
+
flags: [
|
|
4026
|
+
{
|
|
4027
|
+
flag: "--group-by <fields>",
|
|
4028
|
+
summary: "One or two comma-separated fields: a contact field, a custom field, or emailDomain"
|
|
4029
|
+
},
|
|
4030
|
+
{
|
|
4031
|
+
flag: "--bucket <period>",
|
|
4032
|
+
summary: "Count per UTC day, week or month of createdAt"
|
|
4033
|
+
},
|
|
4034
|
+
{ flag: "--search <text>", summary: "Free-text search" },
|
|
4035
|
+
{
|
|
4036
|
+
flag: "--filter <filters...>",
|
|
4037
|
+
summary: "Structured filter field:operator[:value], repeatable"
|
|
4038
|
+
},
|
|
4039
|
+
{ flag: "--audience <audienceId>", summary: "Scope to one audience" },
|
|
4040
|
+
{ flag: "--logic <logic>", summary: "Filter combinator: and | or" },
|
|
4041
|
+
INPUT_FLAG
|
|
4042
|
+
],
|
|
4043
|
+
examples: [
|
|
4044
|
+
"brew-cli contacts count-by --group-by emailDomain",
|
|
4045
|
+
"brew-cli contacts count-by --bucket month --filter subscribed:equals:true",
|
|
4046
|
+
"brew-cli contacts count-by --group-by country,plan --json"
|
|
4047
|
+
],
|
|
4048
|
+
run: async ({ ctx, flags }) => {
|
|
4049
|
+
const base = await readJsonFlag(ctx, flags.input, "--input");
|
|
4050
|
+
const input = mergeInput(base, {
|
|
4051
|
+
groupBy: flagString(flags.groupBy),
|
|
4052
|
+
bucket: flagString(flags.bucket),
|
|
4053
|
+
search: flagString(flags.search),
|
|
4054
|
+
filters: parseFilterFlags(flags.filter),
|
|
4055
|
+
audienceId: flagString(flags.audience),
|
|
4056
|
+
logic: flagString(flags.logic)
|
|
4057
|
+
});
|
|
4058
|
+
if (input.groupBy === void 0 && input.bucket === void 0) {
|
|
4059
|
+
throw new CliUsageError(
|
|
4060
|
+
"Pass --group-by, --bucket, or both (`contacts count` gives the total alone)."
|
|
4061
|
+
);
|
|
4062
|
+
}
|
|
4063
|
+
const body = await ctx.client().contacts.countBy(
|
|
4064
|
+
asSdkInput({
|
|
4065
|
+
...input,
|
|
4066
|
+
...input.groupBy === void 0 ? {} : { groupBy: toFieldList2(input.groupBy) }
|
|
4067
|
+
})
|
|
4068
|
+
);
|
|
4069
|
+
return { data: body, human: renderCounts2(body) };
|
|
4070
|
+
}
|
|
4071
|
+
});
|
|
4072
|
+
function toFieldList2(value) {
|
|
4073
|
+
if (typeof value !== "string") {
|
|
4074
|
+
return value;
|
|
4075
|
+
}
|
|
4076
|
+
return value.split(",").map((field) => field.trim()).filter((field) => field !== "");
|
|
4077
|
+
}
|
|
4078
|
+
function renderCounts2(body) {
|
|
4079
|
+
const groups = body.groups ?? [];
|
|
4080
|
+
if (groups.length === 0) {
|
|
4081
|
+
return `No contacts counted (total ${body.count}).`;
|
|
4082
|
+
}
|
|
4083
|
+
const rows = groups.map((group) => ({
|
|
4084
|
+
group: Object.entries(group.key).map(([field, value]) => `${field}=${String(value)}`).join(" "),
|
|
4085
|
+
...group.bucket === void 0 || group.bucket === null ? {} : { bucket: group.bucket },
|
|
4086
|
+
count: group.count
|
|
4087
|
+
}));
|
|
4088
|
+
const table = renderTable(rows, [
|
|
4089
|
+
...rows.some((row) => row.group !== "") ? [{ key: "group", header: "GROUP" }] : [],
|
|
4090
|
+
...rows.some((row) => "bucket" in row) ? [{ key: "bucket", header: "PERIOD" }] : [],
|
|
4091
|
+
{ key: "count", header: "COUNT" }
|
|
4092
|
+
]);
|
|
4093
|
+
const otherCount = body.otherCount ?? 0;
|
|
4094
|
+
const footer = [
|
|
4095
|
+
`total ${body.count}`,
|
|
4096
|
+
otherCount > 0 ? `${otherCount} in unlisted groups` : ""
|
|
4097
|
+
].filter(Boolean).join(" \xB7 ");
|
|
4098
|
+
return `${table}
|
|
4099
|
+
${footer}`;
|
|
4100
|
+
}
|
|
4101
|
+
|
|
3353
4102
|
// src/commands/contacts/delete.ts
|
|
3354
4103
|
var contactsDeleteCommand = defineCommand({
|
|
3355
4104
|
path: ["contacts", "delete"],
|
|
@@ -3439,35 +4188,65 @@ var contactsGetCommand = defineCommand({
|
|
|
3439
4188
|
// src/commands/contacts/import-csv.ts
|
|
3440
4189
|
var contactsImportCsvCommand = defineCommand({
|
|
3441
4190
|
path: ["contacts", "import-csv"],
|
|
3442
|
-
summary: "Bulk-import contacts from a CSV file or stdin",
|
|
4191
|
+
summary: "Bulk-import contacts from a CSV file or stdin (free; --validate costs 2 credits per address)",
|
|
3443
4192
|
sdkMethod: "contacts.importCsv",
|
|
3444
4193
|
route: { method: "POST", path: "/v1/contacts/import-csv" },
|
|
3445
4194
|
commandClass: "write",
|
|
3446
4195
|
flags: [
|
|
3447
|
-
{
|
|
4196
|
+
{
|
|
4197
|
+
flag: "--file <path>",
|
|
4198
|
+
summary: "CSV file to import, or - for stdin (or csv in --input)"
|
|
4199
|
+
},
|
|
3448
4200
|
{
|
|
3449
4201
|
flag: "--mapping <pairs...>",
|
|
3450
4202
|
summary: "Column mapping csvColumn=fieldName, repeatable"
|
|
3451
4203
|
},
|
|
4204
|
+
{
|
|
4205
|
+
flag: "--date-order <order>",
|
|
4206
|
+
summary: "month_first or day_first: how to read dates like 03/04/2026 (a day over 12 in the column wins; default month-first, with a DATE_ORDER_ASSUMED warning)"
|
|
4207
|
+
},
|
|
4208
|
+
{
|
|
4209
|
+
flag: "--validate",
|
|
4210
|
+
summary: "Deliverability-check every imported address (2 credits per address)"
|
|
4211
|
+
},
|
|
4212
|
+
{
|
|
4213
|
+
flag: "--consent-source <source>",
|
|
4214
|
+
summary: "Stamp a marketing consent record on every row: api, form or import (sets the source of a record given in --input)"
|
|
4215
|
+
},
|
|
4216
|
+
INPUT_FLAG,
|
|
3452
4217
|
IDEMPOTENCY_FLAG
|
|
3453
4218
|
],
|
|
3454
4219
|
examples: [
|
|
3455
4220
|
"brew-cli contacts import-csv --file contacts.csv",
|
|
3456
|
-
"cat contacts.csv | brew-cli contacts import-csv --file - --mapping Email=email"
|
|
4221
|
+
"cat contacts.csv | brew-cli contacts import-csv --file - --mapping Email=email",
|
|
4222
|
+
"brew-cli contacts import-csv --file eu-signups.csv --date-order day_first --consent-source import"
|
|
3457
4223
|
],
|
|
3458
4224
|
run: async ({ ctx, flags }) => {
|
|
3459
|
-
|
|
3460
|
-
|
|
4225
|
+
if (flags.input === "-" && flags.file === "-") {
|
|
4226
|
+
throw new CliUsageError("Only one of --input and --file can read stdin.");
|
|
4227
|
+
}
|
|
4228
|
+
const base = await readJsonFlag(ctx, flags.input, "--input");
|
|
4229
|
+
const consentSource = flagString(flags.consentSource);
|
|
4230
|
+
const input = mergeInput(base, {
|
|
4231
|
+
csv: await readTextFlag(ctx, flags.file, "--file"),
|
|
4232
|
+
mapping: parseKeyValues(flags.mapping, "--mapping"),
|
|
4233
|
+
dateOrder: flagString(flags.dateOrder),
|
|
4234
|
+
validate: flags.validate === true ? true : void 0
|
|
4235
|
+
});
|
|
4236
|
+
if (typeof input.csv !== "string" || input.csv.trim() === "") {
|
|
3461
4237
|
throw new CliUsageError(
|
|
3462
|
-
"--file
|
|
4238
|
+
"Pass the CSV with --file (a path, or - for stdin) or as csv in --input."
|
|
3463
4239
|
);
|
|
3464
4240
|
}
|
|
3465
|
-
|
|
4241
|
+
if (consentSource !== void 0) {
|
|
4242
|
+
const record = input.consent;
|
|
4243
|
+
input.consent = {
|
|
4244
|
+
...typeof record === "object" && record !== null ? record : {},
|
|
4245
|
+
source: consentSource
|
|
4246
|
+
};
|
|
4247
|
+
}
|
|
3466
4248
|
const result = await ctx.client().contacts.importCsv(
|
|
3467
|
-
asSdkInput(
|
|
3468
|
-
csv,
|
|
3469
|
-
...mapping === void 0 ? {} : { mapping }
|
|
3470
|
-
}),
|
|
4249
|
+
asSdkInput(input),
|
|
3471
4250
|
requestOptions(flags)
|
|
3472
4251
|
);
|
|
3473
4252
|
return { data: result };
|
|
@@ -3826,14 +4605,13 @@ var contentAddImageCommand = defineCommand({
|
|
|
3826
4605
|
return { data: result };
|
|
3827
4606
|
}
|
|
3828
4607
|
});
|
|
3829
|
-
|
|
3830
|
-
// src/commands/content/generate-image.ts
|
|
3831
4608
|
var contentGenerateImageCommand = defineCommand({
|
|
3832
4609
|
path: ["content", "generate-image"],
|
|
3833
4610
|
summary: "Generate or edit an image from a prompt",
|
|
3834
4611
|
sdkMethod: "content.generateImage",
|
|
3835
4612
|
route: { method: "POST", path: "/v1/content/generate-image" },
|
|
3836
4613
|
commandClass: "write",
|
|
4614
|
+
defaultTimeoutMs: GENERATE_IMAGE_DEFAULT_TIMEOUT_MS,
|
|
3837
4615
|
isCredited: true,
|
|
3838
4616
|
flags: [
|
|
3839
4617
|
{ flag: "--prompt <text>", summary: "What to generate" },
|
|
@@ -3874,14 +4652,13 @@ var contentGenerateImageCommand = defineCommand({
|
|
|
3874
4652
|
return { data: result };
|
|
3875
4653
|
}
|
|
3876
4654
|
});
|
|
3877
|
-
|
|
3878
|
-
// src/commands/content/gif.ts
|
|
3879
4655
|
var contentGifCommand = defineCommand({
|
|
3880
4656
|
path: ["content", "gif"],
|
|
3881
4657
|
summary: "Create an animated GIF from a prompt, image, or video",
|
|
3882
4658
|
sdkMethod: "content.gif",
|
|
3883
4659
|
route: { method: "POST", path: "/v1/content/gif" },
|
|
3884
4660
|
commandClass: "write",
|
|
4661
|
+
defaultTimeoutMs: GIF_DEFAULT_TIMEOUT_MS,
|
|
3885
4662
|
isCredited: true,
|
|
3886
4663
|
flags: [
|
|
3887
4664
|
{ flag: "--from <source>", summary: "prompt | image | video" },
|
|
@@ -4063,32 +4840,35 @@ var contentTransformCommand = defineCommand({
|
|
|
4063
4840
|
var contractsInferCommand = defineCommand({
|
|
4064
4841
|
path: ["contracts", "infer"],
|
|
4065
4842
|
summary: "Draft a payload contract from a real example payload \u2014 nothing is saved; PUT the draft on a trigger",
|
|
4066
|
-
sdkMethod:
|
|
4067
|
-
isRawTransport: true,
|
|
4843
|
+
sdkMethod: "payloadContracts.infer",
|
|
4068
4844
|
route: { method: "POST", path: "/v1/payload-contracts/infer" },
|
|
4069
4845
|
commandClass: "read",
|
|
4070
4846
|
flags: [INPUT_FLAG],
|
|
4071
4847
|
examples: [
|
|
4072
4848
|
`brew-cli contracts infer --input '{"email":"jane@example.com","order":{"total":9.5}}'`
|
|
4073
4849
|
],
|
|
4074
|
-
// Accepts either the bare example object or `{ example: {...} }
|
|
4075
|
-
//
|
|
4850
|
+
// Accepts either the bare example object or `{ example: {...} }`; only the
|
|
4851
|
+
// envelope carries `subjectKind` (a bare example may have its own field of
|
|
4852
|
+
// that name). Un-inferable spots (nulls, empty arrays) come back under
|
|
4853
|
+
// `issues`.
|
|
4076
4854
|
run: async ({ ctx, flags }) => {
|
|
4077
4855
|
const input = mergeInput(
|
|
4078
4856
|
await readJsonFlag(ctx, flags.input, "--input"),
|
|
4079
4857
|
{}
|
|
4080
4858
|
);
|
|
4081
|
-
const
|
|
4859
|
+
const isEnvelope = input.example !== void 0;
|
|
4860
|
+
const example = isEnvelope ? input.example : input;
|
|
4082
4861
|
if (typeof example !== "object" || example === null || Array.isArray(example) || Object.keys(example).length === 0) {
|
|
4083
4862
|
throw new CliUsageError(
|
|
4084
4863
|
"--input with a non-empty example object is required (the JSON your system sends)."
|
|
4085
4864
|
);
|
|
4086
4865
|
}
|
|
4087
|
-
const body = await
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4866
|
+
const body = await ctx.client().payloadContracts.infer(
|
|
4867
|
+
asSdkInput({
|
|
4868
|
+
example,
|
|
4869
|
+
...isEnvelope && input.subjectKind !== void 0 ? { subjectKind: input.subjectKind } : {}
|
|
4870
|
+
})
|
|
4871
|
+
);
|
|
4092
4872
|
return { data: body };
|
|
4093
4873
|
}
|
|
4094
4874
|
});
|
|
@@ -4125,24 +4905,8 @@ var dataRunCommand = defineCommand({
|
|
|
4125
4905
|
}
|
|
4126
4906
|
});
|
|
4127
4907
|
|
|
4128
|
-
// src/skip-list.ts
|
|
4129
|
-
var SDK_SKIP_LIST = [
|
|
4130
|
-
{
|
|
4131
|
-
sdkPath: "automations.triggers.getContract",
|
|
4132
|
-
reason: "covered by `automations triggers contract get` (raw route, bound pre-SDK-v9; SDK-method migration tracked separately)"
|
|
4133
|
-
},
|
|
4134
|
-
{
|
|
4135
|
-
sdkPath: "automations.triggers.putContract",
|
|
4136
|
-
reason: "covered by `automations triggers contract put` (raw route, bound pre-SDK-v9)"
|
|
4137
|
-
},
|
|
4138
|
-
{
|
|
4139
|
-
sdkPath: "automations.triggers.validatePayload",
|
|
4140
|
-
reason: "covered by `automations triggers contract validate` (raw route, bound pre-SDK-v9)"
|
|
4141
|
-
},
|
|
4142
|
-
{
|
|
4143
|
-
sdkPath: "payloadContracts.infer",
|
|
4144
|
-
reason: "covered by `contracts infer` (raw route, bound pre-SDK-v9)"
|
|
4145
|
-
},
|
|
4908
|
+
// src/skip-list.ts
|
|
4909
|
+
var SDK_SKIP_LIST = [
|
|
4146
4910
|
{
|
|
4147
4911
|
sdkPath: "contacts.searchAll",
|
|
4148
4912
|
reason: "auto-pager covered by `contacts search --all`"
|
|
@@ -4187,7 +4951,9 @@ function buildManifest() {
|
|
|
4187
4951
|
"1": "API or runtime error",
|
|
4188
4952
|
"2": "usage error",
|
|
4189
4953
|
"3": "authentication error",
|
|
4190
|
-
"4": "confirmation required (re-run with --yes)"
|
|
4954
|
+
"4": "confirmation required (re-run with --yes)",
|
|
4955
|
+
"130": "interrupted by SIGINT (Ctrl-C); the CLI then re-raises the signal",
|
|
4956
|
+
"143": "terminated by SIGTERM; the CLI then re-raises the signal"
|
|
4191
4957
|
},
|
|
4192
4958
|
confirmationProtocol: {
|
|
4193
4959
|
exitCode: 4,
|
|
@@ -4295,6 +5061,7 @@ var doctorCommand = defineCommand({
|
|
|
4295
5061
|
isReachable = health.status === "ok";
|
|
4296
5062
|
apiVersion = health.version ?? null;
|
|
4297
5063
|
} catch (error) {
|
|
5064
|
+
rethrowIfStopped(ctx);
|
|
4298
5065
|
reachabilityError = error instanceof Error ? error.message : String(error);
|
|
4299
5066
|
}
|
|
4300
5067
|
let isAuthValid = null;
|
|
@@ -4305,6 +5072,7 @@ var doctorCommand = defineCommand({
|
|
|
4305
5072
|
isAuthValid = true;
|
|
4306
5073
|
plan = usage.plan?.key ?? null;
|
|
4307
5074
|
} catch (error) {
|
|
5075
|
+
rethrowIfStopped(ctx);
|
|
4308
5076
|
if ((error instanceof BrewApiError || error instanceof CliApiError) && (error.status === 401 || error.status === 403)) {
|
|
4309
5077
|
isAuthValid = false;
|
|
4310
5078
|
}
|
|
@@ -4330,6 +5098,7 @@ var doctorCommand = defineCommand({
|
|
|
4330
5098
|
extraLocally = [...localOps].filter((op) => !liveOps.has(op));
|
|
4331
5099
|
isSurfaceChecked = true;
|
|
4332
5100
|
} catch {
|
|
5101
|
+
rethrowIfStopped(ctx);
|
|
4333
5102
|
}
|
|
4334
5103
|
}
|
|
4335
5104
|
const isHealthy = isReachable && isAuthValid !== false && isSurfaceChecked && missingLocally.length === 0;
|
|
@@ -4367,6 +5136,9 @@ ${missingLocally.map((op) => ` ${op}`).join("\n")}` : "-- surface not chec
|
|
|
4367
5136
|
};
|
|
4368
5137
|
}
|
|
4369
5138
|
});
|
|
5139
|
+
function rethrowIfStopped(ctx) {
|
|
5140
|
+
ctx.signal.throwIfAborted();
|
|
5141
|
+
}
|
|
4370
5142
|
|
|
4371
5143
|
// src/commands/domains/add.ts
|
|
4372
5144
|
var domainsAddCommand = defineCommand({
|
|
@@ -4513,6 +5285,193 @@ function renderDomains(rows) {
|
|
|
4513
5285
|
]);
|
|
4514
5286
|
}
|
|
4515
5287
|
|
|
5288
|
+
// src/commands/domains/unsubscribes/add.ts
|
|
5289
|
+
var domainsUnsubscribesAddCommand = defineCommand({
|
|
5290
|
+
path: ["domains", "unsubscribes", "add"],
|
|
5291
|
+
summary: "Add addresses to a marketing domain's unsubscribe list (that domain only)",
|
|
5292
|
+
sdkMethod: "domains.unsubscribes.add",
|
|
5293
|
+
route: { method: "POST", path: "/v1/domains/{domainId}/unsubscribes" },
|
|
5294
|
+
commandClass: "write",
|
|
5295
|
+
args: [
|
|
5296
|
+
{ name: "domainId", summary: "Marketing domain id", isRequired: true }
|
|
5297
|
+
],
|
|
5298
|
+
flags: [
|
|
5299
|
+
{
|
|
5300
|
+
flag: "--email <addresses...>",
|
|
5301
|
+
summary: "Address to suppress, repeatable (up to 1,000)"
|
|
5302
|
+
},
|
|
5303
|
+
IDEMPOTENCY_FLAG
|
|
5304
|
+
],
|
|
5305
|
+
examples: [
|
|
5306
|
+
"brew-cli domains unsubscribes add dom_123 --email ada@example.com --email bo@example.com"
|
|
5307
|
+
],
|
|
5308
|
+
run: async ({ ctx, args, flags }) => {
|
|
5309
|
+
const emails = toStringArray(flags.email);
|
|
5310
|
+
if (emails === void 0) {
|
|
5311
|
+
throw new CliUsageError("Pass at least one --email.");
|
|
5312
|
+
}
|
|
5313
|
+
const body = await ctx.client().domains.unsubscribes.add(
|
|
5314
|
+
{ domainId: args.domainId ?? "", emails: [...emails] },
|
|
5315
|
+
requestOptions(flags)
|
|
5316
|
+
);
|
|
5317
|
+
return { data: body };
|
|
5318
|
+
}
|
|
5319
|
+
});
|
|
5320
|
+
|
|
5321
|
+
// src/commands/domains/unsubscribes/export.ts
|
|
5322
|
+
var domainsUnsubscribesExportCommand = defineCommand({
|
|
5323
|
+
path: ["domains", "unsubscribes", "export"],
|
|
5324
|
+
summary: "A domain's unsubscribe list as CSV (truncated when capped)",
|
|
5325
|
+
sdkMethod: "domains.unsubscribes.export",
|
|
5326
|
+
route: {
|
|
5327
|
+
method: "GET",
|
|
5328
|
+
path: "/v1/domains/{domainId}/unsubscribes/export"
|
|
5329
|
+
},
|
|
5330
|
+
commandClass: "read",
|
|
5331
|
+
args: [
|
|
5332
|
+
{ name: "domainId", summary: "Marketing domain id", isRequired: true }
|
|
5333
|
+
],
|
|
5334
|
+
flags: [
|
|
5335
|
+
{
|
|
5336
|
+
flag: "--scope <scope>",
|
|
5337
|
+
summary: "any (default), domain or all"
|
|
5338
|
+
}
|
|
5339
|
+
],
|
|
5340
|
+
examples: [
|
|
5341
|
+
"brew-cli domains unsubscribes export dom_123 > optouts.csv",
|
|
5342
|
+
"brew-cli domains unsubscribes export dom_123 --scope all --json"
|
|
5343
|
+
],
|
|
5344
|
+
run: async ({ ctx, args, flags }) => {
|
|
5345
|
+
const scope = flagString(flags.scope);
|
|
5346
|
+
const body = await ctx.client().domains.unsubscribes.export(
|
|
5347
|
+
asSdkInput({
|
|
5348
|
+
domainId: args.domainId ?? "",
|
|
5349
|
+
...scope === void 0 ? {} : { scope }
|
|
5350
|
+
})
|
|
5351
|
+
);
|
|
5352
|
+
return { data: body, human: body.csv };
|
|
5353
|
+
}
|
|
5354
|
+
});
|
|
5355
|
+
|
|
5356
|
+
// src/commands/domains/unsubscribes/import.ts
|
|
5357
|
+
var domainsUnsubscribesImportCommand = defineCommand({
|
|
5358
|
+
path: ["domains", "unsubscribes", "import"],
|
|
5359
|
+
summary: "Add every address in a CSV to a domain's unsubscribe list",
|
|
5360
|
+
sdkMethod: "domains.unsubscribes.import",
|
|
5361
|
+
route: {
|
|
5362
|
+
method: "POST",
|
|
5363
|
+
path: "/v1/domains/{domainId}/unsubscribes/import"
|
|
5364
|
+
},
|
|
5365
|
+
commandClass: "write",
|
|
5366
|
+
args: [
|
|
5367
|
+
{ name: "domainId", summary: "Marketing domain id", isRequired: true }
|
|
5368
|
+
],
|
|
5369
|
+
flags: [
|
|
5370
|
+
{ flag: "--file <path>", summary: "CSV file, or - for stdin" },
|
|
5371
|
+
{
|
|
5372
|
+
flag: "--column <header>",
|
|
5373
|
+
summary: "The column holding the addresses (by header)"
|
|
5374
|
+
},
|
|
5375
|
+
IDEMPOTENCY_FLAG
|
|
5376
|
+
],
|
|
5377
|
+
examples: [
|
|
5378
|
+
"brew-cli domains unsubscribes import dom_123 --file optouts.csv --column Email"
|
|
5379
|
+
],
|
|
5380
|
+
run: async ({ ctx, args, flags }) => {
|
|
5381
|
+
const csv = await readTextFlag(ctx, flags.file, "--file");
|
|
5382
|
+
if (csv === void 0 || csv.trim() === "") {
|
|
5383
|
+
throw new CliUsageError(
|
|
5384
|
+
"--file is required (a CSV path, or - for stdin)."
|
|
5385
|
+
);
|
|
5386
|
+
}
|
|
5387
|
+
const column = flagString(flags.column);
|
|
5388
|
+
const body = await ctx.client().domains.unsubscribes.import(
|
|
5389
|
+
{
|
|
5390
|
+
domainId: args.domainId ?? "",
|
|
5391
|
+
csv,
|
|
5392
|
+
...column === void 0 ? {} : { column }
|
|
5393
|
+
},
|
|
5394
|
+
requestOptions(flags)
|
|
5395
|
+
);
|
|
5396
|
+
return { data: body };
|
|
5397
|
+
}
|
|
5398
|
+
});
|
|
5399
|
+
|
|
5400
|
+
// src/commands/domains/unsubscribes/list.ts
|
|
5401
|
+
var domainsUnsubscribesListCommand = defineCommand({
|
|
5402
|
+
path: ["domains", "unsubscribes", "list"],
|
|
5403
|
+
summary: "One page of a marketing domain's unsubscribe list, newest first",
|
|
5404
|
+
sdkMethod: "domains.unsubscribes.list",
|
|
5405
|
+
route: { method: "GET", path: "/v1/domains/{domainId}/unsubscribes" },
|
|
5406
|
+
commandClass: "read",
|
|
5407
|
+
args: [
|
|
5408
|
+
{ name: "domainId", summary: "Marketing domain id", isRequired: true }
|
|
5409
|
+
],
|
|
5410
|
+
flags: [
|
|
5411
|
+
{ flag: "--q <text>", summary: "Search by address" },
|
|
5412
|
+
{
|
|
5413
|
+
flag: "--scope <scope>",
|
|
5414
|
+
summary: "any (default), domain (this list only) or all (brand-wide opt-outs)"
|
|
5415
|
+
},
|
|
5416
|
+
LIMIT_FLAG,
|
|
5417
|
+
CURSOR_FLAG
|
|
5418
|
+
],
|
|
5419
|
+
examples: [
|
|
5420
|
+
"brew-cli domains unsubscribes list dom_123",
|
|
5421
|
+
"brew-cli domains unsubscribes list dom_123 --scope domain --q ada"
|
|
5422
|
+
],
|
|
5423
|
+
run: async ({ ctx, args, flags }) => {
|
|
5424
|
+
const limit = flagInt(flags.limit, "--limit");
|
|
5425
|
+
const q = flagString(flags.q);
|
|
5426
|
+
const scope = flagString(flags.scope);
|
|
5427
|
+
const cursor = flagString(flags.cursor);
|
|
5428
|
+
const body = await ctx.client().domains.unsubscribes.list(
|
|
5429
|
+
asSdkInput({
|
|
5430
|
+
domainId: args.domainId ?? "",
|
|
5431
|
+
...q === void 0 ? {} : { q },
|
|
5432
|
+
...scope === void 0 ? {} : { scope },
|
|
5433
|
+
...limit === void 0 ? {} : { limit },
|
|
5434
|
+
...cursor === void 0 ? {} : { cursor }
|
|
5435
|
+
})
|
|
5436
|
+
);
|
|
5437
|
+
return { data: body, human: renderRows(body.data) };
|
|
5438
|
+
}
|
|
5439
|
+
});
|
|
5440
|
+
function renderRows(rows) {
|
|
5441
|
+
if (rows.length === 0) {
|
|
5442
|
+
return "No unsubscribes on this list.";
|
|
5443
|
+
}
|
|
5444
|
+
return renderTable(rows, [
|
|
5445
|
+
{ key: "email", header: "EMAIL" },
|
|
5446
|
+
{ key: "scope", header: "SCOPE" },
|
|
5447
|
+
{ key: "unsubscribedAt", header: "UNSUBSCRIBED" }
|
|
5448
|
+
]);
|
|
5449
|
+
}
|
|
5450
|
+
|
|
5451
|
+
// src/commands/domains/unsubscribes/remove.ts
|
|
5452
|
+
var domainsUnsubscribesRemoveCommand = defineCommand({
|
|
5453
|
+
path: ["domains", "unsubscribes", "remove"],
|
|
5454
|
+
summary: "Take one address off a domain's unsubscribe list (never re-subscribes a brand-wide opt-out)",
|
|
5455
|
+
sdkMethod: "domains.unsubscribes.remove",
|
|
5456
|
+
route: {
|
|
5457
|
+
method: "DELETE",
|
|
5458
|
+
path: "/v1/domains/{domainId}/unsubscribes/{email}"
|
|
5459
|
+
},
|
|
5460
|
+
commandClass: "write",
|
|
5461
|
+
args: [
|
|
5462
|
+
{ name: "domainId", summary: "Marketing domain id", isRequired: true },
|
|
5463
|
+
{ name: "email", summary: "Address to remove", isRequired: true }
|
|
5464
|
+
],
|
|
5465
|
+
flags: [],
|
|
5466
|
+
examples: ["brew-cli domains unsubscribes remove dom_123 ada@example.com"],
|
|
5467
|
+
run: async ({ ctx, args }) => ({
|
|
5468
|
+
data: await ctx.client().domains.unsubscribes.remove({
|
|
5469
|
+
domainId: args.domainId ?? "",
|
|
5470
|
+
email: args.email ?? ""
|
|
5471
|
+
})
|
|
5472
|
+
})
|
|
5473
|
+
});
|
|
5474
|
+
|
|
4516
5475
|
// src/commands/domains/update.ts
|
|
4517
5476
|
var domainsUpdateCommand = defineCommand({
|
|
4518
5477
|
path: ["domains", "update"],
|
|
@@ -4586,6 +5545,7 @@ var emailsAuditCommand = defineCommand({
|
|
|
4586
5545
|
sdkMethod: "emails.auditEmail",
|
|
4587
5546
|
route: { method: "POST", path: "/v1/emails/audit" },
|
|
4588
5547
|
commandClass: "write",
|
|
5548
|
+
defaultTimeoutMs: AUDIT_EMAIL_DEFAULT_TIMEOUT_MS,
|
|
4589
5549
|
isCredited: true,
|
|
4590
5550
|
flags: [
|
|
4591
5551
|
{
|
|
@@ -4629,19 +5589,11 @@ var emailsAuditCommand = defineCommand({
|
|
|
4629
5589
|
"--file is required (a path, or - for stdin), or emailHtml via --input."
|
|
4630
5590
|
);
|
|
4631
5591
|
}
|
|
4632
|
-
const
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
});
|
|
4638
|
-
return { data: result };
|
|
4639
|
-
} catch (error) {
|
|
4640
|
-
if (signal.aborted) {
|
|
4641
|
-
throw signal.reason;
|
|
4642
|
-
}
|
|
4643
|
-
throw error;
|
|
4644
|
-
}
|
|
5592
|
+
const result = await ctx.client().emails.auditEmail(
|
|
5593
|
+
asSdkInput(input),
|
|
5594
|
+
requestOptions(flags)
|
|
5595
|
+
);
|
|
5596
|
+
return { data: result };
|
|
4645
5597
|
}
|
|
4646
5598
|
});
|
|
4647
5599
|
|
|
@@ -4658,19 +5610,41 @@ var emailsCloneCommand = defineCommand({
|
|
|
4658
5610
|
flag: "--email-version-id <id>",
|
|
4659
5611
|
summary: "Exact source version to clone (default: latest)"
|
|
4660
5612
|
},
|
|
5613
|
+
{
|
|
5614
|
+
flag: "--title <title>",
|
|
5615
|
+
summary: "Name for the clone (default: Copy of <source title>)"
|
|
5616
|
+
},
|
|
5617
|
+
{
|
|
5618
|
+
flag: "--group-id <groupId>",
|
|
5619
|
+
summary: "File the clone under an existing group (grp_\u2026), or ungrouped"
|
|
5620
|
+
},
|
|
5621
|
+
{
|
|
5622
|
+
flag: "--group-name <name>",
|
|
5623
|
+
summary: "File the clone under a group found (or created) by this name; not with --group-id"
|
|
5624
|
+
},
|
|
4661
5625
|
IDEMPOTENCY_FLAG
|
|
4662
5626
|
],
|
|
4663
5627
|
examples: [
|
|
4664
5628
|
"brew-cli emails clone eml_2SmZOWV3ZQ7W5x6g3m4p",
|
|
4665
|
-
"brew-cli emails clone eml_2SmZOWV3ZQ7W5x6g3m4p --email-version-id emv_9f2kX"
|
|
5629
|
+
"brew-cli emails clone eml_2SmZOWV3ZQ7W5x6g3m4p --email-version-id emv_9f2kX",
|
|
5630
|
+
'brew-cli emails clone eml_2SmZOWV3ZQ7W5x6g3m4p --title "Fall sale (B)" --group-name "Fall campaign"'
|
|
4666
5631
|
],
|
|
4667
5632
|
run: async ({ ctx, args, flags }) => {
|
|
4668
5633
|
const emailVersionId = flagString(flags.emailVersionId);
|
|
5634
|
+
const title = flagString(flags.title);
|
|
5635
|
+
const groupId = flagString(flags.groupId);
|
|
5636
|
+
const groupName = flagString(flags.groupName);
|
|
5637
|
+
if (groupId !== void 0 && groupName !== void 0) {
|
|
5638
|
+
throw new CliUsageError("Pass --group-id or --group-name, not both.");
|
|
5639
|
+
}
|
|
4669
5640
|
return {
|
|
4670
5641
|
data: await ctx.client().emails.clone(
|
|
4671
5642
|
asSdkInput({
|
|
4672
5643
|
emailId: args.emailId ?? "",
|
|
4673
|
-
...emailVersionId === void 0 ? {} : { emailVersionId }
|
|
5644
|
+
...emailVersionId === void 0 ? {} : { emailVersionId },
|
|
5645
|
+
...title === void 0 ? {} : { title },
|
|
5646
|
+
...groupId === void 0 ? {} : { groupId },
|
|
5647
|
+
...groupName === void 0 ? {} : { groupName }
|
|
4674
5648
|
}),
|
|
4675
5649
|
requestOptions(flags)
|
|
4676
5650
|
)
|
|
@@ -4757,8 +5731,6 @@ var emailsDeleteCommand = defineCommand({
|
|
|
4757
5731
|
data: await ctx.client().emails.delete({ emailId: args.emailId ?? "" })
|
|
4758
5732
|
})
|
|
4759
5733
|
});
|
|
4760
|
-
|
|
4761
|
-
// src/commands/emails/edit.ts
|
|
4762
5734
|
var ENVELOPE_FIELDS = ["title", "subjectLine", "groupId"];
|
|
4763
5735
|
function isText(value) {
|
|
4764
5736
|
return typeof value === "string" && value !== "";
|
|
@@ -4769,6 +5741,7 @@ var emailsEditCommand = defineCommand({
|
|
|
4769
5741
|
sdkMethod: "emails.edit",
|
|
4770
5742
|
route: { method: "PATCH", path: "/v1/emails/{emailId}" },
|
|
4771
5743
|
commandClass: "write",
|
|
5744
|
+
defaultTimeoutMs: EDIT_EMAIL_DEFAULT_TIMEOUT_MS,
|
|
4772
5745
|
isCredited: true,
|
|
4773
5746
|
args: [{ name: "emailId", summary: "Design id to edit", isRequired: true }],
|
|
4774
5747
|
flags: [
|
|
@@ -4835,8 +5808,7 @@ var emailsEditCommand = defineCommand({
|
|
|
4835
5808
|
isPromptEdit ? "Editing email\u2026 (typically 30-90s)" : envelope.length === 1 && envelope[0] === "subjectLine" ? "Setting the subject line\u2026" : "Updating the design (no AI run)\u2026"
|
|
4836
5809
|
);
|
|
4837
5810
|
const result = await ctx.client().emails.edit(
|
|
4838
|
-
asSdkInput({ ...input, emailId: args.emailId ?? "" })
|
|
4839
|
-
{ timeoutMs: 24e4 }
|
|
5811
|
+
asSdkInput({ ...input, emailId: args.emailId ?? "" })
|
|
4840
5812
|
);
|
|
4841
5813
|
return { data: result };
|
|
4842
5814
|
}
|
|
@@ -4853,12 +5825,16 @@ var emailsExportCommand = defineCommand({
|
|
|
4853
5825
|
flags: [
|
|
4854
5826
|
{
|
|
4855
5827
|
flag: "--provider <provider>",
|
|
4856
|
-
summary: "Connected ESP: braze, hubspot, klaviyo, mailchimp, iterable, postmark, onesignal, mailgun, sendgrid"
|
|
5828
|
+
summary: "Connected ESP: braze, brevo, hubspot, klaviyo, mailchimp, mailjet, iterable, postmark, onesignal, mailgun, sendgrid"
|
|
4857
5829
|
},
|
|
4858
5830
|
{
|
|
4859
5831
|
flag: "--template-name <name>",
|
|
4860
5832
|
summary: "Template name in the ESP (default: the email title)"
|
|
4861
5833
|
},
|
|
5834
|
+
{
|
|
5835
|
+
flag: "--sender-email <email>",
|
|
5836
|
+
summary: "Brevo or Mailjet: the active sender to use (omit when the account has exactly one)"
|
|
5837
|
+
},
|
|
4862
5838
|
{
|
|
4863
5839
|
flag: "--dry-run",
|
|
4864
5840
|
summary: "Validate design, ownership, and ESP connection without creating a template"
|
|
@@ -4873,16 +5849,18 @@ var emailsExportCommand = defineCommand({
|
|
|
4873
5849
|
const provider = flagString(flags.provider);
|
|
4874
5850
|
if (provider === void 0) {
|
|
4875
5851
|
throw new CliUsageError(
|
|
4876
|
-
"--provider is required: braze, hubspot, klaviyo, mailchimp, iterable, postmark, onesignal, mailgun, or sendgrid."
|
|
5852
|
+
"--provider is required: braze, brevo, hubspot, klaviyo, mailchimp, mailjet, iterable, postmark, onesignal, mailgun, or sendgrid."
|
|
4877
5853
|
);
|
|
4878
5854
|
}
|
|
4879
5855
|
const templateName = flagString(flags.templateName);
|
|
5856
|
+
const senderEmail = flagString(flags.senderEmail);
|
|
4880
5857
|
return {
|
|
4881
5858
|
data: await ctx.client().emails.export(
|
|
4882
5859
|
asSdkInput({
|
|
4883
5860
|
emailId: args.emailId ?? "",
|
|
4884
5861
|
provider,
|
|
4885
5862
|
...templateName === void 0 ? {} : { templateName },
|
|
5863
|
+
...senderEmail === void 0 ? {} : { senderEmail },
|
|
4886
5864
|
// The spec field is snake_case, unlike the rest of the API surface.
|
|
4887
5865
|
...flags.dryRun === true ? { dryRun: true } : {}
|
|
4888
5866
|
}),
|
|
@@ -4891,14 +5869,13 @@ var emailsExportCommand = defineCommand({
|
|
|
4891
5869
|
};
|
|
4892
5870
|
}
|
|
4893
5871
|
});
|
|
4894
|
-
|
|
4895
|
-
// src/commands/emails/generate.ts
|
|
4896
5872
|
var emailsGenerateCommand = defineCommand({
|
|
4897
5873
|
path: ["emails", "generate"],
|
|
4898
5874
|
summary: "Generate a new on-brand email design from a prompt",
|
|
4899
5875
|
sdkMethod: "emails.generate",
|
|
4900
5876
|
route: { method: "POST", path: "/v1/emails" },
|
|
4901
5877
|
commandClass: "write",
|
|
5878
|
+
defaultTimeoutMs: GENERATE_EMAIL_DEFAULT_TIMEOUT_MS,
|
|
4902
5879
|
isCredited: true,
|
|
4903
5880
|
flags: [
|
|
4904
5881
|
{ flag: "--prompt <text>", summary: "What the email should be" },
|
|
@@ -4942,10 +5919,10 @@ var emailsGenerateCommand = defineCommand({
|
|
|
4942
5919
|
);
|
|
4943
5920
|
}
|
|
4944
5921
|
progress(ctx, "Generating email\u2026 (typically 30-90s)");
|
|
4945
|
-
const result = await ctx.client().emails.generate(
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
5922
|
+
const result = await ctx.client().emails.generate(
|
|
5923
|
+
asSdkInput(input),
|
|
5924
|
+
requestOptions(flags)
|
|
5925
|
+
);
|
|
4949
5926
|
return { data: result };
|
|
4950
5927
|
}
|
|
4951
5928
|
});
|
|
@@ -4967,20 +5944,39 @@ var emailsGetCommand = defineCommand({
|
|
|
4967
5944
|
flags: [
|
|
4968
5945
|
{
|
|
4969
5946
|
flag: "--include <tokens>",
|
|
4970
|
-
summary: "Comma-separated expansions: html, versions"
|
|
5947
|
+
summary: "Comma-separated expansions: html, versions, text, links"
|
|
5948
|
+
},
|
|
5949
|
+
{
|
|
5950
|
+
flag: "--email-version-id <emailVersionId>",
|
|
5951
|
+
summary: "Read this saved version instead of the current head (ids from --include versions)"
|
|
5952
|
+
},
|
|
5953
|
+
{
|
|
5954
|
+
flag: "--run-id <runId>",
|
|
5955
|
+
summary: "Read the version a generate or edit run produced (the runId it returned)"
|
|
4971
5956
|
}
|
|
4972
5957
|
],
|
|
4973
5958
|
examples: [
|
|
4974
5959
|
"brew-cli emails get eml_2SmZOWV3ZQ7W5x6g3m4p",
|
|
4975
|
-
"brew-cli emails get eml_2SmZOWV3ZQ7W5x6g3m4p --include html,versions"
|
|
5960
|
+
"brew-cli emails get eml_2SmZOWV3ZQ7W5x6g3m4p --include html,versions",
|
|
5961
|
+
"brew-cli emails get eml_2SmZOWV3ZQ7W5x6g3m4p --include text,links",
|
|
5962
|
+
"brew-cli emails get eml_2SmZOWV3ZQ7W5x6g3m4p --email-version-id emv_2SmZOWV3ZQ7W5x6g3m4p --include html"
|
|
4976
5963
|
],
|
|
4977
5964
|
run: async ({ ctx, args, flags }) => {
|
|
5965
|
+
const emailId = args.emailId ?? "";
|
|
4978
5966
|
const include = flagString(flags.include);
|
|
5967
|
+
const emailVersionId = flagString(flags.emailVersionId);
|
|
5968
|
+
const runId = flagString(flags.runId);
|
|
5969
|
+
if (emailVersionId !== void 0 && runId !== void 0) {
|
|
5970
|
+
throw new CliUsageError(
|
|
5971
|
+
"Pass --email-version-id or --run-id, not both: each selects the version to read."
|
|
5972
|
+
);
|
|
5973
|
+
}
|
|
4979
5974
|
return {
|
|
4980
|
-
data: await ctx.client().emails.get(
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
5975
|
+
data: await ctx.client().emails.get(emailId, {
|
|
5976
|
+
...include === void 0 ? {} : { include },
|
|
5977
|
+
...emailVersionId === void 0 ? {} : { emailVersionId },
|
|
5978
|
+
...runId === void 0 ? {} : { runId }
|
|
5979
|
+
})
|
|
4984
5980
|
};
|
|
4985
5981
|
}
|
|
4986
5982
|
});
|
|
@@ -4989,8 +5985,7 @@ var emailsGetCommand = defineCommand({
|
|
|
4989
5985
|
var emailsGetAuditCommand = defineCommand({
|
|
4990
5986
|
path: ["emails", "get-audit"],
|
|
4991
5987
|
summary: "Read a saved email audit: one page of its findings (free; never reruns the audit)",
|
|
4992
|
-
sdkMethod:
|
|
4993
|
-
isRawTransport: true,
|
|
5988
|
+
sdkMethod: "emails.getAudit",
|
|
4994
5989
|
route: { method: "GET", path: "/v1/emails/audits/{auditId}" },
|
|
4995
5990
|
commandClass: "read",
|
|
4996
5991
|
args: [
|
|
@@ -5000,7 +5995,12 @@ var emailsGetAuditCommand = defineCommand({
|
|
|
5000
5995
|
isRequired: true
|
|
5001
5996
|
}
|
|
5002
5997
|
],
|
|
5003
|
-
|
|
5998
|
+
// Its own page size: a saved audit pages 1-50 findings (default 10), not
|
|
5999
|
+
// the shared list's 1-100.
|
|
6000
|
+
flags: [
|
|
6001
|
+
{ flag: "--limit <n>", summary: "Findings per page, 1-50 (default 10)" },
|
|
6002
|
+
CURSOR_FLAG
|
|
6003
|
+
],
|
|
5004
6004
|
examples: [
|
|
5005
6005
|
"brew-cli emails get-audit 6f1e2d3c-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
|
|
5006
6006
|
"brew-cli emails get-audit 6f1e2d3c-4b5a-4c7d-8e9f-0a1b2c3d4e5f --limit 20"
|
|
@@ -5009,13 +6009,10 @@ var emailsGetAuditCommand = defineCommand({
|
|
|
5009
6009
|
// says whether one exists.
|
|
5010
6010
|
run: async ({ ctx, args, flags }) => {
|
|
5011
6011
|
const limit = flagInt(flags.limit, "--limit");
|
|
5012
|
-
const
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
cursor: flagString(flags.cursor),
|
|
5017
|
-
limit: limit === void 0 ? void 0 : String(limit)
|
|
5018
|
-
}
|
|
6012
|
+
const cursor = flagString(flags.cursor);
|
|
6013
|
+
const body = await ctx.client().emails.getAudit(args.auditId ?? "", {
|
|
6014
|
+
...cursor === void 0 ? {} : { cursor },
|
|
6015
|
+
...limit === void 0 ? {} : { limit }
|
|
5019
6016
|
});
|
|
5020
6017
|
return { data: body };
|
|
5021
6018
|
}
|
|
@@ -5025,8 +6022,7 @@ var emailsGetAuditCommand = defineCommand({
|
|
|
5025
6022
|
var emailsGetClientPreviewCommand = defineCommand({
|
|
5026
6023
|
path: ["emails", "get-client-preview"],
|
|
5027
6024
|
summary: "Poll a client-preview rendering job: per-client screenshot links once it settles (free; never re-renders)",
|
|
5028
|
-
sdkMethod:
|
|
5029
|
-
isRawTransport: true,
|
|
6025
|
+
sdkMethod: "emails.getClientPreview",
|
|
5030
6026
|
route: { method: "GET", path: "/v1/emails/client-previews/{previewId}" },
|
|
5031
6027
|
commandClass: "read",
|
|
5032
6028
|
args: [
|
|
@@ -5045,10 +6041,7 @@ var emailsGetClientPreviewCommand = defineCommand({
|
|
|
5045
6041
|
// `failed`; each client then carries its `imageUrl`, or a `reason` and
|
|
5046
6042
|
// whether it is `retryable`.
|
|
5047
6043
|
run: async ({ ctx, args }) => {
|
|
5048
|
-
const body = await
|
|
5049
|
-
method: "GET",
|
|
5050
|
-
path: `/v1/emails/client-previews/${encodeURIComponent(args.previewId ?? "")}`
|
|
5051
|
-
});
|
|
6044
|
+
const body = await ctx.client().emails.getClientPreview(args.previewId ?? "");
|
|
5052
6045
|
return { data: body };
|
|
5053
6046
|
}
|
|
5054
6047
|
});
|
|
@@ -5262,14 +6255,13 @@ var emailsGroupsUpdateCommand = defineCommand({
|
|
|
5262
6255
|
};
|
|
5263
6256
|
}
|
|
5264
6257
|
});
|
|
5265
|
-
|
|
5266
|
-
// src/commands/emails/import.ts
|
|
5267
6258
|
var emailsImportCommand = defineCommand({
|
|
5268
6259
|
path: ["emails", "import"],
|
|
5269
6260
|
summary: "Import existing HTML, MJML, or JSX as a new editable design",
|
|
5270
6261
|
sdkMethod: "emails.import",
|
|
5271
6262
|
route: { method: "POST", path: "/v1/emails/import" },
|
|
5272
6263
|
commandClass: "write",
|
|
6264
|
+
defaultTimeoutMs: IMPORT_EMAIL_DEFAULT_TIMEOUT_MS,
|
|
5273
6265
|
flags: [
|
|
5274
6266
|
{
|
|
5275
6267
|
flag: "--file <path>",
|
|
@@ -5315,14 +6307,13 @@ var emailsImportCommand = defineCommand({
|
|
|
5315
6307
|
return { data: result };
|
|
5316
6308
|
}
|
|
5317
6309
|
});
|
|
5318
|
-
|
|
5319
|
-
// src/commands/emails/import-figma.ts
|
|
5320
6310
|
var emailsImportFigmaCommand = defineCommand({
|
|
5321
6311
|
path: ["emails", "import-figma"],
|
|
5322
6312
|
summary: "Convert one Figma frame into an editable design (deterministic, free)",
|
|
5323
6313
|
sdkMethod: "emails.importFigma",
|
|
5324
6314
|
route: { method: "POST", path: "/v1/emails/figma" },
|
|
5325
6315
|
commandClass: "write",
|
|
6316
|
+
defaultTimeoutMs: IMPORT_FIGMA_DEFAULT_TIMEOUT_MS,
|
|
5326
6317
|
flags: [
|
|
5327
6318
|
{
|
|
5328
6319
|
flag: "--url <figmaUrl>",
|
|
@@ -5340,6 +6331,14 @@ var emailsImportFigmaCommand = defineCommand({
|
|
|
5340
6331
|
flag: "--subject-line <text>",
|
|
5341
6332
|
summary: "The design's default inbox subject line"
|
|
5342
6333
|
},
|
|
6334
|
+
{
|
|
6335
|
+
flag: "--group-id <groupId>",
|
|
6336
|
+
summary: "File the design under an existing group (grp_\u2026), or ungrouped"
|
|
6337
|
+
},
|
|
6338
|
+
{
|
|
6339
|
+
flag: "--group-name <name>",
|
|
6340
|
+
summary: "File the design under a group found (or created) by this name; not with --group-id"
|
|
6341
|
+
},
|
|
5343
6342
|
IDEMPOTENCY_FLAG
|
|
5344
6343
|
],
|
|
5345
6344
|
examples: [
|
|
@@ -5356,13 +6355,20 @@ var emailsImportFigmaCommand = defineCommand({
|
|
|
5356
6355
|
const title = flagString(flags.title);
|
|
5357
6356
|
const format = flagString(flags.format);
|
|
5358
6357
|
const subjectLine = flagString(flags.subjectLine);
|
|
6358
|
+
const groupId = flagString(flags.groupId);
|
|
6359
|
+
const groupName = flagString(flags.groupName);
|
|
6360
|
+
if (groupId !== void 0 && groupName !== void 0) {
|
|
6361
|
+
throw new CliUsageError("Pass --group-id or --group-name, not both.");
|
|
6362
|
+
}
|
|
5359
6363
|
return {
|
|
5360
6364
|
data: await ctx.client().emails.importFigma(
|
|
5361
6365
|
asSdkInput({
|
|
5362
6366
|
figmaUrl,
|
|
5363
6367
|
...title === void 0 ? {} : { title },
|
|
5364
6368
|
...format === void 0 ? {} : { format },
|
|
5365
|
-
...subjectLine === void 0 ? {} : { subjectLine }
|
|
6369
|
+
...subjectLine === void 0 ? {} : { subjectLine },
|
|
6370
|
+
...groupId === void 0 ? {} : { groupId },
|
|
6371
|
+
...groupName === void 0 ? {} : { groupName }
|
|
5366
6372
|
}),
|
|
5367
6373
|
requestOptions(flags)
|
|
5368
6374
|
)
|
|
@@ -5408,6 +6414,10 @@ var emailsListCommand = defineCommand({
|
|
|
5408
6414
|
route: { method: "GET", path: "/v1/emails" },
|
|
5409
6415
|
commandClass: "read",
|
|
5410
6416
|
flags: [
|
|
6417
|
+
{
|
|
6418
|
+
flag: "--search <text>",
|
|
6419
|
+
summary: "Only designs whose title contains this text, or whose title, subject, preview or text matches its words"
|
|
6420
|
+
},
|
|
5411
6421
|
{
|
|
5412
6422
|
flag: "--status <status>",
|
|
5413
6423
|
summary: "Filter by status: generating | ready | failed"
|
|
@@ -5467,6 +6477,7 @@ var emailsListCommand = defineCommand({
|
|
|
5467
6477
|
);
|
|
5468
6478
|
const base = await readJsonFlag(ctx, flags.input, "--input");
|
|
5469
6479
|
const input = mergeInput(base, {
|
|
6480
|
+
search: flagString(flags.search),
|
|
5470
6481
|
status: flagString(flags.status),
|
|
5471
6482
|
groupId: flagString(flags.groupId),
|
|
5472
6483
|
sortBy: flagString(flags.sortBy) ?? flagString(flags.sort) ?? legacy.sortBy,
|
|
@@ -5541,14 +6552,13 @@ function renderEmails(rows) {
|
|
|
5541
6552
|
{ key: "updatedAt", header: "UPDATED" }
|
|
5542
6553
|
]);
|
|
5543
6554
|
}
|
|
5544
|
-
|
|
5545
|
-
// src/commands/emails/preview-clients.ts
|
|
5546
6555
|
var emailsPreviewClientsCommand = defineCommand({
|
|
5547
6556
|
path: ["emails", "preview-clients"],
|
|
5548
6557
|
summary: "Start a rendering job across real email clients (10 credits); poll it with `emails get-client-preview`",
|
|
5549
6558
|
sdkMethod: "emails.previewClients",
|
|
5550
6559
|
route: { method: "POST", path: "/v1/emails/{emailId}/client-previews" },
|
|
5551
6560
|
commandClass: "write",
|
|
6561
|
+
defaultTimeoutMs: PREVIEW_EMAIL_CLIENTS_DEFAULT_TIMEOUT_MS,
|
|
5552
6562
|
isCredited: true,
|
|
5553
6563
|
args: [
|
|
5554
6564
|
{ name: "emailId", summary: "Design id to preview", isRequired: true }
|
|
@@ -5828,8 +6838,48 @@ var fieldsListCommand = defineCommand({
|
|
|
5828
6838
|
sdkMethod: "fields.list",
|
|
5829
6839
|
route: { method: "GET", path: "/v1/fields" },
|
|
5830
6840
|
commandClass: "read",
|
|
5831
|
-
|
|
5832
|
-
|
|
6841
|
+
flags: [
|
|
6842
|
+
{
|
|
6843
|
+
flag: "--include <tokens>",
|
|
6844
|
+
summary: "Expansions: coverage (per-field fill stats: percent of contacts with a value, and top values)"
|
|
6845
|
+
},
|
|
6846
|
+
{
|
|
6847
|
+
flag: "--audience-id <audienceId>",
|
|
6848
|
+
summary: "Scope --include coverage to one saved audience (ignored without it)"
|
|
6849
|
+
},
|
|
6850
|
+
LIMIT_FLAG,
|
|
6851
|
+
CURSOR_FLAG,
|
|
6852
|
+
ALL_FLAG
|
|
6853
|
+
],
|
|
6854
|
+
examples: [
|
|
6855
|
+
"brew-cli fields list",
|
|
6856
|
+
"brew-cli fields list --include coverage",
|
|
6857
|
+
"brew-cli fields list --include coverage --audience-id aud_2SmZOWV3ZQ7W5x6g3m4p --all --json"
|
|
6858
|
+
],
|
|
6859
|
+
run: async ({ ctx, flags }) => {
|
|
6860
|
+
const input = {
|
|
6861
|
+
include: flagString(flags.include),
|
|
6862
|
+
audienceId: flagString(flags.audienceId),
|
|
6863
|
+
limit: flagInt(flags.limit, "--limit"),
|
|
6864
|
+
cursor: flagString(flags.cursor)
|
|
6865
|
+
};
|
|
6866
|
+
const fields = ctx.client().fields;
|
|
6867
|
+
if (flags.all === true) {
|
|
6868
|
+
const rows = await collectAll(
|
|
6869
|
+
ctx,
|
|
6870
|
+
(cursor) => fields.list(
|
|
6871
|
+
asSdkInput({
|
|
6872
|
+
...input,
|
|
6873
|
+
...cursor === void 0 ? {} : { cursor }
|
|
6874
|
+
})
|
|
6875
|
+
)
|
|
6876
|
+
);
|
|
6877
|
+
return {
|
|
6878
|
+
data: { data: rows, pagination: { cursor: null, hasMore: false } }
|
|
6879
|
+
};
|
|
6880
|
+
}
|
|
6881
|
+
return { data: await fields.list(asSdkInput(input)) };
|
|
6882
|
+
}
|
|
5833
6883
|
});
|
|
5834
6884
|
|
|
5835
6885
|
// src/commands/flows/get.ts
|
|
@@ -6024,9 +7074,31 @@ var integrationsListCommand = defineCommand({
|
|
|
6024
7074
|
sdkMethod: "integrations.list",
|
|
6025
7075
|
route: { method: "GET", path: "/v1/integrations" },
|
|
6026
7076
|
commandClass: "read",
|
|
7077
|
+
flags: [LIMIT_FLAG, CURSOR_FLAG, ALL_FLAG],
|
|
6027
7078
|
examples: ["brew-cli integrations list", "brew-cli integrations list --json"],
|
|
6028
|
-
run: async ({ ctx }) => {
|
|
6029
|
-
const
|
|
7079
|
+
run: async ({ ctx, flags }) => {
|
|
7080
|
+
const limit = flagInt(flags.limit, "--limit");
|
|
7081
|
+
const cursor = flagString(flags.cursor);
|
|
7082
|
+
if (limit === void 0 && cursor === void 0 && flags.all !== true) {
|
|
7083
|
+
const result2 = await ctx.client().integrations.list();
|
|
7084
|
+
return { data: result2, human: renderIntegrations(result2.data) };
|
|
7085
|
+
}
|
|
7086
|
+
const page = (after) => rawRequest(ctx, {
|
|
7087
|
+
method: "GET",
|
|
7088
|
+
path: "/v1/integrations",
|
|
7089
|
+
query: {
|
|
7090
|
+
limit: limit === void 0 ? void 0 : String(limit),
|
|
7091
|
+
cursor: after
|
|
7092
|
+
}
|
|
7093
|
+
});
|
|
7094
|
+
if (flags.all === true) {
|
|
7095
|
+
const rows = await collectAll(ctx, (next) => page(next ?? cursor));
|
|
7096
|
+
return {
|
|
7097
|
+
data: { data: rows, pagination: { cursor: null, hasMore: false } },
|
|
7098
|
+
human: renderIntegrations(rows)
|
|
7099
|
+
};
|
|
7100
|
+
}
|
|
7101
|
+
const result = await page(cursor);
|
|
6030
7102
|
return { data: result, human: renderIntegrations(result.data) };
|
|
6031
7103
|
}
|
|
6032
7104
|
});
|
|
@@ -6066,7 +7138,7 @@ var loginCommand = defineCommand({
|
|
|
6066
7138
|
globals: { ...ctx.globals, apiKey: candidate },
|
|
6067
7139
|
env: ctx.io.env
|
|
6068
7140
|
});
|
|
6069
|
-
await buildSdkClient(auth).usage.get();
|
|
7141
|
+
await buildSdkClient(auth, sdkTransportFor(ctx)).usage.get();
|
|
6070
7142
|
const path = updateConfig(ctx.io.env, { apiKey: candidate });
|
|
6071
7143
|
progress(ctx, `Logged in. Key stored in ${path} (0600).`);
|
|
6072
7144
|
return {
|
|
@@ -6193,8 +7265,7 @@ var sendsResumeCommand = defineCommand({
|
|
|
6193
7265
|
var templatesGetCommand = defineCommand({
|
|
6194
7266
|
path: ["templates", "get"],
|
|
6195
7267
|
summary: "Fetch one public template: its links and the referenceEmailId to remix; --include html adds its HTML",
|
|
6196
|
-
sdkMethod:
|
|
6197
|
-
isRawTransport: true,
|
|
7268
|
+
sdkMethod: "templates.get",
|
|
6198
7269
|
route: { method: "GET", path: "/v1/templates/{templateId}" },
|
|
6199
7270
|
commandClass: "read",
|
|
6200
7271
|
args: [
|
|
@@ -6215,11 +7286,11 @@ var templatesGetCommand = defineCommand({
|
|
|
6215
7286
|
"brew-cli templates get seed-vercel-newsletter --include html --json"
|
|
6216
7287
|
],
|
|
6217
7288
|
run: async ({ ctx, args, flags }) => {
|
|
6218
|
-
const
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
7289
|
+
const include = flagString(flags.include);
|
|
7290
|
+
const body = await ctx.client().templates.get(
|
|
7291
|
+
args.templateId ?? "",
|
|
7292
|
+
include === void 0 ? void 0 : { include: asSdkInput(include) }
|
|
7293
|
+
);
|
|
6223
7294
|
return { data: body };
|
|
6224
7295
|
}
|
|
6225
7296
|
});
|
|
@@ -6505,6 +7576,7 @@ var whoamiCommand = defineCommand({
|
|
|
6505
7576
|
if (error instanceof BrewApiError && (error.status === 401 || error.status === 403)) {
|
|
6506
7577
|
throw error;
|
|
6507
7578
|
}
|
|
7579
|
+
ctx.signal.throwIfAborted();
|
|
6508
7580
|
progress(
|
|
6509
7581
|
ctx,
|
|
6510
7582
|
"Warning: could not reach /v1/usage \u2014 showing local credential state only."
|
|
@@ -6535,6 +7607,7 @@ var ALL_COMMANDS = [
|
|
|
6535
7607
|
contactsSearchCommand,
|
|
6536
7608
|
contactsGetCommand,
|
|
6537
7609
|
contactsCountCommand,
|
|
7610
|
+
contactsCountByCommand,
|
|
6538
7611
|
contactsUpsertCommand,
|
|
6539
7612
|
contactsUpsertManyCommand,
|
|
6540
7613
|
contactsUpdateCommand,
|
|
@@ -6618,6 +7691,7 @@ var ALL_COMMANDS = [
|
|
|
6618
7691
|
analyticsCampaignsCommand,
|
|
6619
7692
|
analyticsAutomationsCommand,
|
|
6620
7693
|
analyticsEventsCommand,
|
|
7694
|
+
analyticsEventCountsCommand,
|
|
6621
7695
|
analyticsSendsListCommand,
|
|
6622
7696
|
analyticsSendsGetCommand,
|
|
6623
7697
|
analyticsTriggerInstancesListCommand,
|
|
@@ -6637,6 +7711,11 @@ var ALL_COMMANDS = [
|
|
|
6637
7711
|
domainsHealthCommand,
|
|
6638
7712
|
domainsUpdateCommand,
|
|
6639
7713
|
domainsDeleteCommand,
|
|
7714
|
+
domainsUnsubscribesListCommand,
|
|
7715
|
+
domainsUnsubscribesAddCommand,
|
|
7716
|
+
domainsUnsubscribesRemoveCommand,
|
|
7717
|
+
domainsUnsubscribesImportCommand,
|
|
7718
|
+
domainsUnsubscribesExportCommand,
|
|
6640
7719
|
contentGenerateImageCommand,
|
|
6641
7720
|
contentGifCommand,
|
|
6642
7721
|
contentTransformCommand,
|
|
@@ -6695,6 +7774,7 @@ Environment:
|
|
|
6695
7774
|
Exit codes:
|
|
6696
7775
|
0 success \xB7 1 API/runtime error \xB7 2 usage error \xB7 3 auth error
|
|
6697
7776
|
4 confirmation required (re-run with --yes)
|
|
7777
|
+
130 interrupted (SIGINT) \xB7 143 terminated (SIGTERM)
|
|
6698
7778
|
|
|
6699
7779
|
Agents: run \`${CLI_NAME} docs --agent\` for the machine-readable manifest.
|
|
6700
7780
|
`;
|
|
@@ -6712,8 +7792,8 @@ async function run(argv, io, commands = ALL_COMMANDS) {
|
|
|
6712
7792
|
(text) => {
|
|
6713
7793
|
commanderStderr.push(text);
|
|
6714
7794
|
},
|
|
6715
|
-
(
|
|
6716
|
-
commandExitCode =
|
|
7795
|
+
(code2) => {
|
|
7796
|
+
commandExitCode = code2;
|
|
6717
7797
|
}
|
|
6718
7798
|
);
|
|
6719
7799
|
const flushCommanderStderr = () => {
|
|
@@ -6733,9 +7813,51 @@ async function run(argv, io, commands = ALL_COMMANDS) {
|
|
|
6733
7813
|
}
|
|
6734
7814
|
flushCommanderStderr();
|
|
6735
7815
|
const ctx = activeContext ?? makeFallbackContext(io, argv);
|
|
6736
|
-
|
|
6737
|
-
|
|
7816
|
+
const reported = reportedError(ctx, error);
|
|
7817
|
+
printError(ctx, reported, errorContextFor(ctx, commands));
|
|
7818
|
+
return toExitCode(reported);
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
function reportedError(ctx, error) {
|
|
7822
|
+
if (!ctx.signal.aborted) {
|
|
7823
|
+
return error;
|
|
7824
|
+
}
|
|
7825
|
+
if (error instanceof BrewApiError || error instanceof CliApiError) {
|
|
7826
|
+
return error;
|
|
6738
7827
|
}
|
|
7828
|
+
return ctx.signal.reason;
|
|
7829
|
+
}
|
|
7830
|
+
function errorContextFor(ctx, commands) {
|
|
7831
|
+
const request = ctx.transport.lastRequest();
|
|
7832
|
+
const drain = ctx.transport.drain();
|
|
7833
|
+
const bound = request === void 0 ? void 0 : commandFor(request, commands);
|
|
7834
|
+
const isRead = bound === void 0 ? ctx.traits?.isRead : bound.commandClass === "read";
|
|
7835
|
+
return {
|
|
7836
|
+
...request === void 0 ? {} : { request },
|
|
7837
|
+
...drain === void 0 ? {} : { drain },
|
|
7838
|
+
...isRead === void 0 ? {} : { isRead },
|
|
7839
|
+
replays: bound === void 0 ? ctx.traits?.replays === true : (bound.flags ?? []).includes(IDEMPOTENCY_FLAG),
|
|
7840
|
+
readsStdin: readsStdin(ctx.rawArgv),
|
|
7841
|
+
retryCommand: (key) => buildRetryCommand(ctx.rawArgv, key)
|
|
7842
|
+
};
|
|
7843
|
+
}
|
|
7844
|
+
function commandFor(request, commands) {
|
|
7845
|
+
const apiPath = request.path.split("?")[0] ?? "";
|
|
7846
|
+
return commands.find(
|
|
7847
|
+
(spec) => spec.route !== void 0 && spec.route.method === request.method && matchesTemplate(spec.route.path, apiPath)
|
|
7848
|
+
);
|
|
7849
|
+
}
|
|
7850
|
+
function readsStdin(rawArgv) {
|
|
7851
|
+
return rawArgv.some(
|
|
7852
|
+
(token, index) => token === "-" && ["--input", "--file", "--data"].includes(rawArgv[index - 1] ?? "")
|
|
7853
|
+
);
|
|
7854
|
+
}
|
|
7855
|
+
function traitsOf(spec) {
|
|
7856
|
+
return {
|
|
7857
|
+
isRead: spec.commandClass === "read",
|
|
7858
|
+
replays: (spec.flags ?? []).includes(IDEMPOTENCY_FLAG),
|
|
7859
|
+
defaultTimeoutMs: spec.defaultTimeoutMs
|
|
7860
|
+
};
|
|
6739
7861
|
}
|
|
6740
7862
|
function handleCommanderError(error, ctx, flush) {
|
|
6741
7863
|
const isHelpCode = error.code === "commander.helpDisplayed" || error.code === "commander.help";
|
|
@@ -6824,16 +7946,21 @@ function registerCommand(program, spec, io, rawArgv, onContext, onExitCode) {
|
|
|
6824
7946
|
},
|
|
6825
7947
|
{}
|
|
6826
7948
|
);
|
|
6827
|
-
const ctx = makeContext({ io, flags, rawArgv });
|
|
7949
|
+
const ctx = makeContext({ io, flags, rawArgv, traits: traitsOf(spec) });
|
|
6828
7950
|
onContext?.(ctx);
|
|
6829
7951
|
const invocation = { ctx, args: positional, flags };
|
|
6830
7952
|
await enforceConfirmation(spec, invocation);
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
if (outcome
|
|
6835
|
-
|
|
7953
|
+
ctx.startDeadline();
|
|
7954
|
+
try {
|
|
7955
|
+
const outcome = await spec.run(invocation);
|
|
7956
|
+
if (outcome !== void 0) {
|
|
7957
|
+
printData(ctx, outcome.data, outcome.human);
|
|
7958
|
+
if (outcome.exitCode !== void 0 && outcome.exitCode !== 0) {
|
|
7959
|
+
onExitCode?.(outcome.exitCode);
|
|
7960
|
+
}
|
|
6836
7961
|
}
|
|
7962
|
+
} finally {
|
|
7963
|
+
ctx.stopDeadline();
|
|
6837
7964
|
}
|
|
6838
7965
|
});
|
|
6839
7966
|
}
|
|
@@ -6872,20 +7999,77 @@ function buildCommandHelpFooter(spec) {
|
|
|
6872
7999
|
return lines.join("\n");
|
|
6873
8000
|
}
|
|
6874
8001
|
|
|
8002
|
+
// src/lib/interrupt.ts
|
|
8003
|
+
function createInterrupter(input) {
|
|
8004
|
+
const controller = new AbortController();
|
|
8005
|
+
const graceMs = input.graceMs ?? 3e3;
|
|
8006
|
+
let first;
|
|
8007
|
+
let grace;
|
|
8008
|
+
const forceExit = (signal, why) => {
|
|
8009
|
+
input.stderr.write(`
|
|
8010
|
+
brew-cli: ${why}; exiting without waiting.
|
|
8011
|
+
`);
|
|
8012
|
+
input.exit(exitCodeForSignal(signal));
|
|
8013
|
+
};
|
|
8014
|
+
const trigger = (signal) => {
|
|
8015
|
+
if (first !== void 0) {
|
|
8016
|
+
forceExit(signal, `${signal} received twice`);
|
|
8017
|
+
return;
|
|
8018
|
+
}
|
|
8019
|
+
first = signal;
|
|
8020
|
+
controller.abort(new CliInterruptError(signal));
|
|
8021
|
+
grace = setTimeout(() => {
|
|
8022
|
+
forceExit(signal, `still stopping ${graceMs / 1e3}s after ${signal}`);
|
|
8023
|
+
}, graceMs);
|
|
8024
|
+
grace.unref?.();
|
|
8025
|
+
};
|
|
8026
|
+
input.source.on("SIGINT", () => {
|
|
8027
|
+
trigger("SIGINT");
|
|
8028
|
+
});
|
|
8029
|
+
input.source.on("SIGTERM", () => {
|
|
8030
|
+
trigger("SIGTERM");
|
|
8031
|
+
});
|
|
8032
|
+
return {
|
|
8033
|
+
signal: controller.signal,
|
|
8034
|
+
trigger,
|
|
8035
|
+
received: () => first,
|
|
8036
|
+
settle: () => {
|
|
8037
|
+
if (grace !== void 0) {
|
|
8038
|
+
clearTimeout(grace);
|
|
8039
|
+
}
|
|
8040
|
+
}
|
|
8041
|
+
};
|
|
8042
|
+
}
|
|
8043
|
+
|
|
6875
8044
|
// src/bin.ts
|
|
6876
|
-
function realIo() {
|
|
8045
|
+
function realIo(interrupter2) {
|
|
8046
|
+
const { signal } = interrupter2;
|
|
6877
8047
|
return {
|
|
6878
8048
|
stdout: process.stdout,
|
|
6879
8049
|
stderr: process.stderr,
|
|
6880
8050
|
isTtyOut: process.stdout.isTTY === true,
|
|
6881
8051
|
isTtyIn: process.stdin.isTTY === true,
|
|
6882
8052
|
env: process.env,
|
|
8053
|
+
signal,
|
|
6883
8054
|
readStdin: async () => {
|
|
8055
|
+
const stop = () => {
|
|
8056
|
+
process.stdin.destroy();
|
|
8057
|
+
};
|
|
8058
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
6884
8059
|
process.stdin.setEncoding("utf8");
|
|
6885
8060
|
let data = "";
|
|
6886
|
-
|
|
6887
|
-
|
|
8061
|
+
try {
|
|
8062
|
+
for await (const chunk of process.stdin) {
|
|
8063
|
+
data += chunk;
|
|
8064
|
+
}
|
|
8065
|
+
} catch (error) {
|
|
8066
|
+
if (!signal.aborted) {
|
|
8067
|
+
throw error;
|
|
8068
|
+
}
|
|
8069
|
+
} finally {
|
|
8070
|
+
signal.removeEventListener("abort", stop);
|
|
6888
8071
|
}
|
|
8072
|
+
signal.throwIfAborted();
|
|
6889
8073
|
return data;
|
|
6890
8074
|
},
|
|
6891
8075
|
readLine: async (prompt) => {
|
|
@@ -6893,14 +8077,34 @@ function realIo() {
|
|
|
6893
8077
|
input: process.stdin,
|
|
6894
8078
|
output: process.stderr
|
|
6895
8079
|
});
|
|
8080
|
+
rl.on("SIGINT", () => {
|
|
8081
|
+
interrupter2.trigger("SIGINT");
|
|
8082
|
+
});
|
|
6896
8083
|
try {
|
|
6897
|
-
return await rl.question(prompt);
|
|
8084
|
+
return await rl.question(prompt, { signal });
|
|
6898
8085
|
} finally {
|
|
6899
8086
|
rl.close();
|
|
6900
8087
|
}
|
|
6901
8088
|
}
|
|
6902
8089
|
};
|
|
6903
8090
|
}
|
|
6904
|
-
|
|
8091
|
+
var interrupter = createInterrupter({
|
|
8092
|
+
source: process,
|
|
8093
|
+
stderr: process.stderr,
|
|
8094
|
+
exit: (code2) => process.exit(code2)
|
|
8095
|
+
});
|
|
8096
|
+
var code = await run(process.argv.slice(2), realIo(interrupter));
|
|
8097
|
+
interrupter.settle();
|
|
8098
|
+
process.exitCode = code;
|
|
8099
|
+
var received = interrupter.received();
|
|
8100
|
+
if (received !== void 0 && code === exitCodeForSignal(received) && process.platform !== "win32") {
|
|
8101
|
+
await new Promise((resolve) => {
|
|
8102
|
+
process.stderr.write("", () => {
|
|
8103
|
+
resolve();
|
|
8104
|
+
});
|
|
8105
|
+
});
|
|
8106
|
+
process.removeAllListeners(received);
|
|
8107
|
+
process.kill(process.pid, received);
|
|
8108
|
+
}
|
|
6905
8109
|
//# sourceMappingURL=bin.js.map
|
|
6906
8110
|
//# sourceMappingURL=bin.js.map
|