@primitivedotdev/cli 0.31.6 → 0.31.7
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/index.js +155 -15
- package/package.json +1 -1
package/dist/oclif/index.js
CHANGED
|
@@ -13887,6 +13887,21 @@ function resolveChatResponseBody(reply) {
|
|
|
13887
13887
|
function matchDescription(strategy) {
|
|
13888
13888
|
return strategy === "strict" ? "strict, matched by reply_to_sent_email_id" : "fallback, matched by sender/time window";
|
|
13889
13889
|
}
|
|
13890
|
+
function normalizeEmailAddress(value) {
|
|
13891
|
+
return value.trim().toLowerCase();
|
|
13892
|
+
}
|
|
13893
|
+
function derivedReplySubject(parent) {
|
|
13894
|
+
const subject = parent.subject?.trim();
|
|
13895
|
+
if (!subject) return "Re: (no subject)";
|
|
13896
|
+
return /^re:/i.test(subject) ? subject : `Re: ${subject}`;
|
|
13897
|
+
}
|
|
13898
|
+
function assertParentMatchesRecipient(parent, recipient) {
|
|
13899
|
+
if (normalizeEmailAddress(parent.from_email) === normalizeEmailAddress(recipient)) return;
|
|
13900
|
+
throw cliError$6(`Inbound email ${parent.id} is from ${parent.from_email}, not ${recipient}. Use \`primitive chat ${parent.from_email} --reply <message> --reply-to-email-id ${parent.id}\` or omit --reply-to-email-id to continue the latest inbound from ${recipient}.`);
|
|
13901
|
+
}
|
|
13902
|
+
function emailDetailFromEnvelope(envelope) {
|
|
13903
|
+
return envelope?.data ?? envelope ?? null;
|
|
13904
|
+
}
|
|
13890
13905
|
function buildCommand(kind, description, argv, options = {}) {
|
|
13891
13906
|
const requiresMessage = options.requiresMessage ?? false;
|
|
13892
13907
|
return {
|
|
@@ -13907,15 +13922,15 @@ function buildChatFollowUpCommands(context) {
|
|
|
13907
13922
|
"primitive",
|
|
13908
13923
|
"chat",
|
|
13909
13924
|
context.recipient,
|
|
13925
|
+
"--reply",
|
|
13910
13926
|
"<message>",
|
|
13911
13927
|
"--from",
|
|
13912
13928
|
context.from,
|
|
13913
|
-
"--
|
|
13914
|
-
context.
|
|
13929
|
+
"--reply-to-email-id",
|
|
13930
|
+
context.reply.id,
|
|
13915
13931
|
"--timeout",
|
|
13916
13932
|
String(context.timeoutSeconds)
|
|
13917
13933
|
];
|
|
13918
|
-
if (context.reply.message_id) continueParts.push("--in-reply-to", context.reply.message_id);
|
|
13919
13934
|
if (context.json) continueParts.push("--json");
|
|
13920
13935
|
if (context.quiet) continueParts.push("--quiet");
|
|
13921
13936
|
if (context.strictOnly) continueParts.push("--strict-only");
|
|
@@ -14049,6 +14064,55 @@ function formatChatRecoveryContext(context) {
|
|
|
14049
14064
|
for (const { description, command } of buildChatRecoveryCommands(context)) lines.push(` ${description}:`, ` ${command}`);
|
|
14050
14065
|
return lines.join("\n");
|
|
14051
14066
|
}
|
|
14067
|
+
async function loadInboundEmailDetail(params) {
|
|
14068
|
+
const result = await getEmail({
|
|
14069
|
+
client: params.apiClient.client,
|
|
14070
|
+
path: { id: params.id },
|
|
14071
|
+
responseStyle: "fields"
|
|
14072
|
+
});
|
|
14073
|
+
if (result.error) {
|
|
14074
|
+
const payload = extractErrorPayload(result.error);
|
|
14075
|
+
writeErrorWithHints(payload);
|
|
14076
|
+
surfaceUnauthorizedHint({
|
|
14077
|
+
...params.authFailureContext,
|
|
14078
|
+
payload
|
|
14079
|
+
});
|
|
14080
|
+
throw new Errors.CLIError(`Could not load inbound email ${params.id}.`, { exit: 1 });
|
|
14081
|
+
}
|
|
14082
|
+
const detail = emailDetailFromEnvelope(result.data);
|
|
14083
|
+
if (!detail) throw new Errors.CLIError(`Could not load inbound email ${params.id}: the API returned no email body.`, { exit: 1 });
|
|
14084
|
+
return detail;
|
|
14085
|
+
}
|
|
14086
|
+
async function findLatestInboundFromRecipient(params) {
|
|
14087
|
+
const result = await searchEmails({
|
|
14088
|
+
client: params.apiClient.client,
|
|
14089
|
+
query: {
|
|
14090
|
+
from: params.recipient,
|
|
14091
|
+
to: params.from,
|
|
14092
|
+
include_facets: "false",
|
|
14093
|
+
limit: params.pageSize,
|
|
14094
|
+
snippet: "false",
|
|
14095
|
+
sort: "received_at_desc"
|
|
14096
|
+
},
|
|
14097
|
+
responseStyle: "fields"
|
|
14098
|
+
});
|
|
14099
|
+
if (result.error) {
|
|
14100
|
+
const payload = extractErrorPayload(result.error);
|
|
14101
|
+
writeErrorWithHints(payload);
|
|
14102
|
+
surfaceUnauthorizedHint({
|
|
14103
|
+
...params.authFailureContext,
|
|
14104
|
+
payload
|
|
14105
|
+
});
|
|
14106
|
+
throw new Errors.CLIError("Could not find a prior chat reply.", { exit: 1 });
|
|
14107
|
+
}
|
|
14108
|
+
const row = (result.data?.data ?? []).find((email) => email.status === "accepted" || email.status === "completed");
|
|
14109
|
+
if (!row) return null;
|
|
14110
|
+
return loadInboundEmailDetail({
|
|
14111
|
+
apiClient: params.apiClient,
|
|
14112
|
+
authFailureContext: params.authFailureContext,
|
|
14113
|
+
id: row.id
|
|
14114
|
+
});
|
|
14115
|
+
}
|
|
14052
14116
|
var ChatCommand = class ChatCommand extends Command {
|
|
14053
14117
|
static description = `Send a message to an address and wait for the reply.
|
|
14054
14118
|
|
|
@@ -14062,6 +14126,13 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14062
14126
|
follow-up commands as templates. The default transcript is for humans;
|
|
14063
14127
|
agents and scripts should pass --json for parse-safe output.
|
|
14064
14128
|
|
|
14129
|
+
To continue an existing chat, pass --reply '<message>'. By default,
|
|
14130
|
+
the CLI replies to the latest inbound email from the recipient to
|
|
14131
|
+
your sender address. For exact continuation, pass
|
|
14132
|
+
--reply-to-email-id <inbound-email-id>. Reply mode uses Primitive's
|
|
14133
|
+
reply endpoint, so the reply subject and threading headers are
|
|
14134
|
+
derived from the inbound email instead of copied into CLI flags.
|
|
14135
|
+
|
|
14065
14136
|
--json emits a structured envelope with both sides of the exchange,
|
|
14066
14137
|
a direct response_body field, match details, and follow-up command
|
|
14067
14138
|
metadata such as kind, argv, placeholders, and requires_message.
|
|
@@ -14079,6 +14150,8 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14079
14150
|
static examples = [
|
|
14080
14151
|
"<%= config.bin %> chat help@agent.acme.dev 'how do I rotate my API key?'",
|
|
14081
14152
|
"cat error.log | <%= config.bin %> chat help@agent.acme.dev --subject 'webhook 401s'",
|
|
14153
|
+
"<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing'",
|
|
14154
|
+
"<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing' --reply-to-email-id <inbound-email-id>",
|
|
14082
14155
|
"<%= config.bin %> chat help@agent.acme.dev 'follow up question' --json",
|
|
14083
14156
|
"<%= config.bin %> chat help@agent.acme.dev 'one more thing' --timeout 300"
|
|
14084
14157
|
];
|
|
@@ -14106,7 +14179,9 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14106
14179
|
}),
|
|
14107
14180
|
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
14108
14181
|
subject: Flags.string({ description: "Subject line. Defaults to the first line of the message when omitted." }),
|
|
14109
|
-
|
|
14182
|
+
reply: Flags.string({ description: "Reply body. Continues the latest inbound email from the recipient to your sender address; pass --reply-to-email-id for an exact thread." }),
|
|
14183
|
+
"reply-to-email-id": Flags.string({ description: "Inbound email id to continue exactly. Uses Primitive's reply endpoint, so recipient, subject, and threading headers are derived from the inbound email." }),
|
|
14184
|
+
"in-reply-to": Flags.string({ description: "Raw Message-Id of the parent email to thread a new send against. Prefer --reply-to-email-id with --reply when continuing an inbound email stored by Primitive." }),
|
|
14110
14185
|
json: Flags.boolean({ description: "Emit a structured JSON envelope { sent, reply, response_body, response_body_format, match, follow_up_commands } on stdout instead of the human-readable transcript." }),
|
|
14111
14186
|
quiet: Flags.boolean({ description: "Suppress stderr progress updates while sending and waiting. Errors and recovery commands are still written to stderr." }),
|
|
14112
14187
|
timeout: Flags.integer({
|
|
@@ -14136,8 +14211,12 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14136
14211
|
};
|
|
14137
14212
|
async run() {
|
|
14138
14213
|
const { args, flags } = await this.parse(ChatCommand);
|
|
14139
|
-
const
|
|
14140
|
-
if (
|
|
14214
|
+
const replyMode = flags.reply !== void 0 || flags["reply-to-email-id"] !== void 0;
|
|
14215
|
+
if (flags.reply !== void 0 && args.message !== void 0 && args.message !== "") throw cliError$6("Pass the reply body either as --reply or as the positional message, not both.");
|
|
14216
|
+
if (replyMode && flags.subject !== void 0) throw cliError$6("--subject is not used with --reply. Primitive derives the reply subject from the inbound email.");
|
|
14217
|
+
if (replyMode && flags["in-reply-to"] !== void 0) throw cliError$6("Use --reply-to-email-id with --reply instead of raw --in-reply-to.");
|
|
14218
|
+
const message = flags.reply !== void 0 ? flags.reply : args.message !== void 0 && args.message !== "" ? args.message : await readStdinToString();
|
|
14219
|
+
if (!message.trim()) throw cliError$6(replyMode ? "Reply body is empty." : "Message body is empty.");
|
|
14141
14220
|
await runWithTiming(flags.time, async () => {
|
|
14142
14221
|
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
14143
14222
|
apiKey: flags["api-key"],
|
|
@@ -14150,12 +14229,71 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14150
14229
|
baseUrlOverridden,
|
|
14151
14230
|
configDir: this.config.configDir
|
|
14152
14231
|
};
|
|
14153
|
-
const from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
14154
|
-
const subject = flags.subject ?? deriveSubject(message);
|
|
14155
|
-
const sentAtIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
14156
14232
|
const progress = flags.quiet ? null : new ChatProgressIndicator(process.stderr);
|
|
14157
|
-
|
|
14158
|
-
|
|
14233
|
+
let from;
|
|
14234
|
+
let parentReply;
|
|
14235
|
+
let subject;
|
|
14236
|
+
if (replyMode) {
|
|
14237
|
+
const replyContext = await (async () => {
|
|
14238
|
+
let replyContextFailureMessage = "Could not load reply context.";
|
|
14239
|
+
try {
|
|
14240
|
+
if (flags["reply-to-email-id"] !== void 0) {
|
|
14241
|
+
progress?.start(`Loading reply context for ${flags["reply-to-email-id"]}`);
|
|
14242
|
+
const exactParentReply = await loadInboundEmailDetail({
|
|
14243
|
+
apiClient,
|
|
14244
|
+
authFailureContext,
|
|
14245
|
+
id: flags["reply-to-email-id"]
|
|
14246
|
+
});
|
|
14247
|
+
replyContextFailureMessage = `Inbound email ${flags["reply-to-email-id"]} does not match recipient ${args.recipient}.`;
|
|
14248
|
+
assertParentMatchesRecipient(exactParentReply, args.recipient);
|
|
14249
|
+
return {
|
|
14250
|
+
from: flags.from ?? exactParentReply.to_email,
|
|
14251
|
+
parentReply: exactParentReply
|
|
14252
|
+
};
|
|
14253
|
+
}
|
|
14254
|
+
const replyFrom = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
14255
|
+
progress?.start(`Finding latest inbound email from ${args.recipient}`);
|
|
14256
|
+
const latestParentReply = await findLatestInboundFromRecipient({
|
|
14257
|
+
apiClient,
|
|
14258
|
+
authFailureContext,
|
|
14259
|
+
from: replyFrom,
|
|
14260
|
+
pageSize: flags["page-size"],
|
|
14261
|
+
recipient: args.recipient
|
|
14262
|
+
});
|
|
14263
|
+
if (!latestParentReply) {
|
|
14264
|
+
replyContextFailureMessage = "No prior inbound email found.";
|
|
14265
|
+
throw cliError$6(`No prior inbound email from ${args.recipient} to ${replyFrom}. Start a new chat with \`primitive chat ${args.recipient} <message>\`, pass --from, or pass --reply-to-email-id <inbound-email-id>.`);
|
|
14266
|
+
}
|
|
14267
|
+
replyContextFailureMessage = `Inbound email ${latestParentReply.id} does not match recipient ${args.recipient}.`;
|
|
14268
|
+
assertParentMatchesRecipient(latestParentReply, args.recipient);
|
|
14269
|
+
return {
|
|
14270
|
+
from: replyFrom,
|
|
14271
|
+
parentReply: latestParentReply
|
|
14272
|
+
};
|
|
14273
|
+
} catch (error) {
|
|
14274
|
+
progress?.fail(replyContextFailureMessage);
|
|
14275
|
+
throw error;
|
|
14276
|
+
}
|
|
14277
|
+
})();
|
|
14278
|
+
from = replyContext.from;
|
|
14279
|
+
parentReply = replyContext.parentReply;
|
|
14280
|
+
subject = derivedReplySubject(replyContext.parentReply);
|
|
14281
|
+
} else {
|
|
14282
|
+
from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
14283
|
+
subject = flags.subject ?? deriveSubject(message);
|
|
14284
|
+
}
|
|
14285
|
+
const sentAtIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
14286
|
+
if (replyMode) progress?.update(`Sending reply to ${args.recipient}`);
|
|
14287
|
+
else progress?.start(`Sending message to ${args.recipient}`);
|
|
14288
|
+
const sendResult = parentReply !== void 0 ? await replyToEmail({
|
|
14289
|
+
body: {
|
|
14290
|
+
body_text: message,
|
|
14291
|
+
from
|
|
14292
|
+
},
|
|
14293
|
+
client: apiClient.client,
|
|
14294
|
+
path: { id: parentReply.id },
|
|
14295
|
+
responseStyle: "fields"
|
|
14296
|
+
}) : await sendEmail({
|
|
14159
14297
|
body: {
|
|
14160
14298
|
from,
|
|
14161
14299
|
to: args.recipient,
|
|
@@ -14167,7 +14305,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14167
14305
|
responseStyle: "fields"
|
|
14168
14306
|
});
|
|
14169
14307
|
if (sendResult.error) {
|
|
14170
|
-
progress?.fail("Message send failed.");
|
|
14308
|
+
progress?.fail(replyMode ? "Reply send failed." : "Message send failed.");
|
|
14171
14309
|
const errorPayload = extractErrorPayload(sendResult.error);
|
|
14172
14310
|
writeErrorWithHints(errorPayload);
|
|
14173
14311
|
surfaceUnauthorizedHint({
|
|
@@ -14182,13 +14320,15 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14182
14320
|
progress?.fail("Send succeeded but the API returned no data.");
|
|
14183
14321
|
throw cliError$6("Send succeeded but the API returned no data.");
|
|
14184
14322
|
}
|
|
14185
|
-
|
|
14323
|
+
const replyAddress = sent.from || from;
|
|
14324
|
+
progress?.update(`${replyMode ? "Reply" : "Message"} sent; waiting for reply from ${args.recipient}`, {
|
|
14186
14325
|
heartbeatMs: 15e3,
|
|
14187
14326
|
timeoutSeconds: flags.timeout
|
|
14188
14327
|
});
|
|
14189
14328
|
const baseContext = {
|
|
14190
|
-
from,
|
|
14329
|
+
from: replyAddress,
|
|
14191
14330
|
json: flags.json,
|
|
14331
|
+
parentReply,
|
|
14192
14332
|
quiet: flags.quiet,
|
|
14193
14333
|
recipient: args.recipient,
|
|
14194
14334
|
sent,
|
|
@@ -14203,7 +14343,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14203
14343
|
replyResult = await waitForReply({
|
|
14204
14344
|
apiClient,
|
|
14205
14345
|
authFailureContext,
|
|
14206
|
-
from,
|
|
14346
|
+
from: replyAddress,
|
|
14207
14347
|
interval: flags.interval,
|
|
14208
14348
|
notice: (message) => {
|
|
14209
14349
|
if (progress) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitivedotdev/cli",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.7",
|
|
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,
|