@primitivedotdev/cli 0.36.0 → 0.38.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.
|
@@ -949,6 +949,22 @@ function deleteCliCredentials(configDir) {
|
|
|
949
949
|
rmSync(credentialsPath(configDir), { force: true });
|
|
950
950
|
deleteChatState(configDir);
|
|
951
951
|
}
|
|
952
|
+
function saveSignupCredentials(params) {
|
|
953
|
+
deleteChatState(params.configDir);
|
|
954
|
+
saveCliCredentials(params.configDir, {
|
|
955
|
+
access_token: params.signup.access_token,
|
|
956
|
+
api_base_url: params.apiBaseUrl,
|
|
957
|
+
auth_method: "oauth",
|
|
958
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
959
|
+
expires_at: cliAccessTokenExpiresAt(params.signup.expires_in),
|
|
960
|
+
oauth_client_id: params.signup.oauth_client_id,
|
|
961
|
+
oauth_grant_id: params.signup.oauth_grant_id,
|
|
962
|
+
org_id: params.signup.org_id,
|
|
963
|
+
org_name: params.signup.org_name,
|
|
964
|
+
refresh_token: params.signup.refresh_token,
|
|
965
|
+
token_type: params.signup.token_type
|
|
966
|
+
});
|
|
967
|
+
}
|
|
952
968
|
function deleteCliCredentialsLock(configDir) {
|
|
953
969
|
rmSync(credentialsLockPath(configDir), {
|
|
954
970
|
force: true,
|
|
@@ -1311,4 +1327,4 @@ function redactCliEnvironment(environment) {
|
|
|
1311
1327
|
};
|
|
1312
1328
|
}
|
|
1313
1329
|
//#endregion
|
|
1314
|
-
export {
|
|
1330
|
+
export { PrimitiveApiClient as A, saveCliCredentials as C, loadActiveChatState as D, deleteChatState as E, createConfig as M, loadChatConversationByLocalId as O, resolveCliAuth as S, chatStatePath as T, deleteCliCredentials as _, normalizeCliEnvironmentName as a, loadCliCredentials as b, resolveConfigEnvironment as c, validateCliHeaderName as d, validateCliHeaderValue as f, credentialsPath as g, credentialsLockPath as h, loadCliConfig as i, createClient as j, saveActiveChatState as k, saveCliConfig as l, cliAccessTokenExpiresAt as m, deleteCliConfig as n, redactCliEnvironment as o, acquireCliCredentialsLock as p, emptyCliConfig as r, removeCliEnvironment as s, DEFAULT_ENVIRONMENT as t, upsertCliEnvironment as u, deleteCliCredentialsLock as v, saveSignupCredentials as w, normalizeApiBaseUrl as x, detectPrimitiveKeyEnvMisname as y };
|
package/dist/oclif/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as PrimitiveApiClient, C as saveCliCredentials, D as loadActiveChatState, E as deleteChatState, M as createConfig, O as loadChatConversationByLocalId, S as resolveCliAuth, T as chatStatePath, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as loadCliCredentials, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as createClient, k as saveActiveChatState, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveSignupCredentials, x as normalizeApiBaseUrl, y as detectPrimitiveKeyEnvMisname } from "../cli-config-B5hrwe8q.js";
|
|
2
2
|
import { Args, Command, Errors, Flags, ux } from "@oclif/core";
|
|
3
3
|
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -14464,8 +14464,11 @@ const RESERVED_FLAG_NAMES = new Set([
|
|
|
14464
14464
|
"envelope",
|
|
14465
14465
|
"output"
|
|
14466
14466
|
]);
|
|
14467
|
-
function bodyFieldFlag(field) {
|
|
14468
|
-
const common = {
|
|
14467
|
+
function bodyFieldFlag(field, aliases) {
|
|
14468
|
+
const common = {
|
|
14469
|
+
description: field.description || field.name,
|
|
14470
|
+
...aliases && aliases.length > 0 ? { aliases } : {}
|
|
14471
|
+
};
|
|
14469
14472
|
if (field.kind === "boolean") return Flags.boolean({
|
|
14470
14473
|
...common,
|
|
14471
14474
|
allowNo: true
|
|
@@ -14507,12 +14510,13 @@ function buildFlags(operation) {
|
|
|
14507
14510
|
flags["raw-body"] = Flags.string({ description: "Full request body as raw JSON. Escape hatch for nested or complex fields (e.g. arrays); prefer per-field flags (e.g. --to, --from, --body-text) when available." });
|
|
14508
14511
|
flags["body-file"] = Flags.string({ description: "Path to a JSON file used as the request body. Same role as --raw-body for callers passing a saved payload." });
|
|
14509
14512
|
const bodyFields = extractBodyFields(operation.requestSchema);
|
|
14513
|
+
const aliasesForOperation = OPERATION_FLAG_ALIASES[operation.sdkName];
|
|
14510
14514
|
for (const field of bodyFields) {
|
|
14511
14515
|
if (field.kind === "complex") continue;
|
|
14512
14516
|
const name = flagName(field.name);
|
|
14513
14517
|
if (RESERVED_FLAG_NAMES.has(name)) continue;
|
|
14514
14518
|
if (flags[name] !== void 0) continue;
|
|
14515
|
-
flags[name] = bodyFieldFlag(field);
|
|
14519
|
+
flags[name] = bodyFieldFlag(field, aliasesForOperation?.[field.name]);
|
|
14516
14520
|
bodyFieldFlagToProperty.set(name, field.name);
|
|
14517
14521
|
}
|
|
14518
14522
|
}
|
|
@@ -14561,8 +14565,21 @@ const OPERATION_HINTS = {
|
|
|
14561
14565
|
createFunction: "Tip: prefer `primitive functions deploy --name <name> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
14562
14566
|
updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
14563
14567
|
createFunctionSecret: "Tip: prefer `primitive functions set-secret --id <id> --key <KEY> --value <value> [--redeploy]` for secret writes that also push the binding live. This raw command exists for callers passing JSON.",
|
|
14564
|
-
setFunctionSecret: "Tip: prefer `primitive functions set-secret --id <id> --key <KEY> --value <value> [--redeploy]` for secret writes that also push the binding live. This raw command exists for callers passing JSON."
|
|
14568
|
+
setFunctionSecret: "Tip: prefer `primitive functions set-secret --id <id> --key <KEY> --value <value> [--redeploy]` for secret writes that also push the binding live. This raw command exists for callers passing JSON.",
|
|
14569
|
+
startAgentSignup: "Tip: also pass --signup-code <code> (request from Primitive; invite-only during the agent beta) and --terms-accepted. Capture the signup_token from the response and feed it to `primitive agent verify-agent-signup --signup-token <token> --verification-code <6-digit-code>` (the verify flag accepts --code as an alias). The high-level `primitive signup <email>` command walks an interactive user through both steps with friendlier prompts.",
|
|
14570
|
+
verifyAgentSignup: "Tip: pass --verification-code <code> (or --code; both work). The response carries OAuth tokens but not your assigned inbox domain; run `primitive domains list` (or `primitive whoami`) after success to see the managed *.primitive.email address that routes to this account."
|
|
14565
14571
|
};
|
|
14572
|
+
const OPERATION_FLAG_ALIASES = { verifyAgentSignup: { verification_code: ["code"] } };
|
|
14573
|
+
const OPERATION_SUCCESS_HOOKS = { verifyAgentSignup: ({ envelope, configDir, apiBaseUrl, writeStderr }) => {
|
|
14574
|
+
const data = envelope?.data;
|
|
14575
|
+
if (!data?.access_token || !data?.refresh_token) return;
|
|
14576
|
+
saveSignupCredentials({
|
|
14577
|
+
apiBaseUrl,
|
|
14578
|
+
configDir,
|
|
14579
|
+
signup: data
|
|
14580
|
+
});
|
|
14581
|
+
writeStderr("Credentials saved to the CLI config; `primitive whoami` will work on the next call.\n");
|
|
14582
|
+
} };
|
|
14566
14583
|
function createOperationCommand(operation) {
|
|
14567
14584
|
const { flags, bodyFieldFlagToProperty } = buildFlags(operation);
|
|
14568
14585
|
const baseDescription = operation.description !== null && operation.description !== void 0 ? canonicalizeCliReferences(operation.description) : `${operation.method} ${operation.path}`;
|
|
@@ -14656,6 +14673,20 @@ function createOperationCommand(operation) {
|
|
|
14656
14673
|
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
14657
14674
|
process.stderr.write(chunk);
|
|
14658
14675
|
} });
|
|
14676
|
+
const successHook = OPERATION_SUCCESS_HOOKS[operation.sdkName];
|
|
14677
|
+
if (successHook) try {
|
|
14678
|
+
successHook({
|
|
14679
|
+
envelope,
|
|
14680
|
+
configDir: this.config.configDir,
|
|
14681
|
+
apiBaseUrl: auth.apiBaseUrl,
|
|
14682
|
+
writeStderr: (chunk) => {
|
|
14683
|
+
process.stderr.write(chunk);
|
|
14684
|
+
}
|
|
14685
|
+
});
|
|
14686
|
+
} catch (hookError) {
|
|
14687
|
+
const detail = hookError instanceof Error ? hookError.message : String(hookError);
|
|
14688
|
+
process.stderr.write(`Warning: ${operation.sdkName} succeeded but its post-success hook threw (${detail}). The response below is still valid; act on it manually.\n`);
|
|
14689
|
+
}
|
|
14659
14690
|
this.log(JSON.stringify(operationOutputPayload(envelope, parsedFlags.envelope === true), null, 2));
|
|
14660
14691
|
if (isIncompleteDomainVerification(operation, envelope)) {
|
|
14661
14692
|
writeIncompleteDomainVerificationHint();
|
|
@@ -17677,8 +17708,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
17677
17708
|
name: "Primitive Team",
|
|
17678
17709
|
url: "https://primitive.dev"
|
|
17679
17710
|
};
|
|
17680
|
-
const SDK_VERSION_RANGE = "^0.
|
|
17681
|
-
const CLI_VERSION_RANGE = "^0.
|
|
17711
|
+
const SDK_VERSION_RANGE = "^0.38.0";
|
|
17712
|
+
const CLI_VERSION_RANGE = "^0.38.0";
|
|
17682
17713
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
17683
17714
|
function renderHandler() {
|
|
17684
17715
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -17753,22 +17784,50 @@ function inboundRecipientDomains(event: EmailReceivedEvent): Set<string> {
|
|
|
17753
17784
|
// SMTP recipients instead.
|
|
17754
17785
|
//
|
|
17755
17786
|
// The default check skips:
|
|
17787
|
+
// - bounce notifications, which RFC 5321 requires to use an empty
|
|
17788
|
+
// SMTP envelope sender (MAIL FROM:<>). Replying to a null sender
|
|
17789
|
+
// is forbidden and would itself bounce, producing a
|
|
17790
|
+
// bounce-of-bounce chain. The body header on a bounce typically
|
|
17791
|
+
// reads "From: MAILER-DAEMON@..." which a naive From-only check
|
|
17792
|
+
// would treat as a normal sender, so we gate on the envelope here.
|
|
17756
17793
|
// - direct self-mail where From equals one of the inbound recipients;
|
|
17757
|
-
// - mailer-daemon/postmaster bounces from the same domain as the
|
|
17794
|
+
// - mailer-daemon/postmaster bounces from the same domain as the
|
|
17795
|
+
// inbound, as a backup for bounces that arrive with a non-empty
|
|
17796
|
+
// envelope sender;
|
|
17758
17797
|
// - any address explicitly listed in EXTRA_SELF_ADDRESSES.
|
|
17759
17798
|
//
|
|
17760
|
-
//
|
|
17761
|
-
//
|
|
17762
|
-
//
|
|
17763
|
-
//
|
|
17799
|
+
// Anything with no identifiable sender at all (envelope + From both
|
|
17800
|
+
// empty) is treated as a loop terminator: better to drop one ambiguous
|
|
17801
|
+
// message than to reply blindly and loop on a bounce.
|
|
17802
|
+
//
|
|
17803
|
+
// Extend this helper if you need stricter detection. Common additions
|
|
17804
|
+
// not implemented here today:
|
|
17805
|
+
// - Honor RFC 3834 auto-response headers: skip when an
|
|
17806
|
+
// auto-submitted header is anything other than "no", or when a
|
|
17807
|
+
// list-unsubscribe / precedence: bulk header is present. The
|
|
17808
|
+
// EmailReceivedEvent.email.headers shape does not currently surface
|
|
17809
|
+
// these, so detection requires either a parsed-headers field on
|
|
17810
|
+
// the event or a parse of the raw RFC 822 body.
|
|
17764
17811
|
// - Track Message-ID / In-Reply-To chains to break ping-pong loops
|
|
17765
17812
|
// between two cooperating handlers on different domains.
|
|
17813
|
+
// - Rate-limit replies per sender per hour as a safety net.
|
|
17766
17814
|
export function isLoop(event: EmailReceivedEvent): boolean {
|
|
17815
|
+
// RFC 5321: bounce notifications use the null MAIL FROM (envelope
|
|
17816
|
+
// sender = empty string). Some MTAs report this as "<>" verbatim.
|
|
17817
|
+
// Treat either as an unambiguous bounce signal.
|
|
17818
|
+
const envelopeSender = (event.email.smtp.mail_from || "").trim();
|
|
17819
|
+
if (envelopeSender === "" || envelopeSender === "<>") return true;
|
|
17820
|
+
|
|
17767
17821
|
const fromAddresses = [
|
|
17768
17822
|
...extractEmailAddresses(event.email.headers.from),
|
|
17769
17823
|
...extractEmailAddresses(event.email.smtp.mail_from),
|
|
17770
17824
|
];
|
|
17771
|
-
|
|
17825
|
+
// No identifiable sender across either envelope or header: treat as
|
|
17826
|
+
// a loop terminator. Was return false in the original template; that
|
|
17827
|
+
// returned mail with empty headers straight back into the handler
|
|
17828
|
+
// and let bounces with malformed bodies slip past the bounce guard
|
|
17829
|
+
// above.
|
|
17830
|
+
if (fromAddresses.length === 0) return true;
|
|
17772
17831
|
|
|
17773
17832
|
const inboundAddresses = new Set(inboundRecipientAddresses(event));
|
|
17774
17833
|
const inboundDomains = inboundRecipientDomains(event);
|
|
@@ -18244,6 +18303,25 @@ function emitLogRows(rows, jsonl) {
|
|
|
18244
18303
|
process.stdout.write(`${line}\n`);
|
|
18245
18304
|
}
|
|
18246
18305
|
}
|
|
18306
|
+
async function readFunctionInvocations(client, id) {
|
|
18307
|
+
try {
|
|
18308
|
+
const result = await getFunction({
|
|
18309
|
+
client,
|
|
18310
|
+
path: { id },
|
|
18311
|
+
responseStyle: "fields",
|
|
18312
|
+
signal: AbortSignal.timeout(3e3)
|
|
18313
|
+
});
|
|
18314
|
+
if (result.error) return null;
|
|
18315
|
+
const fn = result.data?.data;
|
|
18316
|
+
if (!fn) return null;
|
|
18317
|
+
return {
|
|
18318
|
+
invocations_total: typeof fn.invocations_total === "number" ? fn.invocations_total : 0,
|
|
18319
|
+
invocations_24h: typeof fn.invocations_24h === "number" ? fn.invocations_24h : 0
|
|
18320
|
+
};
|
|
18321
|
+
} catch {
|
|
18322
|
+
return null;
|
|
18323
|
+
}
|
|
18324
|
+
}
|
|
18247
18325
|
var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
|
|
18248
18326
|
static description = "List or follow function execution logs. Defaults to compact text output; use --jsonl for one JSON object per log row.";
|
|
18249
18327
|
static summary = "List or follow a function's execution logs";
|
|
@@ -18342,7 +18420,14 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
|
|
|
18342
18420
|
cursor = page.next_cursor;
|
|
18343
18421
|
}
|
|
18344
18422
|
if (rows.length === 0 && !wroteEmptyHint) {
|
|
18345
|
-
|
|
18423
|
+
let emptyHint;
|
|
18424
|
+
if (flags.follow) emptyHint = hasObservedLogs ? "Waiting for new function logs...\n" : "No function logs yet. Waiting for new rows...\n";
|
|
18425
|
+
else if (flags.cursor) emptyHint = "No more function logs after this cursor.\n";
|
|
18426
|
+
else {
|
|
18427
|
+
const fnInvocations = await readFunctionInvocations(apiClient.client, flags.id);
|
|
18428
|
+
emptyHint = fnInvocations && fnInvocations.invocations_total > 0 ? `No function logs yet, but this function has been invoked ${fnInvocations.invocations_total} time(s) (${fnInvocations.invocations_24h} in the last 24h). Your handler likely has no console.log/console.error calls on the path that fired. Add logging and redeploy to surface details.\n` : "No function logs yet. Trigger the function, then run this command again.\n";
|
|
18429
|
+
}
|
|
18430
|
+
process.stderr.write(emptyHint);
|
|
18346
18431
|
wroteEmptyHint = true;
|
|
18347
18432
|
}
|
|
18348
18433
|
emitLogRows(rows, flags.jsonl);
|
|
@@ -20128,22 +20213,6 @@ async function checkExistingCredentials(params) {
|
|
|
20128
20213
|
}
|
|
20129
20214
|
throw cliError$2(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before ${copy.actionGerund}.`);
|
|
20130
20215
|
}
|
|
20131
|
-
function saveSignupCredentials(params) {
|
|
20132
|
-
deleteChatState(params.configDir);
|
|
20133
|
-
saveCliCredentials(params.configDir, {
|
|
20134
|
-
access_token: params.signup.access_token,
|
|
20135
|
-
api_base_url: params.apiBaseUrl,
|
|
20136
|
-
auth_method: "oauth",
|
|
20137
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20138
|
-
expires_at: cliAccessTokenExpiresAt(params.signup.expires_in),
|
|
20139
|
-
oauth_client_id: params.signup.oauth_client_id,
|
|
20140
|
-
oauth_grant_id: params.signup.oauth_grant_id,
|
|
20141
|
-
org_id: params.signup.org_id,
|
|
20142
|
-
org_name: params.signup.org_name,
|
|
20143
|
-
refresh_token: params.signup.refresh_token,
|
|
20144
|
-
token_type: params.signup.token_type
|
|
20145
|
-
});
|
|
20146
|
-
}
|
|
20147
20216
|
function writeStartInstructions(start, copy = DEFAULT_SIGNUP_COMMAND_COPY) {
|
|
20148
20217
|
process$1.stderr.write(`Sent a ${start.verification_code_length}-digit verification code to ${start.email}.\n`);
|
|
20149
20218
|
process$1.stderr.write(`The code expires in ${formatSignupSeconds(start.expires_in)}.\n`);
|
|
@@ -21504,12 +21573,14 @@ var OtpResendCommand = class extends SigninOtpResendCommand {
|
|
|
21504
21573
|
};
|
|
21505
21574
|
//#endregion
|
|
21506
21575
|
//#region src/oclif/commands/whoami.ts
|
|
21507
|
-
function formatWhoamiSummary(account) {
|
|
21508
|
-
|
|
21576
|
+
function formatWhoamiSummary(account, managedInboxDomain) {
|
|
21577
|
+
const lines = [
|
|
21509
21578
|
`Authenticated as ${account.email}`,
|
|
21510
21579
|
`Account id: ${account.id}`,
|
|
21511
21580
|
`Plan: ${account.plan}`
|
|
21512
|
-
]
|
|
21581
|
+
];
|
|
21582
|
+
if (managedInboxDomain) lines.push(`Managed inbox: any-local-part@${managedInboxDomain}`);
|
|
21583
|
+
return lines.join("\n");
|
|
21513
21584
|
}
|
|
21514
21585
|
var WhoamiCommand = class WhoamiCommand extends Command {
|
|
21515
21586
|
static description = `Print the account currently authenticated by saved OAuth credentials or an explicit API key. Useful as a credentials smoke test: confirms auth is live and shows which account it belongs to.
|
|
@@ -21563,11 +21634,22 @@ var WhoamiCommand = class WhoamiCommand extends Command {
|
|
|
21563
21634
|
process.stderr.write("Server returned an empty account body; this should not happen for a valid key.\n");
|
|
21564
21635
|
throw new Errors.CLIError("unexpected empty response");
|
|
21565
21636
|
}
|
|
21637
|
+
let managedInboxDomain = null;
|
|
21638
|
+
try {
|
|
21639
|
+
const domainsResult = await listDomains({
|
|
21640
|
+
client: apiClient.client,
|
|
21641
|
+
responseStyle: "fields"
|
|
21642
|
+
});
|
|
21643
|
+
if (!domainsResult.error) managedInboxDomain = (domainsResult.data?.data ?? []).find((row) => row.verified && row.managed_zone !== null)?.domain ?? null;
|
|
21644
|
+
} catch {}
|
|
21566
21645
|
if (flags.json) {
|
|
21567
|
-
this.log(JSON.stringify(
|
|
21646
|
+
this.log(JSON.stringify({
|
|
21647
|
+
...account,
|
|
21648
|
+
managed_inbox_domain: managedInboxDomain
|
|
21649
|
+
}, null, 2));
|
|
21568
21650
|
return;
|
|
21569
21651
|
}
|
|
21570
|
-
this.log(formatWhoamiSummary(account));
|
|
21652
|
+
this.log(formatWhoamiSummary(account, managedInboxDomain));
|
|
21571
21653
|
});
|
|
21572
21654
|
}
|
|
21573
21655
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as resolveConfigEnvironment, i as loadCliConfig, x as normalizeApiBaseUrl } from "../cli-config-
|
|
1
|
+
import { c as resolveConfigEnvironment, i as loadCliConfig, x as normalizeApiBaseUrl } from "../cli-config-B5hrwe8q.js";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitivedotdev/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|