@primitivedotdev/cli 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/oclif/index.js +1593 -853
- package/man/primitive.1 +20 -0
- package/package.json +8 -2
package/dist/oclif/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Args, Command, Errors, Flags } from "@oclif/core";
|
|
2
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
4
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
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) => {
|
|
@@ -6769,16 +6769,21 @@ const openapiDocument = {
|
|
|
6769
6769
|
"type": "string",
|
|
6770
6770
|
"minLength": 1,
|
|
6771
6771
|
"maxLength": 1048576,
|
|
6772
|
-
"description": "
|
|
6772
|
+
"description": "Pre-built handler as a single ESM module. Up to 1 MiB UTF-8.\nMust export a default `{ async fetch(req, env, ctx) { ... } }`\nobject. Provide either `code` or `files`, not both.\n"
|
|
6773
6773
|
},
|
|
6774
6774
|
"sourceMap": {
|
|
6775
6775
|
"type": "string",
|
|
6776
6776
|
"minLength": 1,
|
|
6777
6777
|
"maxLength": 5242880,
|
|
6778
|
-
"description": "Optional source map for the bundle. Up to 5 MiB UTF-8.\nStored with the deployment attempt and sent to the runtime\nto symbolicate stack traces in the function's logs
|
|
6778
|
+
"description": "Optional source map for the bundle. Up to 5 MiB UTF-8.\nStored with the deployment attempt and sent to the runtime\nto symbolicate stack traces in the function's logs. Only\nvalid with `code`.\n"
|
|
6779
|
+
},
|
|
6780
|
+
"files": {
|
|
6781
|
+
"type": "object",
|
|
6782
|
+
"additionalProperties": { "type": "string" },
|
|
6783
|
+
"description": "Source files for a managed build, as a map of path to file\ncontents (for example {\"package.json\": \"...\",\n\"src/index.ts\": \"...\"}). Provide this INSTEAD of `code` to\nhave the server install dependencies and bundle the source\nfor the Workers runtime before deploying. Include a\npackage.json (its `dependencies` are installed). Provide\neither `code` or `files`, not both.\n"
|
|
6779
6784
|
}
|
|
6780
6785
|
},
|
|
6781
|
-
"required": ["name"
|
|
6786
|
+
"required": ["name"]
|
|
6782
6787
|
},
|
|
6783
6788
|
"CreateFunctionResult": {
|
|
6784
6789
|
"type": "object",
|
|
@@ -6805,15 +6810,20 @@ const openapiDocument = {
|
|
|
6805
6810
|
"type": "string",
|
|
6806
6811
|
"minLength": 1,
|
|
6807
6812
|
"maxLength": 1048576,
|
|
6808
|
-
"description": "New
|
|
6813
|
+
"description": "New pre-built handler. Same rules as CreateFunctionInput.code. Provide either `code` or `files`, not both."
|
|
6809
6814
|
},
|
|
6810
6815
|
"sourceMap": {
|
|
6811
6816
|
"type": "string",
|
|
6812
6817
|
"minLength": 1,
|
|
6813
6818
|
"maxLength": 5242880
|
|
6819
|
+
},
|
|
6820
|
+
"files": {
|
|
6821
|
+
"type": "object",
|
|
6822
|
+
"additionalProperties": { "type": "string" },
|
|
6823
|
+
"description": "Source files for a managed build, as a map of path to file\ncontents. Provide this INSTEAD of `code` to rebuild and\nredeploy from source. Same rules as CreateFunctionInput.files.\n"
|
|
6814
6824
|
}
|
|
6815
6825
|
},
|
|
6816
|
-
"required": [
|
|
6826
|
+
"required": []
|
|
6817
6827
|
},
|
|
6818
6828
|
"TestInvocationResult": {
|
|
6819
6829
|
"type": "object",
|
|
@@ -10512,16 +10522,21 @@ const operationManifest = [
|
|
|
10512
10522
|
"type": "string",
|
|
10513
10523
|
"minLength": 1,
|
|
10514
10524
|
"maxLength": 1048576,
|
|
10515
|
-
"description": "
|
|
10525
|
+
"description": "Pre-built handler as a single ESM module. Up to 1 MiB UTF-8.\nMust export a default `{ async fetch(req, env, ctx) { ... } }`\nobject. Provide either `code` or `files`, not both.\n"
|
|
10516
10526
|
},
|
|
10517
10527
|
"sourceMap": {
|
|
10518
10528
|
"type": "string",
|
|
10519
10529
|
"minLength": 1,
|
|
10520
10530
|
"maxLength": 5242880,
|
|
10521
|
-
"description": "Optional source map for the bundle. Up to 5 MiB UTF-8.\nStored with the deployment attempt and sent to the runtime\nto symbolicate stack traces in the function's logs
|
|
10531
|
+
"description": "Optional source map for the bundle. Up to 5 MiB UTF-8.\nStored with the deployment attempt and sent to the runtime\nto symbolicate stack traces in the function's logs. Only\nvalid with `code`.\n"
|
|
10532
|
+
},
|
|
10533
|
+
"files": {
|
|
10534
|
+
"type": "object",
|
|
10535
|
+
"additionalProperties": { "type": "string" },
|
|
10536
|
+
"description": "Source files for a managed build, as a map of path to file\ncontents (for example {\"package.json\": \"...\",\n\"src/index.ts\": \"...\"}). Provide this INSTEAD of `code` to\nhave the server install dependencies and bundle the source\nfor the Workers runtime before deploying. Include a\npackage.json (its `dependencies` are installed). Provide\neither `code` or `files`, not both.\n"
|
|
10522
10537
|
}
|
|
10523
10538
|
},
|
|
10524
|
-
"required": ["name"
|
|
10539
|
+
"required": ["name"]
|
|
10525
10540
|
},
|
|
10526
10541
|
"responseSchema": {
|
|
10527
10542
|
"type": "object",
|
|
@@ -11574,15 +11589,20 @@ const operationManifest = [
|
|
|
11574
11589
|
"type": "string",
|
|
11575
11590
|
"minLength": 1,
|
|
11576
11591
|
"maxLength": 1048576,
|
|
11577
|
-
"description": "New
|
|
11592
|
+
"description": "New pre-built handler. Same rules as CreateFunctionInput.code. Provide either `code` or `files`, not both."
|
|
11578
11593
|
},
|
|
11579
11594
|
"sourceMap": {
|
|
11580
11595
|
"type": "string",
|
|
11581
11596
|
"minLength": 1,
|
|
11582
11597
|
"maxLength": 5242880
|
|
11598
|
+
},
|
|
11599
|
+
"files": {
|
|
11600
|
+
"type": "object",
|
|
11601
|
+
"additionalProperties": { "type": "string" },
|
|
11602
|
+
"description": "Source files for a managed build, as a map of path to file\ncontents. Provide this INSTEAD of `code` to rebuild and\nredeploy from source. Same rules as CreateFunctionInput.files.\n"
|
|
11583
11603
|
}
|
|
11584
11604
|
},
|
|
11585
|
-
"required": [
|
|
11605
|
+
"required": []
|
|
11586
11606
|
},
|
|
11587
11607
|
"responseSchema": {
|
|
11588
11608
|
"type": "object",
|
|
@@ -13133,8 +13153,14 @@ var PrimitiveApiClient = class {
|
|
|
13133
13153
|
//#region src/oclif/auth.ts
|
|
13134
13154
|
const CREDENTIALS_FILE = "credentials.json";
|
|
13135
13155
|
const CREDENTIALS_LOCK_DIR = "credentials.lock";
|
|
13156
|
+
const CREDENTIALS_LOCK_OWNER_FILE = "owner.json";
|
|
13136
13157
|
const CREDENTIALS_LOCK_STALE_MS = 1800 * 1e3;
|
|
13137
13158
|
const MALFORMED_CREDENTIALS_HINT = "Run `primitive logout` and then `primitive signin`.";
|
|
13159
|
+
const CREDENTIALS_LOCK_CLEANUP_SIGNALS = [
|
|
13160
|
+
"SIGINT",
|
|
13161
|
+
"SIGTERM",
|
|
13162
|
+
"SIGHUP"
|
|
13163
|
+
];
|
|
13138
13164
|
function isRecord$2(value) {
|
|
13139
13165
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13140
13166
|
}
|
|
@@ -13181,6 +13207,9 @@ function parseCredentials(raw) {
|
|
|
13181
13207
|
function credentialsPath(configDir) {
|
|
13182
13208
|
return join(configDir, CREDENTIALS_FILE);
|
|
13183
13209
|
}
|
|
13210
|
+
function credentialsLockPath(configDir) {
|
|
13211
|
+
return join(configDir, CREDENTIALS_LOCK_DIR);
|
|
13212
|
+
}
|
|
13184
13213
|
function normalize(url, fallback) {
|
|
13185
13214
|
const trimmed = url?.trim();
|
|
13186
13215
|
if (!trimmed) return fallback;
|
|
@@ -13239,6 +13268,12 @@ function saveCliCredentials(configDir, credentials) {
|
|
|
13239
13268
|
function deleteCliCredentials(configDir) {
|
|
13240
13269
|
rmSync(credentialsPath(configDir), { force: true });
|
|
13241
13270
|
}
|
|
13271
|
+
function deleteCliCredentialsLock(configDir) {
|
|
13272
|
+
rmSync(credentialsLockPath(configDir), {
|
|
13273
|
+
force: true,
|
|
13274
|
+
recursive: true
|
|
13275
|
+
});
|
|
13276
|
+
}
|
|
13242
13277
|
function errorCode(error) {
|
|
13243
13278
|
return error && typeof error === "object" ? error.code : void 0;
|
|
13244
13279
|
}
|
|
@@ -13256,12 +13291,85 @@ function removeStaleCliCredentialsLock(lockPath, staleMs, now) {
|
|
|
13256
13291
|
});
|
|
13257
13292
|
return true;
|
|
13258
13293
|
}
|
|
13294
|
+
function readCliCredentialsLockOwner(lockPath) {
|
|
13295
|
+
let raw;
|
|
13296
|
+
try {
|
|
13297
|
+
raw = readFileSync(join(lockPath, CREDENTIALS_LOCK_OWNER_FILE), "utf8");
|
|
13298
|
+
} catch (error) {
|
|
13299
|
+
if (errorCode(error) === "ENOENT") return null;
|
|
13300
|
+
throw error;
|
|
13301
|
+
}
|
|
13302
|
+
try {
|
|
13303
|
+
const pid = JSON.parse(raw)?.pid;
|
|
13304
|
+
return Number.isInteger(pid) && pid > 0 ? { pid } : null;
|
|
13305
|
+
} catch {
|
|
13306
|
+
return null;
|
|
13307
|
+
}
|
|
13308
|
+
}
|
|
13309
|
+
function processIsRunning(pid) {
|
|
13310
|
+
try {
|
|
13311
|
+
process.kill(pid, 0);
|
|
13312
|
+
return true;
|
|
13313
|
+
} catch (error) {
|
|
13314
|
+
if (errorCode(error) === "ESRCH") return false;
|
|
13315
|
+
return true;
|
|
13316
|
+
}
|
|
13317
|
+
}
|
|
13318
|
+
function removeRecoverableCliCredentialsLock(params) {
|
|
13319
|
+
const owner = readCliCredentialsLockOwner(params.lockPath);
|
|
13320
|
+
if (owner && params.isRunning(owner.pid)) return false;
|
|
13321
|
+
if (owner) {
|
|
13322
|
+
rmSync(params.lockPath, {
|
|
13323
|
+
force: true,
|
|
13324
|
+
recursive: true
|
|
13325
|
+
});
|
|
13326
|
+
return true;
|
|
13327
|
+
}
|
|
13328
|
+
return removeStaleCliCredentialsLock(params.lockPath, params.staleMs, params.now);
|
|
13329
|
+
}
|
|
13330
|
+
function writeCliCredentialsLockOwner(lockPath) {
|
|
13331
|
+
const ownerPath = join(lockPath, CREDENTIALS_LOCK_OWNER_FILE);
|
|
13332
|
+
writeFileSync(ownerPath, `${JSON.stringify({
|
|
13333
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13334
|
+
pid: process.pid
|
|
13335
|
+
})}\n`, { mode: 384 });
|
|
13336
|
+
chmodSync(ownerPath, 384);
|
|
13337
|
+
}
|
|
13338
|
+
function installCredentialsLockSignalCleanup(lockPath) {
|
|
13339
|
+
let active = true;
|
|
13340
|
+
const listeners = CREDENTIALS_LOCK_CLEANUP_SIGNALS.map((signal) => {
|
|
13341
|
+
const listener = () => {
|
|
13342
|
+
if (!active) return;
|
|
13343
|
+
active = false;
|
|
13344
|
+
rmSync(lockPath, {
|
|
13345
|
+
force: true,
|
|
13346
|
+
recursive: true
|
|
13347
|
+
});
|
|
13348
|
+
process.exit(signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 129);
|
|
13349
|
+
};
|
|
13350
|
+
process.once(signal, listener);
|
|
13351
|
+
return {
|
|
13352
|
+
listener,
|
|
13353
|
+
signal
|
|
13354
|
+
};
|
|
13355
|
+
});
|
|
13356
|
+
return () => {
|
|
13357
|
+
if (!active) return;
|
|
13358
|
+
active = false;
|
|
13359
|
+
for (const { listener, signal } of listeners) process.removeListener(signal, listener);
|
|
13360
|
+
};
|
|
13361
|
+
}
|
|
13362
|
+
function credentialsLockInProgressMessage(lockPath) {
|
|
13363
|
+
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}.`;
|
|
13364
|
+
}
|
|
13259
13365
|
function acquireCliCredentialsLock(configDir, options = {}) {
|
|
13260
13366
|
mkdirSync(configDir, {
|
|
13261
13367
|
mode: 448,
|
|
13262
13368
|
recursive: true
|
|
13263
13369
|
});
|
|
13264
|
-
const lockPath =
|
|
13370
|
+
const lockPath = credentialsLockPath(configDir);
|
|
13371
|
+
const installSignalHandlers = options.installSignalHandlers ?? true;
|
|
13372
|
+
const isRunning = options.isProcessRunning ?? processIsRunning;
|
|
13265
13373
|
const now = options.now ?? Date.now;
|
|
13266
13374
|
const staleMs = options.staleMs ?? CREDENTIALS_LOCK_STALE_MS;
|
|
13267
13375
|
let acquired = false;
|
|
@@ -13271,14 +13379,30 @@ function acquireCliCredentialsLock(configDir, options = {}) {
|
|
|
13271
13379
|
break;
|
|
13272
13380
|
} catch (error) {
|
|
13273
13381
|
if (errorCode(error) !== "EEXIST") throw error;
|
|
13274
|
-
if (
|
|
13275
|
-
|
|
13382
|
+
if (removeRecoverableCliCredentialsLock({
|
|
13383
|
+
isRunning,
|
|
13384
|
+
lockPath,
|
|
13385
|
+
now,
|
|
13386
|
+
staleMs
|
|
13387
|
+
})) continue;
|
|
13388
|
+
throw new Error(credentialsLockInProgressMessage(lockPath));
|
|
13389
|
+
}
|
|
13390
|
+
if (!acquired) throw new Error(credentialsLockInProgressMessage(lockPath));
|
|
13391
|
+
try {
|
|
13392
|
+
writeCliCredentialsLockOwner(lockPath);
|
|
13393
|
+
} catch (error) {
|
|
13394
|
+
rmSync(lockPath, {
|
|
13395
|
+
force: true,
|
|
13396
|
+
recursive: true
|
|
13397
|
+
});
|
|
13398
|
+
throw error;
|
|
13276
13399
|
}
|
|
13277
|
-
|
|
13400
|
+
const removeSignalCleanup = installSignalHandlers ? installCredentialsLockSignalCleanup(lockPath) : () => void 0;
|
|
13278
13401
|
let released = false;
|
|
13279
13402
|
return () => {
|
|
13280
13403
|
if (released) return;
|
|
13281
13404
|
released = true;
|
|
13405
|
+
removeSignalCleanup();
|
|
13282
13406
|
rmSync(lockPath, {
|
|
13283
13407
|
force: true,
|
|
13284
13408
|
recursive: true
|
|
@@ -13298,7 +13422,7 @@ function resolveCliAuth(params) {
|
|
|
13298
13422
|
const credentials = loadCliCredentials(params.configDir);
|
|
13299
13423
|
if (credentials) return {
|
|
13300
13424
|
apiKey: credentials.access_token,
|
|
13301
|
-
apiBaseUrl1:
|
|
13425
|
+
apiBaseUrl1: credentials.api_base_url_1,
|
|
13302
13426
|
apiBaseUrl2,
|
|
13303
13427
|
credentials,
|
|
13304
13428
|
source: "stored"
|
|
@@ -14051,7 +14175,10 @@ const RESERVED_FLAG_NAMES = new Set([
|
|
|
14051
14175
|
]);
|
|
14052
14176
|
function bodyFieldFlag(field) {
|
|
14053
14177
|
const common = { description: field.description || field.name };
|
|
14054
|
-
if (field.kind === "boolean") return Flags.boolean(
|
|
14178
|
+
if (field.kind === "boolean") return Flags.boolean({
|
|
14179
|
+
...common,
|
|
14180
|
+
allowNo: true
|
|
14181
|
+
});
|
|
14055
14182
|
if (field.kind === "integer") return Flags.integer({
|
|
14056
14183
|
...common,
|
|
14057
14184
|
...numericFlagOptions(field)
|
|
@@ -14084,7 +14211,10 @@ function buildFlags(operation) {
|
|
|
14084
14211
|
}),
|
|
14085
14212
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
14086
14213
|
};
|
|
14087
|
-
if (!operation.binaryResponse)
|
|
14214
|
+
if (!operation.binaryResponse) {
|
|
14215
|
+
flags.json = Flags.boolean({ description: "Accepted for consistency with task-focused commands. Generated API commands already print JSON by default." });
|
|
14216
|
+
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." });
|
|
14217
|
+
}
|
|
14088
14218
|
for (const parameter of [...operation.pathParams, ...operation.queryParams]) flags[flagName(parameter.name)] = flagForParameter(parameter);
|
|
14089
14219
|
const bodyFieldFlagToProperty = /* @__PURE__ */ new Map();
|
|
14090
14220
|
if (operation.hasJsonBody) {
|
|
@@ -14126,11 +14256,21 @@ function collectValues(parameters, flags) {
|
|
|
14126
14256
|
function operationOutputPayload(envelope, includeEnvelope) {
|
|
14127
14257
|
return includeEnvelope ? envelope ?? null : envelope?.data ?? null;
|
|
14128
14258
|
}
|
|
14259
|
+
function isIncompleteDomainVerification(operation, envelope) {
|
|
14260
|
+
if (operation.sdkName !== "verifyDomain") return false;
|
|
14261
|
+
const data = envelope?.data;
|
|
14262
|
+
if (!data || typeof data !== "object") return false;
|
|
14263
|
+
return data.verified === false;
|
|
14264
|
+
}
|
|
14265
|
+
function writeIncompleteDomainVerificationHint() {
|
|
14266
|
+
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");
|
|
14267
|
+
}
|
|
14129
14268
|
const OPERATION_HINTS = {
|
|
14130
14269
|
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
14270
|
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
14271
|
downloadDomainZoneFile: "Tip: prefer `primitive domains zone-file --id <domain-id> --output <domain>.zone` for CLI-friendly file output.",
|
|
14133
14272
|
getInboxStatus: "Tip: prefer `primitive inbox status` for a compact readiness summary and next-step commands.",
|
|
14273
|
+
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
14274
|
sendEmail: "Tip: prefer `primitive send --to <address> --body <text> --attachment <file>` for file attachments. This raw command exists for callers passing JSON.",
|
|
14135
14275
|
createFunction: "Tip: prefer `primitive functions deploy --name <name> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
14136
14276
|
updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
@@ -14233,6 +14373,10 @@ function createOperationCommand(operation) {
|
|
|
14233
14373
|
process.stderr.write(chunk);
|
|
14234
14374
|
} });
|
|
14235
14375
|
this.log(JSON.stringify(operationOutputPayload(envelope, parsedFlags.envelope === true), null, 2));
|
|
14376
|
+
if (isIncompleteDomainVerification(operation, envelope)) {
|
|
14377
|
+
writeIncompleteDomainVerificationHint();
|
|
14378
|
+
process.exitCode = 1;
|
|
14379
|
+
}
|
|
14236
14380
|
});
|
|
14237
14381
|
}
|
|
14238
14382
|
}
|
|
@@ -14348,6 +14492,13 @@ function cursorFromRows(rows) {
|
|
|
14348
14492
|
const last = rows.at(-1);
|
|
14349
14493
|
return last ? encodeReceivedAtSearchCursor(last) : null;
|
|
14350
14494
|
}
|
|
14495
|
+
function cursorFromAcceptedRows(rows) {
|
|
14496
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
14497
|
+
const row = rows[i];
|
|
14498
|
+
if (row.status === "accepted" || row.status === "completed") return encodeReceivedAtSearchCursor(row);
|
|
14499
|
+
}
|
|
14500
|
+
return null;
|
|
14501
|
+
}
|
|
14351
14502
|
function collectNewAcceptedEmails(rows, seenIds) {
|
|
14352
14503
|
const fresh = [];
|
|
14353
14504
|
for (const row of rows) {
|
|
@@ -14613,41 +14764,40 @@ function buildChatFollowUpCommands(context) {
|
|
|
14613
14764
|
return commands;
|
|
14614
14765
|
}
|
|
14615
14766
|
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
|
-
];
|
|
14767
|
+
const commands = [buildCommand("wait_threaded_reply", "Wait for the threaded reply again", [
|
|
14768
|
+
"primitive",
|
|
14769
|
+
"emails",
|
|
14770
|
+
"wait",
|
|
14771
|
+
"--reply-to-sent-email-id",
|
|
14772
|
+
context.sent.id,
|
|
14773
|
+
"--to",
|
|
14774
|
+
context.from,
|
|
14775
|
+
"--since",
|
|
14776
|
+
context.sentAtIso,
|
|
14777
|
+
"--timeout",
|
|
14778
|
+
String(context.timeoutSeconds)
|
|
14779
|
+
])];
|
|
14780
|
+
if (!context.strictOnly) commands.push(buildCommand("wait_fallback_reply", "Fallback wait by sender/time window", [
|
|
14781
|
+
"primitive",
|
|
14782
|
+
"emails",
|
|
14783
|
+
"wait",
|
|
14784
|
+
"--from",
|
|
14785
|
+
context.recipient,
|
|
14786
|
+
"--to",
|
|
14787
|
+
context.from,
|
|
14788
|
+
"--since",
|
|
14789
|
+
context.sentAtIso,
|
|
14790
|
+
"--timeout",
|
|
14791
|
+
String(context.timeoutSeconds)
|
|
14792
|
+
]));
|
|
14793
|
+
commands.push(buildCommand("inspect_sent_email", "Inspect the outbound send", [
|
|
14794
|
+
"primitive",
|
|
14795
|
+
"sent",
|
|
14796
|
+
"get",
|
|
14797
|
+
"--id",
|
|
14798
|
+
context.sent.id
|
|
14799
|
+
]));
|
|
14800
|
+
return commands;
|
|
14651
14801
|
}
|
|
14652
14802
|
function buildChatJsonEnvelope(context) {
|
|
14653
14803
|
const responseBody = resolveChatResponseBody(context.reply);
|
|
@@ -14794,7 +14944,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14794
14944
|
static summary = "Chat with an agent over email (send and wait for the reply)";
|
|
14795
14945
|
static examples = [
|
|
14796
14946
|
"<%= 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
|
|
14947
|
+
"cat error.log | <%= config.bin %> chat help@agent.acme.dev",
|
|
14798
14948
|
"<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing'",
|
|
14799
14949
|
"<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing' --reply-to-email-id <inbound-email-id>",
|
|
14800
14950
|
"<%= config.bin %> chat help@agent.acme.dev 'follow up question' --json",
|
|
@@ -14823,7 +14973,10 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14823
14973
|
hidden: true
|
|
14824
14974
|
}),
|
|
14825
14975
|
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
14826
|
-
subject: Flags.string({
|
|
14976
|
+
subject: Flags.string({
|
|
14977
|
+
description: "Advanced email transport override. Usually omit; chat threading does not depend on the subject.",
|
|
14978
|
+
hidden: true
|
|
14979
|
+
}),
|
|
14827
14980
|
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
14981
|
"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
14982
|
"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 +15283,38 @@ function redactConfig(config) {
|
|
|
15130
15283
|
environments: Object.fromEntries(Object.entries(config.environments).map(([name, environment]) => [name, redactCliEnvironment(environment)]))
|
|
15131
15284
|
};
|
|
15132
15285
|
}
|
|
15286
|
+
function upsertCliEnvironmentAndClearCredentialsIfSwitched(params) {
|
|
15287
|
+
const previousConfig = loadOrCreateConfig(params.configDir);
|
|
15288
|
+
const previousActiveEnvironment = resolveConfigEnvironment(previousConfig);
|
|
15289
|
+
const previousEnvironment = previousActiveEnvironment?.name ?? null;
|
|
15290
|
+
const config = upsertCliEnvironment({
|
|
15291
|
+
apiBaseUrl1: params.apiBaseUrl1,
|
|
15292
|
+
apiBaseUrl2: params.apiBaseUrl2,
|
|
15293
|
+
config: previousConfig,
|
|
15294
|
+
environmentName: params.environmentName,
|
|
15295
|
+
headers: params.headers,
|
|
15296
|
+
unsetHeaders: params.unsetHeaders
|
|
15297
|
+
});
|
|
15298
|
+
const activeEnvironment = resolveConfigEnvironment(config);
|
|
15299
|
+
const environment = activeEnvironment?.name ?? null;
|
|
15300
|
+
const shouldClearCredentials = existsSync(credentialsPath(params.configDir)) && (previousEnvironment !== environment || previousActiveEnvironment?.config.api_base_url_1 !== activeEnvironment?.config.api_base_url_1);
|
|
15301
|
+
let removedCredentials = false;
|
|
15302
|
+
if (shouldClearCredentials) {
|
|
15303
|
+
const releaseLock = acquireCliCredentialsLock(params.configDir);
|
|
15304
|
+
try {
|
|
15305
|
+
saveCliConfig(params.configDir, config);
|
|
15306
|
+
removedCredentials = existsSync(credentialsPath(params.configDir));
|
|
15307
|
+
deleteCliCredentials(params.configDir);
|
|
15308
|
+
} finally {
|
|
15309
|
+
releaseLock();
|
|
15310
|
+
}
|
|
15311
|
+
} else saveCliConfig(params.configDir, config);
|
|
15312
|
+
return {
|
|
15313
|
+
environment,
|
|
15314
|
+
previousEnvironment,
|
|
15315
|
+
removedCredentials
|
|
15316
|
+
};
|
|
15317
|
+
}
|
|
15133
15318
|
function switchCliEnvironment(configDir, environmentName) {
|
|
15134
15319
|
const environment = normalizeCliEnvironmentName(environmentName);
|
|
15135
15320
|
const config = loadOrCreateConfig(configDir);
|
|
@@ -15179,16 +15364,16 @@ var ConfigSetCommand = class ConfigSetCommand extends Command {
|
|
|
15179
15364
|
const { flags } = await this.parse(ConfigSetCommand);
|
|
15180
15365
|
const headers = flags.header ?? [];
|
|
15181
15366
|
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
|
|
15367
|
+
const { environment, removedCredentials } = upsertCliEnvironmentAndClearCredentialsIfSwitched({
|
|
15183
15368
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
15184
15369
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
15185
|
-
|
|
15370
|
+
configDir: this.config.configDir,
|
|
15186
15371
|
environmentName: flags.environment,
|
|
15187
15372
|
headers,
|
|
15188
15373
|
unsetHeaders: flags["unset-header"]
|
|
15189
15374
|
});
|
|
15190
|
-
|
|
15191
|
-
process.stderr.write(
|
|
15375
|
+
process.stderr.write(`Primitive CLI environment ${environment} is active.\n`);
|
|
15376
|
+
if (removedCredentials) process.stderr.write("Removed saved Primitive CLI credentials. Run `primitive signin` to authenticate in the active environment.\n");
|
|
15192
15377
|
}
|
|
15193
15378
|
};
|
|
15194
15379
|
var ConfigUseCommand = class ConfigUseCommand extends Command {
|
|
@@ -15878,7 +16063,9 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
15878
16063
|
process.exitCode = 1;
|
|
15879
16064
|
return;
|
|
15880
16065
|
}
|
|
15881
|
-
|
|
16066
|
+
const nextCursor = cursorFromAcceptedRows(page.rows);
|
|
16067
|
+
const cursorAdvanced = Boolean(nextCursor && nextCursor !== cursor);
|
|
16068
|
+
if (nextCursor) cursor = nextCursor;
|
|
15882
16069
|
for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
|
|
15883
16070
|
if (flags.table) {
|
|
15884
16071
|
if (!headerPrinted) {
|
|
@@ -15890,7 +16077,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
15890
16077
|
matched += 1;
|
|
15891
16078
|
if (matched >= flags.number) return;
|
|
15892
16079
|
}
|
|
15893
|
-
if (
|
|
16080
|
+
if (cursorAdvanced) continue;
|
|
15894
16081
|
if (deadline !== null && Date.now() >= deadline) break;
|
|
15895
16082
|
await sleep$1(flags.interval * 1e3);
|
|
15896
16083
|
}
|
|
@@ -16000,7 +16187,9 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
16000
16187
|
process.exitCode = 1;
|
|
16001
16188
|
return;
|
|
16002
16189
|
}
|
|
16003
|
-
|
|
16190
|
+
const nextCursor = cursorFromAcceptedRows(page.rows);
|
|
16191
|
+
const cursorAdvanced = Boolean(nextCursor && nextCursor !== cursor);
|
|
16192
|
+
if (nextCursor) cursor = nextCursor;
|
|
16004
16193
|
for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
|
|
16005
16194
|
if (flags.jsonl) this.log(JSON.stringify(email));
|
|
16006
16195
|
else {
|
|
@@ -16013,7 +16202,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
16013
16202
|
printed += 1;
|
|
16014
16203
|
if (flags.number && printed >= flags.number) return;
|
|
16015
16204
|
}
|
|
16016
|
-
if (
|
|
16205
|
+
if (cursorAdvanced) continue;
|
|
16017
16206
|
if (deadline !== null && Date.now() >= deadline) break;
|
|
16018
16207
|
await sleep$1(flags.interval * 1e3);
|
|
16019
16208
|
}
|
|
@@ -16105,6 +16294,129 @@ async function waitForFunctionDeploy(params) {
|
|
|
16105
16294
|
}
|
|
16106
16295
|
}
|
|
16107
16296
|
//#endregion
|
|
16297
|
+
//#region src/oclif/function-source.ts
|
|
16298
|
+
function collectSourceFiles(dir) {
|
|
16299
|
+
let pkgRaw;
|
|
16300
|
+
try {
|
|
16301
|
+
pkgRaw = readFileSync(join(dir, "package.json"), "utf8");
|
|
16302
|
+
} catch {
|
|
16303
|
+
return {
|
|
16304
|
+
kind: "error",
|
|
16305
|
+
message: `No package.json found in ${dir}. A managed build needs a package.json (its "dependencies" are installed).`
|
|
16306
|
+
};
|
|
16307
|
+
}
|
|
16308
|
+
let pkg;
|
|
16309
|
+
try {
|
|
16310
|
+
pkg = JSON.parse(pkgRaw);
|
|
16311
|
+
} catch (error) {
|
|
16312
|
+
return {
|
|
16313
|
+
kind: "error",
|
|
16314
|
+
message: `package.json in ${dir} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
16315
|
+
};
|
|
16316
|
+
}
|
|
16317
|
+
delete pkg.devDependencies;
|
|
16318
|
+
const files = { "package.json": `${JSON.stringify(pkg, null, 2)}\n` };
|
|
16319
|
+
const srcDir = join(dir, "src");
|
|
16320
|
+
if (isDirectory(srcDir)) for (const abs of walk(srcDir)) files[relative(dir, abs).split(sep).join("/")] = readFileSync(abs, "utf8");
|
|
16321
|
+
if (Object.keys(files).length === 1) return {
|
|
16322
|
+
kind: "error",
|
|
16323
|
+
message: `No source files found under ${srcDir}. Put your handler at src/index.ts.`
|
|
16324
|
+
};
|
|
16325
|
+
return {
|
|
16326
|
+
kind: "ok",
|
|
16327
|
+
files
|
|
16328
|
+
};
|
|
16329
|
+
}
|
|
16330
|
+
function isDirectory(path) {
|
|
16331
|
+
try {
|
|
16332
|
+
return statSync(path).isDirectory();
|
|
16333
|
+
} catch {
|
|
16334
|
+
return false;
|
|
16335
|
+
}
|
|
16336
|
+
}
|
|
16337
|
+
function* walk(dir) {
|
|
16338
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
16339
|
+
const abs = join(dir, entry.name);
|
|
16340
|
+
if (entry.isDirectory()) yield* walk(abs);
|
|
16341
|
+
else yield abs;
|
|
16342
|
+
}
|
|
16343
|
+
}
|
|
16344
|
+
async function runSourceDeploy(api, params) {
|
|
16345
|
+
const listed = await api.listFunctions();
|
|
16346
|
+
if (listed.error) return {
|
|
16347
|
+
kind: "error",
|
|
16348
|
+
payload: extractErrorPayload(listed.error),
|
|
16349
|
+
stage: "lookup"
|
|
16350
|
+
};
|
|
16351
|
+
const foundId = (listed.data?.data ?? []).find((f) => f.name === params.name)?.id ?? null;
|
|
16352
|
+
if (foundId !== null) {
|
|
16353
|
+
const updated = await api.updateFunction({
|
|
16354
|
+
files: params.files,
|
|
16355
|
+
id: foundId
|
|
16356
|
+
});
|
|
16357
|
+
if (updated.error) return {
|
|
16358
|
+
kind: "error",
|
|
16359
|
+
payload: extractErrorPayload(updated.error),
|
|
16360
|
+
stage: "redeploy"
|
|
16361
|
+
};
|
|
16362
|
+
const data = updated.data?.data;
|
|
16363
|
+
if (!data) return {
|
|
16364
|
+
kind: "error",
|
|
16365
|
+
payload: {
|
|
16366
|
+
code: "client_error",
|
|
16367
|
+
message: "Redeploy returned no data"
|
|
16368
|
+
},
|
|
16369
|
+
stage: "redeploy"
|
|
16370
|
+
};
|
|
16371
|
+
return {
|
|
16372
|
+
action: "redeployed",
|
|
16373
|
+
kind: "ok",
|
|
16374
|
+
result: data
|
|
16375
|
+
};
|
|
16376
|
+
}
|
|
16377
|
+
const created = await api.createFunction({
|
|
16378
|
+
files: params.files,
|
|
16379
|
+
name: params.name
|
|
16380
|
+
});
|
|
16381
|
+
if (created.error) return {
|
|
16382
|
+
kind: "error",
|
|
16383
|
+
payload: extractErrorPayload(created.error),
|
|
16384
|
+
stage: "create"
|
|
16385
|
+
};
|
|
16386
|
+
const data = created.data?.data;
|
|
16387
|
+
if (!data) return {
|
|
16388
|
+
kind: "error",
|
|
16389
|
+
payload: {
|
|
16390
|
+
code: "client_error",
|
|
16391
|
+
message: "Create returned no data"
|
|
16392
|
+
},
|
|
16393
|
+
stage: "create"
|
|
16394
|
+
};
|
|
16395
|
+
return {
|
|
16396
|
+
action: "created",
|
|
16397
|
+
kind: "ok",
|
|
16398
|
+
result: data
|
|
16399
|
+
};
|
|
16400
|
+
}
|
|
16401
|
+
function renderBuildFailure(payload, write) {
|
|
16402
|
+
if (typeof payload !== "object" || payload === null) return false;
|
|
16403
|
+
const error = payload.error ?? payload;
|
|
16404
|
+
if (typeof error !== "object" || error === null) return false;
|
|
16405
|
+
if (error.code !== "build_failed") return false;
|
|
16406
|
+
const details = error.details;
|
|
16407
|
+
const phase = typeof details === "object" && details !== null ? details.phase : void 0;
|
|
16408
|
+
write(`Build failed${typeof phase === "string" ? ` during ${phase}` : ""}.\n`);
|
|
16409
|
+
const errors = typeof details === "object" && details !== null ? details.errors : void 0;
|
|
16410
|
+
if (Array.isArray(errors)) for (const e of errors) {
|
|
16411
|
+
if (typeof e !== "object" || e === null) continue;
|
|
16412
|
+
const item = e;
|
|
16413
|
+
const loc = typeof item.file === "string" ? ` (${item.file}${typeof item.line === "number" ? `:${item.line}` : ""})` : "";
|
|
16414
|
+
write(` [${String(item.code)}] ${String(item.message)}${loc}\n`);
|
|
16415
|
+
if (typeof item.hint === "string") write(` hint: ${item.hint}\n`);
|
|
16416
|
+
}
|
|
16417
|
+
return true;
|
|
16418
|
+
}
|
|
16419
|
+
//#endregion
|
|
16108
16420
|
//#region src/oclif/lint/raw-send-mail-fetch.ts
|
|
16109
16421
|
const RAW_SEND_MAIL_FETCH_REGEX = /fetch\s*\(\s*[`'"][^`'"]*primitive\.dev[^`'"]*\/send-mail(?![A-Za-z0-9_-])/g;
|
|
16110
16422
|
const SNIPPET_PADDING = 60;
|
|
@@ -16571,6 +16883,8 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
16571
16883
|
static summary = "Deploy a new function from a bundled handler file";
|
|
16572
16884
|
static examples = [
|
|
16573
16885
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js",
|
|
16886
|
+
"<%= config.bin %> functions deploy --name triage --source ./triage-agent",
|
|
16887
|
+
"<%= config.bin %> functions deploy --name triage --source . --wait",
|
|
16574
16888
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --wait",
|
|
16575
16889
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
16576
16890
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
|
|
@@ -16596,10 +16910,8 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
16596
16910
|
description: "Slug-style name. Lowercase letters, digits, hyphens, underscores. 1-64 chars. Must be unique within the org.",
|
|
16597
16911
|
required: true
|
|
16598
16912
|
}),
|
|
16599
|
-
file: Flags.string({
|
|
16600
|
-
|
|
16601
|
-
required: true
|
|
16602
|
-
}),
|
|
16913
|
+
file: Flags.string({ description: "Path to the bundled ESM handler file (single self-contained module). Loaded as the `code` body field. Exactly one of --file or --source is required." }),
|
|
16914
|
+
source: Flags.string({ description: "Path to a project directory (containing package.json and src/) to deploy via managed build: the source is uploaded and the server installs dependencies, bundles for the Workers runtime, and deploys. Idempotent by name (creates the function, or redeploys it if --name already exists), so it is safe to run on every push. Exactly one of --file or --source is required." }),
|
|
16603
16915
|
"source-map-file": Flags.string({ description: "Optional path to a source map for the bundle. Stored with the deployment attempt and used to symbolicate stack traces in function logs." }),
|
|
16604
16916
|
secret: Flags.string({
|
|
16605
16917
|
description: `Secret KEY=VALUE to seed on the deployed function. Repeatable. KEY must match \`^[A-Z_][A-Z0-9_]*$\`; VALUE may contain \`=\` (only the first \`=\` is treated as a delimiter). Each KEY may only appear once per command. Passing one or more --secret flags fans out the deploy to create-function, set-secret per pair, then a final redeploy so the running handler picks up the bindings. ${SECRET_FLAG_SECURITY_NOTE}`,
|
|
@@ -16641,6 +16953,17 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
16641
16953
|
process.exitCode = 1;
|
|
16642
16954
|
return;
|
|
16643
16955
|
}
|
|
16956
|
+
if (flags.file === void 0 === (flags.source === void 0)) {
|
|
16957
|
+
process.stderr.write("Provide exactly one of --file (a pre-built bundle) or --source (a project directory for managed build).\n");
|
|
16958
|
+
process.exitCode = 1;
|
|
16959
|
+
return;
|
|
16960
|
+
}
|
|
16961
|
+
if (flags.source !== void 0) {
|
|
16962
|
+
await this.runSourceMode(flags, flags.source);
|
|
16963
|
+
return;
|
|
16964
|
+
}
|
|
16965
|
+
const file = flags.file;
|
|
16966
|
+
if (file === void 0) return;
|
|
16644
16967
|
const parsedSecrets = resolveSecretFlags({
|
|
16645
16968
|
fromEnv: flags["secret-from-env"] ?? [],
|
|
16646
16969
|
fromEnvFile: flags["secret-from-env-file"] ?? [],
|
|
@@ -16653,7 +16976,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
16653
16976
|
process.exitCode = 1;
|
|
16654
16977
|
return;
|
|
16655
16978
|
}
|
|
16656
|
-
const code = readTextFileFlag(
|
|
16979
|
+
const code = readTextFileFlag(file, "--file");
|
|
16657
16980
|
const sourceMap = flags["source-map-file"] ? readTextFileFlag(flags["source-map-file"], "--source-map-file") : void 0;
|
|
16658
16981
|
emitRawSendMailFetchWarning(code, (chunk) => process.stderr.write(chunk));
|
|
16659
16982
|
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
@@ -16759,6 +17082,101 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
16759
17082
|
this.log(JSON.stringify(payload, null, 2));
|
|
16760
17083
|
});
|
|
16761
17084
|
}
|
|
17085
|
+
async runSourceMode(flags, sourceDir) {
|
|
17086
|
+
if ((flags.secret?.length ?? 0) > 0 || (flags["secret-from-env"]?.length ?? 0) > 0 || (flags["secret-from-file"]?.length ?? 0) > 0 || (flags["secret-from-env-file"]?.length ?? 0) > 0 || flags["secret-from-stdin"] !== void 0) {
|
|
17087
|
+
process.stderr.write("Secret flags are not supported with --source yet. Deploy from source first, then set secrets with `primitive functions set-secret` and redeploy.\n");
|
|
17088
|
+
process.exitCode = 1;
|
|
17089
|
+
return;
|
|
17090
|
+
}
|
|
17091
|
+
const collected = collectSourceFiles(sourceDir);
|
|
17092
|
+
if (collected.kind === "error") {
|
|
17093
|
+
process.stderr.write(`${collected.message}\n`);
|
|
17094
|
+
process.exitCode = 1;
|
|
17095
|
+
return;
|
|
17096
|
+
}
|
|
17097
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
17098
|
+
apiKey: flags["api-key"],
|
|
17099
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
17100
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
17101
|
+
configDir: this.config.configDir
|
|
17102
|
+
});
|
|
17103
|
+
const authFailureContext = {
|
|
17104
|
+
auth,
|
|
17105
|
+
baseUrlOverridden,
|
|
17106
|
+
configDir: this.config.configDir
|
|
17107
|
+
};
|
|
17108
|
+
const outcome = await runSourceDeploy({
|
|
17109
|
+
createFunction: (p) => createFunction({
|
|
17110
|
+
body: {
|
|
17111
|
+
files: p.files,
|
|
17112
|
+
name: p.name
|
|
17113
|
+
},
|
|
17114
|
+
client: apiClient.client,
|
|
17115
|
+
responseStyle: "fields"
|
|
17116
|
+
}),
|
|
17117
|
+
listFunctions: () => listFunctions({
|
|
17118
|
+
client: apiClient.client,
|
|
17119
|
+
responseStyle: "fields"
|
|
17120
|
+
}),
|
|
17121
|
+
updateFunction: (p) => updateFunction({
|
|
17122
|
+
body: { files: p.files },
|
|
17123
|
+
client: apiClient.client,
|
|
17124
|
+
path: { id: p.id },
|
|
17125
|
+
responseStyle: "fields"
|
|
17126
|
+
})
|
|
17127
|
+
}, {
|
|
17128
|
+
files: collected.files,
|
|
17129
|
+
name: flags.name
|
|
17130
|
+
});
|
|
17131
|
+
if (outcome.kind === "error") {
|
|
17132
|
+
renderBuildFailure(outcome.payload, (chunk) => process.stderr.write(chunk));
|
|
17133
|
+
writeErrorWithHints(outcome.payload);
|
|
17134
|
+
surfaceUnauthorizedHint({
|
|
17135
|
+
...authFailureContext,
|
|
17136
|
+
payload: outcome.payload
|
|
17137
|
+
});
|
|
17138
|
+
process.exitCode = 1;
|
|
17139
|
+
return;
|
|
17140
|
+
}
|
|
17141
|
+
const payload = outcome.result;
|
|
17142
|
+
if (flags.wait) {
|
|
17143
|
+
const waitResult = await waitForFunctionDeploy({
|
|
17144
|
+
getFunction: (p) => getFunction({
|
|
17145
|
+
client: apiClient.client,
|
|
17146
|
+
path: { id: p.id },
|
|
17147
|
+
responseStyle: "fields"
|
|
17148
|
+
}),
|
|
17149
|
+
id: payload.id,
|
|
17150
|
+
initial: payload,
|
|
17151
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
17152
|
+
timeoutSeconds: flags.timeout,
|
|
17153
|
+
writeStderr: (chunk) => process.stderr.write(chunk)
|
|
17154
|
+
});
|
|
17155
|
+
if (waitResult.kind === "error") {
|
|
17156
|
+
writeErrorWithHints(waitResult.payload);
|
|
17157
|
+
surfaceUnauthorizedHint({
|
|
17158
|
+
...authFailureContext,
|
|
17159
|
+
payload: waitResult.payload
|
|
17160
|
+
});
|
|
17161
|
+
process.exitCode = 1;
|
|
17162
|
+
return;
|
|
17163
|
+
}
|
|
17164
|
+
if (waitResult.kind === "timeout") {
|
|
17165
|
+
const status = waitResult.lastFunction?.deploy_status ?? "unknown";
|
|
17166
|
+
process.stderr.write(`Timed out after ${flags.timeout}s waiting for function ${payload.id} deploy to finish (last status: ${status}).\n`);
|
|
17167
|
+
process.exitCode = 2;
|
|
17168
|
+
return;
|
|
17169
|
+
}
|
|
17170
|
+
this.log(JSON.stringify(waitResult.function, null, 2));
|
|
17171
|
+
if (waitResult.kind === "failed") {
|
|
17172
|
+
const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
|
|
17173
|
+
process.stderr.write(`Function ${payload.id} deploy failed${detail}\n`);
|
|
17174
|
+
process.exitCode = 1;
|
|
17175
|
+
}
|
|
17176
|
+
return;
|
|
17177
|
+
}
|
|
17178
|
+
this.log(JSON.stringify(payload, null, 2));
|
|
17179
|
+
}
|
|
16762
17180
|
};
|
|
16763
17181
|
//#endregion
|
|
16764
17182
|
//#region src/oclif/function-templates.ts
|
|
@@ -16768,8 +17186,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
16768
17186
|
name: "Primitive Team",
|
|
16769
17187
|
url: "https://primitive.dev"
|
|
16770
17188
|
};
|
|
16771
|
-
const SDK_VERSION_RANGE = "^0.
|
|
16772
|
-
const CLI_VERSION_RANGE = "^0.
|
|
17189
|
+
const SDK_VERSION_RANGE = "^0.33.0";
|
|
17190
|
+
const CLI_VERSION_RANGE = "^0.33.0";
|
|
16773
17191
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
16774
17192
|
function renderHandler() {
|
|
16775
17193
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -16795,49 +17213,93 @@ interface Env {
|
|
|
16795
17213
|
PRIMITIVE_WEBHOOK_SECRET: string;
|
|
16796
17214
|
}
|
|
16797
17215
|
|
|
16798
|
-
//
|
|
16799
|
-
//
|
|
16800
|
-
//
|
|
16801
|
-
// you later switch to
|
|
16802
|
-
|
|
16803
|
-
|
|
17216
|
+
// Optional loop-protection knob. client.reply() server-defaults the
|
|
17217
|
+
// outbound from-address from the inbound recipient, so most handlers
|
|
17218
|
+
// do not need to fill this in. Add any extra addresses your handler
|
|
17219
|
+
// sends from if you later switch to client.send or a custom domain.
|
|
17220
|
+
const EXTRA_SELF_ADDRESSES: string[] = [
|
|
17221
|
+
// "bot@your-domain.example",
|
|
17222
|
+
];
|
|
17223
|
+
|
|
17224
|
+
function extractEmailAddresses(value: string | null | undefined): string[] {
|
|
17225
|
+
return (
|
|
17226
|
+
value?.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi)?.map((address) =>
|
|
17227
|
+
address.toLowerCase(),
|
|
17228
|
+
) ?? []
|
|
17229
|
+
);
|
|
17230
|
+
}
|
|
17231
|
+
|
|
17232
|
+
function domainPart(address: string): string | null {
|
|
17233
|
+
const at = address.lastIndexOf("@");
|
|
17234
|
+
return at === -1 ? null : address.slice(at + 1).toLowerCase();
|
|
17235
|
+
}
|
|
17236
|
+
|
|
17237
|
+
function localPart(address: string): string {
|
|
17238
|
+
const at = address.lastIndexOf("@");
|
|
17239
|
+
return at === -1 ? address.toLowerCase() : address.slice(0, at).toLowerCase();
|
|
17240
|
+
}
|
|
17241
|
+
|
|
17242
|
+
function inboundRecipientAddresses(event: EmailReceivedEvent): string[] {
|
|
17243
|
+
return [
|
|
17244
|
+
...event.email.smtp.rcpt_to.flatMap(extractEmailAddresses),
|
|
17245
|
+
...extractEmailAddresses(event.email.headers.to),
|
|
17246
|
+
];
|
|
17247
|
+
}
|
|
17248
|
+
|
|
17249
|
+
function inboundRecipientDomains(event: EmailReceivedEvent): Set<string> {
|
|
17250
|
+
return new Set(
|
|
17251
|
+
inboundRecipientAddresses(event)
|
|
17252
|
+
.map(domainPart)
|
|
17253
|
+
.filter((domain): domain is string => domain !== null),
|
|
17254
|
+
);
|
|
17255
|
+
}
|
|
16804
17256
|
|
|
16805
17257
|
// Loop protection. A newly deployed Function starts as a fallback
|
|
16806
|
-
// endpoint for managed
|
|
16807
|
-
//
|
|
16808
|
-
//
|
|
16809
|
-
//
|
|
17258
|
+
// endpoint for managed domains and can receive bounces or auto-replies
|
|
17259
|
+
// generated by its own outbound mail. Do not hardcode a managed suffix:
|
|
17260
|
+
// staging, production, and custom domains all arrive through the same
|
|
17261
|
+
// webhook shape. Derive the handler's inbound domains from the actual
|
|
17262
|
+
// SMTP recipients instead.
|
|
16810
17263
|
//
|
|
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.
|
|
17264
|
+
// The default check skips:
|
|
17265
|
+
// - direct self-mail where From equals one of the inbound recipients;
|
|
17266
|
+
// - mailer-daemon/postmaster bounces from the same domain as the inbound;
|
|
17267
|
+
// - any address explicitly listed in EXTRA_SELF_ADDRESSES.
|
|
16821
17268
|
//
|
|
16822
17269
|
// 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
17270
|
// - Honor RFC 3834 auto-response headers: skip when
|
|
16827
|
-
//
|
|
16828
|
-
//
|
|
16829
|
-
// is present.
|
|
17271
|
+
// event.email.headers["auto-submitted"] is anything other than "no",
|
|
17272
|
+
// or when a List-Unsubscribe / Precedence: bulk header is present.
|
|
16830
17273
|
// - Track Message-ID / In-Reply-To chains to break ping-pong loops
|
|
16831
17274
|
// between two cooperating handlers on different domains.
|
|
16832
17275
|
export function isLoop(event: EmailReceivedEvent): boolean {
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
16838
|
-
|
|
16839
|
-
|
|
16840
|
-
|
|
17276
|
+
const fromAddresses = [
|
|
17277
|
+
...extractEmailAddresses(event.email.headers.from),
|
|
17278
|
+
...extractEmailAddresses(event.email.smtp.mail_from),
|
|
17279
|
+
];
|
|
17280
|
+
if (fromAddresses.length === 0) return false;
|
|
17281
|
+
|
|
17282
|
+
const inboundAddresses = new Set(inboundRecipientAddresses(event));
|
|
17283
|
+
const inboundDomains = inboundRecipientDomains(event);
|
|
17284
|
+
const extraSelfAddresses = new Set(
|
|
17285
|
+
EXTRA_SELF_ADDRESSES.map((address) => address.toLowerCase()),
|
|
17286
|
+
);
|
|
17287
|
+
|
|
17288
|
+
for (const from of fromAddresses) {
|
|
17289
|
+
if (inboundAddresses.has(from)) return true;
|
|
17290
|
+
if (extraSelfAddresses.has(from)) return true;
|
|
17291
|
+
|
|
17292
|
+
const fromDomain = domainPart(from);
|
|
17293
|
+
const fromLocal = localPart(from);
|
|
17294
|
+
const sameInboundDomain = fromDomain ? inboundDomains.has(fromDomain) : false;
|
|
17295
|
+
if (
|
|
17296
|
+
sameInboundDomain &&
|
|
17297
|
+
(fromLocal === "mailer-daemon" || fromLocal === "postmaster")
|
|
17298
|
+
) {
|
|
17299
|
+
return true;
|
|
17300
|
+
}
|
|
17301
|
+
}
|
|
17302
|
+
|
|
16841
17303
|
return false;
|
|
16842
17304
|
}
|
|
16843
17305
|
|
|
@@ -16885,11 +17347,16 @@ export default {
|
|
|
16885
17347
|
apiBaseUrl1: env.PRIMITIVE_API_BASE_URL,
|
|
16886
17348
|
});
|
|
16887
17349
|
|
|
17350
|
+
// To add an LLM or another API, store its key as a Function secret.
|
|
17351
|
+
// Example:
|
|
17352
|
+
// export OPENAI_KEY=sk-...
|
|
17353
|
+
// primitive functions set-secret --id <fn-id> --key OPENAI_KEY --value-from-env OPENAI_KEY --redeploy
|
|
17354
|
+
|
|
16888
17355
|
// Recipient gate
|
|
16889
17356
|
// https://www.primitive.dev/docs/sending#who-you-can-send-to
|
|
16890
17357
|
// Even via client.reply, sends to the original sender are
|
|
16891
17358
|
// subject to the recipient gate. New accounts can send to
|
|
16892
|
-
//
|
|
17359
|
+
// Primitive-managed domains, verified domains, addresses that
|
|
16893
17360
|
// have authenticated to you, and other org-member signup emails.
|
|
16894
17361
|
// Sends to arbitrary external addresses return 403
|
|
16895
17362
|
// recipient_not_allowed with a structured gates[] array until
|
|
@@ -16939,8 +17406,10 @@ function renderPackageJson(name) {
|
|
|
16939
17406
|
type: "module",
|
|
16940
17407
|
scripts: {
|
|
16941
17408
|
build: "node build.mjs",
|
|
16942
|
-
deploy: `npm run build && primitive functions deploy --name ${name} --file ./dist/handler.js`,
|
|
16943
|
-
|
|
17409
|
+
deploy: `npm run build && primitive functions deploy --name ${name} --file ./dist/handler.js --wait`,
|
|
17410
|
+
"test:function": "primitive functions test --id $PRIMITIVE_FUNCTION_ID --wait --show-sends",
|
|
17411
|
+
logs: "primitive functions logs --id $PRIMITIVE_FUNCTION_ID",
|
|
17412
|
+
redeploy: "npm run build && primitive functions redeploy --id $PRIMITIVE_FUNCTION_ID --file ./dist/handler.js --wait"
|
|
16944
17413
|
},
|
|
16945
17414
|
dependencies: { "@primitivedotdev/sdk": SDK_VERSION_RANGE },
|
|
16946
17415
|
devDependencies: {
|
|
@@ -17011,23 +17480,57 @@ npm run build
|
|
|
17011
17480
|
npm run deploy
|
|
17012
17481
|
\`\`\`
|
|
17013
17482
|
|
|
17014
|
-
The deploy step calls \`primitive functions deploy\` (provided
|
|
17015
|
-
\`@primitivedotdev/cli\` package; install with
|
|
17483
|
+
The deploy step calls \`primitive functions deploy --wait\` (provided
|
|
17484
|
+
by the \`@primitivedotdev/cli\` package; install with
|
|
17016
17485
|
\`npm install -g @primitivedotdev/cli\` or run via
|
|
17017
17486
|
\`npx @primitivedotdev/cli@latest <command>\`). It requires
|
|
17018
17487
|
\`PRIMITIVE_API_KEY\` to be set in your shell (or pass \`--api-key\`).
|
|
17019
17488
|
Run \`primitive signin\` once to save a key in your CLI config if you
|
|
17020
17489
|
prefer that to an env var.
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17490
|
+
|
|
17491
|
+
After the first deploy, copy the returned function id into your shell:
|
|
17492
|
+
|
|
17493
|
+
\`\`\`
|
|
17494
|
+
export PRIMITIVE_FUNCTION_ID=<fn-id>
|
|
17495
|
+
\`\`\`
|
|
17496
|
+
|
|
17497
|
+
## Prove it works
|
|
17498
|
+
|
|
17499
|
+
\`\`\`
|
|
17500
|
+
primitive inbox status
|
|
17501
|
+
npm run test:function
|
|
17502
|
+
npm run logs
|
|
17503
|
+
\`\`\`
|
|
17504
|
+
|
|
17505
|
+
\`npm run test:function\` sends a real test email through MX, waits for
|
|
17506
|
+
the Function to process it, and prints any outbound replies emitted by
|
|
17507
|
+
the handler.
|
|
17508
|
+
|
|
17509
|
+
## Redeploy
|
|
17510
|
+
|
|
17511
|
+
\`\`\`
|
|
17512
|
+
npm run redeploy
|
|
17513
|
+
\`\`\`
|
|
17514
|
+
|
|
17515
|
+
## Secrets
|
|
17516
|
+
|
|
17517
|
+
Use secrets for API keys used by your handler. \`--redeploy\` makes the
|
|
17518
|
+
new value visible to the running Function immediately.
|
|
17519
|
+
|
|
17520
|
+
\`\`\`
|
|
17521
|
+
export OPENAI_KEY=sk-...
|
|
17522
|
+
primitive functions set-secret --id "$PRIMITIVE_FUNCTION_ID" --key OPENAI_KEY --value-from-env OPENAI_KEY --redeploy
|
|
17523
|
+
\`\`\`
|
|
17524
|
+
`;
|
|
17525
|
+
}
|
|
17526
|
+
function renderEmailReplyTemplateFiles(name) {
|
|
17527
|
+
return [
|
|
17528
|
+
{
|
|
17529
|
+
contents: renderHandler(),
|
|
17530
|
+
relativePath: "handler.ts"
|
|
17531
|
+
},
|
|
17532
|
+
{
|
|
17533
|
+
contents: renderPackageJson(name),
|
|
17031
17534
|
relativePath: "package.json"
|
|
17032
17535
|
},
|
|
17033
17536
|
{
|
|
@@ -17192,8 +17695,9 @@ var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
|
17192
17695
|
this.log("Next:");
|
|
17193
17696
|
this.log(` cd ${outDir}`);
|
|
17194
17697
|
this.log(" npm install");
|
|
17195
|
-
this.log(" npm run
|
|
17196
|
-
this.log(
|
|
17698
|
+
this.log(" npm run deploy");
|
|
17699
|
+
this.log(" export PRIMITIVE_FUNCTION_ID=<id-from-deploy-output>");
|
|
17700
|
+
this.log(" npm run test:function");
|
|
17197
17701
|
}
|
|
17198
17702
|
};
|
|
17199
17703
|
//#endregion
|
|
@@ -17823,26 +18327,32 @@ var FunctionsTemplatesCommand = class FunctionsTemplatesCommand extends Command
|
|
|
17823
18327
|
//#endregion
|
|
17824
18328
|
//#region src/oclif/commands/functions-test-function.ts
|
|
17825
18329
|
const DEFAULT_WAIT_TIMEOUT_SECONDS = 60;
|
|
17826
|
-
const
|
|
18330
|
+
const TERMINAL_TEST_TRACE_STATES = new Set([
|
|
18331
|
+
"completed",
|
|
18332
|
+
"failed",
|
|
18333
|
+
"send_failed"
|
|
18334
|
+
]);
|
|
17827
18335
|
function buildFunctionTestOutcome(params) {
|
|
18336
|
+
const inbound = params.trace.inbound_email;
|
|
17828
18337
|
const outcome = {
|
|
17829
18338
|
elapsed_seconds: params.elapsedSeconds,
|
|
17830
18339
|
function_id: params.functionId,
|
|
17831
18340
|
inbound_domain: params.invocation.inbound_domain,
|
|
17832
|
-
inbound_id:
|
|
18341
|
+
inbound_id: inbound?.id ?? null,
|
|
17833
18342
|
inbound_to: params.invocation.to,
|
|
17834
18343
|
poll_since: params.invocation.poll_since,
|
|
18344
|
+
state: params.trace.state,
|
|
17835
18345
|
test_run_id: params.invocation.test_run_id,
|
|
17836
18346
|
test_send_id: params.invocation.send_id,
|
|
17837
18347
|
test_subject: params.invocation.subject,
|
|
17838
18348
|
trace_url: params.invocation.trace_url,
|
|
17839
18349
|
watch_url: params.invocation.watch_url,
|
|
17840
|
-
webhook_attempt_count:
|
|
17841
|
-
webhook_last_error:
|
|
17842
|
-
webhook_last_status_code:
|
|
17843
|
-
webhook_status:
|
|
18350
|
+
webhook_attempt_count: inbound?.webhook_attempt_count ?? null,
|
|
18351
|
+
webhook_last_error: inbound?.webhook_last_error ?? null,
|
|
18352
|
+
webhook_last_status_code: inbound?.webhook_last_status_code ?? null,
|
|
18353
|
+
webhook_status: inbound?.webhook_status ?? null
|
|
17844
18354
|
};
|
|
17845
|
-
if (params.showSends) outcome.sent_emails = params.
|
|
18355
|
+
if (params.showSends) outcome.sent_emails = params.trace.replies;
|
|
17846
18356
|
return outcome;
|
|
17847
18357
|
}
|
|
17848
18358
|
function writeFunctionTestProgress(message, writeStderr = (chunk) => {
|
|
@@ -17941,7 +18451,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
17941
18451
|
required: true
|
|
17942
18452
|
}),
|
|
17943
18453
|
"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
|
|
18454
|
+
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
18455
|
"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
18456
|
timeout: Flags.integer({
|
|
17947
18457
|
default: DEFAULT_WAIT_TIMEOUT_SECONDS,
|
|
@@ -18001,41 +18511,15 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
18001
18511
|
const timeoutMs = flags.timeout * 1e3;
|
|
18002
18512
|
const pollIntervalMs = flags["poll-interval"] * 1e3;
|
|
18003
18513
|
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;
|
|
18514
|
+
writeFunctionTestProgress(`Waiting for test run ${invocation.test_run_id} to complete for ${invocation.to}...`);
|
|
18515
|
+
let trace;
|
|
18035
18516
|
while (!isExpired()) {
|
|
18036
|
-
const result = await
|
|
18517
|
+
const result = await getFunctionTestRunTrace({
|
|
18037
18518
|
client: apiClient.client,
|
|
18038
|
-
path: {
|
|
18519
|
+
path: {
|
|
18520
|
+
id: flags.id,
|
|
18521
|
+
run_id: invocation.test_run_id
|
|
18522
|
+
},
|
|
18039
18523
|
responseStyle: "fields"
|
|
18040
18524
|
});
|
|
18041
18525
|
if (result.error) {
|
|
@@ -18051,24 +18535,22 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
18051
18535
|
return;
|
|
18052
18536
|
}
|
|
18053
18537
|
const fetched = result.data.data;
|
|
18054
|
-
if (
|
|
18055
|
-
|
|
18538
|
+
if (TERMINAL_TEST_TRACE_STATES.has(fetched.state)) {
|
|
18539
|
+
trace = fetched;
|
|
18056
18540
|
break;
|
|
18057
18541
|
}
|
|
18058
18542
|
await sleep$1(pollIntervalMs);
|
|
18059
18543
|
}
|
|
18060
|
-
if (!
|
|
18061
|
-
const elapsedSeconds = Math.round((Date.now() - startedAt) / 1e3);
|
|
18544
|
+
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
18545
|
const outcome = buildFunctionTestOutcome({
|
|
18063
|
-
|
|
18064
|
-
elapsedSeconds,
|
|
18546
|
+
elapsedSeconds: Math.round((Date.now() - startedAt) / 1e3),
|
|
18065
18547
|
functionId: flags.id,
|
|
18066
|
-
inboundId,
|
|
18067
18548
|
invocation,
|
|
18068
|
-
showSends: shouldShowSends
|
|
18549
|
+
showSends: shouldShowSends,
|
|
18550
|
+
trace
|
|
18069
18551
|
});
|
|
18070
18552
|
this.log(JSON.stringify(outcome, null, 2));
|
|
18071
|
-
if (
|
|
18553
|
+
if (trace.state === "failed" || trace.state === "send_failed") process.exitCode = 1;
|
|
18072
18554
|
});
|
|
18073
18555
|
}
|
|
18074
18556
|
};
|
|
@@ -18319,7 +18801,7 @@ async function checkExistingLogin(params) {
|
|
|
18319
18801
|
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
18802
|
};
|
|
18321
18803
|
}
|
|
18322
|
-
var LoginCommand = class extends Command {
|
|
18804
|
+
var LoginCommand$1 = class extends Command {
|
|
18323
18805
|
static description = "Log in by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
|
|
18324
18806
|
static summary = "Log in with browser approval";
|
|
18325
18807
|
static examples = [
|
|
@@ -18343,6 +18825,9 @@ var LoginCommand = class extends Command {
|
|
|
18343
18825
|
async run() {
|
|
18344
18826
|
const commandClass = this.constructor;
|
|
18345
18827
|
const { flags } = await this.parse(commandClass);
|
|
18828
|
+
await this.runBrowserLogin(flags, this.retryCommand());
|
|
18829
|
+
}
|
|
18830
|
+
async runBrowserLogin(flags, retryCommand = this.retryCommand()) {
|
|
18346
18831
|
let releaseCredentialsLock;
|
|
18347
18832
|
try {
|
|
18348
18833
|
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
@@ -18350,7 +18835,7 @@ var LoginCommand = class extends Command {
|
|
|
18350
18835
|
throw cliError$3(error instanceof Error ? error.message : String(error));
|
|
18351
18836
|
}
|
|
18352
18837
|
try {
|
|
18353
|
-
await this.runWithCredentialLock(flags,
|
|
18838
|
+
await this.runWithCredentialLock(flags, retryCommand);
|
|
18354
18839
|
} finally {
|
|
18355
18840
|
releaseCredentialsLock();
|
|
18356
18841
|
}
|
|
@@ -18458,567 +18943,132 @@ var LoginCommand = class extends Command {
|
|
|
18458
18943
|
}
|
|
18459
18944
|
};
|
|
18460
18945
|
//#endregion
|
|
18461
|
-
//#region src/oclif/commands/
|
|
18946
|
+
//#region src/oclif/commands/signup.ts
|
|
18947
|
+
const INVALID_VERIFICATION_CODE = "invalid_verification_code";
|
|
18948
|
+
const EXPIRED_TOKEN = "expired_token";
|
|
18949
|
+
const INVALID_SIGNUP_TOKEN = "invalid_signup_token";
|
|
18950
|
+
const SLOW_DOWN = "slow_down";
|
|
18951
|
+
const PENDING_SIGNUP_FILE = "signup.json";
|
|
18952
|
+
const DEFAULT_SIGNUP_COMMAND_COPY = {
|
|
18953
|
+
actionNoun: "signup",
|
|
18954
|
+
actionGerund: "creating a new account",
|
|
18955
|
+
confirmCommand: (email) => `signup confirm ${email} <code>`,
|
|
18956
|
+
resendCommand: (email) => `signup resend ${email}`,
|
|
18957
|
+
startCommand: (email) => `signup ${email}`
|
|
18958
|
+
};
|
|
18462
18959
|
function cliError$2(message) {
|
|
18463
18960
|
return new Errors.CLIError(message, { exit: 1 });
|
|
18464
18961
|
}
|
|
18465
18962
|
function unwrapData$1(value) {
|
|
18466
18963
|
return value?.data ?? null;
|
|
18467
18964
|
}
|
|
18468
|
-
function
|
|
18469
|
-
return
|
|
18965
|
+
function isRecord(value) {
|
|
18966
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18470
18967
|
}
|
|
18471
|
-
|
|
18472
|
-
|
|
18473
|
-
|
|
18474
|
-
|
|
18475
|
-
|
|
18968
|
+
function normalizeEmail(email) {
|
|
18969
|
+
return email.trim().toLowerCase();
|
|
18970
|
+
}
|
|
18971
|
+
function pendingSignupFromJson(value) {
|
|
18972
|
+
if (!isRecord(value)) return null;
|
|
18973
|
+
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;
|
|
18974
|
+
return {
|
|
18975
|
+
api_base_url_1: value.api_base_url_1,
|
|
18976
|
+
created_at: value.created_at,
|
|
18977
|
+
email: value.email,
|
|
18978
|
+
expires_at: value.expires_at,
|
|
18979
|
+
expires_in: value.expires_in,
|
|
18980
|
+
resend_after: value.resend_after,
|
|
18981
|
+
signup_token: value.signup_token,
|
|
18982
|
+
verification_code_length: value.verification_code_length
|
|
18476
18983
|
};
|
|
18477
|
-
|
|
18984
|
+
}
|
|
18985
|
+
function pendingSignupPath(configDir) {
|
|
18986
|
+
return join(configDir, PENDING_SIGNUP_FILE);
|
|
18987
|
+
}
|
|
18988
|
+
function deletePendingAgentSignup(configDir) {
|
|
18989
|
+
rmSync(pendingSignupPath(configDir), { force: true });
|
|
18990
|
+
}
|
|
18991
|
+
function pendingSignupFromStart(start, apiBaseUrl1) {
|
|
18992
|
+
return {
|
|
18993
|
+
...start,
|
|
18994
|
+
api_base_url_1: apiBaseUrl1,
|
|
18995
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18996
|
+
expires_at: new Date(Date.now() + start.expires_in * 1e3).toISOString()
|
|
18997
|
+
};
|
|
18998
|
+
}
|
|
18999
|
+
function savePendingAgentSignup(configDir, start, apiBaseUrl1) {
|
|
19000
|
+
mkdirSync(configDir, {
|
|
19001
|
+
mode: 448,
|
|
19002
|
+
recursive: true
|
|
19003
|
+
});
|
|
19004
|
+
const pending = pendingSignupFromStart(start, apiBaseUrl1);
|
|
19005
|
+
const path = pendingSignupPath(configDir);
|
|
19006
|
+
const tempPath = join(configDir, `${PENDING_SIGNUP_FILE}.${process$1.pid}.${randomUUID()}.tmp`);
|
|
18478
19007
|
try {
|
|
18479
|
-
|
|
19008
|
+
writeFileSync(tempPath, `${JSON.stringify(pending, null, 2)}\n`, { mode: 384 });
|
|
19009
|
+
chmodSync(tempPath, 384);
|
|
19010
|
+
renameSync(tempPath, path);
|
|
19011
|
+
chmodSync(path, 384);
|
|
19012
|
+
return pending;
|
|
18480
19013
|
} 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;
|
|
19014
|
+
rmSync(tempPath, { force: true });
|
|
19015
|
+
throw error;
|
|
18486
19016
|
}
|
|
18487
|
-
|
|
18488
|
-
|
|
19017
|
+
}
|
|
19018
|
+
function loadPendingAgentSignup(configDir, apiBaseUrl1) {
|
|
19019
|
+
const path = pendingSignupPath(configDir);
|
|
19020
|
+
let contents;
|
|
18489
19021
|
try {
|
|
18490
|
-
|
|
18491
|
-
apiBaseUrl1: params.flags["api-base-url-1"],
|
|
18492
|
-
configDir: params.configDir,
|
|
18493
|
-
credentialsLockHeld: true
|
|
18494
|
-
});
|
|
19022
|
+
contents = readFileSync(path, "utf8");
|
|
18495
19023
|
} catch (error) {
|
|
18496
|
-
if (
|
|
18497
|
-
process.stderr.write("Logged out (OAuth session was already expired or revoked on the server).\n");
|
|
18498
|
-
return;
|
|
18499
|
-
}
|
|
19024
|
+
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
18500
19025
|
throw error;
|
|
18501
19026
|
}
|
|
18502
|
-
|
|
18503
|
-
|
|
18504
|
-
|
|
18505
|
-
|
|
18506
|
-
|
|
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.");
|
|
19027
|
+
let pending;
|
|
19028
|
+
try {
|
|
19029
|
+
pending = pendingSignupFromJson(JSON.parse(contents));
|
|
19030
|
+
} catch {
|
|
19031
|
+
pending = null;
|
|
18520
19032
|
}
|
|
18521
|
-
|
|
18522
|
-
|
|
18523
|
-
|
|
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
|
-
}
|
|
19033
|
+
if (!pending) {
|
|
19034
|
+
deletePendingAgentSignup(configDir);
|
|
19035
|
+
return null;
|
|
18551
19036
|
}
|
|
18552
|
-
|
|
18553
|
-
|
|
18554
|
-
|
|
18555
|
-
|
|
18556
|
-
|
|
19037
|
+
if (pending.api_base_url_1 !== apiBaseUrl1) return null;
|
|
19038
|
+
if (new Date(pending.expires_at).getTime() <= Date.now()) {
|
|
19039
|
+
deletePendingAgentSignup(configDir);
|
|
19040
|
+
return null;
|
|
19041
|
+
}
|
|
19042
|
+
return {
|
|
19043
|
+
...pending,
|
|
19044
|
+
expires_in: Math.max(0, Math.ceil((new Date(pending.expires_at).getTime() - Date.now()) / 1e3))
|
|
19045
|
+
};
|
|
18557
19046
|
}
|
|
18558
|
-
function
|
|
18559
|
-
|
|
18560
|
-
|
|
19047
|
+
function requirePendingSignupForEmail(params) {
|
|
19048
|
+
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
19049
|
+
const pending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
|
|
19050
|
+
if (!pending) throw cliError$2(`No pending ${copy.actionNoun} for ${params.email}. Run \`primitive ${copy.startCommand(params.email)}\` first.`);
|
|
19051
|
+
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.`);
|
|
19052
|
+
return pending;
|
|
18561
19053
|
}
|
|
18562
|
-
function
|
|
18563
|
-
|
|
19054
|
+
function retryAfterSeconds(result) {
|
|
19055
|
+
const raw = result.response?.headers.get("retry-after");
|
|
19056
|
+
if (!raw) return null;
|
|
19057
|
+
const parsed = Number.parseInt(raw, 10);
|
|
19058
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
18564
19059
|
}
|
|
18565
|
-
function
|
|
18566
|
-
|
|
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
|
-
}
|
|
19060
|
+
function normalizeAnswer(value) {
|
|
19061
|
+
return value.trim();
|
|
18577
19062
|
}
|
|
18578
|
-
function
|
|
19063
|
+
async function promptLine(question) {
|
|
19064
|
+
const rl = createInterface({
|
|
19065
|
+
input: process$1.stdin,
|
|
19066
|
+
output: process$1.stderr
|
|
19067
|
+
});
|
|
18579
19068
|
try {
|
|
18580
|
-
return
|
|
18581
|
-
|
|
18582
|
-
|
|
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;
|
|
18975
|
-
throw error;
|
|
18976
|
-
}
|
|
18977
|
-
let pending;
|
|
18978
|
-
try {
|
|
18979
|
-
pending = pendingSignupFromJson(JSON.parse(contents));
|
|
18980
|
-
} catch {
|
|
18981
|
-
pending = null;
|
|
18982
|
-
}
|
|
18983
|
-
if (!pending) {
|
|
18984
|
-
deletePendingAgentSignup(configDir);
|
|
18985
|
-
return null;
|
|
18986
|
-
}
|
|
18987
|
-
if (pending.api_base_url_1 !== apiBaseUrl1) return null;
|
|
18988
|
-
if (new Date(pending.expires_at).getTime() <= Date.now()) {
|
|
18989
|
-
deletePendingAgentSignup(configDir);
|
|
18990
|
-
return null;
|
|
18991
|
-
}
|
|
18992
|
-
return {
|
|
18993
|
-
...pending,
|
|
18994
|
-
expires_in: Math.max(0, Math.ceil((new Date(pending.expires_at).getTime() - Date.now()) / 1e3))
|
|
18995
|
-
};
|
|
18996
|
-
}
|
|
18997
|
-
function requirePendingSignupForEmail(params) {
|
|
18998
|
-
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
18999
|
-
const pending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
|
|
19000
|
-
if (!pending) throw cliError$1(`No pending ${copy.actionNoun} for ${params.email}. Run \`primitive ${copy.startCommand(params.email)}\` first.`);
|
|
19001
|
-
if (normalizeEmail(pending.email) !== normalizeEmail(params.email)) throw cliError$1(`Pending ${copy.actionNoun} is for ${pending.email}, not ${params.email}. Run \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
|
|
19002
|
-
return pending;
|
|
19003
|
-
}
|
|
19004
|
-
function retryAfterSeconds(result) {
|
|
19005
|
-
const raw = result.response?.headers.get("retry-after");
|
|
19006
|
-
if (!raw) return null;
|
|
19007
|
-
const parsed = Number.parseInt(raw, 10);
|
|
19008
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
19009
|
-
}
|
|
19010
|
-
function normalizeAnswer(value) {
|
|
19011
|
-
return value.trim();
|
|
19012
|
-
}
|
|
19013
|
-
async function promptLine(question) {
|
|
19014
|
-
const rl = createInterface({
|
|
19015
|
-
input: process$1.stdin,
|
|
19016
|
-
output: process$1.stderr
|
|
19017
|
-
});
|
|
19018
|
-
try {
|
|
19019
|
-
return normalizeAnswer(await rl.question(question));
|
|
19020
|
-
} finally {
|
|
19021
|
-
rl.close();
|
|
19069
|
+
return normalizeAnswer(await rl.question(question));
|
|
19070
|
+
} finally {
|
|
19071
|
+
rl.close();
|
|
19022
19072
|
}
|
|
19023
19073
|
}
|
|
19024
19074
|
function formatSignupSeconds(seconds) {
|
|
@@ -19039,7 +19089,7 @@ async function confirmTerms() {
|
|
|
19039
19089
|
process$1.stderr.write(" https://primitive.dev/terms\n");
|
|
19040
19090
|
process$1.stderr.write(" https://primitive.dev/privacy\n");
|
|
19041
19091
|
const answer = (await promptRequired("Type 'yes' to continue: ")).toLowerCase();
|
|
19042
|
-
if (answer !== "yes" && answer !== "y") throw cliError$
|
|
19092
|
+
if (answer !== "yes" && answer !== "y") throw cliError$2("You must accept the terms to create an account.");
|
|
19043
19093
|
}
|
|
19044
19094
|
async function checkExistingCredentials(params) {
|
|
19045
19095
|
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
@@ -19070,9 +19120,9 @@ async function checkExistingCredentials(params) {
|
|
|
19070
19120
|
}
|
|
19071
19121
|
if (existingStatus.status === "blocked") {
|
|
19072
19122
|
writeErrorWithHints(existingStatus.payload);
|
|
19073
|
-
throw cliError$
|
|
19123
|
+
throw cliError$2(existingStatus.message);
|
|
19074
19124
|
}
|
|
19075
|
-
throw cliError$
|
|
19125
|
+
throw cliError$2(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before ${copy.actionGerund}.`);
|
|
19076
19126
|
}
|
|
19077
19127
|
function saveSignupCredentials(params) {
|
|
19078
19128
|
saveCliCredentials(params.configDir, {
|
|
@@ -19106,7 +19156,7 @@ async function startSignup(params) {
|
|
|
19106
19156
|
started: false
|
|
19107
19157
|
};
|
|
19108
19158
|
}
|
|
19109
|
-
throw cliError$
|
|
19159
|
+
throw cliError$2(`Pending ${copy.actionNoun} is for ${existingPending.email}. Run \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
|
|
19110
19160
|
}
|
|
19111
19161
|
if (params.flags.force) deletePendingAgentSignup(params.configDir);
|
|
19112
19162
|
const promptRequiredFn = params.deps.promptRequired ?? promptRequired;
|
|
@@ -19126,10 +19176,10 @@ async function startSignup(params) {
|
|
|
19126
19176
|
});
|
|
19127
19177
|
if (started.error) {
|
|
19128
19178
|
writeErrorWithHints(extractErrorPayload(started.error));
|
|
19129
|
-
throw cliError$
|
|
19179
|
+
throw cliError$2("Could not start Primitive agent signup.");
|
|
19130
19180
|
}
|
|
19131
|
-
const startResult = unwrapData(started.data);
|
|
19132
|
-
if (!startResult) throw cliError$
|
|
19181
|
+
const startResult = unwrapData$1(started.data);
|
|
19182
|
+
if (!startResult) throw cliError$2("Primitive API returned an empty agent signup response.");
|
|
19133
19183
|
return {
|
|
19134
19184
|
pending: savePendingAgentSignup(params.configDir, startResult, params.apiBaseUrl1),
|
|
19135
19185
|
started: true
|
|
@@ -19142,7 +19192,7 @@ async function resendVerificationCode(params) {
|
|
|
19142
19192
|
responseStyle: "fields"
|
|
19143
19193
|
});
|
|
19144
19194
|
if (resent.data) {
|
|
19145
|
-
const resend = unwrapData(resent.data);
|
|
19195
|
+
const resend = unwrapData$1(resent.data);
|
|
19146
19196
|
const next = resend ? {
|
|
19147
19197
|
email: resend.email,
|
|
19148
19198
|
expires_in: resend.expires_in,
|
|
@@ -19167,7 +19217,7 @@ async function resendVerificationCode(params) {
|
|
|
19167
19217
|
}
|
|
19168
19218
|
if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(params.configDir);
|
|
19169
19219
|
writeErrorWithHints(payload);
|
|
19170
|
-
throw cliError$
|
|
19220
|
+
throw cliError$2("Could not resend Primitive agent signup verification email.");
|
|
19171
19221
|
}
|
|
19172
19222
|
async function runSignupStartWithCredentialLock(params) {
|
|
19173
19223
|
const { configDir, flags } = params;
|
|
@@ -19227,8 +19277,8 @@ async function runSignupConfirmWithCredentialLock(params) {
|
|
|
19227
19277
|
responseStyle: "fields"
|
|
19228
19278
|
});
|
|
19229
19279
|
if (verified.data) {
|
|
19230
|
-
const signup = unwrapData(verified.data);
|
|
19231
|
-
if (!signup) throw cliError$
|
|
19280
|
+
const signup = unwrapData$1(verified.data);
|
|
19281
|
+
if (!signup) throw cliError$2("Primitive API returned an empty agent signup verification response.");
|
|
19232
19282
|
saveSignupCredentials({
|
|
19233
19283
|
apiBaseUrl1,
|
|
19234
19284
|
configDir,
|
|
@@ -19242,10 +19292,10 @@ async function runSignupConfirmWithCredentialLock(params) {
|
|
|
19242
19292
|
}
|
|
19243
19293
|
const payload = extractErrorPayload(verified.error);
|
|
19244
19294
|
const code = extractErrorCode(payload);
|
|
19245
|
-
if (code === INVALID_VERIFICATION_CODE) throw cliError$
|
|
19295
|
+
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
19296
|
if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(configDir);
|
|
19247
19297
|
writeErrorWithHints(payload);
|
|
19248
|
-
throw cliError$
|
|
19298
|
+
throw cliError$2("Primitive agent signup failed while verifying the account.");
|
|
19249
19299
|
}
|
|
19250
19300
|
async function runSignupResendWithCredentialLock(params) {
|
|
19251
19301
|
const deps = params.deps ?? {};
|
|
@@ -19332,40 +19382,268 @@ async function runSignupInteractiveWithCredentialLock(params) {
|
|
|
19332
19382
|
}
|
|
19333
19383
|
}
|
|
19334
19384
|
}
|
|
19335
|
-
function commonStartFlags() {
|
|
19336
|
-
return {
|
|
19337
|
-
"accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
|
|
19385
|
+
function commonStartFlags() {
|
|
19386
|
+
return {
|
|
19387
|
+
"accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
|
|
19388
|
+
"api-base-url-1": Flags.string({
|
|
19389
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19390
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19391
|
+
hidden: true
|
|
19392
|
+
}),
|
|
19393
|
+
"device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
|
|
19394
|
+
force: Flags.boolean({
|
|
19395
|
+
char: "f",
|
|
19396
|
+
description: "Replace saved credentials or pending signup state when needed"
|
|
19397
|
+
}),
|
|
19398
|
+
"signup-code": Flags.string({
|
|
19399
|
+
description: "Signup code required to create an account",
|
|
19400
|
+
env: "PRIMITIVE_SIGNUP_CODE"
|
|
19401
|
+
})
|
|
19402
|
+
};
|
|
19403
|
+
}
|
|
19404
|
+
var SignupCommand = class SignupCommand extends Command {
|
|
19405
|
+
static args = { email: Args.string({
|
|
19406
|
+
description: "Email address to sign up",
|
|
19407
|
+
required: false
|
|
19408
|
+
}) };
|
|
19409
|
+
static description = "Start a Primitive account signup, send an email verification code, and save a pending signup token locally.";
|
|
19410
|
+
static summary = "Start account signup";
|
|
19411
|
+
static examples = [
|
|
19412
|
+
"<%= config.bin %> signup user@example.com",
|
|
19413
|
+
"<%= config.bin %> signup user@example.com --signup-code invite-code --accept-terms",
|
|
19414
|
+
"<%= config.bin %> signup confirm user@example.com 123456"
|
|
19415
|
+
];
|
|
19416
|
+
static flags = commonStartFlags();
|
|
19417
|
+
async run() {
|
|
19418
|
+
const { args, flags } = await this.parse(SignupCommand);
|
|
19419
|
+
let releaseCredentialsLock;
|
|
19420
|
+
try {
|
|
19421
|
+
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
19422
|
+
} catch (error) {
|
|
19423
|
+
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
19424
|
+
}
|
|
19425
|
+
try {
|
|
19426
|
+
await runSignupStartWithCredentialLock({
|
|
19427
|
+
configDir: this.config.configDir,
|
|
19428
|
+
email: args.email,
|
|
19429
|
+
flags
|
|
19430
|
+
});
|
|
19431
|
+
} finally {
|
|
19432
|
+
releaseCredentialsLock();
|
|
19433
|
+
}
|
|
19434
|
+
}
|
|
19435
|
+
};
|
|
19436
|
+
var SignupConfirmCommand = class SignupConfirmCommand extends Command {
|
|
19437
|
+
static args = {
|
|
19438
|
+
email: Args.string({
|
|
19439
|
+
description: "Email address used to start signup",
|
|
19440
|
+
required: true
|
|
19441
|
+
}),
|
|
19442
|
+
code: Args.string({
|
|
19443
|
+
description: "Verification code from the signup email",
|
|
19444
|
+
required: true
|
|
19445
|
+
})
|
|
19446
|
+
};
|
|
19447
|
+
static description = "Confirm a pending Primitive signup, create an OAuth session, and save CLI credentials locally.";
|
|
19448
|
+
static summary = "Confirm account signup";
|
|
19449
|
+
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"];
|
|
19450
|
+
static flags = {
|
|
19451
|
+
"api-base-url-1": Flags.string({
|
|
19452
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19453
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19454
|
+
hidden: true
|
|
19455
|
+
}),
|
|
19456
|
+
force: Flags.boolean({
|
|
19457
|
+
char: "f",
|
|
19458
|
+
description: "Replace saved credentials after verification"
|
|
19459
|
+
}),
|
|
19460
|
+
"org-id": Flags.string({ description: "Workspace id to target when the email belongs to multiple workspaces" })
|
|
19461
|
+
};
|
|
19462
|
+
async run() {
|
|
19463
|
+
const { args, flags } = await this.parse(SignupConfirmCommand);
|
|
19464
|
+
let releaseCredentialsLock;
|
|
19465
|
+
try {
|
|
19466
|
+
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
19467
|
+
} catch (error) {
|
|
19468
|
+
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
19469
|
+
}
|
|
19470
|
+
try {
|
|
19471
|
+
await runSignupConfirmWithCredentialLock({
|
|
19472
|
+
code: args.code,
|
|
19473
|
+
configDir: this.config.configDir,
|
|
19474
|
+
email: args.email,
|
|
19475
|
+
flags
|
|
19476
|
+
});
|
|
19477
|
+
} finally {
|
|
19478
|
+
releaseCredentialsLock();
|
|
19479
|
+
}
|
|
19480
|
+
}
|
|
19481
|
+
};
|
|
19482
|
+
var SignupResendCommand = class SignupResendCommand extends Command {
|
|
19483
|
+
static args = { email: Args.string({
|
|
19484
|
+
description: "Email address used to start signup",
|
|
19485
|
+
required: true
|
|
19486
|
+
}) };
|
|
19487
|
+
static description = "Resend the verification code for a pending signup.";
|
|
19488
|
+
static summary = "Resend signup verification code";
|
|
19489
|
+
static examples = ["<%= config.bin %> signup resend user@example.com"];
|
|
19490
|
+
static flags = { "api-base-url-1": Flags.string({
|
|
19491
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19492
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19493
|
+
hidden: true
|
|
19494
|
+
}) };
|
|
19495
|
+
async run() {
|
|
19496
|
+
const { args, flags } = await this.parse(SignupResendCommand);
|
|
19497
|
+
let releaseCredentialsLock;
|
|
19498
|
+
try {
|
|
19499
|
+
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
19500
|
+
} catch (error) {
|
|
19501
|
+
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
19502
|
+
}
|
|
19503
|
+
try {
|
|
19504
|
+
await runSignupResendWithCredentialLock({
|
|
19505
|
+
configDir: this.config.configDir,
|
|
19506
|
+
email: args.email,
|
|
19507
|
+
flags
|
|
19508
|
+
});
|
|
19509
|
+
} finally {
|
|
19510
|
+
releaseCredentialsLock();
|
|
19511
|
+
}
|
|
19512
|
+
}
|
|
19513
|
+
};
|
|
19514
|
+
var SignupInteractiveCommand = class SignupInteractiveCommand extends Command {
|
|
19515
|
+
static description = "Run the full signup flow in one interactive terminal session.";
|
|
19516
|
+
static summary = "Run interactive account signup";
|
|
19517
|
+
static examples = ["<%= config.bin %> signup interactive"];
|
|
19518
|
+
static flags = commonStartFlags();
|
|
19519
|
+
async run() {
|
|
19520
|
+
const { flags } = await this.parse(SignupInteractiveCommand);
|
|
19521
|
+
let releaseCredentialsLock;
|
|
19522
|
+
try {
|
|
19523
|
+
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
19524
|
+
} catch (error) {
|
|
19525
|
+
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
19526
|
+
}
|
|
19527
|
+
try {
|
|
19528
|
+
await runSignupInteractiveWithCredentialLock({
|
|
19529
|
+
configDir: this.config.configDir,
|
|
19530
|
+
flags
|
|
19531
|
+
});
|
|
19532
|
+
} finally {
|
|
19533
|
+
releaseCredentialsLock();
|
|
19534
|
+
}
|
|
19535
|
+
}
|
|
19536
|
+
};
|
|
19537
|
+
//#endregion
|
|
19538
|
+
//#region src/oclif/commands/logout.ts
|
|
19539
|
+
function cliError$1(message) {
|
|
19540
|
+
return new Errors.CLIError(message, { exit: 1 });
|
|
19541
|
+
}
|
|
19542
|
+
function unwrapData(value) {
|
|
19543
|
+
return value?.data ?? null;
|
|
19544
|
+
}
|
|
19545
|
+
function isSavedOAuthSessionExpiredError(error) {
|
|
19546
|
+
return error instanceof Error && error.message === "Saved Primitive CLI OAuth session expired or was revoked. Run `primitive signin` to authenticate again.";
|
|
19547
|
+
}
|
|
19548
|
+
async function runLogoutWithCredentialLock(params) {
|
|
19549
|
+
const deps = {
|
|
19550
|
+
cliLogout,
|
|
19551
|
+
createAuthenticatedCliApiClient,
|
|
19552
|
+
...params.deps
|
|
19553
|
+
};
|
|
19554
|
+
let credentials;
|
|
19555
|
+
try {
|
|
19556
|
+
credentials = loadCliCredentials(params.configDir);
|
|
19557
|
+
} catch (error) {
|
|
19558
|
+
deleteCliCredentials(params.configDir);
|
|
19559
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
19560
|
+
process.stderr.write(`Removed unreadable Primitive CLI credentials. Backing OAuth grant was not revoked: ${detail}\n`);
|
|
19561
|
+
process.exitCode = 1;
|
|
19562
|
+
return;
|
|
19563
|
+
}
|
|
19564
|
+
if (!credentials) throw cliError$1("Not logged in. Run `primitive signin` to create saved CLI credentials.");
|
|
19565
|
+
let authenticated;
|
|
19566
|
+
try {
|
|
19567
|
+
authenticated = await deps.createAuthenticatedCliApiClient({
|
|
19568
|
+
apiBaseUrl1: params.flags["api-base-url-1"],
|
|
19569
|
+
configDir: params.configDir,
|
|
19570
|
+
credentialsLockHeld: true
|
|
19571
|
+
});
|
|
19572
|
+
} catch (error) {
|
|
19573
|
+
if (isSavedOAuthSessionExpiredError(error) && loadCliCredentials(params.configDir) === null) {
|
|
19574
|
+
process.stderr.write("Logged out (OAuth session was already expired or revoked on the server).\n");
|
|
19575
|
+
return;
|
|
19576
|
+
}
|
|
19577
|
+
throw error;
|
|
19578
|
+
}
|
|
19579
|
+
const freshCredentials = authenticated.auth.credentials ?? credentials;
|
|
19580
|
+
const result = await deps.cliLogout({
|
|
19581
|
+
body: { key_id: freshCredentials.oauth_grant_id },
|
|
19582
|
+
client: authenticated.apiClient.client,
|
|
19583
|
+
responseStyle: "fields"
|
|
19584
|
+
});
|
|
19585
|
+
if (result.error) {
|
|
19586
|
+
const payload = extractErrorPayload(result.error);
|
|
19587
|
+
const code = extractErrorCode(payload);
|
|
19588
|
+
if (code === API_ERROR_CODES.unauthorized || code === API_ERROR_CODES.notFound) {
|
|
19589
|
+
deleteCliCredentials(params.configDir);
|
|
19590
|
+
writeErrorWithHints(payload);
|
|
19591
|
+
process.stderr.write("Removed saved Primitive CLI credentials because the backing OAuth grant is already unavailable.\n");
|
|
19592
|
+
process.exitCode = 1;
|
|
19593
|
+
return;
|
|
19594
|
+
}
|
|
19595
|
+
writeErrorWithHints(payload);
|
|
19596
|
+
throw cliError$1("Could not revoke the saved Primitive CLI OAuth grant.");
|
|
19597
|
+
}
|
|
19598
|
+
const logout = unwrapData(result.data);
|
|
19599
|
+
deleteCliCredentials(params.configDir);
|
|
19600
|
+
const grantId = logout?.oauth_grant_id ?? freshCredentials.oauth_grant_id;
|
|
19601
|
+
process.stderr.write(`Logged out and revoked OAuth grant ${grantId}.\n`);
|
|
19602
|
+
}
|
|
19603
|
+
function runForceLogout(params) {
|
|
19604
|
+
const localCredentialsPath = credentialsPath(params.configDir);
|
|
19605
|
+
const pendingPath = pendingSignupPath(params.configDir);
|
|
19606
|
+
const lockPath = credentialsLockPath(params.configDir);
|
|
19607
|
+
const removed = [
|
|
19608
|
+
existsSync(localCredentialsPath) ? "local Primitive CLI credentials" : null,
|
|
19609
|
+
existsSync(pendingPath) ? "pending email-code auth state" : null,
|
|
19610
|
+
existsSync(lockPath) ? "credential lock" : null
|
|
19611
|
+
].filter((value) => value !== null);
|
|
19612
|
+
deleteCliCredentials(params.configDir);
|
|
19613
|
+
deletePendingAgentSignup(params.configDir);
|
|
19614
|
+
deleteCliCredentialsLock(params.configDir);
|
|
19615
|
+
if (removed.length === 0) {
|
|
19616
|
+
process.stderr.write("No local Primitive CLI auth state was present. Backing OAuth grant was not revoked.\n");
|
|
19617
|
+
return;
|
|
19618
|
+
}
|
|
19619
|
+
process.stderr.write(`Removed ${formatList(removed)}. Backing OAuth grant was not revoked.\n`);
|
|
19620
|
+
}
|
|
19621
|
+
function formatList(values) {
|
|
19622
|
+
if (values.length <= 1) return values[0] ?? "";
|
|
19623
|
+
if (values.length === 2) return `${values[0]} and ${values[1]}`;
|
|
19624
|
+
return `${values.slice(0, -1).join(", ")}, and ${values.at(-1)}`;
|
|
19625
|
+
}
|
|
19626
|
+
var LogoutCommand = class LogoutCommand extends Command {
|
|
19627
|
+
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.";
|
|
19628
|
+
static summary = "Log out and revoke the saved CLI OAuth grant";
|
|
19629
|
+
static examples = ["<%= config.bin %> logout", "<%= config.bin %> logout --force"];
|
|
19630
|
+
static flags = {
|
|
19338
19631
|
"api-base-url-1": Flags.string({
|
|
19339
19632
|
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19340
19633
|
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19341
19634
|
hidden: true
|
|
19342
19635
|
}),
|
|
19343
|
-
"device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
|
|
19344
19636
|
force: Flags.boolean({
|
|
19345
19637
|
char: "f",
|
|
19346
|
-
description: "
|
|
19347
|
-
}),
|
|
19348
|
-
"signup-code": Flags.string({
|
|
19349
|
-
description: "Signup code required to create an account",
|
|
19350
|
-
env: "PRIMITIVE_SIGNUP_CODE"
|
|
19638
|
+
description: "Remove local CLI credentials, pending email-code auth state, and any credential lock without revoking the server OAuth grant"
|
|
19351
19639
|
})
|
|
19352
19640
|
};
|
|
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
19641
|
async run() {
|
|
19368
|
-
const {
|
|
19642
|
+
const { flags } = await this.parse(LogoutCommand);
|
|
19643
|
+
if (flags.force) {
|
|
19644
|
+
runForceLogout({ configDir: this.config.configDir });
|
|
19645
|
+
return;
|
|
19646
|
+
}
|
|
19369
19647
|
let releaseCredentialsLock;
|
|
19370
19648
|
try {
|
|
19371
19649
|
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
@@ -19373,9 +19651,8 @@ var SignupCommand = class SignupCommand extends Command {
|
|
|
19373
19651
|
throw cliError$1(error instanceof Error ? error.message : String(error));
|
|
19374
19652
|
}
|
|
19375
19653
|
try {
|
|
19376
|
-
await
|
|
19654
|
+
await runLogoutWithCredentialLock({
|
|
19377
19655
|
configDir: this.config.configDir,
|
|
19378
|
-
email: args.email,
|
|
19379
19656
|
flags
|
|
19380
19657
|
});
|
|
19381
19658
|
} finally {
|
|
@@ -19383,105 +19660,344 @@ var SignupCommand = class SignupCommand extends Command {
|
|
|
19383
19660
|
}
|
|
19384
19661
|
}
|
|
19385
19662
|
};
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
|
|
19663
|
+
//#endregion
|
|
19664
|
+
//#region src/oclif/message-body-sources.ts
|
|
19665
|
+
function defaultReadFile(path) {
|
|
19666
|
+
return readFileSync(path, "utf8");
|
|
19667
|
+
}
|
|
19668
|
+
function defaultReadStdin() {
|
|
19669
|
+
if (process.stdin.isTTY) throw new Error("stdin is a TTY; pipe a value into this command or pass a file/string source instead.");
|
|
19670
|
+
return readFileSync(0, "utf8");
|
|
19671
|
+
}
|
|
19672
|
+
function selectedSources(sources) {
|
|
19673
|
+
return sources.filter(([, selected]) => selected).map(([label]) => label);
|
|
19674
|
+
}
|
|
19675
|
+
function readTextFile(path, label, readFile) {
|
|
19676
|
+
try {
|
|
19677
|
+
return {
|
|
19678
|
+
content: readFile(path),
|
|
19679
|
+
kind: "ok"
|
|
19680
|
+
};
|
|
19681
|
+
} catch (error) {
|
|
19682
|
+
return {
|
|
19683
|
+
kind: "error",
|
|
19684
|
+
message: `Could not read ${label} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
19685
|
+
};
|
|
19686
|
+
}
|
|
19687
|
+
}
|
|
19688
|
+
function readTextStdin(label, readStdin) {
|
|
19689
|
+
try {
|
|
19690
|
+
return {
|
|
19691
|
+
content: readStdin(),
|
|
19692
|
+
kind: "ok"
|
|
19693
|
+
};
|
|
19694
|
+
} catch (error) {
|
|
19695
|
+
return {
|
|
19696
|
+
kind: "error",
|
|
19697
|
+
message: `Could not read ${label}: ${error instanceof Error ? error.message : String(error)}`
|
|
19698
|
+
};
|
|
19699
|
+
}
|
|
19700
|
+
}
|
|
19701
|
+
function resolveMessageBodies(input) {
|
|
19702
|
+
const bodySources = selectedSources([
|
|
19703
|
+
["--body", input.body !== void 0],
|
|
19704
|
+
["--body-file", input.bodyFile !== void 0],
|
|
19705
|
+
["--body-stdin", input.bodyStdin === true]
|
|
19706
|
+
]);
|
|
19707
|
+
if (bodySources.length > 1) return {
|
|
19708
|
+
kind: "error",
|
|
19709
|
+
message: `Pass only one plain-text body source (got ${bodySources.join(", ")}).`
|
|
19710
|
+
};
|
|
19711
|
+
const htmlSources = selectedSources([
|
|
19712
|
+
["--html", input.html !== void 0],
|
|
19713
|
+
["--html-file", input.htmlFile !== void 0],
|
|
19714
|
+
["--html-stdin", input.htmlStdin === true]
|
|
19715
|
+
]);
|
|
19716
|
+
if (htmlSources.length > 1) return {
|
|
19717
|
+
kind: "error",
|
|
19718
|
+
message: `Pass only one HTML body source (got ${htmlSources.join(", ")}).`
|
|
19719
|
+
};
|
|
19720
|
+
const stdinSources = selectedSources([["--body-stdin", input.bodyStdin === true], ["--html-stdin", input.htmlStdin === true]]);
|
|
19721
|
+
if (stdinSources.length > 1) return {
|
|
19722
|
+
kind: "error",
|
|
19723
|
+
message: `Stdin can only be consumed once (got ${stdinSources.join(", ")}).`
|
|
19724
|
+
};
|
|
19725
|
+
if (bodySources.length === 0 && htmlSources.length === 0) return {
|
|
19726
|
+
kind: "error",
|
|
19727
|
+
message: "Either a plain-text body source or an HTML body source is required."
|
|
19728
|
+
};
|
|
19729
|
+
const readFile = input.readFile ?? defaultReadFile;
|
|
19730
|
+
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
19731
|
+
let body = input.body;
|
|
19732
|
+
let html = input.html;
|
|
19733
|
+
if (input.bodyFile !== void 0) {
|
|
19734
|
+
const result = readTextFile(input.bodyFile, "--body-file", readFile);
|
|
19735
|
+
if (result.kind === "error") return result;
|
|
19736
|
+
body = result.content;
|
|
19737
|
+
}
|
|
19738
|
+
if (input.bodyStdin === true) {
|
|
19739
|
+
const result = readTextStdin("--body-stdin", readStdin);
|
|
19740
|
+
if (result.kind === "error") return result;
|
|
19741
|
+
body = result.content;
|
|
19742
|
+
}
|
|
19743
|
+
if (input.htmlFile !== void 0) {
|
|
19744
|
+
const result = readTextFile(input.htmlFile, "--html-file", readFile);
|
|
19745
|
+
if (result.kind === "error") return result;
|
|
19746
|
+
html = result.content;
|
|
19747
|
+
}
|
|
19748
|
+
if (input.htmlStdin === true) {
|
|
19749
|
+
const result = readTextStdin("--html-stdin", readStdin);
|
|
19750
|
+
if (result.kind === "error") return result;
|
|
19751
|
+
html = result.content;
|
|
19752
|
+
}
|
|
19753
|
+
if (!body && !html) return {
|
|
19754
|
+
kind: "error",
|
|
19755
|
+
message: "Either a non-empty plain-text body or a non-empty HTML body is required."
|
|
19756
|
+
};
|
|
19757
|
+
return {
|
|
19758
|
+
...body !== void 0 ? { body } : {},
|
|
19759
|
+
...html !== void 0 ? { html } : {},
|
|
19760
|
+
kind: "ok"
|
|
19761
|
+
};
|
|
19762
|
+
}
|
|
19763
|
+
//#endregion
|
|
19764
|
+
//#region src/oclif/commands/reply.ts
|
|
19765
|
+
var ReplyCommand = class ReplyCommand extends Command {
|
|
19766
|
+
static description = `Reply to an inbound email.
|
|
19767
|
+
|
|
19768
|
+
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.`;
|
|
19769
|
+
static summary = "Reply to an inbound email";
|
|
19770
|
+
static examples = [
|
|
19771
|
+
"<%= config.bin %> reply --id <inbound-email-id> --body 'Thanks, got it.'",
|
|
19772
|
+
"<%= config.bin %> reply --id <inbound-email-id> --body-file ./reply.txt",
|
|
19773
|
+
"<%= config.bin %> reply --id <inbound-email-id> --html '<p>Thanks, got it.</p>' --wait",
|
|
19774
|
+
"<%= config.bin %> reply --id <inbound-email-id> --from 'Support <support@example.com>' --body 'Thanks!'"
|
|
19775
|
+
];
|
|
19776
|
+
static flags = {
|
|
19777
|
+
"api-key": Flags.string({
|
|
19778
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
19779
|
+
env: "PRIMITIVE_API_KEY"
|
|
19391
19780
|
}),
|
|
19392
|
-
|
|
19393
|
-
description: "
|
|
19781
|
+
"api-base-url-1": Flags.string({
|
|
19782
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19783
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19784
|
+
hidden: true
|
|
19785
|
+
}),
|
|
19786
|
+
"api-base-url-2": Flags.string({
|
|
19787
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
19788
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
19789
|
+
hidden: true
|
|
19790
|
+
}),
|
|
19791
|
+
id: Flags.string({
|
|
19792
|
+
description: "Inbound email id to reply to.",
|
|
19394
19793
|
required: true
|
|
19395
|
-
})
|
|
19794
|
+
}),
|
|
19795
|
+
body: Flags.string({ description: "Plain-text reply body. Either --body or --html (or both) is required." }),
|
|
19796
|
+
"body-file": Flags.string({ description: "Read the plain-text reply body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
|
|
19797
|
+
"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." }),
|
|
19798
|
+
html: Flags.string({ description: "HTML reply body. Either --body or --html (or both) is required." }),
|
|
19799
|
+
"html-file": Flags.string({ description: "Read the HTML reply body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
19800
|
+
"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." }),
|
|
19801
|
+
from: Flags.string({ description: "Optional From header override. Defaults to the inbound recipient." }),
|
|
19802
|
+
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." }),
|
|
19803
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
19396
19804
|
};
|
|
19397
|
-
|
|
19398
|
-
|
|
19399
|
-
|
|
19805
|
+
async run() {
|
|
19806
|
+
const { flags } = await this.parse(ReplyCommand);
|
|
19807
|
+
const bodies = resolveMessageBodies({
|
|
19808
|
+
body: flags.body,
|
|
19809
|
+
bodyFile: flags["body-file"],
|
|
19810
|
+
bodyStdin: flags["body-stdin"],
|
|
19811
|
+
html: flags.html,
|
|
19812
|
+
htmlFile: flags["html-file"],
|
|
19813
|
+
htmlStdin: flags["html-stdin"]
|
|
19814
|
+
});
|
|
19815
|
+
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
19816
|
+
await runWithTiming(flags.time, async () => {
|
|
19817
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
19818
|
+
apiKey: flags["api-key"],
|
|
19819
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
19820
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
19821
|
+
configDir: this.config.configDir
|
|
19822
|
+
});
|
|
19823
|
+
const result = await replyToEmail({
|
|
19824
|
+
body: {
|
|
19825
|
+
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
19826
|
+
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
19827
|
+
...flags.from !== void 0 ? { from: flags.from } : {},
|
|
19828
|
+
...flags.wait !== void 0 ? { wait: flags.wait } : {}
|
|
19829
|
+
},
|
|
19830
|
+
client: apiClient.client,
|
|
19831
|
+
path: { id: flags.id },
|
|
19832
|
+
responseStyle: "fields"
|
|
19833
|
+
});
|
|
19834
|
+
if (result.error) {
|
|
19835
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
19836
|
+
writeErrorWithHints(errorPayload);
|
|
19837
|
+
surfaceUnauthorizedHint({
|
|
19838
|
+
auth,
|
|
19839
|
+
baseUrlOverridden,
|
|
19840
|
+
configDir: this.config.configDir,
|
|
19841
|
+
payload: errorPayload
|
|
19842
|
+
});
|
|
19843
|
+
process.exitCode = 1;
|
|
19844
|
+
return;
|
|
19845
|
+
}
|
|
19846
|
+
const envelope = result.data;
|
|
19847
|
+
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
19848
|
+
process.stderr.write(chunk);
|
|
19849
|
+
} });
|
|
19850
|
+
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
19851
|
+
});
|
|
19852
|
+
}
|
|
19853
|
+
};
|
|
19854
|
+
//#endregion
|
|
19855
|
+
//#region src/oclif/attachments.ts
|
|
19856
|
+
function readAttachmentBytes(path, readFile) {
|
|
19857
|
+
try {
|
|
19858
|
+
return Buffer.from(readFile(path));
|
|
19859
|
+
} catch (error) {
|
|
19860
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
19861
|
+
throw new Errors.CLIError(`Could not read --attachment ${path}: ${detail}`, { exit: 1 });
|
|
19862
|
+
}
|
|
19863
|
+
}
|
|
19864
|
+
function hasControlCharacter(value) {
|
|
19865
|
+
return Array.from(value).some((character) => {
|
|
19866
|
+
const code = character.charCodeAt(0);
|
|
19867
|
+
return code <= 31 || code >= 127 && code <= 159;
|
|
19868
|
+
});
|
|
19869
|
+
}
|
|
19870
|
+
function validateAttachmentFilename(path, filename) {
|
|
19871
|
+
if (!filename) throw new Errors.CLIError(`Could not derive an attachment filename from ${path}. Pass a file path.`, { exit: 1 });
|
|
19872
|
+
if (hasControlCharacter(filename)) throw new Errors.CLIError(`Attachment filename ${filename} contains control characters.`, { exit: 1 });
|
|
19873
|
+
}
|
|
19874
|
+
function readAttachmentFiles(paths, readFile = readFileSync) {
|
|
19875
|
+
if (!paths || paths.length === 0) return void 0;
|
|
19876
|
+
return paths.map((path) => {
|
|
19877
|
+
const filename = basename(path);
|
|
19878
|
+
validateAttachmentFilename(path, filename);
|
|
19879
|
+
const bytes = readAttachmentBytes(path, readFile);
|
|
19880
|
+
if (bytes.length === 0) throw new Errors.CLIError(`Attachment file ${path} is empty. Attachments must contain at least one byte.`, { exit: 1 });
|
|
19881
|
+
return {
|
|
19882
|
+
content_base64: bytes.toString("base64"),
|
|
19883
|
+
filename
|
|
19884
|
+
};
|
|
19885
|
+
});
|
|
19886
|
+
}
|
|
19887
|
+
//#endregion
|
|
19888
|
+
//#region src/oclif/commands/send.ts
|
|
19889
|
+
var SendCommand = class SendCommand extends Command {
|
|
19890
|
+
static description = `Send an outbound email. Agent-grade shortcut for \`sending send\` with sensible defaults.
|
|
19891
|
+
|
|
19892
|
+
--from defaults to agent@<your-first-verified-outbound-domain> when omitted.
|
|
19893
|
+
--subject defaults to the first line of the body when omitted.
|
|
19894
|
+
--attachment attaches a file; repeat it to attach multiple files.
|
|
19895
|
+
|
|
19896
|
+
For the full flag set (custom message-id threading on the wire,
|
|
19897
|
+
references arrays, etc.), use \`primitive sending send\`.`;
|
|
19898
|
+
static summary = "Send an email (simplified, agent-friendly)";
|
|
19899
|
+
static examples = [
|
|
19900
|
+
"<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
|
|
19901
|
+
"<%= config.bin %> send --to alice@example.com --body-file ./message.txt",
|
|
19902
|
+
"<%= config.bin %> send --to alice@example.com --body 'See attached.' --attachment ./report.pdf",
|
|
19903
|
+
"<%= config.bin %> send --to alice@example.com --from support@yourcompany.com --subject 'Quick question' --body 'Are you free Thursday?'",
|
|
19904
|
+
"<%= config.bin %> send --to alice@example.com --html '<p>Hello!</p>'",
|
|
19905
|
+
"<%= config.bin %> send --to alice@example.com --body 'Confirmed' --wait",
|
|
19906
|
+
"<%= 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"
|
|
19907
|
+
];
|
|
19400
19908
|
static flags = {
|
|
19909
|
+
"api-key": Flags.string({
|
|
19910
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
19911
|
+
env: "PRIMITIVE_API_KEY"
|
|
19912
|
+
}),
|
|
19401
19913
|
"api-base-url-1": Flags.string({
|
|
19402
19914
|
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19403
19915
|
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19404
19916
|
hidden: true
|
|
19405
19917
|
}),
|
|
19406
|
-
|
|
19407
|
-
|
|
19408
|
-
|
|
19918
|
+
"api-base-url-2": Flags.string({
|
|
19919
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
19920
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
19921
|
+
hidden: true
|
|
19409
19922
|
}),
|
|
19410
|
-
|
|
19923
|
+
to: Flags.string({
|
|
19924
|
+
description: "Recipient address (e.g. alice@example.com).",
|
|
19925
|
+
required: true
|
|
19926
|
+
}),
|
|
19927
|
+
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
19928
|
+
subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
|
|
19929
|
+
body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
|
|
19930
|
+
"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." }),
|
|
19931
|
+
"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." }),
|
|
19932
|
+
html: Flags.string({ description: "HTML message body. Either --body or --html (or both) is required." }),
|
|
19933
|
+
"html-file": Flags.string({ description: "Read the HTML message body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
19934
|
+
"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." }),
|
|
19935
|
+
attachment: Flags.string({
|
|
19936
|
+
description: "Attach a file to the email. Repeatable. Sends file bytes as a MIME attachment; use --body-file only for message body text.",
|
|
19937
|
+
multiple: true
|
|
19938
|
+
}),
|
|
19939
|
+
"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>`." }),
|
|
19940
|
+
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." }),
|
|
19941
|
+
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
19942
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
19411
19943
|
};
|
|
19412
19944
|
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
|
|
19945
|
+
const { flags } = await this.parse(SendCommand);
|
|
19946
|
+
const bodies = resolveMessageBodies({
|
|
19947
|
+
body: flags.body,
|
|
19948
|
+
bodyFile: flags["body-file"],
|
|
19949
|
+
bodyStdin: flags["body-stdin"],
|
|
19950
|
+
html: flags.html,
|
|
19951
|
+
htmlFile: flags["html-file"],
|
|
19952
|
+
htmlStdin: flags["html-stdin"]
|
|
19953
|
+
});
|
|
19954
|
+
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
19955
|
+
const attachments = readAttachmentFiles(flags.attachment);
|
|
19956
|
+
await runWithTiming(flags.time, async () => {
|
|
19957
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
19958
|
+
apiKey: flags["api-key"],
|
|
19959
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
19960
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
19961
|
+
configDir: this.config.configDir
|
|
19458
19962
|
});
|
|
19459
|
-
|
|
19460
|
-
|
|
19461
|
-
|
|
19462
|
-
|
|
19463
|
-
};
|
|
19464
|
-
|
|
19465
|
-
|
|
19466
|
-
|
|
19467
|
-
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
|
|
19475
|
-
|
|
19476
|
-
|
|
19477
|
-
|
|
19478
|
-
|
|
19479
|
-
|
|
19480
|
-
flags
|
|
19963
|
+
const authFailureContext = {
|
|
19964
|
+
auth,
|
|
19965
|
+
baseUrlOverridden,
|
|
19966
|
+
configDir: this.config.configDir
|
|
19967
|
+
};
|
|
19968
|
+
const from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
19969
|
+
const subject = flags.subject ?? (bodies.body ? deriveSubject(bodies.body) : "Message");
|
|
19970
|
+
const result = await sendEmail({
|
|
19971
|
+
body: {
|
|
19972
|
+
from,
|
|
19973
|
+
to: flags.to,
|
|
19974
|
+
subject,
|
|
19975
|
+
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
19976
|
+
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
19977
|
+
...attachments !== void 0 ? { attachments } : {},
|
|
19978
|
+
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
|
|
19979
|
+
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
19980
|
+
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
19981
|
+
},
|
|
19982
|
+
client: apiClient._sendClient,
|
|
19983
|
+
responseStyle: "fields"
|
|
19481
19984
|
});
|
|
19482
|
-
|
|
19483
|
-
|
|
19484
|
-
|
|
19985
|
+
if (result.error) {
|
|
19986
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
19987
|
+
writeErrorWithHints(errorPayload);
|
|
19988
|
+
surfaceUnauthorizedHint({
|
|
19989
|
+
...authFailureContext,
|
|
19990
|
+
payload: errorPayload
|
|
19991
|
+
});
|
|
19992
|
+
process.exitCode = 1;
|
|
19993
|
+
return;
|
|
19994
|
+
}
|
|
19995
|
+
const envelope = result.data;
|
|
19996
|
+
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
19997
|
+
process.stderr.write(chunk);
|
|
19998
|
+
} });
|
|
19999
|
+
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
20000
|
+
});
|
|
19485
20001
|
}
|
|
19486
20002
|
};
|
|
19487
20003
|
//#endregion
|
|
@@ -19496,6 +20012,34 @@ const SIGNIN_OTP_COPY = {
|
|
|
19496
20012
|
resendCommand: (email) => `signin otp resend ${email}`,
|
|
19497
20013
|
startCommand: (email) => `signin otp ${email}`
|
|
19498
20014
|
};
|
|
20015
|
+
const SIGNIN_EMAIL_COPY = {
|
|
20016
|
+
actionNoun: "sign-in",
|
|
20017
|
+
actionGerund: "signing in",
|
|
20018
|
+
confirmCommand: (email) => `signin confirm ${email} <code>`,
|
|
20019
|
+
resendCommand: (email) => `signin resend ${email}`,
|
|
20020
|
+
startCommand: (email) => `signin ${email}`
|
|
20021
|
+
};
|
|
20022
|
+
const LOGIN_EMAIL_COPY = {
|
|
20023
|
+
actionNoun: "login",
|
|
20024
|
+
actionGerund: "logging in",
|
|
20025
|
+
confirmCommand: (email) => `login confirm ${email} <code>`,
|
|
20026
|
+
resendCommand: (email) => `login resend ${email}`,
|
|
20027
|
+
startCommand: (email) => `login ${email}`
|
|
20028
|
+
};
|
|
20029
|
+
const LOGIN_OTP_COPY = {
|
|
20030
|
+
actionNoun: "login",
|
|
20031
|
+
actionGerund: "logging in",
|
|
20032
|
+
confirmCommand: (email) => `login otp confirm ${email} <code>`,
|
|
20033
|
+
resendCommand: (email) => `login otp resend ${email}`,
|
|
20034
|
+
startCommand: (email) => `login otp ${email}`
|
|
20035
|
+
};
|
|
20036
|
+
const OTP_COPY = {
|
|
20037
|
+
actionNoun: "email-code auth",
|
|
20038
|
+
actionGerund: "authenticating",
|
|
20039
|
+
confirmCommand: (email) => `otp confirm ${email} <code>`,
|
|
20040
|
+
resendCommand: (email) => `otp resend ${email}`,
|
|
20041
|
+
startCommand: (email) => `otp ${email}`
|
|
20042
|
+
};
|
|
19499
20043
|
function acquireCredentialsLock(configDir) {
|
|
19500
20044
|
try {
|
|
19501
20045
|
return acquireCliCredentialsLock(configDir);
|
|
@@ -19514,31 +20058,91 @@ function commonOtpStartFlags() {
|
|
|
19514
20058
|
"device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
|
|
19515
20059
|
force: Flags.boolean({
|
|
19516
20060
|
char: "f",
|
|
19517
|
-
description: "Replace saved credentials or pending
|
|
20061
|
+
description: "Replace saved credentials or pending email-code auth state when needed"
|
|
19518
20062
|
}),
|
|
19519
20063
|
"signup-code": Flags.string({
|
|
19520
|
-
description: "Signup code required to start
|
|
20064
|
+
description: "Signup code required to start email-code sign-in",
|
|
19521
20065
|
env: "PRIMITIVE_SIGNUP_CODE"
|
|
19522
20066
|
})
|
|
19523
20067
|
};
|
|
19524
20068
|
}
|
|
19525
|
-
var SigninCommand = class extends LoginCommand {
|
|
19526
|
-
static
|
|
20069
|
+
var SigninCommand = class extends LoginCommand$1 {
|
|
20070
|
+
static args = { email: Args.string({
|
|
20071
|
+
description: "Email address for email-code sign-in. Omit it to use browser approval.",
|
|
20072
|
+
required: false
|
|
20073
|
+
}) };
|
|
20074
|
+
static description = `Sign in or log in to an existing Primitive account and save an org-scoped OAuth session locally.
|
|
19527
20075
|
|
|
19528
|
-
|
|
20076
|
+
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
20077
|
static summary = "Sign in to an existing account";
|
|
19530
20078
|
static examples = [
|
|
19531
20079
|
"<%= config.bin %> signin",
|
|
19532
20080
|
"<%= config.bin %> signin browser",
|
|
19533
20081
|
"<%= config.bin %> signin --no-browser",
|
|
20082
|
+
"<%= config.bin %> signin user@example.com --signup-code invite-code --accept-terms",
|
|
20083
|
+
"<%= config.bin %> signin confirm user@example.com 123456",
|
|
19534
20084
|
"<%= config.bin %> signin otp user@example.com --signup-code invite-code --accept-terms",
|
|
19535
20085
|
"<%= config.bin %> signin otp confirm user@example.com 123456"
|
|
19536
20086
|
];
|
|
20087
|
+
static flags = {
|
|
20088
|
+
...LoginCommand$1.flags,
|
|
20089
|
+
...commonOtpStartFlags(),
|
|
20090
|
+
force: Flags.boolean({
|
|
20091
|
+
char: "f",
|
|
20092
|
+
description: "Replace saved credentials or pending email-code auth state when needed, without first verifying the existing session"
|
|
20093
|
+
})
|
|
20094
|
+
};
|
|
20095
|
+
async run() {
|
|
20096
|
+
const commandClass = this.constructor;
|
|
20097
|
+
const { args, flags } = await this.parse(commandClass);
|
|
20098
|
+
if (!args.email) {
|
|
20099
|
+
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\`.`);
|
|
20100
|
+
await this.runBrowserLogin(flags, this.retryCommand());
|
|
20101
|
+
return;
|
|
20102
|
+
}
|
|
20103
|
+
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
20104
|
+
try {
|
|
20105
|
+
await runSignupStartWithCredentialLock({
|
|
20106
|
+
configDir: this.config.configDir,
|
|
20107
|
+
copy: this.emailCodeCopy(),
|
|
20108
|
+
email: args.email,
|
|
20109
|
+
flags
|
|
20110
|
+
});
|
|
20111
|
+
} finally {
|
|
20112
|
+
releaseCredentialsLock();
|
|
20113
|
+
}
|
|
20114
|
+
}
|
|
19537
20115
|
retryCommand() {
|
|
19538
20116
|
return "signin";
|
|
19539
20117
|
}
|
|
20118
|
+
emailCodeCopy() {
|
|
20119
|
+
return SIGNIN_EMAIL_COPY;
|
|
20120
|
+
}
|
|
20121
|
+
};
|
|
20122
|
+
var LoginCommand = class extends SigninCommand {
|
|
20123
|
+
static args = SigninCommand.args;
|
|
20124
|
+
static description = `Log in or sign in to an existing Primitive account and save an org-scoped OAuth session locally.
|
|
20125
|
+
|
|
20126
|
+
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>\`.`;
|
|
20127
|
+
static summary = "Log in to an existing account";
|
|
20128
|
+
static examples = [
|
|
20129
|
+
"<%= config.bin %> login",
|
|
20130
|
+
"<%= config.bin %> login browser",
|
|
20131
|
+
"<%= config.bin %> login --no-browser",
|
|
20132
|
+
"<%= config.bin %> login user@example.com --signup-code invite-code --accept-terms",
|
|
20133
|
+
"<%= config.bin %> login confirm user@example.com 123456",
|
|
20134
|
+
"<%= config.bin %> login otp user@example.com --signup-code invite-code --accept-terms",
|
|
20135
|
+
"<%= config.bin %> login otp confirm user@example.com 123456"
|
|
20136
|
+
];
|
|
20137
|
+
static flags = SigninCommand.flags;
|
|
20138
|
+
retryCommand() {
|
|
20139
|
+
return "login";
|
|
20140
|
+
}
|
|
20141
|
+
emailCodeCopy() {
|
|
20142
|
+
return LOGIN_EMAIL_COPY;
|
|
20143
|
+
}
|
|
19540
20144
|
};
|
|
19541
|
-
var SigninBrowserCommand = class extends LoginCommand {
|
|
20145
|
+
var SigninBrowserCommand = class extends LoginCommand$1 {
|
|
19542
20146
|
static description = "Sign in to an existing Primitive account by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
|
|
19543
20147
|
static summary = "Sign in with browser approval";
|
|
19544
20148
|
static examples = [
|
|
@@ -19551,7 +20155,20 @@ var SigninBrowserCommand = class extends LoginCommand {
|
|
|
19551
20155
|
return "signin browser";
|
|
19552
20156
|
}
|
|
19553
20157
|
};
|
|
19554
|
-
var
|
|
20158
|
+
var LoginBrowserCommand = class extends LoginCommand$1 {
|
|
20159
|
+
static description = "Log in to an existing Primitive account by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
|
|
20160
|
+
static summary = "Log in with browser approval";
|
|
20161
|
+
static examples = [
|
|
20162
|
+
"<%= config.bin %> login browser",
|
|
20163
|
+
"<%= config.bin %> login browser --device-name work-laptop",
|
|
20164
|
+
"<%= config.bin %> login browser --no-browser",
|
|
20165
|
+
"<%= config.bin %> login browser --force"
|
|
20166
|
+
];
|
|
20167
|
+
retryCommand() {
|
|
20168
|
+
return "login browser";
|
|
20169
|
+
}
|
|
20170
|
+
};
|
|
20171
|
+
var SigninOtpCommand = class extends Command {
|
|
19555
20172
|
static args = { email: Args.string({
|
|
19556
20173
|
description: "Email address to sign in with",
|
|
19557
20174
|
required: false
|
|
@@ -19561,12 +20178,13 @@ var SigninOtpCommand = class SigninOtpCommand extends Command {
|
|
|
19561
20178
|
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
20179
|
static flags = commonOtpStartFlags();
|
|
19563
20180
|
async run() {
|
|
19564
|
-
const
|
|
20181
|
+
const commandClass = this.constructor;
|
|
20182
|
+
const { args, flags } = await this.parse(commandClass);
|
|
19565
20183
|
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
19566
20184
|
try {
|
|
19567
20185
|
await runSignupStartWithCredentialLock({
|
|
19568
20186
|
configDir: this.config.configDir,
|
|
19569
|
-
copy:
|
|
20187
|
+
copy: this.emailCodeCopy(),
|
|
19570
20188
|
email: args.email,
|
|
19571
20189
|
flags
|
|
19572
20190
|
});
|
|
@@ -19574,8 +20192,31 @@ var SigninOtpCommand = class SigninOtpCommand extends Command {
|
|
|
19574
20192
|
releaseCredentialsLock();
|
|
19575
20193
|
}
|
|
19576
20194
|
}
|
|
20195
|
+
emailCodeCopy() {
|
|
20196
|
+
return SIGNIN_OTP_COPY;
|
|
20197
|
+
}
|
|
19577
20198
|
};
|
|
19578
|
-
var
|
|
20199
|
+
var LoginOtpCommand = class extends SigninOtpCommand {
|
|
20200
|
+
static args = SigninOtpCommand.args;
|
|
20201
|
+
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.";
|
|
20202
|
+
static summary = "Start OTP login";
|
|
20203
|
+
static examples = ["<%= config.bin %> login otp user@example.com --signup-code invite-code --accept-terms", "<%= config.bin %> login otp confirm user@example.com 123456"];
|
|
20204
|
+
static flags = SigninOtpCommand.flags;
|
|
20205
|
+
emailCodeCopy() {
|
|
20206
|
+
return LOGIN_OTP_COPY;
|
|
20207
|
+
}
|
|
20208
|
+
};
|
|
20209
|
+
var OtpCommand = class extends SigninOtpCommand {
|
|
20210
|
+
static args = SigninOtpCommand.args;
|
|
20211
|
+
static description = "Start email-code authentication, send a verification code, and save the pending token locally. Requires a signup code.";
|
|
20212
|
+
static summary = "Start email-code auth";
|
|
20213
|
+
static examples = ["<%= config.bin %> otp user@example.com --signup-code invite-code --accept-terms", "<%= config.bin %> otp confirm user@example.com 123456"];
|
|
20214
|
+
static flags = SigninOtpCommand.flags;
|
|
20215
|
+
emailCodeCopy() {
|
|
20216
|
+
return OTP_COPY;
|
|
20217
|
+
}
|
|
20218
|
+
};
|
|
20219
|
+
var SigninOtpConfirmCommand = class extends Command {
|
|
19579
20220
|
static args = {
|
|
19580
20221
|
email: Args.string({
|
|
19581
20222
|
description: "Email address used to start OTP sign-in",
|
|
@@ -19602,13 +20243,14 @@ var SigninOtpConfirmCommand = class SigninOtpConfirmCommand extends Command {
|
|
|
19602
20243
|
"org-id": Flags.string({ description: "Workspace id to target when the email belongs to multiple workspaces" })
|
|
19603
20244
|
};
|
|
19604
20245
|
async run() {
|
|
19605
|
-
const
|
|
20246
|
+
const commandClass = this.constructor;
|
|
20247
|
+
const { args, flags } = await this.parse(commandClass);
|
|
19606
20248
|
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
19607
20249
|
try {
|
|
19608
20250
|
await runSignupConfirmWithCredentialLock({
|
|
19609
20251
|
code: args.code,
|
|
19610
20252
|
configDir: this.config.configDir,
|
|
19611
|
-
copy:
|
|
20253
|
+
copy: this.emailCodeCopy(),
|
|
19612
20254
|
email: args.email,
|
|
19613
20255
|
flags
|
|
19614
20256
|
});
|
|
@@ -19616,8 +20258,51 @@ var SigninOtpConfirmCommand = class SigninOtpConfirmCommand extends Command {
|
|
|
19616
20258
|
releaseCredentialsLock();
|
|
19617
20259
|
}
|
|
19618
20260
|
}
|
|
20261
|
+
emailCodeCopy() {
|
|
20262
|
+
return SIGNIN_OTP_COPY;
|
|
20263
|
+
}
|
|
20264
|
+
};
|
|
20265
|
+
var SigninConfirmCommand = class extends SigninOtpConfirmCommand {
|
|
20266
|
+
static args = SigninOtpConfirmCommand.args;
|
|
20267
|
+
static description = "Confirm a pending email-code sign-in, create an OAuth session, and save CLI credentials locally.";
|
|
20268
|
+
static summary = "Confirm email-code sign-in";
|
|
20269
|
+
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"];
|
|
20270
|
+
static flags = SigninOtpConfirmCommand.flags;
|
|
20271
|
+
emailCodeCopy() {
|
|
20272
|
+
return SIGNIN_EMAIL_COPY;
|
|
20273
|
+
}
|
|
19619
20274
|
};
|
|
19620
|
-
var
|
|
20275
|
+
var LoginConfirmCommand = class extends SigninOtpConfirmCommand {
|
|
20276
|
+
static args = SigninOtpConfirmCommand.args;
|
|
20277
|
+
static description = "Confirm a pending email-code login, create an OAuth session, and save CLI credentials locally.";
|
|
20278
|
+
static summary = "Confirm email-code login";
|
|
20279
|
+
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"];
|
|
20280
|
+
static flags = SigninOtpConfirmCommand.flags;
|
|
20281
|
+
emailCodeCopy() {
|
|
20282
|
+
return LOGIN_EMAIL_COPY;
|
|
20283
|
+
}
|
|
20284
|
+
};
|
|
20285
|
+
var LoginOtpConfirmCommand = class extends SigninOtpConfirmCommand {
|
|
20286
|
+
static args = SigninOtpConfirmCommand.args;
|
|
20287
|
+
static description = "Confirm a pending OTP login, create an OAuth session, and save CLI credentials locally.";
|
|
20288
|
+
static summary = "Confirm OTP login";
|
|
20289
|
+
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"];
|
|
20290
|
+
static flags = SigninOtpConfirmCommand.flags;
|
|
20291
|
+
emailCodeCopy() {
|
|
20292
|
+
return LOGIN_OTP_COPY;
|
|
20293
|
+
}
|
|
20294
|
+
};
|
|
20295
|
+
var OtpConfirmCommand = class extends SigninOtpConfirmCommand {
|
|
20296
|
+
static args = SigninOtpConfirmCommand.args;
|
|
20297
|
+
static description = "Confirm pending email-code authentication, create an OAuth session, and save CLI credentials locally.";
|
|
20298
|
+
static summary = "Confirm email-code auth";
|
|
20299
|
+
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"];
|
|
20300
|
+
static flags = SigninOtpConfirmCommand.flags;
|
|
20301
|
+
emailCodeCopy() {
|
|
20302
|
+
return OTP_COPY;
|
|
20303
|
+
}
|
|
20304
|
+
};
|
|
20305
|
+
var SigninOtpResendCommand = class extends Command {
|
|
19621
20306
|
static args = { email: Args.string({
|
|
19622
20307
|
description: "Email address used to start OTP sign-in",
|
|
19623
20308
|
required: true
|
|
@@ -19631,12 +20316,13 @@ var SigninOtpResendCommand = class SigninOtpResendCommand extends Command {
|
|
|
19631
20316
|
hidden: true
|
|
19632
20317
|
}) };
|
|
19633
20318
|
async run() {
|
|
19634
|
-
const
|
|
20319
|
+
const commandClass = this.constructor;
|
|
20320
|
+
const { args, flags } = await this.parse(commandClass);
|
|
19635
20321
|
const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
|
|
19636
20322
|
try {
|
|
19637
20323
|
await runSignupResendWithCredentialLock({
|
|
19638
20324
|
configDir: this.config.configDir,
|
|
19639
|
-
copy:
|
|
20325
|
+
copy: this.emailCodeCopy(),
|
|
19640
20326
|
email: args.email,
|
|
19641
20327
|
flags
|
|
19642
20328
|
});
|
|
@@ -19644,6 +20330,49 @@ var SigninOtpResendCommand = class SigninOtpResendCommand extends Command {
|
|
|
19644
20330
|
releaseCredentialsLock();
|
|
19645
20331
|
}
|
|
19646
20332
|
}
|
|
20333
|
+
emailCodeCopy() {
|
|
20334
|
+
return SIGNIN_OTP_COPY;
|
|
20335
|
+
}
|
|
20336
|
+
};
|
|
20337
|
+
var SigninResendCommand = class extends SigninOtpResendCommand {
|
|
20338
|
+
static args = SigninOtpResendCommand.args;
|
|
20339
|
+
static description = "Resend the verification code for a pending email-code sign-in.";
|
|
20340
|
+
static summary = "Resend email-code sign-in code";
|
|
20341
|
+
static examples = ["<%= config.bin %> signin resend user@example.com"];
|
|
20342
|
+
static flags = SigninOtpResendCommand.flags;
|
|
20343
|
+
emailCodeCopy() {
|
|
20344
|
+
return SIGNIN_EMAIL_COPY;
|
|
20345
|
+
}
|
|
20346
|
+
};
|
|
20347
|
+
var LoginResendCommand = class extends SigninOtpResendCommand {
|
|
20348
|
+
static args = SigninOtpResendCommand.args;
|
|
20349
|
+
static description = "Resend the verification code for a pending email-code login.";
|
|
20350
|
+
static summary = "Resend email-code login code";
|
|
20351
|
+
static examples = ["<%= config.bin %> login resend user@example.com"];
|
|
20352
|
+
static flags = SigninOtpResendCommand.flags;
|
|
20353
|
+
emailCodeCopy() {
|
|
20354
|
+
return LOGIN_EMAIL_COPY;
|
|
20355
|
+
}
|
|
20356
|
+
};
|
|
20357
|
+
var LoginOtpResendCommand = class extends SigninOtpResendCommand {
|
|
20358
|
+
static args = SigninOtpResendCommand.args;
|
|
20359
|
+
static description = "Resend the verification code for a pending OTP login.";
|
|
20360
|
+
static summary = "Resend OTP login code";
|
|
20361
|
+
static examples = ["<%= config.bin %> login otp resend user@example.com"];
|
|
20362
|
+
static flags = SigninOtpResendCommand.flags;
|
|
20363
|
+
emailCodeCopy() {
|
|
20364
|
+
return LOGIN_OTP_COPY;
|
|
20365
|
+
}
|
|
20366
|
+
};
|
|
20367
|
+
var OtpResendCommand = class extends SigninOtpResendCommand {
|
|
20368
|
+
static args = SigninOtpResendCommand.args;
|
|
20369
|
+
static description = "Resend the verification code for pending email-code authentication.";
|
|
20370
|
+
static summary = "Resend email-code auth code";
|
|
20371
|
+
static examples = ["<%= config.bin %> otp resend user@example.com"];
|
|
20372
|
+
static flags = SigninOtpResendCommand.flags;
|
|
20373
|
+
emailCodeCopy() {
|
|
20374
|
+
return OTP_COPY;
|
|
20375
|
+
}
|
|
19647
20376
|
};
|
|
19648
20377
|
//#endregion
|
|
19649
20378
|
//#region src/oclif/commands/whoami.ts
|
|
@@ -19990,11 +20719,22 @@ const COMMANDS = {
|
|
|
19990
20719
|
reply: ReplyCommand,
|
|
19991
20720
|
chat: ChatCommand,
|
|
19992
20721
|
login: LoginCommand,
|
|
20722
|
+
"login:browser": LoginBrowserCommand,
|
|
20723
|
+
"login:confirm": LoginConfirmCommand,
|
|
20724
|
+
"login:otp": LoginOtpCommand,
|
|
20725
|
+
"login:otp:confirm": LoginOtpConfirmCommand,
|
|
20726
|
+
"login:otp:resend": LoginOtpResendCommand,
|
|
20727
|
+
"login:resend": LoginResendCommand,
|
|
20728
|
+
otp: OtpCommand,
|
|
20729
|
+
"otp:confirm": OtpConfirmCommand,
|
|
20730
|
+
"otp:resend": OtpResendCommand,
|
|
19993
20731
|
signin: SigninCommand,
|
|
19994
20732
|
"signin:browser": SigninBrowserCommand,
|
|
20733
|
+
"signin:confirm": SigninConfirmCommand,
|
|
19995
20734
|
"signin:otp": SigninOtpCommand,
|
|
19996
20735
|
"signin:otp:confirm": SigninOtpConfirmCommand,
|
|
19997
20736
|
"signin:otp:resend": SigninOtpResendCommand,
|
|
20737
|
+
"signin:resend": SigninResendCommand,
|
|
19998
20738
|
signup: SignupCommand,
|
|
19999
20739
|
"signup:confirm": SignupConfirmCommand,
|
|
20000
20740
|
"signup:interactive": SignupInteractiveCommand,
|