@primitivedotdev/cli 0.30.3 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/oclif/index.js +526 -252
- package/package.json +1 -1
package/dist/oclif/index.js
CHANGED
|
@@ -1478,9 +1478,8 @@ const getSentEmail = (options) => (options.client ?? client).get({
|
|
|
1478
1478
|
* List functions
|
|
1479
1479
|
*
|
|
1480
1480
|
* Returns every active (non-deleted) function in the org, newest
|
|
1481
|
-
* first. Each entry carries
|
|
1482
|
-
*
|
|
1483
|
-
* the source code or deploy errors, use `GET /functions/{id}`.
|
|
1481
|
+
* first. Each entry carries deploy status and timestamps. To
|
|
1482
|
+
* inspect the source code or deploy errors, use `GET /functions/{id}`.
|
|
1484
1483
|
*
|
|
1485
1484
|
*/
|
|
1486
1485
|
const listFunctions = (options) => (options?.client ?? client).get({
|
|
@@ -1496,13 +1495,14 @@ const listFunctions = (options) => (options?.client ?? client).get({
|
|
|
1496
1495
|
*
|
|
1497
1496
|
* Creates and deploys a new function. The handler must be a single
|
|
1498
1497
|
* ESM module whose default export is an object with an async
|
|
1499
|
-
* `fetch(request, env)` method (Workers-style).
|
|
1500
|
-
*
|
|
1501
|
-
*
|
|
1502
|
-
* `
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1498
|
+
* `fetch(request, env)` method (Workers-style). Primitive signs
|
|
1499
|
+
* each delivery and forwards the `Primitive-Signature` header to
|
|
1500
|
+
* the handler. Verify the raw request body with
|
|
1501
|
+
* `PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification
|
|
1502
|
+
* the request body parses to an `email.received` event (see
|
|
1503
|
+
* `EmailReceivedEvent` and the Webhook payload section for the full
|
|
1504
|
+
* schema). Code is bundled before being uploaded; ship a single
|
|
1505
|
+
* self-contained file rather than relying on external imports.
|
|
1506
1506
|
*
|
|
1507
1507
|
* **Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`
|
|
1508
1508
|
* (optional) is capped at 5 MiB UTF-8, stored with each deployment
|
|
@@ -1512,12 +1512,12 @@ const listFunctions = (options) => (options?.client ?? client).get({
|
|
|
1512
1512
|
* **Auto-wiring.** On successful deploy, Primitive automatically
|
|
1513
1513
|
* creates a webhook endpoint that delivers inbound mail to the
|
|
1514
1514
|
* function. There is nothing to configure on the Endpoints API
|
|
1515
|
-
* for this to work; the
|
|
1516
|
-
*
|
|
1515
|
+
* for this to work; the internal runtime URL is not returned by
|
|
1516
|
+
* the API and is not a customer-facing integration surface.
|
|
1517
1517
|
*
|
|
1518
1518
|
* **Secrets.** New functions ship with the managed secrets
|
|
1519
|
-
* (`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY
|
|
1520
|
-
* bound. Add user-set secrets via
|
|
1519
|
+
* (`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,
|
|
1520
|
+
* `PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via
|
|
1521
1521
|
* `POST /functions/{id}/secrets`; secret writes only land in the
|
|
1522
1522
|
* running handler on the next redeploy.
|
|
1523
1523
|
*
|
|
@@ -1663,9 +1663,9 @@ const getFunctionTestRunTrace = (options) => (options.client ?? client).get({
|
|
|
1663
1663
|
* never returned.** Secret writes are write-only.
|
|
1664
1664
|
*
|
|
1665
1665
|
* Managed entries (e.g. `PRIMITIVE_WEBHOOK_SECRET`,
|
|
1666
|
-
* `PRIMITIVE_API_KEY`) carry a
|
|
1667
|
-
* `created_at` / `updated_at`. They
|
|
1668
|
-
* or deleted via this API.
|
|
1666
|
+
* `PRIMITIVE_API_KEY`, `PRIMITIVE_API_BASE_URL`) carry a
|
|
1667
|
+
* `description` instead of `created_at` / `updated_at`. They
|
|
1668
|
+
* cannot be created, updated, or deleted via this API.
|
|
1669
1669
|
*
|
|
1670
1670
|
*/
|
|
1671
1671
|
const listFunctionSecrets = (options) => (options.client ?? client).get({
|
|
@@ -1831,7 +1831,7 @@ const openapiDocument = {
|
|
|
1831
1831
|
},
|
|
1832
1832
|
{
|
|
1833
1833
|
"name": "Functions",
|
|
1834
|
-
"description": "Deploy JavaScript handlers that run on inbound mail. Each function\nis a single ESM module whose default export is an object with an\nasync `fetch(request, env)` method, in the shape of a Workers-style\nhandler.
|
|
1834
|
+
"description": "Deploy JavaScript handlers that run on inbound mail. Each function\nis a single ESM module whose default export is an object with an\nasync `fetch(request, env)` method, in the shape of a Workers-style\nhandler. Primitive signs each delivery and forwards the\n`Primitive-Signature` header to the handler; verify the raw request\nbody with `PRIMITIVE_WEBHOOK_SECRET` before trusting the parsed\n`email.received` event (see `EmailReceivedEvent` and the Webhook\npayload section for the full schema). Code runs on\nPrimitive's edge runtime; there is no infrastructure to manage.\nSecrets land in `env` as encrypted bindings and are refreshed on\nevery redeploy.\n"
|
|
1835
1835
|
}
|
|
1836
1836
|
],
|
|
1837
1837
|
"paths": {
|
|
@@ -2413,6 +2413,15 @@ const openapiDocument = {
|
|
|
2413
2413
|
},
|
|
2414
2414
|
"description": "Filter by domain ID."
|
|
2415
2415
|
},
|
|
2416
|
+
{
|
|
2417
|
+
"name": "reply_to_sent_email_id",
|
|
2418
|
+
"in": "query",
|
|
2419
|
+
"schema": {
|
|
2420
|
+
"type": "string",
|
|
2421
|
+
"format": "uuid"
|
|
2422
|
+
},
|
|
2423
|
+
"description": "Filter to inbound emails that are replies to a specific\noutbound send. The value is a `sent_emails.id` (UUID). At\ninbound ingest, Primitive matches the parsed In-Reply-To\nheader (or References as a fallback) against\n`sent_emails.message_id` in the same org and records the\nresolved id on `emails.reply_to_sent_email_id`. This filter\nis the strict-threading lookup behind `primitive chat` and\nany UI that wants to show the inbound reply to a given\nsend. NULL on inbound that isn't a threaded reply to one\nof your sends, so existing emails received before this\ningestion landed will not match.\n"
|
|
2424
|
+
},
|
|
2416
2425
|
{
|
|
2417
2426
|
"name": "status",
|
|
2418
2427
|
"in": "query",
|
|
@@ -3162,7 +3171,7 @@ const openapiDocument = {
|
|
|
3162
3171
|
"get": {
|
|
3163
3172
|
"operationId": "listFunctions",
|
|
3164
3173
|
"summary": "List functions",
|
|
3165
|
-
"description": "Returns every active (non-deleted) function in the org, newest\nfirst. Each entry carries
|
|
3174
|
+
"description": "Returns every active (non-deleted) function in the org, newest\nfirst. Each entry carries deploy status and timestamps. To\ninspect the source code or deploy errors, use `GET /functions/{id}`.\n",
|
|
3166
3175
|
"tags": ["Functions"],
|
|
3167
3176
|
"responses": {
|
|
3168
3177
|
"200": {
|
|
@@ -3181,7 +3190,7 @@ const openapiDocument = {
|
|
|
3181
3190
|
"post": {
|
|
3182
3191
|
"operationId": "createFunction",
|
|
3183
3192
|
"summary": "Deploy a function",
|
|
3184
|
-
"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).
|
|
3193
|
+
"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**Auto-wiring.** On successful deploy, Primitive automatically\ncreates a webhook endpoint that delivers inbound mail to the\nfunction. There is nothing to configure on the Endpoints API\nfor this to work; the internal runtime URL is not returned by\nthe API and is not a customer-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",
|
|
3185
3194
|
"tags": ["Functions"],
|
|
3186
3195
|
"requestBody": {
|
|
3187
3196
|
"required": true,
|
|
@@ -3350,7 +3359,7 @@ const openapiDocument = {
|
|
|
3350
3359
|
"get": {
|
|
3351
3360
|
"operationId": "listFunctionSecrets",
|
|
3352
3361
|
"summary": "List a function's secrets",
|
|
3353
|
-
"description": "Returns metadata for every secret bound to the function, with\nmanaged entries (provisioned by Primitive) listed first and\nuser-set entries listed alphabetically after. **Values are\nnever returned.** Secret writes are write-only.\n\nManaged entries (e.g. `PRIMITIVE_WEBHOOK_SECRET`,\n`PRIMITIVE_API_KEY`) carry a
|
|
3362
|
+
"description": "Returns metadata for every secret bound to the function, with\nmanaged entries (provisioned by Primitive) listed first and\nuser-set entries listed alphabetically after. **Values are\nnever returned.** Secret writes are write-only.\n\nManaged entries (e.g. `PRIMITIVE_WEBHOOK_SECRET`,\n`PRIMITIVE_API_KEY`, `PRIMITIVE_API_BASE_URL`) carry a\n`description` instead of `created_at` / `updated_at`. They\ncannot be created, updated, or deleted via this API.\n",
|
|
3354
3363
|
"tags": ["Functions"],
|
|
3355
3364
|
"responses": {
|
|
3356
3365
|
"200": {
|
|
@@ -4666,6 +4675,11 @@ const openapiDocument = {
|
|
|
4666
4675
|
"type": "array",
|
|
4667
4676
|
"description": "Sent emails recorded as replies to this inbound, in send\norder (ascending). Populated when a customer's send-mail\nrequest carries an `in_reply_to` Message-ID that matches\nthis inbound's `message_id` in the same org. Includes\nattempts that were gate-denied, so the array reflects every\nrecorded reply attempt regardless of outcome.\n",
|
|
4668
4677
|
"items": { "$ref": "#/components/schemas/EmailDetailReply" }
|
|
4678
|
+
},
|
|
4679
|
+
"reply_to_sent_email_id": {
|
|
4680
|
+
"type": ["string", "null"],
|
|
4681
|
+
"format": "uuid",
|
|
4682
|
+
"description": "The `sent_emails.id` of the outbound this inbound was a\nreply to, when resolvable. Set at inbound ingest by\nmatching the parsed In-Reply-To (or References, as a\nfallback) against `sent_emails.message_id` in the same\norg. The mirror of `sent_emails.in_reply_to_email_id` for\nthe inbound side of a thread. NULL when the inbound is\nnot a threaded reply to one of your sends, when neither\nheader survived the path through intermediate MTAs, or on\ninbound received before this auto-link landed.\n"
|
|
4669
4683
|
}
|
|
4670
4684
|
},
|
|
4671
4685
|
"required": [
|
|
@@ -5511,11 +5525,6 @@ const openapiDocument = {
|
|
|
5511
5525
|
"format": "date-time",
|
|
5512
5526
|
"description": "Timestamp of the most recent successful deploy. Null until the first deploy succeeds."
|
|
5513
5527
|
},
|
|
5514
|
-
"gateway_url": {
|
|
5515
|
-
"type": "string",
|
|
5516
|
-
"format": "uri",
|
|
5517
|
-
"description": "URL the platform's webhook delivery loop posts to in order\nto invoke the function. Reference only; not directly\ncallable from outside.\n"
|
|
5518
|
-
},
|
|
5519
5528
|
"created_at": {
|
|
5520
5529
|
"type": "string",
|
|
5521
5530
|
"format": "date-time"
|
|
@@ -5529,7 +5538,6 @@ const openapiDocument = {
|
|
|
5529
5538
|
"id",
|
|
5530
5539
|
"name",
|
|
5531
5540
|
"deploy_status",
|
|
5532
|
-
"gateway_url",
|
|
5533
5541
|
"created_at",
|
|
5534
5542
|
"updated_at"
|
|
5535
5543
|
]
|
|
@@ -5556,10 +5564,6 @@ const openapiDocument = {
|
|
|
5556
5564
|
"type": ["string", "null"],
|
|
5557
5565
|
"format": "date-time"
|
|
5558
5566
|
},
|
|
5559
|
-
"gateway_url": {
|
|
5560
|
-
"type": "string",
|
|
5561
|
-
"format": "uri"
|
|
5562
|
-
},
|
|
5563
5567
|
"created_at": {
|
|
5564
5568
|
"type": "string",
|
|
5565
5569
|
"format": "date-time"
|
|
@@ -5574,7 +5578,6 @@ const openapiDocument = {
|
|
|
5574
5578
|
"name",
|
|
5575
5579
|
"code",
|
|
5576
5580
|
"deploy_status",
|
|
5577
|
-
"gateway_url",
|
|
5578
5581
|
"created_at",
|
|
5579
5582
|
"updated_at"
|
|
5580
5583
|
]
|
|
@@ -5612,17 +5615,12 @@ const openapiDocument = {
|
|
|
5612
5615
|
"format": "uuid"
|
|
5613
5616
|
},
|
|
5614
5617
|
"name": { "type": "string" },
|
|
5615
|
-
"deploy_status": { "$ref": "#/components/schemas/FunctionDeployStatus" }
|
|
5616
|
-
"gateway_url": {
|
|
5617
|
-
"type": "string",
|
|
5618
|
-
"format": "uri"
|
|
5619
|
-
}
|
|
5618
|
+
"deploy_status": { "$ref": "#/components/schemas/FunctionDeployStatus" }
|
|
5620
5619
|
},
|
|
5621
5620
|
"required": [
|
|
5622
5621
|
"id",
|
|
5623
5622
|
"name",
|
|
5624
|
-
"deploy_status"
|
|
5625
|
-
"gateway_url"
|
|
5623
|
+
"deploy_status"
|
|
5626
5624
|
]
|
|
5627
5625
|
},
|
|
5628
5626
|
"UpdateFunctionInput": {
|
|
@@ -6098,7 +6096,7 @@ const openapiDocument = {
|
|
|
6098
6096
|
"key": {
|
|
6099
6097
|
"type": "string",
|
|
6100
6098
|
"pattern": "^[A-Z_][A-Z0-9_]*$",
|
|
6101
|
-
"description": "Uppercase letters, digits, and underscores. Must start with\na letter or underscore. System-managed keys (e.g.\nPRIMITIVE_WEBHOOK_SECRET) are reserved.\n"
|
|
6099
|
+
"description": "Uppercase letters, digits, and underscores. Must start with\na letter or underscore. System-managed keys (e.g.\nPRIMITIVE_WEBHOOK_SECRET, PRIMITIVE_API_KEY, and\nPRIMITIVE_API_BASE_URL) are reserved.\n"
|
|
6102
6100
|
},
|
|
6103
6101
|
"value": {
|
|
6104
6102
|
"type": "string",
|
|
@@ -7336,6 +7334,11 @@ const operationManifest = [
|
|
|
7336
7334
|
"created_at"
|
|
7337
7335
|
]
|
|
7338
7336
|
}
|
|
7337
|
+
},
|
|
7338
|
+
"reply_to_sent_email_id": {
|
|
7339
|
+
"type": ["string", "null"],
|
|
7340
|
+
"format": "uuid",
|
|
7341
|
+
"description": "The `sent_emails.id` of the outbound this inbound was a\nreply to, when resolvable. Set at inbound ingest by\nmatching the parsed In-Reply-To (or References, as a\nfallback) against `sent_emails.message_id` in the same\norg. The mirror of `sent_emails.in_reply_to_email_id` for\nthe inbound side of a thread. NULL when the inbound is\nnot a threaded reply to one of your sends, when neither\nheader survived the path through intermediate MTAs, or on\ninbound received before this auto-link landed.\n"
|
|
7339
7342
|
}
|
|
7340
7343
|
},
|
|
7341
7344
|
"required": [
|
|
@@ -7588,6 +7591,13 @@ const operationManifest = [
|
|
|
7588
7591
|
"required": false,
|
|
7589
7592
|
"type": "string"
|
|
7590
7593
|
},
|
|
7594
|
+
{
|
|
7595
|
+
"description": "Filter to inbound emails that are replies to a specific\noutbound send. The value is a `sent_emails.id` (UUID). At\ninbound ingest, Primitive matches the parsed In-Reply-To\nheader (or References as a fallback) against\n`sent_emails.message_id` in the same org and records the\nresolved id on `emails.reply_to_sent_email_id`. This filter\nis the strict-threading lookup behind `primitive chat` and\nany UI that wants to show the inbound reply to a given\nsend. NULL on inbound that isn't a threaded reply to one\nof your sends, so existing emails received before this\ningestion landed will not match.\n",
|
|
7596
|
+
"enum": null,
|
|
7597
|
+
"name": "reply_to_sent_email_id",
|
|
7598
|
+
"required": false,
|
|
7599
|
+
"type": "string"
|
|
7600
|
+
},
|
|
7591
7601
|
{
|
|
7592
7602
|
"description": "Filter by inbound email lifecycle status.",
|
|
7593
7603
|
"enum": null,
|
|
@@ -8411,7 +8421,7 @@ const operationManifest = [
|
|
|
8411
8421
|
"binaryResponse": false,
|
|
8412
8422
|
"bodyRequired": true,
|
|
8413
8423
|
"command": "create-function",
|
|
8414
|
-
"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).
|
|
8424
|
+
"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**Auto-wiring.** On successful deploy, Primitive automatically\ncreates a webhook endpoint that delivers inbound mail to the\nfunction. There is nothing to configure on the Endpoints API\nfor this to work; the internal runtime URL is not returned by\nthe API and is not a customer-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",
|
|
8415
8425
|
"hasJsonBody": true,
|
|
8416
8426
|
"method": "POST",
|
|
8417
8427
|
"operationId": "createFunction",
|
|
@@ -8459,17 +8469,12 @@ const operationManifest = [
|
|
|
8459
8469
|
"failed"
|
|
8460
8470
|
],
|
|
8461
8471
|
"description": "Lifecycle state of the latest deploy attempt:\n * `pending` — deploy in flight; the runtime has not yet\n confirmed the new bundle is live.\n * `deployed` — the running edge handler is the latest code.\n * `failed` — the most recent deploy attempt failed; the\n previously-live code (if any) is still running. The\n `deploy_error` field carries the error message.\n"
|
|
8462
|
-
},
|
|
8463
|
-
"gateway_url": {
|
|
8464
|
-
"type": "string",
|
|
8465
|
-
"format": "uri"
|
|
8466
8472
|
}
|
|
8467
8473
|
},
|
|
8468
8474
|
"required": [
|
|
8469
8475
|
"id",
|
|
8470
8476
|
"name",
|
|
8471
|
-
"deploy_status"
|
|
8472
|
-
"gateway_url"
|
|
8477
|
+
"deploy_status"
|
|
8473
8478
|
]
|
|
8474
8479
|
},
|
|
8475
8480
|
"sdkName": "createFunction",
|
|
@@ -8502,7 +8507,7 @@ const operationManifest = [
|
|
|
8502
8507
|
"key": {
|
|
8503
8508
|
"type": "string",
|
|
8504
8509
|
"pattern": "^[A-Z_][A-Z0-9_]*$",
|
|
8505
|
-
"description": "Uppercase letters, digits, and underscores. Must start with\na letter or underscore. System-managed keys (e.g.\nPRIMITIVE_WEBHOOK_SECRET) are reserved.\n"
|
|
8510
|
+
"description": "Uppercase letters, digits, and underscores. Must start with\na letter or underscore. System-managed keys (e.g.\nPRIMITIVE_WEBHOOK_SECRET, PRIMITIVE_API_KEY, and\nPRIMITIVE_API_BASE_URL) are reserved.\n"
|
|
8506
8511
|
},
|
|
8507
8512
|
"value": {
|
|
8508
8513
|
"type": "string",
|
|
@@ -8645,10 +8650,6 @@ const operationManifest = [
|
|
|
8645
8650
|
"type": ["string", "null"],
|
|
8646
8651
|
"format": "date-time"
|
|
8647
8652
|
},
|
|
8648
|
-
"gateway_url": {
|
|
8649
|
-
"type": "string",
|
|
8650
|
-
"format": "uri"
|
|
8651
|
-
},
|
|
8652
8653
|
"created_at": {
|
|
8653
8654
|
"type": "string",
|
|
8654
8655
|
"format": "date-time"
|
|
@@ -8663,7 +8664,6 @@ const operationManifest = [
|
|
|
8663
8664
|
"name",
|
|
8664
8665
|
"code",
|
|
8665
8666
|
"deploy_status",
|
|
8666
|
-
"gateway_url",
|
|
8667
8667
|
"created_at",
|
|
8668
8668
|
"updated_at"
|
|
8669
8669
|
]
|
|
@@ -9209,7 +9209,7 @@ const operationManifest = [
|
|
|
9209
9209
|
"binaryResponse": false,
|
|
9210
9210
|
"bodyRequired": false,
|
|
9211
9211
|
"command": "list-function-secrets",
|
|
9212
|
-
"description": "Returns metadata for every secret bound to the function, with\nmanaged entries (provisioned by Primitive) listed first and\nuser-set entries listed alphabetically after. **Values are\nnever returned.** Secret writes are write-only.\n\nManaged entries (e.g. `PRIMITIVE_WEBHOOK_SECRET`,\n`PRIMITIVE_API_KEY`) carry a
|
|
9212
|
+
"description": "Returns metadata for every secret bound to the function, with\nmanaged entries (provisioned by Primitive) listed first and\nuser-set entries listed alphabetically after. **Values are\nnever returned.** Secret writes are write-only.\n\nManaged entries (e.g. `PRIMITIVE_WEBHOOK_SECRET`,\n`PRIMITIVE_API_KEY`, `PRIMITIVE_API_BASE_URL`) carry a\n`description` instead of `created_at` / `updated_at`. They\ncannot be created, updated, or deleted via this API.\n",
|
|
9213
9213
|
"hasJsonBody": false,
|
|
9214
9214
|
"method": "GET",
|
|
9215
9215
|
"operationId": "listFunctionSecrets",
|
|
@@ -9265,7 +9265,7 @@ const operationManifest = [
|
|
|
9265
9265
|
"binaryResponse": false,
|
|
9266
9266
|
"bodyRequired": false,
|
|
9267
9267
|
"command": "list-functions",
|
|
9268
|
-
"description": "Returns every active (non-deleted) function in the org, newest\nfirst. Each entry carries
|
|
9268
|
+
"description": "Returns every active (non-deleted) function in the org, newest\nfirst. Each entry carries deploy status and timestamps. To\ninspect the source code or deploy errors, use `GET /functions/{id}`.\n",
|
|
9269
9269
|
"hasJsonBody": false,
|
|
9270
9270
|
"method": "GET",
|
|
9271
9271
|
"operationId": "listFunctions",
|
|
@@ -9302,11 +9302,6 @@ const operationManifest = [
|
|
|
9302
9302
|
"format": "date-time",
|
|
9303
9303
|
"description": "Timestamp of the most recent successful deploy. Null until the first deploy succeeds."
|
|
9304
9304
|
},
|
|
9305
|
-
"gateway_url": {
|
|
9306
|
-
"type": "string",
|
|
9307
|
-
"format": "uri",
|
|
9308
|
-
"description": "URL the platform's webhook delivery loop posts to in order\nto invoke the function. Reference only; not directly\ncallable from outside.\n"
|
|
9309
|
-
},
|
|
9310
9305
|
"created_at": {
|
|
9311
9306
|
"type": "string",
|
|
9312
9307
|
"format": "date-time"
|
|
@@ -9320,7 +9315,6 @@ const operationManifest = [
|
|
|
9320
9315
|
"id",
|
|
9321
9316
|
"name",
|
|
9322
9317
|
"deploy_status",
|
|
9323
|
-
"gateway_url",
|
|
9324
9318
|
"created_at",
|
|
9325
9319
|
"updated_at"
|
|
9326
9320
|
]
|
|
@@ -9549,10 +9543,6 @@ const operationManifest = [
|
|
|
9549
9543
|
"type": ["string", "null"],
|
|
9550
9544
|
"format": "date-time"
|
|
9551
9545
|
},
|
|
9552
|
-
"gateway_url": {
|
|
9553
|
-
"type": "string",
|
|
9554
|
-
"format": "uri"
|
|
9555
|
-
},
|
|
9556
9546
|
"created_at": {
|
|
9557
9547
|
"type": "string",
|
|
9558
9548
|
"format": "date-time"
|
|
@@ -9567,7 +9557,6 @@ const operationManifest = [
|
|
|
9567
9557
|
"name",
|
|
9568
9558
|
"code",
|
|
9569
9559
|
"deploy_status",
|
|
9570
|
-
"gateway_url",
|
|
9571
9560
|
"created_at",
|
|
9572
9561
|
"updated_at"
|
|
9573
9562
|
]
|
|
@@ -11062,7 +11051,7 @@ function resolveConfigEnvironment(config) {
|
|
|
11062
11051
|
} : null;
|
|
11063
11052
|
}
|
|
11064
11053
|
function upsertCliEnvironment(params) {
|
|
11065
|
-
const name = normalizeCliEnvironmentName(params.environmentName ??
|
|
11054
|
+
const name = normalizeCliEnvironmentName(params.environmentName ?? "default");
|
|
11066
11055
|
const existing = params.config.environments[name] ?? {};
|
|
11067
11056
|
const nextHeaders = { ...existing.headers ?? {} };
|
|
11068
11057
|
for (const assignment of params.headers ?? []) {
|
|
@@ -11139,6 +11128,7 @@ function resolveCliApiRequestConfig(params) {
|
|
|
11139
11128
|
const currentEnvironment = resolveConfigEnvironment(loadCliConfig(params.configDir));
|
|
11140
11129
|
const configuredApiBaseUrl1 = currentEnvironment?.config.api_base_url_1;
|
|
11141
11130
|
const configuredApiBaseUrl2 = currentEnvironment?.config.api_base_url_2;
|
|
11131
|
+
if (currentEnvironment !== null && currentEnvironment.name !== "default" && params.apiBaseUrl1 === void 0 && configuredApiBaseUrl1 === void 0) throw new Errors.CLIError(`The active Primitive CLI environment \`${currentEnvironment.name}\` does not specify an api_base_url_1. Set one with \`primitive config set --environment ${currentEnvironment.name} --api-base-url-1 https://...\`, or switch to a different environment with \`primitive config use <name>\`. Refusing to fall back to the production default for a non-default environment.`, { exit: 1 });
|
|
11142
11132
|
const apiBaseUrl1 = params.apiBaseUrl1 !== void 0 ? normalizeApiBaseUrl1(params.apiBaseUrl1) : configuredApiBaseUrl1;
|
|
11143
11133
|
const apiBaseUrl2 = params.apiBaseUrl2 !== void 0 ? normalizeApiBaseUrl2(params.apiBaseUrl2) : configuredApiBaseUrl2;
|
|
11144
11134
|
return {
|
|
@@ -11410,26 +11400,26 @@ function coerceParameterValue(parameter, value) {
|
|
|
11410
11400
|
if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") return value;
|
|
11411
11401
|
throw new Errors.CLIError(`Unsupported flag value for --${parameter.name}`);
|
|
11412
11402
|
}
|
|
11413
|
-
function cliError$
|
|
11403
|
+
function cliError$6(message) {
|
|
11414
11404
|
return new Errors.CLIError(message, { exit: 1 });
|
|
11415
11405
|
}
|
|
11416
11406
|
function parseJson(source, flagLabel) {
|
|
11417
11407
|
try {
|
|
11418
11408
|
return JSON.parse(source);
|
|
11419
11409
|
} catch (error) {
|
|
11420
|
-
throw cliError$
|
|
11410
|
+
throw cliError$6(`${flagLabel} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
11421
11411
|
}
|
|
11422
11412
|
}
|
|
11423
11413
|
function readJsonBody(flags) {
|
|
11424
11414
|
const bodyFile = flags["body-file"];
|
|
11425
11415
|
const rawBody = flags["raw-body"];
|
|
11426
|
-
if (bodyFile && rawBody) throw cliError$
|
|
11416
|
+
if (bodyFile && rawBody) throw cliError$6("Use either --raw-body or --body-file, not both");
|
|
11427
11417
|
if (typeof bodyFile === "string") {
|
|
11428
11418
|
let contents;
|
|
11429
11419
|
try {
|
|
11430
11420
|
contents = readFileSync(bodyFile, "utf8");
|
|
11431
11421
|
} catch (error) {
|
|
11432
|
-
throw cliError$
|
|
11422
|
+
throw cliError$6(`Could not read --body-file ${bodyFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
11433
11423
|
}
|
|
11434
11424
|
return parseJson(contents, `--body-file ${bodyFile}`);
|
|
11435
11425
|
}
|
|
@@ -11439,7 +11429,7 @@ function readTextFileFlag(path, flagLabel) {
|
|
|
11439
11429
|
try {
|
|
11440
11430
|
return readFileSync(path, "utf8");
|
|
11441
11431
|
} catch (error) {
|
|
11442
|
-
throw cliError$
|
|
11432
|
+
throw cliError$6(`Could not read ${flagLabel} ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
11443
11433
|
}
|
|
11444
11434
|
}
|
|
11445
11435
|
function extractErrorPayload(raw) {
|
|
@@ -11504,15 +11494,27 @@ function writeErrorWithHints(payload) {
|
|
|
11504
11494
|
}
|
|
11505
11495
|
if (code in NETWORK_ERROR_HINTS) process.stderr.write(`${NETWORK_ERROR_HINTS[code]}\n`);
|
|
11506
11496
|
}
|
|
11507
|
-
|
|
11508
|
-
|
|
11497
|
+
/**
|
|
11498
|
+
* Surface a user-facing hint when a request comes back unauthorized.
|
|
11499
|
+
*
|
|
11500
|
+
* Deliberately does NOT mutate the saved credentials.json. Auto-
|
|
11501
|
+
* deleting on any 401 made transient rejections look like permanent
|
|
11502
|
+
* credential failures and forced unnecessary re-login cycles; we
|
|
11503
|
+
* surface a hint and let the user decide whether to re-authenticate.
|
|
11504
|
+
*
|
|
11505
|
+
* The one legitimate auto-delete case lives in `primitive login`'s
|
|
11506
|
+
* `checkExistingLogin`: the user has explicitly asked to log in,
|
|
11507
|
+
* existing credentials are probed, and if they fail we clean up
|
|
11508
|
+
* before minting a new key. That path calls `deleteCliCredentials`
|
|
11509
|
+
* directly rather than going through this function.
|
|
11510
|
+
*/
|
|
11511
|
+
function surfaceUnauthorizedHint(params) {
|
|
11512
|
+
if (extractErrorCode(params.payload) !== API_ERROR_CODES.unauthorized || params.auth.source !== "stored") return;
|
|
11509
11513
|
if (params.baseUrlOverridden && params.auth.credentials !== null && params.auth.apiBaseUrl1 !== params.auth.credentials.api_base_url_1) {
|
|
11510
|
-
process.stderr.write("Saved Primitive CLI credentials were rejected by the overridden API base URL. The
|
|
11511
|
-
return
|
|
11514
|
+
process.stderr.write("Saved Primitive CLI credentials were rejected by the overridden API base URL. The saved credential is preserved; unset PRIMITIVE_API_BASE_URL_1, run `primitive config reset` to clear configured URL overrides, or run `primitive logout` to remove the stored credential.\n");
|
|
11515
|
+
return;
|
|
11512
11516
|
}
|
|
11513
|
-
|
|
11514
|
-
process.stderr.write("Removed saved Primitive CLI credentials because the backing API key is no longer valid. Run `primitive login` to create a new one.\n");
|
|
11515
|
-
return true;
|
|
11517
|
+
process.stderr.write("Your saved Primitive CLI credential was rejected. If the command was working a moment ago, please retry; brief retries often clear transient rejections. If it keeps failing, run `primitive logout && primitive login` to mint a fresh credential.\n");
|
|
11516
11518
|
}
|
|
11517
11519
|
function formatElapsed(ms) {
|
|
11518
11520
|
const seconds = ms / 1e3;
|
|
@@ -11676,7 +11678,7 @@ function createOperationCommand(operation) {
|
|
|
11676
11678
|
if (result.error) {
|
|
11677
11679
|
const errorPayload = extractErrorPayload(result.error);
|
|
11678
11680
|
writeErrorWithHints(errorPayload);
|
|
11679
|
-
|
|
11681
|
+
surfaceUnauthorizedHint({
|
|
11680
11682
|
auth,
|
|
11681
11683
|
baseUrlOverridden,
|
|
11682
11684
|
configDir: this.config.configDir,
|
|
@@ -11737,6 +11739,384 @@ function canonicalizeCliReferences(description) {
|
|
|
11737
11739
|
return description.replaceAll("`primitive emails:latest`", "`primitive emails latest`").replaceAll("`primitive describe emails:get-email | jq '.responseSchema.properties'`", "`primitive describe emails:get | jq '.responseSchema.properties'`");
|
|
11738
11740
|
}
|
|
11739
11741
|
//#endregion
|
|
11742
|
+
//#region src/oclif/outbound-defaults.ts
|
|
11743
|
+
const SUBJECT_MAX_LENGTH = 200;
|
|
11744
|
+
function deriveSubject(body) {
|
|
11745
|
+
for (const line of body.split("\n")) {
|
|
11746
|
+
const trimmed = line.trim();
|
|
11747
|
+
if (!trimmed) continue;
|
|
11748
|
+
return trimmed.length > SUBJECT_MAX_LENGTH ? `${trimmed.slice(0, SUBJECT_MAX_LENGTH - 3)}...` : trimmed;
|
|
11749
|
+
}
|
|
11750
|
+
return "Message";
|
|
11751
|
+
}
|
|
11752
|
+
function isVerifiedDomain(domain) {
|
|
11753
|
+
return domain.is_active === true;
|
|
11754
|
+
}
|
|
11755
|
+
async function pickDefaultFromAddress(apiClient, authFailureContext) {
|
|
11756
|
+
const result = await listDomains({
|
|
11757
|
+
client: apiClient.client,
|
|
11758
|
+
responseStyle: "fields"
|
|
11759
|
+
});
|
|
11760
|
+
if (result.error) {
|
|
11761
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
11762
|
+
if (extractErrorCode(errorPayload) === API_ERROR_CODES.unauthorized) {
|
|
11763
|
+
writeErrorWithHints(errorPayload);
|
|
11764
|
+
surfaceUnauthorizedHint({
|
|
11765
|
+
...authFailureContext,
|
|
11766
|
+
payload: errorPayload
|
|
11767
|
+
});
|
|
11768
|
+
throw new Errors.CLIError("Cannot send: API key is missing or invalid (see hint above).", { exit: 1 });
|
|
11769
|
+
}
|
|
11770
|
+
throw new Errors.CLIError(`Could not look up your verified domains to default --from. Pass --from explicitly. Underlying error: ${formatErrorPayload(errorPayload)}`);
|
|
11771
|
+
}
|
|
11772
|
+
const first = result.data?.data?.find(isVerifiedDomain);
|
|
11773
|
+
if (!first) throw new Errors.CLIError("No active verified outbound domain found on this account; pass --from explicitly. To set up outbound, claim a domain via `primitive domains add` and verify it.");
|
|
11774
|
+
return `agent@${first.domain}`;
|
|
11775
|
+
}
|
|
11776
|
+
//#endregion
|
|
11777
|
+
//#region src/oclif/commands/emails-poll.ts
|
|
11778
|
+
function quoteDslValue(value) {
|
|
11779
|
+
if (/^[^\s"]+$/.test(value)) return value;
|
|
11780
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
11781
|
+
}
|
|
11782
|
+
function combineQ(q, domain) {
|
|
11783
|
+
const parts = [q?.trim(), domain ? `domain:${quoteDslValue(domain.trim())}` : void 0].filter((part) => Boolean(part));
|
|
11784
|
+
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
11785
|
+
}
|
|
11786
|
+
function normalizeIsoDate(value, label) {
|
|
11787
|
+
const parsed = new Date(value);
|
|
11788
|
+
if (Number.isNaN(parsed.getTime())) throw new Error(`${label} must be a valid date or ISO-8601 timestamp.`);
|
|
11789
|
+
return parsed.toISOString();
|
|
11790
|
+
}
|
|
11791
|
+
function filtersFromFlags(flags) {
|
|
11792
|
+
return {
|
|
11793
|
+
body: flags.body,
|
|
11794
|
+
domain: flags.domain,
|
|
11795
|
+
domainId: flags["domain-id"],
|
|
11796
|
+
from: flags.from,
|
|
11797
|
+
hasAttachment: flags["has-attachment"],
|
|
11798
|
+
q: flags.q,
|
|
11799
|
+
replyToSentEmailId: flags["reply-to-sent-email-id"],
|
|
11800
|
+
spamScoreGte: flags["spam-score-gte"],
|
|
11801
|
+
spamScoreLt: flags["spam-score-lt"],
|
|
11802
|
+
subject: flags.subject,
|
|
11803
|
+
to: flags.to
|
|
11804
|
+
};
|
|
11805
|
+
}
|
|
11806
|
+
function sinceFromFlags(flags) {
|
|
11807
|
+
if (flags.since) return normalizeIsoDate(flags.since, "--since");
|
|
11808
|
+
return flags["include-existing"] ? void 0 : (/* @__PURE__ */ new Date()).toISOString();
|
|
11809
|
+
}
|
|
11810
|
+
function buildEmailSearchQuery(params) {
|
|
11811
|
+
const query = {
|
|
11812
|
+
include_facets: "false",
|
|
11813
|
+
limit: params.pageSize,
|
|
11814
|
+
snippet: "false",
|
|
11815
|
+
sort: "received_at_asc"
|
|
11816
|
+
};
|
|
11817
|
+
const q = combineQ(params.filters.q, params.filters.domain);
|
|
11818
|
+
if (q) query.q = q;
|
|
11819
|
+
if (params.filters.body) query.body = params.filters.body;
|
|
11820
|
+
if (params.filters.domainId) query.domain_id = params.filters.domainId;
|
|
11821
|
+
if (params.filters.from) query.from = params.filters.from;
|
|
11822
|
+
if (params.filters.hasAttachment !== void 0) query.has_attachment = params.filters.hasAttachment ? "true" : "false";
|
|
11823
|
+
if (params.filters.spamScoreGte !== void 0) query.spam_score_gte = params.filters.spamScoreGte;
|
|
11824
|
+
if (params.filters.spamScoreLt !== void 0) query.spam_score_lt = params.filters.spamScoreLt;
|
|
11825
|
+
if (params.filters.replyToSentEmailId) query.reply_to_sent_email_id = params.filters.replyToSentEmailId;
|
|
11826
|
+
if (params.filters.subject) query.subject = params.filters.subject;
|
|
11827
|
+
if (params.filters.to) query.to = params.filters.to;
|
|
11828
|
+
if (params.since) query.date_from = params.since;
|
|
11829
|
+
if (params.cursor) query.cursor = params.cursor;
|
|
11830
|
+
return query;
|
|
11831
|
+
}
|
|
11832
|
+
function encodeReceivedAtSearchCursor(email) {
|
|
11833
|
+
const raw = `r|${new Date(email.received_at).toISOString()}|${email.id}`;
|
|
11834
|
+
return Buffer.from(raw, "utf8").toString("base64url");
|
|
11835
|
+
}
|
|
11836
|
+
function cursorFromRows(rows) {
|
|
11837
|
+
const last = rows.at(-1);
|
|
11838
|
+
return last ? encodeReceivedAtSearchCursor(last) : null;
|
|
11839
|
+
}
|
|
11840
|
+
function collectNewAcceptedEmails(rows, seenIds) {
|
|
11841
|
+
const fresh = [];
|
|
11842
|
+
for (const row of rows) {
|
|
11843
|
+
if (row.status !== "accepted" && row.status !== "completed") continue;
|
|
11844
|
+
if (seenIds.has(row.id)) continue;
|
|
11845
|
+
seenIds.add(row.id);
|
|
11846
|
+
fresh.push(row);
|
|
11847
|
+
}
|
|
11848
|
+
return fresh;
|
|
11849
|
+
}
|
|
11850
|
+
async function fetchEmailSearchPage(params) {
|
|
11851
|
+
const result = await searchEmails({
|
|
11852
|
+
client: params.apiClient.client,
|
|
11853
|
+
query: buildEmailSearchQuery({
|
|
11854
|
+
cursor: params.cursor,
|
|
11855
|
+
filters: params.filters,
|
|
11856
|
+
pageSize: params.pageSize,
|
|
11857
|
+
since: params.since
|
|
11858
|
+
}),
|
|
11859
|
+
responseStyle: "fields"
|
|
11860
|
+
});
|
|
11861
|
+
if (result.error) return {
|
|
11862
|
+
ok: false,
|
|
11863
|
+
error: result.error
|
|
11864
|
+
};
|
|
11865
|
+
const envelope = result.data;
|
|
11866
|
+
const rows = envelope?.data ?? [];
|
|
11867
|
+
return {
|
|
11868
|
+
ok: true,
|
|
11869
|
+
cursor: envelope?.meta.cursor ?? cursorFromRows(rows),
|
|
11870
|
+
rows
|
|
11871
|
+
};
|
|
11872
|
+
}
|
|
11873
|
+
function sleep$1(ms) {
|
|
11874
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11875
|
+
}
|
|
11876
|
+
//#endregion
|
|
11877
|
+
//#region src/oclif/commands/chat.ts
|
|
11878
|
+
const DEFAULT_CHAT_TIMEOUT_SECONDS = 120;
|
|
11879
|
+
const DEFAULT_STRICT_PHASE_SECONDS = 60;
|
|
11880
|
+
function cliError$5(message) {
|
|
11881
|
+
return new Errors.CLIError(message, { exit: 1 });
|
|
11882
|
+
}
|
|
11883
|
+
async function readStdinToString() {
|
|
11884
|
+
if (process.stdin.isTTY) throw cliError$5("No message provided. Pass the message as the second positional argument or pipe it via stdin.");
|
|
11885
|
+
const chunks = [];
|
|
11886
|
+
for await (const chunk of process.stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
11887
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
11888
|
+
}
|
|
11889
|
+
var ChatCommand = class ChatCommand extends Command {
|
|
11890
|
+
static description = `Send a message to an address and wait for the reply.
|
|
11891
|
+
|
|
11892
|
+
This is the first-party verb for talking to agents that live behind
|
|
11893
|
+
email addresses. \`primitive send\` is transport (fire-and-forget);
|
|
11894
|
+
\`primitive chat\` is semantic (send + wait for the threaded reply).
|
|
11895
|
+
|
|
11896
|
+
The message body can be given as the second positional argument or
|
|
11897
|
+
piped via stdin. The reply body is written to stdout; --json emits a
|
|
11898
|
+
structured envelope with both sides of the exchange.
|
|
11899
|
+
|
|
11900
|
+
Matching the reply: the wait phase polls inbound mail filtered by
|
|
11901
|
+
the recipient as sender and the send time as a lower bound. The
|
|
11902
|
+
first match is taken; the full inbound row is then fetched for the
|
|
11903
|
+
body. Exits non-zero on timeout.`;
|
|
11904
|
+
static summary = "Chat with an agent over email (send and wait for the reply)";
|
|
11905
|
+
static examples = [
|
|
11906
|
+
"<%= config.bin %> chat help@agent.acme.dev 'how do I rotate my API key?'",
|
|
11907
|
+
"cat error.log | <%= config.bin %> chat help@agent.acme.dev --subject 'webhook 401s'",
|
|
11908
|
+
"<%= config.bin %> chat help@agent.acme.dev 'follow up question' --json",
|
|
11909
|
+
"<%= config.bin %> chat help@agent.acme.dev 'one more thing' --timeout 300"
|
|
11910
|
+
];
|
|
11911
|
+
static args = {
|
|
11912
|
+
recipient: Args.string({
|
|
11913
|
+
description: "Address to chat with (e.g. help@agent.acme.dev).",
|
|
11914
|
+
required: true
|
|
11915
|
+
}),
|
|
11916
|
+
message: Args.string({ description: "Message body. If omitted, read from stdin." })
|
|
11917
|
+
};
|
|
11918
|
+
static flags = {
|
|
11919
|
+
"api-key": Flags.string({
|
|
11920
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
11921
|
+
env: "PRIMITIVE_API_KEY"
|
|
11922
|
+
}),
|
|
11923
|
+
"api-base-url-1": Flags.string({
|
|
11924
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
11925
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
11926
|
+
hidden: true
|
|
11927
|
+
}),
|
|
11928
|
+
"api-base-url-2": Flags.string({
|
|
11929
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
11930
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
11931
|
+
hidden: true
|
|
11932
|
+
}),
|
|
11933
|
+
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
11934
|
+
subject: Flags.string({ description: "Subject line. Defaults to the first line of the message when omitted." }),
|
|
11935
|
+
"in-reply-to": Flags.string({ description: "Message-Id of the parent email to thread this against. Use when continuing a prior conversation from outside the CLI; for an inbound you received via Primitive, prefer `primitive reply --id <inbound-id>`." }),
|
|
11936
|
+
json: Flags.boolean({ description: "Emit a structured JSON envelope { sent, reply } on stdout instead of just the reply body." }),
|
|
11937
|
+
timeout: Flags.integer({
|
|
11938
|
+
default: DEFAULT_CHAT_TIMEOUT_SECONDS,
|
|
11939
|
+
description: "Seconds to wait for a reply before exiting non-zero; 0 waits forever.",
|
|
11940
|
+
min: 0
|
|
11941
|
+
}),
|
|
11942
|
+
"strict-phase-seconds": Flags.integer({
|
|
11943
|
+
default: DEFAULT_STRICT_PHASE_SECONDS,
|
|
11944
|
+
description: "Seconds to wait in strict-threading mode (filter by reply_to_sent_email_id) before falling back to time-window matching. Set to the full --timeout to disable the fallback; --strict-only is the explicit way to do that.",
|
|
11945
|
+
min: 1
|
|
11946
|
+
}),
|
|
11947
|
+
"strict-only": Flags.boolean({ description: "Disable the time-window fallback. Only accept inbounds whose threading headers (In-Reply-To / References) resolve to this send. Recommended when correctness matters more than success rate (e.g. agents talking to agents)." }),
|
|
11948
|
+
interval: Flags.integer({
|
|
11949
|
+
default: 2,
|
|
11950
|
+
description: "Seconds between polls while waiting for the reply.",
|
|
11951
|
+
min: 1
|
|
11952
|
+
}),
|
|
11953
|
+
"page-size": Flags.integer({
|
|
11954
|
+
default: 50,
|
|
11955
|
+
description: "Inbound emails to fetch per poll while waiting (1-100). Internal tuning knob.",
|
|
11956
|
+
max: 100,
|
|
11957
|
+
min: 1,
|
|
11958
|
+
hidden: true
|
|
11959
|
+
}),
|
|
11960
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
11961
|
+
};
|
|
11962
|
+
async run() {
|
|
11963
|
+
const { args, flags } = await this.parse(ChatCommand);
|
|
11964
|
+
const message = args.message !== void 0 && args.message !== "" ? args.message : await readStdinToString();
|
|
11965
|
+
if (!message.trim()) throw cliError$5("Message body is empty.");
|
|
11966
|
+
await runWithTiming(flags.time, async () => {
|
|
11967
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
11968
|
+
apiKey: flags["api-key"],
|
|
11969
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
11970
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
11971
|
+
configDir: this.config.configDir
|
|
11972
|
+
});
|
|
11973
|
+
const authFailureContext = {
|
|
11974
|
+
auth,
|
|
11975
|
+
baseUrlOverridden,
|
|
11976
|
+
configDir: this.config.configDir
|
|
11977
|
+
};
|
|
11978
|
+
const from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
11979
|
+
const subject = flags.subject ?? deriveSubject(message);
|
|
11980
|
+
const sentAtIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
11981
|
+
const sendResult = await sendEmail({
|
|
11982
|
+
body: {
|
|
11983
|
+
from,
|
|
11984
|
+
to: args.recipient,
|
|
11985
|
+
subject,
|
|
11986
|
+
body_text: message,
|
|
11987
|
+
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {}
|
|
11988
|
+
},
|
|
11989
|
+
client: apiClient._sendClient,
|
|
11990
|
+
responseStyle: "fields"
|
|
11991
|
+
});
|
|
11992
|
+
if (sendResult.error) {
|
|
11993
|
+
const errorPayload = extractErrorPayload(sendResult.error);
|
|
11994
|
+
writeErrorWithHints(errorPayload);
|
|
11995
|
+
surfaceUnauthorizedHint({
|
|
11996
|
+
...authFailureContext,
|
|
11997
|
+
payload: errorPayload
|
|
11998
|
+
});
|
|
11999
|
+
process.exitCode = 1;
|
|
12000
|
+
return;
|
|
12001
|
+
}
|
|
12002
|
+
const sent = sendResult.data?.data;
|
|
12003
|
+
if (!sent) throw cliError$5("Send succeeded but the API returned no data.");
|
|
12004
|
+
const reply = await waitForReply({
|
|
12005
|
+
apiClient,
|
|
12006
|
+
authFailureContext,
|
|
12007
|
+
from,
|
|
12008
|
+
interval: flags.interval,
|
|
12009
|
+
pageSize: flags["page-size"],
|
|
12010
|
+
recipient: args.recipient,
|
|
12011
|
+
sentAtIso,
|
|
12012
|
+
sentId: sent.id,
|
|
12013
|
+
strictOnly: flags["strict-only"],
|
|
12014
|
+
strictPhaseSeconds: flags["strict-phase-seconds"],
|
|
12015
|
+
timeoutSeconds: flags.timeout
|
|
12016
|
+
});
|
|
12017
|
+
if (reply === null) {
|
|
12018
|
+
process.stderr.write(`Timed out after ${flags.timeout}s waiting for a reply from ${args.recipient}.\n`);
|
|
12019
|
+
process.exitCode = 1;
|
|
12020
|
+
return;
|
|
12021
|
+
}
|
|
12022
|
+
if (flags.json) {
|
|
12023
|
+
const envelope = {
|
|
12024
|
+
sent,
|
|
12025
|
+
reply
|
|
12026
|
+
};
|
|
12027
|
+
this.log(JSON.stringify(envelope, null, 2));
|
|
12028
|
+
} else {
|
|
12029
|
+
const body = reply.body_text ?? reply.body_html ?? "";
|
|
12030
|
+
this.log(body);
|
|
12031
|
+
}
|
|
12032
|
+
});
|
|
12033
|
+
}
|
|
12034
|
+
};
|
|
12035
|
+
async function waitForReply(params) {
|
|
12036
|
+
const totalDeadline = params.timeoutSeconds === 0 ? null : Date.now() + params.timeoutSeconds * 1e3;
|
|
12037
|
+
const strictDeadlineFromBudget = Date.now() + params.strictPhaseSeconds * 1e3;
|
|
12038
|
+
const strictDeadline = params.strictOnly ? totalDeadline : totalDeadline === null ? strictDeadlineFromBudget : Math.min(strictDeadlineFromBudget, totalDeadline);
|
|
12039
|
+
const phases = [{
|
|
12040
|
+
label: "strict",
|
|
12041
|
+
filters: { replyToSentEmailId: params.sentId },
|
|
12042
|
+
deadline: strictDeadline
|
|
12043
|
+
}];
|
|
12044
|
+
if (!params.strictOnly) phases.push({
|
|
12045
|
+
label: "fallback",
|
|
12046
|
+
filters: {
|
|
12047
|
+
from: params.recipient,
|
|
12048
|
+
to: params.from
|
|
12049
|
+
},
|
|
12050
|
+
deadline: totalDeadline
|
|
12051
|
+
});
|
|
12052
|
+
let strictFilterUnsupported = false;
|
|
12053
|
+
for (const phase of phases) {
|
|
12054
|
+
if (phase.label === "strict" && strictFilterUnsupported) continue;
|
|
12055
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
12056
|
+
let cursor = null;
|
|
12057
|
+
while (true) {
|
|
12058
|
+
if (phase.deadline !== null && Date.now() >= phase.deadline) break;
|
|
12059
|
+
const page = await fetchEmailSearchPage({
|
|
12060
|
+
apiClient: params.apiClient,
|
|
12061
|
+
cursor,
|
|
12062
|
+
filters: phase.filters,
|
|
12063
|
+
pageSize: params.pageSize,
|
|
12064
|
+
since: params.sentAtIso
|
|
12065
|
+
});
|
|
12066
|
+
if (!page.ok) {
|
|
12067
|
+
const payload = extractErrorPayload(page.error);
|
|
12068
|
+
writeErrorWithHints(payload);
|
|
12069
|
+
surfaceUnauthorizedHint({
|
|
12070
|
+
...params.authFailureContext,
|
|
12071
|
+
payload
|
|
12072
|
+
});
|
|
12073
|
+
throw new Errors.CLIError("Failed to poll for reply.", { exit: 1 });
|
|
12074
|
+
}
|
|
12075
|
+
let lastAccepted;
|
|
12076
|
+
for (let i = page.rows.length - 1; i >= 0; i--) {
|
|
12077
|
+
const row = page.rows[i];
|
|
12078
|
+
if (row.status === "accepted" || row.status === "completed") {
|
|
12079
|
+
lastAccepted = row;
|
|
12080
|
+
break;
|
|
12081
|
+
}
|
|
12082
|
+
}
|
|
12083
|
+
if (lastAccepted) cursor = encodeReceivedAtSearchCursor(lastAccepted);
|
|
12084
|
+
const matches = collectNewAcceptedEmails(page.rows, seenIds);
|
|
12085
|
+
for (const match of matches) {
|
|
12086
|
+
const full = await getEmail({
|
|
12087
|
+
client: params.apiClient.client,
|
|
12088
|
+
path: { id: match.id },
|
|
12089
|
+
responseStyle: "fields"
|
|
12090
|
+
});
|
|
12091
|
+
if (full.error) {
|
|
12092
|
+
const payload = extractErrorPayload(full.error);
|
|
12093
|
+
writeErrorWithHints(payload);
|
|
12094
|
+
surfaceUnauthorizedHint({
|
|
12095
|
+
...params.authFailureContext,
|
|
12096
|
+
payload
|
|
12097
|
+
});
|
|
12098
|
+
throw new Errors.CLIError(`Reply landed but fetching the full body failed (id=${match.id}).`, { exit: 1 });
|
|
12099
|
+
}
|
|
12100
|
+
const envelope = full.data;
|
|
12101
|
+
const detail = envelope?.data ?? envelope ?? null;
|
|
12102
|
+
if (!detail) throw new Errors.CLIError(`Reply landed but the email body could not be loaded (id=${match.id}).`, { exit: 1 });
|
|
12103
|
+
if (phase.label === "strict" && detail.reply_to_sent_email_id !== params.sentId) {
|
|
12104
|
+
if (!strictFilterUnsupported) process.stderr.write(params.strictOnly ? "Strict-phase reply matching is not supported by this Primitive API host; --strict-only requires server support so the command will exit without a match.\n" : "Strict-phase reply matching is not supported by this Primitive API host; falling back to time-window matching.\n");
|
|
12105
|
+
strictFilterUnsupported = true;
|
|
12106
|
+
continue;
|
|
12107
|
+
}
|
|
12108
|
+
return detail;
|
|
12109
|
+
}
|
|
12110
|
+
if (strictFilterUnsupported && phase.label === "strict") break;
|
|
12111
|
+
if (lastAccepted !== void 0) continue;
|
|
12112
|
+
if (phase.deadline !== null && Date.now() >= phase.deadline) break;
|
|
12113
|
+
if (totalDeadline !== null && Date.now() >= totalDeadline) return null;
|
|
12114
|
+
await sleep$1(params.interval * 1e3);
|
|
12115
|
+
}
|
|
12116
|
+
}
|
|
12117
|
+
return null;
|
|
12118
|
+
}
|
|
12119
|
+
//#endregion
|
|
11740
12120
|
//#region src/oclif/commands/config.ts
|
|
11741
12121
|
function loadOrCreateConfig(configDir) {
|
|
11742
12122
|
return loadCliConfig(configDir) ?? emptyCliConfig();
|
|
@@ -12197,7 +12577,7 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
|
|
|
12197
12577
|
if (result.error) {
|
|
12198
12578
|
const errorPayload = extractErrorPayload(result.error);
|
|
12199
12579
|
writeErrorWithHints(errorPayload);
|
|
12200
|
-
|
|
12580
|
+
surfaceUnauthorizedHint({
|
|
12201
12581
|
auth,
|
|
12202
12582
|
baseUrlOverridden,
|
|
12203
12583
|
configDir: this.config.configDir,
|
|
@@ -12223,104 +12603,6 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
|
|
|
12223
12603
|
}
|
|
12224
12604
|
};
|
|
12225
12605
|
//#endregion
|
|
12226
|
-
//#region src/oclif/commands/emails-poll.ts
|
|
12227
|
-
function quoteDslValue(value) {
|
|
12228
|
-
if (/^[^\s"]+$/.test(value)) return value;
|
|
12229
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
12230
|
-
}
|
|
12231
|
-
function combineQ(q, domain) {
|
|
12232
|
-
const parts = [q?.trim(), domain ? `domain:${quoteDslValue(domain.trim())}` : void 0].filter((part) => Boolean(part));
|
|
12233
|
-
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
12234
|
-
}
|
|
12235
|
-
function normalizeIsoDate(value, label) {
|
|
12236
|
-
const parsed = new Date(value);
|
|
12237
|
-
if (Number.isNaN(parsed.getTime())) throw new Error(`${label} must be a valid date or ISO-8601 timestamp.`);
|
|
12238
|
-
return parsed.toISOString();
|
|
12239
|
-
}
|
|
12240
|
-
function filtersFromFlags(flags) {
|
|
12241
|
-
return {
|
|
12242
|
-
body: flags.body,
|
|
12243
|
-
domain: flags.domain,
|
|
12244
|
-
domainId: flags["domain-id"],
|
|
12245
|
-
from: flags.from,
|
|
12246
|
-
hasAttachment: flags["has-attachment"],
|
|
12247
|
-
q: flags.q,
|
|
12248
|
-
spamScoreGte: flags["spam-score-gte"],
|
|
12249
|
-
spamScoreLt: flags["spam-score-lt"],
|
|
12250
|
-
subject: flags.subject,
|
|
12251
|
-
to: flags.to
|
|
12252
|
-
};
|
|
12253
|
-
}
|
|
12254
|
-
function sinceFromFlags(flags) {
|
|
12255
|
-
if (flags.since) return normalizeIsoDate(flags.since, "--since");
|
|
12256
|
-
return flags["include-existing"] ? void 0 : (/* @__PURE__ */ new Date()).toISOString();
|
|
12257
|
-
}
|
|
12258
|
-
function buildEmailSearchQuery(params) {
|
|
12259
|
-
const query = {
|
|
12260
|
-
include_facets: "false",
|
|
12261
|
-
limit: params.pageSize,
|
|
12262
|
-
snippet: "false",
|
|
12263
|
-
sort: "received_at_asc"
|
|
12264
|
-
};
|
|
12265
|
-
const q = combineQ(params.filters.q, params.filters.domain);
|
|
12266
|
-
if (q) query.q = q;
|
|
12267
|
-
if (params.filters.body) query.body = params.filters.body;
|
|
12268
|
-
if (params.filters.domainId) query.domain_id = params.filters.domainId;
|
|
12269
|
-
if (params.filters.from) query.from = params.filters.from;
|
|
12270
|
-
if (params.filters.hasAttachment !== void 0) query.has_attachment = params.filters.hasAttachment ? "true" : "false";
|
|
12271
|
-
if (params.filters.spamScoreGte !== void 0) query.spam_score_gte = params.filters.spamScoreGte;
|
|
12272
|
-
if (params.filters.spamScoreLt !== void 0) query.spam_score_lt = params.filters.spamScoreLt;
|
|
12273
|
-
if (params.filters.subject) query.subject = params.filters.subject;
|
|
12274
|
-
if (params.filters.to) query.to = params.filters.to;
|
|
12275
|
-
if (params.since) query.date_from = params.since;
|
|
12276
|
-
if (params.cursor) query.cursor = params.cursor;
|
|
12277
|
-
return query;
|
|
12278
|
-
}
|
|
12279
|
-
function encodeReceivedAtSearchCursor(email) {
|
|
12280
|
-
const raw = `r|${new Date(email.received_at).toISOString()}|${email.id}`;
|
|
12281
|
-
return Buffer.from(raw, "utf8").toString("base64url");
|
|
12282
|
-
}
|
|
12283
|
-
function cursorFromRows(rows) {
|
|
12284
|
-
const last = rows.at(-1);
|
|
12285
|
-
return last ? encodeReceivedAtSearchCursor(last) : null;
|
|
12286
|
-
}
|
|
12287
|
-
function collectNewAcceptedEmails(rows, seenIds) {
|
|
12288
|
-
const fresh = [];
|
|
12289
|
-
for (const row of rows) {
|
|
12290
|
-
if (row.status !== "accepted" && row.status !== "completed") continue;
|
|
12291
|
-
if (seenIds.has(row.id)) continue;
|
|
12292
|
-
seenIds.add(row.id);
|
|
12293
|
-
fresh.push(row);
|
|
12294
|
-
}
|
|
12295
|
-
return fresh;
|
|
12296
|
-
}
|
|
12297
|
-
async function fetchEmailSearchPage(params) {
|
|
12298
|
-
const result = await searchEmails({
|
|
12299
|
-
client: params.apiClient.client,
|
|
12300
|
-
query: buildEmailSearchQuery({
|
|
12301
|
-
cursor: params.cursor,
|
|
12302
|
-
filters: params.filters,
|
|
12303
|
-
pageSize: params.pageSize,
|
|
12304
|
-
since: params.since
|
|
12305
|
-
}),
|
|
12306
|
-
responseStyle: "fields"
|
|
12307
|
-
});
|
|
12308
|
-
if (result.error) return {
|
|
12309
|
-
ok: false,
|
|
12310
|
-
error: result.error
|
|
12311
|
-
};
|
|
12312
|
-
const envelope = result.data;
|
|
12313
|
-
const rows = envelope?.data ?? [];
|
|
12314
|
-
return {
|
|
12315
|
-
ok: true,
|
|
12316
|
-
cursor: envelope?.meta.cursor ?? cursorFromRows(rows),
|
|
12317
|
-
rows
|
|
12318
|
-
};
|
|
12319
|
-
}
|
|
12320
|
-
function sleep$1(ms) {
|
|
12321
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12322
|
-
}
|
|
12323
|
-
//#endregion
|
|
12324
12606
|
//#region src/oclif/commands/emails-wait.ts
|
|
12325
12607
|
const DEFAULT_WAIT_TIMEOUT_SECONDS$1 = 300;
|
|
12326
12608
|
function cliError$4(message) {
|
|
@@ -12373,6 +12655,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
12373
12655
|
min: 1
|
|
12374
12656
|
}),
|
|
12375
12657
|
q: Flags.string({ description: "Full-text search DSL query" }),
|
|
12658
|
+
"reply-to-sent-email-id": Flags.string({ description: "Filter to inbound emails that are threaded replies to a specific outbound send (UUID from a /v1/send-mail response). Combine with --to and --since for the strictest version of the wait-for-reply pattern." }),
|
|
12376
12659
|
since: Flags.string({ description: "Only match emails received on or after this date/time" }),
|
|
12377
12660
|
"spam-score-gte": Flags.integer({ description: "Only match emails with spam score greater than or equal to this value" }),
|
|
12378
12661
|
"spam-score-lt": Flags.integer({ description: "Only match emails with spam score below this value" }),
|
|
@@ -12417,7 +12700,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
12417
12700
|
if (!page.ok) {
|
|
12418
12701
|
const payload = extractErrorPayload(page.error);
|
|
12419
12702
|
writeErrorWithHints(payload);
|
|
12420
|
-
|
|
12703
|
+
surfaceUnauthorizedHint({
|
|
12421
12704
|
auth,
|
|
12422
12705
|
baseUrlOverridden,
|
|
12423
12706
|
configDir: this.config.configDir,
|
|
@@ -12539,7 +12822,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
12539
12822
|
if (!page.ok) {
|
|
12540
12823
|
const payload = extractErrorPayload(page.error);
|
|
12541
12824
|
writeErrorWithHints(payload);
|
|
12542
|
-
|
|
12825
|
+
surfaceUnauthorizedHint({
|
|
12543
12826
|
auth,
|
|
12544
12827
|
baseUrlOverridden,
|
|
12545
12828
|
configDir: this.config.configDir,
|
|
@@ -12593,7 +12876,6 @@ function toDeployWaitSnapshot(value) {
|
|
|
12593
12876
|
...value.deploy_error !== void 0 ? { deploy_error: value.deploy_error } : {},
|
|
12594
12877
|
deploy_status: value.deploy_status,
|
|
12595
12878
|
...value.deployed_at !== void 0 ? { deployed_at: value.deployed_at } : {},
|
|
12596
|
-
gateway_url: value.gateway_url,
|
|
12597
12879
|
id: value.id,
|
|
12598
12880
|
name: value.name,
|
|
12599
12881
|
...value.updated_at !== void 0 ? { updated_at: value.updated_at } : {}
|
|
@@ -13261,7 +13543,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
13261
13543
|
process.stderr.write(`Function ${outcome.created.name} (${outcome.created.id}) was created and secrets [${succeeded}] were written, but the final redeploy failed; the new bindings are NOT yet live. Re-run \`primitive functions redeploy --id ${outcome.created.id} --file <bundle>\` once the cause is fixed.\n`);
|
|
13262
13544
|
}
|
|
13263
13545
|
writeErrorWithHints(outcome.payload);
|
|
13264
|
-
|
|
13546
|
+
surfaceUnauthorizedHint({
|
|
13265
13547
|
...authFailureContext,
|
|
13266
13548
|
payload: outcome.payload
|
|
13267
13549
|
});
|
|
@@ -13284,7 +13566,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
13284
13566
|
});
|
|
13285
13567
|
if (waitResult.kind === "error") {
|
|
13286
13568
|
writeErrorWithHints(waitResult.payload);
|
|
13287
|
-
|
|
13569
|
+
surfaceUnauthorizedHint({
|
|
13288
13570
|
...authFailureContext,
|
|
13289
13571
|
payload: waitResult.payload
|
|
13290
13572
|
});
|
|
@@ -13317,22 +13599,33 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
13317
13599
|
name: "Primitive Team",
|
|
13318
13600
|
url: "https://primitive.dev"
|
|
13319
13601
|
};
|
|
13320
|
-
const SDK_VERSION_RANGE = "^0.
|
|
13321
|
-
const CLI_VERSION_RANGE = "^0.
|
|
13602
|
+
const SDK_VERSION_RANGE = "^0.31.1";
|
|
13603
|
+
const CLI_VERSION_RANGE = "^0.31.1";
|
|
13322
13604
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
13323
13605
|
function renderHandler() {
|
|
13324
|
-
return `// env.PRIMITIVE_API_KEY
|
|
13606
|
+
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
13607
|
+
// env.PRIMITIVE_API_BASE_URL are auto-injected by the Primitive Functions runtime.
|
|
13325
13608
|
//
|
|
13326
13609
|
// Imports are taken from the \`/api\` subpath, NOT the package root.
|
|
13327
13610
|
// The root export pulls in webhook signing helpers that depend on
|
|
13328
13611
|
// \`node:crypto\`, which breaks Workers-style bundles. The \`/api\`
|
|
13329
|
-
// subpath is Workers-safe and exposes everything a handler needs
|
|
13612
|
+
// subpath is Workers-safe and exposes everything a handler needs,
|
|
13613
|
+
// including Web Crypto signature verification.
|
|
13330
13614
|
import {
|
|
13331
13615
|
createPrimitiveClient,
|
|
13332
13616
|
normalizeReceivedEmail,
|
|
13617
|
+
PRIMITIVE_SIGNATURE_HEADER,
|
|
13333
13618
|
type EmailReceivedEvent,
|
|
13619
|
+
verifyWebhookSignature,
|
|
13620
|
+
WebhookVerificationError,
|
|
13334
13621
|
} from "@primitivedotdev/sdk/api";
|
|
13335
13622
|
|
|
13623
|
+
interface Env {
|
|
13624
|
+
PRIMITIVE_API_KEY: string;
|
|
13625
|
+
PRIMITIVE_API_BASE_URL: string;
|
|
13626
|
+
PRIMITIVE_WEBHOOK_SECRET: string;
|
|
13627
|
+
}
|
|
13628
|
+
|
|
13336
13629
|
// Loop-protection knob. Only used by the isLoop helper below; the
|
|
13337
13630
|
// handler's outbound reply address is server-defaulted (no
|
|
13338
13631
|
// from-address parameter is passed to client.reply). Update this if
|
|
@@ -13382,10 +13675,26 @@ export function isLoop(event: EmailReceivedEvent): boolean {
|
|
|
13382
13675
|
export default {
|
|
13383
13676
|
async fetch(
|
|
13384
13677
|
req: Request,
|
|
13385
|
-
env:
|
|
13678
|
+
env: Env,
|
|
13386
13679
|
): Promise<Response> {
|
|
13387
13680
|
try {
|
|
13388
|
-
const
|
|
13681
|
+
const rawBody = await req.text();
|
|
13682
|
+
const signatureHeader = req.headers.get(PRIMITIVE_SIGNATURE_HEADER) ?? "";
|
|
13683
|
+
|
|
13684
|
+
try {
|
|
13685
|
+
await verifyWebhookSignature({
|
|
13686
|
+
rawBody,
|
|
13687
|
+
signatureHeader,
|
|
13688
|
+
secret: env.PRIMITIVE_WEBHOOK_SECRET,
|
|
13689
|
+
});
|
|
13690
|
+
} catch (signatureError) {
|
|
13691
|
+
if (signatureError instanceof WebhookVerificationError) {
|
|
13692
|
+
return new Response("invalid signature", { status: 401 });
|
|
13693
|
+
}
|
|
13694
|
+
throw signatureError;
|
|
13695
|
+
}
|
|
13696
|
+
|
|
13697
|
+
const event = JSON.parse(rawBody) as EmailReceivedEvent;
|
|
13389
13698
|
|
|
13390
13699
|
// Only "email.received" exists today. Future event types will
|
|
13391
13700
|
// arrive with a different discriminator; return 2xx so the
|
|
@@ -13395,15 +13704,17 @@ export default {
|
|
|
13395
13704
|
return Response.json({ ok: true, skipped: event.event });
|
|
13396
13705
|
}
|
|
13397
13706
|
|
|
13398
|
-
// Loop protection runs immediately after
|
|
13399
|
-
//
|
|
13400
|
-
//
|
|
13401
|
-
// how to extend it.
|
|
13707
|
+
// Loop protection runs immediately after signature verification
|
|
13708
|
+
// and the event-type check. See isLoop above for what's covered
|
|
13709
|
+
// and how to extend it.
|
|
13402
13710
|
if (isLoop(event)) {
|
|
13403
13711
|
return Response.json({ ok: true, skipped: "loop" });
|
|
13404
13712
|
}
|
|
13405
13713
|
|
|
13406
|
-
const client = createPrimitiveClient({
|
|
13714
|
+
const client = createPrimitiveClient({
|
|
13715
|
+
apiKey: env.PRIMITIVE_API_KEY,
|
|
13716
|
+
apiBaseUrl1: env.PRIMITIVE_API_BASE_URL,
|
|
13717
|
+
});
|
|
13407
13718
|
|
|
13408
13719
|
// Recipient gate
|
|
13409
13720
|
// https://www.primitive.dev/docs/sending#who-you-can-send-to
|
|
@@ -13571,7 +13882,7 @@ function renderEmailReplyTemplateFiles(name) {
|
|
|
13571
13882
|
const FUNCTION_TEMPLATES = [{
|
|
13572
13883
|
author: PRIMITIVE_TEAM_AUTHOR,
|
|
13573
13884
|
dependencies: ["@primitivedotdev/sdk"],
|
|
13574
|
-
description: "A deployable TypeScript email handler that
|
|
13885
|
+
description: "A deployable TypeScript email handler that verifies signed email.received events, skips likely loops, and replies with the Primitive SDK.",
|
|
13575
13886
|
devDependencies: [
|
|
13576
13887
|
"@primitivedotdev/cli",
|
|
13577
13888
|
"esbuild",
|
|
@@ -13828,7 +14139,7 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
|
|
|
13828
14139
|
if (result.error) {
|
|
13829
14140
|
const errorPayload = extractErrorPayload(result.error);
|
|
13830
14141
|
writeErrorWithHints(errorPayload);
|
|
13831
|
-
|
|
14142
|
+
surfaceUnauthorizedHint({
|
|
13832
14143
|
auth,
|
|
13833
14144
|
baseUrlOverridden,
|
|
13834
14145
|
configDir: this.config.configDir,
|
|
@@ -14084,7 +14395,7 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
14084
14395
|
process.stderr.write(`Secrets [${succeeded}] were written, but the redeploy step failed; the new bindings are NOT yet live. Re-run \`primitive functions redeploy --id ${flags.id} --file <bundle>\` once the cause is fixed.\n`);
|
|
14085
14396
|
}
|
|
14086
14397
|
writeErrorWithHints(outcome.payload);
|
|
14087
|
-
|
|
14398
|
+
surfaceUnauthorizedHint({
|
|
14088
14399
|
...authFailureContext,
|
|
14089
14400
|
payload: outcome.payload
|
|
14090
14401
|
});
|
|
@@ -14106,7 +14417,7 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
14106
14417
|
});
|
|
14107
14418
|
if (waitResult.kind === "error") {
|
|
14108
14419
|
writeErrorWithHints(waitResult.payload);
|
|
14109
|
-
|
|
14420
|
+
surfaceUnauthorizedHint({
|
|
14110
14421
|
...authFailureContext,
|
|
14111
14422
|
payload: waitResult.payload
|
|
14112
14423
|
});
|
|
@@ -14307,7 +14618,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
14307
14618
|
if (outcome.stage === "get-function") process.stderr.write("Secret was written, but reading current function code for redeploy failed; the secret is NOT yet live. Re-run with --redeploy, or call `primitive functions redeploy --id <id> --file <bundle>` once you have the bundle.\n");
|
|
14308
14619
|
else if (outcome.stage === "redeploy") process.stderr.write("Secret was written, but the redeploy step failed; the secret is NOT yet live. Inspect the function's deploy_error and re-run `primitive functions redeploy --id <id> --file <bundle>` once the cause is fixed.\n");
|
|
14309
14620
|
writeErrorWithHints(outcome.payload);
|
|
14310
|
-
|
|
14621
|
+
surfaceUnauthorizedHint({
|
|
14311
14622
|
...authFailureContext,
|
|
14312
14623
|
payload: outcome.payload
|
|
14313
14624
|
});
|
|
@@ -14495,7 +14806,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
14495
14806
|
if (triggerResult.error) {
|
|
14496
14807
|
const payload = extractErrorPayload(triggerResult.error);
|
|
14497
14808
|
writeErrorWithHints(payload);
|
|
14498
|
-
|
|
14809
|
+
surfaceUnauthorizedHint({
|
|
14499
14810
|
auth,
|
|
14500
14811
|
baseUrlOverridden,
|
|
14501
14812
|
configDir: this.config.configDir,
|
|
@@ -14533,7 +14844,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
14533
14844
|
if (!page.ok) {
|
|
14534
14845
|
const payload = extractErrorPayload(page.error);
|
|
14535
14846
|
writeErrorWithHints(payload);
|
|
14536
|
-
|
|
14847
|
+
surfaceUnauthorizedHint({
|
|
14537
14848
|
auth,
|
|
14538
14849
|
baseUrlOverridden,
|
|
14539
14850
|
configDir: this.config.configDir,
|
|
@@ -14561,7 +14872,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
14561
14872
|
if (result.error) {
|
|
14562
14873
|
const payload = extractErrorPayload(result.error);
|
|
14563
14874
|
writeErrorWithHints(payload);
|
|
14564
|
-
|
|
14875
|
+
surfaceUnauthorizedHint({
|
|
14565
14876
|
auth,
|
|
14566
14877
|
baseUrlOverridden,
|
|
14567
14878
|
configDir: this.config.configDir,
|
|
@@ -14641,22 +14952,17 @@ async function checkExistingLogin(params) {
|
|
|
14641
14952
|
})))(apiClient);
|
|
14642
14953
|
if (!result.error) return { status: "valid" };
|
|
14643
14954
|
const payload = extractErrorPayload(result.error);
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
},
|
|
14652
|
-
baseUrlOverridden: requestConfig.baseUrlOverridden,
|
|
14653
|
-
configDir: params.configDir,
|
|
14654
|
-
payload
|
|
14655
|
-
})) return { status: "removed_stale" };
|
|
14955
|
+
const code = extractErrorCode(payload);
|
|
14956
|
+
const baseUrlDiffersFromSaved = requestConfig.baseUrlOverridden && requestConfig.apiBaseUrl1 !== params.credentials.api_base_url_1;
|
|
14957
|
+
if (code === API_ERROR_CODES.unauthorized && !baseUrlDiffersFromSaved) {
|
|
14958
|
+
deleteCliCredentials(params.configDir);
|
|
14959
|
+
process.stderr.write("Removed saved Primitive CLI credentials because the existing key was rejected during login. Continuing with a fresh login.\n");
|
|
14960
|
+
return { status: "removed_stale" };
|
|
14961
|
+
}
|
|
14656
14962
|
return {
|
|
14657
14963
|
status: "blocked",
|
|
14658
14964
|
payload,
|
|
14659
|
-
message:
|
|
14965
|
+
message: code === API_ERROR_CODES.unauthorized ? "Saved Primitive CLI credentials were rejected by an API URL different from the one they were saved with. Run `primitive logout` to remove them, or switch back to the original environment before logging in again." : "A saved Primitive CLI login exists, but the CLI could not verify whether it is still valid. Run `primitive logout` before logging in again."
|
|
14660
14966
|
};
|
|
14661
14967
|
}
|
|
14662
14968
|
var LoginCommand = class LoginCommand extends Command {
|
|
@@ -15041,7 +15347,7 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
15041
15347
|
if (result.error) {
|
|
15042
15348
|
const errorPayload = extractErrorPayload(result.error);
|
|
15043
15349
|
writeErrorWithHints(errorPayload);
|
|
15044
|
-
|
|
15350
|
+
surfaceUnauthorizedHint({
|
|
15045
15351
|
auth,
|
|
15046
15352
|
baseUrlOverridden,
|
|
15047
15353
|
configDir: this.config.configDir,
|
|
@@ -15060,39 +15366,6 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
15060
15366
|
};
|
|
15061
15367
|
//#endregion
|
|
15062
15368
|
//#region src/oclif/commands/send.ts
|
|
15063
|
-
const SUBJECT_MAX_LENGTH = 200;
|
|
15064
|
-
function deriveSubject(body) {
|
|
15065
|
-
for (const line of body.split("\n")) {
|
|
15066
|
-
const trimmed = line.trim();
|
|
15067
|
-
if (!trimmed) continue;
|
|
15068
|
-
return trimmed.length > SUBJECT_MAX_LENGTH ? `${trimmed.slice(0, SUBJECT_MAX_LENGTH - 3)}...` : trimmed;
|
|
15069
|
-
}
|
|
15070
|
-
return "Message";
|
|
15071
|
-
}
|
|
15072
|
-
function isVerifiedDomain(domain) {
|
|
15073
|
-
return domain.is_active === true;
|
|
15074
|
-
}
|
|
15075
|
-
async function pickDefaultFromAddress(apiClient, authFailureContext) {
|
|
15076
|
-
const result = await listDomains({
|
|
15077
|
-
client: apiClient.client,
|
|
15078
|
-
responseStyle: "fields"
|
|
15079
|
-
});
|
|
15080
|
-
if (result.error) {
|
|
15081
|
-
const errorPayload = extractErrorPayload(result.error);
|
|
15082
|
-
if (extractErrorCode(errorPayload) === API_ERROR_CODES.unauthorized) {
|
|
15083
|
-
writeErrorWithHints(errorPayload);
|
|
15084
|
-
removeStaleSavedCredentialOnUnauthorized({
|
|
15085
|
-
...authFailureContext,
|
|
15086
|
-
payload: errorPayload
|
|
15087
|
-
});
|
|
15088
|
-
throw new Errors.CLIError("Cannot send: API key is missing or invalid (see hint above).", { exit: 1 });
|
|
15089
|
-
}
|
|
15090
|
-
throw new Errors.CLIError(`Could not look up your verified domains to default --from. Pass --from explicitly. Underlying error: ${formatErrorPayload(errorPayload)}`);
|
|
15091
|
-
}
|
|
15092
|
-
const first = result.data?.data?.find(isVerifiedDomain);
|
|
15093
|
-
if (!first) throw new Errors.CLIError("No active verified outbound domain found on this account; pass --from explicitly. To set up outbound, claim a domain via `primitive domains add` and verify it.");
|
|
15094
|
-
return `agent@${first.domain}`;
|
|
15095
|
-
}
|
|
15096
15369
|
var SendCommand = class SendCommand extends Command {
|
|
15097
15370
|
static description = `Send an outbound email. Agent-grade shortcut for \`sending send\` with sensible defaults.
|
|
15098
15371
|
|
|
@@ -15184,7 +15457,7 @@ var SendCommand = class SendCommand extends Command {
|
|
|
15184
15457
|
if (result.error) {
|
|
15185
15458
|
const errorPayload = extractErrorPayload(result.error);
|
|
15186
15459
|
writeErrorWithHints(errorPayload);
|
|
15187
|
-
|
|
15460
|
+
surfaceUnauthorizedHint({
|
|
15188
15461
|
...authFailureContext,
|
|
15189
15462
|
payload: errorPayload
|
|
15190
15463
|
});
|
|
@@ -15629,7 +15902,7 @@ var WhoamiCommand = class WhoamiCommand extends Command {
|
|
|
15629
15902
|
if (result.error) {
|
|
15630
15903
|
const errorPayload = extractErrorPayload(result.error);
|
|
15631
15904
|
writeErrorWithHints(errorPayload);
|
|
15632
|
-
|
|
15905
|
+
surfaceUnauthorizedHint({
|
|
15633
15906
|
auth,
|
|
15634
15907
|
baseUrlOverridden,
|
|
15635
15908
|
configDir: this.config.configDir,
|
|
@@ -15860,6 +16133,7 @@ const COMMANDS = {
|
|
|
15860
16133
|
describe: DescribeCommand,
|
|
15861
16134
|
send: SendCommand,
|
|
15862
16135
|
reply: ReplyCommand,
|
|
16136
|
+
chat: ChatCommand,
|
|
15863
16137
|
login: LoginCommand,
|
|
15864
16138
|
signup: SignupCommand,
|
|
15865
16139
|
logout: LogoutCommand,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitivedotdev/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
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,
|