@primitivedotdev/sdk 0.10.0 → 0.11.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 +217 -47
- package/dist/oclif/commands/send.js +159 -0
- package/dist/oclif/index.js +6 -0
- package/oclif.manifest.json +308 -28
- 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 `--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; --body for the
|
|
106
|
+
* leftovers below."
|
|
107
|
+
*/
|
|
108
|
+
function renderRequestSchemaSummary(schema) {
|
|
109
|
+
const fields = extractBodyFields(schema);
|
|
110
|
+
if (fields.length === 0)
|
|
70
111
|
return null;
|
|
71
|
-
const
|
|
72
|
-
|
|
112
|
+
const complex = fields.filter((f) => f.kind === "complex");
|
|
113
|
+
if (complex.length === 0)
|
|
114
|
+
return null;
|
|
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 --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) {
|
|
@@ -196,6 +242,44 @@ 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
|
+
// `--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
|
+
const RESERVED_FLAG_NAMES = new Set([
|
|
250
|
+
"api-key",
|
|
251
|
+
"base-url",
|
|
252
|
+
"body",
|
|
253
|
+
"body-file",
|
|
254
|
+
"output",
|
|
255
|
+
]);
|
|
256
|
+
function bodyFieldFlag(field) {
|
|
257
|
+
// Flag descriptions cap at 80 chars so oclif's --help output
|
|
258
|
+
// stays readable; the schema's full description is also visible
|
|
259
|
+
// via `primitive list-operations | jq`.
|
|
260
|
+
const descMax = 80;
|
|
261
|
+
const trimmedDesc = field.description.length > descMax
|
|
262
|
+
? `${field.description.slice(0, descMax - 3)}...`
|
|
263
|
+
: field.description;
|
|
264
|
+
// Field-flag UX choice: do NOT mark scalar body fields as
|
|
265
|
+
// required at the oclif level even when the JSON Schema marks
|
|
266
|
+
// them required. Reason: a caller can satisfy the requirement
|
|
267
|
+
// either via the individual flag OR via --body / --body-file.
|
|
268
|
+
// Marking the flag required would force the individual-flag
|
|
269
|
+
// form. The runtime body merger validates the final assembled
|
|
270
|
+
// body against the same server-side schema either way.
|
|
271
|
+
const common = {
|
|
272
|
+
description: trimmedDesc || field.name,
|
|
273
|
+
};
|
|
274
|
+
if (field.kind === "boolean")
|
|
275
|
+
return Flags.boolean(common);
|
|
276
|
+
if (field.kind === "integer")
|
|
277
|
+
return Flags.integer(common);
|
|
278
|
+
if (field.enumValues) {
|
|
279
|
+
return Flags.string({ ...common, options: field.enumValues });
|
|
280
|
+
}
|
|
281
|
+
return Flags.string(common);
|
|
282
|
+
}
|
|
199
283
|
function buildFlags(operation) {
|
|
200
284
|
const flags = {
|
|
201
285
|
"api-key": Flags.string({
|
|
@@ -210,18 +294,67 @@ function buildFlags(operation) {
|
|
|
210
294
|
for (const parameter of [...operation.pathParams, ...operation.queryParams]) {
|
|
211
295
|
flags[flagName(parameter.name)] = flagForParameter(parameter);
|
|
212
296
|
}
|
|
297
|
+
const bodyFieldFlagToProperty = new Map();
|
|
213
298
|
if (operation.hasJsonBody) {
|
|
214
|
-
flags.body = Flags.string({
|
|
299
|
+
flags.body = Flags.string({
|
|
300
|
+
description: "Full request body as JSON. Prefer per-field flags (e.g. --to, --from, --body-text) when available; --body is the escape hatch for nested or complex fields.",
|
|
301
|
+
});
|
|
215
302
|
flags["body-file"] = Flags.string({
|
|
216
|
-
description: "Path to a JSON file used as the request body",
|
|
303
|
+
description: "Path to a JSON file used as the request body. Same role as --body for callers passing a saved payload.",
|
|
217
304
|
});
|
|
305
|
+
// Expand top-level scalar body fields into individual flags so
|
|
306
|
+
// `primitive sending:send-email --to alice@x --from support@x
|
|
307
|
+
// --body-text "hi"` works without constructing JSON. Driven by
|
|
308
|
+
// the requestSchema embedded on the manifest. Skip flags that
|
|
309
|
+
// collide with reserved names or with path/query params already
|
|
310
|
+
// added above; those collisions fall back to --body.
|
|
311
|
+
//
|
|
312
|
+
// Collisions are tracked in the returned map so the run()
|
|
313
|
+
// handler doesn't misread a path/query param's value as a
|
|
314
|
+
// body-field override. (A naive "look up parsedFlags[name]"
|
|
315
|
+
// pass would happily pick up the path param's value and
|
|
316
|
+
// silently write it into the body.)
|
|
317
|
+
const bodyFields = extractBodyFields(operation.requestSchema);
|
|
318
|
+
for (const field of bodyFields) {
|
|
319
|
+
if (field.kind === "complex")
|
|
320
|
+
continue;
|
|
321
|
+
const name = flagName(field.name);
|
|
322
|
+
if (RESERVED_FLAG_NAMES.has(name))
|
|
323
|
+
continue;
|
|
324
|
+
if (flags[name] !== undefined)
|
|
325
|
+
continue;
|
|
326
|
+
flags[name] = bodyFieldFlag(field);
|
|
327
|
+
bodyFieldFlagToProperty.set(name, field.name);
|
|
328
|
+
}
|
|
218
329
|
}
|
|
219
330
|
if (operation.binaryResponse) {
|
|
220
331
|
flags.output = Flags.string({
|
|
221
332
|
description: "Write binary response bytes to a file",
|
|
222
333
|
});
|
|
223
334
|
}
|
|
224
|
-
return flags;
|
|
335
|
+
return { flags, bodyFieldFlagToProperty };
|
|
336
|
+
}
|
|
337
|
+
// Pull body field values out of the parsed CLI flags. Returns
|
|
338
|
+
// only fields the user actually supplied (omits undefined). Used
|
|
339
|
+
// to override / extend the JSON --body when both forms are
|
|
340
|
+
// present (per-field flags take precedence on key conflicts).
|
|
341
|
+
//
|
|
342
|
+
// The `bodyFieldFlagToProperty` allowlist comes from buildFlags and
|
|
343
|
+
// records ONLY the flags actually registered as body-field flags.
|
|
344
|
+
// Without it, this function would naively read parsedFlags by
|
|
345
|
+
// kebab-cased field name and pick up values from a colliding path
|
|
346
|
+
// or query param flag, silently writing them into the body under
|
|
347
|
+
// the body-field key. The allowlist keeps the merge honest: only
|
|
348
|
+
// flags this CLI generator owns end up in the body.
|
|
349
|
+
function collectBodyFieldFlags(parsedFlags, bodyFieldFlagToProperty) {
|
|
350
|
+
const result = {};
|
|
351
|
+
for (const [flag, property] of bodyFieldFlagToProperty) {
|
|
352
|
+
const value = parsedFlags[flag];
|
|
353
|
+
if (value === undefined)
|
|
354
|
+
continue;
|
|
355
|
+
result[property] = value;
|
|
356
|
+
}
|
|
357
|
+
return result;
|
|
225
358
|
}
|
|
226
359
|
function collectValues(parameters, flags) {
|
|
227
360
|
const values = {};
|
|
@@ -234,7 +367,7 @@ function collectValues(parameters, flags) {
|
|
|
234
367
|
return values;
|
|
235
368
|
}
|
|
236
369
|
export function createOperationCommand(operation) {
|
|
237
|
-
const flags = buildFlags(operation);
|
|
370
|
+
const { flags, bodyFieldFlagToProperty } = buildFlags(operation);
|
|
238
371
|
// Append a "Body fields" summary to the description so agents
|
|
239
372
|
// running `<command> --help` learn the JSON shape immediately.
|
|
240
373
|
// Without this, `--help` only said "JSON request body" and agents
|
|
@@ -262,11 +395,48 @@ export function createOperationCommand(operation) {
|
|
|
262
395
|
? parsedFlags["base-url"]
|
|
263
396
|
: undefined,
|
|
264
397
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
398
|
+
// Two body sources, merged: explicit JSON via --body /
|
|
399
|
+
// --body-file (the base) plus per-field flags (the
|
|
400
|
+
// overrides). Per-field flag values take precedence on key
|
|
401
|
+
// conflicts so a caller can pass a base payload via --body
|
|
402
|
+
// and override one field on the command line.
|
|
403
|
+
let body;
|
|
404
|
+
if (operation.hasJsonBody) {
|
|
405
|
+
const explicit = readJsonBody(parsedFlags);
|
|
406
|
+
const overrides = collectBodyFieldFlags(parsedFlags, bodyFieldFlagToProperty);
|
|
407
|
+
if (Object.keys(overrides).length > 0) {
|
|
408
|
+
if (explicit === undefined) {
|
|
409
|
+
body = overrides;
|
|
410
|
+
}
|
|
411
|
+
else if (explicit !== null &&
|
|
412
|
+
typeof explicit === "object" &&
|
|
413
|
+
!Array.isArray(explicit)) {
|
|
414
|
+
body = { ...explicit, ...overrides };
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
// Caller passed --body as null, an array, or a
|
|
418
|
+
// primitive AND also passed per-field flags. We can't
|
|
419
|
+
// merge per-field overrides into a non-object body
|
|
420
|
+
// shape, and silently dropping either source would
|
|
421
|
+
// leave the caller's actual intent unclear. Refuse
|
|
422
|
+
// loudly so the next attempt is unambiguous.
|
|
423
|
+
const explicitKind = explicit === null
|
|
424
|
+
? "null"
|
|
425
|
+
: Array.isArray(explicit)
|
|
426
|
+
? "array"
|
|
427
|
+
: typeof explicit;
|
|
428
|
+
const overrideFlags = Object.keys(overrides)
|
|
429
|
+
.map((p) => `--${flagName(p)}`)
|
|
430
|
+
.join(", ");
|
|
431
|
+
throw new Errors.CLIError(`--body must be a JSON object when also passing per-field flags (got ${explicitKind}); supplied per-field flags: ${overrideFlags}. Either drop --body and rely on the per-field flags, or move every field into the JSON --body and drop the flags.`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
body = explicit;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
268
438
|
if (operation.bodyRequired && body === undefined) {
|
|
269
|
-
throw new Errors.CLIError(`Operation ${operation.operationId} requires
|
|
439
|
+
throw new Errors.CLIError(`Operation ${operation.operationId} requires a body. Pass each field as a --flag (see --help) or supply JSON via --body / --body-file.`);
|
|
270
440
|
}
|
|
271
441
|
const operationFn = operations[operation.sdkName];
|
|
272
442
|
const result = await operationFn({
|
|
@@ -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;
|
package/dist/oclif/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
4
5
|
import { renderFishCompletion } from "./fish-completion.js";
|
|
5
6
|
class ListOperationsCommand extends Command {
|
|
6
7
|
static description = "List all generated API operations";
|
|
@@ -38,5 +39,10 @@ const generatedCommands = Object.fromEntries(operationManifest.map((operation) =
|
|
|
38
39
|
export const COMMANDS = {
|
|
39
40
|
completion: CompletionCommand,
|
|
40
41
|
"list-operations": ListOperationsCommand,
|
|
42
|
+
// `send` is the agent-grade shortcut for sending:send-email with
|
|
43
|
+
// sensible defaults (auto from-address, auto subject). The full
|
|
44
|
+
// operation stays available under sending:send-email for callers
|
|
45
|
+
// who want every flag.
|
|
46
|
+
send: SendCommand,
|
|
41
47
|
...generatedCommands,
|
|
42
48
|
};
|
package/oclif.manifest.json
CHANGED
|
@@ -42,6 +42,100 @@
|
|
|
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
|
+
},
|
|
45
139
|
"account:get-account": {
|
|
46
140
|
"aliases": [],
|
|
47
141
|
"args": {},
|
|
@@ -173,7 +267,7 @@
|
|
|
173
267
|
"account:update-account": {
|
|
174
268
|
"aliases": [],
|
|
175
269
|
"args": {},
|
|
176
|
-
"description": "PATCH /account
|
|
270
|
+
"description": "PATCH /account",
|
|
177
271
|
"flags": {
|
|
178
272
|
"api-key": {
|
|
179
273
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -192,18 +286,31 @@
|
|
|
192
286
|
"type": "option"
|
|
193
287
|
},
|
|
194
288
|
"body": {
|
|
195
|
-
"description": "
|
|
289
|
+
"description": "Full request body as JSON. Prefer per-field flags (e.g. --to, --from, --body-text) when available; --body is the escape hatch for nested or complex fields.",
|
|
196
290
|
"name": "body",
|
|
197
291
|
"hasDynamicHelp": false,
|
|
198
292
|
"multiple": false,
|
|
199
293
|
"type": "option"
|
|
200
294
|
},
|
|
201
295
|
"body-file": {
|
|
202
|
-
"description": "Path to a JSON file used as the request body",
|
|
296
|
+
"description": "Path to a JSON file used as the request body. Same role as --body for callers passing a saved payload.",
|
|
203
297
|
"name": "body-file",
|
|
204
298
|
"hasDynamicHelp": false,
|
|
205
299
|
"multiple": false,
|
|
206
300
|
"type": "option"
|
|
301
|
+
},
|
|
302
|
+
"discard-content-on-webhook-confirmed": {
|
|
303
|
+
"description": "Whether to discard email content after the webhook endpoint confirms receipt.",
|
|
304
|
+
"name": "discard-content-on-webhook-confirmed",
|
|
305
|
+
"allowNo": false,
|
|
306
|
+
"type": "boolean"
|
|
307
|
+
},
|
|
308
|
+
"spam-threshold": {
|
|
309
|
+
"description": "Global spam score threshold (0-15). Emails scoring above this are rejected. S...",
|
|
310
|
+
"name": "spam-threshold",
|
|
311
|
+
"hasDynamicHelp": false,
|
|
312
|
+
"multiple": false,
|
|
313
|
+
"type": "option"
|
|
207
314
|
}
|
|
208
315
|
},
|
|
209
316
|
"hasDynamicHelp": false,
|
|
@@ -219,7 +326,7 @@
|
|
|
219
326
|
"domains:add-domain": {
|
|
220
327
|
"aliases": [],
|
|
221
328
|
"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
|
|
329
|
+
"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
330
|
"flags": {
|
|
224
331
|
"api-key": {
|
|
225
332
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -238,18 +345,25 @@
|
|
|
238
345
|
"type": "option"
|
|
239
346
|
},
|
|
240
347
|
"body": {
|
|
241
|
-
"description": "
|
|
348
|
+
"description": "Full request body as JSON. Prefer per-field flags (e.g. --to, --from, --body-text) when available; --body is the escape hatch for nested or complex fields.",
|
|
242
349
|
"name": "body",
|
|
243
350
|
"hasDynamicHelp": false,
|
|
244
351
|
"multiple": false,
|
|
245
352
|
"type": "option"
|
|
246
353
|
},
|
|
247
354
|
"body-file": {
|
|
248
|
-
"description": "Path to a JSON file used as the request body",
|
|
355
|
+
"description": "Path to a JSON file used as the request body. Same role as --body for callers passing a saved payload.",
|
|
249
356
|
"name": "body-file",
|
|
250
357
|
"hasDynamicHelp": false,
|
|
251
358
|
"multiple": false,
|
|
252
359
|
"type": "option"
|
|
360
|
+
},
|
|
361
|
+
"domain": {
|
|
362
|
+
"description": "The domain name to claim (e.g. \"example.com\")",
|
|
363
|
+
"name": "domain",
|
|
364
|
+
"hasDynamicHelp": false,
|
|
365
|
+
"multiple": false,
|
|
366
|
+
"type": "option"
|
|
253
367
|
}
|
|
254
368
|
},
|
|
255
369
|
"hasDynamicHelp": false,
|
|
@@ -337,7 +451,7 @@
|
|
|
337
451
|
"domains:update-domain": {
|
|
338
452
|
"aliases": [],
|
|
339
453
|
"args": {},
|
|
340
|
-
"description": "Update a verified domain's settings. Only verified domains can be\nupdated. Per-domain spam thresholds require a Pro plan.\n
|
|
454
|
+
"description": "Update a verified domain's settings. Only verified domains can be\nupdated. Per-domain spam thresholds require a Pro plan.\n",
|
|
341
455
|
"flags": {
|
|
342
456
|
"api-key": {
|
|
343
457
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -364,18 +478,31 @@
|
|
|
364
478
|
"type": "option"
|
|
365
479
|
},
|
|
366
480
|
"body": {
|
|
367
|
-
"description": "
|
|
481
|
+
"description": "Full request body as JSON. Prefer per-field flags (e.g. --to, --from, --body-text) when available; --body is the escape hatch for nested or complex fields.",
|
|
368
482
|
"name": "body",
|
|
369
483
|
"hasDynamicHelp": false,
|
|
370
484
|
"multiple": false,
|
|
371
485
|
"type": "option"
|
|
372
486
|
},
|
|
373
487
|
"body-file": {
|
|
374
|
-
"description": "Path to a JSON file used as the request body",
|
|
488
|
+
"description": "Path to a JSON file used as the request body. Same role as --body for callers passing a saved payload.",
|
|
375
489
|
"name": "body-file",
|
|
376
490
|
"hasDynamicHelp": false,
|
|
377
491
|
"multiple": false,
|
|
378
492
|
"type": "option"
|
|
493
|
+
},
|
|
494
|
+
"is-active": {
|
|
495
|
+
"description": "Whether the domain accepts incoming emails",
|
|
496
|
+
"name": "is-active",
|
|
497
|
+
"allowNo": false,
|
|
498
|
+
"type": "boolean"
|
|
499
|
+
},
|
|
500
|
+
"spam-threshold": {
|
|
501
|
+
"description": "Per-domain spam threshold override (Pro plan required)",
|
|
502
|
+
"name": "spam-threshold",
|
|
503
|
+
"hasDynamicHelp": false,
|
|
504
|
+
"multiple": false,
|
|
505
|
+
"type": "option"
|
|
379
506
|
}
|
|
380
507
|
},
|
|
381
508
|
"hasDynamicHelp": false,
|
|
@@ -755,7 +882,7 @@
|
|
|
755
882
|
"endpoints:create-endpoint": {
|
|
756
883
|
"aliases": [],
|
|
757
884
|
"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
|
|
885
|
+
"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 --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
886
|
"flags": {
|
|
760
887
|
"api-key": {
|
|
761
888
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -774,18 +901,38 @@
|
|
|
774
901
|
"type": "option"
|
|
775
902
|
},
|
|
776
903
|
"body": {
|
|
777
|
-
"description": "
|
|
904
|
+
"description": "Full request body as JSON. Prefer per-field flags (e.g. --to, --from, --body-text) when available; --body is the escape hatch for nested or complex fields.",
|
|
778
905
|
"name": "body",
|
|
779
906
|
"hasDynamicHelp": false,
|
|
780
907
|
"multiple": false,
|
|
781
908
|
"type": "option"
|
|
782
909
|
},
|
|
783
910
|
"body-file": {
|
|
784
|
-
"description": "Path to a JSON file used as the request body",
|
|
911
|
+
"description": "Path to a JSON file used as the request body. Same role as --body for callers passing a saved payload.",
|
|
785
912
|
"name": "body-file",
|
|
786
913
|
"hasDynamicHelp": false,
|
|
787
914
|
"multiple": false,
|
|
788
915
|
"type": "option"
|
|
916
|
+
},
|
|
917
|
+
"url": {
|
|
918
|
+
"description": "The webhook URL to deliver events to",
|
|
919
|
+
"name": "url",
|
|
920
|
+
"hasDynamicHelp": false,
|
|
921
|
+
"multiple": false,
|
|
922
|
+
"type": "option"
|
|
923
|
+
},
|
|
924
|
+
"domain-id": {
|
|
925
|
+
"description": "Restrict to emails from a specific domain",
|
|
926
|
+
"name": "domain-id",
|
|
927
|
+
"hasDynamicHelp": false,
|
|
928
|
+
"multiple": false,
|
|
929
|
+
"type": "option"
|
|
930
|
+
},
|
|
931
|
+
"enabled": {
|
|
932
|
+
"description": "Whether the endpoint is active",
|
|
933
|
+
"name": "enabled",
|
|
934
|
+
"allowNo": false,
|
|
935
|
+
"type": "boolean"
|
|
789
936
|
}
|
|
790
937
|
},
|
|
791
938
|
"hasDynamicHelp": false,
|
|
@@ -913,7 +1060,7 @@
|
|
|
913
1060
|
"endpoints:update-endpoint": {
|
|
914
1061
|
"aliases": [],
|
|
915
1062
|
"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
|
|
1063
|
+
"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 --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
1064
|
"flags": {
|
|
918
1065
|
"api-key": {
|
|
919
1066
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -940,18 +1087,38 @@
|
|
|
940
1087
|
"type": "option"
|
|
941
1088
|
},
|
|
942
1089
|
"body": {
|
|
943
|
-
"description": "
|
|
1090
|
+
"description": "Full request body as JSON. Prefer per-field flags (e.g. --to, --from, --body-text) when available; --body is the escape hatch for nested or complex fields.",
|
|
944
1091
|
"name": "body",
|
|
945
1092
|
"hasDynamicHelp": false,
|
|
946
1093
|
"multiple": false,
|
|
947
1094
|
"type": "option"
|
|
948
1095
|
},
|
|
949
1096
|
"body-file": {
|
|
950
|
-
"description": "Path to a JSON file used as the request body",
|
|
1097
|
+
"description": "Path to a JSON file used as the request body. Same role as --body for callers passing a saved payload.",
|
|
951
1098
|
"name": "body-file",
|
|
952
1099
|
"hasDynamicHelp": false,
|
|
953
1100
|
"multiple": false,
|
|
954
1101
|
"type": "option"
|
|
1102
|
+
},
|
|
1103
|
+
"domain-id": {
|
|
1104
|
+
"description": "domain_id",
|
|
1105
|
+
"name": "domain-id",
|
|
1106
|
+
"hasDynamicHelp": false,
|
|
1107
|
+
"multiple": false,
|
|
1108
|
+
"type": "option"
|
|
1109
|
+
},
|
|
1110
|
+
"enabled": {
|
|
1111
|
+
"description": "enabled",
|
|
1112
|
+
"name": "enabled",
|
|
1113
|
+
"allowNo": false,
|
|
1114
|
+
"type": "boolean"
|
|
1115
|
+
},
|
|
1116
|
+
"url": {
|
|
1117
|
+
"description": "New webhook URL (triggers endpoint rotation)",
|
|
1118
|
+
"name": "url",
|
|
1119
|
+
"hasDynamicHelp": false,
|
|
1120
|
+
"multiple": false,
|
|
1121
|
+
"type": "option"
|
|
955
1122
|
}
|
|
956
1123
|
},
|
|
957
1124
|
"hasDynamicHelp": false,
|
|
@@ -967,7 +1134,7 @@
|
|
|
967
1134
|
"filters:create-filter": {
|
|
968
1135
|
"aliases": [],
|
|
969
1136
|
"args": {},
|
|
970
|
-
"description": "Creates a new whitelist or blocklist filter. Per-domain filters\nrequire a Pro plan. Patterns are stored as lowercase.\n
|
|
1137
|
+
"description": "Creates a new whitelist or blocklist filter. Per-domain filters\nrequire a Pro plan. Patterns are stored as lowercase.\n",
|
|
971
1138
|
"flags": {
|
|
972
1139
|
"api-key": {
|
|
973
1140
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -986,18 +1153,43 @@
|
|
|
986
1153
|
"type": "option"
|
|
987
1154
|
},
|
|
988
1155
|
"body": {
|
|
989
|
-
"description": "
|
|
1156
|
+
"description": "Full request body as JSON. Prefer per-field flags (e.g. --to, --from, --body-text) when available; --body is the escape hatch for nested or complex fields.",
|
|
990
1157
|
"name": "body",
|
|
991
1158
|
"hasDynamicHelp": false,
|
|
992
1159
|
"multiple": false,
|
|
993
1160
|
"type": "option"
|
|
994
1161
|
},
|
|
995
1162
|
"body-file": {
|
|
996
|
-
"description": "Path to a JSON file used as the request body",
|
|
1163
|
+
"description": "Path to a JSON file used as the request body. Same role as --body for callers passing a saved payload.",
|
|
997
1164
|
"name": "body-file",
|
|
998
1165
|
"hasDynamicHelp": false,
|
|
999
1166
|
"multiple": false,
|
|
1000
1167
|
"type": "option"
|
|
1168
|
+
},
|
|
1169
|
+
"pattern": {
|
|
1170
|
+
"description": "Email address or pattern to filter",
|
|
1171
|
+
"name": "pattern",
|
|
1172
|
+
"hasDynamicHelp": false,
|
|
1173
|
+
"multiple": false,
|
|
1174
|
+
"type": "option"
|
|
1175
|
+
},
|
|
1176
|
+
"type": {
|
|
1177
|
+
"description": "type",
|
|
1178
|
+
"name": "type",
|
|
1179
|
+
"hasDynamicHelp": false,
|
|
1180
|
+
"multiple": false,
|
|
1181
|
+
"options": [
|
|
1182
|
+
"whitelist",
|
|
1183
|
+
"blocklist"
|
|
1184
|
+
],
|
|
1185
|
+
"type": "option"
|
|
1186
|
+
},
|
|
1187
|
+
"domain-id": {
|
|
1188
|
+
"description": "Restrict filter to a specific domain (Pro plan required)",
|
|
1189
|
+
"name": "domain-id",
|
|
1190
|
+
"hasDynamicHelp": false,
|
|
1191
|
+
"multiple": false,
|
|
1192
|
+
"type": "option"
|
|
1001
1193
|
}
|
|
1002
1194
|
},
|
|
1003
1195
|
"hasDynamicHelp": false,
|
|
@@ -1085,7 +1277,7 @@
|
|
|
1085
1277
|
"filters:update-filter": {
|
|
1086
1278
|
"aliases": [],
|
|
1087
1279
|
"args": {},
|
|
1088
|
-
"description": "Toggle a filter's enabled state
|
|
1280
|
+
"description": "Toggle a filter's enabled state.",
|
|
1089
1281
|
"flags": {
|
|
1090
1282
|
"api-key": {
|
|
1091
1283
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -1112,18 +1304,24 @@
|
|
|
1112
1304
|
"type": "option"
|
|
1113
1305
|
},
|
|
1114
1306
|
"body": {
|
|
1115
|
-
"description": "
|
|
1307
|
+
"description": "Full request body as JSON. Prefer per-field flags (e.g. --to, --from, --body-text) when available; --body is the escape hatch for nested or complex fields.",
|
|
1116
1308
|
"name": "body",
|
|
1117
1309
|
"hasDynamicHelp": false,
|
|
1118
1310
|
"multiple": false,
|
|
1119
1311
|
"type": "option"
|
|
1120
1312
|
},
|
|
1121
1313
|
"body-file": {
|
|
1122
|
-
"description": "Path to a JSON file used as the request body",
|
|
1314
|
+
"description": "Path to a JSON file used as the request body. Same role as --body for callers passing a saved payload.",
|
|
1123
1315
|
"name": "body-file",
|
|
1124
1316
|
"hasDynamicHelp": false,
|
|
1125
1317
|
"multiple": false,
|
|
1126
1318
|
"type": "option"
|
|
1319
|
+
},
|
|
1320
|
+
"enabled": {
|
|
1321
|
+
"description": "enabled",
|
|
1322
|
+
"name": "enabled",
|
|
1323
|
+
"allowNo": false,
|
|
1324
|
+
"type": "boolean"
|
|
1127
1325
|
}
|
|
1128
1326
|
},
|
|
1129
1327
|
"hasDynamicHelp": false,
|
|
@@ -1139,7 +1337,7 @@
|
|
|
1139
1337
|
"sending:reply-to-email": {
|
|
1140
1338
|
"aliases": [],
|
|
1141
1339
|
"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
|
|
1340
|
+
"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
1341
|
"flags": {
|
|
1144
1342
|
"api-key": {
|
|
1145
1343
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -1166,18 +1364,45 @@
|
|
|
1166
1364
|
"type": "option"
|
|
1167
1365
|
},
|
|
1168
1366
|
"body": {
|
|
1169
|
-
"description": "
|
|
1367
|
+
"description": "Full request body as JSON. Prefer per-field flags (e.g. --to, --from, --body-text) when available; --body is the escape hatch for nested or complex fields.",
|
|
1170
1368
|
"name": "body",
|
|
1171
1369
|
"hasDynamicHelp": false,
|
|
1172
1370
|
"multiple": false,
|
|
1173
1371
|
"type": "option"
|
|
1174
1372
|
},
|
|
1175
1373
|
"body-file": {
|
|
1176
|
-
"description": "Path to a JSON file used as the request body",
|
|
1374
|
+
"description": "Path to a JSON file used as the request body. Same role as --body for callers passing a saved payload.",
|
|
1177
1375
|
"name": "body-file",
|
|
1178
1376
|
"hasDynamicHelp": false,
|
|
1179
1377
|
"multiple": false,
|
|
1180
1378
|
"type": "option"
|
|
1379
|
+
},
|
|
1380
|
+
"body-html": {
|
|
1381
|
+
"description": "HTML reply body. At least one of body_text or body_html is required.",
|
|
1382
|
+
"name": "body-html",
|
|
1383
|
+
"hasDynamicHelp": false,
|
|
1384
|
+
"multiple": false,
|
|
1385
|
+
"type": "option"
|
|
1386
|
+
},
|
|
1387
|
+
"body-text": {
|
|
1388
|
+
"description": "Plain-text reply body. At least one of body_text or body_html is required. Th...",
|
|
1389
|
+
"name": "body-text",
|
|
1390
|
+
"hasDynamicHelp": false,
|
|
1391
|
+
"multiple": false,
|
|
1392
|
+
"type": "option"
|
|
1393
|
+
},
|
|
1394
|
+
"from": {
|
|
1395
|
+
"description": "Optional override for the reply's From header. Defaults to",
|
|
1396
|
+
"name": "from",
|
|
1397
|
+
"hasDynamicHelp": false,
|
|
1398
|
+
"multiple": false,
|
|
1399
|
+
"type": "option"
|
|
1400
|
+
},
|
|
1401
|
+
"wait": {
|
|
1402
|
+
"description": "When true, wait for the first downstream SMTP delivery outcome before returni...",
|
|
1403
|
+
"name": "wait",
|
|
1404
|
+
"allowNo": false,
|
|
1405
|
+
"type": "boolean"
|
|
1181
1406
|
}
|
|
1182
1407
|
},
|
|
1183
1408
|
"hasDynamicHelp": false,
|
|
@@ -1193,7 +1418,7 @@
|
|
|
1193
1418
|
"sending:send-email": {
|
|
1194
1419
|
"aliases": [],
|
|
1195
1420
|
"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
|
|
1421
|
+
"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 --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
1422
|
"flags": {
|
|
1198
1423
|
"api-key": {
|
|
1199
1424
|
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
|
|
@@ -1212,18 +1437,73 @@
|
|
|
1212
1437
|
"type": "option"
|
|
1213
1438
|
},
|
|
1214
1439
|
"body": {
|
|
1215
|
-
"description": "
|
|
1440
|
+
"description": "Full request body as JSON. Prefer per-field flags (e.g. --to, --from, --body-text) when available; --body is the escape hatch for nested or complex fields.",
|
|
1216
1441
|
"name": "body",
|
|
1217
1442
|
"hasDynamicHelp": false,
|
|
1218
1443
|
"multiple": false,
|
|
1219
1444
|
"type": "option"
|
|
1220
1445
|
},
|
|
1221
1446
|
"body-file": {
|
|
1222
|
-
"description": "Path to a JSON file used as the request body",
|
|
1447
|
+
"description": "Path to a JSON file used as the request body. Same role as --body for callers passing a saved payload.",
|
|
1223
1448
|
"name": "body-file",
|
|
1224
1449
|
"hasDynamicHelp": false,
|
|
1225
1450
|
"multiple": false,
|
|
1226
1451
|
"type": "option"
|
|
1452
|
+
},
|
|
1453
|
+
"from": {
|
|
1454
|
+
"description": "RFC 5322 From header. The sender domain must be a verified outbound domain fo...",
|
|
1455
|
+
"name": "from",
|
|
1456
|
+
"hasDynamicHelp": false,
|
|
1457
|
+
"multiple": false,
|
|
1458
|
+
"type": "option"
|
|
1459
|
+
},
|
|
1460
|
+
"subject": {
|
|
1461
|
+
"description": "Subject line for the outbound message",
|
|
1462
|
+
"name": "subject",
|
|
1463
|
+
"hasDynamicHelp": false,
|
|
1464
|
+
"multiple": false,
|
|
1465
|
+
"type": "option"
|
|
1466
|
+
},
|
|
1467
|
+
"to": {
|
|
1468
|
+
"description": "Recipient address. Recipient eligibility depends on your account's outbound e...",
|
|
1469
|
+
"name": "to",
|
|
1470
|
+
"hasDynamicHelp": false,
|
|
1471
|
+
"multiple": false,
|
|
1472
|
+
"type": "option"
|
|
1473
|
+
},
|
|
1474
|
+
"body-html": {
|
|
1475
|
+
"description": "HTML message body. At least one of body_text or body_html is required. The co...",
|
|
1476
|
+
"name": "body-html",
|
|
1477
|
+
"hasDynamicHelp": false,
|
|
1478
|
+
"multiple": false,
|
|
1479
|
+
"type": "option"
|
|
1480
|
+
},
|
|
1481
|
+
"body-text": {
|
|
1482
|
+
"description": "Plain-text message body. At least one of body_text or body_html is required. ...",
|
|
1483
|
+
"name": "body-text",
|
|
1484
|
+
"hasDynamicHelp": false,
|
|
1485
|
+
"multiple": false,
|
|
1486
|
+
"type": "option"
|
|
1487
|
+
},
|
|
1488
|
+
"in-reply-to": {
|
|
1489
|
+
"description": "Message-ID of the direct parent email when sending a threaded reply.",
|
|
1490
|
+
"name": "in-reply-to",
|
|
1491
|
+
"hasDynamicHelp": false,
|
|
1492
|
+
"multiple": false,
|
|
1493
|
+
"type": "option"
|
|
1494
|
+
},
|
|
1495
|
+
"wait": {
|
|
1496
|
+
"description": "When true, wait for the first downstream SMTP delivery outcome before returning.",
|
|
1497
|
+
"name": "wait",
|
|
1498
|
+
"allowNo": false,
|
|
1499
|
+
"type": "boolean"
|
|
1500
|
+
},
|
|
1501
|
+
"wait-timeout-ms": {
|
|
1502
|
+
"description": "Maximum time to wait for a delivery outcome when wait is true. Defaults to 30...",
|
|
1503
|
+
"name": "wait-timeout-ms",
|
|
1504
|
+
"hasDynamicHelp": false,
|
|
1505
|
+
"multiple": false,
|
|
1506
|
+
"type": "option"
|
|
1227
1507
|
}
|
|
1228
1508
|
},
|
|
1229
1509
|
"hasDynamicHelp": false,
|
|
@@ -1363,5 +1643,5 @@
|
|
|
1363
1643
|
"enableJsonFlag": false
|
|
1364
1644
|
}
|
|
1365
1645
|
},
|
|
1366
|
-
"version": "0.
|
|
1646
|
+
"version": "0.11.0"
|
|
1367
1647
|
}
|