@primitivedotdev/cli 1.0.0 → 1.1.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 +1 -1
- package/dist/oclif/index.js +305 -74
- package/dist/oclif/root-signup-hint.js +3 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Use `primitive signin <email> --signup-code <code> --accept-terms`, then `primit
|
|
|
48
48
|
|
|
49
49
|
Use `primitive logout --force` to remove local CLI credentials, pending email-code auth state, and stale credential locks without contacting Primitive. This is the recovery command when an interrupted auth command leaves the CLI saying another credential operation is already in progress.
|
|
50
50
|
|
|
51
|
-
Use `primitive signup <email>` for new account creation, then `primitive signup confirm <email> <code>` with the emailed verification code. Non-interactive signup is available with `--
|
|
51
|
+
Use `primitive signup <email>` for new account creation, then `primitive signup confirm <email> <code>` with the emailed verification code. Non-interactive signup is available with `--accept-terms` (pass `--signup-code <code>` too if you have one).
|
|
52
52
|
|
|
53
53
|
## Command style
|
|
54
54
|
|
package/dist/oclif/index.js
CHANGED
|
@@ -124,10 +124,11 @@ const pollCliLogin = (options) => (options.client ?? client).post({
|
|
|
124
124
|
/**
|
|
125
125
|
* Start CLI account signup
|
|
126
126
|
*
|
|
127
|
-
* Starts a terminal-native CLI signup.
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
127
|
+
* Starts a terminal-native CLI signup. `signup_code` is optional;
|
|
128
|
+
* omit it to sign up without one. The API creates a pending signup
|
|
129
|
+
* session, sends an email verification code, and returns an opaque
|
|
130
|
+
* signup token used by the resend and verify steps. This endpoint
|
|
131
|
+
* does not require an API key.
|
|
131
132
|
*
|
|
132
133
|
*/
|
|
133
134
|
const startCliSignup = (options) => (options.client ?? client).post({
|
|
@@ -156,10 +157,12 @@ const resendCliSignupVerification = (options) => (options.client ?? client).post
|
|
|
156
157
|
/**
|
|
157
158
|
* Verify CLI signup and create OAuth session
|
|
158
159
|
*
|
|
159
|
-
* Verifies the email code for a CLI signup session
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
160
|
+
* Verifies the email code for a CLI signup session and creates the
|
|
161
|
+
* account. When the session was started with a `signup_code`, the
|
|
162
|
+
* reserved code is redeemed; sessions started without a code skip
|
|
163
|
+
* the redemption step. Either way an org-scoped OAuth CLI session
|
|
164
|
+
* is created and the token set is returned exactly once. This
|
|
165
|
+
* endpoint does not require an API key.
|
|
163
166
|
*
|
|
164
167
|
*/
|
|
165
168
|
const verifyCliSignup = (options) => (options.client ?? client).post({
|
|
@@ -173,10 +176,11 @@ const verifyCliSignup = (options) => (options.client ?? client).post({
|
|
|
173
176
|
/**
|
|
174
177
|
* Start agent account signup
|
|
175
178
|
*
|
|
176
|
-
* Starts an agent-native signup session.
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
179
|
+
* Starts an agent-native signup session. `signup_code` is optional;
|
|
180
|
+
* omit it to sign up without one. The API creates a pending signup
|
|
181
|
+
* session, sends an email verification code, and returns an opaque
|
|
182
|
+
* signup token used by the resend and verify steps. This endpoint
|
|
183
|
+
* does not require an API key.
|
|
180
184
|
*
|
|
181
185
|
*/
|
|
182
186
|
const startAgentSignup = (options) => (options.client ?? client).post({
|
|
@@ -205,11 +209,15 @@ const resendAgentSignupVerification = (options) => (options.client ?? client).po
|
|
|
205
209
|
/**
|
|
206
210
|
* Verify agent signup and create OAuth tokens
|
|
207
211
|
*
|
|
208
|
-
* Verifies the email code for an agent signup session
|
|
209
|
-
* when needed
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
212
|
+
* Verifies the email code for an agent signup session and creates
|
|
213
|
+
* the account when needed. When the session was started with a
|
|
214
|
+
* `signup_code`, the reserved code is redeemed; sessions started
|
|
215
|
+
* without a code skip the redemption step. An org-scoped OAuth
|
|
216
|
+
* session for CLI authentication is minted and the raw tokens are
|
|
217
|
+
* returned exactly once. For existing users, the optional `org_id`
|
|
218
|
+
* selects which accessible workspace should receive the new
|
|
219
|
+
* session (no signup-code redemption is performed for existing
|
|
220
|
+
* users regardless of how the session was started).
|
|
213
221
|
*
|
|
214
222
|
*/
|
|
215
223
|
const verifyAgentSignup = (options) => (options.client ?? client).post({
|
|
@@ -1642,7 +1650,7 @@ const openapiDocument = {
|
|
|
1642
1650
|
"/cli/signup/start": { "post": {
|
|
1643
1651
|
"operationId": "startCliSignup",
|
|
1644
1652
|
"summary": "Start CLI account signup",
|
|
1645
|
-
"description": "Starts a terminal-native CLI signup.
|
|
1653
|
+
"description": "Starts a terminal-native CLI signup. `signup_code` is optional;\nomit it to sign up without one. The API creates a pending signup\nsession, sends an email verification code, and returns an opaque\nsignup token used by the resend and verify steps. This endpoint\ndoes not require an API key.\n",
|
|
1646
1654
|
"tags": ["CLI"],
|
|
1647
1655
|
"security": [],
|
|
1648
1656
|
"requestBody": {
|
|
@@ -1704,7 +1712,7 @@ const openapiDocument = {
|
|
|
1704
1712
|
"/cli/signup/verify": { "post": {
|
|
1705
1713
|
"operationId": "verifyCliSignup",
|
|
1706
1714
|
"summary": "Verify CLI signup and create OAuth session",
|
|
1707
|
-
"description": "Verifies the email code for a CLI signup session
|
|
1715
|
+
"description": "Verifies the email code for a CLI signup session and creates the\naccount. When the session was started with a `signup_code`, the\nreserved code is redeemed; sessions started without a code skip\nthe redemption step. Either way an org-scoped OAuth CLI session\nis created and the token set is returned exactly once. This\nendpoint does not require an API key.\n",
|
|
1708
1716
|
"tags": ["CLI"],
|
|
1709
1717
|
"security": [],
|
|
1710
1718
|
"requestBody": {
|
|
@@ -1733,7 +1741,7 @@ const openapiDocument = {
|
|
|
1733
1741
|
"/agent/signup/start": { "post": {
|
|
1734
1742
|
"operationId": "startAgentSignup",
|
|
1735
1743
|
"summary": "Start agent account signup",
|
|
1736
|
-
"description": "Starts an agent-native signup session.
|
|
1744
|
+
"description": "Starts an agent-native signup session. `signup_code` is optional;\nomit it to sign up without one. The API creates a pending signup\nsession, sends an email verification code, and returns an opaque\nsignup token used by the resend and verify steps. This endpoint\ndoes not require an API key.\n",
|
|
1737
1745
|
"tags": ["Agent"],
|
|
1738
1746
|
"security": [],
|
|
1739
1747
|
"requestBody": {
|
|
@@ -1795,7 +1803,7 @@ const openapiDocument = {
|
|
|
1795
1803
|
"/agent/signup/verify": { "post": {
|
|
1796
1804
|
"operationId": "verifyAgentSignup",
|
|
1797
1805
|
"summary": "Verify agent signup and create OAuth tokens",
|
|
1798
|
-
"description": "Verifies the email code for an agent signup session
|
|
1806
|
+
"description": "Verifies the email code for an agent signup session and creates\nthe account when needed. When the session was started with a\n`signup_code`, the reserved code is redeemed; sessions started\nwithout a code skip the redemption step. An org-scoped OAuth\nsession for CLI authentication is minted and the raw tokens are\nreturned exactly once. For existing users, the optional `org_id`\nselects which accessible workspace should receive the new\nsession (no signup-code redemption is performed for existing\nusers regardless of how the session was started).\n",
|
|
1799
1807
|
"tags": ["Agent"],
|
|
1800
1808
|
"security": [],
|
|
1801
1809
|
"requestBody": {
|
|
@@ -4045,7 +4053,8 @@ const openapiDocument = {
|
|
|
4045
4053
|
"signup_code": {
|
|
4046
4054
|
"type": "string",
|
|
4047
4055
|
"minLength": 1,
|
|
4048
|
-
"maxLength": 128
|
|
4056
|
+
"maxLength": 128,
|
|
4057
|
+
"description": "Optional signup code. Omit if you do not have one."
|
|
4049
4058
|
},
|
|
4050
4059
|
"terms_accepted": {
|
|
4051
4060
|
"type": "boolean",
|
|
@@ -4064,11 +4073,7 @@ const openapiDocument = {
|
|
|
4064
4073
|
"description": "Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer"
|
|
4065
4074
|
}
|
|
4066
4075
|
},
|
|
4067
|
-
"required": [
|
|
4068
|
-
"email",
|
|
4069
|
-
"signup_code",
|
|
4070
|
-
"terms_accepted"
|
|
4071
|
-
]
|
|
4076
|
+
"required": ["email", "terms_accepted"]
|
|
4072
4077
|
},
|
|
4073
4078
|
"CliSignupStartResult": {
|
|
4074
4079
|
"type": "object",
|
|
@@ -4233,7 +4238,8 @@ const openapiDocument = {
|
|
|
4233
4238
|
"signup_code": {
|
|
4234
4239
|
"type": "string",
|
|
4235
4240
|
"minLength": 1,
|
|
4236
|
-
"maxLength": 128
|
|
4241
|
+
"maxLength": 128,
|
|
4242
|
+
"description": "Optional signup code. Omit if you do not have one."
|
|
4237
4243
|
},
|
|
4238
4244
|
"terms_accepted": {
|
|
4239
4245
|
"type": "boolean",
|
|
@@ -4252,11 +4258,7 @@ const openapiDocument = {
|
|
|
4252
4258
|
"description": "Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer"
|
|
4253
4259
|
}
|
|
4254
4260
|
},
|
|
4255
|
-
"required": [
|
|
4256
|
-
"email",
|
|
4257
|
-
"signup_code",
|
|
4258
|
-
"terms_accepted"
|
|
4259
|
-
]
|
|
4261
|
+
"required": ["email", "terms_accepted"]
|
|
4260
4262
|
},
|
|
4261
4263
|
"AgentSignupStartResult": {
|
|
4262
4264
|
"type": "object",
|
|
@@ -7781,7 +7783,7 @@ const operationManifest = [
|
|
|
7781
7783
|
"binaryResponse": false,
|
|
7782
7784
|
"bodyRequired": true,
|
|
7783
7785
|
"command": "start-agent-signup",
|
|
7784
|
-
"description": "Starts an agent-native signup session.
|
|
7786
|
+
"description": "Starts an agent-native signup session. `signup_code` is optional;\nomit it to sign up without one. The API creates a pending signup\nsession, sends an email verification code, and returns an opaque\nsignup token used by the resend and verify steps. This endpoint\ndoes not require an API key.\n",
|
|
7785
7787
|
"hasJsonBody": true,
|
|
7786
7788
|
"method": "POST",
|
|
7787
7789
|
"operationId": "startAgentSignup",
|
|
@@ -7800,7 +7802,8 @@ const operationManifest = [
|
|
|
7800
7802
|
"signup_code": {
|
|
7801
7803
|
"type": "string",
|
|
7802
7804
|
"minLength": 1,
|
|
7803
|
-
"maxLength": 128
|
|
7805
|
+
"maxLength": 128,
|
|
7806
|
+
"description": "Optional signup code. Omit if you do not have one."
|
|
7804
7807
|
},
|
|
7805
7808
|
"terms_accepted": {
|
|
7806
7809
|
"type": "boolean",
|
|
@@ -7819,11 +7822,7 @@ const operationManifest = [
|
|
|
7819
7822
|
"description": "Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer"
|
|
7820
7823
|
}
|
|
7821
7824
|
},
|
|
7822
|
-
"required": [
|
|
7823
|
-
"email",
|
|
7824
|
-
"signup_code",
|
|
7825
|
-
"terms_accepted"
|
|
7826
|
-
]
|
|
7825
|
+
"required": ["email", "terms_accepted"]
|
|
7827
7826
|
},
|
|
7828
7827
|
"responseSchema": {
|
|
7829
7828
|
"type": "object",
|
|
@@ -7866,7 +7865,7 @@ const operationManifest = [
|
|
|
7866
7865
|
"binaryResponse": false,
|
|
7867
7866
|
"bodyRequired": true,
|
|
7868
7867
|
"command": "verify-agent-signup",
|
|
7869
|
-
"description": "Verifies the email code for an agent signup session
|
|
7868
|
+
"description": "Verifies the email code for an agent signup session and creates\nthe account when needed. When the session was started with a\n`signup_code`, the reserved code is redeemed; sessions started\nwithout a code skip the redemption step. An org-scoped OAuth\nsession for CLI authentication is minted and the raw tokens are\nreturned exactly once. For existing users, the optional `org_id`\nselects which accessible workspace should receive the new\nsession (no signup-code redemption is performed for existing\nusers regardless of how the session was started).\n",
|
|
7870
7869
|
"hasJsonBody": true,
|
|
7871
7870
|
"method": "POST",
|
|
7872
7871
|
"operationId": "verifyAgentSignup",
|
|
@@ -8236,7 +8235,7 @@ const operationManifest = [
|
|
|
8236
8235
|
"binaryResponse": false,
|
|
8237
8236
|
"bodyRequired": true,
|
|
8238
8237
|
"command": "start-cli-signup",
|
|
8239
|
-
"description": "Starts a terminal-native CLI signup.
|
|
8238
|
+
"description": "Starts a terminal-native CLI signup. `signup_code` is optional;\nomit it to sign up without one. The API creates a pending signup\nsession, sends an email verification code, and returns an opaque\nsignup token used by the resend and verify steps. This endpoint\ndoes not require an API key.\n",
|
|
8240
8239
|
"hasJsonBody": true,
|
|
8241
8240
|
"method": "POST",
|
|
8242
8241
|
"operationId": "startCliSignup",
|
|
@@ -8255,7 +8254,8 @@ const operationManifest = [
|
|
|
8255
8254
|
"signup_code": {
|
|
8256
8255
|
"type": "string",
|
|
8257
8256
|
"minLength": 1,
|
|
8258
|
-
"maxLength": 128
|
|
8257
|
+
"maxLength": 128,
|
|
8258
|
+
"description": "Optional signup code. Omit if you do not have one."
|
|
8259
8259
|
},
|
|
8260
8260
|
"terms_accepted": {
|
|
8261
8261
|
"type": "boolean",
|
|
@@ -8274,11 +8274,7 @@ const operationManifest = [
|
|
|
8274
8274
|
"description": "Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer"
|
|
8275
8275
|
}
|
|
8276
8276
|
},
|
|
8277
|
-
"required": [
|
|
8278
|
-
"email",
|
|
8279
|
-
"signup_code",
|
|
8280
|
-
"terms_accepted"
|
|
8281
|
-
]
|
|
8277
|
+
"required": ["email", "terms_accepted"]
|
|
8282
8278
|
},
|
|
8283
8279
|
"responseSchema": {
|
|
8284
8280
|
"type": "object",
|
|
@@ -8321,7 +8317,7 @@ const operationManifest = [
|
|
|
8321
8317
|
"binaryResponse": false,
|
|
8322
8318
|
"bodyRequired": true,
|
|
8323
8319
|
"command": "verify-cli-signup",
|
|
8324
|
-
"description": "Verifies the email code for a CLI signup session
|
|
8320
|
+
"description": "Verifies the email code for a CLI signup session and creates the\naccount. When the session was started with a `signup_code`, the\nreserved code is redeemed; sessions started without a code skip\nthe redemption step. Either way an org-scoped OAuth CLI session\nis created and the token set is returned exactly once. This\nendpoint does not require an API key.\n",
|
|
8325
8321
|
"hasJsonBody": true,
|
|
8326
8322
|
"method": "POST",
|
|
8327
8323
|
"operationId": "verifyCliSignup",
|
|
@@ -14566,7 +14562,7 @@ const OPERATION_HINTS = {
|
|
|
14566
14562
|
updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
14567
14563
|
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.",
|
|
14568
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.",
|
|
14569
|
-
startAgentSignup: "Tip:
|
|
14565
|
+
startAgentSignup: "Tip: pass --terms-accepted, and optionally --signup-code <code> if you have one. 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
14566
|
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."
|
|
14571
14567
|
};
|
|
14572
14568
|
const OPERATION_FLAG_ALIASES = { verifyAgentSignup: { verification_code: ["code"] } };
|
|
@@ -16645,8 +16641,8 @@ var DomainsZoneFileCommand = class DomainsZoneFileCommand extends Command {
|
|
|
16645
16641
|
};
|
|
16646
16642
|
//#endregion
|
|
16647
16643
|
//#region src/oclif/commands/emails-latest.ts
|
|
16648
|
-
const DEFAULT_LIMIT$
|
|
16649
|
-
const MAX_LIMIT$
|
|
16644
|
+
const DEFAULT_LIMIT$2 = 10;
|
|
16645
|
+
const MAX_LIMIT$2 = 100;
|
|
16650
16646
|
const SUBJECT_DISPLAY_WIDTH = 50;
|
|
16651
16647
|
const ADDRESS_DISPLAY_WIDTH = 32;
|
|
16652
16648
|
const ID_DISPLAY_WIDTH_SHORT = 8;
|
|
@@ -16696,10 +16692,10 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
|
|
|
16696
16692
|
hidden: true
|
|
16697
16693
|
}),
|
|
16698
16694
|
limit: Flags.integer({
|
|
16699
|
-
description: `Number of rows to print (1-${MAX_LIMIT$
|
|
16700
|
-
default: DEFAULT_LIMIT$
|
|
16695
|
+
description: `Number of rows to print (1-${MAX_LIMIT$2}, default ${DEFAULT_LIMIT$2}).`,
|
|
16696
|
+
default: DEFAULT_LIMIT$2,
|
|
16701
16697
|
min: 1,
|
|
16702
|
-
max: MAX_LIMIT$
|
|
16698
|
+
max: MAX_LIMIT$2
|
|
16703
16699
|
}),
|
|
16704
16700
|
json: Flags.boolean({ description: "Print the raw response envelope (with full UUIDs and meta) as JSON on STDOUT instead of the text table. Useful for piping into `jq`, capturing ids for follow-up commands, or scripting." }),
|
|
16705
16701
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
@@ -17970,8 +17966,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
17970
17966
|
name: "Primitive Team",
|
|
17971
17967
|
url: "https://primitive.dev"
|
|
17972
17968
|
};
|
|
17973
|
-
const SDK_VERSION_RANGE = "^1.
|
|
17974
|
-
const CLI_VERSION_RANGE = "^1.
|
|
17969
|
+
const SDK_VERSION_RANGE = "^1.1.0";
|
|
17970
|
+
const CLI_VERSION_RANGE = "^1.1.0";
|
|
17975
17971
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
17976
17972
|
function renderHandler() {
|
|
17977
17973
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -20214,7 +20210,8 @@ const DEFAULT_SIGNUP_COMMAND_COPY = {
|
|
|
20214
20210
|
actionGerund: "creating a new account",
|
|
20215
20211
|
confirmCommand: (email) => `signup confirm ${email} <code>`,
|
|
20216
20212
|
resendCommand: (email) => `signup resend ${email}`,
|
|
20217
|
-
startCommand: (email) => `signup ${email}
|
|
20213
|
+
startCommand: (email) => `signup ${email}`,
|
|
20214
|
+
codeRequired: false
|
|
20218
20215
|
};
|
|
20219
20216
|
function cliError$2(message) {
|
|
20220
20217
|
return new Errors.CLIError(message, { exit: 1 });
|
|
@@ -20329,7 +20326,7 @@ function readPendingAgentSignupState(configDir, apiBaseUrl) {
|
|
|
20329
20326
|
return pending;
|
|
20330
20327
|
}
|
|
20331
20328
|
function pendingSignupStartCommand(email) {
|
|
20332
|
-
return `primitive signup ${email ?? "<email>"} --
|
|
20329
|
+
return `primitive signup ${email ?? "<email>"} --accept-terms`;
|
|
20333
20330
|
}
|
|
20334
20331
|
function buildSignupStatus(params) {
|
|
20335
20332
|
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
@@ -20495,16 +20492,17 @@ async function startSignup(params) {
|
|
|
20495
20492
|
throw cliError$2(`Pending ${copy.actionNoun} is for ${existingPending.email}. Run \`primitive signup status\` to inspect it, or \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
|
|
20496
20493
|
}
|
|
20497
20494
|
if (params.flags.force) deletePendingAgentSignup(params.configDir);
|
|
20498
|
-
const promptRequiredFn = params.deps.promptRequired ?? promptRequired;
|
|
20499
20495
|
const confirmTermsFn = params.deps.confirmTerms ?? confirmTerms;
|
|
20496
|
+
const promptRequiredFn = params.deps.promptRequired ?? promptRequired;
|
|
20500
20497
|
const startFn = params.deps.startAgentSignup ?? startAgentSignup;
|
|
20501
|
-
const
|
|
20498
|
+
const rawSignupCode = params.flags["signup-code"];
|
|
20499
|
+
const signupCode = (rawSignupCode && rawSignupCode.trim().length > 0 ? rawSignupCode : void 0) ?? (copy.codeRequired ? await promptRequiredFn("Signup code: ") : void 0);
|
|
20502
20500
|
if (!params.flags["accept-terms"]) await confirmTermsFn();
|
|
20503
20501
|
const started = await startFn({
|
|
20504
20502
|
body: {
|
|
20505
20503
|
device_name: params.flags["device-name"] ?? hostname(),
|
|
20506
20504
|
email: params.email,
|
|
20507
|
-
signup_code: signupCode,
|
|
20505
|
+
...signupCode ? { signup_code: signupCode } : {},
|
|
20508
20506
|
terms_accepted: true
|
|
20509
20507
|
},
|
|
20510
20508
|
client: params.apiClient.client,
|
|
@@ -20734,7 +20732,7 @@ function commonStartFlags() {
|
|
|
20734
20732
|
description: "Replace saved credentials or pending signup state when needed"
|
|
20735
20733
|
}),
|
|
20736
20734
|
"signup-code": Flags.string({
|
|
20737
|
-
description: "
|
|
20735
|
+
description: "Optional signup code. Omit if you do not have one.",
|
|
20738
20736
|
env: "PRIMITIVE_SIGNUP_CODE"
|
|
20739
20737
|
})
|
|
20740
20738
|
};
|
|
@@ -20748,6 +20746,7 @@ var SignupCommand = class SignupCommand extends Command {
|
|
|
20748
20746
|
static summary = "Start account signup";
|
|
20749
20747
|
static examples = [
|
|
20750
20748
|
"<%= config.bin %> signup user@example.com",
|
|
20749
|
+
"<%= config.bin %> signup user@example.com --accept-terms",
|
|
20751
20750
|
"<%= config.bin %> signup user@example.com --signup-code invite-code --accept-terms",
|
|
20752
20751
|
"<%= config.bin %> signup confirm user@example.com 123456"
|
|
20753
20752
|
];
|
|
@@ -21223,8 +21222,8 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
21223
21222
|
};
|
|
21224
21223
|
//#endregion
|
|
21225
21224
|
//#region src/oclif/commands/semantic-search.ts
|
|
21226
|
-
const DEFAULT_LIMIT = 10;
|
|
21227
|
-
const MAX_LIMIT = 100;
|
|
21225
|
+
const DEFAULT_LIMIT$1 = 10;
|
|
21226
|
+
const MAX_LIMIT$1 = 100;
|
|
21228
21227
|
const SCORE_WIDTH = 7;
|
|
21229
21228
|
const SOURCE_WIDTH = 4;
|
|
21230
21229
|
const SUBJECT_WIDTH = 40;
|
|
@@ -21290,10 +21289,10 @@ var SemanticSearchCommand = class SemanticSearchCommand extends Command {
|
|
|
21290
21289
|
"date-from": Flags.string({ description: "Only include mail at or after this ISO-8601 timestamp." }),
|
|
21291
21290
|
"date-to": Flags.string({ description: "Only include mail at or before this ISO-8601 timestamp." }),
|
|
21292
21291
|
limit: Flags.integer({
|
|
21293
|
-
description: `Maximum results to return (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}).`,
|
|
21294
|
-
default: DEFAULT_LIMIT,
|
|
21292
|
+
description: `Maximum results to return (1-${MAX_LIMIT$1}, default ${DEFAULT_LIMIT$1}).`,
|
|
21293
|
+
default: DEFAULT_LIMIT$1,
|
|
21295
21294
|
min: 1,
|
|
21296
|
-
max: MAX_LIMIT
|
|
21295
|
+
max: MAX_LIMIT$1
|
|
21297
21296
|
}),
|
|
21298
21297
|
cursor: Flags.string({ description: "Opaque pagination cursor from a prior response's meta.cursor." }),
|
|
21299
21298
|
json: Flags.boolean({ description: "Print the raw response envelope as JSON on STDOUT instead of the text table." }),
|
|
@@ -21350,6 +21349,230 @@ var SemanticSearchCommand = class SemanticSearchCommand extends Command {
|
|
|
21350
21349
|
}
|
|
21351
21350
|
};
|
|
21352
21351
|
//#endregion
|
|
21352
|
+
//#region src/oclif/commands/search.ts
|
|
21353
|
+
const DEFAULT_LIMIT = 10;
|
|
21354
|
+
const MAX_LIMIT = 100;
|
|
21355
|
+
const LEXICAL_ONLY_FLAGS = [
|
|
21356
|
+
"from",
|
|
21357
|
+
"to",
|
|
21358
|
+
"subject",
|
|
21359
|
+
"body",
|
|
21360
|
+
"domain",
|
|
21361
|
+
"domain-id",
|
|
21362
|
+
"has-attachment",
|
|
21363
|
+
"status",
|
|
21364
|
+
"sort",
|
|
21365
|
+
"snippet",
|
|
21366
|
+
"include-facets"
|
|
21367
|
+
];
|
|
21368
|
+
var SearchCommand = class SearchCommand extends Command {
|
|
21369
|
+
static description = `Search received (default) or both received and sent mail (with --mode).
|
|
21370
|
+
|
|
21371
|
+
Default behavior is lexical full-text matching: the positional query is sent as \`q=<query>\` to the inbound search endpoint, which matches against subject, body, sender, and recipient in a single pass. Structured filters (--from, --to, --subject, --body, --domain, --has-attachment, --date-from, --date-to, --status) AND with the text query.
|
|
21372
|
+
|
|
21373
|
+
Pass --mode to switch to the cross-corpus semantic backend (covers inbound and outbound). \`--mode keyword\` is plain full-text; \`--mode semantic\` is embedding-only; \`--mode hybrid\` blends both. Semantic modes require the Pro plan with the semantic_search_enabled entitlement.
|
|
21374
|
+
|
|
21375
|
+
Output is a fixed-width text table by default (header on STDERR so rows stay grep/awk-friendly). Use --json for the raw envelope.`;
|
|
21376
|
+
static summary = "Search mail (lexical by default; --mode for semantic)";
|
|
21377
|
+
static examples = [
|
|
21378
|
+
"<%= config.bin %> search \"invoice\"",
|
|
21379
|
+
"<%= config.bin %> search \"renewal\" --from acme.com",
|
|
21380
|
+
"<%= config.bin %> search \"kickoff\" --mode hybrid",
|
|
21381
|
+
"<%= config.bin %> search \"shipping\" --mode keyword --corpus outbound",
|
|
21382
|
+
"<%= config.bin %> search \"needle\" --json | jq '.data[0].id'"
|
|
21383
|
+
];
|
|
21384
|
+
static args = { query: Args.string({
|
|
21385
|
+
description: "The search query. Matched against every indexed field (subject, body, sender, recipient) when lexical; against the embedding when semantic.",
|
|
21386
|
+
required: true
|
|
21387
|
+
}) };
|
|
21388
|
+
static flags = {
|
|
21389
|
+
"api-key": Flags.string({
|
|
21390
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
21391
|
+
env: "PRIMITIVE_API_KEY"
|
|
21392
|
+
}),
|
|
21393
|
+
"api-base-url": Flags.string({
|
|
21394
|
+
description: API_BASE_URL_FLAG_DESCRIPTION,
|
|
21395
|
+
env: "PRIMITIVE_API_BASE_URL",
|
|
21396
|
+
hidden: true
|
|
21397
|
+
}),
|
|
21398
|
+
mode: Flags.string({
|
|
21399
|
+
description: "Switch to the cross-corpus semantic backend. Omit for the default lexical backend.",
|
|
21400
|
+
options: [
|
|
21401
|
+
"hybrid",
|
|
21402
|
+
"semantic",
|
|
21403
|
+
"keyword"
|
|
21404
|
+
]
|
|
21405
|
+
}),
|
|
21406
|
+
from: Flags.string({ description: "Lexical only. Filter by sender address or sender domain." }),
|
|
21407
|
+
to: Flags.string({ description: "Lexical only. Filter by recipient address or recipient domain." }),
|
|
21408
|
+
subject: Flags.string({ description: "Lexical only. Full-text search restricted to the subject." }),
|
|
21409
|
+
body: Flags.string({ description: "Lexical only. Full-text search restricted to the parsed text body." }),
|
|
21410
|
+
domain: Flags.string({ description: "Lexical only. Filter by the recipient's mail domain." }),
|
|
21411
|
+
"domain-id": Flags.string({ description: "Lexical only. Filter by domain ID." }),
|
|
21412
|
+
"has-attachment": Flags.string({
|
|
21413
|
+
description: "Lexical only. Filter by whether the email has one or more attachments.",
|
|
21414
|
+
options: ["true", "false"]
|
|
21415
|
+
}),
|
|
21416
|
+
status: Flags.string({
|
|
21417
|
+
description: "Lexical only. Filter by parse status.",
|
|
21418
|
+
options: [
|
|
21419
|
+
"pending",
|
|
21420
|
+
"accepted",
|
|
21421
|
+
"completed",
|
|
21422
|
+
"rejected"
|
|
21423
|
+
]
|
|
21424
|
+
}),
|
|
21425
|
+
sort: Flags.string({
|
|
21426
|
+
description: "Lexical only. Result ordering.",
|
|
21427
|
+
options: [
|
|
21428
|
+
"relevance",
|
|
21429
|
+
"received_at_desc",
|
|
21430
|
+
"received_at_asc"
|
|
21431
|
+
]
|
|
21432
|
+
}),
|
|
21433
|
+
snippet: Flags.string({
|
|
21434
|
+
description: "Lexical only. Include match-centered subject/body highlights on each row.",
|
|
21435
|
+
options: ["true", "false"]
|
|
21436
|
+
}),
|
|
21437
|
+
"include-facets": Flags.string({
|
|
21438
|
+
description: "Lexical only. Include facet counts for sender, domain, status, and attachment presence.",
|
|
21439
|
+
options: ["true", "false"]
|
|
21440
|
+
}),
|
|
21441
|
+
corpus: Flags.string({
|
|
21442
|
+
description: "Semantic only. Restrict to inbound or outbound mail. Pass twice to include both (the default).",
|
|
21443
|
+
options: ["inbound", "outbound"],
|
|
21444
|
+
multiple: true
|
|
21445
|
+
}),
|
|
21446
|
+
"date-from": Flags.string({ description: "Only include mail at or after this ISO-8601 timestamp." }),
|
|
21447
|
+
"date-to": Flags.string({ description: "Only include mail at or before this ISO-8601 timestamp." }),
|
|
21448
|
+
limit: Flags.integer({
|
|
21449
|
+
description: `Maximum results to return (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}).`,
|
|
21450
|
+
default: DEFAULT_LIMIT,
|
|
21451
|
+
min: 1,
|
|
21452
|
+
max: MAX_LIMIT
|
|
21453
|
+
}),
|
|
21454
|
+
cursor: Flags.string({ description: "Opaque pagination cursor from a prior response's meta.cursor." }),
|
|
21455
|
+
json: Flags.boolean({ description: "Print the raw response envelope as JSON on STDOUT instead of the text table." }),
|
|
21456
|
+
envelope: Flags.boolean({ description: "Lexical text-table only. Surface the next pagination cursor (if any) on STDERR below the table. Has no effect with --json; --json already prints the full envelope including meta." }),
|
|
21457
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
21458
|
+
};
|
|
21459
|
+
async run() {
|
|
21460
|
+
const { args, flags } = await this.parse(SearchCommand);
|
|
21461
|
+
await runWithTiming(flags.time, async () => {
|
|
21462
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
21463
|
+
apiKey: flags["api-key"],
|
|
21464
|
+
apiBaseUrl: flags["api-base-url"],
|
|
21465
|
+
configDir: this.config.configDir
|
|
21466
|
+
});
|
|
21467
|
+
const handleError = (error) => {
|
|
21468
|
+
const errorPayload = extractErrorPayload(error);
|
|
21469
|
+
writeErrorWithHints(errorPayload);
|
|
21470
|
+
surfaceUnauthorizedHint({
|
|
21471
|
+
auth,
|
|
21472
|
+
baseUrlOverridden,
|
|
21473
|
+
configDir: this.config.configDir,
|
|
21474
|
+
payload: errorPayload
|
|
21475
|
+
});
|
|
21476
|
+
process.exitCode = 1;
|
|
21477
|
+
};
|
|
21478
|
+
if (flags.mode) {
|
|
21479
|
+
if (flags.envelope) {
|
|
21480
|
+
process.stderr.write("--envelope only applies to lexical mode. Use --json for the raw semantic envelope.\n");
|
|
21481
|
+
process.exitCode = 2;
|
|
21482
|
+
return;
|
|
21483
|
+
}
|
|
21484
|
+
const incompatible = LEXICAL_ONLY_FLAGS.filter((name) => flags[name] !== void 0);
|
|
21485
|
+
if (incompatible.length > 0) {
|
|
21486
|
+
const isOne = incompatible.length === 1;
|
|
21487
|
+
process.stderr.write(`Flag${isOne ? "" : "s"} --${incompatible.join(", --")} only ${isOne ? "applies" : "apply"} to the lexical backend. Omit --mode to use ${isOne ? "it" : "them"}.\n`);
|
|
21488
|
+
process.exitCode = 2;
|
|
21489
|
+
return;
|
|
21490
|
+
}
|
|
21491
|
+
const result = await semanticSearch({
|
|
21492
|
+
client: apiClient.client,
|
|
21493
|
+
body: {
|
|
21494
|
+
query: args.query,
|
|
21495
|
+
mode: flags.mode,
|
|
21496
|
+
...flags.corpus ? { corpus: flags.corpus } : {},
|
|
21497
|
+
...flags["date-from"] ? { date_from: flags["date-from"] } : {},
|
|
21498
|
+
...flags["date-to"] ? { date_to: flags["date-to"] } : {},
|
|
21499
|
+
limit: flags.limit,
|
|
21500
|
+
...flags.cursor ? { cursor: flags.cursor } : {}
|
|
21501
|
+
},
|
|
21502
|
+
responseStyle: "fields"
|
|
21503
|
+
});
|
|
21504
|
+
if (result.error) {
|
|
21505
|
+
handleError(result.error);
|
|
21506
|
+
return;
|
|
21507
|
+
}
|
|
21508
|
+
const envelope = result.data;
|
|
21509
|
+
if (flags.json) {
|
|
21510
|
+
this.log(JSON.stringify(envelope ?? null, null, 2));
|
|
21511
|
+
return;
|
|
21512
|
+
}
|
|
21513
|
+
const rows = envelope?.data ?? [];
|
|
21514
|
+
if (rows.length === 0) {
|
|
21515
|
+
process.stderr.write("No matching mail.\n");
|
|
21516
|
+
return;
|
|
21517
|
+
}
|
|
21518
|
+
process.stderr.write(`${formatHeader()}\n`);
|
|
21519
|
+
for (const row of rows) this.log(formatRow(row));
|
|
21520
|
+
const nextCursor = envelope?.meta?.cursor ?? null;
|
|
21521
|
+
if (nextCursor) process.stderr.write(`\nNext page: pass --cursor ${nextCursor}\n`);
|
|
21522
|
+
return;
|
|
21523
|
+
}
|
|
21524
|
+
if (flags.corpus && flags.corpus.length > 0) {
|
|
21525
|
+
process.stderr.write("--corpus only applies to semantic mode. Pass --mode keyword|semantic|hybrid to enable it.\n");
|
|
21526
|
+
process.exitCode = 2;
|
|
21527
|
+
return;
|
|
21528
|
+
}
|
|
21529
|
+
const query = {
|
|
21530
|
+
q: flags.domain ? `${args.query} domain:${quoteDslValue(flags.domain)}` : args.query,
|
|
21531
|
+
limit: flags.limit
|
|
21532
|
+
};
|
|
21533
|
+
if (flags.from) query.from = flags.from;
|
|
21534
|
+
if (flags.to) query.to = flags.to;
|
|
21535
|
+
if (flags.subject) query.subject = flags.subject;
|
|
21536
|
+
if (flags.body) query.body = flags.body;
|
|
21537
|
+
if (flags["domain-id"]) query.domain_id = flags["domain-id"];
|
|
21538
|
+
if (flags["has-attachment"]) query.has_attachment = flags["has-attachment"];
|
|
21539
|
+
if (flags.status) query.status = flags.status;
|
|
21540
|
+
if (flags.sort) query.sort = flags.sort;
|
|
21541
|
+
if (flags.snippet) query.snippet = flags.snippet;
|
|
21542
|
+
if (flags["include-facets"]) query.include_facets = flags["include-facets"];
|
|
21543
|
+
if (flags["date-from"]) query.date_from = flags["date-from"];
|
|
21544
|
+
if (flags["date-to"]) query.date_to = flags["date-to"];
|
|
21545
|
+
if (flags.cursor) query.cursor = flags.cursor;
|
|
21546
|
+
const result = await searchEmails({
|
|
21547
|
+
client: apiClient.client,
|
|
21548
|
+
query,
|
|
21549
|
+
responseStyle: "fields"
|
|
21550
|
+
});
|
|
21551
|
+
if (result.error) {
|
|
21552
|
+
handleError(result.error);
|
|
21553
|
+
return;
|
|
21554
|
+
}
|
|
21555
|
+
const envelope = result.data;
|
|
21556
|
+
if (flags.json) {
|
|
21557
|
+
this.log(JSON.stringify(envelope ?? null, null, 2));
|
|
21558
|
+
return;
|
|
21559
|
+
}
|
|
21560
|
+
const rows = envelope?.data ?? [];
|
|
21561
|
+
if (rows.length === 0) {
|
|
21562
|
+
process.stderr.write("No matching mail.\n");
|
|
21563
|
+
return;
|
|
21564
|
+
}
|
|
21565
|
+
const idWidth = pickIdWidth(Boolean(process.stdout.isTTY));
|
|
21566
|
+
process.stderr.write(`${formatHeader$1(idWidth)}\n`);
|
|
21567
|
+
for (const row of rows) this.log(formatRow$1(row, idWidth));
|
|
21568
|
+
if (flags.envelope) {
|
|
21569
|
+
const nextCursor = envelope?.meta?.cursor ?? null;
|
|
21570
|
+
if (nextCursor) process.stderr.write(`\nNext page: pass --cursor ${nextCursor}\n`);
|
|
21571
|
+
}
|
|
21572
|
+
});
|
|
21573
|
+
}
|
|
21574
|
+
};
|
|
21575
|
+
//#endregion
|
|
21353
21576
|
//#region src/oclif/commands/send.ts
|
|
21354
21577
|
var SendCommand = class SendCommand extends Command {
|
|
21355
21578
|
static description = `Send an outbound email. Agent-grade shortcut for \`sending send\` with sensible defaults.
|
|
@@ -21469,35 +21692,40 @@ const SIGNIN_OTP_COPY = {
|
|
|
21469
21692
|
actionGerund: "signing in",
|
|
21470
21693
|
confirmCommand: (email) => `signin otp confirm ${email} <code>`,
|
|
21471
21694
|
resendCommand: (email) => `signin otp resend ${email}`,
|
|
21472
|
-
startCommand: (email) => `signin otp ${email}
|
|
21695
|
+
startCommand: (email) => `signin otp ${email}`,
|
|
21696
|
+
codeRequired: true
|
|
21473
21697
|
};
|
|
21474
21698
|
const SIGNIN_EMAIL_COPY = {
|
|
21475
21699
|
actionNoun: "sign-in",
|
|
21476
21700
|
actionGerund: "signing in",
|
|
21477
21701
|
confirmCommand: (email) => `signin confirm ${email} <code>`,
|
|
21478
21702
|
resendCommand: (email) => `signin resend ${email}`,
|
|
21479
|
-
startCommand: (email) => `signin ${email}
|
|
21703
|
+
startCommand: (email) => `signin ${email}`,
|
|
21704
|
+
codeRequired: true
|
|
21480
21705
|
};
|
|
21481
21706
|
const LOGIN_EMAIL_COPY = {
|
|
21482
21707
|
actionNoun: "login",
|
|
21483
21708
|
actionGerund: "logging in",
|
|
21484
21709
|
confirmCommand: (email) => `login confirm ${email} <code>`,
|
|
21485
21710
|
resendCommand: (email) => `login resend ${email}`,
|
|
21486
|
-
startCommand: (email) => `login ${email}
|
|
21711
|
+
startCommand: (email) => `login ${email}`,
|
|
21712
|
+
codeRequired: true
|
|
21487
21713
|
};
|
|
21488
21714
|
const LOGIN_OTP_COPY = {
|
|
21489
21715
|
actionNoun: "login",
|
|
21490
21716
|
actionGerund: "logging in",
|
|
21491
21717
|
confirmCommand: (email) => `login otp confirm ${email} <code>`,
|
|
21492
21718
|
resendCommand: (email) => `login otp resend ${email}`,
|
|
21493
|
-
startCommand: (email) => `login otp ${email}
|
|
21719
|
+
startCommand: (email) => `login otp ${email}`,
|
|
21720
|
+
codeRequired: true
|
|
21494
21721
|
};
|
|
21495
21722
|
const OTP_COPY = {
|
|
21496
21723
|
actionNoun: "email-code auth",
|
|
21497
21724
|
actionGerund: "authenticating",
|
|
21498
21725
|
confirmCommand: (email) => `otp confirm ${email} <code>`,
|
|
21499
21726
|
resendCommand: (email) => `otp resend ${email}`,
|
|
21500
|
-
startCommand: (email) => `otp ${email}
|
|
21727
|
+
startCommand: (email) => `otp ${email}`,
|
|
21728
|
+
codeRequired: true
|
|
21501
21729
|
};
|
|
21502
21730
|
function acquireCredentialsLock(configDir) {
|
|
21503
21731
|
try {
|
|
@@ -22170,7 +22398,8 @@ function resolveOperationAlias(id) {
|
|
|
22170
22398
|
const OVERRIDDEN_OPERATION_IDS = new Set([
|
|
22171
22399
|
"domains:download-domain-zone-file",
|
|
22172
22400
|
"functions:test-function",
|
|
22173
|
-
"inbox:get-inbox-status"
|
|
22401
|
+
"inbox:get-inbox-status",
|
|
22402
|
+
"search:semantic-search"
|
|
22174
22403
|
]);
|
|
22175
22404
|
const generatedCommands = Object.fromEntries(operationManifest.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(operationId(operation))).map((operation) => [operationId(operation), createOperationCommand(operation)]));
|
|
22176
22405
|
const COMMANDS = {
|
|
@@ -22214,7 +22443,9 @@ const COMMANDS = {
|
|
|
22214
22443
|
"emails:latest": EmailsLatestCommand,
|
|
22215
22444
|
"emails:watch": EmailsWatchCommand,
|
|
22216
22445
|
"emails:wait": EmailsWaitCommand,
|
|
22446
|
+
search: SearchCommand,
|
|
22217
22447
|
"semantic-search": SemanticSearchCommand,
|
|
22448
|
+
"search:semantic-search": SemanticSearchCommand,
|
|
22218
22449
|
"domains:zone-file": DomainsZoneFileCommand,
|
|
22219
22450
|
"domains:download-domain-zone-file": DomainsZoneFileCommand,
|
|
22220
22451
|
"inbox:setup": InboxSetupCommand,
|
|
@@ -113,8 +113,9 @@ function loggedOutSignupHint() {
|
|
|
113
113
|
return [
|
|
114
114
|
"New to Primitive?",
|
|
115
115
|
" You or your user don't have an account yet?",
|
|
116
|
-
" Run `primitive signup <email> --
|
|
117
|
-
" to create an account
|
|
116
|
+
" Run `primitive signup <email> --accept-terms`",
|
|
117
|
+
" to create an account and get started.",
|
|
118
|
+
" Add `--signup-code <code>` if you have one.",
|
|
118
119
|
""
|
|
119
120
|
].join("\n");
|
|
120
121
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitivedotdev/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.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,
|
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
"description": "List, inspect, and wait for received emails. Prefer task aliases like `primitive emails list`, `primitive emails get`, `primitive emails latest`, `primitive emails wait`, and `primitive emails watch`; generated API names remain available for compatibility."
|
|
63
63
|
},
|
|
64
64
|
"search": {
|
|
65
|
-
"description": "
|
|
65
|
+
"description": "Cross-corpus semantic search. Prefer `primitive search <query>` for lexical inbound search and `primitive semantic-search <query>` for meaning-aware ranking across inbound and outbound.",
|
|
66
|
+
"hidden": true
|
|
66
67
|
},
|
|
67
68
|
"sending": {
|
|
68
69
|
"description": "Send outbound emails. Prefer `primitive send` for fresh sends and `primitive reply --id <inbound-id>` for replies. Use `primitive domains list` or `primitive inbox status` to find usable sender domains for --from; `primitive sending permissions` lists recipient-scope destinations you may send to."
|