@primitivedotdev/cli 0.31.8 → 0.32.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/README.md +11 -2
- package/dist/oclif/index.js +1292 -797
- package/man/primitive.1 +20 -0
- package/package.json +9 -3
package/dist/oclif/index.js
CHANGED
|
@@ -2,10 +2,10 @@ import { Args, Command, Errors, Flags } from "@oclif/core";
|
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
6
5
|
import { hostname } from "node:os";
|
|
7
6
|
import process$1 from "node:process";
|
|
8
7
|
import { createInterface } from "node:readline/promises";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
9
|
//#region \0rolldown/runtime.js
|
|
10
10
|
var __defProp = Object.defineProperty;
|
|
11
11
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -13133,8 +13133,14 @@ var PrimitiveApiClient = class {
|
|
|
13133
13133
|
//#region src/oclif/auth.ts
|
|
13134
13134
|
const CREDENTIALS_FILE = "credentials.json";
|
|
13135
13135
|
const CREDENTIALS_LOCK_DIR = "credentials.lock";
|
|
13136
|
+
const CREDENTIALS_LOCK_OWNER_FILE = "owner.json";
|
|
13136
13137
|
const CREDENTIALS_LOCK_STALE_MS = 1800 * 1e3;
|
|
13137
13138
|
const MALFORMED_CREDENTIALS_HINT = "Run `primitive logout` and then `primitive signin`.";
|
|
13139
|
+
const CREDENTIALS_LOCK_CLEANUP_SIGNALS = [
|
|
13140
|
+
"SIGINT",
|
|
13141
|
+
"SIGTERM",
|
|
13142
|
+
"SIGHUP"
|
|
13143
|
+
];
|
|
13138
13144
|
function isRecord$2(value) {
|
|
13139
13145
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13140
13146
|
}
|
|
@@ -13181,6 +13187,9 @@ function parseCredentials(raw) {
|
|
|
13181
13187
|
function credentialsPath(configDir) {
|
|
13182
13188
|
return join(configDir, CREDENTIALS_FILE);
|
|
13183
13189
|
}
|
|
13190
|
+
function credentialsLockPath(configDir) {
|
|
13191
|
+
return join(configDir, CREDENTIALS_LOCK_DIR);
|
|
13192
|
+
}
|
|
13184
13193
|
function normalize(url, fallback) {
|
|
13185
13194
|
const trimmed = url?.trim();
|
|
13186
13195
|
if (!trimmed) return fallback;
|
|
@@ -13239,6 +13248,12 @@ function saveCliCredentials(configDir, credentials) {
|
|
|
13239
13248
|
function deleteCliCredentials(configDir) {
|
|
13240
13249
|
rmSync(credentialsPath(configDir), { force: true });
|
|
13241
13250
|
}
|
|
13251
|
+
function deleteCliCredentialsLock(configDir) {
|
|
13252
|
+
rmSync(credentialsLockPath(configDir), {
|
|
13253
|
+
force: true,
|
|
13254
|
+
recursive: true
|
|
13255
|
+
});
|
|
13256
|
+
}
|
|
13242
13257
|
function errorCode(error) {
|
|
13243
13258
|
return error && typeof error === "object" ? error.code : void 0;
|
|
13244
13259
|
}
|
|
@@ -13256,12 +13271,85 @@ function removeStaleCliCredentialsLock(lockPath, staleMs, now) {
|
|
|
13256
13271
|
});
|
|
13257
13272
|
return true;
|
|
13258
13273
|
}
|
|
13274
|
+
function readCliCredentialsLockOwner(lockPath) {
|
|
13275
|
+
let raw;
|
|
13276
|
+
try {
|
|
13277
|
+
raw = readFileSync(join(lockPath, CREDENTIALS_LOCK_OWNER_FILE), "utf8");
|
|
13278
|
+
} catch (error) {
|
|
13279
|
+
if (errorCode(error) === "ENOENT") return null;
|
|
13280
|
+
throw error;
|
|
13281
|
+
}
|
|
13282
|
+
try {
|
|
13283
|
+
const pid = JSON.parse(raw)?.pid;
|
|
13284
|
+
return Number.isInteger(pid) && pid > 0 ? { pid } : null;
|
|
13285
|
+
} catch {
|
|
13286
|
+
return null;
|
|
13287
|
+
}
|
|
13288
|
+
}
|
|
13289
|
+
function processIsRunning(pid) {
|
|
13290
|
+
try {
|
|
13291
|
+
process.kill(pid, 0);
|
|
13292
|
+
return true;
|
|
13293
|
+
} catch (error) {
|
|
13294
|
+
if (errorCode(error) === "ESRCH") return false;
|
|
13295
|
+
return true;
|
|
13296
|
+
}
|
|
13297
|
+
}
|
|
13298
|
+
function removeRecoverableCliCredentialsLock(params) {
|
|
13299
|
+
const owner = readCliCredentialsLockOwner(params.lockPath);
|
|
13300
|
+
if (owner && params.isRunning(owner.pid)) return false;
|
|
13301
|
+
if (owner) {
|
|
13302
|
+
rmSync(params.lockPath, {
|
|
13303
|
+
force: true,
|
|
13304
|
+
recursive: true
|
|
13305
|
+
});
|
|
13306
|
+
return true;
|
|
13307
|
+
}
|
|
13308
|
+
return removeStaleCliCredentialsLock(params.lockPath, params.staleMs, params.now);
|
|
13309
|
+
}
|
|
13310
|
+
function writeCliCredentialsLockOwner(lockPath) {
|
|
13311
|
+
const ownerPath = join(lockPath, CREDENTIALS_LOCK_OWNER_FILE);
|
|
13312
|
+
writeFileSync(ownerPath, `${JSON.stringify({
|
|
13313
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13314
|
+
pid: process.pid
|
|
13315
|
+
})}\n`, { mode: 384 });
|
|
13316
|
+
chmodSync(ownerPath, 384);
|
|
13317
|
+
}
|
|
13318
|
+
function installCredentialsLockSignalCleanup(lockPath) {
|
|
13319
|
+
let active = true;
|
|
13320
|
+
const listeners = CREDENTIALS_LOCK_CLEANUP_SIGNALS.map((signal) => {
|
|
13321
|
+
const listener = () => {
|
|
13322
|
+
if (!active) return;
|
|
13323
|
+
active = false;
|
|
13324
|
+
rmSync(lockPath, {
|
|
13325
|
+
force: true,
|
|
13326
|
+
recursive: true
|
|
13327
|
+
});
|
|
13328
|
+
process.exit(signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 129);
|
|
13329
|
+
};
|
|
13330
|
+
process.once(signal, listener);
|
|
13331
|
+
return {
|
|
13332
|
+
listener,
|
|
13333
|
+
signal
|
|
13334
|
+
};
|
|
13335
|
+
});
|
|
13336
|
+
return () => {
|
|
13337
|
+
if (!active) return;
|
|
13338
|
+
active = false;
|
|
13339
|
+
for (const { listener, signal } of listeners) process.removeListener(signal, listener);
|
|
13340
|
+
};
|
|
13341
|
+
}
|
|
13342
|
+
function credentialsLockInProgressMessage(lockPath) {
|
|
13343
|
+
return `Another Primitive CLI credential operation is already in progress. Wait for it to finish, then retry. If no Primitive auth command is still running, run \`primitive logout --force\` to clear local CLI auth state and remove ${lockPath}.`;
|
|
13344
|
+
}
|
|
13259
13345
|
function acquireCliCredentialsLock(configDir, options = {}) {
|
|
13260
13346
|
mkdirSync(configDir, {
|
|
13261
13347
|
mode: 448,
|
|
13262
13348
|
recursive: true
|
|
13263
13349
|
});
|
|
13264
|
-
const lockPath =
|
|
13350
|
+
const lockPath = credentialsLockPath(configDir);
|
|
13351
|
+
const installSignalHandlers = options.installSignalHandlers ?? true;
|
|
13352
|
+
const isRunning = options.isProcessRunning ?? processIsRunning;
|
|
13265
13353
|
const now = options.now ?? Date.now;
|
|
13266
13354
|
const staleMs = options.staleMs ?? CREDENTIALS_LOCK_STALE_MS;
|
|
13267
13355
|
let acquired = false;
|
|
@@ -13271,14 +13359,30 @@ function acquireCliCredentialsLock(configDir, options = {}) {
|
|
|
13271
13359
|
break;
|
|
13272
13360
|
} catch (error) {
|
|
13273
13361
|
if (errorCode(error) !== "EEXIST") throw error;
|
|
13274
|
-
if (
|
|
13275
|
-
|
|
13362
|
+
if (removeRecoverableCliCredentialsLock({
|
|
13363
|
+
isRunning,
|
|
13364
|
+
lockPath,
|
|
13365
|
+
now,
|
|
13366
|
+
staleMs
|
|
13367
|
+
})) continue;
|
|
13368
|
+
throw new Error(credentialsLockInProgressMessage(lockPath));
|
|
13369
|
+
}
|
|
13370
|
+
if (!acquired) throw new Error(credentialsLockInProgressMessage(lockPath));
|
|
13371
|
+
try {
|
|
13372
|
+
writeCliCredentialsLockOwner(lockPath);
|
|
13373
|
+
} catch (error) {
|
|
13374
|
+
rmSync(lockPath, {
|
|
13375
|
+
force: true,
|
|
13376
|
+
recursive: true
|
|
13377
|
+
});
|
|
13378
|
+
throw error;
|
|
13276
13379
|
}
|
|
13277
|
-
|
|
13380
|
+
const removeSignalCleanup = installSignalHandlers ? installCredentialsLockSignalCleanup(lockPath) : () => void 0;
|
|
13278
13381
|
let released = false;
|
|
13279
13382
|
return () => {
|
|
13280
13383
|
if (released) return;
|
|
13281
13384
|
released = true;
|
|
13385
|
+
removeSignalCleanup();
|
|
13282
13386
|
rmSync(lockPath, {
|
|
13283
13387
|
force: true,
|
|
13284
13388
|
recursive: true
|
|
@@ -13298,7 +13402,7 @@ function resolveCliAuth(params) {
|
|
|
13298
13402
|
const credentials = loadCliCredentials(params.configDir);
|
|
13299
13403
|
if (credentials) return {
|
|
13300
13404
|
apiKey: credentials.access_token,
|
|
13301
|
-
apiBaseUrl1:
|
|
13405
|
+
apiBaseUrl1: credentials.api_base_url_1,
|
|
13302
13406
|
apiBaseUrl2,
|
|
13303
13407
|
credentials,
|
|
13304
13408
|
source: "stored"
|
|
@@ -14051,7 +14155,10 @@ const RESERVED_FLAG_NAMES = new Set([
|
|
|
14051
14155
|
]);
|
|
14052
14156
|
function bodyFieldFlag(field) {
|
|
14053
14157
|
const common = { description: field.description || field.name };
|
|
14054
|
-
if (field.kind === "boolean") return Flags.boolean(
|
|
14158
|
+
if (field.kind === "boolean") return Flags.boolean({
|
|
14159
|
+
...common,
|
|
14160
|
+
allowNo: true
|
|
14161
|
+
});
|
|
14055
14162
|
if (field.kind === "integer") return Flags.integer({
|
|
14056
14163
|
...common,
|
|
14057
14164
|
...numericFlagOptions(field)
|
|
@@ -14084,7 +14191,10 @@ function buildFlags(operation) {
|
|
|
14084
14191
|
}),
|
|
14085
14192
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
14086
14193
|
};
|
|
14087
|
-
if (!operation.binaryResponse)
|
|
14194
|
+
if (!operation.binaryResponse) {
|
|
14195
|
+
flags.json = Flags.boolean({ description: "Accepted for consistency with task-focused commands. Generated API commands already print JSON by default." });
|
|
14196
|
+
flags.envelope = Flags.boolean({ description: "Print the full response envelope, including pagination metadata such as meta.cursor. Defaults to printing only the data payload for backward compatibility." });
|
|
14197
|
+
}
|
|
14088
14198
|
for (const parameter of [...operation.pathParams, ...operation.queryParams]) flags[flagName(parameter.name)] = flagForParameter(parameter);
|
|
14089
14199
|
const bodyFieldFlagToProperty = /* @__PURE__ */ new Map();
|
|
14090
14200
|
if (operation.hasJsonBody) {
|
|
@@ -14126,11 +14236,21 @@ function collectValues(parameters, flags) {
|
|
|
14126
14236
|
function operationOutputPayload(envelope, includeEnvelope) {
|
|
14127
14237
|
return includeEnvelope ? envelope ?? null : envelope?.data ?? null;
|
|
14128
14238
|
}
|
|
14239
|
+
function isIncompleteDomainVerification(operation, envelope) {
|
|
14240
|
+
if (operation.sdkName !== "verifyDomain") return false;
|
|
14241
|
+
const data = envelope?.data;
|
|
14242
|
+
if (!data || typeof data !== "object") return false;
|
|
14243
|
+
return data.verified === false;
|
|
14244
|
+
}
|
|
14245
|
+
function writeIncompleteDomainVerificationHint() {
|
|
14246
|
+
process.stderr.write("Domain verification is incomplete. Add or fix the DNS records shown above, or run `primitive domains zone-file --id <domain-id>` to download the complete zone file, then retry `primitive domains verify --id <domain-id>`.\n");
|
|
14247
|
+
}
|
|
14129
14248
|
const OPERATION_HINTS = {
|
|
14130
14249
|
addDomain: "Tip: after this returns a domain id, run `primitive domains zone-file --id <domain-id> --output <domain>.zone` when the user wants an importable DNS zone file.",
|
|
14131
14250
|
verifyDomain: "Tip: if DNS is still missing, run `primitive domains zone-file --id <domain-id> --output <domain>.zone` to give the user an importable DNS zone file.",
|
|
14132
14251
|
downloadDomainZoneFile: "Tip: prefer `primitive domains zone-file --id <domain-id> --output <domain>.zone` for CLI-friendly file output.",
|
|
14133
14252
|
getInboxStatus: "Tip: prefer `primitive inbox status` for a compact readiness summary and next-step commands.",
|
|
14253
|
+
getSendPermissions: "Tip: this command answers where you may send mail to. To find usable sender domains for --from, run `primitive domains list` or `primitive inbox status` and use an address at an active verified domain.",
|
|
14134
14254
|
sendEmail: "Tip: prefer `primitive send --to <address> --body <text> --attachment <file>` for file attachments. This raw command exists for callers passing JSON.",
|
|
14135
14255
|
createFunction: "Tip: prefer `primitive functions deploy --name <name> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
14136
14256
|
updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
@@ -14233,6 +14353,10 @@ function createOperationCommand(operation) {
|
|
|
14233
14353
|
process.stderr.write(chunk);
|
|
14234
14354
|
} });
|
|
14235
14355
|
this.log(JSON.stringify(operationOutputPayload(envelope, parsedFlags.envelope === true), null, 2));
|
|
14356
|
+
if (isIncompleteDomainVerification(operation, envelope)) {
|
|
14357
|
+
writeIncompleteDomainVerificationHint();
|
|
14358
|
+
process.exitCode = 1;
|
|
14359
|
+
}
|
|
14236
14360
|
});
|
|
14237
14361
|
}
|
|
14238
14362
|
}
|
|
@@ -14348,6 +14472,13 @@ function cursorFromRows(rows) {
|
|
|
14348
14472
|
const last = rows.at(-1);
|
|
14349
14473
|
return last ? encodeReceivedAtSearchCursor(last) : null;
|
|
14350
14474
|
}
|
|
14475
|
+
function cursorFromAcceptedRows(rows) {
|
|
14476
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
14477
|
+
const row = rows[i];
|
|
14478
|
+
if (row.status === "accepted" || row.status === "completed") return encodeReceivedAtSearchCursor(row);
|
|
14479
|
+
}
|
|
14480
|
+
return null;
|
|
14481
|
+
}
|
|
14351
14482
|
function collectNewAcceptedEmails(rows, seenIds) {
|
|
14352
14483
|
const fresh = [];
|
|
14353
14484
|
for (const row of rows) {
|
|
@@ -14560,6 +14691,8 @@ function buildCommand(kind, description, argv, options = {}) {
|
|
|
14560
14691
|
}
|
|
14561
14692
|
function buildChatFollowUpCommands(context) {
|
|
14562
14693
|
const commands = [];
|
|
14694
|
+
const hasCustomStrictPhase = context.strictPhaseSeconds !== DEFAULT_STRICT_PHASE_SECONDS;
|
|
14695
|
+
const shouldPreferStrictContinuation = context.strictOnly || context.matchStrategy === "strict" && !hasCustomStrictPhase;
|
|
14563
14696
|
const continueParts = [
|
|
14564
14697
|
"primitive",
|
|
14565
14698
|
"chat",
|
|
@@ -14575,8 +14708,8 @@ function buildChatFollowUpCommands(context) {
|
|
|
14575
14708
|
];
|
|
14576
14709
|
if (context.json) continueParts.push("--json");
|
|
14577
14710
|
if (context.quiet) continueParts.push("--quiet");
|
|
14578
|
-
if (
|
|
14579
|
-
else if (
|
|
14711
|
+
if (shouldPreferStrictContinuation) continueParts.push("--strict-only");
|
|
14712
|
+
else if (hasCustomStrictPhase) continueParts.push("--strict-phase-seconds", String(context.strictPhaseSeconds));
|
|
14580
14713
|
commands.push(buildCommand("continue_chat", "Continue this chat", continueParts, { requiresMessage: true }));
|
|
14581
14714
|
commands.push(buildCommand("reply_direct", "Reply directly to the inbound email", [
|
|
14582
14715
|
"primitive",
|
|
@@ -14611,41 +14744,40 @@ function buildChatFollowUpCommands(context) {
|
|
|
14611
14744
|
return commands;
|
|
14612
14745
|
}
|
|
14613
14746
|
function buildChatRecoveryCommands(context) {
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
|
|
14622
|
-
|
|
14623
|
-
|
|
14624
|
-
|
|
14625
|
-
|
|
14626
|
-
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
];
|
|
14747
|
+
const commands = [buildCommand("wait_threaded_reply", "Wait for the threaded reply again", [
|
|
14748
|
+
"primitive",
|
|
14749
|
+
"emails",
|
|
14750
|
+
"wait",
|
|
14751
|
+
"--reply-to-sent-email-id",
|
|
14752
|
+
context.sent.id,
|
|
14753
|
+
"--to",
|
|
14754
|
+
context.from,
|
|
14755
|
+
"--since",
|
|
14756
|
+
context.sentAtIso,
|
|
14757
|
+
"--timeout",
|
|
14758
|
+
String(context.timeoutSeconds)
|
|
14759
|
+
])];
|
|
14760
|
+
if (!context.strictOnly) commands.push(buildCommand("wait_fallback_reply", "Fallback wait by sender/time window", [
|
|
14761
|
+
"primitive",
|
|
14762
|
+
"emails",
|
|
14763
|
+
"wait",
|
|
14764
|
+
"--from",
|
|
14765
|
+
context.recipient,
|
|
14766
|
+
"--to",
|
|
14767
|
+
context.from,
|
|
14768
|
+
"--since",
|
|
14769
|
+
context.sentAtIso,
|
|
14770
|
+
"--timeout",
|
|
14771
|
+
String(context.timeoutSeconds)
|
|
14772
|
+
]));
|
|
14773
|
+
commands.push(buildCommand("inspect_sent_email", "Inspect the outbound send", [
|
|
14774
|
+
"primitive",
|
|
14775
|
+
"sent",
|
|
14776
|
+
"get",
|
|
14777
|
+
"--id",
|
|
14778
|
+
context.sent.id
|
|
14779
|
+
]));
|
|
14780
|
+
return commands;
|
|
14649
14781
|
}
|
|
14650
14782
|
function buildChatJsonEnvelope(context) {
|
|
14651
14783
|
const responseBody = resolveChatResponseBody(context.reply);
|
|
@@ -14685,7 +14817,7 @@ function formatChatResponse(context) {
|
|
|
14685
14817
|
];
|
|
14686
14818
|
if (context.reply.reply_to_sent_email_id) lines.push(` Reply to sent email id: ${context.reply.reply_to_sent_email_id}`);
|
|
14687
14819
|
if (context.reply.message_id) lines.push(` Message-Id: ${context.reply.message_id}`);
|
|
14688
|
-
lines.push("", "Helpful follow-up commands", " Replace <message> before running commands that include it.", " Commands are templates; use --json for parse-safe output.");
|
|
14820
|
+
lines.push("", "Helpful follow-up commands", " Replace <message> before running commands that include it.", " Commands are templates; use --json for parse-safe output.", " When shown, --strict-only prefers timing out over matching the wrong reply.");
|
|
14689
14821
|
for (const { description, command } of buildChatFollowUpCommands(context)) lines.push(` ${description}:`, ` ${command}`);
|
|
14690
14822
|
lines.push("", `Response body (${responseBody.format}; use --json for parsing)`, "----- BEGIN RESPONSE -----", responseBody.body || "(empty response)", "----- END RESPONSE -----");
|
|
14691
14823
|
return lines.join("\n");
|
|
@@ -14785,13 +14917,14 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14785
14917
|
--strict-only is not set, it falls back to a weaker sender/time
|
|
14786
14918
|
window match: from=<recipient>, to=<sender>, and since=<send time>.
|
|
14787
14919
|
The fallback can catch clients that strip threading headers, but it
|
|
14788
|
-
is less exact than strict matching.
|
|
14789
|
-
|
|
14790
|
-
|
|
14920
|
+
is less exact than strict matching. Use --strict-only when matching
|
|
14921
|
+
the wrong reply is worse than timing out. Progress is written to
|
|
14922
|
+
stderr while the CLI waits. Exits non-zero on timeout and prints
|
|
14923
|
+
recovery commands when the send succeeded but no reply was returned.`;
|
|
14791
14924
|
static summary = "Chat with an agent over email (send and wait for the reply)";
|
|
14792
14925
|
static examples = [
|
|
14793
14926
|
"<%= config.bin %> chat help@agent.acme.dev 'how do I rotate my API key?'",
|
|
14794
|
-
"cat error.log | <%= config.bin %> chat help@agent.acme.dev
|
|
14927
|
+
"cat error.log | <%= config.bin %> chat help@agent.acme.dev",
|
|
14795
14928
|
"<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing'",
|
|
14796
14929
|
"<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing' --reply-to-email-id <inbound-email-id>",
|
|
14797
14930
|
"<%= config.bin %> chat help@agent.acme.dev 'follow up question' --json",
|
|
@@ -14820,7 +14953,10 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14820
14953
|
hidden: true
|
|
14821
14954
|
}),
|
|
14822
14955
|
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
14823
|
-
subject: Flags.string({
|
|
14956
|
+
subject: Flags.string({
|
|
14957
|
+
description: "Advanced email transport override. Usually omit; chat threading does not depend on the subject.",
|
|
14958
|
+
hidden: true
|
|
14959
|
+
}),
|
|
14824
14960
|
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." }),
|
|
14825
14961
|
"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." }),
|
|
14826
14962
|
"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." }),
|
|
@@ -14836,7 +14972,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14836
14972
|
description: "Seconds to wait in strict-threading mode (filter by reply_to_sent_email_id) before falling back to time-window matching. Set to the full --timeout to disable the fallback; --strict-only is the explicit way to do that.",
|
|
14837
14973
|
min: 1
|
|
14838
14974
|
}),
|
|
14839
|
-
"strict-only": Flags.boolean({ description: "Disable the time-window fallback. Only accept inbounds whose threading headers (In-Reply-To / References) resolve to this send.
|
|
14975
|
+
"strict-only": Flags.boolean({ description: "Disable the time-window fallback. Only accept inbounds whose threading headers (In-Reply-To / References) resolve to this send. Use when matching the wrong reply is worse than timing out." }),
|
|
14840
14976
|
interval: Flags.integer({
|
|
14841
14977
|
default: 2,
|
|
14842
14978
|
description: "Seconds between polls while waiting for the reply.",
|
|
@@ -15127,6 +15263,38 @@ function redactConfig(config) {
|
|
|
15127
15263
|
environments: Object.fromEntries(Object.entries(config.environments).map(([name, environment]) => [name, redactCliEnvironment(environment)]))
|
|
15128
15264
|
};
|
|
15129
15265
|
}
|
|
15266
|
+
function upsertCliEnvironmentAndClearCredentialsIfSwitched(params) {
|
|
15267
|
+
const previousConfig = loadOrCreateConfig(params.configDir);
|
|
15268
|
+
const previousActiveEnvironment = resolveConfigEnvironment(previousConfig);
|
|
15269
|
+
const previousEnvironment = previousActiveEnvironment?.name ?? null;
|
|
15270
|
+
const config = upsertCliEnvironment({
|
|
15271
|
+
apiBaseUrl1: params.apiBaseUrl1,
|
|
15272
|
+
apiBaseUrl2: params.apiBaseUrl2,
|
|
15273
|
+
config: previousConfig,
|
|
15274
|
+
environmentName: params.environmentName,
|
|
15275
|
+
headers: params.headers,
|
|
15276
|
+
unsetHeaders: params.unsetHeaders
|
|
15277
|
+
});
|
|
15278
|
+
const activeEnvironment = resolveConfigEnvironment(config);
|
|
15279
|
+
const environment = activeEnvironment?.name ?? null;
|
|
15280
|
+
const shouldClearCredentials = existsSync(credentialsPath(params.configDir)) && (previousEnvironment !== environment || previousActiveEnvironment?.config.api_base_url_1 !== activeEnvironment?.config.api_base_url_1);
|
|
15281
|
+
let removedCredentials = false;
|
|
15282
|
+
if (shouldClearCredentials) {
|
|
15283
|
+
const releaseLock = acquireCliCredentialsLock(params.configDir);
|
|
15284
|
+
try {
|
|
15285
|
+
saveCliConfig(params.configDir, config);
|
|
15286
|
+
removedCredentials = existsSync(credentialsPath(params.configDir));
|
|
15287
|
+
deleteCliCredentials(params.configDir);
|
|
15288
|
+
} finally {
|
|
15289
|
+
releaseLock();
|
|
15290
|
+
}
|
|
15291
|
+
} else saveCliConfig(params.configDir, config);
|
|
15292
|
+
return {
|
|
15293
|
+
environment,
|
|
15294
|
+
previousEnvironment,
|
|
15295
|
+
removedCredentials
|
|
15296
|
+
};
|
|
15297
|
+
}
|
|
15130
15298
|
function switchCliEnvironment(configDir, environmentName) {
|
|
15131
15299
|
const environment = normalizeCliEnvironmentName(environmentName);
|
|
15132
15300
|
const config = loadOrCreateConfig(configDir);
|
|
@@ -15176,16 +15344,16 @@ var ConfigSetCommand = class ConfigSetCommand extends Command {
|
|
|
15176
15344
|
const { flags } = await this.parse(ConfigSetCommand);
|
|
15177
15345
|
const headers = flags.header ?? [];
|
|
15178
15346
|
if (flags["api-base-url-1"] === void 0 && flags["api-base-url-2"] === void 0 && headers.length === 0 && (flags["unset-header"] ?? []).length === 0) throw new Errors.CLIError("Nothing to set. Pass an API base URL, --header, or --unset-header.", { exit: 1 });
|
|
15179
|
-
const
|
|
15347
|
+
const { environment, removedCredentials } = upsertCliEnvironmentAndClearCredentialsIfSwitched({
|
|
15180
15348
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
15181
15349
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
15182
|
-
|
|
15350
|
+
configDir: this.config.configDir,
|
|
15183
15351
|
environmentName: flags.environment,
|
|
15184
15352
|
headers,
|
|
15185
15353
|
unsetHeaders: flags["unset-header"]
|
|
15186
15354
|
});
|
|
15187
|
-
|
|
15188
|
-
process.stderr.write(
|
|
15355
|
+
process.stderr.write(`Primitive CLI environment ${environment} is active.\n`);
|
|
15356
|
+
if (removedCredentials) process.stderr.write("Removed saved Primitive CLI credentials. Run `primitive signin` to authenticate in the active environment.\n");
|
|
15189
15357
|
}
|
|
15190
15358
|
};
|
|
15191
15359
|
var ConfigUseCommand = class ConfigUseCommand extends Command {
|
|
@@ -15875,7 +16043,9 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
15875
16043
|
process.exitCode = 1;
|
|
15876
16044
|
return;
|
|
15877
16045
|
}
|
|
15878
|
-
|
|
16046
|
+
const nextCursor = cursorFromAcceptedRows(page.rows);
|
|
16047
|
+
const cursorAdvanced = Boolean(nextCursor && nextCursor !== cursor);
|
|
16048
|
+
if (nextCursor) cursor = nextCursor;
|
|
15879
16049
|
for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
|
|
15880
16050
|
if (flags.table) {
|
|
15881
16051
|
if (!headerPrinted) {
|
|
@@ -15887,7 +16057,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
15887
16057
|
matched += 1;
|
|
15888
16058
|
if (matched >= flags.number) return;
|
|
15889
16059
|
}
|
|
15890
|
-
if (
|
|
16060
|
+
if (cursorAdvanced) continue;
|
|
15891
16061
|
if (deadline !== null && Date.now() >= deadline) break;
|
|
15892
16062
|
await sleep$1(flags.interval * 1e3);
|
|
15893
16063
|
}
|
|
@@ -15997,7 +16167,9 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
15997
16167
|
process.exitCode = 1;
|
|
15998
16168
|
return;
|
|
15999
16169
|
}
|
|
16000
|
-
|
|
16170
|
+
const nextCursor = cursorFromAcceptedRows(page.rows);
|
|
16171
|
+
const cursorAdvanced = Boolean(nextCursor && nextCursor !== cursor);
|
|
16172
|
+
if (nextCursor) cursor = nextCursor;
|
|
16001
16173
|
for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
|
|
16002
16174
|
if (flags.jsonl) this.log(JSON.stringify(email));
|
|
16003
16175
|
else {
|
|
@@ -16010,7 +16182,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
16010
16182
|
printed += 1;
|
|
16011
16183
|
if (flags.number && printed >= flags.number) return;
|
|
16012
16184
|
}
|
|
16013
|
-
if (
|
|
16185
|
+
if (cursorAdvanced) continue;
|
|
16014
16186
|
if (deadline !== null && Date.now() >= deadline) break;
|
|
16015
16187
|
await sleep$1(flags.interval * 1e3);
|
|
16016
16188
|
}
|
|
@@ -16765,8 +16937,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
16765
16937
|
name: "Primitive Team",
|
|
16766
16938
|
url: "https://primitive.dev"
|
|
16767
16939
|
};
|
|
16768
|
-
const SDK_VERSION_RANGE = "^0.
|
|
16769
|
-
const CLI_VERSION_RANGE = "^0.
|
|
16940
|
+
const SDK_VERSION_RANGE = "^0.32.0";
|
|
16941
|
+
const CLI_VERSION_RANGE = "^0.32.1";
|
|
16770
16942
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
16771
16943
|
function renderHandler() {
|
|
16772
16944
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -16792,49 +16964,93 @@ interface Env {
|
|
|
16792
16964
|
PRIMITIVE_WEBHOOK_SECRET: string;
|
|
16793
16965
|
}
|
|
16794
16966
|
|
|
16795
|
-
//
|
|
16796
|
-
//
|
|
16797
|
-
//
|
|
16798
|
-
// you later switch to
|
|
16799
|
-
|
|
16800
|
-
|
|
16967
|
+
// Optional loop-protection knob. client.reply() server-defaults the
|
|
16968
|
+
// outbound from-address from the inbound recipient, so most handlers
|
|
16969
|
+
// do not need to fill this in. Add any extra addresses your handler
|
|
16970
|
+
// sends from if you later switch to client.send or a custom domain.
|
|
16971
|
+
const EXTRA_SELF_ADDRESSES: string[] = [
|
|
16972
|
+
// "bot@your-domain.example",
|
|
16973
|
+
];
|
|
16974
|
+
|
|
16975
|
+
function extractEmailAddresses(value: string | null | undefined): string[] {
|
|
16976
|
+
return (
|
|
16977
|
+
value?.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi)?.map((address) =>
|
|
16978
|
+
address.toLowerCase(),
|
|
16979
|
+
) ?? []
|
|
16980
|
+
);
|
|
16981
|
+
}
|
|
16982
|
+
|
|
16983
|
+
function domainPart(address: string): string | null {
|
|
16984
|
+
const at = address.lastIndexOf("@");
|
|
16985
|
+
return at === -1 ? null : address.slice(at + 1).toLowerCase();
|
|
16986
|
+
}
|
|
16987
|
+
|
|
16988
|
+
function localPart(address: string): string {
|
|
16989
|
+
const at = address.lastIndexOf("@");
|
|
16990
|
+
return at === -1 ? address.toLowerCase() : address.slice(0, at).toLowerCase();
|
|
16991
|
+
}
|
|
16992
|
+
|
|
16993
|
+
function inboundRecipientAddresses(event: EmailReceivedEvent): string[] {
|
|
16994
|
+
return [
|
|
16995
|
+
...event.email.smtp.rcpt_to.flatMap(extractEmailAddresses),
|
|
16996
|
+
...extractEmailAddresses(event.email.headers.to),
|
|
16997
|
+
];
|
|
16998
|
+
}
|
|
16999
|
+
|
|
17000
|
+
function inboundRecipientDomains(event: EmailReceivedEvent): Set<string> {
|
|
17001
|
+
return new Set(
|
|
17002
|
+
inboundRecipientAddresses(event)
|
|
17003
|
+
.map(domainPart)
|
|
17004
|
+
.filter((domain): domain is string => domain !== null),
|
|
17005
|
+
);
|
|
17006
|
+
}
|
|
16801
17007
|
|
|
16802
17008
|
// Loop protection. A newly deployed Function starts as a fallback
|
|
16803
|
-
// endpoint for managed
|
|
16804
|
-
//
|
|
16805
|
-
//
|
|
16806
|
-
//
|
|
17009
|
+
// endpoint for managed domains and can receive bounces or auto-replies
|
|
17010
|
+
// generated by its own outbound mail. Do not hardcode a managed suffix:
|
|
17011
|
+
// staging, production, and custom domains all arrive through the same
|
|
17012
|
+
// webhook shape. Derive the handler's inbound domains from the actual
|
|
17013
|
+
// SMTP recipients instead.
|
|
16807
17014
|
//
|
|
16808
|
-
// The default check
|
|
16809
|
-
//
|
|
16810
|
-
//
|
|
16811
|
-
//
|
|
16812
|
-
// Substring matching is deliberate so display-name forms like
|
|
16813
|
-
// "Support <support@example.com>" match a bare-address REPLY_FROM,
|
|
16814
|
-
// but it also accepts false positives where REPLY_FROM is a suffix
|
|
16815
|
-
// of another address (e.g. REPLY_FROM="info@x.com" matches
|
|
16816
|
-
// "mr.info@x.com"). For strict equality, parse the address out of the
|
|
16817
|
-
// header and exact-match against REPLY_FROM.
|
|
17015
|
+
// The default check skips:
|
|
17016
|
+
// - direct self-mail where From equals one of the inbound recipients;
|
|
17017
|
+
// - mailer-daemon/postmaster bounces from the same domain as the inbound;
|
|
17018
|
+
// - any address explicitly listed in EXTRA_SELF_ADDRESSES.
|
|
16818
17019
|
//
|
|
16819
17020
|
// Extend this helper if you need stricter detection. Common additions:
|
|
16820
|
-
// - Match the org's signup / account-owner email (not auto-injected
|
|
16821
|
-
// into env today; either bake it into a SIGNUP_EMAIL const or read
|
|
16822
|
-
// it from a secret you set via \`primitive functions:set-secret\`).
|
|
16823
17021
|
// - Honor RFC 3834 auto-response headers: skip when
|
|
16824
|
-
//
|
|
16825
|
-
//
|
|
16826
|
-
// is present.
|
|
17022
|
+
// event.email.headers["auto-submitted"] is anything other than "no",
|
|
17023
|
+
// or when a List-Unsubscribe / Precedence: bulk header is present.
|
|
16827
17024
|
// - Track Message-ID / In-Reply-To chains to break ping-pong loops
|
|
16828
17025
|
// between two cooperating handlers on different domains.
|
|
16829
17026
|
export function isLoop(event: EmailReceivedEvent): boolean {
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
17027
|
+
const fromAddresses = [
|
|
17028
|
+
...extractEmailAddresses(event.email.headers.from),
|
|
17029
|
+
...extractEmailAddresses(event.email.smtp.mail_from),
|
|
17030
|
+
];
|
|
17031
|
+
if (fromAddresses.length === 0) return false;
|
|
17032
|
+
|
|
17033
|
+
const inboundAddresses = new Set(inboundRecipientAddresses(event));
|
|
17034
|
+
const inboundDomains = inboundRecipientDomains(event);
|
|
17035
|
+
const extraSelfAddresses = new Set(
|
|
17036
|
+
EXTRA_SELF_ADDRESSES.map((address) => address.toLowerCase()),
|
|
17037
|
+
);
|
|
17038
|
+
|
|
17039
|
+
for (const from of fromAddresses) {
|
|
17040
|
+
if (inboundAddresses.has(from)) return true;
|
|
17041
|
+
if (extraSelfAddresses.has(from)) return true;
|
|
17042
|
+
|
|
17043
|
+
const fromDomain = domainPart(from);
|
|
17044
|
+
const fromLocal = localPart(from);
|
|
17045
|
+
const sameInboundDomain = fromDomain ? inboundDomains.has(fromDomain) : false;
|
|
17046
|
+
if (
|
|
17047
|
+
sameInboundDomain &&
|
|
17048
|
+
(fromLocal === "mailer-daemon" || fromLocal === "postmaster")
|
|
17049
|
+
) {
|
|
17050
|
+
return true;
|
|
17051
|
+
}
|
|
17052
|
+
}
|
|
17053
|
+
|
|
16838
17054
|
return false;
|
|
16839
17055
|
}
|
|
16840
17056
|
|
|
@@ -16882,11 +17098,16 @@ export default {
|
|
|
16882
17098
|
apiBaseUrl1: env.PRIMITIVE_API_BASE_URL,
|
|
16883
17099
|
});
|
|
16884
17100
|
|
|
17101
|
+
// To add an LLM or another API, store its key as a Function secret.
|
|
17102
|
+
// Example:
|
|
17103
|
+
// export OPENAI_KEY=sk-...
|
|
17104
|
+
// primitive functions set-secret --id <fn-id> --key OPENAI_KEY --value-from-env OPENAI_KEY --redeploy
|
|
17105
|
+
|
|
16885
17106
|
// Recipient gate
|
|
16886
17107
|
// https://www.primitive.dev/docs/sending#who-you-can-send-to
|
|
16887
17108
|
// Even via client.reply, sends to the original sender are
|
|
16888
17109
|
// subject to the recipient gate. New accounts can send to
|
|
16889
|
-
//
|
|
17110
|
+
// Primitive-managed domains, verified domains, addresses that
|
|
16890
17111
|
// have authenticated to you, and other org-member signup emails.
|
|
16891
17112
|
// Sends to arbitrary external addresses return 403
|
|
16892
17113
|
// recipient_not_allowed with a structured gates[] array until
|
|
@@ -16936,8 +17157,10 @@ function renderPackageJson(name) {
|
|
|
16936
17157
|
type: "module",
|
|
16937
17158
|
scripts: {
|
|
16938
17159
|
build: "node build.mjs",
|
|
16939
|
-
deploy: `npm run build && primitive functions deploy --name ${name} --file ./dist/handler.js`,
|
|
16940
|
-
|
|
17160
|
+
deploy: `npm run build && primitive functions deploy --name ${name} --file ./dist/handler.js --wait`,
|
|
17161
|
+
"test:function": "primitive functions test --id $PRIMITIVE_FUNCTION_ID --wait --show-sends",
|
|
17162
|
+
logs: "primitive functions logs --id $PRIMITIVE_FUNCTION_ID",
|
|
17163
|
+
redeploy: "npm run build && primitive functions redeploy --id $PRIMITIVE_FUNCTION_ID --file ./dist/handler.js --wait"
|
|
16941
17164
|
},
|
|
16942
17165
|
dependencies: { "@primitivedotdev/sdk": SDK_VERSION_RANGE },
|
|
16943
17166
|
devDependencies: {
|
|
@@ -17008,13 +17231,47 @@ npm run build
|
|
|
17008
17231
|
npm run deploy
|
|
17009
17232
|
\`\`\`
|
|
17010
17233
|
|
|
17011
|
-
The deploy step calls \`primitive functions deploy\` (provided
|
|
17012
|
-
\`@primitivedotdev/cli\` package; install with
|
|
17234
|
+
The deploy step calls \`primitive functions deploy --wait\` (provided
|
|
17235
|
+
by the \`@primitivedotdev/cli\` package; install with
|
|
17013
17236
|
\`npm install -g @primitivedotdev/cli\` or run via
|
|
17014
17237
|
\`npx @primitivedotdev/cli@latest <command>\`). It requires
|
|
17015
17238
|
\`PRIMITIVE_API_KEY\` to be set in your shell (or pass \`--api-key\`).
|
|
17016
17239
|
Run \`primitive signin\` once to save a key in your CLI config if you
|
|
17017
17240
|
prefer that to an env var.
|
|
17241
|
+
|
|
17242
|
+
After the first deploy, copy the returned function id into your shell:
|
|
17243
|
+
|
|
17244
|
+
\`\`\`
|
|
17245
|
+
export PRIMITIVE_FUNCTION_ID=<fn-id>
|
|
17246
|
+
\`\`\`
|
|
17247
|
+
|
|
17248
|
+
## Prove it works
|
|
17249
|
+
|
|
17250
|
+
\`\`\`
|
|
17251
|
+
primitive inbox status
|
|
17252
|
+
npm run test:function
|
|
17253
|
+
npm run logs
|
|
17254
|
+
\`\`\`
|
|
17255
|
+
|
|
17256
|
+
\`npm run test:function\` sends a real test email through MX, waits for
|
|
17257
|
+
the Function to process it, and prints any outbound replies emitted by
|
|
17258
|
+
the handler.
|
|
17259
|
+
|
|
17260
|
+
## Redeploy
|
|
17261
|
+
|
|
17262
|
+
\`\`\`
|
|
17263
|
+
npm run redeploy
|
|
17264
|
+
\`\`\`
|
|
17265
|
+
|
|
17266
|
+
## Secrets
|
|
17267
|
+
|
|
17268
|
+
Use secrets for API keys used by your handler. \`--redeploy\` makes the
|
|
17269
|
+
new value visible to the running Function immediately.
|
|
17270
|
+
|
|
17271
|
+
\`\`\`
|
|
17272
|
+
export OPENAI_KEY=sk-...
|
|
17273
|
+
primitive functions set-secret --id "$PRIMITIVE_FUNCTION_ID" --key OPENAI_KEY --value-from-env OPENAI_KEY --redeploy
|
|
17274
|
+
\`\`\`
|
|
17018
17275
|
`;
|
|
17019
17276
|
}
|
|
17020
17277
|
function renderEmailReplyTemplateFiles(name) {
|
|
@@ -17189,8 +17446,9 @@ var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
|
17189
17446
|
this.log("Next:");
|
|
17190
17447
|
this.log(` cd ${outDir}`);
|
|
17191
17448
|
this.log(" npm install");
|
|
17192
|
-
this.log(" npm run
|
|
17193
|
-
this.log(
|
|
17449
|
+
this.log(" npm run deploy");
|
|
17450
|
+
this.log(" export PRIMITIVE_FUNCTION_ID=<id-from-deploy-output>");
|
|
17451
|
+
this.log(" npm run test:function");
|
|
17194
17452
|
}
|
|
17195
17453
|
};
|
|
17196
17454
|
//#endregion
|
|
@@ -17820,26 +18078,32 @@ var FunctionsTemplatesCommand = class FunctionsTemplatesCommand extends Command
|
|
|
17820
18078
|
//#endregion
|
|
17821
18079
|
//#region src/oclif/commands/functions-test-function.ts
|
|
17822
18080
|
const DEFAULT_WAIT_TIMEOUT_SECONDS = 60;
|
|
17823
|
-
const
|
|
18081
|
+
const TERMINAL_TEST_TRACE_STATES = new Set([
|
|
18082
|
+
"completed",
|
|
18083
|
+
"failed",
|
|
18084
|
+
"send_failed"
|
|
18085
|
+
]);
|
|
17824
18086
|
function buildFunctionTestOutcome(params) {
|
|
18087
|
+
const inbound = params.trace.inbound_email;
|
|
17825
18088
|
const outcome = {
|
|
17826
18089
|
elapsed_seconds: params.elapsedSeconds,
|
|
17827
18090
|
function_id: params.functionId,
|
|
17828
18091
|
inbound_domain: params.invocation.inbound_domain,
|
|
17829
|
-
inbound_id:
|
|
18092
|
+
inbound_id: inbound?.id ?? null,
|
|
17830
18093
|
inbound_to: params.invocation.to,
|
|
17831
18094
|
poll_since: params.invocation.poll_since,
|
|
18095
|
+
state: params.trace.state,
|
|
17832
18096
|
test_run_id: params.invocation.test_run_id,
|
|
17833
18097
|
test_send_id: params.invocation.send_id,
|
|
17834
18098
|
test_subject: params.invocation.subject,
|
|
17835
18099
|
trace_url: params.invocation.trace_url,
|
|
17836
18100
|
watch_url: params.invocation.watch_url,
|
|
17837
|
-
webhook_attempt_count:
|
|
17838
|
-
webhook_last_error:
|
|
17839
|
-
webhook_last_status_code:
|
|
17840
|
-
webhook_status:
|
|
18101
|
+
webhook_attempt_count: inbound?.webhook_attempt_count ?? null,
|
|
18102
|
+
webhook_last_error: inbound?.webhook_last_error ?? null,
|
|
18103
|
+
webhook_last_status_code: inbound?.webhook_last_status_code ?? null,
|
|
18104
|
+
webhook_status: inbound?.webhook_status ?? null
|
|
17841
18105
|
};
|
|
17842
|
-
if (params.showSends) outcome.sent_emails = params.
|
|
18106
|
+
if (params.showSends) outcome.sent_emails = params.trace.replies;
|
|
17843
18107
|
return outcome;
|
|
17844
18108
|
}
|
|
17845
18109
|
function writeFunctionTestProgress(message, writeStderr = (chunk) => {
|
|
@@ -17938,7 +18202,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
17938
18202
|
required: true
|
|
17939
18203
|
}),
|
|
17940
18204
|
"local-part": Flags.string({ description: "Override the synthetic local-part the test inbound is addressed to. Otherwise the runtime picks `__primitive_function_test+<random>`." }),
|
|
17941
|
-
wait: Flags.boolean({ description: "Block until the function
|
|
18205
|
+
wait: Flags.boolean({ description: "Block until the function test run reaches `completed`, `failed`, or `send_failed`, or --timeout elapses. Exits non-zero on timeout or terminal failure." }),
|
|
17942
18206
|
"show-sends": Flags.boolean({ description: "When the wait resolves, also print the outbound emails the function emitted while processing the test inbound (id, status, to, subject). Implies --wait." }),
|
|
17943
18207
|
timeout: Flags.integer({
|
|
17944
18208
|
default: DEFAULT_WAIT_TIMEOUT_SECONDS,
|
|
@@ -17998,41 +18262,15 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
17998
18262
|
const timeoutMs = flags.timeout * 1e3;
|
|
17999
18263
|
const pollIntervalMs = flags["poll-interval"] * 1e3;
|
|
18000
18264
|
const isExpired = () => flags.timeout > 0 && Date.now() - startedAt > timeoutMs;
|
|
18001
|
-
writeFunctionTestProgress(`Waiting for test
|
|
18002
|
-
let
|
|
18003
|
-
while (!isExpired()) {
|
|
18004
|
-
const page = await fetchEmailSearchPage({
|
|
18005
|
-
apiClient,
|
|
18006
|
-
filters: { to: invocation.to },
|
|
18007
|
-
pageSize: 25,
|
|
18008
|
-
since: invocation.poll_since
|
|
18009
|
-
});
|
|
18010
|
-
if (!page.ok) {
|
|
18011
|
-
const payload = extractErrorPayload(page.error);
|
|
18012
|
-
writeErrorWithHints(payload);
|
|
18013
|
-
surfaceUnauthorizedHint({
|
|
18014
|
-
auth,
|
|
18015
|
-
baseUrlOverridden,
|
|
18016
|
-
configDir: this.config.configDir,
|
|
18017
|
-
payload
|
|
18018
|
-
});
|
|
18019
|
-
process.exitCode = 1;
|
|
18020
|
-
return;
|
|
18021
|
-
}
|
|
18022
|
-
const found = page.rows[0];
|
|
18023
|
-
if (found) {
|
|
18024
|
-
inboundId = found.id;
|
|
18025
|
-
break;
|
|
18026
|
-
}
|
|
18027
|
-
await sleep$1(pollIntervalMs);
|
|
18028
|
-
}
|
|
18029
|
-
if (!inboundId) 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 });
|
|
18030
|
-
writeFunctionTestProgress(`Inbound landed (${inboundId}). Waiting for function to run...`);
|
|
18031
|
-
let detail;
|
|
18265
|
+
writeFunctionTestProgress(`Waiting for test run ${invocation.test_run_id} to complete for ${invocation.to}...`);
|
|
18266
|
+
let trace;
|
|
18032
18267
|
while (!isExpired()) {
|
|
18033
|
-
const result = await
|
|
18268
|
+
const result = await getFunctionTestRunTrace({
|
|
18034
18269
|
client: apiClient.client,
|
|
18035
|
-
path: {
|
|
18270
|
+
path: {
|
|
18271
|
+
id: flags.id,
|
|
18272
|
+
run_id: invocation.test_run_id
|
|
18273
|
+
},
|
|
18036
18274
|
responseStyle: "fields"
|
|
18037
18275
|
});
|
|
18038
18276
|
if (result.error) {
|
|
@@ -18048,24 +18286,22 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
18048
18286
|
return;
|
|
18049
18287
|
}
|
|
18050
18288
|
const fetched = result.data.data;
|
|
18051
|
-
if (
|
|
18052
|
-
|
|
18289
|
+
if (TERMINAL_TEST_TRACE_STATES.has(fetched.state)) {
|
|
18290
|
+
trace = fetched;
|
|
18053
18291
|
break;
|
|
18054
18292
|
}
|
|
18055
18293
|
await sleep$1(pollIntervalMs);
|
|
18056
18294
|
}
|
|
18057
|
-
if (!
|
|
18058
|
-
const elapsedSeconds = Math.round((Date.now() - startedAt) / 1e3);
|
|
18295
|
+
if (!trace) this.error(`Timed out after ${flags.timeout}s waiting for function test run ${invocation.test_run_id} to complete. Browse ${invocation.watch_url} for the live view, or inspect ${invocation.trace_url}.`, { exit: 2 });
|
|
18059
18296
|
const outcome = buildFunctionTestOutcome({
|
|
18060
|
-
|
|
18061
|
-
elapsedSeconds,
|
|
18297
|
+
elapsedSeconds: Math.round((Date.now() - startedAt) / 1e3),
|
|
18062
18298
|
functionId: flags.id,
|
|
18063
|
-
inboundId,
|
|
18064
18299
|
invocation,
|
|
18065
|
-
showSends: shouldShowSends
|
|
18300
|
+
showSends: shouldShowSends,
|
|
18301
|
+
trace
|
|
18066
18302
|
});
|
|
18067
18303
|
this.log(JSON.stringify(outcome, null, 2));
|
|
18068
|
-
if (
|
|
18304
|
+
if (trace.state === "failed" || trace.state === "send_failed") process.exitCode = 1;
|
|
18069
18305
|
});
|
|
18070
18306
|
}
|
|
18071
18307
|
};
|
|
@@ -18316,7 +18552,7 @@ async function checkExistingLogin(params) {
|
|
|
18316
18552
|
message: code === API_ERROR_CODES.unauthorized ? "Saved Primitive CLI OAuth credentials were rejected by an API URL different from the one they were saved with. Run `primitive logout` to remove them, or switch back to the original environment before logging in again." : "A saved Primitive CLI OAuth session exists, but the CLI could not verify whether it is still valid. Run `primitive logout` before logging in again."
|
|
18317
18553
|
};
|
|
18318
18554
|
}
|
|
18319
|
-
var LoginCommand = class extends Command {
|
|
18555
|
+
var LoginCommand$1 = class extends Command {
|
|
18320
18556
|
static description = "Log in by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
|
|
18321
18557
|
static summary = "Log in with browser approval";
|
|
18322
18558
|
static examples = [
|
|
@@ -18340,6 +18576,9 @@ var LoginCommand = class extends Command {
|
|
|
18340
18576
|
async run() {
|
|
18341
18577
|
const commandClass = this.constructor;
|
|
18342
18578
|
const { flags } = await this.parse(commandClass);
|
|
18579
|
+
await this.runBrowserLogin(flags, this.retryCommand());
|
|
18580
|
+
}
|
|
18581
|
+
async runBrowserLogin(flags, retryCommand = this.retryCommand()) {
|
|
18343
18582
|
let releaseCredentialsLock;
|
|
18344
18583
|
try {
|
|
18345
18584
|
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
@@ -18347,7 +18586,7 @@ var LoginCommand = class extends Command {
|
|
|
18347
18586
|
throw cliError$3(error instanceof Error ? error.message : String(error));
|
|
18348
18587
|
}
|
|
18349
18588
|
try {
|
|
18350
|
-
await this.runWithCredentialLock(flags,
|
|
18589
|
+
await this.runWithCredentialLock(flags, retryCommand);
|
|
18351
18590
|
} finally {
|
|
18352
18591
|
releaseCredentialsLock();
|
|
18353
18592
|
}
|
|
@@ -18455,520 +18694,85 @@ var LoginCommand = class extends Command {
|
|
|
18455
18694
|
}
|
|
18456
18695
|
};
|
|
18457
18696
|
//#endregion
|
|
18458
|
-
//#region src/oclif/commands/
|
|
18697
|
+
//#region src/oclif/commands/signup.ts
|
|
18698
|
+
const INVALID_VERIFICATION_CODE = "invalid_verification_code";
|
|
18699
|
+
const EXPIRED_TOKEN = "expired_token";
|
|
18700
|
+
const INVALID_SIGNUP_TOKEN = "invalid_signup_token";
|
|
18701
|
+
const SLOW_DOWN = "slow_down";
|
|
18702
|
+
const PENDING_SIGNUP_FILE = "signup.json";
|
|
18703
|
+
const DEFAULT_SIGNUP_COMMAND_COPY = {
|
|
18704
|
+
actionNoun: "signup",
|
|
18705
|
+
actionGerund: "creating a new account",
|
|
18706
|
+
confirmCommand: (email) => `signup confirm ${email} <code>`,
|
|
18707
|
+
resendCommand: (email) => `signup resend ${email}`,
|
|
18708
|
+
startCommand: (email) => `signup ${email}`
|
|
18709
|
+
};
|
|
18459
18710
|
function cliError$2(message) {
|
|
18460
18711
|
return new Errors.CLIError(message, { exit: 1 });
|
|
18461
18712
|
}
|
|
18462
18713
|
function unwrapData$1(value) {
|
|
18463
18714
|
return value?.data ?? null;
|
|
18464
18715
|
}
|
|
18465
|
-
function
|
|
18466
|
-
return
|
|
18716
|
+
function isRecord(value) {
|
|
18717
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18467
18718
|
}
|
|
18468
|
-
|
|
18469
|
-
|
|
18470
|
-
|
|
18471
|
-
|
|
18472
|
-
|
|
18719
|
+
function normalizeEmail(email) {
|
|
18720
|
+
return email.trim().toLowerCase();
|
|
18721
|
+
}
|
|
18722
|
+
function pendingSignupFromJson(value) {
|
|
18723
|
+
if (!isRecord(value)) return null;
|
|
18724
|
+
if (typeof value.signup_token !== "string" || typeof value.email !== "string" || typeof value.expires_in !== "number" || typeof value.resend_after !== "number" || typeof value.verification_code_length !== "number" || typeof value.api_base_url_1 !== "string" || typeof value.created_at !== "string" || typeof value.expires_at !== "string") return null;
|
|
18725
|
+
return {
|
|
18726
|
+
api_base_url_1: value.api_base_url_1,
|
|
18727
|
+
created_at: value.created_at,
|
|
18728
|
+
email: value.email,
|
|
18729
|
+
expires_at: value.expires_at,
|
|
18730
|
+
expires_in: value.expires_in,
|
|
18731
|
+
resend_after: value.resend_after,
|
|
18732
|
+
signup_token: value.signup_token,
|
|
18733
|
+
verification_code_length: value.verification_code_length
|
|
18473
18734
|
};
|
|
18474
|
-
|
|
18735
|
+
}
|
|
18736
|
+
function pendingSignupPath(configDir) {
|
|
18737
|
+
return join(configDir, PENDING_SIGNUP_FILE);
|
|
18738
|
+
}
|
|
18739
|
+
function deletePendingAgentSignup(configDir) {
|
|
18740
|
+
rmSync(pendingSignupPath(configDir), { force: true });
|
|
18741
|
+
}
|
|
18742
|
+
function pendingSignupFromStart(start, apiBaseUrl1) {
|
|
18743
|
+
return {
|
|
18744
|
+
...start,
|
|
18745
|
+
api_base_url_1: apiBaseUrl1,
|
|
18746
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18747
|
+
expires_at: new Date(Date.now() + start.expires_in * 1e3).toISOString()
|
|
18748
|
+
};
|
|
18749
|
+
}
|
|
18750
|
+
function savePendingAgentSignup(configDir, start, apiBaseUrl1) {
|
|
18751
|
+
mkdirSync(configDir, {
|
|
18752
|
+
mode: 448,
|
|
18753
|
+
recursive: true
|
|
18754
|
+
});
|
|
18755
|
+
const pending = pendingSignupFromStart(start, apiBaseUrl1);
|
|
18756
|
+
const path = pendingSignupPath(configDir);
|
|
18757
|
+
const tempPath = join(configDir, `${PENDING_SIGNUP_FILE}.${process$1.pid}.${randomUUID()}.tmp`);
|
|
18475
18758
|
try {
|
|
18476
|
-
|
|
18759
|
+
writeFileSync(tempPath, `${JSON.stringify(pending, null, 2)}\n`, { mode: 384 });
|
|
18760
|
+
chmodSync(tempPath, 384);
|
|
18761
|
+
renameSync(tempPath, path);
|
|
18762
|
+
chmodSync(path, 384);
|
|
18763
|
+
return pending;
|
|
18477
18764
|
} catch (error) {
|
|
18478
|
-
|
|
18479
|
-
|
|
18480
|
-
process.stderr.write(`Removed unreadable Primitive CLI credentials. Backing OAuth grant was not revoked: ${detail}\n`);
|
|
18481
|
-
process.exitCode = 1;
|
|
18482
|
-
return;
|
|
18765
|
+
rmSync(tempPath, { force: true });
|
|
18766
|
+
throw error;
|
|
18483
18767
|
}
|
|
18484
|
-
|
|
18485
|
-
|
|
18768
|
+
}
|
|
18769
|
+
function loadPendingAgentSignup(configDir, apiBaseUrl1) {
|
|
18770
|
+
const path = pendingSignupPath(configDir);
|
|
18771
|
+
let contents;
|
|
18486
18772
|
try {
|
|
18487
|
-
|
|
18488
|
-
apiBaseUrl1: params.flags["api-base-url-1"],
|
|
18489
|
-
configDir: params.configDir,
|
|
18490
|
-
credentialsLockHeld: true
|
|
18491
|
-
});
|
|
18773
|
+
contents = readFileSync(path, "utf8");
|
|
18492
18774
|
} catch (error) {
|
|
18493
|
-
if (
|
|
18494
|
-
process.stderr.write("Logged out (OAuth session was already expired or revoked on the server).\n");
|
|
18495
|
-
return;
|
|
18496
|
-
}
|
|
18497
|
-
throw error;
|
|
18498
|
-
}
|
|
18499
|
-
const freshCredentials = authenticated.auth.credentials ?? credentials;
|
|
18500
|
-
const result = await deps.cliLogout({
|
|
18501
|
-
body: { key_id: freshCredentials.oauth_grant_id },
|
|
18502
|
-
client: authenticated.apiClient.client,
|
|
18503
|
-
responseStyle: "fields"
|
|
18504
|
-
});
|
|
18505
|
-
if (result.error) {
|
|
18506
|
-
const payload = extractErrorPayload(result.error);
|
|
18507
|
-
const code = extractErrorCode(payload);
|
|
18508
|
-
if (code === API_ERROR_CODES.unauthorized || code === API_ERROR_CODES.notFound) {
|
|
18509
|
-
deleteCliCredentials(params.configDir);
|
|
18510
|
-
writeErrorWithHints(payload);
|
|
18511
|
-
process.stderr.write("Removed saved Primitive CLI credentials because the backing OAuth grant is already unavailable.\n");
|
|
18512
|
-
process.exitCode = 1;
|
|
18513
|
-
return;
|
|
18514
|
-
}
|
|
18515
|
-
writeErrorWithHints(payload);
|
|
18516
|
-
throw cliError$2("Could not revoke the saved Primitive CLI OAuth grant.");
|
|
18517
|
-
}
|
|
18518
|
-
const logout = unwrapData$1(result.data);
|
|
18519
|
-
deleteCliCredentials(params.configDir);
|
|
18520
|
-
const grantId = logout?.oauth_grant_id ?? freshCredentials.oauth_grant_id;
|
|
18521
|
-
process.stderr.write(`Logged out and revoked OAuth grant ${grantId}.\n`);
|
|
18522
|
-
}
|
|
18523
|
-
var LogoutCommand = class LogoutCommand extends Command {
|
|
18524
|
-
static description = "Log out by revoking the saved Primitive CLI OAuth grant and deleting local credentials.";
|
|
18525
|
-
static summary = "Log out and revoke the saved CLI OAuth grant";
|
|
18526
|
-
static examples = ["<%= config.bin %> logout"];
|
|
18527
|
-
static flags = { "api-base-url-1": Flags.string({
|
|
18528
|
-
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
18529
|
-
env: "PRIMITIVE_API_BASE_URL_1",
|
|
18530
|
-
hidden: true
|
|
18531
|
-
}) };
|
|
18532
|
-
async run() {
|
|
18533
|
-
const { flags } = await this.parse(LogoutCommand);
|
|
18534
|
-
let releaseCredentialsLock;
|
|
18535
|
-
try {
|
|
18536
|
-
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
18537
|
-
} catch (error) {
|
|
18538
|
-
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
18539
|
-
}
|
|
18540
|
-
try {
|
|
18541
|
-
await runLogoutWithCredentialLock({
|
|
18542
|
-
configDir: this.config.configDir,
|
|
18543
|
-
flags
|
|
18544
|
-
});
|
|
18545
|
-
} finally {
|
|
18546
|
-
releaseCredentialsLock();
|
|
18547
|
-
}
|
|
18548
|
-
}
|
|
18549
|
-
};
|
|
18550
|
-
//#endregion
|
|
18551
|
-
//#region src/oclif/message-body-sources.ts
|
|
18552
|
-
function defaultReadFile(path) {
|
|
18553
|
-
return readFileSync(path, "utf8");
|
|
18554
|
-
}
|
|
18555
|
-
function defaultReadStdin() {
|
|
18556
|
-
if (process.stdin.isTTY) throw new Error("stdin is a TTY; pipe a value into this command or pass a file/string source instead.");
|
|
18557
|
-
return readFileSync(0, "utf8");
|
|
18558
|
-
}
|
|
18559
|
-
function selectedSources(sources) {
|
|
18560
|
-
return sources.filter(([, selected]) => selected).map(([label]) => label);
|
|
18561
|
-
}
|
|
18562
|
-
function readTextFile(path, label, readFile) {
|
|
18563
|
-
try {
|
|
18564
|
-
return {
|
|
18565
|
-
content: readFile(path),
|
|
18566
|
-
kind: "ok"
|
|
18567
|
-
};
|
|
18568
|
-
} catch (error) {
|
|
18569
|
-
return {
|
|
18570
|
-
kind: "error",
|
|
18571
|
-
message: `Could not read ${label} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
18572
|
-
};
|
|
18573
|
-
}
|
|
18574
|
-
}
|
|
18575
|
-
function readTextStdin(label, readStdin) {
|
|
18576
|
-
try {
|
|
18577
|
-
return {
|
|
18578
|
-
content: readStdin(),
|
|
18579
|
-
kind: "ok"
|
|
18580
|
-
};
|
|
18581
|
-
} catch (error) {
|
|
18582
|
-
return {
|
|
18583
|
-
kind: "error",
|
|
18584
|
-
message: `Could not read ${label}: ${error instanceof Error ? error.message : String(error)}`
|
|
18585
|
-
};
|
|
18586
|
-
}
|
|
18587
|
-
}
|
|
18588
|
-
function resolveMessageBodies(input) {
|
|
18589
|
-
const bodySources = selectedSources([
|
|
18590
|
-
["--body", input.body !== void 0],
|
|
18591
|
-
["--body-file", input.bodyFile !== void 0],
|
|
18592
|
-
["--body-stdin", input.bodyStdin === true]
|
|
18593
|
-
]);
|
|
18594
|
-
if (bodySources.length > 1) return {
|
|
18595
|
-
kind: "error",
|
|
18596
|
-
message: `Pass only one plain-text body source (got ${bodySources.join(", ")}).`
|
|
18597
|
-
};
|
|
18598
|
-
const htmlSources = selectedSources([
|
|
18599
|
-
["--html", input.html !== void 0],
|
|
18600
|
-
["--html-file", input.htmlFile !== void 0],
|
|
18601
|
-
["--html-stdin", input.htmlStdin === true]
|
|
18602
|
-
]);
|
|
18603
|
-
if (htmlSources.length > 1) return {
|
|
18604
|
-
kind: "error",
|
|
18605
|
-
message: `Pass only one HTML body source (got ${htmlSources.join(", ")}).`
|
|
18606
|
-
};
|
|
18607
|
-
const stdinSources = selectedSources([["--body-stdin", input.bodyStdin === true], ["--html-stdin", input.htmlStdin === true]]);
|
|
18608
|
-
if (stdinSources.length > 1) return {
|
|
18609
|
-
kind: "error",
|
|
18610
|
-
message: `Stdin can only be consumed once (got ${stdinSources.join(", ")}).`
|
|
18611
|
-
};
|
|
18612
|
-
if (bodySources.length === 0 && htmlSources.length === 0) return {
|
|
18613
|
-
kind: "error",
|
|
18614
|
-
message: "Either a plain-text body source or an HTML body source is required."
|
|
18615
|
-
};
|
|
18616
|
-
const readFile = input.readFile ?? defaultReadFile;
|
|
18617
|
-
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
18618
|
-
let body = input.body;
|
|
18619
|
-
let html = input.html;
|
|
18620
|
-
if (input.bodyFile !== void 0) {
|
|
18621
|
-
const result = readTextFile(input.bodyFile, "--body-file", readFile);
|
|
18622
|
-
if (result.kind === "error") return result;
|
|
18623
|
-
body = result.content;
|
|
18624
|
-
}
|
|
18625
|
-
if (input.bodyStdin === true) {
|
|
18626
|
-
const result = readTextStdin("--body-stdin", readStdin);
|
|
18627
|
-
if (result.kind === "error") return result;
|
|
18628
|
-
body = result.content;
|
|
18629
|
-
}
|
|
18630
|
-
if (input.htmlFile !== void 0) {
|
|
18631
|
-
const result = readTextFile(input.htmlFile, "--html-file", readFile);
|
|
18632
|
-
if (result.kind === "error") return result;
|
|
18633
|
-
html = result.content;
|
|
18634
|
-
}
|
|
18635
|
-
if (input.htmlStdin === true) {
|
|
18636
|
-
const result = readTextStdin("--html-stdin", readStdin);
|
|
18637
|
-
if (result.kind === "error") return result;
|
|
18638
|
-
html = result.content;
|
|
18639
|
-
}
|
|
18640
|
-
if (!body && !html) return {
|
|
18641
|
-
kind: "error",
|
|
18642
|
-
message: "Either a non-empty plain-text body or a non-empty HTML body is required."
|
|
18643
|
-
};
|
|
18644
|
-
return {
|
|
18645
|
-
...body !== void 0 ? { body } : {},
|
|
18646
|
-
...html !== void 0 ? { html } : {},
|
|
18647
|
-
kind: "ok"
|
|
18648
|
-
};
|
|
18649
|
-
}
|
|
18650
|
-
//#endregion
|
|
18651
|
-
//#region src/oclif/commands/reply.ts
|
|
18652
|
-
var ReplyCommand = class ReplyCommand extends Command {
|
|
18653
|
-
static description = `Reply to an inbound email.
|
|
18654
|
-
|
|
18655
|
-
The API derives recipients, the Re: subject, and threading headers from the inbound email id. Use \`primitive send --in-reply-to <message-id>\` only when you need to thread against a raw Message-Id instead of an inbound email stored by Primitive.`;
|
|
18656
|
-
static summary = "Reply to an inbound email";
|
|
18657
|
-
static examples = [
|
|
18658
|
-
"<%= config.bin %> reply --id <inbound-email-id> --body 'Thanks, got it.'",
|
|
18659
|
-
"<%= config.bin %> reply --id <inbound-email-id> --body-file ./reply.txt",
|
|
18660
|
-
"<%= config.bin %> reply --id <inbound-email-id> --html '<p>Thanks, got it.</p>' --wait",
|
|
18661
|
-
"<%= config.bin %> reply --id <inbound-email-id> --from 'Support <support@example.com>' --body 'Thanks!'"
|
|
18662
|
-
];
|
|
18663
|
-
static flags = {
|
|
18664
|
-
"api-key": Flags.string({
|
|
18665
|
-
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
18666
|
-
env: "PRIMITIVE_API_KEY"
|
|
18667
|
-
}),
|
|
18668
|
-
"api-base-url-1": Flags.string({
|
|
18669
|
-
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
18670
|
-
env: "PRIMITIVE_API_BASE_URL_1",
|
|
18671
|
-
hidden: true
|
|
18672
|
-
}),
|
|
18673
|
-
"api-base-url-2": Flags.string({
|
|
18674
|
-
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
18675
|
-
env: "PRIMITIVE_API_BASE_URL_2",
|
|
18676
|
-
hidden: true
|
|
18677
|
-
}),
|
|
18678
|
-
id: Flags.string({
|
|
18679
|
-
description: "Inbound email id to reply to.",
|
|
18680
|
-
required: true
|
|
18681
|
-
}),
|
|
18682
|
-
body: Flags.string({ description: "Plain-text reply body. Either --body or --html (or both) is required." }),
|
|
18683
|
-
"body-file": Flags.string({ description: "Read the plain-text reply body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
|
|
18684
|
-
"body-stdin": Flags.boolean({ description: "Read the plain-text reply body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
|
|
18685
|
-
html: Flags.string({ description: "HTML reply body. Either --body or --html (or both) is required." }),
|
|
18686
|
-
"html-file": Flags.string({ description: "Read the HTML reply body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
18687
|
-
"html-stdin": Flags.boolean({ description: "Read the HTML reply body from stdin. Mutually exclusive with --html and --html-file. Stdin can only be consumed once." }),
|
|
18688
|
-
from: Flags.string({ description: "Optional From header override. Defaults to the inbound recipient." }),
|
|
18689
|
-
wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the reply for delivery." }),
|
|
18690
|
-
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
18691
|
-
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
18692
|
-
};
|
|
18693
|
-
async run() {
|
|
18694
|
-
const { flags } = await this.parse(ReplyCommand);
|
|
18695
|
-
const bodies = resolveMessageBodies({
|
|
18696
|
-
body: flags.body,
|
|
18697
|
-
bodyFile: flags["body-file"],
|
|
18698
|
-
bodyStdin: flags["body-stdin"],
|
|
18699
|
-
html: flags.html,
|
|
18700
|
-
htmlFile: flags["html-file"],
|
|
18701
|
-
htmlStdin: flags["html-stdin"]
|
|
18702
|
-
});
|
|
18703
|
-
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
18704
|
-
await runWithTiming(flags.time, async () => {
|
|
18705
|
-
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
18706
|
-
apiKey: flags["api-key"],
|
|
18707
|
-
apiBaseUrl1: flags["api-base-url-1"],
|
|
18708
|
-
apiBaseUrl2: flags["api-base-url-2"],
|
|
18709
|
-
configDir: this.config.configDir
|
|
18710
|
-
});
|
|
18711
|
-
const result = await replyToEmail({
|
|
18712
|
-
body: {
|
|
18713
|
-
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
18714
|
-
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
18715
|
-
...flags.from !== void 0 ? { from: flags.from } : {},
|
|
18716
|
-
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
18717
|
-
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
18718
|
-
},
|
|
18719
|
-
client: apiClient.client,
|
|
18720
|
-
path: { id: flags.id },
|
|
18721
|
-
responseStyle: "fields"
|
|
18722
|
-
});
|
|
18723
|
-
if (result.error) {
|
|
18724
|
-
const errorPayload = extractErrorPayload(result.error);
|
|
18725
|
-
writeErrorWithHints(errorPayload);
|
|
18726
|
-
surfaceUnauthorizedHint({
|
|
18727
|
-
auth,
|
|
18728
|
-
baseUrlOverridden,
|
|
18729
|
-
configDir: this.config.configDir,
|
|
18730
|
-
payload: errorPayload
|
|
18731
|
-
});
|
|
18732
|
-
process.exitCode = 1;
|
|
18733
|
-
return;
|
|
18734
|
-
}
|
|
18735
|
-
const envelope = result.data;
|
|
18736
|
-
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
18737
|
-
process.stderr.write(chunk);
|
|
18738
|
-
} });
|
|
18739
|
-
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
18740
|
-
});
|
|
18741
|
-
}
|
|
18742
|
-
};
|
|
18743
|
-
//#endregion
|
|
18744
|
-
//#region src/oclif/attachments.ts
|
|
18745
|
-
function readAttachmentBytes(path, readFile) {
|
|
18746
|
-
try {
|
|
18747
|
-
return Buffer.from(readFile(path));
|
|
18748
|
-
} catch (error) {
|
|
18749
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
18750
|
-
throw new Errors.CLIError(`Could not read --attachment ${path}: ${detail}`, { exit: 1 });
|
|
18751
|
-
}
|
|
18752
|
-
}
|
|
18753
|
-
function hasControlCharacter(value) {
|
|
18754
|
-
return Array.from(value).some((character) => {
|
|
18755
|
-
const code = character.charCodeAt(0);
|
|
18756
|
-
return code <= 31 || code >= 127 && code <= 159;
|
|
18757
|
-
});
|
|
18758
|
-
}
|
|
18759
|
-
function validateAttachmentFilename(path, filename) {
|
|
18760
|
-
if (!filename) throw new Errors.CLIError(`Could not derive an attachment filename from ${path}. Pass a file path.`, { exit: 1 });
|
|
18761
|
-
if (hasControlCharacter(filename)) throw new Errors.CLIError(`Attachment filename ${filename} contains control characters.`, { exit: 1 });
|
|
18762
|
-
}
|
|
18763
|
-
function readAttachmentFiles(paths, readFile = readFileSync) {
|
|
18764
|
-
if (!paths || paths.length === 0) return void 0;
|
|
18765
|
-
return paths.map((path) => {
|
|
18766
|
-
const filename = basename(path);
|
|
18767
|
-
validateAttachmentFilename(path, filename);
|
|
18768
|
-
const bytes = readAttachmentBytes(path, readFile);
|
|
18769
|
-
if (bytes.length === 0) throw new Errors.CLIError(`Attachment file ${path} is empty. Attachments must contain at least one byte.`, { exit: 1 });
|
|
18770
|
-
return {
|
|
18771
|
-
content_base64: bytes.toString("base64"),
|
|
18772
|
-
filename
|
|
18773
|
-
};
|
|
18774
|
-
});
|
|
18775
|
-
}
|
|
18776
|
-
//#endregion
|
|
18777
|
-
//#region src/oclif/commands/send.ts
|
|
18778
|
-
var SendCommand = class SendCommand extends Command {
|
|
18779
|
-
static description = `Send an outbound email. Agent-grade shortcut for \`sending send\` with sensible defaults.
|
|
18780
|
-
|
|
18781
|
-
--from defaults to agent@<your-first-verified-outbound-domain> when omitted.
|
|
18782
|
-
--subject defaults to the first line of the body when omitted.
|
|
18783
|
-
--attachment attaches a file; repeat it to attach multiple files.
|
|
18784
|
-
|
|
18785
|
-
For the full flag set (custom message-id threading on the wire,
|
|
18786
|
-
references arrays, etc.), use \`primitive sending send\`.`;
|
|
18787
|
-
static summary = "Send an email (simplified, agent-friendly)";
|
|
18788
|
-
static examples = [
|
|
18789
|
-
"<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
|
|
18790
|
-
"<%= config.bin %> send --to alice@example.com --body-file ./message.txt",
|
|
18791
|
-
"<%= config.bin %> send --to alice@example.com --body 'See attached.' --attachment ./report.pdf",
|
|
18792
|
-
"<%= config.bin %> send --to alice@example.com --from support@yourcompany.com --subject 'Quick question' --body 'Are you free Thursday?'",
|
|
18793
|
-
"<%= config.bin %> send --to alice@example.com --html '<p>Hello!</p>'",
|
|
18794
|
-
"<%= config.bin %> send --to alice@example.com --body 'Confirmed' --wait",
|
|
18795
|
-
"<%= config.bin %> send --to inbox@your-managed-domain.primitive.email --body 'self-loop smoke test' --wait # any *.primitive.email address routes back to the sending account; useful for proving outbound + inbound work end-to-end"
|
|
18796
|
-
];
|
|
18797
|
-
static flags = {
|
|
18798
|
-
"api-key": Flags.string({
|
|
18799
|
-
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
18800
|
-
env: "PRIMITIVE_API_KEY"
|
|
18801
|
-
}),
|
|
18802
|
-
"api-base-url-1": Flags.string({
|
|
18803
|
-
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
18804
|
-
env: "PRIMITIVE_API_BASE_URL_1",
|
|
18805
|
-
hidden: true
|
|
18806
|
-
}),
|
|
18807
|
-
"api-base-url-2": Flags.string({
|
|
18808
|
-
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
18809
|
-
env: "PRIMITIVE_API_BASE_URL_2",
|
|
18810
|
-
hidden: true
|
|
18811
|
-
}),
|
|
18812
|
-
to: Flags.string({
|
|
18813
|
-
description: "Recipient address (e.g. alice@example.com).",
|
|
18814
|
-
required: true
|
|
18815
|
-
}),
|
|
18816
|
-
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
18817
|
-
subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
|
|
18818
|
-
body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
|
|
18819
|
-
"body-file": Flags.string({ description: "Read the plain-text message body from a UTF-8 file. This does not attach the file; use --attachment for file attachments. Mutually exclusive with --body and --body-stdin." }),
|
|
18820
|
-
"body-stdin": Flags.boolean({ description: "Read the plain-text message body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
|
|
18821
|
-
html: Flags.string({ description: "HTML message body. Either --body or --html (or both) is required." }),
|
|
18822
|
-
"html-file": Flags.string({ description: "Read the HTML message body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
18823
|
-
"html-stdin": Flags.boolean({ description: "Read the HTML message body from stdin. Mutually exclusive with --html and --html-file. Stdin can only be consumed once." }),
|
|
18824
|
-
attachment: Flags.string({
|
|
18825
|
-
description: "Attach a file to the email. Repeatable. Sends file bytes as a MIME attachment; use --body-file only for message body text.",
|
|
18826
|
-
multiple: true
|
|
18827
|
-
}),
|
|
18828
|
-
"in-reply-to": Flags.string({ description: "Message-Id of the parent email when threading a reply on the wire. For replying to an inbound message you received, prefer `primitive reply --id <inbound-id>`." }),
|
|
18829
|
-
wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the message for delivery." }),
|
|
18830
|
-
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
18831
|
-
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
18832
|
-
};
|
|
18833
|
-
async run() {
|
|
18834
|
-
const { flags } = await this.parse(SendCommand);
|
|
18835
|
-
const bodies = resolveMessageBodies({
|
|
18836
|
-
body: flags.body,
|
|
18837
|
-
bodyFile: flags["body-file"],
|
|
18838
|
-
bodyStdin: flags["body-stdin"],
|
|
18839
|
-
html: flags.html,
|
|
18840
|
-
htmlFile: flags["html-file"],
|
|
18841
|
-
htmlStdin: flags["html-stdin"]
|
|
18842
|
-
});
|
|
18843
|
-
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
18844
|
-
const attachments = readAttachmentFiles(flags.attachment);
|
|
18845
|
-
await runWithTiming(flags.time, async () => {
|
|
18846
|
-
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
18847
|
-
apiKey: flags["api-key"],
|
|
18848
|
-
apiBaseUrl1: flags["api-base-url-1"],
|
|
18849
|
-
apiBaseUrl2: flags["api-base-url-2"],
|
|
18850
|
-
configDir: this.config.configDir
|
|
18851
|
-
});
|
|
18852
|
-
const authFailureContext = {
|
|
18853
|
-
auth,
|
|
18854
|
-
baseUrlOverridden,
|
|
18855
|
-
configDir: this.config.configDir
|
|
18856
|
-
};
|
|
18857
|
-
const from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
18858
|
-
const subject = flags.subject ?? (bodies.body ? deriveSubject(bodies.body) : "Message");
|
|
18859
|
-
const result = await sendEmail({
|
|
18860
|
-
body: {
|
|
18861
|
-
from,
|
|
18862
|
-
to: flags.to,
|
|
18863
|
-
subject,
|
|
18864
|
-
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
18865
|
-
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
18866
|
-
...attachments !== void 0 ? { attachments } : {},
|
|
18867
|
-
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
|
|
18868
|
-
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
18869
|
-
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
18870
|
-
},
|
|
18871
|
-
client: apiClient._sendClient,
|
|
18872
|
-
responseStyle: "fields"
|
|
18873
|
-
});
|
|
18874
|
-
if (result.error) {
|
|
18875
|
-
const errorPayload = extractErrorPayload(result.error);
|
|
18876
|
-
writeErrorWithHints(errorPayload);
|
|
18877
|
-
surfaceUnauthorizedHint({
|
|
18878
|
-
...authFailureContext,
|
|
18879
|
-
payload: errorPayload
|
|
18880
|
-
});
|
|
18881
|
-
process.exitCode = 1;
|
|
18882
|
-
return;
|
|
18883
|
-
}
|
|
18884
|
-
const envelope = result.data;
|
|
18885
|
-
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
18886
|
-
process.stderr.write(chunk);
|
|
18887
|
-
} });
|
|
18888
|
-
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
18889
|
-
});
|
|
18890
|
-
}
|
|
18891
|
-
};
|
|
18892
|
-
//#endregion
|
|
18893
|
-
//#region src/oclif/commands/signup.ts
|
|
18894
|
-
const INVALID_VERIFICATION_CODE = "invalid_verification_code";
|
|
18895
|
-
const EXPIRED_TOKEN = "expired_token";
|
|
18896
|
-
const INVALID_SIGNUP_TOKEN = "invalid_signup_token";
|
|
18897
|
-
const SLOW_DOWN = "slow_down";
|
|
18898
|
-
const PENDING_SIGNUP_FILE = "signup.json";
|
|
18899
|
-
const DEFAULT_SIGNUP_COMMAND_COPY = {
|
|
18900
|
-
actionNoun: "signup",
|
|
18901
|
-
actionGerund: "creating a new account",
|
|
18902
|
-
confirmCommand: (email) => `signup confirm ${email} <code>`,
|
|
18903
|
-
resendCommand: (email) => `signup resend ${email}`,
|
|
18904
|
-
startCommand: (email) => `signup ${email}`
|
|
18905
|
-
};
|
|
18906
|
-
function cliError$1(message) {
|
|
18907
|
-
return new Errors.CLIError(message, { exit: 1 });
|
|
18908
|
-
}
|
|
18909
|
-
function unwrapData(value) {
|
|
18910
|
-
return value?.data ?? null;
|
|
18911
|
-
}
|
|
18912
|
-
function isRecord(value) {
|
|
18913
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18914
|
-
}
|
|
18915
|
-
function normalizeEmail(email) {
|
|
18916
|
-
return email.trim().toLowerCase();
|
|
18917
|
-
}
|
|
18918
|
-
function pendingSignupFromJson(value) {
|
|
18919
|
-
if (!isRecord(value)) return null;
|
|
18920
|
-
if (typeof value.signup_token !== "string" || typeof value.email !== "string" || typeof value.expires_in !== "number" || typeof value.resend_after !== "number" || typeof value.verification_code_length !== "number" || typeof value.api_base_url_1 !== "string" || typeof value.created_at !== "string" || typeof value.expires_at !== "string") return null;
|
|
18921
|
-
return {
|
|
18922
|
-
api_base_url_1: value.api_base_url_1,
|
|
18923
|
-
created_at: value.created_at,
|
|
18924
|
-
email: value.email,
|
|
18925
|
-
expires_at: value.expires_at,
|
|
18926
|
-
expires_in: value.expires_in,
|
|
18927
|
-
resend_after: value.resend_after,
|
|
18928
|
-
signup_token: value.signup_token,
|
|
18929
|
-
verification_code_length: value.verification_code_length
|
|
18930
|
-
};
|
|
18931
|
-
}
|
|
18932
|
-
function pendingSignupPath(configDir) {
|
|
18933
|
-
return join(configDir, PENDING_SIGNUP_FILE);
|
|
18934
|
-
}
|
|
18935
|
-
function deletePendingAgentSignup(configDir) {
|
|
18936
|
-
rmSync(pendingSignupPath(configDir), { force: true });
|
|
18937
|
-
}
|
|
18938
|
-
function pendingSignupFromStart(start, apiBaseUrl1) {
|
|
18939
|
-
return {
|
|
18940
|
-
...start,
|
|
18941
|
-
api_base_url_1: apiBaseUrl1,
|
|
18942
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18943
|
-
expires_at: new Date(Date.now() + start.expires_in * 1e3).toISOString()
|
|
18944
|
-
};
|
|
18945
|
-
}
|
|
18946
|
-
function savePendingAgentSignup(configDir, start, apiBaseUrl1) {
|
|
18947
|
-
mkdirSync(configDir, {
|
|
18948
|
-
mode: 448,
|
|
18949
|
-
recursive: true
|
|
18950
|
-
});
|
|
18951
|
-
const pending = pendingSignupFromStart(start, apiBaseUrl1);
|
|
18952
|
-
const path = pendingSignupPath(configDir);
|
|
18953
|
-
const tempPath = join(configDir, `${PENDING_SIGNUP_FILE}.${process$1.pid}.${randomUUID()}.tmp`);
|
|
18954
|
-
try {
|
|
18955
|
-
writeFileSync(tempPath, `${JSON.stringify(pending, null, 2)}\n`, { mode: 384 });
|
|
18956
|
-
chmodSync(tempPath, 384);
|
|
18957
|
-
renameSync(tempPath, path);
|
|
18958
|
-
chmodSync(path, 384);
|
|
18959
|
-
return pending;
|
|
18960
|
-
} catch (error) {
|
|
18961
|
-
rmSync(tempPath, { force: true });
|
|
18962
|
-
throw error;
|
|
18963
|
-
}
|
|
18964
|
-
}
|
|
18965
|
-
function loadPendingAgentSignup(configDir, apiBaseUrl1) {
|
|
18966
|
-
const path = pendingSignupPath(configDir);
|
|
18967
|
-
let contents;
|
|
18968
|
-
try {
|
|
18969
|
-
contents = readFileSync(path, "utf8");
|
|
18970
|
-
} catch (error) {
|
|
18971
|
-
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
18775
|
+
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
18972
18776
|
throw error;
|
|
18973
18777
|
}
|
|
18974
18778
|
let pending;
|
|
@@ -18994,8 +18798,8 @@ function loadPendingAgentSignup(configDir, apiBaseUrl1) {
|
|
|
18994
18798
|
function requirePendingSignupForEmail(params) {
|
|
18995
18799
|
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
18996
18800
|
const pending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
|
|
18997
|
-
if (!pending) throw cliError$
|
|
18998
|
-
if (normalizeEmail(pending.email) !== normalizeEmail(params.email)) throw cliError$
|
|
18801
|
+
if (!pending) throw cliError$2(`No pending ${copy.actionNoun} for ${params.email}. Run \`primitive ${copy.startCommand(params.email)}\` first.`);
|
|
18802
|
+
if (normalizeEmail(pending.email) !== normalizeEmail(params.email)) throw cliError$2(`Pending ${copy.actionNoun} is for ${pending.email}, not ${params.email}. Run \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
|
|
18999
18803
|
return pending;
|
|
19000
18804
|
}
|
|
19001
18805
|
function retryAfterSeconds(result) {
|
|
@@ -19036,7 +18840,7 @@ async function confirmTerms() {
|
|
|
19036
18840
|
process$1.stderr.write(" https://primitive.dev/terms\n");
|
|
19037
18841
|
process$1.stderr.write(" https://primitive.dev/privacy\n");
|
|
19038
18842
|
const answer = (await promptRequired("Type 'yes' to continue: ")).toLowerCase();
|
|
19039
|
-
if (answer !== "yes" && answer !== "y") throw cliError$
|
|
18843
|
+
if (answer !== "yes" && answer !== "y") throw cliError$2("You must accept the terms to create an account.");
|
|
19040
18844
|
}
|
|
19041
18845
|
async function checkExistingCredentials(params) {
|
|
19042
18846
|
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
@@ -19067,9 +18871,9 @@ async function checkExistingCredentials(params) {
|
|
|
19067
18871
|
}
|
|
19068
18872
|
if (existingStatus.status === "blocked") {
|
|
19069
18873
|
writeErrorWithHints(existingStatus.payload);
|
|
19070
|
-
throw cliError$
|
|
18874
|
+
throw cliError$2(existingStatus.message);
|
|
19071
18875
|
}
|
|
19072
|
-
throw cliError$
|
|
18876
|
+
throw cliError$2(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before ${copy.actionGerund}.`);
|
|
19073
18877
|
}
|
|
19074
18878
|
function saveSignupCredentials(params) {
|
|
19075
18879
|
saveCliCredentials(params.configDir, {
|
|
@@ -19103,7 +18907,7 @@ async function startSignup(params) {
|
|
|
19103
18907
|
started: false
|
|
19104
18908
|
};
|
|
19105
18909
|
}
|
|
19106
|
-
throw cliError$
|
|
18910
|
+
throw cliError$2(`Pending ${copy.actionNoun} is for ${existingPending.email}. Run \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
|
|
19107
18911
|
}
|
|
19108
18912
|
if (params.flags.force) deletePendingAgentSignup(params.configDir);
|
|
19109
18913
|
const promptRequiredFn = params.deps.promptRequired ?? promptRequired;
|
|
@@ -19123,10 +18927,10 @@ async function startSignup(params) {
|
|
|
19123
18927
|
});
|
|
19124
18928
|
if (started.error) {
|
|
19125
18929
|
writeErrorWithHints(extractErrorPayload(started.error));
|
|
19126
|
-
throw cliError$
|
|
18930
|
+
throw cliError$2("Could not start Primitive agent signup.");
|
|
19127
18931
|
}
|
|
19128
|
-
const startResult = unwrapData(started.data);
|
|
19129
|
-
if (!startResult) throw cliError$
|
|
18932
|
+
const startResult = unwrapData$1(started.data);
|
|
18933
|
+
if (!startResult) throw cliError$2("Primitive API returned an empty agent signup response.");
|
|
19130
18934
|
return {
|
|
19131
18935
|
pending: savePendingAgentSignup(params.configDir, startResult, params.apiBaseUrl1),
|
|
19132
18936
|
started: true
|
|
@@ -19139,7 +18943,7 @@ async function resendVerificationCode(params) {
|
|
|
19139
18943
|
responseStyle: "fields"
|
|
19140
18944
|
});
|
|
19141
18945
|
if (resent.data) {
|
|
19142
|
-
const resend = unwrapData(resent.data);
|
|
18946
|
+
const resend = unwrapData$1(resent.data);
|
|
19143
18947
|
const next = resend ? {
|
|
19144
18948
|
email: resend.email,
|
|
19145
18949
|
expires_in: resend.expires_in,
|
|
@@ -19164,7 +18968,7 @@ async function resendVerificationCode(params) {
|
|
|
19164
18968
|
}
|
|
19165
18969
|
if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(params.configDir);
|
|
19166
18970
|
writeErrorWithHints(payload);
|
|
19167
|
-
throw cliError$
|
|
18971
|
+
throw cliError$2("Could not resend Primitive agent signup verification email.");
|
|
19168
18972
|
}
|
|
19169
18973
|
async function runSignupStartWithCredentialLock(params) {
|
|
19170
18974
|
const { configDir, flags } = params;
|
|
@@ -19224,8 +19028,8 @@ async function runSignupConfirmWithCredentialLock(params) {
|
|
|
19224
19028
|
responseStyle: "fields"
|
|
19225
19029
|
});
|
|
19226
19030
|
if (verified.data) {
|
|
19227
|
-
const signup = unwrapData(verified.data);
|
|
19228
|
-
if (!signup) throw cliError$
|
|
19031
|
+
const signup = unwrapData$1(verified.data);
|
|
19032
|
+
if (!signup) throw cliError$2("Primitive API returned an empty agent signup verification response.");
|
|
19229
19033
|
saveSignupCredentials({
|
|
19230
19034
|
apiBaseUrl1,
|
|
19231
19035
|
configDir,
|
|
@@ -19239,10 +19043,10 @@ async function runSignupConfirmWithCredentialLock(params) {
|
|
|
19239
19043
|
}
|
|
19240
19044
|
const payload = extractErrorPayload(verified.error);
|
|
19241
19045
|
const code = extractErrorCode(payload);
|
|
19242
|
-
if (code === INVALID_VERIFICATION_CODE) throw cliError$
|
|
19046
|
+
if (code === INVALID_VERIFICATION_CODE) throw cliError$2(`Invalid verification code. Try again or run ${(params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY).resendCommand(params.email)}.`);
|
|
19243
19047
|
if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(configDir);
|
|
19244
19048
|
writeErrorWithHints(payload);
|
|
19245
|
-
throw cliError$
|
|
19049
|
+
throw cliError$2("Primitive agent signup failed while verifying the account.");
|
|
19246
19050
|
}
|
|
19247
19051
|
async function runSignupResendWithCredentialLock(params) {
|
|
19248
19052
|
const deps = params.deps ?? {};
|
|
@@ -19329,40 +19133,268 @@ async function runSignupInteractiveWithCredentialLock(params) {
|
|
|
19329
19133
|
}
|
|
19330
19134
|
}
|
|
19331
19135
|
}
|
|
19332
|
-
function commonStartFlags() {
|
|
19333
|
-
return {
|
|
19334
|
-
"accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
|
|
19136
|
+
function commonStartFlags() {
|
|
19137
|
+
return {
|
|
19138
|
+
"accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
|
|
19139
|
+
"api-base-url-1": Flags.string({
|
|
19140
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19141
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19142
|
+
hidden: true
|
|
19143
|
+
}),
|
|
19144
|
+
"device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
|
|
19145
|
+
force: Flags.boolean({
|
|
19146
|
+
char: "f",
|
|
19147
|
+
description: "Replace saved credentials or pending signup state when needed"
|
|
19148
|
+
}),
|
|
19149
|
+
"signup-code": Flags.string({
|
|
19150
|
+
description: "Signup code required to create an account",
|
|
19151
|
+
env: "PRIMITIVE_SIGNUP_CODE"
|
|
19152
|
+
})
|
|
19153
|
+
};
|
|
19154
|
+
}
|
|
19155
|
+
var SignupCommand = class SignupCommand extends Command {
|
|
19156
|
+
static args = { email: Args.string({
|
|
19157
|
+
description: "Email address to sign up",
|
|
19158
|
+
required: false
|
|
19159
|
+
}) };
|
|
19160
|
+
static description = "Start a Primitive account signup, send an email verification code, and save a pending signup token locally.";
|
|
19161
|
+
static summary = "Start account signup";
|
|
19162
|
+
static examples = [
|
|
19163
|
+
"<%= config.bin %> signup user@example.com",
|
|
19164
|
+
"<%= config.bin %> signup user@example.com --signup-code invite-code --accept-terms",
|
|
19165
|
+
"<%= config.bin %> signup confirm user@example.com 123456"
|
|
19166
|
+
];
|
|
19167
|
+
static flags = commonStartFlags();
|
|
19168
|
+
async run() {
|
|
19169
|
+
const { args, flags } = await this.parse(SignupCommand);
|
|
19170
|
+
let releaseCredentialsLock;
|
|
19171
|
+
try {
|
|
19172
|
+
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
19173
|
+
} catch (error) {
|
|
19174
|
+
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
19175
|
+
}
|
|
19176
|
+
try {
|
|
19177
|
+
await runSignupStartWithCredentialLock({
|
|
19178
|
+
configDir: this.config.configDir,
|
|
19179
|
+
email: args.email,
|
|
19180
|
+
flags
|
|
19181
|
+
});
|
|
19182
|
+
} finally {
|
|
19183
|
+
releaseCredentialsLock();
|
|
19184
|
+
}
|
|
19185
|
+
}
|
|
19186
|
+
};
|
|
19187
|
+
var SignupConfirmCommand = class SignupConfirmCommand extends Command {
|
|
19188
|
+
static args = {
|
|
19189
|
+
email: Args.string({
|
|
19190
|
+
description: "Email address used to start signup",
|
|
19191
|
+
required: true
|
|
19192
|
+
}),
|
|
19193
|
+
code: Args.string({
|
|
19194
|
+
description: "Verification code from the signup email",
|
|
19195
|
+
required: true
|
|
19196
|
+
})
|
|
19197
|
+
};
|
|
19198
|
+
static description = "Confirm a pending Primitive signup, create an OAuth session, and save CLI credentials locally.";
|
|
19199
|
+
static summary = "Confirm account signup";
|
|
19200
|
+
static examples = ["<%= config.bin %> signup confirm user@example.com 123456", "<%= config.bin %> signup confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000"];
|
|
19201
|
+
static flags = {
|
|
19202
|
+
"api-base-url-1": Flags.string({
|
|
19203
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19204
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19205
|
+
hidden: true
|
|
19206
|
+
}),
|
|
19207
|
+
force: Flags.boolean({
|
|
19208
|
+
char: "f",
|
|
19209
|
+
description: "Replace saved credentials after verification"
|
|
19210
|
+
}),
|
|
19211
|
+
"org-id": Flags.string({ description: "Workspace id to target when the email belongs to multiple workspaces" })
|
|
19212
|
+
};
|
|
19213
|
+
async run() {
|
|
19214
|
+
const { args, flags } = await this.parse(SignupConfirmCommand);
|
|
19215
|
+
let releaseCredentialsLock;
|
|
19216
|
+
try {
|
|
19217
|
+
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
19218
|
+
} catch (error) {
|
|
19219
|
+
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
19220
|
+
}
|
|
19221
|
+
try {
|
|
19222
|
+
await runSignupConfirmWithCredentialLock({
|
|
19223
|
+
code: args.code,
|
|
19224
|
+
configDir: this.config.configDir,
|
|
19225
|
+
email: args.email,
|
|
19226
|
+
flags
|
|
19227
|
+
});
|
|
19228
|
+
} finally {
|
|
19229
|
+
releaseCredentialsLock();
|
|
19230
|
+
}
|
|
19231
|
+
}
|
|
19232
|
+
};
|
|
19233
|
+
var SignupResendCommand = class SignupResendCommand extends Command {
|
|
19234
|
+
static args = { email: Args.string({
|
|
19235
|
+
description: "Email address used to start signup",
|
|
19236
|
+
required: true
|
|
19237
|
+
}) };
|
|
19238
|
+
static description = "Resend the verification code for a pending signup.";
|
|
19239
|
+
static summary = "Resend signup verification code";
|
|
19240
|
+
static examples = ["<%= config.bin %> signup resend user@example.com"];
|
|
19241
|
+
static flags = { "api-base-url-1": Flags.string({
|
|
19242
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19243
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19244
|
+
hidden: true
|
|
19245
|
+
}) };
|
|
19246
|
+
async run() {
|
|
19247
|
+
const { args, flags } = await this.parse(SignupResendCommand);
|
|
19248
|
+
let releaseCredentialsLock;
|
|
19249
|
+
try {
|
|
19250
|
+
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
19251
|
+
} catch (error) {
|
|
19252
|
+
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
19253
|
+
}
|
|
19254
|
+
try {
|
|
19255
|
+
await runSignupResendWithCredentialLock({
|
|
19256
|
+
configDir: this.config.configDir,
|
|
19257
|
+
email: args.email,
|
|
19258
|
+
flags
|
|
19259
|
+
});
|
|
19260
|
+
} finally {
|
|
19261
|
+
releaseCredentialsLock();
|
|
19262
|
+
}
|
|
19263
|
+
}
|
|
19264
|
+
};
|
|
19265
|
+
var SignupInteractiveCommand = class SignupInteractiveCommand extends Command {
|
|
19266
|
+
static description = "Run the full signup flow in one interactive terminal session.";
|
|
19267
|
+
static summary = "Run interactive account signup";
|
|
19268
|
+
static examples = ["<%= config.bin %> signup interactive"];
|
|
19269
|
+
static flags = commonStartFlags();
|
|
19270
|
+
async run() {
|
|
19271
|
+
const { flags } = await this.parse(SignupInteractiveCommand);
|
|
19272
|
+
let releaseCredentialsLock;
|
|
19273
|
+
try {
|
|
19274
|
+
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
19275
|
+
} catch (error) {
|
|
19276
|
+
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
19277
|
+
}
|
|
19278
|
+
try {
|
|
19279
|
+
await runSignupInteractiveWithCredentialLock({
|
|
19280
|
+
configDir: this.config.configDir,
|
|
19281
|
+
flags
|
|
19282
|
+
});
|
|
19283
|
+
} finally {
|
|
19284
|
+
releaseCredentialsLock();
|
|
19285
|
+
}
|
|
19286
|
+
}
|
|
19287
|
+
};
|
|
19288
|
+
//#endregion
|
|
19289
|
+
//#region src/oclif/commands/logout.ts
|
|
19290
|
+
function cliError$1(message) {
|
|
19291
|
+
return new Errors.CLIError(message, { exit: 1 });
|
|
19292
|
+
}
|
|
19293
|
+
function unwrapData(value) {
|
|
19294
|
+
return value?.data ?? null;
|
|
19295
|
+
}
|
|
19296
|
+
function isSavedOAuthSessionExpiredError(error) {
|
|
19297
|
+
return error instanceof Error && error.message === "Saved Primitive CLI OAuth session expired or was revoked. Run `primitive signin` to authenticate again.";
|
|
19298
|
+
}
|
|
19299
|
+
async function runLogoutWithCredentialLock(params) {
|
|
19300
|
+
const deps = {
|
|
19301
|
+
cliLogout,
|
|
19302
|
+
createAuthenticatedCliApiClient,
|
|
19303
|
+
...params.deps
|
|
19304
|
+
};
|
|
19305
|
+
let credentials;
|
|
19306
|
+
try {
|
|
19307
|
+
credentials = loadCliCredentials(params.configDir);
|
|
19308
|
+
} catch (error) {
|
|
19309
|
+
deleteCliCredentials(params.configDir);
|
|
19310
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
19311
|
+
process.stderr.write(`Removed unreadable Primitive CLI credentials. Backing OAuth grant was not revoked: ${detail}\n`);
|
|
19312
|
+
process.exitCode = 1;
|
|
19313
|
+
return;
|
|
19314
|
+
}
|
|
19315
|
+
if (!credentials) throw cliError$1("Not logged in. Run `primitive signin` to create saved CLI credentials.");
|
|
19316
|
+
let authenticated;
|
|
19317
|
+
try {
|
|
19318
|
+
authenticated = await deps.createAuthenticatedCliApiClient({
|
|
19319
|
+
apiBaseUrl1: params.flags["api-base-url-1"],
|
|
19320
|
+
configDir: params.configDir,
|
|
19321
|
+
credentialsLockHeld: true
|
|
19322
|
+
});
|
|
19323
|
+
} catch (error) {
|
|
19324
|
+
if (isSavedOAuthSessionExpiredError(error) && loadCliCredentials(params.configDir) === null) {
|
|
19325
|
+
process.stderr.write("Logged out (OAuth session was already expired or revoked on the server).\n");
|
|
19326
|
+
return;
|
|
19327
|
+
}
|
|
19328
|
+
throw error;
|
|
19329
|
+
}
|
|
19330
|
+
const freshCredentials = authenticated.auth.credentials ?? credentials;
|
|
19331
|
+
const result = await deps.cliLogout({
|
|
19332
|
+
body: { key_id: freshCredentials.oauth_grant_id },
|
|
19333
|
+
client: authenticated.apiClient.client,
|
|
19334
|
+
responseStyle: "fields"
|
|
19335
|
+
});
|
|
19336
|
+
if (result.error) {
|
|
19337
|
+
const payload = extractErrorPayload(result.error);
|
|
19338
|
+
const code = extractErrorCode(payload);
|
|
19339
|
+
if (code === API_ERROR_CODES.unauthorized || code === API_ERROR_CODES.notFound) {
|
|
19340
|
+
deleteCliCredentials(params.configDir);
|
|
19341
|
+
writeErrorWithHints(payload);
|
|
19342
|
+
process.stderr.write("Removed saved Primitive CLI credentials because the backing OAuth grant is already unavailable.\n");
|
|
19343
|
+
process.exitCode = 1;
|
|
19344
|
+
return;
|
|
19345
|
+
}
|
|
19346
|
+
writeErrorWithHints(payload);
|
|
19347
|
+
throw cliError$1("Could not revoke the saved Primitive CLI OAuth grant.");
|
|
19348
|
+
}
|
|
19349
|
+
const logout = unwrapData(result.data);
|
|
19350
|
+
deleteCliCredentials(params.configDir);
|
|
19351
|
+
const grantId = logout?.oauth_grant_id ?? freshCredentials.oauth_grant_id;
|
|
19352
|
+
process.stderr.write(`Logged out and revoked OAuth grant ${grantId}.\n`);
|
|
19353
|
+
}
|
|
19354
|
+
function runForceLogout(params) {
|
|
19355
|
+
const localCredentialsPath = credentialsPath(params.configDir);
|
|
19356
|
+
const pendingPath = pendingSignupPath(params.configDir);
|
|
19357
|
+
const lockPath = credentialsLockPath(params.configDir);
|
|
19358
|
+
const removed = [
|
|
19359
|
+
existsSync(localCredentialsPath) ? "local Primitive CLI credentials" : null,
|
|
19360
|
+
existsSync(pendingPath) ? "pending email-code auth state" : null,
|
|
19361
|
+
existsSync(lockPath) ? "credential lock" : null
|
|
19362
|
+
].filter((value) => value !== null);
|
|
19363
|
+
deleteCliCredentials(params.configDir);
|
|
19364
|
+
deletePendingAgentSignup(params.configDir);
|
|
19365
|
+
deleteCliCredentialsLock(params.configDir);
|
|
19366
|
+
if (removed.length === 0) {
|
|
19367
|
+
process.stderr.write("No local Primitive CLI auth state was present. Backing OAuth grant was not revoked.\n");
|
|
19368
|
+
return;
|
|
19369
|
+
}
|
|
19370
|
+
process.stderr.write(`Removed ${formatList(removed)}. Backing OAuth grant was not revoked.\n`);
|
|
19371
|
+
}
|
|
19372
|
+
function formatList(values) {
|
|
19373
|
+
if (values.length <= 1) return values[0] ?? "";
|
|
19374
|
+
if (values.length === 2) return `${values[0]} and ${values[1]}`;
|
|
19375
|
+
return `${values.slice(0, -1).join(", ")}, and ${values.at(-1)}`;
|
|
19376
|
+
}
|
|
19377
|
+
var LogoutCommand = class LogoutCommand extends Command {
|
|
19378
|
+
static description = "Log out by revoking the saved Primitive CLI OAuth grant and deleting local credentials. Use --force to remove local credentials, pending email-code auth state, and stale credential locks without contacting Primitive.";
|
|
19379
|
+
static summary = "Log out and revoke the saved CLI OAuth grant";
|
|
19380
|
+
static examples = ["<%= config.bin %> logout", "<%= config.bin %> logout --force"];
|
|
19381
|
+
static flags = {
|
|
19335
19382
|
"api-base-url-1": Flags.string({
|
|
19336
19383
|
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19337
19384
|
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19338
19385
|
hidden: true
|
|
19339
19386
|
}),
|
|
19340
|
-
"device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
|
|
19341
19387
|
force: Flags.boolean({
|
|
19342
19388
|
char: "f",
|
|
19343
|
-
description: "
|
|
19344
|
-
}),
|
|
19345
|
-
"signup-code": Flags.string({
|
|
19346
|
-
description: "Signup code required to create an account",
|
|
19347
|
-
env: "PRIMITIVE_SIGNUP_CODE"
|
|
19389
|
+
description: "Remove local CLI credentials, pending email-code auth state, and any credential lock without revoking the server OAuth grant"
|
|
19348
19390
|
})
|
|
19349
19391
|
};
|
|
19350
|
-
}
|
|
19351
|
-
var SignupCommand = class SignupCommand extends Command {
|
|
19352
|
-
static args = { email: Args.string({
|
|
19353
|
-
description: "Email address to sign up",
|
|
19354
|
-
required: false
|
|
19355
|
-
}) };
|
|
19356
|
-
static description = "Start a Primitive account signup, send an email verification code, and save a pending signup token locally.";
|
|
19357
|
-
static summary = "Start account signup";
|
|
19358
|
-
static examples = [
|
|
19359
|
-
"<%= config.bin %> signup user@example.com",
|
|
19360
|
-
"<%= config.bin %> signup user@example.com --signup-code invite-code --accept-terms",
|
|
19361
|
-
"<%= config.bin %> signup confirm user@example.com 123456"
|
|
19362
|
-
];
|
|
19363
|
-
static flags = commonStartFlags();
|
|
19364
19392
|
async run() {
|
|
19365
|
-
const {
|
|
19393
|
+
const { flags } = await this.parse(LogoutCommand);
|
|
19394
|
+
if (flags.force) {
|
|
19395
|
+
runForceLogout({ configDir: this.config.configDir });
|
|
19396
|
+
return;
|
|
19397
|
+
}
|
|
19366
19398
|
let releaseCredentialsLock;
|
|
19367
19399
|
try {
|
|
19368
19400
|
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
@@ -19370,9 +19402,8 @@ var SignupCommand = class SignupCommand extends Command {
|
|
|
19370
19402
|
throw cliError$1(error instanceof Error ? error.message : String(error));
|
|
19371
19403
|
}
|
|
19372
19404
|
try {
|
|
19373
|
-
await
|
|
19405
|
+
await runLogoutWithCredentialLock({
|
|
19374
19406
|
configDir: this.config.configDir,
|
|
19375
|
-
email: args.email,
|
|
19376
19407
|
flags
|
|
19377
19408
|
});
|
|
19378
19409
|
} finally {
|
|
@@ -19380,105 +19411,344 @@ var SignupCommand = class SignupCommand extends Command {
|
|
|
19380
19411
|
}
|
|
19381
19412
|
}
|
|
19382
19413
|
};
|
|
19383
|
-
|
|
19384
|
-
|
|
19385
|
-
|
|
19386
|
-
|
|
19387
|
-
|
|
19414
|
+
//#endregion
|
|
19415
|
+
//#region src/oclif/message-body-sources.ts
|
|
19416
|
+
function defaultReadFile(path) {
|
|
19417
|
+
return readFileSync(path, "utf8");
|
|
19418
|
+
}
|
|
19419
|
+
function defaultReadStdin() {
|
|
19420
|
+
if (process.stdin.isTTY) throw new Error("stdin is a TTY; pipe a value into this command or pass a file/string source instead.");
|
|
19421
|
+
return readFileSync(0, "utf8");
|
|
19422
|
+
}
|
|
19423
|
+
function selectedSources(sources) {
|
|
19424
|
+
return sources.filter(([, selected]) => selected).map(([label]) => label);
|
|
19425
|
+
}
|
|
19426
|
+
function readTextFile(path, label, readFile) {
|
|
19427
|
+
try {
|
|
19428
|
+
return {
|
|
19429
|
+
content: readFile(path),
|
|
19430
|
+
kind: "ok"
|
|
19431
|
+
};
|
|
19432
|
+
} catch (error) {
|
|
19433
|
+
return {
|
|
19434
|
+
kind: "error",
|
|
19435
|
+
message: `Could not read ${label} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
19436
|
+
};
|
|
19437
|
+
}
|
|
19438
|
+
}
|
|
19439
|
+
function readTextStdin(label, readStdin) {
|
|
19440
|
+
try {
|
|
19441
|
+
return {
|
|
19442
|
+
content: readStdin(),
|
|
19443
|
+
kind: "ok"
|
|
19444
|
+
};
|
|
19445
|
+
} catch (error) {
|
|
19446
|
+
return {
|
|
19447
|
+
kind: "error",
|
|
19448
|
+
message: `Could not read ${label}: ${error instanceof Error ? error.message : String(error)}`
|
|
19449
|
+
};
|
|
19450
|
+
}
|
|
19451
|
+
}
|
|
19452
|
+
function resolveMessageBodies(input) {
|
|
19453
|
+
const bodySources = selectedSources([
|
|
19454
|
+
["--body", input.body !== void 0],
|
|
19455
|
+
["--body-file", input.bodyFile !== void 0],
|
|
19456
|
+
["--body-stdin", input.bodyStdin === true]
|
|
19457
|
+
]);
|
|
19458
|
+
if (bodySources.length > 1) return {
|
|
19459
|
+
kind: "error",
|
|
19460
|
+
message: `Pass only one plain-text body source (got ${bodySources.join(", ")}).`
|
|
19461
|
+
};
|
|
19462
|
+
const htmlSources = selectedSources([
|
|
19463
|
+
["--html", input.html !== void 0],
|
|
19464
|
+
["--html-file", input.htmlFile !== void 0],
|
|
19465
|
+
["--html-stdin", input.htmlStdin === true]
|
|
19466
|
+
]);
|
|
19467
|
+
if (htmlSources.length > 1) return {
|
|
19468
|
+
kind: "error",
|
|
19469
|
+
message: `Pass only one HTML body source (got ${htmlSources.join(", ")}).`
|
|
19470
|
+
};
|
|
19471
|
+
const stdinSources = selectedSources([["--body-stdin", input.bodyStdin === true], ["--html-stdin", input.htmlStdin === true]]);
|
|
19472
|
+
if (stdinSources.length > 1) return {
|
|
19473
|
+
kind: "error",
|
|
19474
|
+
message: `Stdin can only be consumed once (got ${stdinSources.join(", ")}).`
|
|
19475
|
+
};
|
|
19476
|
+
if (bodySources.length === 0 && htmlSources.length === 0) return {
|
|
19477
|
+
kind: "error",
|
|
19478
|
+
message: "Either a plain-text body source or an HTML body source is required."
|
|
19479
|
+
};
|
|
19480
|
+
const readFile = input.readFile ?? defaultReadFile;
|
|
19481
|
+
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
19482
|
+
let body = input.body;
|
|
19483
|
+
let html = input.html;
|
|
19484
|
+
if (input.bodyFile !== void 0) {
|
|
19485
|
+
const result = readTextFile(input.bodyFile, "--body-file", readFile);
|
|
19486
|
+
if (result.kind === "error") return result;
|
|
19487
|
+
body = result.content;
|
|
19488
|
+
}
|
|
19489
|
+
if (input.bodyStdin === true) {
|
|
19490
|
+
const result = readTextStdin("--body-stdin", readStdin);
|
|
19491
|
+
if (result.kind === "error") return result;
|
|
19492
|
+
body = result.content;
|
|
19493
|
+
}
|
|
19494
|
+
if (input.htmlFile !== void 0) {
|
|
19495
|
+
const result = readTextFile(input.htmlFile, "--html-file", readFile);
|
|
19496
|
+
if (result.kind === "error") return result;
|
|
19497
|
+
html = result.content;
|
|
19498
|
+
}
|
|
19499
|
+
if (input.htmlStdin === true) {
|
|
19500
|
+
const result = readTextStdin("--html-stdin", readStdin);
|
|
19501
|
+
if (result.kind === "error") return result;
|
|
19502
|
+
html = result.content;
|
|
19503
|
+
}
|
|
19504
|
+
if (!body && !html) return {
|
|
19505
|
+
kind: "error",
|
|
19506
|
+
message: "Either a non-empty plain-text body or a non-empty HTML body is required."
|
|
19507
|
+
};
|
|
19508
|
+
return {
|
|
19509
|
+
...body !== void 0 ? { body } : {},
|
|
19510
|
+
...html !== void 0 ? { html } : {},
|
|
19511
|
+
kind: "ok"
|
|
19512
|
+
};
|
|
19513
|
+
}
|
|
19514
|
+
//#endregion
|
|
19515
|
+
//#region src/oclif/commands/reply.ts
|
|
19516
|
+
var ReplyCommand = class ReplyCommand extends Command {
|
|
19517
|
+
static description = `Reply to an inbound email.
|
|
19518
|
+
|
|
19519
|
+
The API derives recipients, the Re: subject, and threading headers from the inbound email id. Use \`primitive send --in-reply-to <message-id>\` only when you need to thread against a raw Message-Id instead of an inbound email stored by Primitive.`;
|
|
19520
|
+
static summary = "Reply to an inbound email";
|
|
19521
|
+
static examples = [
|
|
19522
|
+
"<%= config.bin %> reply --id <inbound-email-id> --body 'Thanks, got it.'",
|
|
19523
|
+
"<%= config.bin %> reply --id <inbound-email-id> --body-file ./reply.txt",
|
|
19524
|
+
"<%= config.bin %> reply --id <inbound-email-id> --html '<p>Thanks, got it.</p>' --wait",
|
|
19525
|
+
"<%= config.bin %> reply --id <inbound-email-id> --from 'Support <support@example.com>' --body 'Thanks!'"
|
|
19526
|
+
];
|
|
19527
|
+
static flags = {
|
|
19528
|
+
"api-key": Flags.string({
|
|
19529
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
19530
|
+
env: "PRIMITIVE_API_KEY"
|
|
19388
19531
|
}),
|
|
19389
|
-
|
|
19390
|
-
description: "
|
|
19532
|
+
"api-base-url-1": Flags.string({
|
|
19533
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19534
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19535
|
+
hidden: true
|
|
19536
|
+
}),
|
|
19537
|
+
"api-base-url-2": Flags.string({
|
|
19538
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
19539
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
19540
|
+
hidden: true
|
|
19541
|
+
}),
|
|
19542
|
+
id: Flags.string({
|
|
19543
|
+
description: "Inbound email id to reply to.",
|
|
19391
19544
|
required: true
|
|
19392
|
-
})
|
|
19545
|
+
}),
|
|
19546
|
+
body: Flags.string({ description: "Plain-text reply body. Either --body or --html (or both) is required." }),
|
|
19547
|
+
"body-file": Flags.string({ description: "Read the plain-text reply body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
|
|
19548
|
+
"body-stdin": Flags.boolean({ description: "Read the plain-text reply body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
|
|
19549
|
+
html: Flags.string({ description: "HTML reply body. Either --body or --html (or both) is required." }),
|
|
19550
|
+
"html-file": Flags.string({ description: "Read the HTML reply body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
19551
|
+
"html-stdin": Flags.boolean({ description: "Read the HTML reply body from stdin. Mutually exclusive with --html and --html-file. Stdin can only be consumed once." }),
|
|
19552
|
+
from: Flags.string({ description: "Optional From header override. Defaults to the inbound recipient." }),
|
|
19553
|
+
wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the reply for delivery." }),
|
|
19554
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
19393
19555
|
};
|
|
19394
|
-
|
|
19395
|
-
|
|
19396
|
-
|
|
19556
|
+
async run() {
|
|
19557
|
+
const { flags } = await this.parse(ReplyCommand);
|
|
19558
|
+
const bodies = resolveMessageBodies({
|
|
19559
|
+
body: flags.body,
|
|
19560
|
+
bodyFile: flags["body-file"],
|
|
19561
|
+
bodyStdin: flags["body-stdin"],
|
|
19562
|
+
html: flags.html,
|
|
19563
|
+
htmlFile: flags["html-file"],
|
|
19564
|
+
htmlStdin: flags["html-stdin"]
|
|
19565
|
+
});
|
|
19566
|
+
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
19567
|
+
await runWithTiming(flags.time, async () => {
|
|
19568
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
19569
|
+
apiKey: flags["api-key"],
|
|
19570
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
19571
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
19572
|
+
configDir: this.config.configDir
|
|
19573
|
+
});
|
|
19574
|
+
const result = await replyToEmail({
|
|
19575
|
+
body: {
|
|
19576
|
+
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
19577
|
+
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
19578
|
+
...flags.from !== void 0 ? { from: flags.from } : {},
|
|
19579
|
+
...flags.wait !== void 0 ? { wait: flags.wait } : {}
|
|
19580
|
+
},
|
|
19581
|
+
client: apiClient.client,
|
|
19582
|
+
path: { id: flags.id },
|
|
19583
|
+
responseStyle: "fields"
|
|
19584
|
+
});
|
|
19585
|
+
if (result.error) {
|
|
19586
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
19587
|
+
writeErrorWithHints(errorPayload);
|
|
19588
|
+
surfaceUnauthorizedHint({
|
|
19589
|
+
auth,
|
|
19590
|
+
baseUrlOverridden,
|
|
19591
|
+
configDir: this.config.configDir,
|
|
19592
|
+
payload: errorPayload
|
|
19593
|
+
});
|
|
19594
|
+
process.exitCode = 1;
|
|
19595
|
+
return;
|
|
19596
|
+
}
|
|
19597
|
+
const envelope = result.data;
|
|
19598
|
+
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
19599
|
+
process.stderr.write(chunk);
|
|
19600
|
+
} });
|
|
19601
|
+
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
19602
|
+
});
|
|
19603
|
+
}
|
|
19604
|
+
};
|
|
19605
|
+
//#endregion
|
|
19606
|
+
//#region src/oclif/attachments.ts
|
|
19607
|
+
function readAttachmentBytes(path, readFile) {
|
|
19608
|
+
try {
|
|
19609
|
+
return Buffer.from(readFile(path));
|
|
19610
|
+
} catch (error) {
|
|
19611
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
19612
|
+
throw new Errors.CLIError(`Could not read --attachment ${path}: ${detail}`, { exit: 1 });
|
|
19613
|
+
}
|
|
19614
|
+
}
|
|
19615
|
+
function hasControlCharacter(value) {
|
|
19616
|
+
return Array.from(value).some((character) => {
|
|
19617
|
+
const code = character.charCodeAt(0);
|
|
19618
|
+
return code <= 31 || code >= 127 && code <= 159;
|
|
19619
|
+
});
|
|
19620
|
+
}
|
|
19621
|
+
function validateAttachmentFilename(path, filename) {
|
|
19622
|
+
if (!filename) throw new Errors.CLIError(`Could not derive an attachment filename from ${path}. Pass a file path.`, { exit: 1 });
|
|
19623
|
+
if (hasControlCharacter(filename)) throw new Errors.CLIError(`Attachment filename ${filename} contains control characters.`, { exit: 1 });
|
|
19624
|
+
}
|
|
19625
|
+
function readAttachmentFiles(paths, readFile = readFileSync) {
|
|
19626
|
+
if (!paths || paths.length === 0) return void 0;
|
|
19627
|
+
return paths.map((path) => {
|
|
19628
|
+
const filename = basename(path);
|
|
19629
|
+
validateAttachmentFilename(path, filename);
|
|
19630
|
+
const bytes = readAttachmentBytes(path, readFile);
|
|
19631
|
+
if (bytes.length === 0) throw new Errors.CLIError(`Attachment file ${path} is empty. Attachments must contain at least one byte.`, { exit: 1 });
|
|
19632
|
+
return {
|
|
19633
|
+
content_base64: bytes.toString("base64"),
|
|
19634
|
+
filename
|
|
19635
|
+
};
|
|
19636
|
+
});
|
|
19637
|
+
}
|
|
19638
|
+
//#endregion
|
|
19639
|
+
//#region src/oclif/commands/send.ts
|
|
19640
|
+
var SendCommand = class SendCommand extends Command {
|
|
19641
|
+
static description = `Send an outbound email. Agent-grade shortcut for \`sending send\` with sensible defaults.
|
|
19642
|
+
|
|
19643
|
+
--from defaults to agent@<your-first-verified-outbound-domain> when omitted.
|
|
19644
|
+
--subject defaults to the first line of the body when omitted.
|
|
19645
|
+
--attachment attaches a file; repeat it to attach multiple files.
|
|
19646
|
+
|
|
19647
|
+
For the full flag set (custom message-id threading on the wire,
|
|
19648
|
+
references arrays, etc.), use \`primitive sending send\`.`;
|
|
19649
|
+
static summary = "Send an email (simplified, agent-friendly)";
|
|
19650
|
+
static examples = [
|
|
19651
|
+
"<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
|
|
19652
|
+
"<%= config.bin %> send --to alice@example.com --body-file ./message.txt",
|
|
19653
|
+
"<%= config.bin %> send --to alice@example.com --body 'See attached.' --attachment ./report.pdf",
|
|
19654
|
+
"<%= config.bin %> send --to alice@example.com --from support@yourcompany.com --subject 'Quick question' --body 'Are you free Thursday?'",
|
|
19655
|
+
"<%= config.bin %> send --to alice@example.com --html '<p>Hello!</p>'",
|
|
19656
|
+
"<%= config.bin %> send --to alice@example.com --body 'Confirmed' --wait",
|
|
19657
|
+
"<%= config.bin %> send --to inbox@your-managed-domain.primitive.email --body 'self-loop smoke test' --wait # any *.primitive.email address routes back to the sending account; useful for proving outbound + inbound work end-to-end"
|
|
19658
|
+
];
|
|
19397
19659
|
static flags = {
|
|
19660
|
+
"api-key": Flags.string({
|
|
19661
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
19662
|
+
env: "PRIMITIVE_API_KEY"
|
|
19663
|
+
}),
|
|
19398
19664
|
"api-base-url-1": Flags.string({
|
|
19399
19665
|
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19400
19666
|
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19401
19667
|
hidden: true
|
|
19402
19668
|
}),
|
|
19403
|
-
|
|
19404
|
-
|
|
19405
|
-
|
|
19669
|
+
"api-base-url-2": Flags.string({
|
|
19670
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
19671
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
19672
|
+
hidden: true
|
|
19406
19673
|
}),
|
|
19407
|
-
|
|
19674
|
+
to: Flags.string({
|
|
19675
|
+
description: "Recipient address (e.g. alice@example.com).",
|
|
19676
|
+
required: true
|
|
19677
|
+
}),
|
|
19678
|
+
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
19679
|
+
subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
|
|
19680
|
+
body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
|
|
19681
|
+
"body-file": Flags.string({ description: "Read the plain-text message body from a UTF-8 file. This does not attach the file; use --attachment for file attachments. Mutually exclusive with --body and --body-stdin." }),
|
|
19682
|
+
"body-stdin": Flags.boolean({ description: "Read the plain-text message body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
|
|
19683
|
+
html: Flags.string({ description: "HTML message body. Either --body or --html (or both) is required." }),
|
|
19684
|
+
"html-file": Flags.string({ description: "Read the HTML message body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
19685
|
+
"html-stdin": Flags.boolean({ description: "Read the HTML message body from stdin. Mutually exclusive with --html and --html-file. Stdin can only be consumed once." }),
|
|
19686
|
+
attachment: Flags.string({
|
|
19687
|
+
description: "Attach a file to the email. Repeatable. Sends file bytes as a MIME attachment; use --body-file only for message body text.",
|
|
19688
|
+
multiple: true
|
|
19689
|
+
}),
|
|
19690
|
+
"in-reply-to": Flags.string({ description: "Message-Id of the parent email when threading a reply on the wire. For replying to an inbound message you received, prefer `primitive reply --id <inbound-id>`." }),
|
|
19691
|
+
wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the message for delivery." }),
|
|
19692
|
+
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
19693
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
19408
19694
|
};
|
|
19409
19695
|
async run() {
|
|
19410
|
-
const {
|
|
19411
|
-
|
|
19412
|
-
|
|
19413
|
-
|
|
19414
|
-
|
|
19415
|
-
|
|
19416
|
-
|
|
19417
|
-
|
|
19418
|
-
|
|
19419
|
-
|
|
19420
|
-
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
|
|
19424
|
-
|
|
19425
|
-
|
|
19426
|
-
|
|
19427
|
-
}
|
|
19428
|
-
};
|
|
19429
|
-
var SignupResendCommand = class SignupResendCommand extends Command {
|
|
19430
|
-
static args = { email: Args.string({
|
|
19431
|
-
description: "Email address used to start signup",
|
|
19432
|
-
required: true
|
|
19433
|
-
}) };
|
|
19434
|
-
static description = "Resend the verification code for a pending signup.";
|
|
19435
|
-
static summary = "Resend signup verification code";
|
|
19436
|
-
static examples = ["<%= config.bin %> signup resend user@example.com"];
|
|
19437
|
-
static flags = { "api-base-url-1": Flags.string({
|
|
19438
|
-
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19439
|
-
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19440
|
-
hidden: true
|
|
19441
|
-
}) };
|
|
19442
|
-
async run() {
|
|
19443
|
-
const { args, flags } = await this.parse(SignupResendCommand);
|
|
19444
|
-
let releaseCredentialsLock;
|
|
19445
|
-
try {
|
|
19446
|
-
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
19447
|
-
} catch (error) {
|
|
19448
|
-
throw cliError$1(error instanceof Error ? error.message : String(error));
|
|
19449
|
-
}
|
|
19450
|
-
try {
|
|
19451
|
-
await runSignupResendWithCredentialLock({
|
|
19452
|
-
configDir: this.config.configDir,
|
|
19453
|
-
email: args.email,
|
|
19454
|
-
flags
|
|
19696
|
+
const { flags } = await this.parse(SendCommand);
|
|
19697
|
+
const bodies = resolveMessageBodies({
|
|
19698
|
+
body: flags.body,
|
|
19699
|
+
bodyFile: flags["body-file"],
|
|
19700
|
+
bodyStdin: flags["body-stdin"],
|
|
19701
|
+
html: flags.html,
|
|
19702
|
+
htmlFile: flags["html-file"],
|
|
19703
|
+
htmlStdin: flags["html-stdin"]
|
|
19704
|
+
});
|
|
19705
|
+
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
19706
|
+
const attachments = readAttachmentFiles(flags.attachment);
|
|
19707
|
+
await runWithTiming(flags.time, async () => {
|
|
19708
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
19709
|
+
apiKey: flags["api-key"],
|
|
19710
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
19711
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
19712
|
+
configDir: this.config.configDir
|
|
19455
19713
|
});
|
|
19456
|
-
|
|
19457
|
-
|
|
19458
|
-
|
|
19459
|
-
|
|
19460
|
-
};
|
|
19461
|
-
|
|
19462
|
-
|
|
19463
|
-
|
|
19464
|
-
|
|
19465
|
-
|
|
19466
|
-
|
|
19467
|
-
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
|
|
19475
|
-
|
|
19476
|
-
|
|
19477
|
-
flags
|
|
19714
|
+
const authFailureContext = {
|
|
19715
|
+
auth,
|
|
19716
|
+
baseUrlOverridden,
|
|
19717
|
+
configDir: this.config.configDir
|
|
19718
|
+
};
|
|
19719
|
+
const from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
19720
|
+
const subject = flags.subject ?? (bodies.body ? deriveSubject(bodies.body) : "Message");
|
|
19721
|
+
const result = await sendEmail({
|
|
19722
|
+
body: {
|
|
19723
|
+
from,
|
|
19724
|
+
to: flags.to,
|
|
19725
|
+
subject,
|
|
19726
|
+
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
19727
|
+
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
19728
|
+
...attachments !== void 0 ? { attachments } : {},
|
|
19729
|
+
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
|
|
19730
|
+
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
19731
|
+
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
19732
|
+
},
|
|
19733
|
+
client: apiClient._sendClient,
|
|
19734
|
+
responseStyle: "fields"
|
|
19478
19735
|
});
|
|
19479
|
-
|
|
19480
|
-
|
|
19481
|
-
|
|
19736
|
+
if (result.error) {
|
|
19737
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
19738
|
+
writeErrorWithHints(errorPayload);
|
|
19739
|
+
surfaceUnauthorizedHint({
|
|
19740
|
+
...authFailureContext,
|
|
19741
|
+
payload: errorPayload
|
|
19742
|
+
});
|
|
19743
|
+
process.exitCode = 1;
|
|
19744
|
+
return;
|
|
19745
|
+
}
|
|
19746
|
+
const envelope = result.data;
|
|
19747
|
+
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
19748
|
+
process.stderr.write(chunk);
|
|
19749
|
+
} });
|
|
19750
|
+
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
19751
|
+
});
|
|
19482
19752
|
}
|
|
19483
19753
|
};
|
|
19484
19754
|
//#endregion
|
|
@@ -19493,6 +19763,34 @@ const SIGNIN_OTP_COPY = {
|
|
|
19493
19763
|
resendCommand: (email) => `signin otp resend ${email}`,
|
|
19494
19764
|
startCommand: (email) => `signin otp ${email}`
|
|
19495
19765
|
};
|
|
19766
|
+
const SIGNIN_EMAIL_COPY = {
|
|
19767
|
+
actionNoun: "sign-in",
|
|
19768
|
+
actionGerund: "signing in",
|
|
19769
|
+
confirmCommand: (email) => `signin confirm ${email} <code>`,
|
|
19770
|
+
resendCommand: (email) => `signin resend ${email}`,
|
|
19771
|
+
startCommand: (email) => `signin ${email}`
|
|
19772
|
+
};
|
|
19773
|
+
const LOGIN_EMAIL_COPY = {
|
|
19774
|
+
actionNoun: "login",
|
|
19775
|
+
actionGerund: "logging in",
|
|
19776
|
+
confirmCommand: (email) => `login confirm ${email} <code>`,
|
|
19777
|
+
resendCommand: (email) => `login resend ${email}`,
|
|
19778
|
+
startCommand: (email) => `login ${email}`
|
|
19779
|
+
};
|
|
19780
|
+
const LOGIN_OTP_COPY = {
|
|
19781
|
+
actionNoun: "login",
|
|
19782
|
+
actionGerund: "logging in",
|
|
19783
|
+
confirmCommand: (email) => `login otp confirm ${email} <code>`,
|
|
19784
|
+
resendCommand: (email) => `login otp resend ${email}`,
|
|
19785
|
+
startCommand: (email) => `login otp ${email}`
|
|
19786
|
+
};
|
|
19787
|
+
const OTP_COPY = {
|
|
19788
|
+
actionNoun: "email-code auth",
|
|
19789
|
+
actionGerund: "authenticating",
|
|
19790
|
+
confirmCommand: (email) => `otp confirm ${email} <code>`,
|
|
19791
|
+
resendCommand: (email) => `otp resend ${email}`,
|
|
19792
|
+
startCommand: (email) => `otp ${email}`
|
|
19793
|
+
};
|
|
19496
19794
|
function acquireCredentialsLock(configDir) {
|
|
19497
19795
|
try {
|
|
19498
19796
|
return acquireCliCredentialsLock(configDir);
|
|
@@ -19511,31 +19809,91 @@ function commonOtpStartFlags() {
|
|
|
19511
19809
|
"device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
|
|
19512
19810
|
force: Flags.boolean({
|
|
19513
19811
|
char: "f",
|
|
19514
|
-
description: "Replace saved credentials or pending
|
|
19812
|
+
description: "Replace saved credentials or pending email-code auth state when needed"
|
|
19515
19813
|
}),
|
|
19516
19814
|
"signup-code": Flags.string({
|
|
19517
|
-
description: "Signup code required to start
|
|
19815
|
+
description: "Signup code required to start email-code sign-in",
|
|
19518
19816
|
env: "PRIMITIVE_SIGNUP_CODE"
|
|
19519
19817
|
})
|
|
19520
19818
|
};
|
|
19521
19819
|
}
|
|
19522
|
-
var SigninCommand = class extends LoginCommand {
|
|
19523
|
-
static
|
|
19820
|
+
var SigninCommand = class extends LoginCommand$1 {
|
|
19821
|
+
static args = { email: Args.string({
|
|
19822
|
+
description: "Email address for email-code sign-in. Omit it to use browser approval.",
|
|
19823
|
+
required: false
|
|
19824
|
+
}) };
|
|
19825
|
+
static description = `Sign in or log in to an existing Primitive account and save an org-scoped OAuth session locally.
|
|
19524
19826
|
|
|
19525
|
-
|
|
19827
|
+
Run \`primitive signin <email> --signup-code <code> --accept-terms\` for email-code sign-in, then \`primitive signin confirm <email> <code>\`. Run \`primitive signin\` with no email to use browser approval; \`primitive signin browser\` is the explicit browser form. \`primitive login\` supports the same flows with login-shaped commands. \`primitive otp <email>\` is the shortest email-code auth form. For new account creation, use \`primitive signup <email>\`.`;
|
|
19526
19828
|
static summary = "Sign in to an existing account";
|
|
19527
19829
|
static examples = [
|
|
19528
19830
|
"<%= config.bin %> signin",
|
|
19529
19831
|
"<%= config.bin %> signin browser",
|
|
19530
19832
|
"<%= config.bin %> signin --no-browser",
|
|
19833
|
+
"<%= config.bin %> signin user@example.com --signup-code invite-code --accept-terms",
|
|
19834
|
+
"<%= config.bin %> signin confirm user@example.com 123456",
|
|
19531
19835
|
"<%= config.bin %> signin otp user@example.com --signup-code invite-code --accept-terms",
|
|
19532
19836
|
"<%= config.bin %> signin otp confirm user@example.com 123456"
|
|
19533
19837
|
];
|
|
19838
|
+
static flags = {
|
|
19839
|
+
...LoginCommand$1.flags,
|
|
19840
|
+
...commonOtpStartFlags(),
|
|
19841
|
+
force: Flags.boolean({
|
|
19842
|
+
char: "f",
|
|
19843
|
+
description: "Replace saved credentials or pending email-code auth state when needed, without first verifying the existing session"
|
|
19844
|
+
})
|
|
19845
|
+
};
|
|
19846
|
+
async run() {
|
|
19847
|
+
const commandClass = this.constructor;
|
|
19848
|
+
const { args, flags } = await this.parse(commandClass);
|
|
19849
|
+
if (!args.email) {
|
|
19850
|
+
if (flags["signup-code"] || flags["accept-terms"]) throw cliError(`Email-code auth needs an email address. Run \`primitive ${this.emailCodeCopy().startCommand("<email>")} --signup-code <code> --accept-terms\`.`);
|
|
19851
|
+
await this.runBrowserLogin(flags, this.retryCommand());
|
|
19852
|
+
return;
|
|
19853
|
+
}
|
|
19854
|
+
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
19855
|
+
try {
|
|
19856
|
+
await runSignupStartWithCredentialLock({
|
|
19857
|
+
configDir: this.config.configDir,
|
|
19858
|
+
copy: this.emailCodeCopy(),
|
|
19859
|
+
email: args.email,
|
|
19860
|
+
flags
|
|
19861
|
+
});
|
|
19862
|
+
} finally {
|
|
19863
|
+
releaseCredentialsLock();
|
|
19864
|
+
}
|
|
19865
|
+
}
|
|
19534
19866
|
retryCommand() {
|
|
19535
19867
|
return "signin";
|
|
19536
19868
|
}
|
|
19869
|
+
emailCodeCopy() {
|
|
19870
|
+
return SIGNIN_EMAIL_COPY;
|
|
19871
|
+
}
|
|
19872
|
+
};
|
|
19873
|
+
var LoginCommand = class extends SigninCommand {
|
|
19874
|
+
static args = SigninCommand.args;
|
|
19875
|
+
static description = `Log in or sign in to an existing Primitive account and save an org-scoped OAuth session locally.
|
|
19876
|
+
|
|
19877
|
+
Run \`primitive login <email> --signup-code <code> --accept-terms\` for email-code login, then \`primitive login confirm <email> <code>\`. Run \`primitive login\` with no email to use browser approval; \`primitive login browser\` is the explicit browser form. \`primitive signin\` supports the same flows with signin-shaped commands. \`primitive otp <email>\` is the shortest email-code auth form. For new account creation, use \`primitive signup <email>\`.`;
|
|
19878
|
+
static summary = "Log in to an existing account";
|
|
19879
|
+
static examples = [
|
|
19880
|
+
"<%= config.bin %> login",
|
|
19881
|
+
"<%= config.bin %> login browser",
|
|
19882
|
+
"<%= config.bin %> login --no-browser",
|
|
19883
|
+
"<%= config.bin %> login user@example.com --signup-code invite-code --accept-terms",
|
|
19884
|
+
"<%= config.bin %> login confirm user@example.com 123456",
|
|
19885
|
+
"<%= config.bin %> login otp user@example.com --signup-code invite-code --accept-terms",
|
|
19886
|
+
"<%= config.bin %> login otp confirm user@example.com 123456"
|
|
19887
|
+
];
|
|
19888
|
+
static flags = SigninCommand.flags;
|
|
19889
|
+
retryCommand() {
|
|
19890
|
+
return "login";
|
|
19891
|
+
}
|
|
19892
|
+
emailCodeCopy() {
|
|
19893
|
+
return LOGIN_EMAIL_COPY;
|
|
19894
|
+
}
|
|
19537
19895
|
};
|
|
19538
|
-
var SigninBrowserCommand = class extends LoginCommand {
|
|
19896
|
+
var SigninBrowserCommand = class extends LoginCommand$1 {
|
|
19539
19897
|
static description = "Sign in to an existing Primitive account by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
|
|
19540
19898
|
static summary = "Sign in with browser approval";
|
|
19541
19899
|
static examples = [
|
|
@@ -19548,7 +19906,20 @@ var SigninBrowserCommand = class extends LoginCommand {
|
|
|
19548
19906
|
return "signin browser";
|
|
19549
19907
|
}
|
|
19550
19908
|
};
|
|
19551
|
-
var
|
|
19909
|
+
var LoginBrowserCommand = class extends LoginCommand$1 {
|
|
19910
|
+
static description = "Log in to an existing Primitive account by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
|
|
19911
|
+
static summary = "Log in with browser approval";
|
|
19912
|
+
static examples = [
|
|
19913
|
+
"<%= config.bin %> login browser",
|
|
19914
|
+
"<%= config.bin %> login browser --device-name work-laptop",
|
|
19915
|
+
"<%= config.bin %> login browser --no-browser",
|
|
19916
|
+
"<%= config.bin %> login browser --force"
|
|
19917
|
+
];
|
|
19918
|
+
retryCommand() {
|
|
19919
|
+
return "login browser";
|
|
19920
|
+
}
|
|
19921
|
+
};
|
|
19922
|
+
var SigninOtpCommand = class extends Command {
|
|
19552
19923
|
static args = { email: Args.string({
|
|
19553
19924
|
description: "Email address to sign in with",
|
|
19554
19925
|
required: false
|
|
@@ -19558,12 +19929,13 @@ var SigninOtpCommand = class SigninOtpCommand extends Command {
|
|
|
19558
19929
|
static examples = ["<%= config.bin %> signin otp user@example.com --signup-code invite-code --accept-terms", "<%= config.bin %> signin otp confirm user@example.com 123456"];
|
|
19559
19930
|
static flags = commonOtpStartFlags();
|
|
19560
19931
|
async run() {
|
|
19561
|
-
const
|
|
19932
|
+
const commandClass = this.constructor;
|
|
19933
|
+
const { args, flags } = await this.parse(commandClass);
|
|
19562
19934
|
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
19563
19935
|
try {
|
|
19564
19936
|
await runSignupStartWithCredentialLock({
|
|
19565
19937
|
configDir: this.config.configDir,
|
|
19566
|
-
copy:
|
|
19938
|
+
copy: this.emailCodeCopy(),
|
|
19567
19939
|
email: args.email,
|
|
19568
19940
|
flags
|
|
19569
19941
|
});
|
|
@@ -19571,8 +19943,31 @@ var SigninOtpCommand = class SigninOtpCommand extends Command {
|
|
|
19571
19943
|
releaseCredentialsLock();
|
|
19572
19944
|
}
|
|
19573
19945
|
}
|
|
19946
|
+
emailCodeCopy() {
|
|
19947
|
+
return SIGNIN_OTP_COPY;
|
|
19948
|
+
}
|
|
19574
19949
|
};
|
|
19575
|
-
var
|
|
19950
|
+
var LoginOtpCommand = class extends SigninOtpCommand {
|
|
19951
|
+
static args = SigninOtpCommand.args;
|
|
19952
|
+
static description = "Start email-code login using Primitive's signup/auth OTP flow, send a verification code, and save the pending token locally. Requires a signup code.";
|
|
19953
|
+
static summary = "Start OTP login";
|
|
19954
|
+
static examples = ["<%= config.bin %> login otp user@example.com --signup-code invite-code --accept-terms", "<%= config.bin %> login otp confirm user@example.com 123456"];
|
|
19955
|
+
static flags = SigninOtpCommand.flags;
|
|
19956
|
+
emailCodeCopy() {
|
|
19957
|
+
return LOGIN_OTP_COPY;
|
|
19958
|
+
}
|
|
19959
|
+
};
|
|
19960
|
+
var OtpCommand = class extends SigninOtpCommand {
|
|
19961
|
+
static args = SigninOtpCommand.args;
|
|
19962
|
+
static description = "Start email-code authentication, send a verification code, and save the pending token locally. Requires a signup code.";
|
|
19963
|
+
static summary = "Start email-code auth";
|
|
19964
|
+
static examples = ["<%= config.bin %> otp user@example.com --signup-code invite-code --accept-terms", "<%= config.bin %> otp confirm user@example.com 123456"];
|
|
19965
|
+
static flags = SigninOtpCommand.flags;
|
|
19966
|
+
emailCodeCopy() {
|
|
19967
|
+
return OTP_COPY;
|
|
19968
|
+
}
|
|
19969
|
+
};
|
|
19970
|
+
var SigninOtpConfirmCommand = class extends Command {
|
|
19576
19971
|
static args = {
|
|
19577
19972
|
email: Args.string({
|
|
19578
19973
|
description: "Email address used to start OTP sign-in",
|
|
@@ -19599,13 +19994,14 @@ var SigninOtpConfirmCommand = class SigninOtpConfirmCommand extends Command {
|
|
|
19599
19994
|
"org-id": Flags.string({ description: "Workspace id to target when the email belongs to multiple workspaces" })
|
|
19600
19995
|
};
|
|
19601
19996
|
async run() {
|
|
19602
|
-
const
|
|
19997
|
+
const commandClass = this.constructor;
|
|
19998
|
+
const { args, flags } = await this.parse(commandClass);
|
|
19603
19999
|
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
19604
20000
|
try {
|
|
19605
20001
|
await runSignupConfirmWithCredentialLock({
|
|
19606
20002
|
code: args.code,
|
|
19607
20003
|
configDir: this.config.configDir,
|
|
19608
|
-
copy:
|
|
20004
|
+
copy: this.emailCodeCopy(),
|
|
19609
20005
|
email: args.email,
|
|
19610
20006
|
flags
|
|
19611
20007
|
});
|
|
@@ -19613,8 +20009,51 @@ var SigninOtpConfirmCommand = class SigninOtpConfirmCommand extends Command {
|
|
|
19613
20009
|
releaseCredentialsLock();
|
|
19614
20010
|
}
|
|
19615
20011
|
}
|
|
20012
|
+
emailCodeCopy() {
|
|
20013
|
+
return SIGNIN_OTP_COPY;
|
|
20014
|
+
}
|
|
20015
|
+
};
|
|
20016
|
+
var SigninConfirmCommand = class extends SigninOtpConfirmCommand {
|
|
20017
|
+
static args = SigninOtpConfirmCommand.args;
|
|
20018
|
+
static description = "Confirm a pending email-code sign-in, create an OAuth session, and save CLI credentials locally.";
|
|
20019
|
+
static summary = "Confirm email-code sign-in";
|
|
20020
|
+
static examples = ["<%= config.bin %> signin confirm user@example.com 123456", "<%= config.bin %> signin confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000"];
|
|
20021
|
+
static flags = SigninOtpConfirmCommand.flags;
|
|
20022
|
+
emailCodeCopy() {
|
|
20023
|
+
return SIGNIN_EMAIL_COPY;
|
|
20024
|
+
}
|
|
19616
20025
|
};
|
|
19617
|
-
var
|
|
20026
|
+
var LoginConfirmCommand = class extends SigninOtpConfirmCommand {
|
|
20027
|
+
static args = SigninOtpConfirmCommand.args;
|
|
20028
|
+
static description = "Confirm a pending email-code login, create an OAuth session, and save CLI credentials locally.";
|
|
20029
|
+
static summary = "Confirm email-code login";
|
|
20030
|
+
static examples = ["<%= config.bin %> login confirm user@example.com 123456", "<%= config.bin %> login confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000"];
|
|
20031
|
+
static flags = SigninOtpConfirmCommand.flags;
|
|
20032
|
+
emailCodeCopy() {
|
|
20033
|
+
return LOGIN_EMAIL_COPY;
|
|
20034
|
+
}
|
|
20035
|
+
};
|
|
20036
|
+
var LoginOtpConfirmCommand = class extends SigninOtpConfirmCommand {
|
|
20037
|
+
static args = SigninOtpConfirmCommand.args;
|
|
20038
|
+
static description = "Confirm a pending OTP login, create an OAuth session, and save CLI credentials locally.";
|
|
20039
|
+
static summary = "Confirm OTP login";
|
|
20040
|
+
static examples = ["<%= config.bin %> login otp confirm user@example.com 123456", "<%= config.bin %> login otp confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000"];
|
|
20041
|
+
static flags = SigninOtpConfirmCommand.flags;
|
|
20042
|
+
emailCodeCopy() {
|
|
20043
|
+
return LOGIN_OTP_COPY;
|
|
20044
|
+
}
|
|
20045
|
+
};
|
|
20046
|
+
var OtpConfirmCommand = class extends SigninOtpConfirmCommand {
|
|
20047
|
+
static args = SigninOtpConfirmCommand.args;
|
|
20048
|
+
static description = "Confirm pending email-code authentication, create an OAuth session, and save CLI credentials locally.";
|
|
20049
|
+
static summary = "Confirm email-code auth";
|
|
20050
|
+
static examples = ["<%= config.bin %> otp confirm user@example.com 123456", "<%= config.bin %> otp confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000"];
|
|
20051
|
+
static flags = SigninOtpConfirmCommand.flags;
|
|
20052
|
+
emailCodeCopy() {
|
|
20053
|
+
return OTP_COPY;
|
|
20054
|
+
}
|
|
20055
|
+
};
|
|
20056
|
+
var SigninOtpResendCommand = class extends Command {
|
|
19618
20057
|
static args = { email: Args.string({
|
|
19619
20058
|
description: "Email address used to start OTP sign-in",
|
|
19620
20059
|
required: true
|
|
@@ -19628,12 +20067,13 @@ var SigninOtpResendCommand = class SigninOtpResendCommand extends Command {
|
|
|
19628
20067
|
hidden: true
|
|
19629
20068
|
}) };
|
|
19630
20069
|
async run() {
|
|
19631
|
-
const
|
|
20070
|
+
const commandClass = this.constructor;
|
|
20071
|
+
const { args, flags } = await this.parse(commandClass);
|
|
19632
20072
|
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
19633
20073
|
try {
|
|
19634
20074
|
await runSignupResendWithCredentialLock({
|
|
19635
20075
|
configDir: this.config.configDir,
|
|
19636
|
-
copy:
|
|
20076
|
+
copy: this.emailCodeCopy(),
|
|
19637
20077
|
email: args.email,
|
|
19638
20078
|
flags
|
|
19639
20079
|
});
|
|
@@ -19641,6 +20081,49 @@ var SigninOtpResendCommand = class SigninOtpResendCommand extends Command {
|
|
|
19641
20081
|
releaseCredentialsLock();
|
|
19642
20082
|
}
|
|
19643
20083
|
}
|
|
20084
|
+
emailCodeCopy() {
|
|
20085
|
+
return SIGNIN_OTP_COPY;
|
|
20086
|
+
}
|
|
20087
|
+
};
|
|
20088
|
+
var SigninResendCommand = class extends SigninOtpResendCommand {
|
|
20089
|
+
static args = SigninOtpResendCommand.args;
|
|
20090
|
+
static description = "Resend the verification code for a pending email-code sign-in.";
|
|
20091
|
+
static summary = "Resend email-code sign-in code";
|
|
20092
|
+
static examples = ["<%= config.bin %> signin resend user@example.com"];
|
|
20093
|
+
static flags = SigninOtpResendCommand.flags;
|
|
20094
|
+
emailCodeCopy() {
|
|
20095
|
+
return SIGNIN_EMAIL_COPY;
|
|
20096
|
+
}
|
|
20097
|
+
};
|
|
20098
|
+
var LoginResendCommand = class extends SigninOtpResendCommand {
|
|
20099
|
+
static args = SigninOtpResendCommand.args;
|
|
20100
|
+
static description = "Resend the verification code for a pending email-code login.";
|
|
20101
|
+
static summary = "Resend email-code login code";
|
|
20102
|
+
static examples = ["<%= config.bin %> login resend user@example.com"];
|
|
20103
|
+
static flags = SigninOtpResendCommand.flags;
|
|
20104
|
+
emailCodeCopy() {
|
|
20105
|
+
return LOGIN_EMAIL_COPY;
|
|
20106
|
+
}
|
|
20107
|
+
};
|
|
20108
|
+
var LoginOtpResendCommand = class extends SigninOtpResendCommand {
|
|
20109
|
+
static args = SigninOtpResendCommand.args;
|
|
20110
|
+
static description = "Resend the verification code for a pending OTP login.";
|
|
20111
|
+
static summary = "Resend OTP login code";
|
|
20112
|
+
static examples = ["<%= config.bin %> login otp resend user@example.com"];
|
|
20113
|
+
static flags = SigninOtpResendCommand.flags;
|
|
20114
|
+
emailCodeCopy() {
|
|
20115
|
+
return LOGIN_OTP_COPY;
|
|
20116
|
+
}
|
|
20117
|
+
};
|
|
20118
|
+
var OtpResendCommand = class extends SigninOtpResendCommand {
|
|
20119
|
+
static args = SigninOtpResendCommand.args;
|
|
20120
|
+
static description = "Resend the verification code for pending email-code authentication.";
|
|
20121
|
+
static summary = "Resend email-code auth code";
|
|
20122
|
+
static examples = ["<%= config.bin %> otp resend user@example.com"];
|
|
20123
|
+
static flags = SigninOtpResendCommand.flags;
|
|
20124
|
+
emailCodeCopy() {
|
|
20125
|
+
return OTP_COPY;
|
|
20126
|
+
}
|
|
19644
20127
|
};
|
|
19645
20128
|
//#endregion
|
|
19646
20129
|
//#region src/oclif/commands/whoami.ts
|
|
@@ -19955,6 +20438,7 @@ const CANONICAL_OPERATION_ALIASES = {
|
|
|
19955
20438
|
"sending:send": "sending:send-email",
|
|
19956
20439
|
"sent:get": "sending:get-sent-email",
|
|
19957
20440
|
"sent:list": "sending:list-sent-emails",
|
|
20441
|
+
"threads:get": "threads:get-thread",
|
|
19958
20442
|
"webhook-deliveries:list": "webhook-deliveries:list-deliveries",
|
|
19959
20443
|
"webhook-deliveries:replay": "webhook-deliveries:replay-delivery"
|
|
19960
20444
|
};
|
|
@@ -19986,11 +20470,22 @@ const COMMANDS = {
|
|
|
19986
20470
|
reply: ReplyCommand,
|
|
19987
20471
|
chat: ChatCommand,
|
|
19988
20472
|
login: LoginCommand,
|
|
20473
|
+
"login:browser": LoginBrowserCommand,
|
|
20474
|
+
"login:confirm": LoginConfirmCommand,
|
|
20475
|
+
"login:otp": LoginOtpCommand,
|
|
20476
|
+
"login:otp:confirm": LoginOtpConfirmCommand,
|
|
20477
|
+
"login:otp:resend": LoginOtpResendCommand,
|
|
20478
|
+
"login:resend": LoginResendCommand,
|
|
20479
|
+
otp: OtpCommand,
|
|
20480
|
+
"otp:confirm": OtpConfirmCommand,
|
|
20481
|
+
"otp:resend": OtpResendCommand,
|
|
19989
20482
|
signin: SigninCommand,
|
|
19990
20483
|
"signin:browser": SigninBrowserCommand,
|
|
20484
|
+
"signin:confirm": SigninConfirmCommand,
|
|
19991
20485
|
"signin:otp": SigninOtpCommand,
|
|
19992
20486
|
"signin:otp:confirm": SigninOtpConfirmCommand,
|
|
19993
20487
|
"signin:otp:resend": SigninOtpResendCommand,
|
|
20488
|
+
"signin:resend": SigninResendCommand,
|
|
19994
20489
|
signup: SignupCommand,
|
|
19995
20490
|
"signup:confirm": SignupConfirmCommand,
|
|
19996
20491
|
"signup:interactive": SignupInteractiveCommand,
|