@primitivedotdev/cli 0.35.1 → 0.37.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/dist/oclif/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as createClient, C as saveCliCredentials, D as loadChatConversationByLocalId, E as loadActiveChatState, O as saveActiveChatState, S as resolveCliAuth, T as deleteChatState, _ 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 createConfig, k as PrimitiveApiClient, 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 chatStatePath, x as normalizeApiBaseUrl, y as detectPrimitiveKeyEnvMisname } from "../cli-config-D7wN_PBc.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";
|
|
@@ -44,8 +44,10 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
44
44
|
getConversation: () => getConversation,
|
|
45
45
|
getEmail: () => getEmail,
|
|
46
46
|
getFunction: () => getFunction,
|
|
47
|
+
getFunctionRouting: () => getFunctionRouting,
|
|
47
48
|
getFunctionTestRunTrace: () => getFunctionTestRunTrace,
|
|
48
49
|
getInboxStatus: () => getInboxStatus,
|
|
50
|
+
getOrgRoutingTopology: () => getOrgRoutingTopology,
|
|
49
51
|
getSendPermissions: () => getSendPermissions,
|
|
50
52
|
getSentEmail: () => getSentEmail,
|
|
51
53
|
getStorageStats: () => getStorageStats,
|
|
@@ -70,12 +72,14 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
70
72
|
searchEmails: () => searchEmails,
|
|
71
73
|
semanticSearch: () => semanticSearch,
|
|
72
74
|
sendEmail: () => sendEmail,
|
|
75
|
+
setFunctionRoute: () => setFunctionRoute,
|
|
73
76
|
setFunctionSecret: () => setFunctionSecret,
|
|
74
77
|
startAgentSignup: () => startAgentSignup,
|
|
75
78
|
startCliLogin: () => startCliLogin,
|
|
76
79
|
startCliSignup: () => startCliSignup,
|
|
77
80
|
testEndpoint: () => testEndpoint,
|
|
78
81
|
testFunction: () => testFunction,
|
|
82
|
+
unsetFunctionRoute: () => unsetFunctionRoute,
|
|
79
83
|
updateAccount: () => updateAccount,
|
|
80
84
|
updateDomain: () => updateDomain,
|
|
81
85
|
updateEndpoint: () => updateEndpoint,
|
|
@@ -1102,11 +1106,13 @@ const listFunctions = (options) => (options?.client ?? client).get({
|
|
|
1102
1106
|
* attempt, and sent to the runtime so stack traces can resolve to
|
|
1103
1107
|
* original source files.
|
|
1104
1108
|
*
|
|
1105
|
-
* **
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1109
|
+
* **Routing.** On successful deploy, the function code is live
|
|
1110
|
+
* in the runtime, but inbound mail will not reach it until at
|
|
1111
|
+
* least one route is bound. Routes are managed from the Primitive
|
|
1112
|
+
* dashboard. A `deploy_status` of `deployed` means the script is
|
|
1113
|
+
* installed, not that the function is receiving mail. The
|
|
1114
|
+
* internal runtime URL is not returned by the API and is not a
|
|
1115
|
+
* customer-facing integration surface.
|
|
1110
1116
|
*
|
|
1111
1117
|
* **Secrets.** New functions ship with the managed secrets
|
|
1112
1118
|
* (`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,
|
|
@@ -1131,7 +1137,7 @@ const createFunction = (options) => (options.client ?? client).post({
|
|
|
1131
1137
|
* Delete a function
|
|
1132
1138
|
*
|
|
1133
1139
|
* Soft-deletes the function row, removes the script from the edge
|
|
1134
|
-
* runtime, and deactivates
|
|
1140
|
+
* runtime, and deactivates any route bound to this function so no
|
|
1135
1141
|
* further inbound mail is delivered. Past deploy history,
|
|
1136
1142
|
* invocations, and logs are retained.
|
|
1137
1143
|
*
|
|
@@ -1248,6 +1254,80 @@ const getFunctionTestRunTrace = (options) => (options.client ?? client).get({
|
|
|
1248
1254
|
...options
|
|
1249
1255
|
});
|
|
1250
1256
|
/**
|
|
1257
|
+
* Get the org's function routing topology
|
|
1258
|
+
*
|
|
1259
|
+
* Returns a single snapshot of how inbound mail is routed across
|
|
1260
|
+
* this org's active domains and functions: which active domain has
|
|
1261
|
+
* which function bound, the org's fallback function (if any), and
|
|
1262
|
+
* every deployed function with no route bound. Use this to answer
|
|
1263
|
+
* "which of my functions actually receive mail?" diagnostically.
|
|
1264
|
+
*
|
|
1265
|
+
*/
|
|
1266
|
+
const getOrgRoutingTopology = (options) => (options?.client ?? client).get({
|
|
1267
|
+
security: [{
|
|
1268
|
+
scheme: "bearer",
|
|
1269
|
+
type: "http"
|
|
1270
|
+
}],
|
|
1271
|
+
url: "/functions/routing-topology",
|
|
1272
|
+
...options
|
|
1273
|
+
});
|
|
1274
|
+
/**
|
|
1275
|
+
* Get a function's current route binding
|
|
1276
|
+
*
|
|
1277
|
+
* Returns the endpoint binding for the function, or null when no
|
|
1278
|
+
* route is currently bound. The binding identifies whether the
|
|
1279
|
+
* function receives mail for a specific domain (scoped) or for any
|
|
1280
|
+
* active domain that has no scoped binding (fallback).
|
|
1281
|
+
*
|
|
1282
|
+
*/
|
|
1283
|
+
const getFunctionRouting = (options) => (options.client ?? client).get({
|
|
1284
|
+
security: [{
|
|
1285
|
+
scheme: "bearer",
|
|
1286
|
+
type: "http"
|
|
1287
|
+
}],
|
|
1288
|
+
url: "/functions/{id}/routing",
|
|
1289
|
+
...options
|
|
1290
|
+
});
|
|
1291
|
+
/**
|
|
1292
|
+
* Unbind any route from a function
|
|
1293
|
+
*
|
|
1294
|
+
* Deactivates every active endpoint bound to this function. The
|
|
1295
|
+
* function stays deployed but stops receiving inbound mail. Safe
|
|
1296
|
+
* to call when no route is currently bound (no-op).
|
|
1297
|
+
*
|
|
1298
|
+
*/
|
|
1299
|
+
const unsetFunctionRoute = (options) => (options.client ?? client).delete({
|
|
1300
|
+
security: [{
|
|
1301
|
+
scheme: "bearer",
|
|
1302
|
+
type: "http"
|
|
1303
|
+
}],
|
|
1304
|
+
url: "/functions/{id}/route",
|
|
1305
|
+
...options
|
|
1306
|
+
});
|
|
1307
|
+
/**
|
|
1308
|
+
* Bind a route to a function
|
|
1309
|
+
*
|
|
1310
|
+
* Binds inbound mail to this function. The route target is either
|
|
1311
|
+
* a specific verified domain (scoped) or the org's fallback (any
|
|
1312
|
+
* active domain with no scoped binding). If another function is
|
|
1313
|
+
* already bound at the target, returns a `conflict` envelope
|
|
1314
|
+
* describing the holder; re-issue with `takeover: true` to
|
|
1315
|
+
* deactivate that prior binding and install this one.
|
|
1316
|
+
*
|
|
1317
|
+
*/
|
|
1318
|
+
const setFunctionRoute = (options) => (options.client ?? client).put({
|
|
1319
|
+
security: [{
|
|
1320
|
+
scheme: "bearer",
|
|
1321
|
+
type: "http"
|
|
1322
|
+
}],
|
|
1323
|
+
url: "/functions/{id}/route",
|
|
1324
|
+
...options,
|
|
1325
|
+
headers: {
|
|
1326
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1327
|
+
...options.headers
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
/**
|
|
1251
1331
|
* List a function's secrets
|
|
1252
1332
|
*
|
|
1253
1333
|
* Returns metadata for every secret bound to the function, with
|
|
@@ -3029,7 +3109,7 @@ const openapiDocument = {
|
|
|
3029
3109
|
"post": {
|
|
3030
3110
|
"operationId": "createFunction",
|
|
3031
3111
|
"summary": "Deploy a function",
|
|
3032
|
-
"description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to an `email.received` event (see\n`EmailReceivedEvent` and the Webhook payload section for the full\nschema). Code is bundled before being uploaded; ship a single\nself-contained file rather than relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**
|
|
3112
|
+
"description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to an `email.received` event (see\n`EmailReceivedEvent` and the Webhook payload section for the full\nschema). Code is bundled before being uploaded; ship a single\nself-contained file rather than relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Routing.** On successful deploy, the function code is live\nin the runtime, but inbound mail will not reach it until at\nleast one route is bound. Routes are managed from the Primitive\ndashboard. A `deploy_status` of `deployed` means the script is\ninstalled, not that the function is receiving mail. The\ninternal runtime URL is not returned by the API and is not a\ncustomer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
|
|
3033
3113
|
"tags": ["Functions"],
|
|
3034
3114
|
"requestBody": {
|
|
3035
3115
|
"required": true,
|
|
@@ -3108,7 +3188,7 @@ const openapiDocument = {
|
|
|
3108
3188
|
"delete": {
|
|
3109
3189
|
"operationId": "deleteFunction",
|
|
3110
3190
|
"summary": "Delete a function",
|
|
3111
|
-
"description": "Soft-deletes the function row, removes the script from the edge\nruntime, and deactivates
|
|
3191
|
+
"description": "Soft-deletes the function row, removes the script from the edge\nruntime, and deactivates any route bound to this function so no\nfurther inbound mail is delivered. Past deploy history,\ninvocations, and logs are retained.\n\nReturns 502 if the runtime delete fails partway; the function\nrow stays in place and the call is safe to retry until it\nsucceeds.\n",
|
|
3112
3192
|
"tags": ["Functions"],
|
|
3113
3193
|
"responses": {
|
|
3114
3194
|
"200": { "$ref": "#/components/responses/Deleted" },
|
|
@@ -3193,6 +3273,92 @@ const openapiDocument = {
|
|
|
3193
3273
|
}
|
|
3194
3274
|
}
|
|
3195
3275
|
},
|
|
3276
|
+
"/functions/routing-topology": { "get": {
|
|
3277
|
+
"operationId": "getOrgRoutingTopology",
|
|
3278
|
+
"summary": "Get the org's function routing topology",
|
|
3279
|
+
"description": "Returns a single snapshot of how inbound mail is routed across\nthis org's active domains and functions: which active domain has\nwhich function bound, the org's fallback function (if any), and\nevery deployed function with no route bound. Use this to answer\n\"which of my functions actually receive mail?\" diagnostically.\n",
|
|
3280
|
+
"tags": ["Functions"],
|
|
3281
|
+
"responses": {
|
|
3282
|
+
"200": {
|
|
3283
|
+
"description": "Routing topology",
|
|
3284
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
3285
|
+
"type": "object",
|
|
3286
|
+
"properties": { "data": { "$ref": "#/components/schemas/RoutingTopology" } }
|
|
3287
|
+
}] } } }
|
|
3288
|
+
},
|
|
3289
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3290
|
+
"403": { "$ref": "#/components/responses/Forbidden" }
|
|
3291
|
+
}
|
|
3292
|
+
} },
|
|
3293
|
+
"/functions/{id}/routing": {
|
|
3294
|
+
"parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
|
|
3295
|
+
"get": {
|
|
3296
|
+
"operationId": "getFunctionRouting",
|
|
3297
|
+
"summary": "Get a function's current route binding",
|
|
3298
|
+
"description": "Returns the endpoint binding for the function, or null when no\nroute is currently bound. The binding identifies whether the\nfunction receives mail for a specific domain (scoped) or for any\nactive domain that has no scoped binding (fallback).\n",
|
|
3299
|
+
"tags": ["Functions"],
|
|
3300
|
+
"responses": {
|
|
3301
|
+
"200": {
|
|
3302
|
+
"description": "Function routing",
|
|
3303
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
3304
|
+
"type": "object",
|
|
3305
|
+
"properties": { "data": { "oneOf": [{ "$ref": "#/components/schemas/FunctionRouting" }, { "type": "null" }] } }
|
|
3306
|
+
}] } } }
|
|
3307
|
+
},
|
|
3308
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3309
|
+
"404": { "$ref": "#/components/responses/NotFound" }
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
},
|
|
3313
|
+
"/functions/{id}/route": {
|
|
3314
|
+
"parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
|
|
3315
|
+
"put": {
|
|
3316
|
+
"operationId": "setFunctionRoute",
|
|
3317
|
+
"summary": "Bind a route to a function",
|
|
3318
|
+
"description": "Binds inbound mail to this function. The route target is either\na specific verified domain (scoped) or the org's fallback (any\nactive domain with no scoped binding). If another function is\nalready bound at the target, returns a `conflict` envelope\ndescribing the holder; re-issue with `takeover: true` to\ndeactivate that prior binding and install this one.\n",
|
|
3319
|
+
"tags": ["Functions"],
|
|
3320
|
+
"requestBody": {
|
|
3321
|
+
"required": true,
|
|
3322
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/FunctionRouteBody" } } }
|
|
3323
|
+
},
|
|
3324
|
+
"responses": {
|
|
3325
|
+
"200": {
|
|
3326
|
+
"description": "Route bound, or conflict requiring takeover",
|
|
3327
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
3328
|
+
"type": "object",
|
|
3329
|
+
"properties": { "data": { "$ref": "#/components/schemas/FunctionRouteResult" } }
|
|
3330
|
+
}] } } }
|
|
3331
|
+
},
|
|
3332
|
+
"400": { "$ref": "#/components/responses/ValidationError" },
|
|
3333
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3334
|
+
"404": { "$ref": "#/components/responses/NotFound" }
|
|
3335
|
+
}
|
|
3336
|
+
},
|
|
3337
|
+
"delete": {
|
|
3338
|
+
"operationId": "unsetFunctionRoute",
|
|
3339
|
+
"summary": "Unbind any route from a function",
|
|
3340
|
+
"description": "Deactivates every active endpoint bound to this function. The\nfunction stays deployed but stops receiving inbound mail. Safe\nto call when no route is currently bound (no-op).\n",
|
|
3341
|
+
"tags": ["Functions"],
|
|
3342
|
+
"responses": {
|
|
3343
|
+
"200": {
|
|
3344
|
+
"description": "Route unbound",
|
|
3345
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
3346
|
+
"type": "object",
|
|
3347
|
+
"properties": { "data": {
|
|
3348
|
+
"type": "object",
|
|
3349
|
+
"properties": { "unrouted": {
|
|
3350
|
+
"type": "boolean",
|
|
3351
|
+
"enum": [true]
|
|
3352
|
+
} },
|
|
3353
|
+
"required": ["unrouted"]
|
|
3354
|
+
} }
|
|
3355
|
+
}] } } }
|
|
3356
|
+
},
|
|
3357
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3358
|
+
"404": { "$ref": "#/components/responses/NotFound" }
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
},
|
|
3196
3362
|
"/functions/{id}/secrets": {
|
|
3197
3363
|
"parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
|
|
3198
3364
|
"get": {
|
|
@@ -6718,6 +6884,178 @@ const openapiDocument = {
|
|
|
6718
6884
|
"trace_url"
|
|
6719
6885
|
]
|
|
6720
6886
|
},
|
|
6887
|
+
"FunctionRouting": {
|
|
6888
|
+
"type": "object",
|
|
6889
|
+
"description": "A single route binding for a function. `domain` is null when the\nbinding is the org's fallback (any active domain without a scoped\nbinding); otherwise it carries the scoped domain. `rules` is\nreserved for future routing predicates.\n",
|
|
6890
|
+
"properties": {
|
|
6891
|
+
"endpoint_id": {
|
|
6892
|
+
"type": "string",
|
|
6893
|
+
"format": "uuid"
|
|
6894
|
+
},
|
|
6895
|
+
"enabled": { "type": "boolean" },
|
|
6896
|
+
"domain": {
|
|
6897
|
+
"type": ["object", "null"],
|
|
6898
|
+
"properties": {
|
|
6899
|
+
"id": {
|
|
6900
|
+
"type": "string",
|
|
6901
|
+
"format": "uuid"
|
|
6902
|
+
},
|
|
6903
|
+
"name": { "type": ["string", "null"] }
|
|
6904
|
+
},
|
|
6905
|
+
"required": ["id"]
|
|
6906
|
+
},
|
|
6907
|
+
"rules": {
|
|
6908
|
+
"type": "object",
|
|
6909
|
+
"description": "Future routing predicates. Currently empty."
|
|
6910
|
+
},
|
|
6911
|
+
"delivery_count": { "type": "integer" },
|
|
6912
|
+
"success_count": { "type": "integer" },
|
|
6913
|
+
"failure_count": { "type": "integer" },
|
|
6914
|
+
"consecutive_fails": { "type": "integer" },
|
|
6915
|
+
"last_delivery_at": {
|
|
6916
|
+
"type": ["string", "null"],
|
|
6917
|
+
"format": "date-time"
|
|
6918
|
+
},
|
|
6919
|
+
"last_success_at": {
|
|
6920
|
+
"type": ["string", "null"],
|
|
6921
|
+
"format": "date-time"
|
|
6922
|
+
},
|
|
6923
|
+
"last_failure_at": {
|
|
6924
|
+
"type": ["string", "null"],
|
|
6925
|
+
"format": "date-time"
|
|
6926
|
+
}
|
|
6927
|
+
},
|
|
6928
|
+
"required": [
|
|
6929
|
+
"endpoint_id",
|
|
6930
|
+
"enabled",
|
|
6931
|
+
"domain",
|
|
6932
|
+
"rules"
|
|
6933
|
+
]
|
|
6934
|
+
},
|
|
6935
|
+
"RoutingTopology": {
|
|
6936
|
+
"type": "object",
|
|
6937
|
+
"description": "Org-wide map of function routing: which domain points at which\nfunction, the org's fallback binding (if any), and every\ndeployed function with no route currently bound.\n",
|
|
6938
|
+
"properties": {
|
|
6939
|
+
"domains": {
|
|
6940
|
+
"type": "array",
|
|
6941
|
+
"items": {
|
|
6942
|
+
"type": "object",
|
|
6943
|
+
"properties": {
|
|
6944
|
+
"domain_id": {
|
|
6945
|
+
"type": "string",
|
|
6946
|
+
"format": "uuid"
|
|
6947
|
+
},
|
|
6948
|
+
"domain": { "type": "string" },
|
|
6949
|
+
"routed_function": {
|
|
6950
|
+
"type": ["object", "null"],
|
|
6951
|
+
"properties": {
|
|
6952
|
+
"id": {
|
|
6953
|
+
"type": "string",
|
|
6954
|
+
"format": "uuid"
|
|
6955
|
+
},
|
|
6956
|
+
"name": { "type": "string" }
|
|
6957
|
+
},
|
|
6958
|
+
"required": ["id", "name"]
|
|
6959
|
+
},
|
|
6960
|
+
"endpoint_enabled": { "type": ["boolean", "null"] }
|
|
6961
|
+
},
|
|
6962
|
+
"required": [
|
|
6963
|
+
"domain_id",
|
|
6964
|
+
"domain",
|
|
6965
|
+
"routed_function",
|
|
6966
|
+
"endpoint_enabled"
|
|
6967
|
+
]
|
|
6968
|
+
}
|
|
6969
|
+
},
|
|
6970
|
+
"fallback_function": {
|
|
6971
|
+
"type": ["object", "null"],
|
|
6972
|
+
"properties": {
|
|
6973
|
+
"id": {
|
|
6974
|
+
"type": "string",
|
|
6975
|
+
"format": "uuid"
|
|
6976
|
+
},
|
|
6977
|
+
"name": { "type": "string" }
|
|
6978
|
+
},
|
|
6979
|
+
"required": ["id", "name"]
|
|
6980
|
+
},
|
|
6981
|
+
"fallback_enabled": { "type": ["boolean", "null"] },
|
|
6982
|
+
"unrouted_functions": {
|
|
6983
|
+
"type": "array",
|
|
6984
|
+
"items": {
|
|
6985
|
+
"type": "object",
|
|
6986
|
+
"properties": {
|
|
6987
|
+
"id": {
|
|
6988
|
+
"type": "string",
|
|
6989
|
+
"format": "uuid"
|
|
6990
|
+
},
|
|
6991
|
+
"name": { "type": "string" }
|
|
6992
|
+
},
|
|
6993
|
+
"required": ["id", "name"]
|
|
6994
|
+
}
|
|
6995
|
+
}
|
|
6996
|
+
},
|
|
6997
|
+
"required": [
|
|
6998
|
+
"domains",
|
|
6999
|
+
"fallback_function",
|
|
7000
|
+
"fallback_enabled",
|
|
7001
|
+
"unrouted_functions"
|
|
7002
|
+
]
|
|
7003
|
+
},
|
|
7004
|
+
"FunctionRouteBody": {
|
|
7005
|
+
"type": "object",
|
|
7006
|
+
"description": "Target for a route binding. Either a specific verified domain\n(scoped) or the org-wide fallback. Pass `takeover: true` to\ndeactivate any conflicting binding before installing this one.\n",
|
|
7007
|
+
"properties": {
|
|
7008
|
+
"target": { "oneOf": [{
|
|
7009
|
+
"type": "object",
|
|
7010
|
+
"properties": {
|
|
7011
|
+
"kind": {
|
|
7012
|
+
"type": "string",
|
|
7013
|
+
"enum": ["domain"]
|
|
7014
|
+
},
|
|
7015
|
+
"domainId": {
|
|
7016
|
+
"type": "string",
|
|
7017
|
+
"format": "uuid"
|
|
7018
|
+
}
|
|
7019
|
+
},
|
|
7020
|
+
"required": ["kind", "domainId"]
|
|
7021
|
+
}, {
|
|
7022
|
+
"type": "object",
|
|
7023
|
+
"properties": { "kind": {
|
|
7024
|
+
"type": "string",
|
|
7025
|
+
"enum": ["fallback"]
|
|
7026
|
+
} },
|
|
7027
|
+
"required": ["kind"]
|
|
7028
|
+
}] },
|
|
7029
|
+
"takeover": {
|
|
7030
|
+
"type": "boolean",
|
|
7031
|
+
"description": "When true, deactivate any conflicting binding before installing this one."
|
|
7032
|
+
}
|
|
7033
|
+
},
|
|
7034
|
+
"required": ["target"]
|
|
7035
|
+
},
|
|
7036
|
+
"FunctionRouteResult": {
|
|
7037
|
+
"type": "object",
|
|
7038
|
+
"description": "On success, carries the new `routing`. On conflict, carries\n`conflict` describing the binding holder so the caller can\nre-issue with `takeover: true`.\n",
|
|
7039
|
+
"properties": {
|
|
7040
|
+
"routing": { "oneOf": [{ "$ref": "#/components/schemas/FunctionRouting" }, { "type": "null" }] },
|
|
7041
|
+
"conflict": {
|
|
7042
|
+
"type": "object",
|
|
7043
|
+
"properties": {
|
|
7044
|
+
"kind": {
|
|
7045
|
+
"type": "string",
|
|
7046
|
+
"enum": ["http", "function"]
|
|
7047
|
+
},
|
|
7048
|
+
"functionId": {
|
|
7049
|
+
"type": ["string", "null"],
|
|
7050
|
+
"format": "uuid"
|
|
7051
|
+
},
|
|
7052
|
+
"functionName": { "type": ["string", "null"] },
|
|
7053
|
+
"url": { "type": ["string", "null"] }
|
|
7054
|
+
},
|
|
7055
|
+
"required": ["kind"]
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
},
|
|
6721
7059
|
"FunctionTestRunState": {
|
|
6722
7060
|
"type": "string",
|
|
6723
7061
|
"description": "High-level state for a function test run trace:\n - `send_failed`: the initial test email send failed.\n - `waiting_for_send`: the test run was created but no send result has been recorded yet.\n - `waiting_for_inbound`: the test send was queued and the matching inbound email has not arrived yet.\n - `waiting_for_function`: the inbound email arrived and webhook/function processing is still in flight.\n - `completed`: the function webhook completed successfully.\n - `failed`: webhook delivery exhausted retries.\n",
|
|
@@ -10433,7 +10771,7 @@ const operationManifest = [
|
|
|
10433
10771
|
"binaryResponse": false,
|
|
10434
10772
|
"bodyRequired": true,
|
|
10435
10773
|
"command": "create-function",
|
|
10436
|
-
"description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to an `email.received` event (see\n`EmailReceivedEvent` and the Webhook payload section for the full\nschema). Code is bundled before being uploaded; ship a single\nself-contained file rather than relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**
|
|
10774
|
+
"description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to an `email.received` event (see\n`EmailReceivedEvent` and the Webhook payload section for the full\nschema). Code is bundled before being uploaded; ship a single\nself-contained file rather than relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Routing.** On successful deploy, the function code is live\nin the runtime, but inbound mail will not reach it until at\nleast one route is bound. Routes are managed from the Primitive\ndashboard. A `deploy_status` of `deployed` means the script is\ninstalled, not that the function is receiving mail. The\ninternal runtime URL is not returned by the API and is not a\ncustomer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
|
|
10437
10775
|
"hasJsonBody": true,
|
|
10438
10776
|
"method": "POST",
|
|
10439
10777
|
"operationId": "createFunction",
|
|
@@ -10569,7 +10907,7 @@ const operationManifest = [
|
|
|
10569
10907
|
"binaryResponse": false,
|
|
10570
10908
|
"bodyRequired": false,
|
|
10571
10909
|
"command": "delete-function",
|
|
10572
|
-
"description": "Soft-deletes the function row, removes the script from the edge\nruntime, and deactivates
|
|
10910
|
+
"description": "Soft-deletes the function row, removes the script from the edge\nruntime, and deactivates any route bound to this function so no\nfurther inbound mail is delivered. Past deploy history,\ninvocations, and logs are retained.\n\nReturns 502 if the runtime delete fails partway; the function\nrow stays in place and the call is safe to retry until it\nsucceeds.\n",
|
|
10573
10911
|
"hasJsonBody": false,
|
|
10574
10912
|
"method": "DELETE",
|
|
10575
10913
|
"operationId": "deleteFunction",
|
|
@@ -10693,62 +11031,133 @@ const operationManifest = [
|
|
|
10693
11031
|
{
|
|
10694
11032
|
"binaryResponse": false,
|
|
10695
11033
|
"bodyRequired": false,
|
|
10696
|
-
"command": "get-function-
|
|
10697
|
-
"description": "Returns the
|
|
11034
|
+
"command": "get-function-routing",
|
|
11035
|
+
"description": "Returns the endpoint binding for the function, or null when no\nroute is currently bound. The binding identifies whether the\nfunction receives mail for a specific domain (scoped) or for any\nactive domain that has no scoped binding (fallback).\n",
|
|
10698
11036
|
"hasJsonBody": false,
|
|
10699
11037
|
"method": "GET",
|
|
10700
|
-
"operationId": "
|
|
10701
|
-
"path": "/functions/{id}/
|
|
11038
|
+
"operationId": "getFunctionRouting",
|
|
11039
|
+
"path": "/functions/{id}/routing",
|
|
10702
11040
|
"pathParams": [{
|
|
10703
11041
|
"description": "Resource UUID",
|
|
10704
11042
|
"enum": null,
|
|
10705
11043
|
"name": "id",
|
|
10706
11044
|
"required": true,
|
|
10707
11045
|
"type": "string"
|
|
10708
|
-
}, {
|
|
10709
|
-
"description": "Function test run id returned by POST /functions/{id}/test.",
|
|
10710
|
-
"enum": null,
|
|
10711
|
-
"name": "run_id",
|
|
10712
|
-
"required": true,
|
|
10713
|
-
"type": "string"
|
|
10714
11046
|
}],
|
|
10715
11047
|
"queryParams": [],
|
|
10716
11048
|
"requestSchema": null,
|
|
10717
|
-
"responseSchema": {
|
|
11049
|
+
"responseSchema": { "oneOf": [{
|
|
10718
11050
|
"type": "object",
|
|
10719
|
-
"description": "
|
|
11051
|
+
"description": "A single route binding for a function. `domain` is null when the\nbinding is the org's fallback (any active domain without a scoped\nbinding); otherwise it carries the scoped domain. `rules` is\nreserved for future routing predicates.\n",
|
|
10720
11052
|
"properties": {
|
|
10721
|
-
"
|
|
11053
|
+
"endpoint_id": {
|
|
10722
11054
|
"type": "string",
|
|
10723
|
-
"
|
|
10724
|
-
"enum": [
|
|
10725
|
-
"send_failed",
|
|
10726
|
-
"waiting_for_send",
|
|
10727
|
-
"waiting_for_inbound",
|
|
10728
|
-
"waiting_for_function",
|
|
10729
|
-
"completed",
|
|
10730
|
-
"failed"
|
|
10731
|
-
]
|
|
11055
|
+
"format": "uuid"
|
|
10732
11056
|
},
|
|
10733
|
-
"
|
|
10734
|
-
|
|
11057
|
+
"enabled": { "type": "boolean" },
|
|
11058
|
+
"domain": {
|
|
11059
|
+
"type": ["object", "null"],
|
|
10735
11060
|
"properties": {
|
|
10736
11061
|
"id": {
|
|
10737
11062
|
"type": "string",
|
|
10738
11063
|
"format": "uuid"
|
|
10739
11064
|
},
|
|
10740
|
-
"
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
11065
|
+
"name": { "type": ["string", "null"] }
|
|
11066
|
+
},
|
|
11067
|
+
"required": ["id"]
|
|
11068
|
+
},
|
|
11069
|
+
"rules": {
|
|
11070
|
+
"type": "object",
|
|
11071
|
+
"description": "Future routing predicates. Currently empty."
|
|
11072
|
+
},
|
|
11073
|
+
"delivery_count": { "type": "integer" },
|
|
11074
|
+
"success_count": { "type": "integer" },
|
|
11075
|
+
"failure_count": { "type": "integer" },
|
|
11076
|
+
"consecutive_fails": { "type": "integer" },
|
|
11077
|
+
"last_delivery_at": {
|
|
11078
|
+
"type": ["string", "null"],
|
|
11079
|
+
"format": "date-time"
|
|
11080
|
+
},
|
|
11081
|
+
"last_success_at": {
|
|
11082
|
+
"type": ["string", "null"],
|
|
11083
|
+
"format": "date-time"
|
|
11084
|
+
},
|
|
11085
|
+
"last_failure_at": {
|
|
11086
|
+
"type": ["string", "null"],
|
|
11087
|
+
"format": "date-time"
|
|
11088
|
+
}
|
|
11089
|
+
},
|
|
11090
|
+
"required": [
|
|
11091
|
+
"endpoint_id",
|
|
11092
|
+
"enabled",
|
|
11093
|
+
"domain",
|
|
11094
|
+
"rules"
|
|
11095
|
+
]
|
|
11096
|
+
}, { "type": "null" }] },
|
|
11097
|
+
"sdkName": "getFunctionRouting",
|
|
11098
|
+
"summary": "Get a function's current route binding",
|
|
11099
|
+
"tag": "Functions",
|
|
11100
|
+
"tagCommand": "functions"
|
|
11101
|
+
},
|
|
11102
|
+
{
|
|
11103
|
+
"binaryResponse": false,
|
|
11104
|
+
"bodyRequired": false,
|
|
11105
|
+
"command": "get-function-test-run-trace",
|
|
11106
|
+
"description": "Returns the current end-to-end trace for a function test run.\nThe trace is intentionally partial while the test is still in\nflight: callers can poll this endpoint and watch it fill in\nfrom send -> inbound -> webhook deliveries -> outbound\nrequests, logs, and replies.\n",
|
|
11107
|
+
"hasJsonBody": false,
|
|
11108
|
+
"method": "GET",
|
|
11109
|
+
"operationId": "getFunctionTestRunTrace",
|
|
11110
|
+
"path": "/functions/{id}/test-runs/{run_id}/trace",
|
|
11111
|
+
"pathParams": [{
|
|
11112
|
+
"description": "Resource UUID",
|
|
11113
|
+
"enum": null,
|
|
11114
|
+
"name": "id",
|
|
11115
|
+
"required": true,
|
|
11116
|
+
"type": "string"
|
|
11117
|
+
}, {
|
|
11118
|
+
"description": "Function test run id returned by POST /functions/{id}/test.",
|
|
11119
|
+
"enum": null,
|
|
11120
|
+
"name": "run_id",
|
|
11121
|
+
"required": true,
|
|
11122
|
+
"type": "string"
|
|
11123
|
+
}],
|
|
11124
|
+
"queryParams": [],
|
|
11125
|
+
"requestSchema": null,
|
|
11126
|
+
"responseSchema": {
|
|
11127
|
+
"type": "object",
|
|
11128
|
+
"description": "End-to-end trace for a `POST /functions/{id}/test` run. The\nshape is stable, but many nested sections are null or empty\nuntil the corresponding phase has happened.\n",
|
|
11129
|
+
"properties": {
|
|
11130
|
+
"state": {
|
|
11131
|
+
"type": "string",
|
|
11132
|
+
"description": "High-level state for a function test run trace:\n - `send_failed`: the initial test email send failed.\n - `waiting_for_send`: the test run was created but no send result has been recorded yet.\n - `waiting_for_inbound`: the test send was queued and the matching inbound email has not arrived yet.\n - `waiting_for_function`: the inbound email arrived and webhook/function processing is still in flight.\n - `completed`: the function webhook completed successfully.\n - `failed`: webhook delivery exhausted retries.\n",
|
|
11133
|
+
"enum": [
|
|
11134
|
+
"send_failed",
|
|
11135
|
+
"waiting_for_send",
|
|
11136
|
+
"waiting_for_inbound",
|
|
11137
|
+
"waiting_for_function",
|
|
11138
|
+
"completed",
|
|
11139
|
+
"failed"
|
|
11140
|
+
]
|
|
11141
|
+
},
|
|
11142
|
+
"test_run": {
|
|
11143
|
+
"type": "object",
|
|
11144
|
+
"properties": {
|
|
11145
|
+
"id": {
|
|
11146
|
+
"type": "string",
|
|
11147
|
+
"format": "uuid"
|
|
11148
|
+
},
|
|
11149
|
+
"function_id": {
|
|
11150
|
+
"type": "string",
|
|
11151
|
+
"format": "uuid"
|
|
11152
|
+
},
|
|
11153
|
+
"inbound_domain": { "type": "string" },
|
|
11154
|
+
"to": { "type": "string" },
|
|
11155
|
+
"from": { "type": "string" },
|
|
11156
|
+
"subject": { "type": "string" },
|
|
11157
|
+
"poll_since": {
|
|
11158
|
+
"type": "string",
|
|
11159
|
+
"format": "date-time"
|
|
11160
|
+
},
|
|
10752
11161
|
"created_at": {
|
|
10753
11162
|
"type": "string",
|
|
10754
11163
|
"format": "date-time"
|
|
@@ -11123,6 +11532,92 @@ const operationManifest = [
|
|
|
11123
11532
|
"tag": "Functions",
|
|
11124
11533
|
"tagCommand": "functions"
|
|
11125
11534
|
},
|
|
11535
|
+
{
|
|
11536
|
+
"binaryResponse": false,
|
|
11537
|
+
"bodyRequired": false,
|
|
11538
|
+
"command": "get-org-routing-topology",
|
|
11539
|
+
"description": "Returns a single snapshot of how inbound mail is routed across\nthis org's active domains and functions: which active domain has\nwhich function bound, the org's fallback function (if any), and\nevery deployed function with no route bound. Use this to answer\n\"which of my functions actually receive mail?\" diagnostically.\n",
|
|
11540
|
+
"hasJsonBody": false,
|
|
11541
|
+
"method": "GET",
|
|
11542
|
+
"operationId": "getOrgRoutingTopology",
|
|
11543
|
+
"path": "/functions/routing-topology",
|
|
11544
|
+
"pathParams": [],
|
|
11545
|
+
"queryParams": [],
|
|
11546
|
+
"requestSchema": null,
|
|
11547
|
+
"responseSchema": {
|
|
11548
|
+
"type": "object",
|
|
11549
|
+
"description": "Org-wide map of function routing: which domain points at which\nfunction, the org's fallback binding (if any), and every\ndeployed function with no route currently bound.\n",
|
|
11550
|
+
"properties": {
|
|
11551
|
+
"domains": {
|
|
11552
|
+
"type": "array",
|
|
11553
|
+
"items": {
|
|
11554
|
+
"type": "object",
|
|
11555
|
+
"properties": {
|
|
11556
|
+
"domain_id": {
|
|
11557
|
+
"type": "string",
|
|
11558
|
+
"format": "uuid"
|
|
11559
|
+
},
|
|
11560
|
+
"domain": { "type": "string" },
|
|
11561
|
+
"routed_function": {
|
|
11562
|
+
"type": ["object", "null"],
|
|
11563
|
+
"properties": {
|
|
11564
|
+
"id": {
|
|
11565
|
+
"type": "string",
|
|
11566
|
+
"format": "uuid"
|
|
11567
|
+
},
|
|
11568
|
+
"name": { "type": "string" }
|
|
11569
|
+
},
|
|
11570
|
+
"required": ["id", "name"]
|
|
11571
|
+
},
|
|
11572
|
+
"endpoint_enabled": { "type": ["boolean", "null"] }
|
|
11573
|
+
},
|
|
11574
|
+
"required": [
|
|
11575
|
+
"domain_id",
|
|
11576
|
+
"domain",
|
|
11577
|
+
"routed_function",
|
|
11578
|
+
"endpoint_enabled"
|
|
11579
|
+
]
|
|
11580
|
+
}
|
|
11581
|
+
},
|
|
11582
|
+
"fallback_function": {
|
|
11583
|
+
"type": ["object", "null"],
|
|
11584
|
+
"properties": {
|
|
11585
|
+
"id": {
|
|
11586
|
+
"type": "string",
|
|
11587
|
+
"format": "uuid"
|
|
11588
|
+
},
|
|
11589
|
+
"name": { "type": "string" }
|
|
11590
|
+
},
|
|
11591
|
+
"required": ["id", "name"]
|
|
11592
|
+
},
|
|
11593
|
+
"fallback_enabled": { "type": ["boolean", "null"] },
|
|
11594
|
+
"unrouted_functions": {
|
|
11595
|
+
"type": "array",
|
|
11596
|
+
"items": {
|
|
11597
|
+
"type": "object",
|
|
11598
|
+
"properties": {
|
|
11599
|
+
"id": {
|
|
11600
|
+
"type": "string",
|
|
11601
|
+
"format": "uuid"
|
|
11602
|
+
},
|
|
11603
|
+
"name": { "type": "string" }
|
|
11604
|
+
},
|
|
11605
|
+
"required": ["id", "name"]
|
|
11606
|
+
}
|
|
11607
|
+
}
|
|
11608
|
+
},
|
|
11609
|
+
"required": [
|
|
11610
|
+
"domains",
|
|
11611
|
+
"fallback_function",
|
|
11612
|
+
"fallback_enabled",
|
|
11613
|
+
"unrouted_functions"
|
|
11614
|
+
]
|
|
11615
|
+
},
|
|
11616
|
+
"sdkName": "getOrgRoutingTopology",
|
|
11617
|
+
"summary": "Get the org's function routing topology",
|
|
11618
|
+
"tag": "Functions",
|
|
11619
|
+
"tagCommand": "functions"
|
|
11620
|
+
},
|
|
11126
11621
|
{
|
|
11127
11622
|
"binaryResponse": false,
|
|
11128
11623
|
"bodyRequired": false,
|
|
@@ -11342,6 +11837,130 @@ const operationManifest = [
|
|
|
11342
11837
|
"tag": "Functions",
|
|
11343
11838
|
"tagCommand": "functions"
|
|
11344
11839
|
},
|
|
11840
|
+
{
|
|
11841
|
+
"binaryResponse": false,
|
|
11842
|
+
"bodyRequired": true,
|
|
11843
|
+
"command": "set-function-route",
|
|
11844
|
+
"description": "Binds inbound mail to this function. The route target is either\na specific verified domain (scoped) or the org's fallback (any\nactive domain with no scoped binding). If another function is\nalready bound at the target, returns a `conflict` envelope\ndescribing the holder; re-issue with `takeover: true` to\ndeactivate that prior binding and install this one.\n",
|
|
11845
|
+
"hasJsonBody": true,
|
|
11846
|
+
"method": "PUT",
|
|
11847
|
+
"operationId": "setFunctionRoute",
|
|
11848
|
+
"path": "/functions/{id}/route",
|
|
11849
|
+
"pathParams": [{
|
|
11850
|
+
"description": "Resource UUID",
|
|
11851
|
+
"enum": null,
|
|
11852
|
+
"name": "id",
|
|
11853
|
+
"required": true,
|
|
11854
|
+
"type": "string"
|
|
11855
|
+
}],
|
|
11856
|
+
"queryParams": [],
|
|
11857
|
+
"requestSchema": {
|
|
11858
|
+
"type": "object",
|
|
11859
|
+
"description": "Target for a route binding. Either a specific verified domain\n(scoped) or the org-wide fallback. Pass `takeover: true` to\ndeactivate any conflicting binding before installing this one.\n",
|
|
11860
|
+
"properties": {
|
|
11861
|
+
"target": { "oneOf": [{
|
|
11862
|
+
"type": "object",
|
|
11863
|
+
"properties": {
|
|
11864
|
+
"kind": {
|
|
11865
|
+
"type": "string",
|
|
11866
|
+
"enum": ["domain"]
|
|
11867
|
+
},
|
|
11868
|
+
"domainId": {
|
|
11869
|
+
"type": "string",
|
|
11870
|
+
"format": "uuid"
|
|
11871
|
+
}
|
|
11872
|
+
},
|
|
11873
|
+
"required": ["kind", "domainId"]
|
|
11874
|
+
}, {
|
|
11875
|
+
"type": "object",
|
|
11876
|
+
"properties": { "kind": {
|
|
11877
|
+
"type": "string",
|
|
11878
|
+
"enum": ["fallback"]
|
|
11879
|
+
} },
|
|
11880
|
+
"required": ["kind"]
|
|
11881
|
+
}] },
|
|
11882
|
+
"takeover": {
|
|
11883
|
+
"type": "boolean",
|
|
11884
|
+
"description": "When true, deactivate any conflicting binding before installing this one."
|
|
11885
|
+
}
|
|
11886
|
+
},
|
|
11887
|
+
"required": ["target"]
|
|
11888
|
+
},
|
|
11889
|
+
"responseSchema": {
|
|
11890
|
+
"type": "object",
|
|
11891
|
+
"description": "On success, carries the new `routing`. On conflict, carries\n`conflict` describing the binding holder so the caller can\nre-issue with `takeover: true`.\n",
|
|
11892
|
+
"properties": {
|
|
11893
|
+
"routing": { "oneOf": [{
|
|
11894
|
+
"type": "object",
|
|
11895
|
+
"description": "A single route binding for a function. `domain` is null when the\nbinding is the org's fallback (any active domain without a scoped\nbinding); otherwise it carries the scoped domain. `rules` is\nreserved for future routing predicates.\n",
|
|
11896
|
+
"properties": {
|
|
11897
|
+
"endpoint_id": {
|
|
11898
|
+
"type": "string",
|
|
11899
|
+
"format": "uuid"
|
|
11900
|
+
},
|
|
11901
|
+
"enabled": { "type": "boolean" },
|
|
11902
|
+
"domain": {
|
|
11903
|
+
"type": ["object", "null"],
|
|
11904
|
+
"properties": {
|
|
11905
|
+
"id": {
|
|
11906
|
+
"type": "string",
|
|
11907
|
+
"format": "uuid"
|
|
11908
|
+
},
|
|
11909
|
+
"name": { "type": ["string", "null"] }
|
|
11910
|
+
},
|
|
11911
|
+
"required": ["id"]
|
|
11912
|
+
},
|
|
11913
|
+
"rules": {
|
|
11914
|
+
"type": "object",
|
|
11915
|
+
"description": "Future routing predicates. Currently empty."
|
|
11916
|
+
},
|
|
11917
|
+
"delivery_count": { "type": "integer" },
|
|
11918
|
+
"success_count": { "type": "integer" },
|
|
11919
|
+
"failure_count": { "type": "integer" },
|
|
11920
|
+
"consecutive_fails": { "type": "integer" },
|
|
11921
|
+
"last_delivery_at": {
|
|
11922
|
+
"type": ["string", "null"],
|
|
11923
|
+
"format": "date-time"
|
|
11924
|
+
},
|
|
11925
|
+
"last_success_at": {
|
|
11926
|
+
"type": ["string", "null"],
|
|
11927
|
+
"format": "date-time"
|
|
11928
|
+
},
|
|
11929
|
+
"last_failure_at": {
|
|
11930
|
+
"type": ["string", "null"],
|
|
11931
|
+
"format": "date-time"
|
|
11932
|
+
}
|
|
11933
|
+
},
|
|
11934
|
+
"required": [
|
|
11935
|
+
"endpoint_id",
|
|
11936
|
+
"enabled",
|
|
11937
|
+
"domain",
|
|
11938
|
+
"rules"
|
|
11939
|
+
]
|
|
11940
|
+
}, { "type": "null" }] },
|
|
11941
|
+
"conflict": {
|
|
11942
|
+
"type": "object",
|
|
11943
|
+
"properties": {
|
|
11944
|
+
"kind": {
|
|
11945
|
+
"type": "string",
|
|
11946
|
+
"enum": ["http", "function"]
|
|
11947
|
+
},
|
|
11948
|
+
"functionId": {
|
|
11949
|
+
"type": ["string", "null"],
|
|
11950
|
+
"format": "uuid"
|
|
11951
|
+
},
|
|
11952
|
+
"functionName": { "type": ["string", "null"] },
|
|
11953
|
+
"url": { "type": ["string", "null"] }
|
|
11954
|
+
},
|
|
11955
|
+
"required": ["kind"]
|
|
11956
|
+
}
|
|
11957
|
+
}
|
|
11958
|
+
},
|
|
11959
|
+
"sdkName": "setFunctionRoute",
|
|
11960
|
+
"summary": "Bind a route to a function",
|
|
11961
|
+
"tag": "Functions",
|
|
11962
|
+
"tagCommand": "functions"
|
|
11963
|
+
},
|
|
11345
11964
|
{
|
|
11346
11965
|
"binaryResponse": false,
|
|
11347
11966
|
"bodyRequired": true,
|
|
@@ -11495,6 +12114,37 @@ const operationManifest = [
|
|
|
11495
12114
|
"tag": "Functions",
|
|
11496
12115
|
"tagCommand": "functions"
|
|
11497
12116
|
},
|
|
12117
|
+
{
|
|
12118
|
+
"binaryResponse": false,
|
|
12119
|
+
"bodyRequired": false,
|
|
12120
|
+
"command": "unset-function-route",
|
|
12121
|
+
"description": "Deactivates every active endpoint bound to this function. The\nfunction stays deployed but stops receiving inbound mail. Safe\nto call when no route is currently bound (no-op).\n",
|
|
12122
|
+
"hasJsonBody": false,
|
|
12123
|
+
"method": "DELETE",
|
|
12124
|
+
"operationId": "unsetFunctionRoute",
|
|
12125
|
+
"path": "/functions/{id}/route",
|
|
12126
|
+
"pathParams": [{
|
|
12127
|
+
"description": "Resource UUID",
|
|
12128
|
+
"enum": null,
|
|
12129
|
+
"name": "id",
|
|
12130
|
+
"required": true,
|
|
12131
|
+
"type": "string"
|
|
12132
|
+
}],
|
|
12133
|
+
"queryParams": [],
|
|
12134
|
+
"requestSchema": null,
|
|
12135
|
+
"responseSchema": {
|
|
12136
|
+
"type": "object",
|
|
12137
|
+
"properties": { "unrouted": {
|
|
12138
|
+
"type": "boolean",
|
|
12139
|
+
"enum": [true]
|
|
12140
|
+
} },
|
|
12141
|
+
"required": ["unrouted"]
|
|
12142
|
+
},
|
|
12143
|
+
"sdkName": "unsetFunctionRoute",
|
|
12144
|
+
"summary": "Unbind any route from a function",
|
|
12145
|
+
"tag": "Functions",
|
|
12146
|
+
"tagCommand": "functions"
|
|
12147
|
+
},
|
|
11498
12148
|
{
|
|
11499
12149
|
"binaryResponse": false,
|
|
11500
12150
|
"bodyRequired": true,
|
|
@@ -13415,6 +14065,10 @@ async function createAuthenticatedCliApiClient(params) {
|
|
|
13415
14065
|
apiBaseUrl: requestConfig.apiBaseUrl,
|
|
13416
14066
|
configDir: params.configDir
|
|
13417
14067
|
});
|
|
14068
|
+
if (auth.apiKey === void 0) {
|
|
14069
|
+
const hint = detectPrimitiveKeyEnvMisname(params.env ?? process.env);
|
|
14070
|
+
if (hint) process.stderr.write(`${hint}\n`);
|
|
14071
|
+
}
|
|
13418
14072
|
if (auth.source === "stored" && auth.credentials) {
|
|
13419
14073
|
const refreshed = await refreshStoredCliCredentials({
|
|
13420
14074
|
apiBaseUrl: auth.apiBaseUrl,
|
|
@@ -13810,8 +14464,11 @@ const RESERVED_FLAG_NAMES = new Set([
|
|
|
13810
14464
|
"envelope",
|
|
13811
14465
|
"output"
|
|
13812
14466
|
]);
|
|
13813
|
-
function bodyFieldFlag(field) {
|
|
13814
|
-
const common = {
|
|
14467
|
+
function bodyFieldFlag(field, aliases) {
|
|
14468
|
+
const common = {
|
|
14469
|
+
description: field.description || field.name,
|
|
14470
|
+
...aliases && aliases.length > 0 ? { aliases } : {}
|
|
14471
|
+
};
|
|
13815
14472
|
if (field.kind === "boolean") return Flags.boolean({
|
|
13816
14473
|
...common,
|
|
13817
14474
|
allowNo: true
|
|
@@ -13853,12 +14510,13 @@ function buildFlags(operation) {
|
|
|
13853
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." });
|
|
13854
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." });
|
|
13855
14512
|
const bodyFields = extractBodyFields(operation.requestSchema);
|
|
14513
|
+
const aliasesForOperation = OPERATION_FLAG_ALIASES[operation.sdkName];
|
|
13856
14514
|
for (const field of bodyFields) {
|
|
13857
14515
|
if (field.kind === "complex") continue;
|
|
13858
14516
|
const name = flagName(field.name);
|
|
13859
14517
|
if (RESERVED_FLAG_NAMES.has(name)) continue;
|
|
13860
14518
|
if (flags[name] !== void 0) continue;
|
|
13861
|
-
flags[name] = bodyFieldFlag(field);
|
|
14519
|
+
flags[name] = bodyFieldFlag(field, aliasesForOperation?.[field.name]);
|
|
13862
14520
|
bodyFieldFlagToProperty.set(name, field.name);
|
|
13863
14521
|
}
|
|
13864
14522
|
}
|
|
@@ -13907,8 +14565,11 @@ const OPERATION_HINTS = {
|
|
|
13907
14565
|
createFunction: "Tip: prefer `primitive functions deploy --name <name> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
13908
14566
|
updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
13909
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.",
|
|
13910
|
-
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."
|
|
13911
14571
|
};
|
|
14572
|
+
const OPERATION_FLAG_ALIASES = { verifyAgentSignup: { verification_code: ["code"] } };
|
|
13912
14573
|
function createOperationCommand(operation) {
|
|
13913
14574
|
const { flags, bodyFieldFlagToProperty } = buildFlags(operation);
|
|
13914
14575
|
const baseDescription = operation.description !== null && operation.description !== void 0 ? canonicalizeCliReferences(operation.description) : `${operation.method} ${operation.path}`;
|
|
@@ -15374,9 +16035,7 @@ function checkApiKey(opts) {
|
|
|
15374
16035
|
message: "provided but does not start with prim_",
|
|
15375
16036
|
hint: "Verify the key is a Primitive API key, not a value from another service."
|
|
15376
16037
|
};
|
|
15377
|
-
|
|
15378
|
-
const primitiveApiKey = env.PRIMITIVE_API_KEY;
|
|
15379
|
-
if ((primitiveKey?.length ?? 0) > 0 && (primitiveApiKey?.length ?? 0) === 0) return {
|
|
16038
|
+
if (detectPrimitiveKeyEnvMisname(env)) return {
|
|
15380
16039
|
status: "fail",
|
|
15381
16040
|
message: "PRIMITIVE_KEY is set but the CLI reads PRIMITIVE_API_KEY",
|
|
15382
16041
|
hint: "Rename your env var, or re-run with PRIMITIVE_API_KEY=$PRIMITIVE_KEY."
|
|
@@ -16603,6 +17262,18 @@ const SECRET_SOURCE_FLAGS_DESCRIPTION = "Safe sources: --secret-from-env KEY rea
|
|
|
16603
17262
|
const SINGLE_SECRET_VALUE_SOURCE_DESCRIPTION = "Instead of --value, use --value-from-env ENV_VAR, --value-file PATH, --value-from-env-file FILE[:KEY], or --stdin to avoid putting the secret value in shell history or process argv. If KEY is omitted from --value-from-env-file, the command's --key is used.";
|
|
16604
17263
|
//#endregion
|
|
16605
17264
|
//#region src/oclif/commands/functions-deploy.ts
|
|
17265
|
+
async function writeRouteStatusHint(apiClient, functionId) {
|
|
17266
|
+
try {
|
|
17267
|
+
const result = await getFunctionRouting({
|
|
17268
|
+
client: apiClient.client,
|
|
17269
|
+
path: { id: functionId },
|
|
17270
|
+
responseStyle: "fields"
|
|
17271
|
+
});
|
|
17272
|
+
if (result.error) return;
|
|
17273
|
+
if (result.data?.data) process.stderr.write("Route bound. Function will receive inbound mail.\n");
|
|
17274
|
+
else process.stderr.write(`Deployed but no route is bound. Inbound mail will not reach this function until you bind one: primitive functions route-set --id ${functionId} --domain <domain-id> (or --fallback)\n`);
|
|
17275
|
+
} catch {}
|
|
17276
|
+
}
|
|
16606
17277
|
async function runDeployWithSecrets(api, params) {
|
|
16607
17278
|
const createResult = await api.createFunction({
|
|
16608
17279
|
code: params.code,
|
|
@@ -16902,10 +17573,11 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
16902
17573
|
const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
|
|
16903
17574
|
process.stderr.write(`Function ${payload.id} deploy failed${detail}\n`);
|
|
16904
17575
|
process.exitCode = 1;
|
|
16905
|
-
}
|
|
17576
|
+
} else await writeRouteStatusHint(apiClient, payload.id);
|
|
16906
17577
|
return;
|
|
16907
17578
|
}
|
|
16908
17579
|
this.log(JSON.stringify(payload, null, 2));
|
|
17580
|
+
await writeRouteStatusHint(apiClient, payload.id);
|
|
16909
17581
|
});
|
|
16910
17582
|
}
|
|
16911
17583
|
async runSourceMode(flags, sourceDir) {
|
|
@@ -16997,10 +17669,11 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
16997
17669
|
const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
|
|
16998
17670
|
process.stderr.write(`Function ${payload.id} deploy failed${detail}\n`);
|
|
16999
17671
|
process.exitCode = 1;
|
|
17000
|
-
}
|
|
17672
|
+
} else await writeRouteStatusHint(apiClient, payload.id);
|
|
17001
17673
|
return;
|
|
17002
17674
|
}
|
|
17003
17675
|
this.log(JSON.stringify(payload, null, 2));
|
|
17676
|
+
await writeRouteStatusHint(apiClient, payload.id);
|
|
17004
17677
|
}
|
|
17005
17678
|
};
|
|
17006
17679
|
//#endregion
|
|
@@ -17011,8 +17684,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
17011
17684
|
name: "Primitive Team",
|
|
17012
17685
|
url: "https://primitive.dev"
|
|
17013
17686
|
};
|
|
17014
|
-
const SDK_VERSION_RANGE = "^0.
|
|
17015
|
-
const CLI_VERSION_RANGE = "^0.
|
|
17687
|
+
const SDK_VERSION_RANGE = "^0.37.0";
|
|
17688
|
+
const CLI_VERSION_RANGE = "^0.37.0";
|
|
17016
17689
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
17017
17690
|
function renderHandler() {
|
|
17018
17691
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -17087,22 +17760,50 @@ function inboundRecipientDomains(event: EmailReceivedEvent): Set<string> {
|
|
|
17087
17760
|
// SMTP recipients instead.
|
|
17088
17761
|
//
|
|
17089
17762
|
// The default check skips:
|
|
17763
|
+
// - bounce notifications, which RFC 5321 requires to use an empty
|
|
17764
|
+
// SMTP envelope sender (MAIL FROM:<>). Replying to a null sender
|
|
17765
|
+
// is forbidden and would itself bounce, producing a
|
|
17766
|
+
// bounce-of-bounce chain. The body header on a bounce typically
|
|
17767
|
+
// reads "From: MAILER-DAEMON@..." which a naive From-only check
|
|
17768
|
+
// would treat as a normal sender, so we gate on the envelope here.
|
|
17090
17769
|
// - direct self-mail where From equals one of the inbound recipients;
|
|
17091
|
-
// - mailer-daemon/postmaster bounces from the same domain as the
|
|
17770
|
+
// - mailer-daemon/postmaster bounces from the same domain as the
|
|
17771
|
+
// inbound, as a backup for bounces that arrive with a non-empty
|
|
17772
|
+
// envelope sender;
|
|
17092
17773
|
// - any address explicitly listed in EXTRA_SELF_ADDRESSES.
|
|
17093
17774
|
//
|
|
17094
|
-
//
|
|
17095
|
-
//
|
|
17096
|
-
//
|
|
17097
|
-
//
|
|
17775
|
+
// Anything with no identifiable sender at all (envelope + From both
|
|
17776
|
+
// empty) is treated as a loop terminator: better to drop one ambiguous
|
|
17777
|
+
// message than to reply blindly and loop on a bounce.
|
|
17778
|
+
//
|
|
17779
|
+
// Extend this helper if you need stricter detection. Common additions
|
|
17780
|
+
// not implemented here today:
|
|
17781
|
+
// - Honor RFC 3834 auto-response headers: skip when an
|
|
17782
|
+
// auto-submitted header is anything other than "no", or when a
|
|
17783
|
+
// list-unsubscribe / precedence: bulk header is present. The
|
|
17784
|
+
// EmailReceivedEvent.email.headers shape does not currently surface
|
|
17785
|
+
// these, so detection requires either a parsed-headers field on
|
|
17786
|
+
// the event or a parse of the raw RFC 822 body.
|
|
17098
17787
|
// - Track Message-ID / In-Reply-To chains to break ping-pong loops
|
|
17099
17788
|
// between two cooperating handlers on different domains.
|
|
17789
|
+
// - Rate-limit replies per sender per hour as a safety net.
|
|
17100
17790
|
export function isLoop(event: EmailReceivedEvent): boolean {
|
|
17791
|
+
// RFC 5321: bounce notifications use the null MAIL FROM (envelope
|
|
17792
|
+
// sender = empty string). Some MTAs report this as "<>" verbatim.
|
|
17793
|
+
// Treat either as an unambiguous bounce signal.
|
|
17794
|
+
const envelopeSender = (event.email.smtp.mail_from || "").trim();
|
|
17795
|
+
if (envelopeSender === "" || envelopeSender === "<>") return true;
|
|
17796
|
+
|
|
17101
17797
|
const fromAddresses = [
|
|
17102
17798
|
...extractEmailAddresses(event.email.headers.from),
|
|
17103
17799
|
...extractEmailAddresses(event.email.smtp.mail_from),
|
|
17104
17800
|
];
|
|
17105
|
-
|
|
17801
|
+
// No identifiable sender across either envelope or header: treat as
|
|
17802
|
+
// a loop terminator. Was return false in the original template; that
|
|
17803
|
+
// returned mail with empty headers straight back into the handler
|
|
17804
|
+
// and let bounces with malformed bodies slip past the bounce guard
|
|
17805
|
+
// above.
|
|
17806
|
+
if (fromAddresses.length === 0) return true;
|
|
17106
17807
|
|
|
17107
17808
|
const inboundAddresses = new Set(inboundRecipientAddresses(event));
|
|
17108
17809
|
const inboundDomains = inboundRecipientDomains(event);
|
|
@@ -17319,6 +18020,23 @@ After the first deploy, copy the returned function id into your shell:
|
|
|
17319
18020
|
export PRIMITIVE_FUNCTION_ID=<fn-id>
|
|
17320
18021
|
\`\`\`
|
|
17321
18022
|
|
|
18023
|
+
## Bind a route
|
|
18024
|
+
|
|
18025
|
+
A deployed Function does not receive inbound mail until a route is
|
|
18026
|
+
bound to it. Without this step, the Function is installed in the
|
|
18027
|
+
runtime but unreachable, and \`functions test\` will refuse to send
|
|
18028
|
+
(returns 422 \`no_endpoint\`).
|
|
18029
|
+
|
|
18030
|
+
\`\`\`
|
|
18031
|
+
primitive functions route-set --id "$PRIMITIVE_FUNCTION_ID" --domain <domain-id>
|
|
18032
|
+
\`\`\`
|
|
18033
|
+
|
|
18034
|
+
Use \`--fallback\` instead of \`--domain\` to bind the Function as the
|
|
18035
|
+
org-wide fallback for any active domain that has no scoped binding.
|
|
18036
|
+
Run \`primitive functions route-get --id "$PRIMITIVE_FUNCTION_ID"\` to
|
|
18037
|
+
inspect the current binding, or \`primitive functions routing-topology\`
|
|
18038
|
+
for the org-wide view of which domain points at which Function.
|
|
18039
|
+
|
|
17322
18040
|
## Prove it works
|
|
17323
18041
|
|
|
17324
18042
|
\`\`\`
|
|
@@ -17561,6 +18279,25 @@ function emitLogRows(rows, jsonl) {
|
|
|
17561
18279
|
process.stdout.write(`${line}\n`);
|
|
17562
18280
|
}
|
|
17563
18281
|
}
|
|
18282
|
+
async function readFunctionInvocations(client, id) {
|
|
18283
|
+
try {
|
|
18284
|
+
const result = await getFunction({
|
|
18285
|
+
client,
|
|
18286
|
+
path: { id },
|
|
18287
|
+
responseStyle: "fields",
|
|
18288
|
+
signal: AbortSignal.timeout(3e3)
|
|
18289
|
+
});
|
|
18290
|
+
if (result.error) return null;
|
|
18291
|
+
const fn = result.data?.data;
|
|
18292
|
+
if (!fn) return null;
|
|
18293
|
+
return {
|
|
18294
|
+
invocations_total: typeof fn.invocations_total === "number" ? fn.invocations_total : 0,
|
|
18295
|
+
invocations_24h: typeof fn.invocations_24h === "number" ? fn.invocations_24h : 0
|
|
18296
|
+
};
|
|
18297
|
+
} catch {
|
|
18298
|
+
return null;
|
|
18299
|
+
}
|
|
18300
|
+
}
|
|
17564
18301
|
var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
|
|
17565
18302
|
static description = "List or follow function execution logs. Defaults to compact text output; use --jsonl for one JSON object per log row.";
|
|
17566
18303
|
static summary = "List or follow a function's execution logs";
|
|
@@ -17659,7 +18396,14 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
|
|
|
17659
18396
|
cursor = page.next_cursor;
|
|
17660
18397
|
}
|
|
17661
18398
|
if (rows.length === 0 && !wroteEmptyHint) {
|
|
17662
|
-
|
|
18399
|
+
let emptyHint;
|
|
18400
|
+
if (flags.follow) emptyHint = hasObservedLogs ? "Waiting for new function logs...\n" : "No function logs yet. Waiting for new rows...\n";
|
|
18401
|
+
else if (flags.cursor) emptyHint = "No more function logs after this cursor.\n";
|
|
18402
|
+
else {
|
|
18403
|
+
const fnInvocations = await readFunctionInvocations(apiClient.client, flags.id);
|
|
18404
|
+
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";
|
|
18405
|
+
}
|
|
18406
|
+
process.stderr.write(emptyHint);
|
|
17663
18407
|
wroteEmptyHint = true;
|
|
17664
18408
|
}
|
|
17665
18409
|
emitLogRows(rows, flags.jsonl);
|
|
@@ -17929,6 +18673,239 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
17929
18673
|
}
|
|
17930
18674
|
};
|
|
17931
18675
|
//#endregion
|
|
18676
|
+
//#region src/oclif/commands/functions-route-get.ts
|
|
18677
|
+
var FunctionsRouteGetCommand = class FunctionsRouteGetCommand extends Command {
|
|
18678
|
+
static description = `Show the current route binding for a function. Returns the binding (domain or fallback, with delivery counters) or null when no route is bound.`;
|
|
18679
|
+
static summary = "Show a function's current route binding";
|
|
18680
|
+
static examples = ["<%= config.bin %> functions route-get --id <fn-id>"];
|
|
18681
|
+
static flags = {
|
|
18682
|
+
"api-key": Flags.string({
|
|
18683
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
18684
|
+
env: "PRIMITIVE_API_KEY"
|
|
18685
|
+
}),
|
|
18686
|
+
"api-base-url": Flags.string({
|
|
18687
|
+
description: API_BASE_URL_FLAG_DESCRIPTION,
|
|
18688
|
+
env: "PRIMITIVE_API_BASE_URL",
|
|
18689
|
+
hidden: true
|
|
18690
|
+
}),
|
|
18691
|
+
id: Flags.string({
|
|
18692
|
+
description: "Function id (UUID) whose route binding to show.",
|
|
18693
|
+
required: true
|
|
18694
|
+
}),
|
|
18695
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
18696
|
+
};
|
|
18697
|
+
async run() {
|
|
18698
|
+
const { flags } = await this.parse(FunctionsRouteGetCommand);
|
|
18699
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
18700
|
+
apiKey: flags["api-key"],
|
|
18701
|
+
apiBaseUrl: flags["api-base-url"],
|
|
18702
|
+
configDir: this.config.configDir
|
|
18703
|
+
});
|
|
18704
|
+
await runWithTiming(flags.time, async () => {
|
|
18705
|
+
const result = await getFunctionRouting({
|
|
18706
|
+
client: apiClient.client,
|
|
18707
|
+
path: { id: flags.id },
|
|
18708
|
+
responseStyle: "fields"
|
|
18709
|
+
});
|
|
18710
|
+
if (result.error) {
|
|
18711
|
+
const payload = extractErrorPayload(result.error);
|
|
18712
|
+
writeErrorWithHints(payload);
|
|
18713
|
+
surfaceUnauthorizedHint({
|
|
18714
|
+
auth,
|
|
18715
|
+
baseUrlOverridden,
|
|
18716
|
+
configDir: this.config.configDir,
|
|
18717
|
+
payload
|
|
18718
|
+
});
|
|
18719
|
+
process.exitCode = 1;
|
|
18720
|
+
return;
|
|
18721
|
+
}
|
|
18722
|
+
this.log(JSON.stringify(result.data.data, null, 2));
|
|
18723
|
+
});
|
|
18724
|
+
}
|
|
18725
|
+
};
|
|
18726
|
+
//#endregion
|
|
18727
|
+
//#region src/oclif/commands/functions-route-set.ts
|
|
18728
|
+
var FunctionsRouteSetCommand = class FunctionsRouteSetCommand extends Command {
|
|
18729
|
+
static description = `Bind inbound mail to a function by setting its route target.
|
|
18730
|
+
|
|
18731
|
+
Exactly one of --domain or --fallback is required. --domain scopes the
|
|
18732
|
+
binding to a single verified inbound domain. --fallback binds the
|
|
18733
|
+
function to any active domain that has no scoped binding of its own.
|
|
18734
|
+
|
|
18735
|
+
If another function is already bound at the target, the API returns a
|
|
18736
|
+
conflict envelope rather than overwriting; re-run with --takeover to
|
|
18737
|
+
deactivate the prior binding before installing this one.`;
|
|
18738
|
+
static summary = "Bind inbound mail to a function";
|
|
18739
|
+
static examples = [
|
|
18740
|
+
"<%= config.bin %> functions route-set --id <fn-id> --domain <domain-id>",
|
|
18741
|
+
"<%= config.bin %> functions route-set --id <fn-id> --fallback",
|
|
18742
|
+
"<%= config.bin %> functions route-set --id <fn-id> --domain <domain-id> --takeover"
|
|
18743
|
+
];
|
|
18744
|
+
static flags = {
|
|
18745
|
+
"api-key": Flags.string({
|
|
18746
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
18747
|
+
env: "PRIMITIVE_API_KEY"
|
|
18748
|
+
}),
|
|
18749
|
+
"api-base-url": Flags.string({
|
|
18750
|
+
description: API_BASE_URL_FLAG_DESCRIPTION,
|
|
18751
|
+
env: "PRIMITIVE_API_BASE_URL",
|
|
18752
|
+
hidden: true
|
|
18753
|
+
}),
|
|
18754
|
+
id: Flags.string({
|
|
18755
|
+
description: "Function id (UUID) to bind a route to.",
|
|
18756
|
+
required: true
|
|
18757
|
+
}),
|
|
18758
|
+
domain: Flags.string({
|
|
18759
|
+
description: "Verified inbound domain id (UUID) to scope this function to. Mutually exclusive with --fallback.",
|
|
18760
|
+
exclusive: ["fallback"]
|
|
18761
|
+
}),
|
|
18762
|
+
fallback: Flags.boolean({
|
|
18763
|
+
description: "Bind this function as the org fallback (any active domain without a scoped binding). Mutually exclusive with --domain.",
|
|
18764
|
+
exclusive: ["domain"]
|
|
18765
|
+
}),
|
|
18766
|
+
takeover: Flags.boolean({ description: "Deactivate any conflicting binding before installing this one. Without this flag, the API returns a `conflict` envelope when another function is already bound at the target." }),
|
|
18767
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
18768
|
+
};
|
|
18769
|
+
async run() {
|
|
18770
|
+
const { flags } = await this.parse(FunctionsRouteSetCommand);
|
|
18771
|
+
if (!flags.domain && !flags.fallback) {
|
|
18772
|
+
process.stderr.write("Provide exactly one of --domain (scoped binding) or --fallback (org fallback).\n");
|
|
18773
|
+
process.exitCode = 1;
|
|
18774
|
+
return;
|
|
18775
|
+
}
|
|
18776
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
18777
|
+
apiKey: flags["api-key"],
|
|
18778
|
+
apiBaseUrl: flags["api-base-url"],
|
|
18779
|
+
configDir: this.config.configDir
|
|
18780
|
+
});
|
|
18781
|
+
await runWithTiming(flags.time, async () => {
|
|
18782
|
+
const target = flags.domain ? {
|
|
18783
|
+
kind: "domain",
|
|
18784
|
+
domainId: flags.domain
|
|
18785
|
+
} : { kind: "fallback" };
|
|
18786
|
+
const result = await setFunctionRoute({
|
|
18787
|
+
client: apiClient.client,
|
|
18788
|
+
path: { id: flags.id },
|
|
18789
|
+
body: {
|
|
18790
|
+
target,
|
|
18791
|
+
...flags.takeover ? { takeover: true } : {}
|
|
18792
|
+
},
|
|
18793
|
+
responseStyle: "fields"
|
|
18794
|
+
});
|
|
18795
|
+
if (result.error) {
|
|
18796
|
+
const payload = extractErrorPayload(result.error);
|
|
18797
|
+
writeErrorWithHints(payload);
|
|
18798
|
+
surfaceUnauthorizedHint({
|
|
18799
|
+
auth,
|
|
18800
|
+
baseUrlOverridden,
|
|
18801
|
+
configDir: this.config.configDir,
|
|
18802
|
+
payload
|
|
18803
|
+
});
|
|
18804
|
+
process.exitCode = 1;
|
|
18805
|
+
return;
|
|
18806
|
+
}
|
|
18807
|
+
this.log(JSON.stringify(result.data.data, null, 2));
|
|
18808
|
+
});
|
|
18809
|
+
}
|
|
18810
|
+
};
|
|
18811
|
+
//#endregion
|
|
18812
|
+
//#region src/oclif/commands/functions-route-unset.ts
|
|
18813
|
+
var FunctionsRouteUnsetCommand = class FunctionsRouteUnsetCommand extends Command {
|
|
18814
|
+
static description = `Unbind every active route from a function. The function stays deployed but stops receiving inbound mail. Safe to call when no route is currently bound.`;
|
|
18815
|
+
static summary = "Unbind any route from a function";
|
|
18816
|
+
static examples = ["<%= config.bin %> functions route-unset --id <fn-id>"];
|
|
18817
|
+
static flags = {
|
|
18818
|
+
"api-key": Flags.string({
|
|
18819
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
18820
|
+
env: "PRIMITIVE_API_KEY"
|
|
18821
|
+
}),
|
|
18822
|
+
"api-base-url": Flags.string({
|
|
18823
|
+
description: API_BASE_URL_FLAG_DESCRIPTION,
|
|
18824
|
+
env: "PRIMITIVE_API_BASE_URL",
|
|
18825
|
+
hidden: true
|
|
18826
|
+
}),
|
|
18827
|
+
id: Flags.string({
|
|
18828
|
+
description: "Function id (UUID) whose routes should be unbound.",
|
|
18829
|
+
required: true
|
|
18830
|
+
}),
|
|
18831
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
18832
|
+
};
|
|
18833
|
+
async run() {
|
|
18834
|
+
const { flags } = await this.parse(FunctionsRouteUnsetCommand);
|
|
18835
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
18836
|
+
apiKey: flags["api-key"],
|
|
18837
|
+
apiBaseUrl: flags["api-base-url"],
|
|
18838
|
+
configDir: this.config.configDir
|
|
18839
|
+
});
|
|
18840
|
+
await runWithTiming(flags.time, async () => {
|
|
18841
|
+
const result = await unsetFunctionRoute({
|
|
18842
|
+
client: apiClient.client,
|
|
18843
|
+
path: { id: flags.id },
|
|
18844
|
+
responseStyle: "fields"
|
|
18845
|
+
});
|
|
18846
|
+
if (result.error) {
|
|
18847
|
+
const payload = extractErrorPayload(result.error);
|
|
18848
|
+
writeErrorWithHints(payload);
|
|
18849
|
+
surfaceUnauthorizedHint({
|
|
18850
|
+
auth,
|
|
18851
|
+
baseUrlOverridden,
|
|
18852
|
+
configDir: this.config.configDir,
|
|
18853
|
+
payload
|
|
18854
|
+
});
|
|
18855
|
+
process.exitCode = 1;
|
|
18856
|
+
return;
|
|
18857
|
+
}
|
|
18858
|
+
this.log(JSON.stringify(result.data.data, null, 2));
|
|
18859
|
+
});
|
|
18860
|
+
}
|
|
18861
|
+
};
|
|
18862
|
+
//#endregion
|
|
18863
|
+
//#region src/oclif/commands/functions-routing-topology.ts
|
|
18864
|
+
var FunctionsRoutingTopologyCommand = class FunctionsRoutingTopologyCommand extends Command {
|
|
18865
|
+
static description = `Show the org-wide function routing topology. Lists every active domain with its bound function (if any), the org fallback function (if any), and every deployed function with no route currently bound.`;
|
|
18866
|
+
static summary = "Show the org-wide function routing topology";
|
|
18867
|
+
static examples = ["<%= config.bin %> functions routing-topology"];
|
|
18868
|
+
static flags = {
|
|
18869
|
+
"api-key": Flags.string({
|
|
18870
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
18871
|
+
env: "PRIMITIVE_API_KEY"
|
|
18872
|
+
}),
|
|
18873
|
+
"api-base-url": Flags.string({
|
|
18874
|
+
description: API_BASE_URL_FLAG_DESCRIPTION,
|
|
18875
|
+
env: "PRIMITIVE_API_BASE_URL",
|
|
18876
|
+
hidden: true
|
|
18877
|
+
}),
|
|
18878
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
18879
|
+
};
|
|
18880
|
+
async run() {
|
|
18881
|
+
const { flags } = await this.parse(FunctionsRoutingTopologyCommand);
|
|
18882
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
18883
|
+
apiKey: flags["api-key"],
|
|
18884
|
+
apiBaseUrl: flags["api-base-url"],
|
|
18885
|
+
configDir: this.config.configDir
|
|
18886
|
+
});
|
|
18887
|
+
await runWithTiming(flags.time, async () => {
|
|
18888
|
+
const result = await getOrgRoutingTopology({
|
|
18889
|
+
client: apiClient.client,
|
|
18890
|
+
responseStyle: "fields"
|
|
18891
|
+
});
|
|
18892
|
+
if (result.error) {
|
|
18893
|
+
const payload = extractErrorPayload(result.error);
|
|
18894
|
+
writeErrorWithHints(payload);
|
|
18895
|
+
surfaceUnauthorizedHint({
|
|
18896
|
+
auth,
|
|
18897
|
+
baseUrlOverridden,
|
|
18898
|
+
configDir: this.config.configDir,
|
|
18899
|
+
payload
|
|
18900
|
+
});
|
|
18901
|
+
process.exitCode = 1;
|
|
18902
|
+
return;
|
|
18903
|
+
}
|
|
18904
|
+
this.log(JSON.stringify(result.data.data, null, 2));
|
|
18905
|
+
});
|
|
18906
|
+
}
|
|
18907
|
+
};
|
|
18908
|
+
//#endregion
|
|
17932
18909
|
//#region src/oclif/commands/functions-set-secret.ts
|
|
17933
18910
|
async function runSetSecret(api, params) {
|
|
17934
18911
|
const setResult = await api.setSecret({
|
|
@@ -18106,6 +19083,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
18106
19083
|
return;
|
|
18107
19084
|
}
|
|
18108
19085
|
this.log(JSON.stringify(outcome.result, null, 2));
|
|
19086
|
+
if (outcome.result.redeploy === void 0) process.stderr.write(`Secret ${flags.key} saved. Not live until redeploy. Re-run with --redeploy, or run \`primitive functions redeploy --id ${flags.id} --file <bundle.js>\`.\n`);
|
|
18109
19087
|
});
|
|
18110
19088
|
}
|
|
18111
19089
|
};
|
|
@@ -18230,7 +19208,7 @@ async function maybeWriteEndpointNoiseWarning(params) {
|
|
|
18230
19208
|
} catch {}
|
|
18231
19209
|
}
|
|
18232
19210
|
var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Command {
|
|
18233
|
-
static description = "Send a real test email through MX to trigger this function. With --wait, blocks until the function has processed the inbound; with --show-sends, also prints any outbound sends the function emitted in response.";
|
|
19211
|
+
static description = "Send a real test email through MX to trigger this function. The function must have an active route bound (see `functions route-set`); without one the API returns a 422 immediately so no doomed test send is queued. With --wait, blocks until the function has processed the inbound; with --show-sends, also prints any outbound sends the function emitted in response.";
|
|
18234
19212
|
static summary = "Trigger a test invocation; with --wait, watch it land";
|
|
18235
19213
|
static examples = [
|
|
18236
19214
|
"<%= config.bin %> functions test --id <fn-id>",
|
|
@@ -18336,13 +19314,35 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
18336
19314
|
return;
|
|
18337
19315
|
}
|
|
18338
19316
|
const fetched = result.data.data;
|
|
19317
|
+
if (fetched.inbound_email && Array.isArray(fetched.deliveries) && fetched.deliveries.length === 0 && Date.now() - startedAt > 15e3) {
|
|
19318
|
+
writeFunctionTestProgress(`Inbound email arrived but no route matched. Bind one with: primitive functions route-set --id ${flags.id} --domain <domain-id> (or --fallback), then retry.`);
|
|
19319
|
+
process.exitCode = 1;
|
|
19320
|
+
return;
|
|
19321
|
+
}
|
|
18339
19322
|
if (TERMINAL_TEST_TRACE_STATES.has(fetched.state)) {
|
|
18340
19323
|
trace = fetched;
|
|
18341
19324
|
break;
|
|
18342
19325
|
}
|
|
18343
19326
|
await sleep$1(pollIntervalMs);
|
|
18344
19327
|
}
|
|
18345
|
-
if (!trace)
|
|
19328
|
+
if (!trace) {
|
|
19329
|
+
const finalTrace = ((await getFunctionTestRunTrace({
|
|
19330
|
+
client: apiClient.client,
|
|
19331
|
+
path: {
|
|
19332
|
+
id: flags.id,
|
|
19333
|
+
run_id: invocation.test_run_id
|
|
19334
|
+
},
|
|
19335
|
+
responseStyle: "fields"
|
|
19336
|
+
}).catch(() => null))?.data)?.data;
|
|
19337
|
+
const inboundLanded = Boolean(finalTrace?.inbound_email);
|
|
19338
|
+
const deliveryCount = finalTrace?.deliveries?.length ?? 0;
|
|
19339
|
+
const logCount = finalTrace?.logs?.length ?? 0;
|
|
19340
|
+
const replyCount = finalTrace?.replies?.length ?? 0;
|
|
19341
|
+
const webhookStatus = finalTrace?.inbound_email?.webhook_status ?? "n/a";
|
|
19342
|
+
writeFunctionTestProgress(`Timed out after ${flags.timeout}s. Trace summary: inbound_landed=${inboundLanded} deliveries=${deliveryCount} logs=${logCount} replies=${replyCount} webhook_status=${webhookStatus}. Browse ${invocation.watch_url} for the live view, or inspect ${invocation.trace_url}.`);
|
|
19343
|
+
process.exitCode = 2;
|
|
19344
|
+
return;
|
|
19345
|
+
}
|
|
18346
19346
|
const outcome = buildFunctionTestOutcome({
|
|
18347
19347
|
elapsedSeconds: Math.round((Date.now() - startedAt) / 1e3),
|
|
18348
19348
|
functionId: flags.id,
|
|
@@ -20565,12 +21565,14 @@ var OtpResendCommand = class extends SigninOtpResendCommand {
|
|
|
20565
21565
|
};
|
|
20566
21566
|
//#endregion
|
|
20567
21567
|
//#region src/oclif/commands/whoami.ts
|
|
20568
|
-
function formatWhoamiSummary(account) {
|
|
20569
|
-
|
|
21568
|
+
function formatWhoamiSummary(account, managedInboxDomain) {
|
|
21569
|
+
const lines = [
|
|
20570
21570
|
`Authenticated as ${account.email}`,
|
|
20571
21571
|
`Account id: ${account.id}`,
|
|
20572
21572
|
`Plan: ${account.plan}`
|
|
20573
|
-
]
|
|
21573
|
+
];
|
|
21574
|
+
if (managedInboxDomain) lines.push(`Managed inbox: any-local-part@${managedInboxDomain}`);
|
|
21575
|
+
return lines.join("\n");
|
|
20574
21576
|
}
|
|
20575
21577
|
var WhoamiCommand = class WhoamiCommand extends Command {
|
|
20576
21578
|
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.
|
|
@@ -20624,11 +21626,22 @@ var WhoamiCommand = class WhoamiCommand extends Command {
|
|
|
20624
21626
|
process.stderr.write("Server returned an empty account body; this should not happen for a valid key.\n");
|
|
20625
21627
|
throw new Errors.CLIError("unexpected empty response");
|
|
20626
21628
|
}
|
|
21629
|
+
let managedInboxDomain = null;
|
|
21630
|
+
try {
|
|
21631
|
+
const domainsResult = await listDomains({
|
|
21632
|
+
client: apiClient.client,
|
|
21633
|
+
responseStyle: "fields"
|
|
21634
|
+
});
|
|
21635
|
+
if (!domainsResult.error) managedInboxDomain = (domainsResult.data?.data ?? []).find((row) => row.verified && row.managed_zone !== null)?.domain ?? null;
|
|
21636
|
+
} catch {}
|
|
20627
21637
|
if (flags.json) {
|
|
20628
|
-
this.log(JSON.stringify(
|
|
21638
|
+
this.log(JSON.stringify({
|
|
21639
|
+
...account,
|
|
21640
|
+
managed_inbox_domain: managedInboxDomain
|
|
21641
|
+
}, null, 2));
|
|
20629
21642
|
return;
|
|
20630
21643
|
}
|
|
20631
|
-
this.log(formatWhoamiSummary(account));
|
|
21644
|
+
this.log(formatWhoamiSummary(account, managedInboxDomain));
|
|
20632
21645
|
});
|
|
20633
21646
|
}
|
|
20634
21647
|
};
|
|
@@ -20944,6 +21957,10 @@ const COMMANDS = {
|
|
|
20944
21957
|
"functions:set-secret": FunctionsSetSecretCommand,
|
|
20945
21958
|
"functions:test": FunctionsTestFunctionCommand,
|
|
20946
21959
|
"functions:test-function": FunctionsTestFunctionCommand,
|
|
21960
|
+
"functions:route-set": FunctionsRouteSetCommand,
|
|
21961
|
+
"functions:route-unset": FunctionsRouteUnsetCommand,
|
|
21962
|
+
"functions:route-get": FunctionsRouteGetCommand,
|
|
21963
|
+
"functions:routing-topology": FunctionsRoutingTopologyCommand,
|
|
20947
21964
|
...Object.fromEntries(Object.entries(CANONICAL_OPERATION_ALIASES).map(([alias, target]) => {
|
|
20948
21965
|
const command = generatedCommands[target];
|
|
20949
21966
|
if (!command) throw new Error(`Missing generated command target for alias ${alias}`);
|