@primitivedotdev/sdk 0.10.0 → 0.12.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/dist/oclif/api-command.js +260 -52
- package/dist/oclif/commands/send.js +159 -0
- package/dist/oclif/commands/whoami.js +68 -0
- package/dist/oclif/index.js +12 -0
- package/oclif.manifest.json +362 -46
- package/package.json +1 -1
|
@@ -7,80 +7,126 @@ function flagName(parameterName) {
|
|
|
7
7
|
function flagDescription(parameter) {
|
|
8
8
|
return parameter.description ?? parameter.name;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
* Render a one-shot description of a JSON Schema's top-level
|
|
12
|
-
* properties so an agent reading `<command> --help` can build a
|
|
13
|
-
* valid `--body` payload without probing the server. Pulled from
|
|
14
|
-
* the resolved request schema embedded on the manifest.
|
|
15
|
-
*
|
|
16
|
-
* Format prioritizes scanability over completeness: required
|
|
17
|
-
* fields first, each line is `<name> <type> [- description]`
|
|
18
|
-
* truncated to a reasonable width. Callers who need the full
|
|
19
|
-
* schema can run `primitive list-operations | jq` and read
|
|
20
|
-
* `requestSchema` directly.
|
|
21
|
-
*/
|
|
22
|
-
function renderRequestSchemaSummary(schema) {
|
|
10
|
+
function extractBodyFields(schema) {
|
|
23
11
|
if (!schema || typeof schema !== "object")
|
|
24
|
-
return
|
|
12
|
+
return [];
|
|
25
13
|
const properties = schema.properties;
|
|
26
14
|
if (!properties || typeof properties !== "object")
|
|
27
|
-
return
|
|
15
|
+
return [];
|
|
28
16
|
const requiredArr = Array.isArray(schema.required)
|
|
29
17
|
? schema.required.filter((k) => typeof k === "string")
|
|
30
18
|
: [];
|
|
31
19
|
const required = new Set(requiredArr);
|
|
32
|
-
const
|
|
33
|
-
|
|
20
|
+
const fields = [];
|
|
21
|
+
for (const [name, raw] of Object.entries(properties)) {
|
|
34
22
|
const propSchema = raw && typeof raw === "object" ? raw : {};
|
|
35
|
-
let type = "any";
|
|
36
23
|
const t = propSchema.type;
|
|
24
|
+
let displayType = "any";
|
|
25
|
+
let kind = "complex";
|
|
37
26
|
if (typeof t === "string") {
|
|
38
|
-
|
|
39
|
-
if (
|
|
27
|
+
displayType = t;
|
|
28
|
+
if (t === "string")
|
|
29
|
+
kind = "string";
|
|
30
|
+
else if (t === "integer" || t === "number")
|
|
31
|
+
kind = "integer";
|
|
32
|
+
else if (t === "boolean")
|
|
33
|
+
kind = "boolean";
|
|
34
|
+
else if (t === "array") {
|
|
40
35
|
const items = propSchema.items;
|
|
41
36
|
if (items && typeof items === "object") {
|
|
42
37
|
const itemType = items.type;
|
|
43
38
|
if (typeof itemType === "string") {
|
|
44
|
-
|
|
39
|
+
displayType = `array<${itemType}>`;
|
|
45
40
|
}
|
|
46
41
|
}
|
|
42
|
+
kind = "complex";
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
kind = "complex";
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
else if (Array.isArray(t)) {
|
|
50
|
-
// Nullable shorthand the codegen normalizes to e.g.
|
|
49
|
+
// Nullable shorthand the codegen normalizes to e.g.
|
|
50
|
+
// ["string","null"]. If exactly one non-null member, surface
|
|
51
|
+
// it as that scalar with a trailing `?`.
|
|
51
52
|
const nonNull = t.filter((s) => s !== "null");
|
|
52
|
-
|
|
53
|
+
if (nonNull.length === 1) {
|
|
54
|
+
const single = nonNull[0];
|
|
55
|
+
displayType = `${single}?`;
|
|
56
|
+
if (single === "string")
|
|
57
|
+
kind = "string";
|
|
58
|
+
else if (single === "integer" || single === "number")
|
|
59
|
+
kind = "integer";
|
|
60
|
+
else if (single === "boolean")
|
|
61
|
+
kind = "boolean";
|
|
62
|
+
else
|
|
63
|
+
kind = "complex";
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
displayType = nonNull.join("|");
|
|
67
|
+
kind = "complex";
|
|
68
|
+
}
|
|
53
69
|
}
|
|
54
70
|
const description = typeof propSchema.description === "string"
|
|
55
71
|
? propSchema.description.split("\n")[0].trim()
|
|
56
72
|
: "";
|
|
57
|
-
|
|
73
|
+
const enumRaw = propSchema.enum;
|
|
74
|
+
const enumValues = kind === "string" && Array.isArray(enumRaw)
|
|
75
|
+
? enumRaw.filter((e) => typeof e === "string")
|
|
76
|
+
: undefined;
|
|
77
|
+
fields.push({
|
|
58
78
|
name,
|
|
59
|
-
type,
|
|
60
79
|
description,
|
|
61
80
|
required: required.has(name),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
displayType,
|
|
82
|
+
kind,
|
|
83
|
+
...(enumValues && enumValues.length > 0 ? { enumValues } : {}),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return fields.sort((a, b) => {
|
|
65
87
|
if (a.required !== b.required)
|
|
66
88
|
return a.required ? -1 : 1;
|
|
67
89
|
return a.name.localeCompare(b.name);
|
|
68
90
|
});
|
|
69
|
-
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Render a "Body fields" summary for the per-command help.
|
|
94
|
+
*
|
|
95
|
+
* Most scalar fields are exposed as individual `--flag` flags,
|
|
96
|
+
* which oclif auto-renders in the FLAGS section above. To avoid
|
|
97
|
+
* duplicating that, the summary here only documents fields that
|
|
98
|
+
* MUST go through `--raw-body` (complex types: arrays, objects,
|
|
99
|
+
* mixed-non-nullable). When an operation has only scalars, the
|
|
100
|
+
* summary is omitted entirely and oclif's FLAGS section is the
|
|
101
|
+
* full story.
|
|
102
|
+
*
|
|
103
|
+
* For operations with mixed scalar and complex fields, we also
|
|
104
|
+
* include a short header pointing the agent at the flag form so
|
|
105
|
+
* the natural reading is "use the flags above; --raw-body for
|
|
106
|
+
* the leftovers below."
|
|
107
|
+
*/
|
|
108
|
+
function renderRequestSchemaSummary(schema) {
|
|
109
|
+
const fields = extractBodyFields(schema);
|
|
110
|
+
if (fields.length === 0)
|
|
111
|
+
return null;
|
|
112
|
+
const complex = fields.filter((f) => f.kind === "complex");
|
|
113
|
+
if (complex.length === 0)
|
|
70
114
|
return null;
|
|
71
|
-
const nameWidth = Math.min(24, Math.max(...
|
|
72
|
-
const lines = ["Body fields (JSON --body):"];
|
|
115
|
+
const nameWidth = Math.min(24, Math.max(...complex.map((f) => f.name.length)));
|
|
73
116
|
const descMax = 78;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
117
|
+
const lines = [
|
|
118
|
+
"Body fields requiring --raw-body JSON (these are not exposed as flags):",
|
|
119
|
+
];
|
|
120
|
+
for (const f of complex) {
|
|
121
|
+
const marker = f.required ? " *" : " ";
|
|
122
|
+
const padName = f.name.padEnd(nameWidth);
|
|
123
|
+
const trimmedDesc = f.description.length > descMax
|
|
124
|
+
? `${f.description.slice(0, descMax - 3)}...`
|
|
125
|
+
: f.description;
|
|
80
126
|
const desc = trimmedDesc ? ` ${trimmedDesc}` : "";
|
|
81
|
-
lines.push(`${
|
|
127
|
+
lines.push(`${marker} ${padName} ${f.displayType}${desc}`);
|
|
82
128
|
}
|
|
83
|
-
lines.push("(* = required)");
|
|
129
|
+
lines.push("(* = required. Scalar body fields are exposed as individual --flag-name flags; see FLAGS above.)");
|
|
84
130
|
return lines.join("\n");
|
|
85
131
|
}
|
|
86
132
|
export function flagForParameter(parameter) {
|
|
@@ -134,9 +180,9 @@ function parseJson(source, flagLabel) {
|
|
|
134
180
|
}
|
|
135
181
|
export function readJsonBody(flags) {
|
|
136
182
|
const bodyFile = flags["body-file"];
|
|
137
|
-
const
|
|
138
|
-
if (bodyFile &&
|
|
139
|
-
throw cliError("Use either --body or --body-file, not both");
|
|
183
|
+
const rawBody = flags["raw-body"];
|
|
184
|
+
if (bodyFile && rawBody) {
|
|
185
|
+
throw cliError("Use either --raw-body or --body-file, not both");
|
|
140
186
|
}
|
|
141
187
|
if (typeof bodyFile === "string") {
|
|
142
188
|
let contents;
|
|
@@ -149,8 +195,8 @@ export function readJsonBody(flags) {
|
|
|
149
195
|
}
|
|
150
196
|
return parseJson(contents, `--body-file ${bodyFile}`);
|
|
151
197
|
}
|
|
152
|
-
if (typeof
|
|
153
|
-
return parseJson(
|
|
198
|
+
if (typeof rawBody === "string") {
|
|
199
|
+
return parseJson(rawBody, "--raw-body");
|
|
154
200
|
}
|
|
155
201
|
return undefined;
|
|
156
202
|
}
|
|
@@ -196,6 +242,56 @@ export function formatErrorPayload(payload) {
|
|
|
196
242
|
}
|
|
197
243
|
return JSON.stringify(payload, null, 2);
|
|
198
244
|
}
|
|
245
|
+
// Reserved flag names the body-field expander must never overwrite.
|
|
246
|
+
// `--raw-body` and `--body-file` are the JSON escape hatches.
|
|
247
|
+
// `--api-key`, `--base-url`, `--output` are infra. Path and query
|
|
248
|
+
// params get added before body fields and take precedence.
|
|
249
|
+
//
|
|
250
|
+
// Note: `--body` is intentionally NOT reserved here. The naive
|
|
251
|
+
// agent expectation (per AGX walkthrough) is that --body means
|
|
252
|
+
// "the message body content," which collides with the JSON
|
|
253
|
+
// escape-hatch meaning we used pre-0.12. The escape hatch is now
|
|
254
|
+
// `--raw-body`; --body is free to be claimed by per-field flag
|
|
255
|
+
// expansion as the kebab-cased version of a `body` schema field
|
|
256
|
+
// (e.g. on a future `body: { ... }` schema). For send-mail today,
|
|
257
|
+
// the body-text field is `body_text` -> `--body-text`, and there
|
|
258
|
+
// is no top-level `body` field, so --body remains unclaimed at
|
|
259
|
+
// the generated-command level. The agent shortcut `primitive
|
|
260
|
+
// send` defines its own --body for the message text.
|
|
261
|
+
const RESERVED_FLAG_NAMES = new Set([
|
|
262
|
+
"api-key",
|
|
263
|
+
"base-url",
|
|
264
|
+
"raw-body",
|
|
265
|
+
"body-file",
|
|
266
|
+
"output",
|
|
267
|
+
]);
|
|
268
|
+
function bodyFieldFlag(field) {
|
|
269
|
+
// Flag descriptions cap at 80 chars so oclif's --help output
|
|
270
|
+
// stays readable; the schema's full description is also visible
|
|
271
|
+
// via `primitive list-operations | jq`.
|
|
272
|
+
const descMax = 80;
|
|
273
|
+
const trimmedDesc = field.description.length > descMax
|
|
274
|
+
? `${field.description.slice(0, descMax - 3)}...`
|
|
275
|
+
: field.description;
|
|
276
|
+
// Field-flag UX choice: do NOT mark scalar body fields as
|
|
277
|
+
// required at the oclif level even when the JSON Schema marks
|
|
278
|
+
// them required. Reason: a caller can satisfy the requirement
|
|
279
|
+
// either via the individual flag OR via --body / --body-file.
|
|
280
|
+
// Marking the flag required would force the individual-flag
|
|
281
|
+
// form. The runtime body merger validates the final assembled
|
|
282
|
+
// body against the same server-side schema either way.
|
|
283
|
+
const common = {
|
|
284
|
+
description: trimmedDesc || field.name,
|
|
285
|
+
};
|
|
286
|
+
if (field.kind === "boolean")
|
|
287
|
+
return Flags.boolean(common);
|
|
288
|
+
if (field.kind === "integer")
|
|
289
|
+
return Flags.integer(common);
|
|
290
|
+
if (field.enumValues) {
|
|
291
|
+
return Flags.string({ ...common, options: field.enumValues });
|
|
292
|
+
}
|
|
293
|
+
return Flags.string(common);
|
|
294
|
+
}
|
|
199
295
|
function buildFlags(operation) {
|
|
200
296
|
const flags = {
|
|
201
297
|
"api-key": Flags.string({
|
|
@@ -210,18 +306,67 @@ function buildFlags(operation) {
|
|
|
210
306
|
for (const parameter of [...operation.pathParams, ...operation.queryParams]) {
|
|
211
307
|
flags[flagName(parameter.name)] = flagForParameter(parameter);
|
|
212
308
|
}
|
|
309
|
+
const bodyFieldFlagToProperty = new Map();
|
|
213
310
|
if (operation.hasJsonBody) {
|
|
214
|
-
flags
|
|
311
|
+
flags["raw-body"] = Flags.string({
|
|
312
|
+
description: "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available.",
|
|
313
|
+
});
|
|
215
314
|
flags["body-file"] = Flags.string({
|
|
216
|
-
description: "Path to a JSON file used as the request body",
|
|
315
|
+
description: "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
217
316
|
});
|
|
317
|
+
// Expand top-level scalar body fields into individual flags so
|
|
318
|
+
// `primitive sending:send-email --to alice@x --from support@x
|
|
319
|
+
// --body-text "hi"` works without constructing JSON. Driven by
|
|
320
|
+
// the requestSchema embedded on the manifest. Skip flags that
|
|
321
|
+
// collide with reserved names or with path/query params already
|
|
322
|
+
// added above; those collisions fall back to --body.
|
|
323
|
+
//
|
|
324
|
+
// Collisions are tracked in the returned map so the run()
|
|
325
|
+
// handler doesn't misread a path/query param's value as a
|
|
326
|
+
// body-field override. (A naive "look up parsedFlags[name]"
|
|
327
|
+
// pass would happily pick up the path param's value and
|
|
328
|
+
// silently write it into the body.)
|
|
329
|
+
const bodyFields = extractBodyFields(operation.requestSchema);
|
|
330
|
+
for (const field of bodyFields) {
|
|
331
|
+
if (field.kind === "complex")
|
|
332
|
+
continue;
|
|
333
|
+
const name = flagName(field.name);
|
|
334
|
+
if (RESERVED_FLAG_NAMES.has(name))
|
|
335
|
+
continue;
|
|
336
|
+
if (flags[name] !== undefined)
|
|
337
|
+
continue;
|
|
338
|
+
flags[name] = bodyFieldFlag(field);
|
|
339
|
+
bodyFieldFlagToProperty.set(name, field.name);
|
|
340
|
+
}
|
|
218
341
|
}
|
|
219
342
|
if (operation.binaryResponse) {
|
|
220
343
|
flags.output = Flags.string({
|
|
221
344
|
description: "Write binary response bytes to a file",
|
|
222
345
|
});
|
|
223
346
|
}
|
|
224
|
-
return flags;
|
|
347
|
+
return { flags, bodyFieldFlagToProperty };
|
|
348
|
+
}
|
|
349
|
+
// Pull body field values out of the parsed CLI flags. Returns
|
|
350
|
+
// only fields the user actually supplied (omits undefined). Used
|
|
351
|
+
// to override / extend the JSON --body when both forms are
|
|
352
|
+
// present (per-field flags take precedence on key conflicts).
|
|
353
|
+
//
|
|
354
|
+
// The `bodyFieldFlagToProperty` allowlist comes from buildFlags and
|
|
355
|
+
// records ONLY the flags actually registered as body-field flags.
|
|
356
|
+
// Without it, this function would naively read parsedFlags by
|
|
357
|
+
// kebab-cased field name and pick up values from a colliding path
|
|
358
|
+
// or query param flag, silently writing them into the body under
|
|
359
|
+
// the body-field key. The allowlist keeps the merge honest: only
|
|
360
|
+
// flags this CLI generator owns end up in the body.
|
|
361
|
+
function collectBodyFieldFlags(parsedFlags, bodyFieldFlagToProperty) {
|
|
362
|
+
const result = {};
|
|
363
|
+
for (const [flag, property] of bodyFieldFlagToProperty) {
|
|
364
|
+
const value = parsedFlags[flag];
|
|
365
|
+
if (value === undefined)
|
|
366
|
+
continue;
|
|
367
|
+
result[property] = value;
|
|
368
|
+
}
|
|
369
|
+
return result;
|
|
225
370
|
}
|
|
226
371
|
function collectValues(parameters, flags) {
|
|
227
372
|
const values = {};
|
|
@@ -234,7 +379,7 @@ function collectValues(parameters, flags) {
|
|
|
234
379
|
return values;
|
|
235
380
|
}
|
|
236
381
|
export function createOperationCommand(operation) {
|
|
237
|
-
const flags = buildFlags(operation);
|
|
382
|
+
const { flags, bodyFieldFlagToProperty } = buildFlags(operation);
|
|
238
383
|
// Append a "Body fields" summary to the description so agents
|
|
239
384
|
// running `<command> --help` learn the JSON shape immediately.
|
|
240
385
|
// Without this, `--help` only said "JSON request body" and agents
|
|
@@ -262,11 +407,48 @@ export function createOperationCommand(operation) {
|
|
|
262
407
|
? parsedFlags["base-url"]
|
|
263
408
|
: undefined,
|
|
264
409
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
410
|
+
// Two body sources, merged: explicit JSON via --body /
|
|
411
|
+
// --body-file (the base) plus per-field flags (the
|
|
412
|
+
// overrides). Per-field flag values take precedence on key
|
|
413
|
+
// conflicts so a caller can pass a base payload via --body
|
|
414
|
+
// and override one field on the command line.
|
|
415
|
+
let body;
|
|
416
|
+
if (operation.hasJsonBody) {
|
|
417
|
+
const explicit = readJsonBody(parsedFlags);
|
|
418
|
+
const overrides = collectBodyFieldFlags(parsedFlags, bodyFieldFlagToProperty);
|
|
419
|
+
if (Object.keys(overrides).length > 0) {
|
|
420
|
+
if (explicit === undefined) {
|
|
421
|
+
body = overrides;
|
|
422
|
+
}
|
|
423
|
+
else if (explicit !== null &&
|
|
424
|
+
typeof explicit === "object" &&
|
|
425
|
+
!Array.isArray(explicit)) {
|
|
426
|
+
body = { ...explicit, ...overrides };
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
// Caller passed --raw-body as null, an array, or a
|
|
430
|
+
// primitive AND also passed per-field flags. We can't
|
|
431
|
+
// merge per-field overrides into a non-object body
|
|
432
|
+
// shape, and silently dropping either source would
|
|
433
|
+
// leave the caller's actual intent unclear. Refuse
|
|
434
|
+
// loudly so the next attempt is unambiguous.
|
|
435
|
+
const explicitKind = explicit === null
|
|
436
|
+
? "null"
|
|
437
|
+
: Array.isArray(explicit)
|
|
438
|
+
? "array"
|
|
439
|
+
: typeof explicit;
|
|
440
|
+
const overrideFlags = Object.keys(overrides)
|
|
441
|
+
.map((p) => `--${flagName(p)}`)
|
|
442
|
+
.join(", ");
|
|
443
|
+
throw new Errors.CLIError(`--raw-body must be a JSON object when also passing per-field flags (got ${explicitKind}); supplied per-field flags: ${overrideFlags}. Either drop --raw-body and rely on the per-field flags, or move every field into the JSON --raw-body and drop the flags.`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
body = explicit;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
268
450
|
if (operation.bodyRequired && body === undefined) {
|
|
269
|
-
throw new Errors.CLIError(`Operation ${operation.operationId} requires
|
|
451
|
+
throw new Errors.CLIError(`Operation ${operation.operationId} requires a body. Pass each field as a --flag (see --help) or supply JSON via --raw-body / --body-file.`);
|
|
270
452
|
}
|
|
271
453
|
const operationFn = operations[operation.sdkName];
|
|
272
454
|
const result = await operationFn({
|
|
@@ -299,8 +481,34 @@ export function createOperationCommand(operation) {
|
|
|
299
481
|
if (cursor) {
|
|
300
482
|
process.stderr.write(`next cursor: ${cursor}\n`);
|
|
301
483
|
}
|
|
484
|
+
// Empty-result hint. When a list-style operation returns
|
|
485
|
+
// an empty array, emit an operation-specific note to
|
|
486
|
+
// stderr so a naive caller can distinguish "nothing here"
|
|
487
|
+
// from "something isn't set up." Stdout still gets the
|
|
488
|
+
// raw `[]` so machine-readable output is unchanged. The
|
|
489
|
+
// AGX walkthrough flagged this: `list-deliveries` returning
|
|
490
|
+
// `[]` left the agent unsure whether they had an empty
|
|
491
|
+
// delivery log or no endpoints configured at all.
|
|
492
|
+
if (Array.isArray(envelope?.data) && envelope.data.length === 0) {
|
|
493
|
+
const hint = EMPTY_RESULT_HINTS[operation.sdkName];
|
|
494
|
+
if (hint)
|
|
495
|
+
process.stderr.write(`${hint}\n`);
|
|
496
|
+
}
|
|
302
497
|
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
303
498
|
}
|
|
304
499
|
}
|
|
305
500
|
return OperationCommand;
|
|
306
501
|
}
|
|
502
|
+
// Empty-state hints for list-style operations whose empty result
|
|
503
|
+
// would otherwise leave the caller wondering "is this empty
|
|
504
|
+
// because there's nothing to list, or because something earlier
|
|
505
|
+
// in the setup chain isn't done?" Keys are the manifest's
|
|
506
|
+
// `sdkName` for the operation. Operations without an entry fall
|
|
507
|
+
// back to no hint (silent empty array, same as before).
|
|
508
|
+
const EMPTY_RESULT_HINTS = {
|
|
509
|
+
listDeliveries: "(no results) Often means no webhook endpoints are configured to receive deliveries. Run `primitive endpoints:list-endpoints` to check.",
|
|
510
|
+
listEndpoints: "(no results) No webhook endpoints configured. Add one with `primitive endpoints:create-endpoint --url <your-url>`.",
|
|
511
|
+
listEmails: "(no results) No inbound emails received yet on this account.",
|
|
512
|
+
listDomains: "(no results) No domains on this account. Add one with `primitive domains:add-domain --domain <yourdomain.example>`.",
|
|
513
|
+
listFilters: "(no results) No filter rules configured.",
|
|
514
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Command, Errors, Flags } from "@oclif/core";
|
|
2
|
+
import { listDomains, sendEmail } from "../../api/generated/sdk.gen.js";
|
|
3
|
+
import { PrimitiveApiClient } from "../../api/index.js";
|
|
4
|
+
import { extractErrorPayload, formatErrorPayload } from "../api-command.js";
|
|
5
|
+
// `primitive send` is the agent-grade shortcut for the most common
|
|
6
|
+
// case: send a fresh outbound email. It wraps `sending:send-email`
|
|
7
|
+
// with two ergonomic defaults that the underlying operation can't
|
|
8
|
+
// express through manifest-driven flag generation alone:
|
|
9
|
+
//
|
|
10
|
+
// 1. `--from` defaults to `agent@<first-verified-domain>` when
|
|
11
|
+
// omitted. Most agents don't know which domains their org has
|
|
12
|
+
// verified for outbound; making them list-domains first to
|
|
13
|
+
// derive a from-address is exactly the kind of email-ops cruft
|
|
14
|
+
// this command exists to hide. Customers with multiple
|
|
15
|
+
// domains, or who want a different local-part, pass --from
|
|
16
|
+
// explicitly.
|
|
17
|
+
// 2. `--subject` defaults to the first non-empty line of the body
|
|
18
|
+
// (capped). Empty subjects get spam-scored, so we always emit
|
|
19
|
+
// something. Callers who want full control pass --subject.
|
|
20
|
+
//
|
|
21
|
+
// `--body` here is the message body (text). The full `send-email`
|
|
22
|
+
// operation distinguishes `body_text` and `body_html`; this
|
|
23
|
+
// shortcut keeps it simple by exposing `--body` for text and
|
|
24
|
+
// `--html` for the HTML alternative. Users who need both can pass
|
|
25
|
+
// both flags or fall back to `sending:send-email` for the full
|
|
26
|
+
// flag list.
|
|
27
|
+
//
|
|
28
|
+
// Compared to `swaks` (which agents likely have in their training
|
|
29
|
+
// data): this is `swaks`-shaped on purpose so an agent
|
|
30
|
+
// pattern-matching from there lands in the happy path. We just
|
|
31
|
+
// don't need swaks's `--server` / `--auth-*` flags because the
|
|
32
|
+
// HTTPS API key is the auth and the server is implicit.
|
|
33
|
+
const SUBJECT_MAX_LENGTH = 70;
|
|
34
|
+
function deriveSubject(body) {
|
|
35
|
+
for (const line of body.split("\n")) {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
if (!trimmed)
|
|
38
|
+
continue;
|
|
39
|
+
return trimmed.length > SUBJECT_MAX_LENGTH
|
|
40
|
+
? `${trimmed.slice(0, SUBJECT_MAX_LENGTH - 3)}...`
|
|
41
|
+
: trimmed;
|
|
42
|
+
}
|
|
43
|
+
return "Message";
|
|
44
|
+
}
|
|
45
|
+
function isVerifiedDomain(domain) {
|
|
46
|
+
return domain.is_active === true;
|
|
47
|
+
}
|
|
48
|
+
async function pickDefaultFromAddress(apiClient) {
|
|
49
|
+
const result = await listDomains({
|
|
50
|
+
client: apiClient.client,
|
|
51
|
+
responseStyle: "fields",
|
|
52
|
+
});
|
|
53
|
+
if (result.error) {
|
|
54
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
55
|
+
throw new Errors.CLIError(`Could not look up your verified domains to default --from. Pass --from explicitly. Underlying error: ${formatErrorPayload(errorPayload)}`);
|
|
56
|
+
}
|
|
57
|
+
const envelope = result.data;
|
|
58
|
+
const first = envelope?.data?.find(isVerifiedDomain);
|
|
59
|
+
if (!first) {
|
|
60
|
+
throw new Errors.CLIError("No active verified outbound domain found on this account; pass --from explicitly. To set up outbound, claim a domain via `primitive domains:add-domain` and verify it.");
|
|
61
|
+
}
|
|
62
|
+
// Local-part: "agent". Any local-part is accepted on managed
|
|
63
|
+
// *.primitive.email subdomains, so this works out of the box for
|
|
64
|
+
// the auto-issued domain pool. For customers with BYO domains
|
|
65
|
+
// and their own MX, "agent@" may or may not be a routable
|
|
66
|
+
// mailbox; if you have a specific address you want to use, pass
|
|
67
|
+
// --from explicitly.
|
|
68
|
+
return `agent@${first.domain}`;
|
|
69
|
+
}
|
|
70
|
+
class SendCommand extends Command {
|
|
71
|
+
static description = `Send an outbound email. Agent-grade shortcut for sending:send-email with sensible defaults.
|
|
72
|
+
|
|
73
|
+
--from defaults to agent@<your-first-verified-outbound-domain> when omitted.
|
|
74
|
+
--subject defaults to the first line of the body when omitted.
|
|
75
|
+
|
|
76
|
+
For the full flag set (custom message-id threading on the wire,
|
|
77
|
+
references arrays, etc.), use \`primitive sending:send-email\`.`;
|
|
78
|
+
static summary = "Send an email (simplified, agent-friendly)";
|
|
79
|
+
static examples = [
|
|
80
|
+
"<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
|
|
81
|
+
"<%= config.bin %> send --to alice@example.com --from support@yourcompany.com --subject 'Quick question' --body 'Are you free Thursday?'",
|
|
82
|
+
"<%= config.bin %> send --to alice@example.com --html '<p>Hello!</p>'",
|
|
83
|
+
"<%= config.bin %> send --to alice@example.com --body 'Confirmed' --wait",
|
|
84
|
+
];
|
|
85
|
+
static flags = {
|
|
86
|
+
"api-key": Flags.string({
|
|
87
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
88
|
+
env: "PRIMITIVE_API_KEY",
|
|
89
|
+
}),
|
|
90
|
+
"base-url": Flags.string({
|
|
91
|
+
description: "API base URL (defaults to PRIMITIVE_API_URL or production)",
|
|
92
|
+
env: "PRIMITIVE_API_URL",
|
|
93
|
+
}),
|
|
94
|
+
to: Flags.string({
|
|
95
|
+
description: "Recipient address (e.g. alice@example.com).",
|
|
96
|
+
required: true,
|
|
97
|
+
}),
|
|
98
|
+
from: Flags.string({
|
|
99
|
+
description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>.",
|
|
100
|
+
}),
|
|
101
|
+
subject: Flags.string({
|
|
102
|
+
description: "Subject line. Defaults to the first line of --body / --html when omitted.",
|
|
103
|
+
}),
|
|
104
|
+
body: Flags.string({
|
|
105
|
+
description: "Plain-text message body. Either --body or --html (or both) is required.",
|
|
106
|
+
}),
|
|
107
|
+
html: Flags.string({
|
|
108
|
+
description: "HTML message body. Either --body or --html (or both) is required.",
|
|
109
|
+
}),
|
|
110
|
+
"in-reply-to": Flags.string({
|
|
111
|
+
description: "Message-Id of the parent email when threading a reply on the wire. For replying to an inbound message you received, prefer `primitive sending:reply-to-email --id <inbound-id>`.",
|
|
112
|
+
}),
|
|
113
|
+
wait: Flags.boolean({
|
|
114
|
+
description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the message for delivery.",
|
|
115
|
+
}),
|
|
116
|
+
"wait-timeout-ms": Flags.integer({
|
|
117
|
+
description: "Maximum time to wait when --wait is set. Defaults to 30000ms.",
|
|
118
|
+
}),
|
|
119
|
+
};
|
|
120
|
+
async run() {
|
|
121
|
+
const { flags } = await this.parse(SendCommand);
|
|
122
|
+
if (!flags.body && !flags.html) {
|
|
123
|
+
throw new Errors.CLIError("Either --body or --html (or both) is required.");
|
|
124
|
+
}
|
|
125
|
+
const apiClient = new PrimitiveApiClient({
|
|
126
|
+
apiKey: flags["api-key"],
|
|
127
|
+
baseUrl: flags["base-url"],
|
|
128
|
+
});
|
|
129
|
+
const from = flags.from ?? (await pickDefaultFromAddress(apiClient));
|
|
130
|
+
const subject = flags.subject ?? (flags.body ? deriveSubject(flags.body) : "Message");
|
|
131
|
+
const result = await sendEmail({
|
|
132
|
+
body: {
|
|
133
|
+
from,
|
|
134
|
+
to: flags.to,
|
|
135
|
+
subject,
|
|
136
|
+
...(flags.body !== undefined ? { body_text: flags.body } : {}),
|
|
137
|
+
...(flags.html !== undefined ? { body_html: flags.html } : {}),
|
|
138
|
+
...(flags["in-reply-to"] !== undefined
|
|
139
|
+
? { in_reply_to: flags["in-reply-to"] }
|
|
140
|
+
: {}),
|
|
141
|
+
...(flags.wait !== undefined ? { wait: flags.wait } : {}),
|
|
142
|
+
...(flags["wait-timeout-ms"] !== undefined
|
|
143
|
+
? { wait_timeout_ms: flags["wait-timeout-ms"] }
|
|
144
|
+
: {}),
|
|
145
|
+
},
|
|
146
|
+
client: apiClient.client,
|
|
147
|
+
responseStyle: "fields",
|
|
148
|
+
});
|
|
149
|
+
if (result.error) {
|
|
150
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
151
|
+
process.stderr.write(`${formatErrorPayload(errorPayload)}\n`);
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const envelope = result.data;
|
|
156
|
+
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export default SendCommand;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Command, Errors, Flags } from "@oclif/core";
|
|
2
|
+
import { getAccount } from "../../api/generated/sdk.gen.js";
|
|
3
|
+
import { PrimitiveApiClient } from "../../api/index.js";
|
|
4
|
+
import { extractErrorPayload, formatErrorPayload } from "../api-command.js";
|
|
5
|
+
// `primitive whoami` is the credentials smoke-test the AGX
|
|
6
|
+
// walkthrough kept asking for. Before this command, a user with a
|
|
7
|
+
// suspect API key had no fast way to verify "is my key live and
|
|
8
|
+
// pointed at the org I expect" short of trying any other call and
|
|
9
|
+
// reading a 401. That ambiguity bit two consecutive walkthroughs.
|
|
10
|
+
//
|
|
11
|
+
// Implementation: thin wrapper over /api/v1/account that prints
|
|
12
|
+
// the account email, plan, id, and onboarding status. Any auth
|
|
13
|
+
// problem surfaces as the standard error envelope, same as the
|
|
14
|
+
// generated commands.
|
|
15
|
+
class WhoamiCommand extends Command {
|
|
16
|
+
static description = `Print the account currently authenticated by the API key. Useful as a credentials smoke test: confirms the key is live and shows which account it belongs to.`;
|
|
17
|
+
static summary = "Print the authenticated account (credentials smoke test)";
|
|
18
|
+
static examples = [
|
|
19
|
+
"<%= config.bin %> whoami",
|
|
20
|
+
"<%= config.bin %> whoami --api-key prim_...",
|
|
21
|
+
];
|
|
22
|
+
static flags = {
|
|
23
|
+
"api-key": Flags.string({
|
|
24
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
25
|
+
env: "PRIMITIVE_API_KEY",
|
|
26
|
+
}),
|
|
27
|
+
"base-url": Flags.string({
|
|
28
|
+
description: "API base URL (defaults to PRIMITIVE_API_URL or production)",
|
|
29
|
+
env: "PRIMITIVE_API_URL",
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const { flags } = await this.parse(WhoamiCommand);
|
|
34
|
+
const apiClient = new PrimitiveApiClient({
|
|
35
|
+
apiKey: flags["api-key"],
|
|
36
|
+
baseUrl: flags["base-url"],
|
|
37
|
+
});
|
|
38
|
+
const result = await getAccount({
|
|
39
|
+
client: apiClient.client,
|
|
40
|
+
responseStyle: "fields",
|
|
41
|
+
});
|
|
42
|
+
if (result.error) {
|
|
43
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
44
|
+
process.stderr.write(`${formatErrorPayload(errorPayload)}\n`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const envelope = result.data;
|
|
49
|
+
const account = envelope?.data;
|
|
50
|
+
if (!account) {
|
|
51
|
+
process.stderr.write("Server returned an empty account body; this should not happen for a valid key.\n");
|
|
52
|
+
throw new Errors.CLIError("unexpected empty response");
|
|
53
|
+
}
|
|
54
|
+
// Concise human-readable summary on stderr; the full account
|
|
55
|
+
// JSON goes to stdout so a script can pipe it.
|
|
56
|
+
const onboarding = account.onboarding_completed === true
|
|
57
|
+
? "complete"
|
|
58
|
+
: account.onboarding_step
|
|
59
|
+
? `in progress (step: ${account.onboarding_step})`
|
|
60
|
+
: "incomplete";
|
|
61
|
+
process.stderr.write(`Authenticated as ${account.email}\n`);
|
|
62
|
+
process.stderr.write(` Account id: ${account.id}\n`);
|
|
63
|
+
process.stderr.write(` Plan: ${account.plan}\n`);
|
|
64
|
+
process.stderr.write(` Onboarding: ${onboarding}\n`);
|
|
65
|
+
this.log(JSON.stringify(account, null, 2));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export default WhoamiCommand;
|
package/dist/oclif/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Args, Command } from "@oclif/core";
|
|
2
2
|
import { operationManifest, } from "../openapi/index.js";
|
|
3
3
|
import { createOperationCommand } from "./api-command.js";
|
|
4
|
+
import SendCommand from "./commands/send.js";
|
|
5
|
+
import WhoamiCommand from "./commands/whoami.js";
|
|
4
6
|
import { renderFishCompletion } from "./fish-completion.js";
|
|
5
7
|
class ListOperationsCommand extends Command {
|
|
6
8
|
static description = "List all generated API operations";
|
|
@@ -38,5 +40,15 @@ const generatedCommands = Object.fromEntries(operationManifest.map((operation) =
|
|
|
38
40
|
export const COMMANDS = {
|
|
39
41
|
completion: CompletionCommand,
|
|
40
42
|
"list-operations": ListOperationsCommand,
|
|
43
|
+
// `send` is the agent-grade shortcut for sending:send-email with
|
|
44
|
+
// sensible defaults (auto from-address, auto subject). The full
|
|
45
|
+
// operation stays available under sending:send-email for callers
|
|
46
|
+
// who want every flag.
|
|
47
|
+
send: SendCommand,
|
|
48
|
+
// `whoami` is the credentials smoke test. Prints the account the
|
|
49
|
+
// current API key authenticates as. AGX walkthroughs kept
|
|
50
|
+
// wanting this before risking a real call against a possibly-
|
|
51
|
+
// bad key.
|
|
52
|
+
whoami: WhoamiCommand,
|
|
41
53
|
...generatedCommands,
|
|
42
54
|
};
|
package/oclif.manifest.json
CHANGED
|
@@ -42,6 +42,136 @@
|
|
|
42
42
|
"summary": "List all generated API operations",
|
|
43
43
|
"enableJsonFlag": false
|
|
44
44
|
},
|
|
45
|
+
"send": {
|
|
46
|
+
"aliases": [],
|
|
47
|
+
"args": {},
|
|
48
|
+
"description": "Send an outbound email. Agent-grade shortcut for sending:send-email with sensible defaults.\n\n --from defaults to agent@<your-first-verified-outbound-domain> when omitted.\n --subject defaults to the first line of the body when omitted.\n\n For the full flag set (custom message-id threading on the wire,\n references arrays, etc.), use `primitive sending:send-email`.",
|
|
49
|
+
"examples": [
|
|
50
|
+
"<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
|
|
51
|
+
"<%= config.bin %> send --to alice@example.com --from support@yourcompany.com --subject 'Quick question' --body 'Are you free Thursday?'",
|
|
52
|
+
"<%= config.bin %> send --to alice@example.com --html '<p>Hello!</p>'",
|
|
53
|
+
"<%= config.bin %> send --to alice@example.com --body 'Confirmed' --wait"
|
|
54
|
+
],
|
|
55
|
+
"flags": {
|
|
56
|
+
"api-key": {
|
|
57
|
+
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
58
|
+
"env": "PRIMITIVE_API_KEY",
|
|
59
|
+
"name": "api-key",
|
|
60
|
+
"hasDynamicHelp": false,
|
|
61
|
+
"multiple": false,
|
|
62
|
+
"type": "option"
|
|
63
|
+
},
|
|
64
|
+
"base-url": {
|
|
65
|
+
"description": "API base URL (defaults to PRIMITIVE_API_URL or production)",
|
|
66
|
+
"env": "PRIMITIVE_API_URL",
|
|
67
|
+
"name": "base-url",
|
|
68
|
+
"hasDynamicHelp": false,
|
|
69
|
+
"multiple": false,
|
|
70
|
+
"type": "option"
|
|
71
|
+
},
|
|
72
|
+
"to": {
|
|
73
|
+
"description": "Recipient address (e.g. alice@example.com).",
|
|
74
|
+
"name": "to",
|
|
75
|
+
"required": true,
|
|
76
|
+
"hasDynamicHelp": false,
|
|
77
|
+
"multiple": false,
|
|
78
|
+
"type": "option"
|
|
79
|
+
},
|
|
80
|
+
"from": {
|
|
81
|
+
"description": "Sender address. Defaults to agent@<your-first-verified-outbound-domain>.",
|
|
82
|
+
"name": "from",
|
|
83
|
+
"hasDynamicHelp": false,
|
|
84
|
+
"multiple": false,
|
|
85
|
+
"type": "option"
|
|
86
|
+
},
|
|
87
|
+
"subject": {
|
|
88
|
+
"description": "Subject line. Defaults to the first line of --body / --html when omitted.",
|
|
89
|
+
"name": "subject",
|
|
90
|
+
"hasDynamicHelp": false,
|
|
91
|
+
"multiple": false,
|
|
92
|
+
"type": "option"
|
|
93
|
+
},
|
|
94
|
+
"body": {
|
|
95
|
+
"description": "Plain-text message body. Either --body or --html (or both) is required.",
|
|
96
|
+
"name": "body",
|
|
97
|
+
"hasDynamicHelp": false,
|
|
98
|
+
"multiple": false,
|
|
99
|
+
"type": "option"
|
|
100
|
+
},
|
|
101
|
+
"html": {
|
|
102
|
+
"description": "HTML message body. Either --body or --html (or both) is required.",
|
|
103
|
+
"name": "html",
|
|
104
|
+
"hasDynamicHelp": false,
|
|
105
|
+
"multiple": false,
|
|
106
|
+
"type": "option"
|
|
107
|
+
},
|
|
108
|
+
"in-reply-to": {
|
|
109
|
+
"description": "Message-Id of the parent email when threading a reply on the wire. For replying to an inbound message you received, prefer `primitive sending:reply-to-email --id <inbound-id>`.",
|
|
110
|
+
"name": "in-reply-to",
|
|
111
|
+
"hasDynamicHelp": false,
|
|
112
|
+
"multiple": false,
|
|
113
|
+
"type": "option"
|
|
114
|
+
},
|
|
115
|
+
"wait": {
|
|
116
|
+
"description": "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the message for delivery.",
|
|
117
|
+
"name": "wait",
|
|
118
|
+
"allowNo": false,
|
|
119
|
+
"type": "boolean"
|
|
120
|
+
},
|
|
121
|
+
"wait-timeout-ms": {
|
|
122
|
+
"description": "Maximum time to wait when --wait is set. Defaults to 30000ms.",
|
|
123
|
+
"name": "wait-timeout-ms",
|
|
124
|
+
"hasDynamicHelp": false,
|
|
125
|
+
"multiple": false,
|
|
126
|
+
"type": "option"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"hasDynamicHelp": false,
|
|
130
|
+
"hiddenAliases": [],
|
|
131
|
+
"id": "send",
|
|
132
|
+
"pluginAlias": "@primitivedotdev/sdk",
|
|
133
|
+
"pluginName": "@primitivedotdev/sdk",
|
|
134
|
+
"pluginType": "core",
|
|
135
|
+
"strict": true,
|
|
136
|
+
"summary": "Send an email (simplified, agent-friendly)",
|
|
137
|
+
"enableJsonFlag": false
|
|
138
|
+
},
|
|
139
|
+
"whoami": {
|
|
140
|
+
"aliases": [],
|
|
141
|
+
"args": {},
|
|
142
|
+
"description": "Print the account currently authenticated by the API key. Useful as a credentials smoke test: confirms the key is live and shows which account it belongs to.",
|
|
143
|
+
"examples": [
|
|
144
|
+
"<%= config.bin %> whoami",
|
|
145
|
+
"<%= config.bin %> whoami --api-key prim_..."
|
|
146
|
+
],
|
|
147
|
+
"flags": {
|
|
148
|
+
"api-key": {
|
|
149
|
+
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
150
|
+
"env": "PRIMITIVE_API_KEY",
|
|
151
|
+
"name": "api-key",
|
|
152
|
+
"hasDynamicHelp": false,
|
|
153
|
+
"multiple": false,
|
|
154
|
+
"type": "option"
|
|
155
|
+
},
|
|
156
|
+
"base-url": {
|
|
157
|
+
"description": "API base URL (defaults to PRIMITIVE_API_URL or production)",
|
|
158
|
+
"env": "PRIMITIVE_API_URL",
|
|
159
|
+
"name": "base-url",
|
|
160
|
+
"hasDynamicHelp": false,
|
|
161
|
+
"multiple": false,
|
|
162
|
+
"type": "option"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"hasDynamicHelp": false,
|
|
166
|
+
"hiddenAliases": [],
|
|
167
|
+
"id": "whoami",
|
|
168
|
+
"pluginAlias": "@primitivedotdev/sdk",
|
|
169
|
+
"pluginName": "@primitivedotdev/sdk",
|
|
170
|
+
"pluginType": "core",
|
|
171
|
+
"strict": true,
|
|
172
|
+
"summary": "Print the authenticated account (credentials smoke test)",
|
|
173
|
+
"enableJsonFlag": false
|
|
174
|
+
},
|
|
45
175
|
"account:get-account": {
|
|
46
176
|
"aliases": [],
|
|
47
177
|
"args": {},
|
|
@@ -173,7 +303,7 @@
|
|
|
173
303
|
"account:update-account": {
|
|
174
304
|
"aliases": [],
|
|
175
305
|
"args": {},
|
|
176
|
-
"description": "PATCH /account
|
|
306
|
+
"description": "PATCH /account",
|
|
177
307
|
"flags": {
|
|
178
308
|
"api-key": {
|
|
179
309
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -191,19 +321,32 @@
|
|
|
191
321
|
"multiple": false,
|
|
192
322
|
"type": "option"
|
|
193
323
|
},
|
|
194
|
-
"body": {
|
|
195
|
-
"description": "
|
|
196
|
-
"name": "body",
|
|
324
|
+
"raw-body": {
|
|
325
|
+
"description": "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available.",
|
|
326
|
+
"name": "raw-body",
|
|
197
327
|
"hasDynamicHelp": false,
|
|
198
328
|
"multiple": false,
|
|
199
329
|
"type": "option"
|
|
200
330
|
},
|
|
201
331
|
"body-file": {
|
|
202
|
-
"description": "Path to a JSON file used as the request body",
|
|
332
|
+
"description": "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
203
333
|
"name": "body-file",
|
|
204
334
|
"hasDynamicHelp": false,
|
|
205
335
|
"multiple": false,
|
|
206
336
|
"type": "option"
|
|
337
|
+
},
|
|
338
|
+
"discard-content-on-webhook-confirmed": {
|
|
339
|
+
"description": "Whether to discard email content after the webhook endpoint confirms receipt.",
|
|
340
|
+
"name": "discard-content-on-webhook-confirmed",
|
|
341
|
+
"allowNo": false,
|
|
342
|
+
"type": "boolean"
|
|
343
|
+
},
|
|
344
|
+
"spam-threshold": {
|
|
345
|
+
"description": "Global spam score threshold (0-15). Emails scoring above this are rejected. S...",
|
|
346
|
+
"name": "spam-threshold",
|
|
347
|
+
"hasDynamicHelp": false,
|
|
348
|
+
"multiple": false,
|
|
349
|
+
"type": "option"
|
|
207
350
|
}
|
|
208
351
|
},
|
|
209
352
|
"hasDynamicHelp": false,
|
|
@@ -219,7 +362,7 @@
|
|
|
219
362
|
"domains:add-domain": {
|
|
220
363
|
"aliases": [],
|
|
221
364
|
"args": {},
|
|
222
|
-
"description": "Creates an unverified domain claim. You will receive a\n`verification_token` to add as a DNS TXT record before\ncalling the verify endpoint.\n
|
|
365
|
+
"description": "Creates an unverified domain claim. You will receive a\n`verification_token` to add as a DNS TXT record before\ncalling the verify endpoint.\n",
|
|
223
366
|
"flags": {
|
|
224
367
|
"api-key": {
|
|
225
368
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -237,19 +380,26 @@
|
|
|
237
380
|
"multiple": false,
|
|
238
381
|
"type": "option"
|
|
239
382
|
},
|
|
240
|
-
"body": {
|
|
241
|
-
"description": "
|
|
242
|
-
"name": "body",
|
|
383
|
+
"raw-body": {
|
|
384
|
+
"description": "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available.",
|
|
385
|
+
"name": "raw-body",
|
|
243
386
|
"hasDynamicHelp": false,
|
|
244
387
|
"multiple": false,
|
|
245
388
|
"type": "option"
|
|
246
389
|
},
|
|
247
390
|
"body-file": {
|
|
248
|
-
"description": "Path to a JSON file used as the request body",
|
|
391
|
+
"description": "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
249
392
|
"name": "body-file",
|
|
250
393
|
"hasDynamicHelp": false,
|
|
251
394
|
"multiple": false,
|
|
252
395
|
"type": "option"
|
|
396
|
+
},
|
|
397
|
+
"domain": {
|
|
398
|
+
"description": "The domain name to claim (e.g. \"example.com\")",
|
|
399
|
+
"name": "domain",
|
|
400
|
+
"hasDynamicHelp": false,
|
|
401
|
+
"multiple": false,
|
|
402
|
+
"type": "option"
|
|
253
403
|
}
|
|
254
404
|
},
|
|
255
405
|
"hasDynamicHelp": false,
|
|
@@ -337,7 +487,7 @@
|
|
|
337
487
|
"domains:update-domain": {
|
|
338
488
|
"aliases": [],
|
|
339
489
|
"args": {},
|
|
340
|
-
"description": "Update a verified domain's settings. Only verified domains can be\nupdated. Per-domain spam thresholds require a Pro plan.\n
|
|
490
|
+
"description": "Update a verified domain's settings. Only verified domains can be\nupdated. Per-domain spam thresholds require a Pro plan.\n",
|
|
341
491
|
"flags": {
|
|
342
492
|
"api-key": {
|
|
343
493
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -363,19 +513,32 @@
|
|
|
363
513
|
"multiple": false,
|
|
364
514
|
"type": "option"
|
|
365
515
|
},
|
|
366
|
-
"body": {
|
|
367
|
-
"description": "
|
|
368
|
-
"name": "body",
|
|
516
|
+
"raw-body": {
|
|
517
|
+
"description": "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available.",
|
|
518
|
+
"name": "raw-body",
|
|
369
519
|
"hasDynamicHelp": false,
|
|
370
520
|
"multiple": false,
|
|
371
521
|
"type": "option"
|
|
372
522
|
},
|
|
373
523
|
"body-file": {
|
|
374
|
-
"description": "Path to a JSON file used as the request body",
|
|
524
|
+
"description": "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
375
525
|
"name": "body-file",
|
|
376
526
|
"hasDynamicHelp": false,
|
|
377
527
|
"multiple": false,
|
|
378
528
|
"type": "option"
|
|
529
|
+
},
|
|
530
|
+
"is-active": {
|
|
531
|
+
"description": "Whether the domain accepts incoming emails",
|
|
532
|
+
"name": "is-active",
|
|
533
|
+
"allowNo": false,
|
|
534
|
+
"type": "boolean"
|
|
535
|
+
},
|
|
536
|
+
"spam-threshold": {
|
|
537
|
+
"description": "Per-domain spam threshold override (Pro plan required)",
|
|
538
|
+
"name": "spam-threshold",
|
|
539
|
+
"hasDynamicHelp": false,
|
|
540
|
+
"multiple": false,
|
|
541
|
+
"type": "option"
|
|
379
542
|
}
|
|
380
543
|
},
|
|
381
544
|
"hasDynamicHelp": false,
|
|
@@ -755,7 +918,7 @@
|
|
|
755
918
|
"endpoints:create-endpoint": {
|
|
756
919
|
"aliases": [],
|
|
757
920
|
"args": {},
|
|
758
|
-
"description": "Creates a new webhook endpoint. If a deactivated endpoint with the\nsame URL and domain exists, it is reactivated instead.\nSubject to plan limits on the number of active endpoints.\n\n\nBody fields
|
|
921
|
+
"description": "Creates a new webhook endpoint. If a deactivated endpoint with the\nsame URL and domain exists, it is reactivated instead.\nSubject to plan limits on the number of active endpoints.\n\n\nBody fields requiring --raw-body JSON (these are not exposed as flags):\n rules object Endpoint-specific filtering rules\n(* = required. Scalar body fields are exposed as individual --flag-name flags; see FLAGS above.)",
|
|
759
922
|
"flags": {
|
|
760
923
|
"api-key": {
|
|
761
924
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -773,19 +936,39 @@
|
|
|
773
936
|
"multiple": false,
|
|
774
937
|
"type": "option"
|
|
775
938
|
},
|
|
776
|
-
"body": {
|
|
777
|
-
"description": "
|
|
778
|
-
"name": "body",
|
|
939
|
+
"raw-body": {
|
|
940
|
+
"description": "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available.",
|
|
941
|
+
"name": "raw-body",
|
|
779
942
|
"hasDynamicHelp": false,
|
|
780
943
|
"multiple": false,
|
|
781
944
|
"type": "option"
|
|
782
945
|
},
|
|
783
946
|
"body-file": {
|
|
784
|
-
"description": "Path to a JSON file used as the request body",
|
|
947
|
+
"description": "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
785
948
|
"name": "body-file",
|
|
786
949
|
"hasDynamicHelp": false,
|
|
787
950
|
"multiple": false,
|
|
788
951
|
"type": "option"
|
|
952
|
+
},
|
|
953
|
+
"url": {
|
|
954
|
+
"description": "The webhook URL to deliver events to",
|
|
955
|
+
"name": "url",
|
|
956
|
+
"hasDynamicHelp": false,
|
|
957
|
+
"multiple": false,
|
|
958
|
+
"type": "option"
|
|
959
|
+
},
|
|
960
|
+
"domain-id": {
|
|
961
|
+
"description": "Restrict to emails from a specific domain",
|
|
962
|
+
"name": "domain-id",
|
|
963
|
+
"hasDynamicHelp": false,
|
|
964
|
+
"multiple": false,
|
|
965
|
+
"type": "option"
|
|
966
|
+
},
|
|
967
|
+
"enabled": {
|
|
968
|
+
"description": "Whether the endpoint is active",
|
|
969
|
+
"name": "enabled",
|
|
970
|
+
"allowNo": false,
|
|
971
|
+
"type": "boolean"
|
|
789
972
|
}
|
|
790
973
|
},
|
|
791
974
|
"hasDynamicHelp": false,
|
|
@@ -913,7 +1096,7 @@
|
|
|
913
1096
|
"endpoints:update-endpoint": {
|
|
914
1097
|
"aliases": [],
|
|
915
1098
|
"args": {},
|
|
916
|
-
"description": "Updates an active webhook endpoint. If the URL is changed, the old\nendpoint is deactivated and a new one is created (or an existing\ndeactivated endpoint with the new URL is reactivated).\n\n\nBody fields
|
|
1099
|
+
"description": "Updates an active webhook endpoint. If the URL is changed, the old\nendpoint is deactivated and a new one is created (or an existing\ndeactivated endpoint with the new URL is reactivated).\n\n\nBody fields requiring --raw-body JSON (these are not exposed as flags):\n rules object\n(* = required. Scalar body fields are exposed as individual --flag-name flags; see FLAGS above.)",
|
|
917
1100
|
"flags": {
|
|
918
1101
|
"api-key": {
|
|
919
1102
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -939,19 +1122,39 @@
|
|
|
939
1122
|
"multiple": false,
|
|
940
1123
|
"type": "option"
|
|
941
1124
|
},
|
|
942
|
-
"body": {
|
|
943
|
-
"description": "
|
|
944
|
-
"name": "body",
|
|
1125
|
+
"raw-body": {
|
|
1126
|
+
"description": "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available.",
|
|
1127
|
+
"name": "raw-body",
|
|
945
1128
|
"hasDynamicHelp": false,
|
|
946
1129
|
"multiple": false,
|
|
947
1130
|
"type": "option"
|
|
948
1131
|
},
|
|
949
1132
|
"body-file": {
|
|
950
|
-
"description": "Path to a JSON file used as the request body",
|
|
1133
|
+
"description": "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
951
1134
|
"name": "body-file",
|
|
952
1135
|
"hasDynamicHelp": false,
|
|
953
1136
|
"multiple": false,
|
|
954
1137
|
"type": "option"
|
|
1138
|
+
},
|
|
1139
|
+
"domain-id": {
|
|
1140
|
+
"description": "domain_id",
|
|
1141
|
+
"name": "domain-id",
|
|
1142
|
+
"hasDynamicHelp": false,
|
|
1143
|
+
"multiple": false,
|
|
1144
|
+
"type": "option"
|
|
1145
|
+
},
|
|
1146
|
+
"enabled": {
|
|
1147
|
+
"description": "enabled",
|
|
1148
|
+
"name": "enabled",
|
|
1149
|
+
"allowNo": false,
|
|
1150
|
+
"type": "boolean"
|
|
1151
|
+
},
|
|
1152
|
+
"url": {
|
|
1153
|
+
"description": "New webhook URL (triggers endpoint rotation)",
|
|
1154
|
+
"name": "url",
|
|
1155
|
+
"hasDynamicHelp": false,
|
|
1156
|
+
"multiple": false,
|
|
1157
|
+
"type": "option"
|
|
955
1158
|
}
|
|
956
1159
|
},
|
|
957
1160
|
"hasDynamicHelp": false,
|
|
@@ -967,7 +1170,7 @@
|
|
|
967
1170
|
"filters:create-filter": {
|
|
968
1171
|
"aliases": [],
|
|
969
1172
|
"args": {},
|
|
970
|
-
"description": "Creates a new whitelist or blocklist filter. Per-domain filters\nrequire a Pro plan. Patterns are stored as lowercase.\n
|
|
1173
|
+
"description": "Creates a new whitelist or blocklist filter. Per-domain filters\nrequire a Pro plan. Patterns are stored as lowercase.\n",
|
|
971
1174
|
"flags": {
|
|
972
1175
|
"api-key": {
|
|
973
1176
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -985,19 +1188,44 @@
|
|
|
985
1188
|
"multiple": false,
|
|
986
1189
|
"type": "option"
|
|
987
1190
|
},
|
|
988
|
-
"body": {
|
|
989
|
-
"description": "
|
|
990
|
-
"name": "body",
|
|
1191
|
+
"raw-body": {
|
|
1192
|
+
"description": "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available.",
|
|
1193
|
+
"name": "raw-body",
|
|
991
1194
|
"hasDynamicHelp": false,
|
|
992
1195
|
"multiple": false,
|
|
993
1196
|
"type": "option"
|
|
994
1197
|
},
|
|
995
1198
|
"body-file": {
|
|
996
|
-
"description": "Path to a JSON file used as the request body",
|
|
1199
|
+
"description": "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
997
1200
|
"name": "body-file",
|
|
998
1201
|
"hasDynamicHelp": false,
|
|
999
1202
|
"multiple": false,
|
|
1000
1203
|
"type": "option"
|
|
1204
|
+
},
|
|
1205
|
+
"pattern": {
|
|
1206
|
+
"description": "Email address or pattern to filter",
|
|
1207
|
+
"name": "pattern",
|
|
1208
|
+
"hasDynamicHelp": false,
|
|
1209
|
+
"multiple": false,
|
|
1210
|
+
"type": "option"
|
|
1211
|
+
},
|
|
1212
|
+
"type": {
|
|
1213
|
+
"description": "type",
|
|
1214
|
+
"name": "type",
|
|
1215
|
+
"hasDynamicHelp": false,
|
|
1216
|
+
"multiple": false,
|
|
1217
|
+
"options": [
|
|
1218
|
+
"whitelist",
|
|
1219
|
+
"blocklist"
|
|
1220
|
+
],
|
|
1221
|
+
"type": "option"
|
|
1222
|
+
},
|
|
1223
|
+
"domain-id": {
|
|
1224
|
+
"description": "Restrict filter to a specific domain (Pro plan required)",
|
|
1225
|
+
"name": "domain-id",
|
|
1226
|
+
"hasDynamicHelp": false,
|
|
1227
|
+
"multiple": false,
|
|
1228
|
+
"type": "option"
|
|
1001
1229
|
}
|
|
1002
1230
|
},
|
|
1003
1231
|
"hasDynamicHelp": false,
|
|
@@ -1085,7 +1313,7 @@
|
|
|
1085
1313
|
"filters:update-filter": {
|
|
1086
1314
|
"aliases": [],
|
|
1087
1315
|
"args": {},
|
|
1088
|
-
"description": "Toggle a filter's enabled state
|
|
1316
|
+
"description": "Toggle a filter's enabled state.",
|
|
1089
1317
|
"flags": {
|
|
1090
1318
|
"api-key": {
|
|
1091
1319
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -1111,19 +1339,25 @@
|
|
|
1111
1339
|
"multiple": false,
|
|
1112
1340
|
"type": "option"
|
|
1113
1341
|
},
|
|
1114
|
-
"body": {
|
|
1115
|
-
"description": "
|
|
1116
|
-
"name": "body",
|
|
1342
|
+
"raw-body": {
|
|
1343
|
+
"description": "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available.",
|
|
1344
|
+
"name": "raw-body",
|
|
1117
1345
|
"hasDynamicHelp": false,
|
|
1118
1346
|
"multiple": false,
|
|
1119
1347
|
"type": "option"
|
|
1120
1348
|
},
|
|
1121
1349
|
"body-file": {
|
|
1122
|
-
"description": "Path to a JSON file used as the request body",
|
|
1350
|
+
"description": "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
1123
1351
|
"name": "body-file",
|
|
1124
1352
|
"hasDynamicHelp": false,
|
|
1125
1353
|
"multiple": false,
|
|
1126
1354
|
"type": "option"
|
|
1355
|
+
},
|
|
1356
|
+
"enabled": {
|
|
1357
|
+
"description": "enabled",
|
|
1358
|
+
"name": "enabled",
|
|
1359
|
+
"allowNo": false,
|
|
1360
|
+
"type": "boolean"
|
|
1127
1361
|
}
|
|
1128
1362
|
},
|
|
1129
1363
|
"hasDynamicHelp": false,
|
|
@@ -1139,7 +1373,7 @@
|
|
|
1139
1373
|
"sending:reply-to-email": {
|
|
1140
1374
|
"aliases": [],
|
|
1141
1375
|
"args": {},
|
|
1142
|
-
"description": "Sends an outbound reply to the inbound email identified by `id`.\nThreading headers (`In-Reply-To`, `References`), recipient\nderivation (Reply-To, then From, then bare sender), and the\n`Re:` subject prefix are all derived server-side from the\nstored inbound row. The request body carries only the message\nbody and optional `wait` flag; passing any header or recipient\noverride is rejected by the schema (`additionalProperties:\nfalse`).\n\nForwards through the same gates as `/send-mail`: the response\nstatus, error envelope, and `idempotent_replay` flag mirror\nthe send-mail contract verbatim.\n
|
|
1376
|
+
"description": "Sends an outbound reply to the inbound email identified by `id`.\nThreading headers (`In-Reply-To`, `References`), recipient\nderivation (Reply-To, then From, then bare sender), and the\n`Re:` subject prefix are all derived server-side from the\nstored inbound row. The request body carries only the message\nbody and optional `wait` flag; passing any header or recipient\noverride is rejected by the schema (`additionalProperties:\nfalse`).\n\nForwards through the same gates as `/send-mail`: the response\nstatus, error envelope, and `idempotent_replay` flag mirror\nthe send-mail contract verbatim.\n",
|
|
1143
1377
|
"flags": {
|
|
1144
1378
|
"api-key": {
|
|
1145
1379
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -1165,19 +1399,46 @@
|
|
|
1165
1399
|
"multiple": false,
|
|
1166
1400
|
"type": "option"
|
|
1167
1401
|
},
|
|
1168
|
-
"body": {
|
|
1169
|
-
"description": "
|
|
1170
|
-
"name": "body",
|
|
1402
|
+
"raw-body": {
|
|
1403
|
+
"description": "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available.",
|
|
1404
|
+
"name": "raw-body",
|
|
1171
1405
|
"hasDynamicHelp": false,
|
|
1172
1406
|
"multiple": false,
|
|
1173
1407
|
"type": "option"
|
|
1174
1408
|
},
|
|
1175
1409
|
"body-file": {
|
|
1176
|
-
"description": "Path to a JSON file used as the request body",
|
|
1410
|
+
"description": "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
1177
1411
|
"name": "body-file",
|
|
1178
1412
|
"hasDynamicHelp": false,
|
|
1179
1413
|
"multiple": false,
|
|
1180
1414
|
"type": "option"
|
|
1415
|
+
},
|
|
1416
|
+
"body-html": {
|
|
1417
|
+
"description": "HTML reply body. At least one of body_text or body_html is required.",
|
|
1418
|
+
"name": "body-html",
|
|
1419
|
+
"hasDynamicHelp": false,
|
|
1420
|
+
"multiple": false,
|
|
1421
|
+
"type": "option"
|
|
1422
|
+
},
|
|
1423
|
+
"body-text": {
|
|
1424
|
+
"description": "Plain-text reply body. At least one of body_text or body_html is required. Th...",
|
|
1425
|
+
"name": "body-text",
|
|
1426
|
+
"hasDynamicHelp": false,
|
|
1427
|
+
"multiple": false,
|
|
1428
|
+
"type": "option"
|
|
1429
|
+
},
|
|
1430
|
+
"from": {
|
|
1431
|
+
"description": "Optional override for the reply's From header. Defaults to",
|
|
1432
|
+
"name": "from",
|
|
1433
|
+
"hasDynamicHelp": false,
|
|
1434
|
+
"multiple": false,
|
|
1435
|
+
"type": "option"
|
|
1436
|
+
},
|
|
1437
|
+
"wait": {
|
|
1438
|
+
"description": "When true, wait for the first downstream SMTP delivery outcome before returni...",
|
|
1439
|
+
"name": "wait",
|
|
1440
|
+
"allowNo": false,
|
|
1441
|
+
"type": "boolean"
|
|
1181
1442
|
}
|
|
1182
1443
|
},
|
|
1183
1444
|
"hasDynamicHelp": false,
|
|
@@ -1193,7 +1454,7 @@
|
|
|
1193
1454
|
"sending:send-email": {
|
|
1194
1455
|
"aliases": [],
|
|
1195
1456
|
"args": {},
|
|
1196
|
-
"description": "Sends an outbound email through Primitive's outbound relay. By default\nthe request returns once the relay accepts the message for delivery.\nSet `wait: true` to wait for the first downstream SMTP delivery outcome.\n\n\nBody fields
|
|
1457
|
+
"description": "Sends an outbound email through Primitive's outbound relay. By default\nthe request returns once the relay accepts the message for delivery.\nSet `wait: true` to wait for the first downstream SMTP delivery outcome.\n\n\nBody fields requiring --raw-body JSON (these are not exposed as flags):\n references array<string> Full ordered message-id chain for the thread.\n(* = required. Scalar body fields are exposed as individual --flag-name flags; see FLAGS above.)",
|
|
1197
1458
|
"flags": {
|
|
1198
1459
|
"api-key": {
|
|
1199
1460
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -1211,19 +1472,74 @@
|
|
|
1211
1472
|
"multiple": false,
|
|
1212
1473
|
"type": "option"
|
|
1213
1474
|
},
|
|
1214
|
-
"body": {
|
|
1215
|
-
"description": "
|
|
1216
|
-
"name": "body",
|
|
1475
|
+
"raw-body": {
|
|
1476
|
+
"description": "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available.",
|
|
1477
|
+
"name": "raw-body",
|
|
1217
1478
|
"hasDynamicHelp": false,
|
|
1218
1479
|
"multiple": false,
|
|
1219
1480
|
"type": "option"
|
|
1220
1481
|
},
|
|
1221
1482
|
"body-file": {
|
|
1222
|
-
"description": "Path to a JSON file used as the request body",
|
|
1483
|
+
"description": "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
1223
1484
|
"name": "body-file",
|
|
1224
1485
|
"hasDynamicHelp": false,
|
|
1225
1486
|
"multiple": false,
|
|
1226
1487
|
"type": "option"
|
|
1488
|
+
},
|
|
1489
|
+
"from": {
|
|
1490
|
+
"description": "RFC 5322 From header. The sender domain must be a verified outbound domain fo...",
|
|
1491
|
+
"name": "from",
|
|
1492
|
+
"hasDynamicHelp": false,
|
|
1493
|
+
"multiple": false,
|
|
1494
|
+
"type": "option"
|
|
1495
|
+
},
|
|
1496
|
+
"subject": {
|
|
1497
|
+
"description": "Subject line for the outbound message",
|
|
1498
|
+
"name": "subject",
|
|
1499
|
+
"hasDynamicHelp": false,
|
|
1500
|
+
"multiple": false,
|
|
1501
|
+
"type": "option"
|
|
1502
|
+
},
|
|
1503
|
+
"to": {
|
|
1504
|
+
"description": "Recipient address. Recipient eligibility depends on your account's outbound e...",
|
|
1505
|
+
"name": "to",
|
|
1506
|
+
"hasDynamicHelp": false,
|
|
1507
|
+
"multiple": false,
|
|
1508
|
+
"type": "option"
|
|
1509
|
+
},
|
|
1510
|
+
"body-html": {
|
|
1511
|
+
"description": "HTML message body. At least one of body_text or body_html is required. The co...",
|
|
1512
|
+
"name": "body-html",
|
|
1513
|
+
"hasDynamicHelp": false,
|
|
1514
|
+
"multiple": false,
|
|
1515
|
+
"type": "option"
|
|
1516
|
+
},
|
|
1517
|
+
"body-text": {
|
|
1518
|
+
"description": "Plain-text message body. At least one of body_text or body_html is required. ...",
|
|
1519
|
+
"name": "body-text",
|
|
1520
|
+
"hasDynamicHelp": false,
|
|
1521
|
+
"multiple": false,
|
|
1522
|
+
"type": "option"
|
|
1523
|
+
},
|
|
1524
|
+
"in-reply-to": {
|
|
1525
|
+
"description": "Message-ID of the direct parent email when sending a threaded reply.",
|
|
1526
|
+
"name": "in-reply-to",
|
|
1527
|
+
"hasDynamicHelp": false,
|
|
1528
|
+
"multiple": false,
|
|
1529
|
+
"type": "option"
|
|
1530
|
+
},
|
|
1531
|
+
"wait": {
|
|
1532
|
+
"description": "When true, wait for the first downstream SMTP delivery outcome before returning.",
|
|
1533
|
+
"name": "wait",
|
|
1534
|
+
"allowNo": false,
|
|
1535
|
+
"type": "boolean"
|
|
1536
|
+
},
|
|
1537
|
+
"wait-timeout-ms": {
|
|
1538
|
+
"description": "Maximum time to wait for a delivery outcome when wait is true. Defaults to 30...",
|
|
1539
|
+
"name": "wait-timeout-ms",
|
|
1540
|
+
"hasDynamicHelp": false,
|
|
1541
|
+
"multiple": false,
|
|
1542
|
+
"type": "option"
|
|
1227
1543
|
}
|
|
1228
1544
|
},
|
|
1229
1545
|
"hasDynamicHelp": false,
|
|
@@ -1363,5 +1679,5 @@
|
|
|
1363
1679
|
"enableJsonFlag": false
|
|
1364
1680
|
}
|
|
1365
1681
|
},
|
|
1366
|
-
"version": "0.
|
|
1682
|
+
"version": "0.12.0"
|
|
1367
1683
|
}
|