@primitivedotdev/cli 0.32.0 → 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 +1280 -789
- package/man/primitive.1 +20 -0
- package/package.json +8 -2
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) {
|
|
@@ -14613,41 +14744,40 @@ function buildChatFollowUpCommands(context) {
|
|
|
14613
14744
|
return commands;
|
|
14614
14745
|
}
|
|
14615
14746
|
function buildChatRecoveryCommands(context) {
|
|
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
|
-
|
|
14649
|
-
|
|
14650
|
-
];
|
|
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;
|
|
14651
14781
|
}
|
|
14652
14782
|
function buildChatJsonEnvelope(context) {
|
|
14653
14783
|
const responseBody = resolveChatResponseBody(context.reply);
|
|
@@ -14794,7 +14924,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14794
14924
|
static summary = "Chat with an agent over email (send and wait for the reply)";
|
|
14795
14925
|
static examples = [
|
|
14796
14926
|
"<%= config.bin %> chat help@agent.acme.dev 'how do I rotate my API key?'",
|
|
14797
|
-
"cat error.log | <%= config.bin %> chat help@agent.acme.dev
|
|
14927
|
+
"cat error.log | <%= config.bin %> chat help@agent.acme.dev",
|
|
14798
14928
|
"<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing'",
|
|
14799
14929
|
"<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing' --reply-to-email-id <inbound-email-id>",
|
|
14800
14930
|
"<%= config.bin %> chat help@agent.acme.dev 'follow up question' --json",
|
|
@@ -14823,7 +14953,10 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14823
14953
|
hidden: true
|
|
14824
14954
|
}),
|
|
14825
14955
|
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
14826
|
-
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
|
+
}),
|
|
14827
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." }),
|
|
14828
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." }),
|
|
14829
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." }),
|
|
@@ -15130,6 +15263,38 @@ function redactConfig(config) {
|
|
|
15130
15263
|
environments: Object.fromEntries(Object.entries(config.environments).map(([name, environment]) => [name, redactCliEnvironment(environment)]))
|
|
15131
15264
|
};
|
|
15132
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
|
+
}
|
|
15133
15298
|
function switchCliEnvironment(configDir, environmentName) {
|
|
15134
15299
|
const environment = normalizeCliEnvironmentName(environmentName);
|
|
15135
15300
|
const config = loadOrCreateConfig(configDir);
|
|
@@ -15179,16 +15344,16 @@ var ConfigSetCommand = class ConfigSetCommand extends Command {
|
|
|
15179
15344
|
const { flags } = await this.parse(ConfigSetCommand);
|
|
15180
15345
|
const headers = flags.header ?? [];
|
|
15181
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 });
|
|
15182
|
-
const
|
|
15347
|
+
const { environment, removedCredentials } = upsertCliEnvironmentAndClearCredentialsIfSwitched({
|
|
15183
15348
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
15184
15349
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
15185
|
-
|
|
15350
|
+
configDir: this.config.configDir,
|
|
15186
15351
|
environmentName: flags.environment,
|
|
15187
15352
|
headers,
|
|
15188
15353
|
unsetHeaders: flags["unset-header"]
|
|
15189
15354
|
});
|
|
15190
|
-
|
|
15191
|
-
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");
|
|
15192
15357
|
}
|
|
15193
15358
|
};
|
|
15194
15359
|
var ConfigUseCommand = class ConfigUseCommand extends Command {
|
|
@@ -15878,7 +16043,9 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
15878
16043
|
process.exitCode = 1;
|
|
15879
16044
|
return;
|
|
15880
16045
|
}
|
|
15881
|
-
|
|
16046
|
+
const nextCursor = cursorFromAcceptedRows(page.rows);
|
|
16047
|
+
const cursorAdvanced = Boolean(nextCursor && nextCursor !== cursor);
|
|
16048
|
+
if (nextCursor) cursor = nextCursor;
|
|
15882
16049
|
for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
|
|
15883
16050
|
if (flags.table) {
|
|
15884
16051
|
if (!headerPrinted) {
|
|
@@ -15890,7 +16057,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
15890
16057
|
matched += 1;
|
|
15891
16058
|
if (matched >= flags.number) return;
|
|
15892
16059
|
}
|
|
15893
|
-
if (
|
|
16060
|
+
if (cursorAdvanced) continue;
|
|
15894
16061
|
if (deadline !== null && Date.now() >= deadline) break;
|
|
15895
16062
|
await sleep$1(flags.interval * 1e3);
|
|
15896
16063
|
}
|
|
@@ -16000,7 +16167,9 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
16000
16167
|
process.exitCode = 1;
|
|
16001
16168
|
return;
|
|
16002
16169
|
}
|
|
16003
|
-
|
|
16170
|
+
const nextCursor = cursorFromAcceptedRows(page.rows);
|
|
16171
|
+
const cursorAdvanced = Boolean(nextCursor && nextCursor !== cursor);
|
|
16172
|
+
if (nextCursor) cursor = nextCursor;
|
|
16004
16173
|
for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
|
|
16005
16174
|
if (flags.jsonl) this.log(JSON.stringify(email));
|
|
16006
16175
|
else {
|
|
@@ -16013,7 +16182,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
16013
16182
|
printed += 1;
|
|
16014
16183
|
if (flags.number && printed >= flags.number) return;
|
|
16015
16184
|
}
|
|
16016
|
-
if (
|
|
16185
|
+
if (cursorAdvanced) continue;
|
|
16017
16186
|
if (deadline !== null && Date.now() >= deadline) break;
|
|
16018
16187
|
await sleep$1(flags.interval * 1e3);
|
|
16019
16188
|
}
|
|
@@ -16769,7 +16938,7 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
16769
16938
|
url: "https://primitive.dev"
|
|
16770
16939
|
};
|
|
16771
16940
|
const SDK_VERSION_RANGE = "^0.32.0";
|
|
16772
|
-
const CLI_VERSION_RANGE = "^0.32.
|
|
16941
|
+
const CLI_VERSION_RANGE = "^0.32.1";
|
|
16773
16942
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
16774
16943
|
function renderHandler() {
|
|
16775
16944
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -16795,49 +16964,93 @@ interface Env {
|
|
|
16795
16964
|
PRIMITIVE_WEBHOOK_SECRET: string;
|
|
16796
16965
|
}
|
|
16797
16966
|
|
|
16798
|
-
//
|
|
16799
|
-
//
|
|
16800
|
-
//
|
|
16801
|
-
// you later switch to
|
|
16802
|
-
|
|
16803
|
-
|
|
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
|
+
}
|
|
16804
17007
|
|
|
16805
17008
|
// Loop protection. A newly deployed Function starts as a fallback
|
|
16806
|
-
// endpoint for managed
|
|
16807
|
-
//
|
|
16808
|
-
//
|
|
16809
|
-
//
|
|
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.
|
|
16810
17014
|
//
|
|
16811
|
-
// The default check
|
|
16812
|
-
//
|
|
16813
|
-
//
|
|
16814
|
-
//
|
|
16815
|
-
// Substring matching is deliberate so display-name forms like
|
|
16816
|
-
// "Support <support@example.com>" match a bare-address REPLY_FROM,
|
|
16817
|
-
// but it also accepts false positives where REPLY_FROM is a suffix
|
|
16818
|
-
// of another address (e.g. REPLY_FROM="info@x.com" matches
|
|
16819
|
-
// "mr.info@x.com"). For strict equality, parse the address out of the
|
|
16820
|
-
// 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.
|
|
16821
17019
|
//
|
|
16822
17020
|
// Extend this helper if you need stricter detection. Common additions:
|
|
16823
|
-
// - Match the org's signup / account-owner email (not auto-injected
|
|
16824
|
-
// into env today; either bake it into a SIGNUP_EMAIL const or read
|
|
16825
|
-
// it from a secret you set via \`primitive functions:set-secret\`).
|
|
16826
17021
|
// - Honor RFC 3834 auto-response headers: skip when
|
|
16827
|
-
//
|
|
16828
|
-
//
|
|
16829
|
-
// is present.
|
|
17022
|
+
// event.email.headers["auto-submitted"] is anything other than "no",
|
|
17023
|
+
// or when a List-Unsubscribe / Precedence: bulk header is present.
|
|
16830
17024
|
// - Track Message-ID / In-Reply-To chains to break ping-pong loops
|
|
16831
17025
|
// between two cooperating handlers on different domains.
|
|
16832
17026
|
export function isLoop(event: EmailReceivedEvent): boolean {
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
16838
|
-
|
|
16839
|
-
|
|
16840
|
-
|
|
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
|
+
|
|
16841
17054
|
return false;
|
|
16842
17055
|
}
|
|
16843
17056
|
|
|
@@ -16885,11 +17098,16 @@ export default {
|
|
|
16885
17098
|
apiBaseUrl1: env.PRIMITIVE_API_BASE_URL,
|
|
16886
17099
|
});
|
|
16887
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
|
+
|
|
16888
17106
|
// Recipient gate
|
|
16889
17107
|
// https://www.primitive.dev/docs/sending#who-you-can-send-to
|
|
16890
17108
|
// Even via client.reply, sends to the original sender are
|
|
16891
17109
|
// subject to the recipient gate. New accounts can send to
|
|
16892
|
-
//
|
|
17110
|
+
// Primitive-managed domains, verified domains, addresses that
|
|
16893
17111
|
// have authenticated to you, and other org-member signup emails.
|
|
16894
17112
|
// Sends to arbitrary external addresses return 403
|
|
16895
17113
|
// recipient_not_allowed with a structured gates[] array until
|
|
@@ -16939,8 +17157,10 @@ function renderPackageJson(name) {
|
|
|
16939
17157
|
type: "module",
|
|
16940
17158
|
scripts: {
|
|
16941
17159
|
build: "node build.mjs",
|
|
16942
|
-
deploy: `npm run build && primitive functions deploy --name ${name} --file ./dist/handler.js`,
|
|
16943
|
-
|
|
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"
|
|
16944
17164
|
},
|
|
16945
17165
|
dependencies: { "@primitivedotdev/sdk": SDK_VERSION_RANGE },
|
|
16946
17166
|
devDependencies: {
|
|
@@ -17011,13 +17231,47 @@ npm run build
|
|
|
17011
17231
|
npm run deploy
|
|
17012
17232
|
\`\`\`
|
|
17013
17233
|
|
|
17014
|
-
The deploy step calls \`primitive functions deploy\` (provided
|
|
17015
|
-
\`@primitivedotdev/cli\` package; install with
|
|
17234
|
+
The deploy step calls \`primitive functions deploy --wait\` (provided
|
|
17235
|
+
by the \`@primitivedotdev/cli\` package; install with
|
|
17016
17236
|
\`npm install -g @primitivedotdev/cli\` or run via
|
|
17017
17237
|
\`npx @primitivedotdev/cli@latest <command>\`). It requires
|
|
17018
17238
|
\`PRIMITIVE_API_KEY\` to be set in your shell (or pass \`--api-key\`).
|
|
17019
17239
|
Run \`primitive signin\` once to save a key in your CLI config if you
|
|
17020
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
|
+
\`\`\`
|
|
17021
17275
|
`;
|
|
17022
17276
|
}
|
|
17023
17277
|
function renderEmailReplyTemplateFiles(name) {
|
|
@@ -17192,8 +17446,9 @@ var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
|
17192
17446
|
this.log("Next:");
|
|
17193
17447
|
this.log(` cd ${outDir}`);
|
|
17194
17448
|
this.log(" npm install");
|
|
17195
|
-
this.log(" npm run
|
|
17196
|
-
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");
|
|
17197
17452
|
}
|
|
17198
17453
|
};
|
|
17199
17454
|
//#endregion
|
|
@@ -17823,26 +18078,32 @@ var FunctionsTemplatesCommand = class FunctionsTemplatesCommand extends Command
|
|
|
17823
18078
|
//#endregion
|
|
17824
18079
|
//#region src/oclif/commands/functions-test-function.ts
|
|
17825
18080
|
const DEFAULT_WAIT_TIMEOUT_SECONDS = 60;
|
|
17826
|
-
const
|
|
18081
|
+
const TERMINAL_TEST_TRACE_STATES = new Set([
|
|
18082
|
+
"completed",
|
|
18083
|
+
"failed",
|
|
18084
|
+
"send_failed"
|
|
18085
|
+
]);
|
|
17827
18086
|
function buildFunctionTestOutcome(params) {
|
|
18087
|
+
const inbound = params.trace.inbound_email;
|
|
17828
18088
|
const outcome = {
|
|
17829
18089
|
elapsed_seconds: params.elapsedSeconds,
|
|
17830
18090
|
function_id: params.functionId,
|
|
17831
18091
|
inbound_domain: params.invocation.inbound_domain,
|
|
17832
|
-
inbound_id:
|
|
18092
|
+
inbound_id: inbound?.id ?? null,
|
|
17833
18093
|
inbound_to: params.invocation.to,
|
|
17834
18094
|
poll_since: params.invocation.poll_since,
|
|
18095
|
+
state: params.trace.state,
|
|
17835
18096
|
test_run_id: params.invocation.test_run_id,
|
|
17836
18097
|
test_send_id: params.invocation.send_id,
|
|
17837
18098
|
test_subject: params.invocation.subject,
|
|
17838
18099
|
trace_url: params.invocation.trace_url,
|
|
17839
18100
|
watch_url: params.invocation.watch_url,
|
|
17840
|
-
webhook_attempt_count:
|
|
17841
|
-
webhook_last_error:
|
|
17842
|
-
webhook_last_status_code:
|
|
17843
|
-
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
|
|
17844
18105
|
};
|
|
17845
|
-
if (params.showSends) outcome.sent_emails = params.
|
|
18106
|
+
if (params.showSends) outcome.sent_emails = params.trace.replies;
|
|
17846
18107
|
return outcome;
|
|
17847
18108
|
}
|
|
17848
18109
|
function writeFunctionTestProgress(message, writeStderr = (chunk) => {
|
|
@@ -17941,7 +18202,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
17941
18202
|
required: true
|
|
17942
18203
|
}),
|
|
17943
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>`." }),
|
|
17944
|
-
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." }),
|
|
17945
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." }),
|
|
17946
18207
|
timeout: Flags.integer({
|
|
17947
18208
|
default: DEFAULT_WAIT_TIMEOUT_SECONDS,
|
|
@@ -18001,41 +18262,15 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
18001
18262
|
const timeoutMs = flags.timeout * 1e3;
|
|
18002
18263
|
const pollIntervalMs = flags["poll-interval"] * 1e3;
|
|
18003
18264
|
const isExpired = () => flags.timeout > 0 && Date.now() - startedAt > timeoutMs;
|
|
18004
|
-
writeFunctionTestProgress(`Waiting for test
|
|
18005
|
-
let
|
|
18006
|
-
while (!isExpired()) {
|
|
18007
|
-
const page = await fetchEmailSearchPage({
|
|
18008
|
-
apiClient,
|
|
18009
|
-
filters: { to: invocation.to },
|
|
18010
|
-
pageSize: 25,
|
|
18011
|
-
since: invocation.poll_since
|
|
18012
|
-
});
|
|
18013
|
-
if (!page.ok) {
|
|
18014
|
-
const payload = extractErrorPayload(page.error);
|
|
18015
|
-
writeErrorWithHints(payload);
|
|
18016
|
-
surfaceUnauthorizedHint({
|
|
18017
|
-
auth,
|
|
18018
|
-
baseUrlOverridden,
|
|
18019
|
-
configDir: this.config.configDir,
|
|
18020
|
-
payload
|
|
18021
|
-
});
|
|
18022
|
-
process.exitCode = 1;
|
|
18023
|
-
return;
|
|
18024
|
-
}
|
|
18025
|
-
const found = page.rows[0];
|
|
18026
|
-
if (found) {
|
|
18027
|
-
inboundId = found.id;
|
|
18028
|
-
break;
|
|
18029
|
-
}
|
|
18030
|
-
await sleep$1(pollIntervalMs);
|
|
18031
|
-
}
|
|
18032
|
-
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 });
|
|
18033
|
-
writeFunctionTestProgress(`Inbound landed (${inboundId}). Waiting for function to run...`);
|
|
18034
|
-
let detail;
|
|
18265
|
+
writeFunctionTestProgress(`Waiting for test run ${invocation.test_run_id} to complete for ${invocation.to}...`);
|
|
18266
|
+
let trace;
|
|
18035
18267
|
while (!isExpired()) {
|
|
18036
|
-
const result = await
|
|
18268
|
+
const result = await getFunctionTestRunTrace({
|
|
18037
18269
|
client: apiClient.client,
|
|
18038
|
-
path: {
|
|
18270
|
+
path: {
|
|
18271
|
+
id: flags.id,
|
|
18272
|
+
run_id: invocation.test_run_id
|
|
18273
|
+
},
|
|
18039
18274
|
responseStyle: "fields"
|
|
18040
18275
|
});
|
|
18041
18276
|
if (result.error) {
|
|
@@ -18051,24 +18286,22 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
18051
18286
|
return;
|
|
18052
18287
|
}
|
|
18053
18288
|
const fetched = result.data.data;
|
|
18054
|
-
if (
|
|
18055
|
-
|
|
18289
|
+
if (TERMINAL_TEST_TRACE_STATES.has(fetched.state)) {
|
|
18290
|
+
trace = fetched;
|
|
18056
18291
|
break;
|
|
18057
18292
|
}
|
|
18058
18293
|
await sleep$1(pollIntervalMs);
|
|
18059
18294
|
}
|
|
18060
|
-
if (!
|
|
18061
|
-
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 });
|
|
18062
18296
|
const outcome = buildFunctionTestOutcome({
|
|
18063
|
-
|
|
18064
|
-
elapsedSeconds,
|
|
18297
|
+
elapsedSeconds: Math.round((Date.now() - startedAt) / 1e3),
|
|
18065
18298
|
functionId: flags.id,
|
|
18066
|
-
inboundId,
|
|
18067
18299
|
invocation,
|
|
18068
|
-
showSends: shouldShowSends
|
|
18300
|
+
showSends: shouldShowSends,
|
|
18301
|
+
trace
|
|
18069
18302
|
});
|
|
18070
18303
|
this.log(JSON.stringify(outcome, null, 2));
|
|
18071
|
-
if (
|
|
18304
|
+
if (trace.state === "failed" || trace.state === "send_failed") process.exitCode = 1;
|
|
18072
18305
|
});
|
|
18073
18306
|
}
|
|
18074
18307
|
};
|
|
@@ -18319,7 +18552,7 @@ async function checkExistingLogin(params) {
|
|
|
18319
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."
|
|
18320
18553
|
};
|
|
18321
18554
|
}
|
|
18322
|
-
var LoginCommand = class extends Command {
|
|
18555
|
+
var LoginCommand$1 = class extends Command {
|
|
18323
18556
|
static description = "Log in by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
|
|
18324
18557
|
static summary = "Log in with browser approval";
|
|
18325
18558
|
static examples = [
|
|
@@ -18343,6 +18576,9 @@ var LoginCommand = class extends Command {
|
|
|
18343
18576
|
async run() {
|
|
18344
18577
|
const commandClass = this.constructor;
|
|
18345
18578
|
const { flags } = await this.parse(commandClass);
|
|
18579
|
+
await this.runBrowserLogin(flags, this.retryCommand());
|
|
18580
|
+
}
|
|
18581
|
+
async runBrowserLogin(flags, retryCommand = this.retryCommand()) {
|
|
18346
18582
|
let releaseCredentialsLock;
|
|
18347
18583
|
try {
|
|
18348
18584
|
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
@@ -18350,7 +18586,7 @@ var LoginCommand = class extends Command {
|
|
|
18350
18586
|
throw cliError$3(error instanceof Error ? error.message : String(error));
|
|
18351
18587
|
}
|
|
18352
18588
|
try {
|
|
18353
|
-
await this.runWithCredentialLock(flags,
|
|
18589
|
+
await this.runWithCredentialLock(flags, retryCommand);
|
|
18354
18590
|
} finally {
|
|
18355
18591
|
releaseCredentialsLock();
|
|
18356
18592
|
}
|
|
@@ -18458,520 +18694,85 @@ var LoginCommand = class extends Command {
|
|
|
18458
18694
|
}
|
|
18459
18695
|
};
|
|
18460
18696
|
//#endregion
|
|
18461
|
-
//#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
|
+
};
|
|
18462
18710
|
function cliError$2(message) {
|
|
18463
18711
|
return new Errors.CLIError(message, { exit: 1 });
|
|
18464
18712
|
}
|
|
18465
18713
|
function unwrapData$1(value) {
|
|
18466
18714
|
return value?.data ?? null;
|
|
18467
18715
|
}
|
|
18468
|
-
function
|
|
18469
|
-
return
|
|
18716
|
+
function isRecord(value) {
|
|
18717
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18470
18718
|
}
|
|
18471
|
-
|
|
18472
|
-
|
|
18473
|
-
|
|
18474
|
-
|
|
18475
|
-
|
|
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
|
|
18476
18734
|
};
|
|
18477
|
-
|
|
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`);
|
|
18478
18758
|
try {
|
|
18479
|
-
|
|
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;
|
|
18480
18764
|
} catch (error) {
|
|
18481
|
-
|
|
18482
|
-
|
|
18483
|
-
process.stderr.write(`Removed unreadable Primitive CLI credentials. Backing OAuth grant was not revoked: ${detail}\n`);
|
|
18484
|
-
process.exitCode = 1;
|
|
18485
|
-
return;
|
|
18765
|
+
rmSync(tempPath, { force: true });
|
|
18766
|
+
throw error;
|
|
18486
18767
|
}
|
|
18487
|
-
|
|
18488
|
-
|
|
18768
|
+
}
|
|
18769
|
+
function loadPendingAgentSignup(configDir, apiBaseUrl1) {
|
|
18770
|
+
const path = pendingSignupPath(configDir);
|
|
18771
|
+
let contents;
|
|
18489
18772
|
try {
|
|
18490
|
-
|
|
18491
|
-
apiBaseUrl1: params.flags["api-base-url-1"],
|
|
18492
|
-
configDir: params.configDir,
|
|
18493
|
-
credentialsLockHeld: true
|
|
18494
|
-
});
|
|
18773
|
+
contents = readFileSync(path, "utf8");
|
|
18495
18774
|
} catch (error) {
|
|
18496
|
-
if (
|
|
18497
|
-
process.stderr.write("Logged out (OAuth session was already expired or revoked on the server).\n");
|
|
18498
|
-
return;
|
|
18499
|
-
}
|
|
18500
|
-
throw error;
|
|
18501
|
-
}
|
|
18502
|
-
const freshCredentials = authenticated.auth.credentials ?? credentials;
|
|
18503
|
-
const result = await deps.cliLogout({
|
|
18504
|
-
body: { key_id: freshCredentials.oauth_grant_id },
|
|
18505
|
-
client: authenticated.apiClient.client,
|
|
18506
|
-
responseStyle: "fields"
|
|
18507
|
-
});
|
|
18508
|
-
if (result.error) {
|
|
18509
|
-
const payload = extractErrorPayload(result.error);
|
|
18510
|
-
const code = extractErrorCode(payload);
|
|
18511
|
-
if (code === API_ERROR_CODES.unauthorized || code === API_ERROR_CODES.notFound) {
|
|
18512
|
-
deleteCliCredentials(params.configDir);
|
|
18513
|
-
writeErrorWithHints(payload);
|
|
18514
|
-
process.stderr.write("Removed saved Primitive CLI credentials because the backing OAuth grant is already unavailable.\n");
|
|
18515
|
-
process.exitCode = 1;
|
|
18516
|
-
return;
|
|
18517
|
-
}
|
|
18518
|
-
writeErrorWithHints(payload);
|
|
18519
|
-
throw cliError$2("Could not revoke the saved Primitive CLI OAuth grant.");
|
|
18520
|
-
}
|
|
18521
|
-
const logout = unwrapData$1(result.data);
|
|
18522
|
-
deleteCliCredentials(params.configDir);
|
|
18523
|
-
const grantId = logout?.oauth_grant_id ?? freshCredentials.oauth_grant_id;
|
|
18524
|
-
process.stderr.write(`Logged out and revoked OAuth grant ${grantId}.\n`);
|
|
18525
|
-
}
|
|
18526
|
-
var LogoutCommand = class LogoutCommand extends Command {
|
|
18527
|
-
static description = "Log out by revoking the saved Primitive CLI OAuth grant and deleting local credentials.";
|
|
18528
|
-
static summary = "Log out and revoke the saved CLI OAuth grant";
|
|
18529
|
-
static examples = ["<%= config.bin %> logout"];
|
|
18530
|
-
static flags = { "api-base-url-1": Flags.string({
|
|
18531
|
-
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
18532
|
-
env: "PRIMITIVE_API_BASE_URL_1",
|
|
18533
|
-
hidden: true
|
|
18534
|
-
}) };
|
|
18535
|
-
async run() {
|
|
18536
|
-
const { flags } = await this.parse(LogoutCommand);
|
|
18537
|
-
let releaseCredentialsLock;
|
|
18538
|
-
try {
|
|
18539
|
-
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
18540
|
-
} catch (error) {
|
|
18541
|
-
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
18542
|
-
}
|
|
18543
|
-
try {
|
|
18544
|
-
await runLogoutWithCredentialLock({
|
|
18545
|
-
configDir: this.config.configDir,
|
|
18546
|
-
flags
|
|
18547
|
-
});
|
|
18548
|
-
} finally {
|
|
18549
|
-
releaseCredentialsLock();
|
|
18550
|
-
}
|
|
18551
|
-
}
|
|
18552
|
-
};
|
|
18553
|
-
//#endregion
|
|
18554
|
-
//#region src/oclif/message-body-sources.ts
|
|
18555
|
-
function defaultReadFile(path) {
|
|
18556
|
-
return readFileSync(path, "utf8");
|
|
18557
|
-
}
|
|
18558
|
-
function defaultReadStdin() {
|
|
18559
|
-
if (process.stdin.isTTY) throw new Error("stdin is a TTY; pipe a value into this command or pass a file/string source instead.");
|
|
18560
|
-
return readFileSync(0, "utf8");
|
|
18561
|
-
}
|
|
18562
|
-
function selectedSources(sources) {
|
|
18563
|
-
return sources.filter(([, selected]) => selected).map(([label]) => label);
|
|
18564
|
-
}
|
|
18565
|
-
function readTextFile(path, label, readFile) {
|
|
18566
|
-
try {
|
|
18567
|
-
return {
|
|
18568
|
-
content: readFile(path),
|
|
18569
|
-
kind: "ok"
|
|
18570
|
-
};
|
|
18571
|
-
} catch (error) {
|
|
18572
|
-
return {
|
|
18573
|
-
kind: "error",
|
|
18574
|
-
message: `Could not read ${label} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
18575
|
-
};
|
|
18576
|
-
}
|
|
18577
|
-
}
|
|
18578
|
-
function readTextStdin(label, readStdin) {
|
|
18579
|
-
try {
|
|
18580
|
-
return {
|
|
18581
|
-
content: readStdin(),
|
|
18582
|
-
kind: "ok"
|
|
18583
|
-
};
|
|
18584
|
-
} catch (error) {
|
|
18585
|
-
return {
|
|
18586
|
-
kind: "error",
|
|
18587
|
-
message: `Could not read ${label}: ${error instanceof Error ? error.message : String(error)}`
|
|
18588
|
-
};
|
|
18589
|
-
}
|
|
18590
|
-
}
|
|
18591
|
-
function resolveMessageBodies(input) {
|
|
18592
|
-
const bodySources = selectedSources([
|
|
18593
|
-
["--body", input.body !== void 0],
|
|
18594
|
-
["--body-file", input.bodyFile !== void 0],
|
|
18595
|
-
["--body-stdin", input.bodyStdin === true]
|
|
18596
|
-
]);
|
|
18597
|
-
if (bodySources.length > 1) return {
|
|
18598
|
-
kind: "error",
|
|
18599
|
-
message: `Pass only one plain-text body source (got ${bodySources.join(", ")}).`
|
|
18600
|
-
};
|
|
18601
|
-
const htmlSources = selectedSources([
|
|
18602
|
-
["--html", input.html !== void 0],
|
|
18603
|
-
["--html-file", input.htmlFile !== void 0],
|
|
18604
|
-
["--html-stdin", input.htmlStdin === true]
|
|
18605
|
-
]);
|
|
18606
|
-
if (htmlSources.length > 1) return {
|
|
18607
|
-
kind: "error",
|
|
18608
|
-
message: `Pass only one HTML body source (got ${htmlSources.join(", ")}).`
|
|
18609
|
-
};
|
|
18610
|
-
const stdinSources = selectedSources([["--body-stdin", input.bodyStdin === true], ["--html-stdin", input.htmlStdin === true]]);
|
|
18611
|
-
if (stdinSources.length > 1) return {
|
|
18612
|
-
kind: "error",
|
|
18613
|
-
message: `Stdin can only be consumed once (got ${stdinSources.join(", ")}).`
|
|
18614
|
-
};
|
|
18615
|
-
if (bodySources.length === 0 && htmlSources.length === 0) return {
|
|
18616
|
-
kind: "error",
|
|
18617
|
-
message: "Either a plain-text body source or an HTML body source is required."
|
|
18618
|
-
};
|
|
18619
|
-
const readFile = input.readFile ?? defaultReadFile;
|
|
18620
|
-
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
18621
|
-
let body = input.body;
|
|
18622
|
-
let html = input.html;
|
|
18623
|
-
if (input.bodyFile !== void 0) {
|
|
18624
|
-
const result = readTextFile(input.bodyFile, "--body-file", readFile);
|
|
18625
|
-
if (result.kind === "error") return result;
|
|
18626
|
-
body = result.content;
|
|
18627
|
-
}
|
|
18628
|
-
if (input.bodyStdin === true) {
|
|
18629
|
-
const result = readTextStdin("--body-stdin", readStdin);
|
|
18630
|
-
if (result.kind === "error") return result;
|
|
18631
|
-
body = result.content;
|
|
18632
|
-
}
|
|
18633
|
-
if (input.htmlFile !== void 0) {
|
|
18634
|
-
const result = readTextFile(input.htmlFile, "--html-file", readFile);
|
|
18635
|
-
if (result.kind === "error") return result;
|
|
18636
|
-
html = result.content;
|
|
18637
|
-
}
|
|
18638
|
-
if (input.htmlStdin === true) {
|
|
18639
|
-
const result = readTextStdin("--html-stdin", readStdin);
|
|
18640
|
-
if (result.kind === "error") return result;
|
|
18641
|
-
html = result.content;
|
|
18642
|
-
}
|
|
18643
|
-
if (!body && !html) return {
|
|
18644
|
-
kind: "error",
|
|
18645
|
-
message: "Either a non-empty plain-text body or a non-empty HTML body is required."
|
|
18646
|
-
};
|
|
18647
|
-
return {
|
|
18648
|
-
...body !== void 0 ? { body } : {},
|
|
18649
|
-
...html !== void 0 ? { html } : {},
|
|
18650
|
-
kind: "ok"
|
|
18651
|
-
};
|
|
18652
|
-
}
|
|
18653
|
-
//#endregion
|
|
18654
|
-
//#region src/oclif/commands/reply.ts
|
|
18655
|
-
var ReplyCommand = class ReplyCommand extends Command {
|
|
18656
|
-
static description = `Reply to an inbound email.
|
|
18657
|
-
|
|
18658
|
-
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.`;
|
|
18659
|
-
static summary = "Reply to an inbound email";
|
|
18660
|
-
static examples = [
|
|
18661
|
-
"<%= config.bin %> reply --id <inbound-email-id> --body 'Thanks, got it.'",
|
|
18662
|
-
"<%= config.bin %> reply --id <inbound-email-id> --body-file ./reply.txt",
|
|
18663
|
-
"<%= config.bin %> reply --id <inbound-email-id> --html '<p>Thanks, got it.</p>' --wait",
|
|
18664
|
-
"<%= config.bin %> reply --id <inbound-email-id> --from 'Support <support@example.com>' --body 'Thanks!'"
|
|
18665
|
-
];
|
|
18666
|
-
static flags = {
|
|
18667
|
-
"api-key": Flags.string({
|
|
18668
|
-
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
18669
|
-
env: "PRIMITIVE_API_KEY"
|
|
18670
|
-
}),
|
|
18671
|
-
"api-base-url-1": Flags.string({
|
|
18672
|
-
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
18673
|
-
env: "PRIMITIVE_API_BASE_URL_1",
|
|
18674
|
-
hidden: true
|
|
18675
|
-
}),
|
|
18676
|
-
"api-base-url-2": Flags.string({
|
|
18677
|
-
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
18678
|
-
env: "PRIMITIVE_API_BASE_URL_2",
|
|
18679
|
-
hidden: true
|
|
18680
|
-
}),
|
|
18681
|
-
id: Flags.string({
|
|
18682
|
-
description: "Inbound email id to reply to.",
|
|
18683
|
-
required: true
|
|
18684
|
-
}),
|
|
18685
|
-
body: Flags.string({ description: "Plain-text reply body. Either --body or --html (or both) is required." }),
|
|
18686
|
-
"body-file": Flags.string({ description: "Read the plain-text reply body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
|
|
18687
|
-
"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." }),
|
|
18688
|
-
html: Flags.string({ description: "HTML reply body. Either --body or --html (or both) is required." }),
|
|
18689
|
-
"html-file": Flags.string({ description: "Read the HTML reply body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
18690
|
-
"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." }),
|
|
18691
|
-
from: Flags.string({ description: "Optional From header override. Defaults to the inbound recipient." }),
|
|
18692
|
-
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." }),
|
|
18693
|
-
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
18694
|
-
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
18695
|
-
};
|
|
18696
|
-
async run() {
|
|
18697
|
-
const { flags } = await this.parse(ReplyCommand);
|
|
18698
|
-
const bodies = resolveMessageBodies({
|
|
18699
|
-
body: flags.body,
|
|
18700
|
-
bodyFile: flags["body-file"],
|
|
18701
|
-
bodyStdin: flags["body-stdin"],
|
|
18702
|
-
html: flags.html,
|
|
18703
|
-
htmlFile: flags["html-file"],
|
|
18704
|
-
htmlStdin: flags["html-stdin"]
|
|
18705
|
-
});
|
|
18706
|
-
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
18707
|
-
await runWithTiming(flags.time, async () => {
|
|
18708
|
-
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
18709
|
-
apiKey: flags["api-key"],
|
|
18710
|
-
apiBaseUrl1: flags["api-base-url-1"],
|
|
18711
|
-
apiBaseUrl2: flags["api-base-url-2"],
|
|
18712
|
-
configDir: this.config.configDir
|
|
18713
|
-
});
|
|
18714
|
-
const result = await replyToEmail({
|
|
18715
|
-
body: {
|
|
18716
|
-
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
18717
|
-
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
18718
|
-
...flags.from !== void 0 ? { from: flags.from } : {},
|
|
18719
|
-
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
18720
|
-
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
18721
|
-
},
|
|
18722
|
-
client: apiClient.client,
|
|
18723
|
-
path: { id: flags.id },
|
|
18724
|
-
responseStyle: "fields"
|
|
18725
|
-
});
|
|
18726
|
-
if (result.error) {
|
|
18727
|
-
const errorPayload = extractErrorPayload(result.error);
|
|
18728
|
-
writeErrorWithHints(errorPayload);
|
|
18729
|
-
surfaceUnauthorizedHint({
|
|
18730
|
-
auth,
|
|
18731
|
-
baseUrlOverridden,
|
|
18732
|
-
configDir: this.config.configDir,
|
|
18733
|
-
payload: errorPayload
|
|
18734
|
-
});
|
|
18735
|
-
process.exitCode = 1;
|
|
18736
|
-
return;
|
|
18737
|
-
}
|
|
18738
|
-
const envelope = result.data;
|
|
18739
|
-
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
18740
|
-
process.stderr.write(chunk);
|
|
18741
|
-
} });
|
|
18742
|
-
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
18743
|
-
});
|
|
18744
|
-
}
|
|
18745
|
-
};
|
|
18746
|
-
//#endregion
|
|
18747
|
-
//#region src/oclif/attachments.ts
|
|
18748
|
-
function readAttachmentBytes(path, readFile) {
|
|
18749
|
-
try {
|
|
18750
|
-
return Buffer.from(readFile(path));
|
|
18751
|
-
} catch (error) {
|
|
18752
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
18753
|
-
throw new Errors.CLIError(`Could not read --attachment ${path}: ${detail}`, { exit: 1 });
|
|
18754
|
-
}
|
|
18755
|
-
}
|
|
18756
|
-
function hasControlCharacter(value) {
|
|
18757
|
-
return Array.from(value).some((character) => {
|
|
18758
|
-
const code = character.charCodeAt(0);
|
|
18759
|
-
return code <= 31 || code >= 127 && code <= 159;
|
|
18760
|
-
});
|
|
18761
|
-
}
|
|
18762
|
-
function validateAttachmentFilename(path, filename) {
|
|
18763
|
-
if (!filename) throw new Errors.CLIError(`Could not derive an attachment filename from ${path}. Pass a file path.`, { exit: 1 });
|
|
18764
|
-
if (hasControlCharacter(filename)) throw new Errors.CLIError(`Attachment filename ${filename} contains control characters.`, { exit: 1 });
|
|
18765
|
-
}
|
|
18766
|
-
function readAttachmentFiles(paths, readFile = readFileSync) {
|
|
18767
|
-
if (!paths || paths.length === 0) return void 0;
|
|
18768
|
-
return paths.map((path) => {
|
|
18769
|
-
const filename = basename(path);
|
|
18770
|
-
validateAttachmentFilename(path, filename);
|
|
18771
|
-
const bytes = readAttachmentBytes(path, readFile);
|
|
18772
|
-
if (bytes.length === 0) throw new Errors.CLIError(`Attachment file ${path} is empty. Attachments must contain at least one byte.`, { exit: 1 });
|
|
18773
|
-
return {
|
|
18774
|
-
content_base64: bytes.toString("base64"),
|
|
18775
|
-
filename
|
|
18776
|
-
};
|
|
18777
|
-
});
|
|
18778
|
-
}
|
|
18779
|
-
//#endregion
|
|
18780
|
-
//#region src/oclif/commands/send.ts
|
|
18781
|
-
var SendCommand = class SendCommand extends Command {
|
|
18782
|
-
static description = `Send an outbound email. Agent-grade shortcut for \`sending send\` with sensible defaults.
|
|
18783
|
-
|
|
18784
|
-
--from defaults to agent@<your-first-verified-outbound-domain> when omitted.
|
|
18785
|
-
--subject defaults to the first line of the body when omitted.
|
|
18786
|
-
--attachment attaches a file; repeat it to attach multiple files.
|
|
18787
|
-
|
|
18788
|
-
For the full flag set (custom message-id threading on the wire,
|
|
18789
|
-
references arrays, etc.), use \`primitive sending send\`.`;
|
|
18790
|
-
static summary = "Send an email (simplified, agent-friendly)";
|
|
18791
|
-
static examples = [
|
|
18792
|
-
"<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
|
|
18793
|
-
"<%= config.bin %> send --to alice@example.com --body-file ./message.txt",
|
|
18794
|
-
"<%= config.bin %> send --to alice@example.com --body 'See attached.' --attachment ./report.pdf",
|
|
18795
|
-
"<%= config.bin %> send --to alice@example.com --from support@yourcompany.com --subject 'Quick question' --body 'Are you free Thursday?'",
|
|
18796
|
-
"<%= config.bin %> send --to alice@example.com --html '<p>Hello!</p>'",
|
|
18797
|
-
"<%= config.bin %> send --to alice@example.com --body 'Confirmed' --wait",
|
|
18798
|
-
"<%= 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"
|
|
18799
|
-
];
|
|
18800
|
-
static flags = {
|
|
18801
|
-
"api-key": Flags.string({
|
|
18802
|
-
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
18803
|
-
env: "PRIMITIVE_API_KEY"
|
|
18804
|
-
}),
|
|
18805
|
-
"api-base-url-1": Flags.string({
|
|
18806
|
-
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
18807
|
-
env: "PRIMITIVE_API_BASE_URL_1",
|
|
18808
|
-
hidden: true
|
|
18809
|
-
}),
|
|
18810
|
-
"api-base-url-2": Flags.string({
|
|
18811
|
-
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
18812
|
-
env: "PRIMITIVE_API_BASE_URL_2",
|
|
18813
|
-
hidden: true
|
|
18814
|
-
}),
|
|
18815
|
-
to: Flags.string({
|
|
18816
|
-
description: "Recipient address (e.g. alice@example.com).",
|
|
18817
|
-
required: true
|
|
18818
|
-
}),
|
|
18819
|
-
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
18820
|
-
subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
|
|
18821
|
-
body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
|
|
18822
|
-
"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." }),
|
|
18823
|
-
"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." }),
|
|
18824
|
-
html: Flags.string({ description: "HTML message body. Either --body or --html (or both) is required." }),
|
|
18825
|
-
"html-file": Flags.string({ description: "Read the HTML message body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
18826
|
-
"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." }),
|
|
18827
|
-
attachment: Flags.string({
|
|
18828
|
-
description: "Attach a file to the email. Repeatable. Sends file bytes as a MIME attachment; use --body-file only for message body text.",
|
|
18829
|
-
multiple: true
|
|
18830
|
-
}),
|
|
18831
|
-
"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>`." }),
|
|
18832
|
-
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." }),
|
|
18833
|
-
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
18834
|
-
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
18835
|
-
};
|
|
18836
|
-
async run() {
|
|
18837
|
-
const { flags } = await this.parse(SendCommand);
|
|
18838
|
-
const bodies = resolveMessageBodies({
|
|
18839
|
-
body: flags.body,
|
|
18840
|
-
bodyFile: flags["body-file"],
|
|
18841
|
-
bodyStdin: flags["body-stdin"],
|
|
18842
|
-
html: flags.html,
|
|
18843
|
-
htmlFile: flags["html-file"],
|
|
18844
|
-
htmlStdin: flags["html-stdin"]
|
|
18845
|
-
});
|
|
18846
|
-
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
18847
|
-
const attachments = readAttachmentFiles(flags.attachment);
|
|
18848
|
-
await runWithTiming(flags.time, async () => {
|
|
18849
|
-
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
18850
|
-
apiKey: flags["api-key"],
|
|
18851
|
-
apiBaseUrl1: flags["api-base-url-1"],
|
|
18852
|
-
apiBaseUrl2: flags["api-base-url-2"],
|
|
18853
|
-
configDir: this.config.configDir
|
|
18854
|
-
});
|
|
18855
|
-
const authFailureContext = {
|
|
18856
|
-
auth,
|
|
18857
|
-
baseUrlOverridden,
|
|
18858
|
-
configDir: this.config.configDir
|
|
18859
|
-
};
|
|
18860
|
-
const from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
18861
|
-
const subject = flags.subject ?? (bodies.body ? deriveSubject(bodies.body) : "Message");
|
|
18862
|
-
const result = await sendEmail({
|
|
18863
|
-
body: {
|
|
18864
|
-
from,
|
|
18865
|
-
to: flags.to,
|
|
18866
|
-
subject,
|
|
18867
|
-
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
18868
|
-
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
18869
|
-
...attachments !== void 0 ? { attachments } : {},
|
|
18870
|
-
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
|
|
18871
|
-
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
18872
|
-
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
18873
|
-
},
|
|
18874
|
-
client: apiClient._sendClient,
|
|
18875
|
-
responseStyle: "fields"
|
|
18876
|
-
});
|
|
18877
|
-
if (result.error) {
|
|
18878
|
-
const errorPayload = extractErrorPayload(result.error);
|
|
18879
|
-
writeErrorWithHints(errorPayload);
|
|
18880
|
-
surfaceUnauthorizedHint({
|
|
18881
|
-
...authFailureContext,
|
|
18882
|
-
payload: errorPayload
|
|
18883
|
-
});
|
|
18884
|
-
process.exitCode = 1;
|
|
18885
|
-
return;
|
|
18886
|
-
}
|
|
18887
|
-
const envelope = result.data;
|
|
18888
|
-
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
18889
|
-
process.stderr.write(chunk);
|
|
18890
|
-
} });
|
|
18891
|
-
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
18892
|
-
});
|
|
18893
|
-
}
|
|
18894
|
-
};
|
|
18895
|
-
//#endregion
|
|
18896
|
-
//#region src/oclif/commands/signup.ts
|
|
18897
|
-
const INVALID_VERIFICATION_CODE = "invalid_verification_code";
|
|
18898
|
-
const EXPIRED_TOKEN = "expired_token";
|
|
18899
|
-
const INVALID_SIGNUP_TOKEN = "invalid_signup_token";
|
|
18900
|
-
const SLOW_DOWN = "slow_down";
|
|
18901
|
-
const PENDING_SIGNUP_FILE = "signup.json";
|
|
18902
|
-
const DEFAULT_SIGNUP_COMMAND_COPY = {
|
|
18903
|
-
actionNoun: "signup",
|
|
18904
|
-
actionGerund: "creating a new account",
|
|
18905
|
-
confirmCommand: (email) => `signup confirm ${email} <code>`,
|
|
18906
|
-
resendCommand: (email) => `signup resend ${email}`,
|
|
18907
|
-
startCommand: (email) => `signup ${email}`
|
|
18908
|
-
};
|
|
18909
|
-
function cliError$1(message) {
|
|
18910
|
-
return new Errors.CLIError(message, { exit: 1 });
|
|
18911
|
-
}
|
|
18912
|
-
function unwrapData(value) {
|
|
18913
|
-
return value?.data ?? null;
|
|
18914
|
-
}
|
|
18915
|
-
function isRecord(value) {
|
|
18916
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18917
|
-
}
|
|
18918
|
-
function normalizeEmail(email) {
|
|
18919
|
-
return email.trim().toLowerCase();
|
|
18920
|
-
}
|
|
18921
|
-
function pendingSignupFromJson(value) {
|
|
18922
|
-
if (!isRecord(value)) return null;
|
|
18923
|
-
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;
|
|
18924
|
-
return {
|
|
18925
|
-
api_base_url_1: value.api_base_url_1,
|
|
18926
|
-
created_at: value.created_at,
|
|
18927
|
-
email: value.email,
|
|
18928
|
-
expires_at: value.expires_at,
|
|
18929
|
-
expires_in: value.expires_in,
|
|
18930
|
-
resend_after: value.resend_after,
|
|
18931
|
-
signup_token: value.signup_token,
|
|
18932
|
-
verification_code_length: value.verification_code_length
|
|
18933
|
-
};
|
|
18934
|
-
}
|
|
18935
|
-
function pendingSignupPath(configDir) {
|
|
18936
|
-
return join(configDir, PENDING_SIGNUP_FILE);
|
|
18937
|
-
}
|
|
18938
|
-
function deletePendingAgentSignup(configDir) {
|
|
18939
|
-
rmSync(pendingSignupPath(configDir), { force: true });
|
|
18940
|
-
}
|
|
18941
|
-
function pendingSignupFromStart(start, apiBaseUrl1) {
|
|
18942
|
-
return {
|
|
18943
|
-
...start,
|
|
18944
|
-
api_base_url_1: apiBaseUrl1,
|
|
18945
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18946
|
-
expires_at: new Date(Date.now() + start.expires_in * 1e3).toISOString()
|
|
18947
|
-
};
|
|
18948
|
-
}
|
|
18949
|
-
function savePendingAgentSignup(configDir, start, apiBaseUrl1) {
|
|
18950
|
-
mkdirSync(configDir, {
|
|
18951
|
-
mode: 448,
|
|
18952
|
-
recursive: true
|
|
18953
|
-
});
|
|
18954
|
-
const pending = pendingSignupFromStart(start, apiBaseUrl1);
|
|
18955
|
-
const path = pendingSignupPath(configDir);
|
|
18956
|
-
const tempPath = join(configDir, `${PENDING_SIGNUP_FILE}.${process$1.pid}.${randomUUID()}.tmp`);
|
|
18957
|
-
try {
|
|
18958
|
-
writeFileSync(tempPath, `${JSON.stringify(pending, null, 2)}\n`, { mode: 384 });
|
|
18959
|
-
chmodSync(tempPath, 384);
|
|
18960
|
-
renameSync(tempPath, path);
|
|
18961
|
-
chmodSync(path, 384);
|
|
18962
|
-
return pending;
|
|
18963
|
-
} catch (error) {
|
|
18964
|
-
rmSync(tempPath, { force: true });
|
|
18965
|
-
throw error;
|
|
18966
|
-
}
|
|
18967
|
-
}
|
|
18968
|
-
function loadPendingAgentSignup(configDir, apiBaseUrl1) {
|
|
18969
|
-
const path = pendingSignupPath(configDir);
|
|
18970
|
-
let contents;
|
|
18971
|
-
try {
|
|
18972
|
-
contents = readFileSync(path, "utf8");
|
|
18973
|
-
} catch (error) {
|
|
18974
|
-
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
18775
|
+
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
18975
18776
|
throw error;
|
|
18976
18777
|
}
|
|
18977
18778
|
let pending;
|
|
@@ -18997,8 +18798,8 @@ function loadPendingAgentSignup(configDir, apiBaseUrl1) {
|
|
|
18997
18798
|
function requirePendingSignupForEmail(params) {
|
|
18998
18799
|
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
18999
18800
|
const pending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
|
|
19000
|
-
if (!pending) throw cliError$
|
|
19001
|
-
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.`);
|
|
19002
18803
|
return pending;
|
|
19003
18804
|
}
|
|
19004
18805
|
function retryAfterSeconds(result) {
|
|
@@ -19039,7 +18840,7 @@ async function confirmTerms() {
|
|
|
19039
18840
|
process$1.stderr.write(" https://primitive.dev/terms\n");
|
|
19040
18841
|
process$1.stderr.write(" https://primitive.dev/privacy\n");
|
|
19041
18842
|
const answer = (await promptRequired("Type 'yes' to continue: ")).toLowerCase();
|
|
19042
|
-
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.");
|
|
19043
18844
|
}
|
|
19044
18845
|
async function checkExistingCredentials(params) {
|
|
19045
18846
|
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
@@ -19070,9 +18871,9 @@ async function checkExistingCredentials(params) {
|
|
|
19070
18871
|
}
|
|
19071
18872
|
if (existingStatus.status === "blocked") {
|
|
19072
18873
|
writeErrorWithHints(existingStatus.payload);
|
|
19073
|
-
throw cliError$
|
|
18874
|
+
throw cliError$2(existingStatus.message);
|
|
19074
18875
|
}
|
|
19075
|
-
throw cliError$
|
|
18876
|
+
throw cliError$2(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before ${copy.actionGerund}.`);
|
|
19076
18877
|
}
|
|
19077
18878
|
function saveSignupCredentials(params) {
|
|
19078
18879
|
saveCliCredentials(params.configDir, {
|
|
@@ -19106,7 +18907,7 @@ async function startSignup(params) {
|
|
|
19106
18907
|
started: false
|
|
19107
18908
|
};
|
|
19108
18909
|
}
|
|
19109
|
-
throw cliError$
|
|
18910
|
+
throw cliError$2(`Pending ${copy.actionNoun} is for ${existingPending.email}. Run \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
|
|
19110
18911
|
}
|
|
19111
18912
|
if (params.flags.force) deletePendingAgentSignup(params.configDir);
|
|
19112
18913
|
const promptRequiredFn = params.deps.promptRequired ?? promptRequired;
|
|
@@ -19126,10 +18927,10 @@ async function startSignup(params) {
|
|
|
19126
18927
|
});
|
|
19127
18928
|
if (started.error) {
|
|
19128
18929
|
writeErrorWithHints(extractErrorPayload(started.error));
|
|
19129
|
-
throw cliError$
|
|
18930
|
+
throw cliError$2("Could not start Primitive agent signup.");
|
|
19130
18931
|
}
|
|
19131
|
-
const startResult = unwrapData(started.data);
|
|
19132
|
-
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.");
|
|
19133
18934
|
return {
|
|
19134
18935
|
pending: savePendingAgentSignup(params.configDir, startResult, params.apiBaseUrl1),
|
|
19135
18936
|
started: true
|
|
@@ -19142,7 +18943,7 @@ async function resendVerificationCode(params) {
|
|
|
19142
18943
|
responseStyle: "fields"
|
|
19143
18944
|
});
|
|
19144
18945
|
if (resent.data) {
|
|
19145
|
-
const resend = unwrapData(resent.data);
|
|
18946
|
+
const resend = unwrapData$1(resent.data);
|
|
19146
18947
|
const next = resend ? {
|
|
19147
18948
|
email: resend.email,
|
|
19148
18949
|
expires_in: resend.expires_in,
|
|
@@ -19167,7 +18968,7 @@ async function resendVerificationCode(params) {
|
|
|
19167
18968
|
}
|
|
19168
18969
|
if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(params.configDir);
|
|
19169
18970
|
writeErrorWithHints(payload);
|
|
19170
|
-
throw cliError$
|
|
18971
|
+
throw cliError$2("Could not resend Primitive agent signup verification email.");
|
|
19171
18972
|
}
|
|
19172
18973
|
async function runSignupStartWithCredentialLock(params) {
|
|
19173
18974
|
const { configDir, flags } = params;
|
|
@@ -19227,8 +19028,8 @@ async function runSignupConfirmWithCredentialLock(params) {
|
|
|
19227
19028
|
responseStyle: "fields"
|
|
19228
19029
|
});
|
|
19229
19030
|
if (verified.data) {
|
|
19230
|
-
const signup = unwrapData(verified.data);
|
|
19231
|
-
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.");
|
|
19232
19033
|
saveSignupCredentials({
|
|
19233
19034
|
apiBaseUrl1,
|
|
19234
19035
|
configDir,
|
|
@@ -19242,10 +19043,10 @@ async function runSignupConfirmWithCredentialLock(params) {
|
|
|
19242
19043
|
}
|
|
19243
19044
|
const payload = extractErrorPayload(verified.error);
|
|
19244
19045
|
const code = extractErrorCode(payload);
|
|
19245
|
-
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)}.`);
|
|
19246
19047
|
if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(configDir);
|
|
19247
19048
|
writeErrorWithHints(payload);
|
|
19248
|
-
throw cliError$
|
|
19049
|
+
throw cliError$2("Primitive agent signup failed while verifying the account.");
|
|
19249
19050
|
}
|
|
19250
19051
|
async function runSignupResendWithCredentialLock(params) {
|
|
19251
19052
|
const deps = params.deps ?? {};
|
|
@@ -19332,40 +19133,268 @@ async function runSignupInteractiveWithCredentialLock(params) {
|
|
|
19332
19133
|
}
|
|
19333
19134
|
}
|
|
19334
19135
|
}
|
|
19335
|
-
function commonStartFlags() {
|
|
19336
|
-
return {
|
|
19337
|
-
"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 = {
|
|
19338
19382
|
"api-base-url-1": Flags.string({
|
|
19339
19383
|
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19340
19384
|
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19341
19385
|
hidden: true
|
|
19342
19386
|
}),
|
|
19343
|
-
"device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
|
|
19344
19387
|
force: Flags.boolean({
|
|
19345
19388
|
char: "f",
|
|
19346
|
-
description: "
|
|
19347
|
-
}),
|
|
19348
|
-
"signup-code": Flags.string({
|
|
19349
|
-
description: "Signup code required to create an account",
|
|
19350
|
-
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"
|
|
19351
19390
|
})
|
|
19352
19391
|
};
|
|
19353
|
-
}
|
|
19354
|
-
var SignupCommand = class SignupCommand extends Command {
|
|
19355
|
-
static args = { email: Args.string({
|
|
19356
|
-
description: "Email address to sign up",
|
|
19357
|
-
required: false
|
|
19358
|
-
}) };
|
|
19359
|
-
static description = "Start a Primitive account signup, send an email verification code, and save a pending signup token locally.";
|
|
19360
|
-
static summary = "Start account signup";
|
|
19361
|
-
static examples = [
|
|
19362
|
-
"<%= config.bin %> signup user@example.com",
|
|
19363
|
-
"<%= config.bin %> signup user@example.com --signup-code invite-code --accept-terms",
|
|
19364
|
-
"<%= config.bin %> signup confirm user@example.com 123456"
|
|
19365
|
-
];
|
|
19366
|
-
static flags = commonStartFlags();
|
|
19367
19392
|
async run() {
|
|
19368
|
-
const {
|
|
19393
|
+
const { flags } = await this.parse(LogoutCommand);
|
|
19394
|
+
if (flags.force) {
|
|
19395
|
+
runForceLogout({ configDir: this.config.configDir });
|
|
19396
|
+
return;
|
|
19397
|
+
}
|
|
19369
19398
|
let releaseCredentialsLock;
|
|
19370
19399
|
try {
|
|
19371
19400
|
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
@@ -19373,9 +19402,8 @@ var SignupCommand = class SignupCommand extends Command {
|
|
|
19373
19402
|
throw cliError$1(error instanceof Error ? error.message : String(error));
|
|
19374
19403
|
}
|
|
19375
19404
|
try {
|
|
19376
|
-
await
|
|
19405
|
+
await runLogoutWithCredentialLock({
|
|
19377
19406
|
configDir: this.config.configDir,
|
|
19378
|
-
email: args.email,
|
|
19379
19407
|
flags
|
|
19380
19408
|
});
|
|
19381
19409
|
} finally {
|
|
@@ -19383,105 +19411,344 @@ var SignupCommand = class SignupCommand extends Command {
|
|
|
19383
19411
|
}
|
|
19384
19412
|
}
|
|
19385
19413
|
};
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
|
|
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"
|
|
19391
19531
|
}),
|
|
19392
|
-
|
|
19393
|
-
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.",
|
|
19394
19544
|
required: true
|
|
19395
|
-
})
|
|
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 })
|
|
19396
19555
|
};
|
|
19397
|
-
|
|
19398
|
-
|
|
19399
|
-
|
|
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
|
+
];
|
|
19400
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
|
+
}),
|
|
19401
19664
|
"api-base-url-1": Flags.string({
|
|
19402
19665
|
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19403
19666
|
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19404
19667
|
hidden: true
|
|
19405
19668
|
}),
|
|
19406
|
-
|
|
19407
|
-
|
|
19408
|
-
|
|
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
|
|
19409
19673
|
}),
|
|
19410
|
-
|
|
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 })
|
|
19411
19694
|
};
|
|
19412
19695
|
async run() {
|
|
19413
|
-
const {
|
|
19414
|
-
|
|
19415
|
-
|
|
19416
|
-
|
|
19417
|
-
|
|
19418
|
-
|
|
19419
|
-
|
|
19420
|
-
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
|
|
19424
|
-
|
|
19425
|
-
|
|
19426
|
-
|
|
19427
|
-
|
|
19428
|
-
|
|
19429
|
-
|
|
19430
|
-
}
|
|
19431
|
-
};
|
|
19432
|
-
var SignupResendCommand = class SignupResendCommand extends Command {
|
|
19433
|
-
static args = { email: Args.string({
|
|
19434
|
-
description: "Email address used to start signup",
|
|
19435
|
-
required: true
|
|
19436
|
-
}) };
|
|
19437
|
-
static description = "Resend the verification code for a pending signup.";
|
|
19438
|
-
static summary = "Resend signup verification code";
|
|
19439
|
-
static examples = ["<%= config.bin %> signup resend user@example.com"];
|
|
19440
|
-
static flags = { "api-base-url-1": Flags.string({
|
|
19441
|
-
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19442
|
-
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19443
|
-
hidden: true
|
|
19444
|
-
}) };
|
|
19445
|
-
async run() {
|
|
19446
|
-
const { args, flags } = await this.parse(SignupResendCommand);
|
|
19447
|
-
let releaseCredentialsLock;
|
|
19448
|
-
try {
|
|
19449
|
-
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
19450
|
-
} catch (error) {
|
|
19451
|
-
throw cliError$1(error instanceof Error ? error.message : String(error));
|
|
19452
|
-
}
|
|
19453
|
-
try {
|
|
19454
|
-
await runSignupResendWithCredentialLock({
|
|
19455
|
-
configDir: this.config.configDir,
|
|
19456
|
-
email: args.email,
|
|
19457
|
-
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
|
|
19458
19713
|
});
|
|
19459
|
-
|
|
19460
|
-
|
|
19461
|
-
|
|
19462
|
-
|
|
19463
|
-
};
|
|
19464
|
-
|
|
19465
|
-
|
|
19466
|
-
|
|
19467
|
-
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
|
|
19475
|
-
|
|
19476
|
-
|
|
19477
|
-
|
|
19478
|
-
|
|
19479
|
-
|
|
19480
|
-
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"
|
|
19481
19735
|
});
|
|
19482
|
-
|
|
19483
|
-
|
|
19484
|
-
|
|
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
|
+
});
|
|
19485
19752
|
}
|
|
19486
19753
|
};
|
|
19487
19754
|
//#endregion
|
|
@@ -19496,6 +19763,34 @@ const SIGNIN_OTP_COPY = {
|
|
|
19496
19763
|
resendCommand: (email) => `signin otp resend ${email}`,
|
|
19497
19764
|
startCommand: (email) => `signin otp ${email}`
|
|
19498
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
|
+
};
|
|
19499
19794
|
function acquireCredentialsLock(configDir) {
|
|
19500
19795
|
try {
|
|
19501
19796
|
return acquireCliCredentialsLock(configDir);
|
|
@@ -19514,31 +19809,91 @@ function commonOtpStartFlags() {
|
|
|
19514
19809
|
"device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
|
|
19515
19810
|
force: Flags.boolean({
|
|
19516
19811
|
char: "f",
|
|
19517
|
-
description: "Replace saved credentials or pending
|
|
19812
|
+
description: "Replace saved credentials or pending email-code auth state when needed"
|
|
19518
19813
|
}),
|
|
19519
19814
|
"signup-code": Flags.string({
|
|
19520
|
-
description: "Signup code required to start
|
|
19815
|
+
description: "Signup code required to start email-code sign-in",
|
|
19521
19816
|
env: "PRIMITIVE_SIGNUP_CODE"
|
|
19522
19817
|
})
|
|
19523
19818
|
};
|
|
19524
19819
|
}
|
|
19525
|
-
var SigninCommand = class extends LoginCommand {
|
|
19526
|
-
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.
|
|
19527
19826
|
|
|
19528
|
-
|
|
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>\`.`;
|
|
19529
19828
|
static summary = "Sign in to an existing account";
|
|
19530
19829
|
static examples = [
|
|
19531
19830
|
"<%= config.bin %> signin",
|
|
19532
19831
|
"<%= config.bin %> signin browser",
|
|
19533
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",
|
|
19534
19835
|
"<%= config.bin %> signin otp user@example.com --signup-code invite-code --accept-terms",
|
|
19535
19836
|
"<%= config.bin %> signin otp confirm user@example.com 123456"
|
|
19536
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
|
+
}
|
|
19537
19866
|
retryCommand() {
|
|
19538
19867
|
return "signin";
|
|
19539
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
|
+
}
|
|
19540
19895
|
};
|
|
19541
|
-
var SigninBrowserCommand = class extends LoginCommand {
|
|
19896
|
+
var SigninBrowserCommand = class extends LoginCommand$1 {
|
|
19542
19897
|
static description = "Sign in to an existing Primitive account by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
|
|
19543
19898
|
static summary = "Sign in with browser approval";
|
|
19544
19899
|
static examples = [
|
|
@@ -19551,7 +19906,20 @@ var SigninBrowserCommand = class extends LoginCommand {
|
|
|
19551
19906
|
return "signin browser";
|
|
19552
19907
|
}
|
|
19553
19908
|
};
|
|
19554
|
-
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 {
|
|
19555
19923
|
static args = { email: Args.string({
|
|
19556
19924
|
description: "Email address to sign in with",
|
|
19557
19925
|
required: false
|
|
@@ -19561,12 +19929,13 @@ var SigninOtpCommand = class SigninOtpCommand extends Command {
|
|
|
19561
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"];
|
|
19562
19930
|
static flags = commonOtpStartFlags();
|
|
19563
19931
|
async run() {
|
|
19564
|
-
const
|
|
19932
|
+
const commandClass = this.constructor;
|
|
19933
|
+
const { args, flags } = await this.parse(commandClass);
|
|
19565
19934
|
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
19566
19935
|
try {
|
|
19567
19936
|
await runSignupStartWithCredentialLock({
|
|
19568
19937
|
configDir: this.config.configDir,
|
|
19569
|
-
copy:
|
|
19938
|
+
copy: this.emailCodeCopy(),
|
|
19570
19939
|
email: args.email,
|
|
19571
19940
|
flags
|
|
19572
19941
|
});
|
|
@@ -19574,8 +19943,31 @@ var SigninOtpCommand = class SigninOtpCommand extends Command {
|
|
|
19574
19943
|
releaseCredentialsLock();
|
|
19575
19944
|
}
|
|
19576
19945
|
}
|
|
19946
|
+
emailCodeCopy() {
|
|
19947
|
+
return SIGNIN_OTP_COPY;
|
|
19948
|
+
}
|
|
19577
19949
|
};
|
|
19578
|
-
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 {
|
|
19579
19971
|
static args = {
|
|
19580
19972
|
email: Args.string({
|
|
19581
19973
|
description: "Email address used to start OTP sign-in",
|
|
@@ -19602,13 +19994,14 @@ var SigninOtpConfirmCommand = class SigninOtpConfirmCommand extends Command {
|
|
|
19602
19994
|
"org-id": Flags.string({ description: "Workspace id to target when the email belongs to multiple workspaces" })
|
|
19603
19995
|
};
|
|
19604
19996
|
async run() {
|
|
19605
|
-
const
|
|
19997
|
+
const commandClass = this.constructor;
|
|
19998
|
+
const { args, flags } = await this.parse(commandClass);
|
|
19606
19999
|
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
19607
20000
|
try {
|
|
19608
20001
|
await runSignupConfirmWithCredentialLock({
|
|
19609
20002
|
code: args.code,
|
|
19610
20003
|
configDir: this.config.configDir,
|
|
19611
|
-
copy:
|
|
20004
|
+
copy: this.emailCodeCopy(),
|
|
19612
20005
|
email: args.email,
|
|
19613
20006
|
flags
|
|
19614
20007
|
});
|
|
@@ -19616,8 +20009,51 @@ var SigninOtpConfirmCommand = class SigninOtpConfirmCommand extends Command {
|
|
|
19616
20009
|
releaseCredentialsLock();
|
|
19617
20010
|
}
|
|
19618
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
|
+
}
|
|
19619
20025
|
};
|
|
19620
|
-
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 {
|
|
19621
20057
|
static args = { email: Args.string({
|
|
19622
20058
|
description: "Email address used to start OTP sign-in",
|
|
19623
20059
|
required: true
|
|
@@ -19631,12 +20067,13 @@ var SigninOtpResendCommand = class SigninOtpResendCommand extends Command {
|
|
|
19631
20067
|
hidden: true
|
|
19632
20068
|
}) };
|
|
19633
20069
|
async run() {
|
|
19634
|
-
const
|
|
20070
|
+
const commandClass = this.constructor;
|
|
20071
|
+
const { args, flags } = await this.parse(commandClass);
|
|
19635
20072
|
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
19636
20073
|
try {
|
|
19637
20074
|
await runSignupResendWithCredentialLock({
|
|
19638
20075
|
configDir: this.config.configDir,
|
|
19639
|
-
copy:
|
|
20076
|
+
copy: this.emailCodeCopy(),
|
|
19640
20077
|
email: args.email,
|
|
19641
20078
|
flags
|
|
19642
20079
|
});
|
|
@@ -19644,6 +20081,49 @@ var SigninOtpResendCommand = class SigninOtpResendCommand extends Command {
|
|
|
19644
20081
|
releaseCredentialsLock();
|
|
19645
20082
|
}
|
|
19646
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
|
+
}
|
|
19647
20127
|
};
|
|
19648
20128
|
//#endregion
|
|
19649
20129
|
//#region src/oclif/commands/whoami.ts
|
|
@@ -19990,11 +20470,22 @@ const COMMANDS = {
|
|
|
19990
20470
|
reply: ReplyCommand,
|
|
19991
20471
|
chat: ChatCommand,
|
|
19992
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,
|
|
19993
20482
|
signin: SigninCommand,
|
|
19994
20483
|
"signin:browser": SigninBrowserCommand,
|
|
20484
|
+
"signin:confirm": SigninConfirmCommand,
|
|
19995
20485
|
"signin:otp": SigninOtpCommand,
|
|
19996
20486
|
"signin:otp:confirm": SigninOtpConfirmCommand,
|
|
19997
20487
|
"signin:otp:resend": SigninOtpResendCommand,
|
|
20488
|
+
"signin:resend": SigninResendCommand,
|
|
19998
20489
|
signup: SignupCommand,
|
|
19999
20490
|
"signup:confirm": SignupConfirmCommand,
|
|
20000
20491
|
"signup:interactive": SignupInteractiveCommand,
|