@brew.new/cli 0.9.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 +989 -341
- 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) {
|
|
@@ -661,6 +975,61 @@ var INPUT_FLAG = {
|
|
|
661
975
|
flag: "--input <json>",
|
|
662
976
|
summary: "Full JSON request body, or - to read stdin (flags override it)"
|
|
663
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
|
+
}
|
|
664
1033
|
|
|
665
1034
|
// src/lib/paginate.ts
|
|
666
1035
|
async function collectAll(ctx, fetchPage) {
|
|
@@ -668,7 +1037,20 @@ async function collectAll(ctx, fetchPage) {
|
|
|
668
1037
|
let cursor;
|
|
669
1038
|
let pageCount = 0;
|
|
670
1039
|
for (; ; ) {
|
|
671
|
-
|
|
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
|
+
}
|
|
672
1054
|
rows.push(...page.data);
|
|
673
1055
|
pageCount += 1;
|
|
674
1056
|
const next = page.pagination?.cursor ?? null;
|
|
@@ -861,116 +1243,15 @@ var analyticsCampaignsCommand = defineCommand({
|
|
|
861
1243
|
}
|
|
862
1244
|
});
|
|
863
1245
|
|
|
864
|
-
// src/lib/raw-request.ts
|
|
865
|
-
async function rawRequest(ctx, request) {
|
|
866
|
-
const auth = resolveAuth({
|
|
867
|
-
globals: ctx.globals,
|
|
868
|
-
env: ctx.io.env,
|
|
869
|
-
...request.allowAnonymous === true ? { allowAnonymous: true } : {}
|
|
870
|
-
});
|
|
871
|
-
const hasBody = request.body !== void 0;
|
|
872
|
-
const idempotencyKey = resolveRawIdempotencyKey(
|
|
873
|
-
request.method,
|
|
874
|
-
request.idempotencyKey
|
|
875
|
-
);
|
|
876
|
-
const headers = buildRawHeaders({
|
|
877
|
-
auth,
|
|
878
|
-
path: request.path,
|
|
879
|
-
hasJsonBody: hasBody,
|
|
880
|
-
idempotencyKey
|
|
881
|
-
});
|
|
882
|
-
const url = new URL(`${auth.apiUrl}${request.path}`);
|
|
883
|
-
for (const [key, value] of Object.entries(request.query ?? {})) {
|
|
884
|
-
if (value !== void 0) {
|
|
885
|
-
url.searchParams.set(key, value);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
const response = await fetch(url, {
|
|
889
|
-
method: request.method,
|
|
890
|
-
headers,
|
|
891
|
-
...hasBody ? { body: JSON.stringify(request.body) } : {},
|
|
892
|
-
...request.signal === void 0 ? {} : { signal: request.signal }
|
|
893
|
-
});
|
|
894
|
-
const text = await response.text();
|
|
895
|
-
const parsed = tryParseJson(text);
|
|
896
|
-
if (!response.ok) {
|
|
897
|
-
throw responseToApiError(response, parsed);
|
|
898
|
-
}
|
|
899
|
-
if (parsed === void 0) {
|
|
900
|
-
throw new CliApiError({
|
|
901
|
-
status: response.status,
|
|
902
|
-
code: "UNEXPECTED_RESPONSE",
|
|
903
|
-
type: "internal_error",
|
|
904
|
-
message: "The API returned a non-JSON success response."
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
return parsed;
|
|
908
|
-
}
|
|
909
|
-
function resolveRawIdempotencyKey(method, provided) {
|
|
910
|
-
if (provided !== void 0 && provided !== "") {
|
|
911
|
-
return provided;
|
|
912
|
-
}
|
|
913
|
-
return method === "POST" ? crypto.randomUUID() : void 0;
|
|
914
|
-
}
|
|
915
|
-
function buildRawHeaders(input) {
|
|
916
|
-
const headers = new Headers({
|
|
917
|
-
authorization: `Bearer ${input.auth.apiKey}`,
|
|
918
|
-
"user-agent": `${CLI_NAME}/${CLI_VERSION}`,
|
|
919
|
-
accept: "application/json, text/plain;q=0.9"
|
|
920
|
-
});
|
|
921
|
-
if (input.hasJsonBody) {
|
|
922
|
-
headers.set("content-type", "application/json");
|
|
923
|
-
}
|
|
924
|
-
if (input.auth.brandId !== void 0 && !isOrgLevelPath(input.path)) {
|
|
925
|
-
headers.set("x-brand-id", input.auth.brandId);
|
|
926
|
-
}
|
|
927
|
-
if (input.idempotencyKey !== void 0 && input.idempotencyKey !== "") {
|
|
928
|
-
headers.set("idempotency-key", input.idempotencyKey);
|
|
929
|
-
}
|
|
930
|
-
return headers;
|
|
931
|
-
}
|
|
932
|
-
function responseToApiError(response, parsed) {
|
|
933
|
-
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
934
|
-
const record = isRecord2(parsed) ? parsed : void 0;
|
|
935
|
-
const standard = record !== void 0 && isRecord2(record.error) ? record.error : void 0;
|
|
936
|
-
const envelope = standard ?? record;
|
|
937
|
-
const readField = (key) => {
|
|
938
|
-
const value = envelope?.[key];
|
|
939
|
-
return typeof value === "string" ? value : void 0;
|
|
940
|
-
};
|
|
941
|
-
const param = readField("param");
|
|
942
|
-
const suggestion = readField("suggestion") ?? (envelope === void 0 ? void 0 : suggestionForStatus(response.status));
|
|
943
|
-
const docs = readField("docs");
|
|
944
|
-
const details = isRecord2(envelope?.details) ? envelope.details : void 0;
|
|
945
|
-
return new CliApiError({
|
|
946
|
-
status: response.status,
|
|
947
|
-
code: readField("code") ?? "HTTP_ERROR",
|
|
948
|
-
type: readField("type") ?? errorTypeForStatus(response.status),
|
|
949
|
-
message: readField("message") ?? `Request failed with status ${response.status}`,
|
|
950
|
-
...param === void 0 ? {} : { param },
|
|
951
|
-
...suggestion === void 0 ? {} : { suggestion },
|
|
952
|
-
...docs === void 0 ? {} : { docs },
|
|
953
|
-
...requestId === void 0 ? {} : { requestId },
|
|
954
|
-
...details === void 0 ? {} : { details },
|
|
955
|
-
...parsed === void 0 ? {} : { body: parsed }
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
function isRecord2(value) {
|
|
959
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
960
|
-
}
|
|
961
|
-
function tryParseJson(text) {
|
|
962
|
-
try {
|
|
963
|
-
return JSON.parse(text);
|
|
964
|
-
} catch {
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
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
|
+
};
|
|
969
1251
|
var analyticsEventCountsCommand = defineCommand({
|
|
970
1252
|
path: ["analytics", "event-counts"],
|
|
971
1253
|
summary: "Count email events per field and/or period (clicks per link, events per day, unsubscribe reasons)",
|
|
972
|
-
sdkMethod:
|
|
973
|
-
isRawTransport: true,
|
|
1254
|
+
sdkMethod: "analytics.eventCounts",
|
|
974
1255
|
route: { method: "GET", path: "/v1/analytics/events" },
|
|
975
1256
|
commandClass: "read",
|
|
976
1257
|
flags: [
|
|
@@ -1018,21 +1299,27 @@ var analyticsEventCountsCommand = defineCommand({
|
|
|
1018
1299
|
"Pass --group-by, --bucket, or both (`analytics events` lists the rows)."
|
|
1019
1300
|
);
|
|
1020
1301
|
}
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
continue;
|
|
1302
|
+
for (const [key, reason] of Object.entries(ROW_ONLY_KEYS)) {
|
|
1303
|
+
if (input[key] !== void 0) {
|
|
1304
|
+
throw new CliUsageError(reason);
|
|
1025
1305
|
}
|
|
1026
|
-
query[key] = value === void 0 || value === null ? void 0 : Array.isArray(value) ? value.map(String).join(",") : String(value);
|
|
1027
1306
|
}
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
+
);
|
|
1033
1314
|
return { data: body, human: renderCounts(body) };
|
|
1034
1315
|
}
|
|
1035
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
|
+
}
|
|
1036
1323
|
function renderCounts(body) {
|
|
1037
1324
|
if (body.groups.length === 0) {
|
|
1038
1325
|
return `No events counted (total ${body.count}).`;
|
|
@@ -1043,7 +1330,7 @@ function renderCounts(body) {
|
|
|
1043
1330
|
count: group.count
|
|
1044
1331
|
}));
|
|
1045
1332
|
const table = renderTable(rows, [
|
|
1046
|
-
{ key: "group", header: "GROUP" },
|
|
1333
|
+
...rows.some((row) => row.group !== "") ? [{ key: "group", header: "GROUP" }] : [],
|
|
1047
1334
|
...rows.some((row) => "bucket" in row) ? [{ key: "bucket", header: "PERIOD" }] : [],
|
|
1048
1335
|
{ key: "count", header: "COUNT" }
|
|
1049
1336
|
]);
|
|
@@ -1402,7 +1689,171 @@ var analyticsTriggerInstancesListCommand = defineCommand({
|
|
|
1402
1689
|
});
|
|
1403
1690
|
return await listTriggerInstances(ctx, input, flags.all === true);
|
|
1404
1691
|
}
|
|
1405
|
-
});
|
|
1692
|
+
});
|
|
1693
|
+
|
|
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
|
+
}
|
|
1755
|
+
async function rawRequest(ctx, request) {
|
|
1756
|
+
const auth = resolveAuth({
|
|
1757
|
+
globals: ctx.globals,
|
|
1758
|
+
env: ctx.io.env,
|
|
1759
|
+
...request.allowAnonymous === true ? { allowAnonymous: true } : {}
|
|
1760
|
+
});
|
|
1761
|
+
const hasBody = request.body !== void 0;
|
|
1762
|
+
const idempotencyKey = resolveRawIdempotencyKey(
|
|
1763
|
+
request.method,
|
|
1764
|
+
request.idempotencyKey
|
|
1765
|
+
);
|
|
1766
|
+
const headers = buildRawHeaders({
|
|
1767
|
+
auth,
|
|
1768
|
+
path: request.path,
|
|
1769
|
+
hasJsonBody: hasBody,
|
|
1770
|
+
idempotencyKey
|
|
1771
|
+
});
|
|
1772
|
+
const url = new URL(`${auth.apiUrl}${request.path}`);
|
|
1773
|
+
for (const [key, value] of Object.entries(request.query ?? {})) {
|
|
1774
|
+
if (value !== void 0) {
|
|
1775
|
+
url.searchParams.set(key, value);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
const { response, text } = await rawFetch(ctx, {
|
|
1779
|
+
method: request.method,
|
|
1780
|
+
url: url.toString(),
|
|
1781
|
+
headers,
|
|
1782
|
+
...hasBody ? { body: JSON.stringify(request.body) } : {},
|
|
1783
|
+
timeoutMs: ctx.budget.attemptMs ?? DEFAULT_ATTEMPT_TIMEOUT_MS
|
|
1784
|
+
});
|
|
1785
|
+
const parsed = tryParseJson(text);
|
|
1786
|
+
if (!response.ok) {
|
|
1787
|
+
throw responseToApiError(response, parsed);
|
|
1788
|
+
}
|
|
1789
|
+
if (parsed === void 0) {
|
|
1790
|
+
throw new CliApiError({
|
|
1791
|
+
status: response.status,
|
|
1792
|
+
code: "UNEXPECTED_RESPONSE",
|
|
1793
|
+
type: "internal_error",
|
|
1794
|
+
message: "The API returned a non-JSON success response."
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
return parsed;
|
|
1798
|
+
}
|
|
1799
|
+
function resolveRawIdempotencyKey(method, provided) {
|
|
1800
|
+
if (provided !== void 0 && provided !== "") {
|
|
1801
|
+
return provided;
|
|
1802
|
+
}
|
|
1803
|
+
return method === "POST" ? crypto.randomUUID() : void 0;
|
|
1804
|
+
}
|
|
1805
|
+
function buildRawHeaders(input) {
|
|
1806
|
+
const headers = new Headers({
|
|
1807
|
+
authorization: `Bearer ${input.auth.apiKey}`,
|
|
1808
|
+
"user-agent": `${CLI_NAME}/${CLI_VERSION}`,
|
|
1809
|
+
accept: "application/json, text/plain;q=0.9"
|
|
1810
|
+
});
|
|
1811
|
+
if (input.hasJsonBody) {
|
|
1812
|
+
headers.set("content-type", "application/json");
|
|
1813
|
+
}
|
|
1814
|
+
if (input.auth.brandId !== void 0 && !isOrgLevelPath(input.path)) {
|
|
1815
|
+
headers.set("x-brand-id", input.auth.brandId);
|
|
1816
|
+
}
|
|
1817
|
+
if (input.idempotencyKey !== void 0 && input.idempotencyKey !== "") {
|
|
1818
|
+
headers.set("idempotency-key", input.idempotencyKey);
|
|
1819
|
+
}
|
|
1820
|
+
return headers;
|
|
1821
|
+
}
|
|
1822
|
+
function responseToApiError(response, parsed) {
|
|
1823
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
1824
|
+
const record = isRecord2(parsed) ? parsed : void 0;
|
|
1825
|
+
const standard = record !== void 0 && isRecord2(record.error) ? record.error : void 0;
|
|
1826
|
+
const envelope = standard ?? record;
|
|
1827
|
+
const readField = (key) => {
|
|
1828
|
+
const value = envelope?.[key];
|
|
1829
|
+
return typeof value === "string" ? value : void 0;
|
|
1830
|
+
};
|
|
1831
|
+
const param = readField("param");
|
|
1832
|
+
const suggestion = readField("suggestion") ?? (envelope === void 0 ? void 0 : suggestionForStatus(response.status));
|
|
1833
|
+
const docs = readField("docs");
|
|
1834
|
+
const details = isRecord2(envelope?.details) ? envelope.details : void 0;
|
|
1835
|
+
return new CliApiError({
|
|
1836
|
+
status: response.status,
|
|
1837
|
+
code: readField("code") ?? "HTTP_ERROR",
|
|
1838
|
+
type: readField("type") ?? errorTypeForStatus(response.status),
|
|
1839
|
+
message: readField("message") ?? `Request failed with status ${response.status}`,
|
|
1840
|
+
...param === void 0 ? {} : { param },
|
|
1841
|
+
...suggestion === void 0 ? {} : { suggestion },
|
|
1842
|
+
...docs === void 0 ? {} : { docs },
|
|
1843
|
+
...requestId === void 0 ? {} : { requestId },
|
|
1844
|
+
...details === void 0 ? {} : { details },
|
|
1845
|
+
...parsed === void 0 ? {} : { body: parsed }
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
function isRecord2(value) {
|
|
1849
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1850
|
+
}
|
|
1851
|
+
function tryParseJson(text) {
|
|
1852
|
+
try {
|
|
1853
|
+
return JSON.parse(text);
|
|
1854
|
+
} catch {
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1406
1857
|
|
|
1407
1858
|
// src/commands/api.ts
|
|
1408
1859
|
var METHODS = ["GET", "POST", "PATCH", "DELETE"];
|
|
@@ -1432,7 +1883,7 @@ var apiCommand = defineCommand({
|
|
|
1432
1883
|
},
|
|
1433
1884
|
{
|
|
1434
1885
|
flag: "--idempotency-key <key>",
|
|
1435
|
-
summary: "Idempotency-Key header for safe POST retries"
|
|
1886
|
+
summary: "Idempotency-Key header for safe POST retries (a POST gets one generated otherwise)"
|
|
1436
1887
|
}
|
|
1437
1888
|
],
|
|
1438
1889
|
examples: [
|
|
@@ -1464,7 +1915,7 @@ var apiCommand = defineCommand({
|
|
|
1464
1915
|
auth,
|
|
1465
1916
|
path,
|
|
1466
1917
|
hasJsonBody: body !== void 0,
|
|
1467
|
-
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
|
|
1468
1919
|
});
|
|
1469
1920
|
for (const header of readHeaderFlags(flags.header)) {
|
|
1470
1921
|
const separator = header.indexOf(":");
|
|
@@ -1478,12 +1929,16 @@ var apiCommand = defineCommand({
|
|
|
1478
1929
|
header.slice(separator + 1).trim()
|
|
1479
1930
|
);
|
|
1480
1931
|
}
|
|
1481
|
-
const response = await
|
|
1932
|
+
const { response, text } = await rawFetch(ctx, {
|
|
1482
1933
|
method,
|
|
1934
|
+
url: `${auth.apiUrl}${path}`,
|
|
1483
1935
|
headers,
|
|
1484
|
-
...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
|
|
1485
1941
|
});
|
|
1486
|
-
const text = await response.text();
|
|
1487
1942
|
const parsed = tryParseJson(text);
|
|
1488
1943
|
if (!response.ok) {
|
|
1489
1944
|
throw responseToApiError(response, parsed);
|
|
@@ -1813,6 +2268,10 @@ var audiencesListCommand = defineCommand({
|
|
|
1813
2268
|
flag: "--include <tokens>",
|
|
1814
2269
|
summary: "0.6 flag: includes ride the detail read now (`audiences get --include count,build`)"
|
|
1815
2270
|
},
|
|
2271
|
+
{
|
|
2272
|
+
flag: "--search <text>",
|
|
2273
|
+
summary: "Only audiences whose name contains this text"
|
|
2274
|
+
},
|
|
1816
2275
|
LIMIT_FLAG,
|
|
1817
2276
|
CURSOR_FLAG,
|
|
1818
2277
|
ALL_FLAG,
|
|
@@ -1829,6 +2288,7 @@ var audiencesListCommand = defineCommand({
|
|
|
1829
2288
|
throw includeRidesDetailRead("audiences get");
|
|
1830
2289
|
}
|
|
1831
2290
|
const input = mergeInput(base, {
|
|
2291
|
+
search: flagString(flags.search),
|
|
1832
2292
|
limit: flagInt(flags.limit, "--limit"),
|
|
1833
2293
|
cursor: flagString(flags.cursor)
|
|
1834
2294
|
});
|
|
@@ -1867,23 +2327,41 @@ function renderAudiences(rows) {
|
|
|
1867
2327
|
// src/commands/audiences/update.ts
|
|
1868
2328
|
var audiencesUpdateCommand = defineCommand({
|
|
1869
2329
|
path: ["audiences", "update"],
|
|
1870
|
-
summary: "Update an audience segment
|
|
2330
|
+
summary: "Update an audience segment: its name, its filters, or its members by email",
|
|
1871
2331
|
sdkMethod: "audiences.update",
|
|
1872
2332
|
route: { method: "PATCH", path: "/v1/audiences/{audienceId}" },
|
|
1873
2333
|
commandClass: "write",
|
|
1874
2334
|
args: [
|
|
1875
2335
|
{ name: "audienceId", summary: "Audience id to update", isRequired: true }
|
|
1876
2336
|
],
|
|
1877
|
-
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
|
+
],
|
|
1878
2349
|
examples: [
|
|
1879
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",
|
|
1880
2352
|
`brew-cli audiences update aud_3k9sQ --input '{"filters":{"filters":[{"field":"plan","operator":"equals","value":"vip"}],"logicalOperator":"and"}}'`
|
|
1881
2353
|
],
|
|
1882
2354
|
run: async ({ ctx, args, flags }) => {
|
|
1883
2355
|
const base = await readJsonFlag(ctx, flags.input, "--input");
|
|
1884
|
-
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
|
+
});
|
|
1885
2361
|
if (Object.keys(input).length === 0) {
|
|
1886
|
-
throw new CliUsageError(
|
|
2362
|
+
throw new CliUsageError(
|
|
2363
|
+
"Nothing to update \u2014 pass --name, --add-email, --remove-email or --input."
|
|
2364
|
+
);
|
|
1887
2365
|
}
|
|
1888
2366
|
const result = await ctx.client().audiences.update(
|
|
1889
2367
|
asSdkInput({
|
|
@@ -2236,7 +2714,16 @@ var automationsListCommand = defineCommand({
|
|
|
2236
2714
|
sdkMethod: "automations.list",
|
|
2237
2715
|
route: { method: "GET", path: "/v1/automations" },
|
|
2238
2716
|
commandClass: "read",
|
|
2239
|
-
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
|
+
],
|
|
2240
2727
|
examples: [
|
|
2241
2728
|
"brew-cli automations list",
|
|
2242
2729
|
"brew-cli automations list --all --json"
|
|
@@ -2244,6 +2731,7 @@ var automationsListCommand = defineCommand({
|
|
|
2244
2731
|
run: async ({ ctx, flags }) => {
|
|
2245
2732
|
const base = await readJsonFlag(ctx, flags.input, "--input");
|
|
2246
2733
|
const input = mergeInput(base, {
|
|
2734
|
+
search: flagString(flags.search),
|
|
2247
2735
|
limit: flagInt(flags.limit, "--limit"),
|
|
2248
2736
|
cursor: flagString(flags.cursor)
|
|
2249
2737
|
});
|
|
@@ -2632,17 +3120,14 @@ var FORMAT_FLAG = {
|
|
|
2632
3120
|
var FORMATS = /* @__PURE__ */ new Set(["json", "ts", "zod", "jsonschema", "skill"]);
|
|
2633
3121
|
var ENFORCEMENT_MODES = /* @__PURE__ */ new Set(["off", "prune", "strict"]);
|
|
2634
3122
|
var VALIDATE_ENFORCEMENT_MODES = /* @__PURE__ */ new Set(["prune", "strict"]);
|
|
2635
|
-
function
|
|
3123
|
+
function formatOption(value) {
|
|
2636
3124
|
const format = flagString(value);
|
|
2637
|
-
if (format
|
|
2638
|
-
return "";
|
|
2639
|
-
}
|
|
2640
|
-
if (!FORMATS.has(format)) {
|
|
3125
|
+
if (format !== void 0 && !FORMATS.has(format)) {
|
|
2641
3126
|
throw new CliUsageError(
|
|
2642
3127
|
`Unknown --format '${format}' (expected json | ts | zod | jsonschema | skill).`
|
|
2643
3128
|
);
|
|
2644
3129
|
}
|
|
2645
|
-
return
|
|
3130
|
+
return format;
|
|
2646
3131
|
}
|
|
2647
3132
|
function contentHuman(body) {
|
|
2648
3133
|
return typeof body.content === "string" ? { human: body.content } : {};
|
|
@@ -2650,8 +3135,7 @@ function contentHuman(body) {
|
|
|
2650
3135
|
var automationsTriggersContractGetCommand = defineCommand({
|
|
2651
3136
|
path: ["automations", "triggers", "contract", "get"],
|
|
2652
3137
|
summary: "Read a trigger payload contract: stored when declared, derived otherwise; --format renders ts/zod/jsonschema/skill",
|
|
2653
|
-
sdkMethod:
|
|
2654
|
-
isRawTransport: true,
|
|
3138
|
+
sdkMethod: "automations.triggers.getContract",
|
|
2655
3139
|
route: {
|
|
2656
3140
|
method: "GET",
|
|
2657
3141
|
path: "/v1/automations/triggers/{triggerEventId}/contract"
|
|
@@ -2671,18 +3155,20 @@ var automationsTriggersContractGetCommand = defineCommand({
|
|
|
2671
3155
|
"brew-cli automations triggers contract get tri_signup --format skill --json | jq -r .content"
|
|
2672
3156
|
],
|
|
2673
3157
|
run: async ({ ctx, args, flags }) => {
|
|
2674
|
-
const
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
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
|
+
);
|
|
2678
3165
|
return { data: body, ...contentHuman(body) };
|
|
2679
3166
|
}
|
|
2680
3167
|
});
|
|
2681
3168
|
var automationsTriggersContractPutCommand = defineCommand({
|
|
2682
3169
|
path: ["automations", "triggers", "contract", "put"],
|
|
2683
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",
|
|
2684
|
-
sdkMethod:
|
|
2685
|
-
isRawTransport: true,
|
|
3171
|
+
sdkMethod: "automations.triggers.putContract",
|
|
2686
3172
|
route: {
|
|
2687
3173
|
method: "PUT",
|
|
2688
3174
|
path: "/v1/automations/triggers/{triggerEventId}/contract"
|
|
@@ -2734,19 +3220,19 @@ var automationsTriggersContractPutCommand = defineCommand({
|
|
|
2734
3220
|
"Empty contract body \u2014 pass --input (fields and/or name) or --enforcement <mode>."
|
|
2735
3221
|
);
|
|
2736
3222
|
}
|
|
2737
|
-
const body = await
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
3223
|
+
const body = await ctx.client().automations.triggers.putContract(
|
|
3224
|
+
asSdkInput({
|
|
3225
|
+
...requestBody,
|
|
3226
|
+
triggerEventId: args.triggerEventId ?? ""
|
|
3227
|
+
})
|
|
3228
|
+
);
|
|
2742
3229
|
return { data: body };
|
|
2743
3230
|
}
|
|
2744
3231
|
});
|
|
2745
3232
|
var automationsTriggersContractValidateCommand = defineCommand({
|
|
2746
3233
|
path: ["automations", "triggers", "contract", "validate"],
|
|
2747
3234
|
summary: "Dry-run a payload against a trigger's contract (the fire path's validator) \u2014 never fires; invalid payloads still exit 0",
|
|
2748
|
-
sdkMethod:
|
|
2749
|
-
isRawTransport: true,
|
|
3235
|
+
sdkMethod: "automations.triggers.validatePayload",
|
|
2750
3236
|
route: {
|
|
2751
3237
|
method: "POST",
|
|
2752
3238
|
path: "/v1/automations/triggers/{triggerEventId}/contract/validate"
|
|
@@ -2781,14 +3267,13 @@ var automationsTriggersContractValidateCommand = defineCommand({
|
|
|
2781
3267
|
`Unknown --enforcement '${enforcement}' (expected prune | strict).`
|
|
2782
3268
|
);
|
|
2783
3269
|
}
|
|
2784
|
-
const body = await
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
body: {
|
|
3270
|
+
const body = await ctx.client().automations.triggers.validatePayload(
|
|
3271
|
+
asSdkInput({
|
|
3272
|
+
triggerEventId: args.triggerEventId ?? "",
|
|
2788
3273
|
payload,
|
|
2789
3274
|
...enforcement !== void 0 ? { enforcement } : {}
|
|
2790
|
-
}
|
|
2791
|
-
|
|
3275
|
+
})
|
|
3276
|
+
);
|
|
2792
3277
|
return { data: body };
|
|
2793
3278
|
}
|
|
2794
3279
|
});
|
|
@@ -3164,8 +3649,7 @@ var RETIRED_FILTERS = {
|
|
|
3164
3649
|
var brandGetImagesCommand = defineCommand({
|
|
3165
3650
|
path: ["brand", "get-images"],
|
|
3166
3651
|
summary: "Browse or semantically search the brand's assets (logos, brand images, images made with Brew)",
|
|
3167
|
-
sdkMethod:
|
|
3168
|
-
isRawTransport: true,
|
|
3652
|
+
sdkMethod: "brand.getImages",
|
|
3169
3653
|
route: { method: "GET", path: "/v1/brand/images" },
|
|
3170
3654
|
commandClass: "read",
|
|
3171
3655
|
flags: [
|
|
@@ -3215,11 +3699,11 @@ var brandGetImagesCommand = defineCommand({
|
|
|
3215
3699
|
throw new CliUsageError(message);
|
|
3216
3700
|
}
|
|
3217
3701
|
}
|
|
3218
|
-
const page = (cursor) =>
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3702
|
+
const page = (cursor) => ctx.client().brand.getImages(
|
|
3703
|
+
asSdkInput(
|
|
3704
|
+
cursor === void 0 ? input : { ...input, cursor }
|
|
3705
|
+
)
|
|
3706
|
+
);
|
|
3223
3707
|
if (flags.all === true) {
|
|
3224
3708
|
const rows = await collectAll(ctx, page);
|
|
3225
3709
|
return {
|
|
@@ -3231,13 +3715,6 @@ var brandGetImagesCommand = defineCommand({
|
|
|
3231
3715
|
return { data: result, human: renderAssets(result.data) };
|
|
3232
3716
|
}
|
|
3233
3717
|
});
|
|
3234
|
-
function toQuery(input) {
|
|
3235
|
-
const query = {};
|
|
3236
|
-
for (const [key, value] of Object.entries(input)) {
|
|
3237
|
-
query[key] = value === void 0 || value === null ? void 0 : String(value);
|
|
3238
|
-
}
|
|
3239
|
-
return query;
|
|
3240
|
-
}
|
|
3241
3718
|
function renderAssets(rows) {
|
|
3242
3719
|
if (rows.length === 0) {
|
|
3243
3720
|
return "No brand assets found.";
|
|
@@ -3538,6 +4015,90 @@ var contactsCountCommand = defineCommand({
|
|
|
3538
4015
|
}
|
|
3539
4016
|
});
|
|
3540
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
|
+
|
|
3541
4102
|
// src/commands/contacts/delete.ts
|
|
3542
4103
|
var contactsDeleteCommand = defineCommand({
|
|
3543
4104
|
path: ["contacts", "delete"],
|
|
@@ -4044,14 +4605,13 @@ var contentAddImageCommand = defineCommand({
|
|
|
4044
4605
|
return { data: result };
|
|
4045
4606
|
}
|
|
4046
4607
|
});
|
|
4047
|
-
|
|
4048
|
-
// src/commands/content/generate-image.ts
|
|
4049
4608
|
var contentGenerateImageCommand = defineCommand({
|
|
4050
4609
|
path: ["content", "generate-image"],
|
|
4051
4610
|
summary: "Generate or edit an image from a prompt",
|
|
4052
4611
|
sdkMethod: "content.generateImage",
|
|
4053
4612
|
route: { method: "POST", path: "/v1/content/generate-image" },
|
|
4054
4613
|
commandClass: "write",
|
|
4614
|
+
defaultTimeoutMs: GENERATE_IMAGE_DEFAULT_TIMEOUT_MS,
|
|
4055
4615
|
isCredited: true,
|
|
4056
4616
|
flags: [
|
|
4057
4617
|
{ flag: "--prompt <text>", summary: "What to generate" },
|
|
@@ -4092,14 +4652,13 @@ var contentGenerateImageCommand = defineCommand({
|
|
|
4092
4652
|
return { data: result };
|
|
4093
4653
|
}
|
|
4094
4654
|
});
|
|
4095
|
-
|
|
4096
|
-
// src/commands/content/gif.ts
|
|
4097
4655
|
var contentGifCommand = defineCommand({
|
|
4098
4656
|
path: ["content", "gif"],
|
|
4099
4657
|
summary: "Create an animated GIF from a prompt, image, or video",
|
|
4100
4658
|
sdkMethod: "content.gif",
|
|
4101
4659
|
route: { method: "POST", path: "/v1/content/gif" },
|
|
4102
4660
|
commandClass: "write",
|
|
4661
|
+
defaultTimeoutMs: GIF_DEFAULT_TIMEOUT_MS,
|
|
4103
4662
|
isCredited: true,
|
|
4104
4663
|
flags: [
|
|
4105
4664
|
{ flag: "--from <source>", summary: "prompt | image | video" },
|
|
@@ -4281,8 +4840,7 @@ var contentTransformCommand = defineCommand({
|
|
|
4281
4840
|
var contractsInferCommand = defineCommand({
|
|
4282
4841
|
path: ["contracts", "infer"],
|
|
4283
4842
|
summary: "Draft a payload contract from a real example payload \u2014 nothing is saved; PUT the draft on a trigger",
|
|
4284
|
-
sdkMethod:
|
|
4285
|
-
isRawTransport: true,
|
|
4843
|
+
sdkMethod: "payloadContracts.infer",
|
|
4286
4844
|
route: { method: "POST", path: "/v1/payload-contracts/infer" },
|
|
4287
4845
|
commandClass: "read",
|
|
4288
4846
|
flags: [INPUT_FLAG],
|
|
@@ -4305,14 +4863,12 @@ var contractsInferCommand = defineCommand({
|
|
|
4305
4863
|
"--input with a non-empty example object is required (the JSON your system sends)."
|
|
4306
4864
|
);
|
|
4307
4865
|
}
|
|
4308
|
-
const body = await
|
|
4309
|
-
|
|
4310
|
-
path: "/v1/payload-contracts/infer",
|
|
4311
|
-
body: {
|
|
4866
|
+
const body = await ctx.client().payloadContracts.infer(
|
|
4867
|
+
asSdkInput({
|
|
4312
4868
|
example,
|
|
4313
4869
|
...isEnvelope && input.subjectKind !== void 0 ? { subjectKind: input.subjectKind } : {}
|
|
4314
|
-
}
|
|
4315
|
-
|
|
4870
|
+
})
|
|
4871
|
+
);
|
|
4316
4872
|
return { data: body };
|
|
4317
4873
|
}
|
|
4318
4874
|
});
|
|
@@ -4351,26 +4907,6 @@ var dataRunCommand = defineCommand({
|
|
|
4351
4907
|
|
|
4352
4908
|
// src/skip-list.ts
|
|
4353
4909
|
var SDK_SKIP_LIST = [
|
|
4354
|
-
{
|
|
4355
|
-
sdkPath: "automations.triggers.getContract",
|
|
4356
|
-
reason: "covered by `automations triggers contract get` (raw route, bound pre-SDK-v9; SDK-method migration tracked separately)"
|
|
4357
|
-
},
|
|
4358
|
-
{
|
|
4359
|
-
sdkPath: "automations.triggers.putContract",
|
|
4360
|
-
reason: "covered by `automations triggers contract put` (raw route, bound pre-SDK-v9)"
|
|
4361
|
-
},
|
|
4362
|
-
{
|
|
4363
|
-
sdkPath: "automations.triggers.validatePayload",
|
|
4364
|
-
reason: "covered by `automations triggers contract validate` (raw route, bound pre-SDK-v9)"
|
|
4365
|
-
},
|
|
4366
|
-
{
|
|
4367
|
-
sdkPath: "brand.getImages",
|
|
4368
|
-
reason: "covered by `brand get-images` (raw route: SDK 10 cannot send `kind` or `sort`, brew-v2#1713; bind SDK 11.2)"
|
|
4369
|
-
},
|
|
4370
|
-
{
|
|
4371
|
-
sdkPath: "payloadContracts.infer",
|
|
4372
|
-
reason: "covered by `contracts infer` (raw route, bound pre-SDK-v9)"
|
|
4373
|
-
},
|
|
4374
4910
|
{
|
|
4375
4911
|
sdkPath: "contacts.searchAll",
|
|
4376
4912
|
reason: "auto-pager covered by `contacts search --all`"
|
|
@@ -4415,7 +4951,9 @@ function buildManifest() {
|
|
|
4415
4951
|
"1": "API or runtime error",
|
|
4416
4952
|
"2": "usage error",
|
|
4417
4953
|
"3": "authentication error",
|
|
4418
|
-
"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"
|
|
4419
4957
|
},
|
|
4420
4958
|
confirmationProtocol: {
|
|
4421
4959
|
exitCode: 4,
|
|
@@ -4523,6 +5061,7 @@ var doctorCommand = defineCommand({
|
|
|
4523
5061
|
isReachable = health.status === "ok";
|
|
4524
5062
|
apiVersion = health.version ?? null;
|
|
4525
5063
|
} catch (error) {
|
|
5064
|
+
rethrowIfStopped(ctx);
|
|
4526
5065
|
reachabilityError = error instanceof Error ? error.message : String(error);
|
|
4527
5066
|
}
|
|
4528
5067
|
let isAuthValid = null;
|
|
@@ -4533,6 +5072,7 @@ var doctorCommand = defineCommand({
|
|
|
4533
5072
|
isAuthValid = true;
|
|
4534
5073
|
plan = usage.plan?.key ?? null;
|
|
4535
5074
|
} catch (error) {
|
|
5075
|
+
rethrowIfStopped(ctx);
|
|
4536
5076
|
if ((error instanceof BrewApiError || error instanceof CliApiError) && (error.status === 401 || error.status === 403)) {
|
|
4537
5077
|
isAuthValid = false;
|
|
4538
5078
|
}
|
|
@@ -4558,6 +5098,7 @@ var doctorCommand = defineCommand({
|
|
|
4558
5098
|
extraLocally = [...localOps].filter((op) => !liveOps.has(op));
|
|
4559
5099
|
isSurfaceChecked = true;
|
|
4560
5100
|
} catch {
|
|
5101
|
+
rethrowIfStopped(ctx);
|
|
4561
5102
|
}
|
|
4562
5103
|
}
|
|
4563
5104
|
const isHealthy = isReachable && isAuthValid !== false && isSurfaceChecked && missingLocally.length === 0;
|
|
@@ -4595,6 +5136,9 @@ ${missingLocally.map((op) => ` ${op}`).join("\n")}` : "-- surface not chec
|
|
|
4595
5136
|
};
|
|
4596
5137
|
}
|
|
4597
5138
|
});
|
|
5139
|
+
function rethrowIfStopped(ctx) {
|
|
5140
|
+
ctx.signal.throwIfAborted();
|
|
5141
|
+
}
|
|
4598
5142
|
|
|
4599
5143
|
// src/commands/domains/add.ts
|
|
4600
5144
|
var domainsAddCommand = defineCommand({
|
|
@@ -4745,8 +5289,7 @@ function renderDomains(rows) {
|
|
|
4745
5289
|
var domainsUnsubscribesAddCommand = defineCommand({
|
|
4746
5290
|
path: ["domains", "unsubscribes", "add"],
|
|
4747
5291
|
summary: "Add addresses to a marketing domain's unsubscribe list (that domain only)",
|
|
4748
|
-
sdkMethod:
|
|
4749
|
-
isRawTransport: true,
|
|
5292
|
+
sdkMethod: "domains.unsubscribes.add",
|
|
4750
5293
|
route: { method: "POST", path: "/v1/domains/{domainId}/unsubscribes" },
|
|
4751
5294
|
commandClass: "write",
|
|
4752
5295
|
args: [
|
|
@@ -4767,12 +5310,10 @@ var domainsUnsubscribesAddCommand = defineCommand({
|
|
|
4767
5310
|
if (emails === void 0) {
|
|
4768
5311
|
throw new CliUsageError("Pass at least one --email.");
|
|
4769
5312
|
}
|
|
4770
|
-
const body = await
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
idempotencyKey: flagString(flags.idempotencyKey)
|
|
4775
|
-
});
|
|
5313
|
+
const body = await ctx.client().domains.unsubscribes.add(
|
|
5314
|
+
{ domainId: args.domainId ?? "", emails: [...emails] },
|
|
5315
|
+
requestOptions(flags)
|
|
5316
|
+
);
|
|
4776
5317
|
return { data: body };
|
|
4777
5318
|
}
|
|
4778
5319
|
});
|
|
@@ -4781,8 +5322,7 @@ var domainsUnsubscribesAddCommand = defineCommand({
|
|
|
4781
5322
|
var domainsUnsubscribesExportCommand = defineCommand({
|
|
4782
5323
|
path: ["domains", "unsubscribes", "export"],
|
|
4783
5324
|
summary: "A domain's unsubscribe list as CSV (truncated when capped)",
|
|
4784
|
-
sdkMethod:
|
|
4785
|
-
isRawTransport: true,
|
|
5325
|
+
sdkMethod: "domains.unsubscribes.export",
|
|
4786
5326
|
route: {
|
|
4787
5327
|
method: "GET",
|
|
4788
5328
|
path: "/v1/domains/{domainId}/unsubscribes/export"
|
|
@@ -4802,11 +5342,13 @@ var domainsUnsubscribesExportCommand = defineCommand({
|
|
|
4802
5342
|
"brew-cli domains unsubscribes export dom_123 --scope all --json"
|
|
4803
5343
|
],
|
|
4804
5344
|
run: async ({ ctx, args, flags }) => {
|
|
4805
|
-
const
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
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
|
+
);
|
|
4810
5352
|
return { data: body, human: body.csv };
|
|
4811
5353
|
}
|
|
4812
5354
|
});
|
|
@@ -4815,8 +5357,7 @@ var domainsUnsubscribesExportCommand = defineCommand({
|
|
|
4815
5357
|
var domainsUnsubscribesImportCommand = defineCommand({
|
|
4816
5358
|
path: ["domains", "unsubscribes", "import"],
|
|
4817
5359
|
summary: "Add every address in a CSV to a domain's unsubscribe list",
|
|
4818
|
-
sdkMethod:
|
|
4819
|
-
isRawTransport: true,
|
|
5360
|
+
sdkMethod: "domains.unsubscribes.import",
|
|
4820
5361
|
route: {
|
|
4821
5362
|
method: "POST",
|
|
4822
5363
|
path: "/v1/domains/{domainId}/unsubscribes/import"
|
|
@@ -4844,12 +5385,14 @@ var domainsUnsubscribesImportCommand = defineCommand({
|
|
|
4844
5385
|
);
|
|
4845
5386
|
}
|
|
4846
5387
|
const column = flagString(flags.column);
|
|
4847
|
-
const body = await
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
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
|
+
);
|
|
4853
5396
|
return { data: body };
|
|
4854
5397
|
}
|
|
4855
5398
|
});
|
|
@@ -4858,8 +5401,7 @@ var domainsUnsubscribesImportCommand = defineCommand({
|
|
|
4858
5401
|
var domainsUnsubscribesListCommand = defineCommand({
|
|
4859
5402
|
path: ["domains", "unsubscribes", "list"],
|
|
4860
5403
|
summary: "One page of a marketing domain's unsubscribe list, newest first",
|
|
4861
|
-
sdkMethod:
|
|
4862
|
-
isRawTransport: true,
|
|
5404
|
+
sdkMethod: "domains.unsubscribes.list",
|
|
4863
5405
|
route: { method: "GET", path: "/v1/domains/{domainId}/unsubscribes" },
|
|
4864
5406
|
commandClass: "read",
|
|
4865
5407
|
args: [
|
|
@@ -4880,16 +5422,18 @@ var domainsUnsubscribesListCommand = defineCommand({
|
|
|
4880
5422
|
],
|
|
4881
5423
|
run: async ({ ctx, args, flags }) => {
|
|
4882
5424
|
const limit = flagInt(flags.limit, "--limit");
|
|
4883
|
-
const
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
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
|
+
);
|
|
4893
5437
|
return { data: body, human: renderRows(body.data) };
|
|
4894
5438
|
}
|
|
4895
5439
|
});
|
|
@@ -4908,8 +5452,7 @@ function renderRows(rows) {
|
|
|
4908
5452
|
var domainsUnsubscribesRemoveCommand = defineCommand({
|
|
4909
5453
|
path: ["domains", "unsubscribes", "remove"],
|
|
4910
5454
|
summary: "Take one address off a domain's unsubscribe list (never re-subscribes a brand-wide opt-out)",
|
|
4911
|
-
sdkMethod:
|
|
4912
|
-
isRawTransport: true,
|
|
5455
|
+
sdkMethod: "domains.unsubscribes.remove",
|
|
4913
5456
|
route: {
|
|
4914
5457
|
method: "DELETE",
|
|
4915
5458
|
path: "/v1/domains/{domainId}/unsubscribes/{email}"
|
|
@@ -4922,9 +5465,9 @@ var domainsUnsubscribesRemoveCommand = defineCommand({
|
|
|
4922
5465
|
flags: [],
|
|
4923
5466
|
examples: ["brew-cli domains unsubscribes remove dom_123 ada@example.com"],
|
|
4924
5467
|
run: async ({ ctx, args }) => ({
|
|
4925
|
-
data: await
|
|
4926
|
-
|
|
4927
|
-
|
|
5468
|
+
data: await ctx.client().domains.unsubscribes.remove({
|
|
5469
|
+
domainId: args.domainId ?? "",
|
|
5470
|
+
email: args.email ?? ""
|
|
4928
5471
|
})
|
|
4929
5472
|
})
|
|
4930
5473
|
});
|
|
@@ -5002,6 +5545,7 @@ var emailsAuditCommand = defineCommand({
|
|
|
5002
5545
|
sdkMethod: "emails.auditEmail",
|
|
5003
5546
|
route: { method: "POST", path: "/v1/emails/audit" },
|
|
5004
5547
|
commandClass: "write",
|
|
5548
|
+
defaultTimeoutMs: AUDIT_EMAIL_DEFAULT_TIMEOUT_MS,
|
|
5005
5549
|
isCredited: true,
|
|
5006
5550
|
flags: [
|
|
5007
5551
|
{
|
|
@@ -5045,19 +5589,11 @@ var emailsAuditCommand = defineCommand({
|
|
|
5045
5589
|
"--file is required (a path, or - for stdin), or emailHtml via --input."
|
|
5046
5590
|
);
|
|
5047
5591
|
}
|
|
5048
|
-
const
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
});
|
|
5054
|
-
return { data: result };
|
|
5055
|
-
} catch (error) {
|
|
5056
|
-
if (signal.aborted) {
|
|
5057
|
-
throw signal.reason;
|
|
5058
|
-
}
|
|
5059
|
-
throw error;
|
|
5060
|
-
}
|
|
5592
|
+
const result = await ctx.client().emails.auditEmail(
|
|
5593
|
+
asSdkInput(input),
|
|
5594
|
+
requestOptions(flags)
|
|
5595
|
+
);
|
|
5596
|
+
return { data: result };
|
|
5061
5597
|
}
|
|
5062
5598
|
});
|
|
5063
5599
|
|
|
@@ -5195,8 +5731,6 @@ var emailsDeleteCommand = defineCommand({
|
|
|
5195
5731
|
data: await ctx.client().emails.delete({ emailId: args.emailId ?? "" })
|
|
5196
5732
|
})
|
|
5197
5733
|
});
|
|
5198
|
-
|
|
5199
|
-
// src/commands/emails/edit.ts
|
|
5200
5734
|
var ENVELOPE_FIELDS = ["title", "subjectLine", "groupId"];
|
|
5201
5735
|
function isText(value) {
|
|
5202
5736
|
return typeof value === "string" && value !== "";
|
|
@@ -5207,6 +5741,7 @@ var emailsEditCommand = defineCommand({
|
|
|
5207
5741
|
sdkMethod: "emails.edit",
|
|
5208
5742
|
route: { method: "PATCH", path: "/v1/emails/{emailId}" },
|
|
5209
5743
|
commandClass: "write",
|
|
5744
|
+
defaultTimeoutMs: EDIT_EMAIL_DEFAULT_TIMEOUT_MS,
|
|
5210
5745
|
isCredited: true,
|
|
5211
5746
|
args: [{ name: "emailId", summary: "Design id to edit", isRequired: true }],
|
|
5212
5747
|
flags: [
|
|
@@ -5273,8 +5808,7 @@ var emailsEditCommand = defineCommand({
|
|
|
5273
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"
|
|
5274
5809
|
);
|
|
5275
5810
|
const result = await ctx.client().emails.edit(
|
|
5276
|
-
asSdkInput({ ...input, emailId: args.emailId ?? "" })
|
|
5277
|
-
{ timeoutMs: 24e4 }
|
|
5811
|
+
asSdkInput({ ...input, emailId: args.emailId ?? "" })
|
|
5278
5812
|
);
|
|
5279
5813
|
return { data: result };
|
|
5280
5814
|
}
|
|
@@ -5335,14 +5869,13 @@ var emailsExportCommand = defineCommand({
|
|
|
5335
5869
|
};
|
|
5336
5870
|
}
|
|
5337
5871
|
});
|
|
5338
|
-
|
|
5339
|
-
// src/commands/emails/generate.ts
|
|
5340
5872
|
var emailsGenerateCommand = defineCommand({
|
|
5341
5873
|
path: ["emails", "generate"],
|
|
5342
5874
|
summary: "Generate a new on-brand email design from a prompt",
|
|
5343
5875
|
sdkMethod: "emails.generate",
|
|
5344
5876
|
route: { method: "POST", path: "/v1/emails" },
|
|
5345
5877
|
commandClass: "write",
|
|
5878
|
+
defaultTimeoutMs: GENERATE_EMAIL_DEFAULT_TIMEOUT_MS,
|
|
5346
5879
|
isCredited: true,
|
|
5347
5880
|
flags: [
|
|
5348
5881
|
{ flag: "--prompt <text>", summary: "What the email should be" },
|
|
@@ -5386,10 +5919,10 @@ var emailsGenerateCommand = defineCommand({
|
|
|
5386
5919
|
);
|
|
5387
5920
|
}
|
|
5388
5921
|
progress(ctx, "Generating email\u2026 (typically 30-90s)");
|
|
5389
|
-
const result = await ctx.client().emails.generate(
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5922
|
+
const result = await ctx.client().emails.generate(
|
|
5923
|
+
asSdkInput(input),
|
|
5924
|
+
requestOptions(flags)
|
|
5925
|
+
);
|
|
5393
5926
|
return { data: result };
|
|
5394
5927
|
}
|
|
5395
5928
|
});
|
|
@@ -5438,16 +5971,11 @@ var emailsGetCommand = defineCommand({
|
|
|
5438
5971
|
"Pass --email-version-id or --run-id, not both: each selects the version to read."
|
|
5439
5972
|
);
|
|
5440
5973
|
}
|
|
5441
|
-
if (emailVersionId === void 0 && runId === void 0) {
|
|
5442
|
-
return {
|
|
5443
|
-
data: await ctx.client().emails.get(emailId, include === void 0 ? void 0 : { include })
|
|
5444
|
-
};
|
|
5445
|
-
}
|
|
5446
5974
|
return {
|
|
5447
|
-
data: await
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5975
|
+
data: await ctx.client().emails.get(emailId, {
|
|
5976
|
+
...include === void 0 ? {} : { include },
|
|
5977
|
+
...emailVersionId === void 0 ? {} : { emailVersionId },
|
|
5978
|
+
...runId === void 0 ? {} : { runId }
|
|
5451
5979
|
})
|
|
5452
5980
|
};
|
|
5453
5981
|
}
|
|
@@ -5457,8 +5985,7 @@ var emailsGetCommand = defineCommand({
|
|
|
5457
5985
|
var emailsGetAuditCommand = defineCommand({
|
|
5458
5986
|
path: ["emails", "get-audit"],
|
|
5459
5987
|
summary: "Read a saved email audit: one page of its findings (free; never reruns the audit)",
|
|
5460
|
-
sdkMethod:
|
|
5461
|
-
isRawTransport: true,
|
|
5988
|
+
sdkMethod: "emails.getAudit",
|
|
5462
5989
|
route: { method: "GET", path: "/v1/emails/audits/{auditId}" },
|
|
5463
5990
|
commandClass: "read",
|
|
5464
5991
|
args: [
|
|
@@ -5482,13 +6009,10 @@ var emailsGetAuditCommand = defineCommand({
|
|
|
5482
6009
|
// says whether one exists.
|
|
5483
6010
|
run: async ({ ctx, args, flags }) => {
|
|
5484
6011
|
const limit = flagInt(flags.limit, "--limit");
|
|
5485
|
-
const
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
cursor: flagString(flags.cursor),
|
|
5490
|
-
limit: limit === void 0 ? void 0 : String(limit)
|
|
5491
|
-
}
|
|
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 }
|
|
5492
6016
|
});
|
|
5493
6017
|
return { data: body };
|
|
5494
6018
|
}
|
|
@@ -5498,8 +6022,7 @@ var emailsGetAuditCommand = defineCommand({
|
|
|
5498
6022
|
var emailsGetClientPreviewCommand = defineCommand({
|
|
5499
6023
|
path: ["emails", "get-client-preview"],
|
|
5500
6024
|
summary: "Poll a client-preview rendering job: per-client screenshot links once it settles (free; never re-renders)",
|
|
5501
|
-
sdkMethod:
|
|
5502
|
-
isRawTransport: true,
|
|
6025
|
+
sdkMethod: "emails.getClientPreview",
|
|
5503
6026
|
route: { method: "GET", path: "/v1/emails/client-previews/{previewId}" },
|
|
5504
6027
|
commandClass: "read",
|
|
5505
6028
|
args: [
|
|
@@ -5518,10 +6041,7 @@ var emailsGetClientPreviewCommand = defineCommand({
|
|
|
5518
6041
|
// `failed`; each client then carries its `imageUrl`, or a `reason` and
|
|
5519
6042
|
// whether it is `retryable`.
|
|
5520
6043
|
run: async ({ ctx, args }) => {
|
|
5521
|
-
const body = await
|
|
5522
|
-
method: "GET",
|
|
5523
|
-
path: `/v1/emails/client-previews/${encodeURIComponent(args.previewId ?? "")}`
|
|
5524
|
-
});
|
|
6044
|
+
const body = await ctx.client().emails.getClientPreview(args.previewId ?? "");
|
|
5525
6045
|
return { data: body };
|
|
5526
6046
|
}
|
|
5527
6047
|
});
|
|
@@ -5735,14 +6255,13 @@ var emailsGroupsUpdateCommand = defineCommand({
|
|
|
5735
6255
|
};
|
|
5736
6256
|
}
|
|
5737
6257
|
});
|
|
5738
|
-
|
|
5739
|
-
// src/commands/emails/import.ts
|
|
5740
6258
|
var emailsImportCommand = defineCommand({
|
|
5741
6259
|
path: ["emails", "import"],
|
|
5742
6260
|
summary: "Import existing HTML, MJML, or JSX as a new editable design",
|
|
5743
6261
|
sdkMethod: "emails.import",
|
|
5744
6262
|
route: { method: "POST", path: "/v1/emails/import" },
|
|
5745
6263
|
commandClass: "write",
|
|
6264
|
+
defaultTimeoutMs: IMPORT_EMAIL_DEFAULT_TIMEOUT_MS,
|
|
5746
6265
|
flags: [
|
|
5747
6266
|
{
|
|
5748
6267
|
flag: "--file <path>",
|
|
@@ -5788,14 +6307,13 @@ var emailsImportCommand = defineCommand({
|
|
|
5788
6307
|
return { data: result };
|
|
5789
6308
|
}
|
|
5790
6309
|
});
|
|
5791
|
-
|
|
5792
|
-
// src/commands/emails/import-figma.ts
|
|
5793
6310
|
var emailsImportFigmaCommand = defineCommand({
|
|
5794
6311
|
path: ["emails", "import-figma"],
|
|
5795
6312
|
summary: "Convert one Figma frame into an editable design (deterministic, free)",
|
|
5796
6313
|
sdkMethod: "emails.importFigma",
|
|
5797
6314
|
route: { method: "POST", path: "/v1/emails/figma" },
|
|
5798
6315
|
commandClass: "write",
|
|
6316
|
+
defaultTimeoutMs: IMPORT_FIGMA_DEFAULT_TIMEOUT_MS,
|
|
5799
6317
|
flags: [
|
|
5800
6318
|
{
|
|
5801
6319
|
flag: "--url <figmaUrl>",
|
|
@@ -5896,6 +6414,10 @@ var emailsListCommand = defineCommand({
|
|
|
5896
6414
|
route: { method: "GET", path: "/v1/emails" },
|
|
5897
6415
|
commandClass: "read",
|
|
5898
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
|
+
},
|
|
5899
6421
|
{
|
|
5900
6422
|
flag: "--status <status>",
|
|
5901
6423
|
summary: "Filter by status: generating | ready | failed"
|
|
@@ -5955,6 +6477,7 @@ var emailsListCommand = defineCommand({
|
|
|
5955
6477
|
);
|
|
5956
6478
|
const base = await readJsonFlag(ctx, flags.input, "--input");
|
|
5957
6479
|
const input = mergeInput(base, {
|
|
6480
|
+
search: flagString(flags.search),
|
|
5958
6481
|
status: flagString(flags.status),
|
|
5959
6482
|
groupId: flagString(flags.groupId),
|
|
5960
6483
|
sortBy: flagString(flags.sortBy) ?? flagString(flags.sort) ?? legacy.sortBy,
|
|
@@ -6029,14 +6552,13 @@ function renderEmails(rows) {
|
|
|
6029
6552
|
{ key: "updatedAt", header: "UPDATED" }
|
|
6030
6553
|
]);
|
|
6031
6554
|
}
|
|
6032
|
-
|
|
6033
|
-
// src/commands/emails/preview-clients.ts
|
|
6034
6555
|
var emailsPreviewClientsCommand = defineCommand({
|
|
6035
6556
|
path: ["emails", "preview-clients"],
|
|
6036
6557
|
summary: "Start a rendering job across real email clients (10 credits); poll it with `emails get-client-preview`",
|
|
6037
6558
|
sdkMethod: "emails.previewClients",
|
|
6038
6559
|
route: { method: "POST", path: "/v1/emails/{emailId}/client-previews" },
|
|
6039
6560
|
commandClass: "write",
|
|
6561
|
+
defaultTimeoutMs: PREVIEW_EMAIL_CLIENTS_DEFAULT_TIMEOUT_MS,
|
|
6040
6562
|
isCredited: true,
|
|
6041
6563
|
args: [
|
|
6042
6564
|
{ name: "emailId", summary: "Design id to preview", isRequired: true }
|
|
@@ -6616,7 +7138,7 @@ var loginCommand = defineCommand({
|
|
|
6616
7138
|
globals: { ...ctx.globals, apiKey: candidate },
|
|
6617
7139
|
env: ctx.io.env
|
|
6618
7140
|
});
|
|
6619
|
-
await buildSdkClient(auth).usage.get();
|
|
7141
|
+
await buildSdkClient(auth, sdkTransportFor(ctx)).usage.get();
|
|
6620
7142
|
const path = updateConfig(ctx.io.env, { apiKey: candidate });
|
|
6621
7143
|
progress(ctx, `Logged in. Key stored in ${path} (0600).`);
|
|
6622
7144
|
return {
|
|
@@ -6743,8 +7265,7 @@ var sendsResumeCommand = defineCommand({
|
|
|
6743
7265
|
var templatesGetCommand = defineCommand({
|
|
6744
7266
|
path: ["templates", "get"],
|
|
6745
7267
|
summary: "Fetch one public template: its links and the referenceEmailId to remix; --include html adds its HTML",
|
|
6746
|
-
sdkMethod:
|
|
6747
|
-
isRawTransport: true,
|
|
7268
|
+
sdkMethod: "templates.get",
|
|
6748
7269
|
route: { method: "GET", path: "/v1/templates/{templateId}" },
|
|
6749
7270
|
commandClass: "read",
|
|
6750
7271
|
args: [
|
|
@@ -6765,11 +7286,11 @@ var templatesGetCommand = defineCommand({
|
|
|
6765
7286
|
"brew-cli templates get seed-vercel-newsletter --include html --json"
|
|
6766
7287
|
],
|
|
6767
7288
|
run: async ({ ctx, args, flags }) => {
|
|
6768
|
-
const
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
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
|
+
);
|
|
6773
7294
|
return { data: body };
|
|
6774
7295
|
}
|
|
6775
7296
|
});
|
|
@@ -7055,6 +7576,7 @@ var whoamiCommand = defineCommand({
|
|
|
7055
7576
|
if (error instanceof BrewApiError && (error.status === 401 || error.status === 403)) {
|
|
7056
7577
|
throw error;
|
|
7057
7578
|
}
|
|
7579
|
+
ctx.signal.throwIfAborted();
|
|
7058
7580
|
progress(
|
|
7059
7581
|
ctx,
|
|
7060
7582
|
"Warning: could not reach /v1/usage \u2014 showing local credential state only."
|
|
@@ -7085,6 +7607,7 @@ var ALL_COMMANDS = [
|
|
|
7085
7607
|
contactsSearchCommand,
|
|
7086
7608
|
contactsGetCommand,
|
|
7087
7609
|
contactsCountCommand,
|
|
7610
|
+
contactsCountByCommand,
|
|
7088
7611
|
contactsUpsertCommand,
|
|
7089
7612
|
contactsUpsertManyCommand,
|
|
7090
7613
|
contactsUpdateCommand,
|
|
@@ -7251,6 +7774,7 @@ Environment:
|
|
|
7251
7774
|
Exit codes:
|
|
7252
7775
|
0 success \xB7 1 API/runtime error \xB7 2 usage error \xB7 3 auth error
|
|
7253
7776
|
4 confirmation required (re-run with --yes)
|
|
7777
|
+
130 interrupted (SIGINT) \xB7 143 terminated (SIGTERM)
|
|
7254
7778
|
|
|
7255
7779
|
Agents: run \`${CLI_NAME} docs --agent\` for the machine-readable manifest.
|
|
7256
7780
|
`;
|
|
@@ -7268,8 +7792,8 @@ async function run(argv, io, commands = ALL_COMMANDS) {
|
|
|
7268
7792
|
(text) => {
|
|
7269
7793
|
commanderStderr.push(text);
|
|
7270
7794
|
},
|
|
7271
|
-
(
|
|
7272
|
-
commandExitCode =
|
|
7795
|
+
(code2) => {
|
|
7796
|
+
commandExitCode = code2;
|
|
7273
7797
|
}
|
|
7274
7798
|
);
|
|
7275
7799
|
const flushCommanderStderr = () => {
|
|
@@ -7289,9 +7813,51 @@ async function run(argv, io, commands = ALL_COMMANDS) {
|
|
|
7289
7813
|
}
|
|
7290
7814
|
flushCommanderStderr();
|
|
7291
7815
|
const ctx = activeContext ?? makeFallbackContext(io, argv);
|
|
7292
|
-
|
|
7293
|
-
|
|
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;
|
|
7294
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
|
+
};
|
|
7295
7861
|
}
|
|
7296
7862
|
function handleCommanderError(error, ctx, flush) {
|
|
7297
7863
|
const isHelpCode = error.code === "commander.helpDisplayed" || error.code === "commander.help";
|
|
@@ -7380,16 +7946,21 @@ function registerCommand(program, spec, io, rawArgv, onContext, onExitCode) {
|
|
|
7380
7946
|
},
|
|
7381
7947
|
{}
|
|
7382
7948
|
);
|
|
7383
|
-
const ctx = makeContext({ io, flags, rawArgv });
|
|
7949
|
+
const ctx = makeContext({ io, flags, rawArgv, traits: traitsOf(spec) });
|
|
7384
7950
|
onContext?.(ctx);
|
|
7385
7951
|
const invocation = { ctx, args: positional, flags };
|
|
7386
7952
|
await enforceConfirmation(spec, invocation);
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
if (outcome
|
|
7391
|
-
|
|
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
|
+
}
|
|
7392
7961
|
}
|
|
7962
|
+
} finally {
|
|
7963
|
+
ctx.stopDeadline();
|
|
7393
7964
|
}
|
|
7394
7965
|
});
|
|
7395
7966
|
}
|
|
@@ -7428,20 +7999,77 @@ function buildCommandHelpFooter(spec) {
|
|
|
7428
7999
|
return lines.join("\n");
|
|
7429
8000
|
}
|
|
7430
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
|
+
|
|
7431
8044
|
// src/bin.ts
|
|
7432
|
-
function realIo() {
|
|
8045
|
+
function realIo(interrupter2) {
|
|
8046
|
+
const { signal } = interrupter2;
|
|
7433
8047
|
return {
|
|
7434
8048
|
stdout: process.stdout,
|
|
7435
8049
|
stderr: process.stderr,
|
|
7436
8050
|
isTtyOut: process.stdout.isTTY === true,
|
|
7437
8051
|
isTtyIn: process.stdin.isTTY === true,
|
|
7438
8052
|
env: process.env,
|
|
8053
|
+
signal,
|
|
7439
8054
|
readStdin: async () => {
|
|
8055
|
+
const stop = () => {
|
|
8056
|
+
process.stdin.destroy();
|
|
8057
|
+
};
|
|
8058
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
7440
8059
|
process.stdin.setEncoding("utf8");
|
|
7441
8060
|
let data = "";
|
|
7442
|
-
|
|
7443
|
-
|
|
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);
|
|
7444
8071
|
}
|
|
8072
|
+
signal.throwIfAborted();
|
|
7445
8073
|
return data;
|
|
7446
8074
|
},
|
|
7447
8075
|
readLine: async (prompt) => {
|
|
@@ -7449,14 +8077,34 @@ function realIo() {
|
|
|
7449
8077
|
input: process.stdin,
|
|
7450
8078
|
output: process.stderr
|
|
7451
8079
|
});
|
|
8080
|
+
rl.on("SIGINT", () => {
|
|
8081
|
+
interrupter2.trigger("SIGINT");
|
|
8082
|
+
});
|
|
7452
8083
|
try {
|
|
7453
|
-
return await rl.question(prompt);
|
|
8084
|
+
return await rl.question(prompt, { signal });
|
|
7454
8085
|
} finally {
|
|
7455
8086
|
rl.close();
|
|
7456
8087
|
}
|
|
7457
8088
|
}
|
|
7458
8089
|
};
|
|
7459
8090
|
}
|
|
7460
|
-
|
|
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
|
+
}
|
|
7461
8109
|
//# sourceMappingURL=bin.js.map
|
|
7462
8110
|
//# sourceMappingURL=bin.js.map
|