@primitivedotdev/cli 0.26.0 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/oclif/commands/functions-test-function.js +238 -0
- package/dist/oclif/index.js +19 -1
- package/oclif.manifest.json +98 -78
- package/package.json +2 -2
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Command, Flags } from "@oclif/core";
|
|
2
|
+
import { getEmail, PrimitiveApiClient, testFunction, } from "@primitivedotdev/sdk/api";
|
|
3
|
+
import { API_BASE_URL_1_FLAG_DESCRIPTION, API_BASE_URL_2_FLAG_DESCRIPTION, baseUrlOverriddenFromFlags, extractErrorPayload, removeStaleSavedCredentialOnUnauthorized, runWithTiming, TIME_FLAG_DESCRIPTION, writeErrorWithHints, } from "../api-command.js";
|
|
4
|
+
import { resolveCliAuth } from "../auth.js";
|
|
5
|
+
import { DEFAULT_EMAIL_POLL_INTERVAL_SECONDS, fetchEmailSearchPage, sleep, } from "./emails-poll.js";
|
|
6
|
+
// `primitive functions:test-function` is the agent-grade shortcut for
|
|
7
|
+
// triggering a real round-trip and (optionally) waiting for the
|
|
8
|
+
// function to actually run before exiting. The underlying
|
|
9
|
+
// `POST /functions/{id}/test` operation only kicks off a synthetic
|
|
10
|
+
// inbound through MX and returns the queued send id; AGX walkthroughs
|
|
11
|
+
// flagged the missing wait-and-show-sends step as the single biggest
|
|
12
|
+
// time-sink in the verification loop.
|
|
13
|
+
//
|
|
14
|
+
// Shapes:
|
|
15
|
+
// primitive functions:test-function --id <fn-id>
|
|
16
|
+
// Fire-and-forget. Returns the TestInvocationResult JSON
|
|
17
|
+
// (recipient, poll_since, watch_url). Same behavior as the
|
|
18
|
+
// auto-generated functions:test-function it replaces.
|
|
19
|
+
//
|
|
20
|
+
// primitive functions:test-function --id <fn-id> --wait
|
|
21
|
+
// Blocks until the test inbound has arrived AND the function's
|
|
22
|
+
// webhook has fired (or --timeout elapses). Exits non-zero on
|
|
23
|
+
// timeout or on exhausted retries.
|
|
24
|
+
//
|
|
25
|
+
// primitive functions:test-function --id <fn-id> --wait --show-sends
|
|
26
|
+
// Same as --wait, plus prints the inbound's `replies` array
|
|
27
|
+
// (every outbound the function emitted while processing the
|
|
28
|
+
// test inbound), with each send's id, status, recipient,
|
|
29
|
+
// subject, and queue id.
|
|
30
|
+
//
|
|
31
|
+
// The auto-generated functions:test-function entry is filtered out
|
|
32
|
+
// of the generated-command set in oclif/index.ts so this hand-rolled
|
|
33
|
+
// version owns the id.
|
|
34
|
+
const DEFAULT_WAIT_TIMEOUT_SECONDS = 60;
|
|
35
|
+
// Terminal states from the EmailWebhookStatus enum. `fired` means the
|
|
36
|
+
// function returned 2xx; `exhausted` means all retries are spent and
|
|
37
|
+
// the delivery is permanently failed. `pending` / `in_flight` /
|
|
38
|
+
// `failed` are intermediate (`failed` is a temporary failure that may
|
|
39
|
+
// retry into `fired` or eventually `exhausted`), so we keep polling.
|
|
40
|
+
const TERMINAL_WEBHOOK_STATUSES = new Set(["fired", "exhausted"]);
|
|
41
|
+
class FunctionsTestFunctionCommand extends Command {
|
|
42
|
+
static description = "Send a real test email through MX to trigger this function. With --wait, blocks until the function has processed the inbound; with --show-sends, also prints any outbound sends the function emitted in response.";
|
|
43
|
+
static summary = "Trigger a test invocation; with --wait, watch it land";
|
|
44
|
+
static examples = [
|
|
45
|
+
"<%= config.bin %> functions:test-function --id <fn-id>",
|
|
46
|
+
"<%= config.bin %> functions:test-function --id <fn-id> --local-part summarize",
|
|
47
|
+
"<%= config.bin %> functions:test-function --id <fn-id> --wait --show-sends",
|
|
48
|
+
"<%= config.bin %> functions:test-function --id <fn-id> --local-part summarize --wait --timeout 120",
|
|
49
|
+
];
|
|
50
|
+
static flags = {
|
|
51
|
+
"api-key": Flags.string({
|
|
52
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
53
|
+
env: "PRIMITIVE_API_KEY",
|
|
54
|
+
}),
|
|
55
|
+
"api-base-url-1": Flags.string({
|
|
56
|
+
description: API_BASE_URL_1_FLAG_DESCRIPTION,
|
|
57
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
58
|
+
hidden: true,
|
|
59
|
+
}),
|
|
60
|
+
"api-base-url-2": Flags.string({
|
|
61
|
+
description: API_BASE_URL_2_FLAG_DESCRIPTION,
|
|
62
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
63
|
+
hidden: true,
|
|
64
|
+
}),
|
|
65
|
+
id: Flags.string({
|
|
66
|
+
description: "Function id (UUID).",
|
|
67
|
+
required: true,
|
|
68
|
+
}),
|
|
69
|
+
"local-part": Flags.string({
|
|
70
|
+
description: "Override the synthetic local-part the test inbound is addressed to. Otherwise the runtime picks `__primitive_function_test+<random>`.",
|
|
71
|
+
}),
|
|
72
|
+
wait: Flags.boolean({
|
|
73
|
+
description: "Block until the function has processed the test inbound (webhook status is `fired` or `exhausted`) or --timeout elapses. Exits non-zero on timeout or on exhausted retries.",
|
|
74
|
+
}),
|
|
75
|
+
"show-sends": Flags.boolean({
|
|
76
|
+
description: "When the wait resolves, also print the outbound emails the function emitted while processing the test inbound (id, status, to, subject). Implies --wait.",
|
|
77
|
+
}),
|
|
78
|
+
timeout: Flags.integer({
|
|
79
|
+
default: DEFAULT_WAIT_TIMEOUT_SECONDS,
|
|
80
|
+
description: "Seconds to wait before exiting non-zero when --wait is set; 0 waits forever.",
|
|
81
|
+
min: 0,
|
|
82
|
+
}),
|
|
83
|
+
"poll-interval": Flags.integer({
|
|
84
|
+
default: DEFAULT_EMAIL_POLL_INTERVAL_SECONDS,
|
|
85
|
+
description: "Seconds between polls while waiting.",
|
|
86
|
+
min: 1,
|
|
87
|
+
}),
|
|
88
|
+
time: Flags.boolean({
|
|
89
|
+
description: TIME_FLAG_DESCRIPTION,
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
async run() {
|
|
93
|
+
const { flags } = await this.parse(FunctionsTestFunctionCommand);
|
|
94
|
+
// --show-sends implies --wait. You can't print what was sent
|
|
95
|
+
// until the function has actually run.
|
|
96
|
+
const shouldWait = flags.wait || flags["show-sends"];
|
|
97
|
+
const shouldShowSends = flags["show-sends"];
|
|
98
|
+
const baseUrlOverridden = baseUrlOverriddenFromFlags(flags);
|
|
99
|
+
const auth = resolveCliAuth({
|
|
100
|
+
apiKey: flags["api-key"],
|
|
101
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
102
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
103
|
+
configDir: this.config.configDir,
|
|
104
|
+
});
|
|
105
|
+
const apiClient = new PrimitiveApiClient({
|
|
106
|
+
apiKey: auth.apiKey,
|
|
107
|
+
apiBaseUrl1: auth.apiBaseUrl1,
|
|
108
|
+
apiBaseUrl2: auth.apiBaseUrl2,
|
|
109
|
+
});
|
|
110
|
+
await runWithTiming(flags.time, async () => {
|
|
111
|
+
// 1. Trigger the test send.
|
|
112
|
+
const triggerResult = await testFunction({
|
|
113
|
+
client: apiClient.client,
|
|
114
|
+
path: { id: flags.id },
|
|
115
|
+
body: flags["local-part"]
|
|
116
|
+
? { local_part: flags["local-part"] }
|
|
117
|
+
: undefined,
|
|
118
|
+
responseStyle: "fields",
|
|
119
|
+
});
|
|
120
|
+
if (triggerResult.error) {
|
|
121
|
+
const payload = extractErrorPayload(triggerResult.error);
|
|
122
|
+
writeErrorWithHints(payload);
|
|
123
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
124
|
+
auth,
|
|
125
|
+
baseUrlOverridden,
|
|
126
|
+
configDir: this.config.configDir,
|
|
127
|
+
payload,
|
|
128
|
+
});
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const invocation = triggerResult.data
|
|
133
|
+
.data;
|
|
134
|
+
if (!shouldWait) {
|
|
135
|
+
// Fire-and-forget path: print the TestInvocationResult JSON
|
|
136
|
+
// unchanged. Same shape the auto-generated command emitted.
|
|
137
|
+
this.log(JSON.stringify(invocation, null, 2));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const startedAt = Date.now();
|
|
141
|
+
const timeoutMs = flags.timeout * 1000;
|
|
142
|
+
const pollIntervalMs = flags["poll-interval"] * 1000;
|
|
143
|
+
const isExpired = () => flags.timeout > 0 && Date.now() - startedAt > timeoutMs;
|
|
144
|
+
// 2. Wait for the test inbound to arrive. The synthetic
|
|
145
|
+
// recipient is unique per call (random suffix in the local-part
|
|
146
|
+
// unless --local-part overrides), so `to` + `since` uniquely
|
|
147
|
+
// identifies the test inbound row.
|
|
148
|
+
this.log(`Waiting for test inbound to arrive at ${invocation.to}...`);
|
|
149
|
+
let inboundId;
|
|
150
|
+
while (!isExpired()) {
|
|
151
|
+
const page = await fetchEmailSearchPage({
|
|
152
|
+
apiClient,
|
|
153
|
+
filters: { to: invocation.to },
|
|
154
|
+
pageSize: 25,
|
|
155
|
+
since: invocation.poll_since,
|
|
156
|
+
});
|
|
157
|
+
if (!page.ok) {
|
|
158
|
+
const payload = extractErrorPayload(page.error);
|
|
159
|
+
writeErrorWithHints(payload);
|
|
160
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
161
|
+
auth,
|
|
162
|
+
baseUrlOverridden,
|
|
163
|
+
configDir: this.config.configDir,
|
|
164
|
+
payload,
|
|
165
|
+
});
|
|
166
|
+
process.exitCode = 1;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const found = page.rows[0];
|
|
170
|
+
if (found) {
|
|
171
|
+
inboundId = found.id;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
await sleep(pollIntervalMs);
|
|
175
|
+
}
|
|
176
|
+
if (!inboundId) {
|
|
177
|
+
this.error(`Timed out after ${flags.timeout}s waiting for test inbound ${invocation.to} to land. Browse ${invocation.watch_url} for the live view.`, { exit: 2 });
|
|
178
|
+
}
|
|
179
|
+
// 3. Wait for the function (webhook) to actually run. We poll
|
|
180
|
+
// the email-detail endpoint because it already carries both the
|
|
181
|
+
// webhook_status terminal state and the `replies` array we'll
|
|
182
|
+
// print under --show-sends. No second endpoint needed.
|
|
183
|
+
this.log(`Inbound landed (${inboundId}). Waiting for function to run...`);
|
|
184
|
+
let detail;
|
|
185
|
+
while (!isExpired()) {
|
|
186
|
+
const result = await getEmail({
|
|
187
|
+
client: apiClient.client,
|
|
188
|
+
path: { id: inboundId },
|
|
189
|
+
responseStyle: "fields",
|
|
190
|
+
});
|
|
191
|
+
if (result.error) {
|
|
192
|
+
const payload = extractErrorPayload(result.error);
|
|
193
|
+
writeErrorWithHints(payload);
|
|
194
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
195
|
+
auth,
|
|
196
|
+
baseUrlOverridden,
|
|
197
|
+
configDir: this.config.configDir,
|
|
198
|
+
payload,
|
|
199
|
+
});
|
|
200
|
+
process.exitCode = 1;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const fetched = result.data.data;
|
|
204
|
+
if (fetched.webhook_status &&
|
|
205
|
+
TERMINAL_WEBHOOK_STATUSES.has(fetched.webhook_status)) {
|
|
206
|
+
detail = fetched;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
await sleep(pollIntervalMs);
|
|
210
|
+
}
|
|
211
|
+
if (!detail) {
|
|
212
|
+
this.error(`Timed out after ${flags.timeout}s waiting for function webhook to fire for inbound ${inboundId}. Browse ${invocation.watch_url} for the live view.`, { exit: 2 });
|
|
213
|
+
}
|
|
214
|
+
// 4. Emit the outcome.
|
|
215
|
+
const elapsedSeconds = Math.round((Date.now() - startedAt) / 1000);
|
|
216
|
+
const outcome = {
|
|
217
|
+
function_id: flags.id,
|
|
218
|
+
inbound_id: inboundId,
|
|
219
|
+
inbound_to: invocation.to,
|
|
220
|
+
webhook_status: detail.webhook_status,
|
|
221
|
+
webhook_attempt_count: detail.webhook_attempt_count,
|
|
222
|
+
webhook_last_status_code: detail.webhook_last_status_code,
|
|
223
|
+
webhook_last_error: detail.webhook_last_error,
|
|
224
|
+
elapsed_seconds: elapsedSeconds,
|
|
225
|
+
};
|
|
226
|
+
if (shouldShowSends) {
|
|
227
|
+
outcome.sent_emails = detail.replies;
|
|
228
|
+
}
|
|
229
|
+
this.log(JSON.stringify(outcome, null, 2));
|
|
230
|
+
// Exit non-zero when the function failed permanently so CI
|
|
231
|
+
// scripts can gate on the exit code.
|
|
232
|
+
if (detail.webhook_status === "exhausted") {
|
|
233
|
+
process.exitCode = 1;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export default FunctionsTestFunctionCommand;
|
package/dist/oclif/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import FunctionsDeployCommand from "./commands/functions-deploy.js";
|
|
|
9
9
|
import FunctionsInitCommand from "./commands/functions-init.js";
|
|
10
10
|
import FunctionsRedeployCommand from "./commands/functions-redeploy.js";
|
|
11
11
|
import FunctionsSetSecretCommand from "./commands/functions-set-secret.js";
|
|
12
|
+
import FunctionsTestFunctionCommand from "./commands/functions-test-function.js";
|
|
12
13
|
import LoginCommand from "./commands/login.js";
|
|
13
14
|
import LogoutCommand from "./commands/logout.js";
|
|
14
15
|
import SendCommand from "./commands/send.js";
|
|
@@ -108,7 +109,17 @@ class CompletionCommand extends Command {
|
|
|
108
109
|
function commandId(operation) {
|
|
109
110
|
return `${operation.tagCommand}:${operation.command}`;
|
|
110
111
|
}
|
|
111
|
-
|
|
112
|
+
// Operation ids whose surface is owned by a hand-rolled command in
|
|
113
|
+
// COMMANDS below. The auto-generated wrapper is filtered out so the
|
|
114
|
+
// hand-rolled command owns the id without a name collision.
|
|
115
|
+
const OVERRIDDEN_OPERATION_IDS = new Set([
|
|
116
|
+
// `functions:test-function` is hand-rolled to add --wait, --show-sends,
|
|
117
|
+
// and --timeout flags on top of the auto-generated POST /functions/{id}/test.
|
|
118
|
+
"functions:test-function",
|
|
119
|
+
]);
|
|
120
|
+
const generatedCommands = Object.fromEntries(operationManifest
|
|
121
|
+
.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(commandId(operation)))
|
|
122
|
+
.map((operation) => [
|
|
112
123
|
commandId(operation),
|
|
113
124
|
createOperationCommand(operation),
|
|
114
125
|
]));
|
|
@@ -171,5 +182,12 @@ export const COMMANDS = {
|
|
|
171
182
|
// visible to the running handler requires a separate redeploy,
|
|
172
183
|
// which this shortcut folds in via --redeploy.
|
|
173
184
|
"functions:set-secret": FunctionsSetSecretCommand,
|
|
185
|
+
// `functions:test-function` is hand-rolled to add --wait, --show-sends,
|
|
186
|
+
// and --timeout on top of POST /functions/{id}/test. Without those
|
|
187
|
+
// flags, agents had to manually thread queued-send + emails:wait +
|
|
188
|
+
// emails:get-email + sending:list-sent-emails to verify a function
|
|
189
|
+
// ran and see what it emitted; AGX walkthroughs flagged that loop as
|
|
190
|
+
// the single biggest verification time-sink.
|
|
191
|
+
"functions:test-function": FunctionsTestFunctionCommand,
|
|
174
192
|
...generatedCommands,
|
|
175
193
|
};
|
package/oclif.manifest.json
CHANGED
|
@@ -1036,6 +1036,103 @@
|
|
|
1036
1036
|
"summary": "Write a function secret (optionally redeploying to push it live)",
|
|
1037
1037
|
"enableJsonFlag": false
|
|
1038
1038
|
},
|
|
1039
|
+
"functions:test-function": {
|
|
1040
|
+
"aliases": [],
|
|
1041
|
+
"args": {},
|
|
1042
|
+
"description": "Send a real test email through MX to trigger this function. With --wait, blocks until the function has processed the inbound; with --show-sends, also prints any outbound sends the function emitted in response.",
|
|
1043
|
+
"examples": [
|
|
1044
|
+
"<%= config.bin %> functions:test-function --id <fn-id>",
|
|
1045
|
+
"<%= config.bin %> functions:test-function --id <fn-id> --local-part summarize",
|
|
1046
|
+
"<%= config.bin %> functions:test-function --id <fn-id> --wait --show-sends",
|
|
1047
|
+
"<%= config.bin %> functions:test-function --id <fn-id> --local-part summarize --wait --timeout 120"
|
|
1048
|
+
],
|
|
1049
|
+
"flags": {
|
|
1050
|
+
"api-key": {
|
|
1051
|
+
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
1052
|
+
"env": "PRIMITIVE_API_KEY",
|
|
1053
|
+
"name": "api-key",
|
|
1054
|
+
"hasDynamicHelp": false,
|
|
1055
|
+
"multiple": false,
|
|
1056
|
+
"type": "option"
|
|
1057
|
+
},
|
|
1058
|
+
"api-base-url-1": {
|
|
1059
|
+
"description": "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
1060
|
+
"env": "PRIMITIVE_API_BASE_URL_1",
|
|
1061
|
+
"hidden": true,
|
|
1062
|
+
"name": "api-base-url-1",
|
|
1063
|
+
"hasDynamicHelp": false,
|
|
1064
|
+
"multiple": false,
|
|
1065
|
+
"type": "option"
|
|
1066
|
+
},
|
|
1067
|
+
"api-base-url-2": {
|
|
1068
|
+
"description": "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
1069
|
+
"env": "PRIMITIVE_API_BASE_URL_2",
|
|
1070
|
+
"hidden": true,
|
|
1071
|
+
"name": "api-base-url-2",
|
|
1072
|
+
"hasDynamicHelp": false,
|
|
1073
|
+
"multiple": false,
|
|
1074
|
+
"type": "option"
|
|
1075
|
+
},
|
|
1076
|
+
"id": {
|
|
1077
|
+
"description": "Function id (UUID).",
|
|
1078
|
+
"name": "id",
|
|
1079
|
+
"required": true,
|
|
1080
|
+
"hasDynamicHelp": false,
|
|
1081
|
+
"multiple": false,
|
|
1082
|
+
"type": "option"
|
|
1083
|
+
},
|
|
1084
|
+
"local-part": {
|
|
1085
|
+
"description": "Override the synthetic local-part the test inbound is addressed to. Otherwise the runtime picks `__primitive_function_test+<random>`.",
|
|
1086
|
+
"name": "local-part",
|
|
1087
|
+
"hasDynamicHelp": false,
|
|
1088
|
+
"multiple": false,
|
|
1089
|
+
"type": "option"
|
|
1090
|
+
},
|
|
1091
|
+
"wait": {
|
|
1092
|
+
"description": "Block until the function has processed the test inbound (webhook status is `fired` or `exhausted`) or --timeout elapses. Exits non-zero on timeout or on exhausted retries.",
|
|
1093
|
+
"name": "wait",
|
|
1094
|
+
"allowNo": false,
|
|
1095
|
+
"type": "boolean"
|
|
1096
|
+
},
|
|
1097
|
+
"show-sends": {
|
|
1098
|
+
"description": "When the wait resolves, also print the outbound emails the function emitted while processing the test inbound (id, status, to, subject). Implies --wait.",
|
|
1099
|
+
"name": "show-sends",
|
|
1100
|
+
"allowNo": false,
|
|
1101
|
+
"type": "boolean"
|
|
1102
|
+
},
|
|
1103
|
+
"timeout": {
|
|
1104
|
+
"description": "Seconds to wait before exiting non-zero when --wait is set; 0 waits forever.",
|
|
1105
|
+
"name": "timeout",
|
|
1106
|
+
"default": 60,
|
|
1107
|
+
"hasDynamicHelp": false,
|
|
1108
|
+
"multiple": false,
|
|
1109
|
+
"type": "option"
|
|
1110
|
+
},
|
|
1111
|
+
"poll-interval": {
|
|
1112
|
+
"description": "Seconds between polls while waiting.",
|
|
1113
|
+
"name": "poll-interval",
|
|
1114
|
+
"default": 2,
|
|
1115
|
+
"hasDynamicHelp": false,
|
|
1116
|
+
"multiple": false,
|
|
1117
|
+
"type": "option"
|
|
1118
|
+
},
|
|
1119
|
+
"time": {
|
|
1120
|
+
"description": "Print the wall-clock duration of this command to stderr after it completes (e.g. `[time: 1.34s]`). Useful for measuring `--wait` send latency, comparing CLI overhead, or capturing timing in scripts.",
|
|
1121
|
+
"name": "time",
|
|
1122
|
+
"allowNo": false,
|
|
1123
|
+
"type": "boolean"
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1126
|
+
"hasDynamicHelp": false,
|
|
1127
|
+
"hiddenAliases": [],
|
|
1128
|
+
"id": "functions:test-function",
|
|
1129
|
+
"pluginAlias": "@primitivedotdev/cli",
|
|
1130
|
+
"pluginName": "@primitivedotdev/cli",
|
|
1131
|
+
"pluginType": "core",
|
|
1132
|
+
"strict": true,
|
|
1133
|
+
"summary": "Trigger a test invocation; with --wait, watch it land",
|
|
1134
|
+
"enableJsonFlag": false
|
|
1135
|
+
},
|
|
1039
1136
|
"account:get-account": {
|
|
1040
1137
|
"aliases": [],
|
|
1041
1138
|
"args": {},
|
|
@@ -3624,83 +3721,6 @@
|
|
|
3624
3721
|
"summary": "Set a secret by key",
|
|
3625
3722
|
"enableJsonFlag": false
|
|
3626
3723
|
},
|
|
3627
|
-
"functions:test-function": {
|
|
3628
|
-
"aliases": [],
|
|
3629
|
-
"args": {},
|
|
3630
|
-
"description": "Sends a real test email from a Primitive-controlled sender to a\nlocal-part on one of the org's verified inbound domains. By\ndefault the recipient is a synthetic\n`__primitive_function_test+<random>@<domain>` address that\nevery handler's catch-all routing receives identically; pass\n`local_part` to override and exercise routing logic that\nbranches on a specific recipient (the common pattern when one\nfunction handles multiple inboxes like `summarize@` and\n`action@`). The function fires through the normal MX delivery\npath, so reply / send-mail calls from inside the handler\nagainst the inbound's `email.id` work the same as in\nproduction. Returns immediately after the send is queued; the\ninvocation appears on the function's invocations list within a\nfew seconds.\n\nRequires that the function is currently `deployed`. Returns 422\nif the function is in `pending` or `failed` state, or if the\norg has no verified inbound domain to receive the test mail.\nReturns 400 if `local_part` is set to a value that does not\nmatch the local-part character set.\n",
|
|
3631
|
-
"flags": {
|
|
3632
|
-
"api-key": {
|
|
3633
|
-
"description": "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
3634
|
-
"env": "PRIMITIVE_API_KEY",
|
|
3635
|
-
"name": "api-key",
|
|
3636
|
-
"hasDynamicHelp": false,
|
|
3637
|
-
"multiple": false,
|
|
3638
|
-
"type": "option"
|
|
3639
|
-
},
|
|
3640
|
-
"api-base-url-1": {
|
|
3641
|
-
"description": "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
3642
|
-
"env": "PRIMITIVE_API_BASE_URL_1",
|
|
3643
|
-
"hidden": true,
|
|
3644
|
-
"name": "api-base-url-1",
|
|
3645
|
-
"hasDynamicHelp": false,
|
|
3646
|
-
"multiple": false,
|
|
3647
|
-
"type": "option"
|
|
3648
|
-
},
|
|
3649
|
-
"api-base-url-2": {
|
|
3650
|
-
"description": "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
3651
|
-
"env": "PRIMITIVE_API_BASE_URL_2",
|
|
3652
|
-
"hidden": true,
|
|
3653
|
-
"name": "api-base-url-2",
|
|
3654
|
-
"hasDynamicHelp": false,
|
|
3655
|
-
"multiple": false,
|
|
3656
|
-
"type": "option"
|
|
3657
|
-
},
|
|
3658
|
-
"time": {
|
|
3659
|
-
"description": "Print the wall-clock duration of this command to stderr after it completes (e.g. `[time: 1.34s]`). Useful for measuring `--wait` send latency, comparing CLI overhead, or capturing timing in scripts.",
|
|
3660
|
-
"name": "time",
|
|
3661
|
-
"allowNo": false,
|
|
3662
|
-
"type": "boolean"
|
|
3663
|
-
},
|
|
3664
|
-
"id": {
|
|
3665
|
-
"description": "Resource UUID",
|
|
3666
|
-
"name": "id",
|
|
3667
|
-
"required": true,
|
|
3668
|
-
"hasDynamicHelp": false,
|
|
3669
|
-
"multiple": false,
|
|
3670
|
-
"type": "option"
|
|
3671
|
-
},
|
|
3672
|
-
"raw-body": {
|
|
3673
|
-
"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.",
|
|
3674
|
-
"name": "raw-body",
|
|
3675
|
-
"hasDynamicHelp": false,
|
|
3676
|
-
"multiple": false,
|
|
3677
|
-
"type": "option"
|
|
3678
|
-
},
|
|
3679
|
-
"body-file": {
|
|
3680
|
-
"description": "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload.",
|
|
3681
|
-
"name": "body-file",
|
|
3682
|
-
"hasDynamicHelp": false,
|
|
3683
|
-
"multiple": false,
|
|
3684
|
-
"type": "option"
|
|
3685
|
-
},
|
|
3686
|
-
"local-part": {
|
|
3687
|
-
"description": "Override the synthetic local-part. When set, the test email is sent to `<local_part>@<picked-domain>` instead of the default `__primitive_function_test+<random>@<picked-domain>`. Must start with an alphanumeric and contain only letters, digits, dots, plus signs, hyphens, or underscores; 1-64 characters total.",
|
|
3688
|
-
"name": "local-part",
|
|
3689
|
-
"hasDynamicHelp": false,
|
|
3690
|
-
"multiple": false,
|
|
3691
|
-
"type": "option"
|
|
3692
|
-
}
|
|
3693
|
-
},
|
|
3694
|
-
"hasDynamicHelp": false,
|
|
3695
|
-
"hiddenAliases": [],
|
|
3696
|
-
"id": "functions:test-function",
|
|
3697
|
-
"pluginAlias": "@primitivedotdev/cli",
|
|
3698
|
-
"pluginName": "@primitivedotdev/cli",
|
|
3699
|
-
"pluginType": "core",
|
|
3700
|
-
"strict": true,
|
|
3701
|
-
"summary": "Send a test invocation",
|
|
3702
|
-
"enableJsonFlag": false
|
|
3703
|
-
},
|
|
3704
3724
|
"functions:update-function": {
|
|
3705
3725
|
"aliases": [],
|
|
3706
3726
|
"args": {},
|
|
@@ -4366,5 +4386,5 @@
|
|
|
4366
4386
|
"enableJsonFlag": false
|
|
4367
4387
|
}
|
|
4368
4388
|
},
|
|
4369
|
-
"version": "0.26.
|
|
4389
|
+
"version": "0.26.1"
|
|
4370
4390
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitivedotdev/cli",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.1",
|
|
4
4
|
"description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"description": "Claim, verify, and manage email domains"
|
|
36
36
|
},
|
|
37
37
|
"emails": {
|
|
38
|
-
"description": "List, inspect, and
|
|
38
|
+
"description": "List, inspect, and wait for received emails. `primitive emails:latest` lists the most recent inbound, `primitive emails:wait` blocks until matching inbound arrives (filter with --to/--from/--subject/--q; bounded by --timeout and --number; ideal for agents and CI), and `primitive emails:watch` streams new matches indefinitely for long-running terminals."
|
|
39
39
|
},
|
|
40
40
|
"sending": {
|
|
41
41
|
"description": "Send outbound emails. For replies to inbound mail, use `sending:reply-to-email --id <inbound-id>` (threading and Re: subject derived server-side); for fresh sends, use `sending:send-email` or the `primitive send` shortcut."
|