@primitivedotdev/cli 0.26.3 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/dist/oclif/index.js +1206 -111
- package/package.json +12 -6
package/dist/oclif/index.js
CHANGED
|
@@ -4,6 +4,8 @@ import { randomUUID } from "node:crypto";
|
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { hostname } from "node:os";
|
|
7
|
+
import process$1 from "node:process";
|
|
8
|
+
import { createInterface } from "node:readline/promises";
|
|
7
9
|
//#region \0rolldown/runtime.js
|
|
8
10
|
var __defProp = Object.defineProperty;
|
|
9
11
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -664,11 +666,13 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
664
666
|
replayDelivery: () => replayDelivery,
|
|
665
667
|
replayEmailWebhooks: () => replayEmailWebhooks,
|
|
666
668
|
replyToEmail: () => replyToEmail,
|
|
669
|
+
resendCliSignupVerification: () => resendCliSignupVerification,
|
|
667
670
|
rotateWebhookSecret: () => rotateWebhookSecret,
|
|
668
671
|
searchEmails: () => searchEmails,
|
|
669
672
|
sendEmail: () => sendEmail,
|
|
670
673
|
setFunctionSecret: () => setFunctionSecret,
|
|
671
674
|
startCliLogin: () => startCliLogin,
|
|
675
|
+
startCliSignup: () => startCliSignup,
|
|
672
676
|
testEndpoint: () => testEndpoint,
|
|
673
677
|
testFunction: () => testFunction,
|
|
674
678
|
updateAccount: () => updateAccount,
|
|
@@ -676,6 +680,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
676
680
|
updateEndpoint: () => updateEndpoint,
|
|
677
681
|
updateFilter: () => updateFilter,
|
|
678
682
|
updateFunction: () => updateFunction,
|
|
683
|
+
verifyCliSignup: () => verifyCliSignup,
|
|
679
684
|
verifyDomain: () => verifyDomain
|
|
680
685
|
});
|
|
681
686
|
/**
|
|
@@ -711,6 +716,54 @@ const pollCliLogin = (options) => (options.client ?? client).post({
|
|
|
711
716
|
}
|
|
712
717
|
});
|
|
713
718
|
/**
|
|
719
|
+
* Start CLI account signup
|
|
720
|
+
*
|
|
721
|
+
* Starts a terminal-native CLI signup. The API validates the signup code,
|
|
722
|
+
* creates a pending signup session, sends an email verification code, and
|
|
723
|
+
* returns an opaque signup token used by the resend and verify steps. This
|
|
724
|
+
* endpoint does not require an API key.
|
|
725
|
+
*
|
|
726
|
+
*/
|
|
727
|
+
const startCliSignup = (options) => (options.client ?? client).post({
|
|
728
|
+
url: "/cli/signup/start",
|
|
729
|
+
...options,
|
|
730
|
+
headers: {
|
|
731
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
732
|
+
...options.headers
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
/**
|
|
736
|
+
* Resend CLI signup verification code
|
|
737
|
+
*
|
|
738
|
+
* Sends a new email verification code for a pending CLI signup session.
|
|
739
|
+
* This endpoint does not require an API key.
|
|
740
|
+
*
|
|
741
|
+
*/
|
|
742
|
+
const resendCliSignupVerification = (options) => (options.client ?? client).post({
|
|
743
|
+
url: "/cli/signup/resend",
|
|
744
|
+
...options,
|
|
745
|
+
headers: {
|
|
746
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
747
|
+
...options.headers
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
/**
|
|
751
|
+
* Verify CLI signup and create API key
|
|
752
|
+
*
|
|
753
|
+
* Verifies the email code for a CLI signup session, creates the account,
|
|
754
|
+
* redeems the reserved signup code, mints an org-scoped CLI API key, and
|
|
755
|
+
* returns the raw key exactly once. This endpoint does not require an API key.
|
|
756
|
+
*
|
|
757
|
+
*/
|
|
758
|
+
const verifyCliSignup = (options) => (options.client ?? client).post({
|
|
759
|
+
url: "/cli/signup/verify",
|
|
760
|
+
...options,
|
|
761
|
+
headers: {
|
|
762
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
763
|
+
...options.headers
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
/**
|
|
714
767
|
* Revoke the current CLI API key
|
|
715
768
|
*
|
|
716
769
|
* Revokes the API key used to authenticate the request. CLI clients use
|
|
@@ -1451,8 +1504,9 @@ const listFunctions = (options) => (options?.client ?? client).get({
|
|
|
1451
1504
|
* than relying on external imports.
|
|
1452
1505
|
*
|
|
1453
1506
|
* **Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`
|
|
1454
|
-
* (optional) is capped at 5 MiB UTF-8
|
|
1455
|
-
*
|
|
1507
|
+
* (optional) is capped at 5 MiB UTF-8, stored with each deployment
|
|
1508
|
+
* attempt, and sent to the runtime so stack traces can resolve to
|
|
1509
|
+
* original source files.
|
|
1456
1510
|
*
|
|
1457
1511
|
* **Auto-wiring.** On successful deploy, Primitive automatically
|
|
1458
1512
|
* creates a webhook endpoint that delivers inbound mail to the
|
|
@@ -1525,11 +1579,10 @@ const getFunction = (options) => (options.client ?? client).get({
|
|
|
1525
1579
|
* passing the same `code` re-runs the deploy and refreshes the
|
|
1526
1580
|
* binding set with the latest values from the secrets table.
|
|
1527
1581
|
*
|
|
1528
|
-
* On
|
|
1529
|
-
*
|
|
1530
|
-
* `
|
|
1531
|
-
*
|
|
1532
|
-
* without polling.
|
|
1582
|
+
* On deploy failure, the previously-deployed code stays live; the
|
|
1583
|
+
* runtime never serves a half-built bundle. The response uses
|
|
1584
|
+
* `error.code` `deploy_failed`, and the function's `deploy_error`
|
|
1585
|
+
* field carries the latest deploy error for dashboard/API reads.
|
|
1533
1586
|
*
|
|
1534
1587
|
*/
|
|
1535
1588
|
const updateFunction = (options) => (options.client ?? client).put({
|
|
@@ -1705,7 +1758,7 @@ const openapiDocument = {
|
|
|
1705
1758
|
"info": {
|
|
1706
1759
|
"title": "Primitive API",
|
|
1707
1760
|
"version": "1.0.0",
|
|
1708
|
-
"description": "The Primitive API lets you manage domains, emails, webhook endpoints,\nfilters, and account settings programmatically.\n\n## Authentication\n\
|
|
1761
|
+
"description": "The Primitive API lets you manage domains, emails, webhook endpoints,\nfilters, and account settings programmatically.\n\n## Authentication\n\nMost endpoints require a Bearer token in the `Authorization` header:\n\n```\nAuthorization: Bearer prim_<your_api_key>\n```\n\nAPI keys are org-scoped. Create and manage them in your dashboard\nunder Settings > API Keys. CLI login and signup endpoints explicitly\ndeclare `security: []`; they do not require an API key because they\nare used to mint one.\n\n## Rate Limiting\n\nThe API enforces a sliding window rate limit of **120 requests per\n60 seconds** per organization. When exceeded, the API returns `429`\nwith a `Retry-After` header indicating how many seconds to wait.\n\n## Pagination\n\nList endpoints use cursor-based pagination. Responses include a\n`meta` object with `total`, `limit`, and `cursor` fields. Pass the\n`cursor` value as a query parameter to fetch the next page. When\n`cursor` is `null`, there are no more results.\n\n## Response Format\n\nAll responses use a consistent envelope:\n\n```json\n{\n \"success\": true,\n \"data\": { ... },\n \"meta\": { \"total\": 42, \"limit\": 50, \"cursor\": \"...\" }\n}\n```\n\nErrors follow the same pattern:\n\n```json\n{\n \"success\": false,\n \"error\": { \"code\": \"not_found\", \"message\": \"Email not found\" }\n}\n```\n\n## Webhook signing\n\nOutbound webhook deliveries (configured via the `endpoints` API)\nare signed so receivers can verify they came from Primitive and\nhave not been tampered with in transit. The signing scheme is\ndeliberately simple so it can be reimplemented in any language\nin a few lines. The Node SDK's `verifyWebhookSignature` helper\nis the reference implementation; the wire details below let you\nwrite a verifier in Python, Go, Ruby, etc. without reading our\nsource.\n\n**Header**: `Primitive-Signature: t=<unix-seconds>,v1=<hex>`\n\nA legacy `MyMX-Signature` header is also sent on every delivery\nwith the same value, retained for back-compatibility with\nintegrations written before the rename. New code should read\n`Primitive-Signature`.\n\n**Signed string**: `${timestamp}.${rawBody}` where `timestamp`\nis the Unix-seconds integer from the `t=` parameter and\n`rawBody` is the exact bytes of the HTTP request body BEFORE\nany JSON decoding. Verify against the raw body, not a\nre-serialized parse, or you will silently mismatch on\ninsignificant whitespace.\n\n**Signature**: HMAC-SHA256 of the signed string, hex-encoded\n(lowercase). Use the account's webhook secret as the HMAC key,\nas a UTF-8 byte sequence.\n\n**Secret**: returned by `GET /account/webhook-secret`. The\nstring looks base64-shaped (e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`)\nbut is NOT base64; use it AS-IS as a UTF-8 string for the HMAC\nkey. Base64-decoding before HMAC will silently produce\nmismatched signatures.\n\n**Tolerance**: by convention, reject deliveries whose `t=`\ntimestamp is more than 5 minutes off your wall-clock to defend\nagainst replay attacks. The Node SDK's helper enforces this by\ndefault.\n\n**Verification recipe** (any language):\n\n```\n1. Read the raw HTTP body (do not parse).\n2. Read `Primitive-Signature: t=<ts>,v1=<sig>`.\n3. Reject if abs(now - ts) > 300 seconds.\n4. expected = HMAC_SHA256_hex(secret_utf8, f\"{ts}.{rawBody}\")\n5. Constant-time compare expected to sig. Reject if not equal.\n```\n\nFor Node, use `verifyWebhookSignature` from\n`@primitivedotdev/sdk/webhook` (or the higher-level\n`handleWebhook` helper if you want a one-liner). For other\nlanguages, the recipe above is everything you need.\n\nTest deliveries: `POST /endpoints/{id}/test` triggers a fake\ndelivery to your endpoint URL, signed with your real account\nsecret, so you can confirm verification end-to-end without\nneeding real inbound mail. The test response carries the exact\n`signature` header value sent on the wire so you can compare\nstrings directly.\n",
|
|
1709
1762
|
"contact": {
|
|
1710
1763
|
"name": "Primitive",
|
|
1711
1764
|
"url": "https://primitive.dev"
|
|
@@ -1877,6 +1930,97 @@ const openapiDocument = {
|
|
|
1877
1930
|
}
|
|
1878
1931
|
}
|
|
1879
1932
|
} },
|
|
1933
|
+
"/cli/signup/start": { "post": {
|
|
1934
|
+
"operationId": "startCliSignup",
|
|
1935
|
+
"summary": "Start CLI account signup",
|
|
1936
|
+
"description": "Starts a terminal-native CLI signup. The API validates the signup code,\ncreates a pending signup session, sends an email verification code, and\nreturns an opaque signup token used by the resend and verify steps. This\nendpoint does not require an API key.\n",
|
|
1937
|
+
"tags": ["CLI"],
|
|
1938
|
+
"security": [],
|
|
1939
|
+
"requestBody": {
|
|
1940
|
+
"required": true,
|
|
1941
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/StartCliSignupInput" } } }
|
|
1942
|
+
},
|
|
1943
|
+
"responses": {
|
|
1944
|
+
"201": {
|
|
1945
|
+
"description": "CLI signup session created and verification email sent",
|
|
1946
|
+
"headers": { "Cache-Control": {
|
|
1947
|
+
"schema": { "type": "string" },
|
|
1948
|
+
"description": "Always `no-store`"
|
|
1949
|
+
} },
|
|
1950
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
1951
|
+
"type": "object",
|
|
1952
|
+
"properties": { "data": { "$ref": "#/components/schemas/CliSignupStartResult" } }
|
|
1953
|
+
}] } } }
|
|
1954
|
+
},
|
|
1955
|
+
"400": { "$ref": "#/components/responses/ValidationError" },
|
|
1956
|
+
"429": { "$ref": "#/components/responses/RateLimited" }
|
|
1957
|
+
}
|
|
1958
|
+
} },
|
|
1959
|
+
"/cli/signup/resend": { "post": {
|
|
1960
|
+
"operationId": "resendCliSignupVerification",
|
|
1961
|
+
"summary": "Resend CLI signup verification code",
|
|
1962
|
+
"description": "Sends a new email verification code for a pending CLI signup session.\nThis endpoint does not require an API key.\n",
|
|
1963
|
+
"tags": ["CLI"],
|
|
1964
|
+
"security": [],
|
|
1965
|
+
"requestBody": {
|
|
1966
|
+
"required": true,
|
|
1967
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResendCliSignupVerificationInput" } } }
|
|
1968
|
+
},
|
|
1969
|
+
"responses": {
|
|
1970
|
+
"200": {
|
|
1971
|
+
"description": "Verification email resent",
|
|
1972
|
+
"headers": { "Cache-Control": {
|
|
1973
|
+
"schema": { "type": "string" },
|
|
1974
|
+
"description": "Always `no-store`"
|
|
1975
|
+
} },
|
|
1976
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
1977
|
+
"type": "object",
|
|
1978
|
+
"properties": { "data": { "$ref": "#/components/schemas/CliSignupResendResult" } }
|
|
1979
|
+
}] } } }
|
|
1980
|
+
},
|
|
1981
|
+
"400": {
|
|
1982
|
+
"description": "Invalid token or expired token",
|
|
1983
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
1984
|
+
},
|
|
1985
|
+
"429": {
|
|
1986
|
+
"description": "Global rate limit exceeded or resend requested too quickly",
|
|
1987
|
+
"headers": { "Retry-After": {
|
|
1988
|
+
"schema": { "type": "integer" },
|
|
1989
|
+
"description": "Seconds to wait before retrying"
|
|
1990
|
+
} },
|
|
1991
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
} },
|
|
1995
|
+
"/cli/signup/verify": { "post": {
|
|
1996
|
+
"operationId": "verifyCliSignup",
|
|
1997
|
+
"summary": "Verify CLI signup and create API key",
|
|
1998
|
+
"description": "Verifies the email code for a CLI signup session, creates the account,\nredeems the reserved signup code, mints an org-scoped CLI API key, and\nreturns the raw key exactly once. This endpoint does not require an API key.\n",
|
|
1999
|
+
"tags": ["CLI"],
|
|
2000
|
+
"security": [],
|
|
2001
|
+
"requestBody": {
|
|
2002
|
+
"required": true,
|
|
2003
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/VerifyCliSignupInput" } } }
|
|
2004
|
+
},
|
|
2005
|
+
"responses": {
|
|
2006
|
+
"200": {
|
|
2007
|
+
"description": "CLI signup verified and API key created",
|
|
2008
|
+
"headers": { "Cache-Control": {
|
|
2009
|
+
"schema": { "type": "string" },
|
|
2010
|
+
"description": "Always `no-store`"
|
|
2011
|
+
} },
|
|
2012
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
2013
|
+
"type": "object",
|
|
2014
|
+
"properties": { "data": { "$ref": "#/components/schemas/CliSignupVerifyResult" } }
|
|
2015
|
+
}] } } }
|
|
2016
|
+
},
|
|
2017
|
+
"400": {
|
|
2018
|
+
"description": "Invalid request, invalid verification code, expired token, invalid signup code, rejected password, or account creation failure",
|
|
2019
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
2020
|
+
},
|
|
2021
|
+
"429": { "$ref": "#/components/responses/RateLimited" }
|
|
2022
|
+
}
|
|
2023
|
+
} },
|
|
1880
2024
|
"/cli/logout": { "post": {
|
|
1881
2025
|
"operationId": "cliLogout",
|
|
1882
2026
|
"summary": "Revoke the current CLI API key",
|
|
@@ -3017,7 +3161,7 @@ const openapiDocument = {
|
|
|
3017
3161
|
"post": {
|
|
3018
3162
|
"operationId": "createFunction",
|
|
3019
3163
|
"summary": "Deploy a function",
|
|
3020
|
-
"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). The gateway\nHMAC-verifies the POST against the org's webhook secret before\ninvoking the handler; the request body parses to an\n`email.received` event (see `EmailReceivedEvent` and the\nWebhook payload section for the full schema). Code is bundled\nbefore being uploaded; ship a single self-contained file rather\nthan 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
|
|
3164
|
+
"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). The gateway\nHMAC-verifies the POST against the org's webhook secret before\ninvoking the handler; the request body parses to an\n`email.received` event (see `EmailReceivedEvent` and the\nWebhook payload section for the full schema). Code is bundled\nbefore being uploaded; ship a single self-contained file rather\nthan 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 gateway URL returned here is for\nreference only and is not directly callable from outside.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`) already\nbound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
|
|
3021
3165
|
"tags": ["Functions"],
|
|
3022
3166
|
"requestBody": {
|
|
3023
3167
|
"required": true,
|
|
@@ -3031,13 +3175,18 @@ const openapiDocument = {
|
|
|
3031
3175
|
"properties": { "data": { "$ref": "#/components/schemas/CreateFunctionResult" } }
|
|
3032
3176
|
}] } } }
|
|
3033
3177
|
},
|
|
3034
|
-
"400": {
|
|
3178
|
+
"400": {
|
|
3179
|
+
"description": "Invalid request parameters or customer-correctable deploy rejection",
|
|
3180
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
3181
|
+
},
|
|
3035
3182
|
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3036
3183
|
"409": {
|
|
3037
3184
|
"description": "A function with this name already exists in the org",
|
|
3038
3185
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
3039
3186
|
},
|
|
3040
|
-
"
|
|
3187
|
+
"424": { "$ref": "#/components/responses/DeployFailed" },
|
|
3188
|
+
"429": { "$ref": "#/components/responses/DeployFailed" },
|
|
3189
|
+
"503": { "$ref": "#/components/responses/DeployFailed" }
|
|
3041
3190
|
}
|
|
3042
3191
|
}
|
|
3043
3192
|
},
|
|
@@ -3063,7 +3212,7 @@ const openapiDocument = {
|
|
|
3063
3212
|
"put": {
|
|
3064
3213
|
"operationId": "updateFunction",
|
|
3065
3214
|
"summary": "Update and redeploy a function",
|
|
3066
|
-
"description": "Replaces the function's source code with the body's `code` and\ntriggers a redeploy. Same size limits as `POST /functions`.\nUse this verb to push secret writes into the running handler:\npassing the same `code` re-runs the deploy and refreshes the\nbinding set with the latest values from the secrets table.\n\nOn
|
|
3215
|
+
"description": "Replaces the function's source code with the body's `code` and\ntriggers a redeploy. Same size limits as `POST /functions`.\nUse this verb to push secret writes into the running handler:\npassing the same `code` re-runs the deploy and refreshes the\nbinding set with the latest values from the secrets table.\n\nOn deploy failure, the previously-deployed code stays live; the\nruntime never serves a half-built bundle. The response uses\n`error.code` `deploy_failed`, and the function's `deploy_error`\nfield carries the latest deploy error for dashboard/API reads.\n",
|
|
3067
3216
|
"tags": ["Functions"],
|
|
3068
3217
|
"requestBody": {
|
|
3069
3218
|
"required": true,
|
|
@@ -3077,10 +3226,15 @@ const openapiDocument = {
|
|
|
3077
3226
|
"properties": { "data": { "$ref": "#/components/schemas/FunctionDetail" } }
|
|
3078
3227
|
}] } } }
|
|
3079
3228
|
},
|
|
3080
|
-
"400": {
|
|
3229
|
+
"400": {
|
|
3230
|
+
"description": "Invalid request parameters or customer-correctable deploy rejection",
|
|
3231
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
3232
|
+
},
|
|
3081
3233
|
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3082
3234
|
"404": { "$ref": "#/components/responses/NotFound" },
|
|
3083
|
-
"
|
|
3235
|
+
"424": { "$ref": "#/components/responses/DeployFailed" },
|
|
3236
|
+
"429": { "$ref": "#/components/responses/DeployFailed" },
|
|
3237
|
+
"503": { "$ref": "#/components/responses/DeployFailed" }
|
|
3084
3238
|
}
|
|
3085
3239
|
},
|
|
3086
3240
|
"delete": {
|
|
@@ -3399,6 +3553,19 @@ const openapiDocument = {
|
|
|
3399
3553
|
}
|
|
3400
3554
|
} }
|
|
3401
3555
|
},
|
|
3556
|
+
"DeployFailed": {
|
|
3557
|
+
"description": "Function deploy could not be completed; previously deployed code remains live",
|
|
3558
|
+
"content": { "application/json": {
|
|
3559
|
+
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
3560
|
+
"example": {
|
|
3561
|
+
"success": false,
|
|
3562
|
+
"error": {
|
|
3563
|
+
"code": "deploy_failed",
|
|
3564
|
+
"message": "Function deploy failed"
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
} }
|
|
3568
|
+
},
|
|
3402
3569
|
"RateLimited": {
|
|
3403
3570
|
"description": "Rate limit exceeded",
|
|
3404
3571
|
"headers": { "Retry-After": {
|
|
@@ -3758,6 +3925,162 @@ const openapiDocument = {
|
|
|
3758
3925
|
"org_name"
|
|
3759
3926
|
]
|
|
3760
3927
|
},
|
|
3928
|
+
"StartCliSignupInput": {
|
|
3929
|
+
"type": "object",
|
|
3930
|
+
"additionalProperties": false,
|
|
3931
|
+
"properties": {
|
|
3932
|
+
"email": {
|
|
3933
|
+
"type": "string",
|
|
3934
|
+
"format": "email",
|
|
3935
|
+
"maxLength": 254
|
|
3936
|
+
},
|
|
3937
|
+
"signup_code": {
|
|
3938
|
+
"type": "string",
|
|
3939
|
+
"minLength": 1,
|
|
3940
|
+
"maxLength": 128
|
|
3941
|
+
},
|
|
3942
|
+
"terms_accepted": {
|
|
3943
|
+
"type": "boolean",
|
|
3944
|
+
"const": true,
|
|
3945
|
+
"description": "Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy"
|
|
3946
|
+
},
|
|
3947
|
+
"device_name": {
|
|
3948
|
+
"type": "string",
|
|
3949
|
+
"minLength": 1,
|
|
3950
|
+
"maxLength": 80,
|
|
3951
|
+
"description": "Human-readable device name used for the created CLI API key"
|
|
3952
|
+
},
|
|
3953
|
+
"metadata": {
|
|
3954
|
+
"type": "object",
|
|
3955
|
+
"additionalProperties": true,
|
|
3956
|
+
"description": "Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer"
|
|
3957
|
+
}
|
|
3958
|
+
},
|
|
3959
|
+
"required": [
|
|
3960
|
+
"email",
|
|
3961
|
+
"signup_code",
|
|
3962
|
+
"terms_accepted"
|
|
3963
|
+
]
|
|
3964
|
+
},
|
|
3965
|
+
"CliSignupStartResult": {
|
|
3966
|
+
"type": "object",
|
|
3967
|
+
"properties": {
|
|
3968
|
+
"signup_token": {
|
|
3969
|
+
"type": "string",
|
|
3970
|
+
"description": "Opaque token used to verify or resend the pending CLI signup"
|
|
3971
|
+
},
|
|
3972
|
+
"email": {
|
|
3973
|
+
"type": "string",
|
|
3974
|
+
"format": "email"
|
|
3975
|
+
},
|
|
3976
|
+
"expires_in": {
|
|
3977
|
+
"type": "integer",
|
|
3978
|
+
"description": "Seconds until the pending signup expires"
|
|
3979
|
+
},
|
|
3980
|
+
"resend_after": {
|
|
3981
|
+
"type": "integer",
|
|
3982
|
+
"description": "Minimum seconds before requesting another verification email"
|
|
3983
|
+
},
|
|
3984
|
+
"verification_code_length": {
|
|
3985
|
+
"type": "integer",
|
|
3986
|
+
"description": "Number of digits in the emailed verification code"
|
|
3987
|
+
}
|
|
3988
|
+
},
|
|
3989
|
+
"required": [
|
|
3990
|
+
"signup_token",
|
|
3991
|
+
"email",
|
|
3992
|
+
"expires_in",
|
|
3993
|
+
"resend_after",
|
|
3994
|
+
"verification_code_length"
|
|
3995
|
+
]
|
|
3996
|
+
},
|
|
3997
|
+
"ResendCliSignupVerificationInput": {
|
|
3998
|
+
"type": "object",
|
|
3999
|
+
"additionalProperties": false,
|
|
4000
|
+
"properties": { "signup_token": {
|
|
4001
|
+
"type": "string",
|
|
4002
|
+
"minLength": 1
|
|
4003
|
+
} },
|
|
4004
|
+
"required": ["signup_token"]
|
|
4005
|
+
},
|
|
4006
|
+
"CliSignupResendResult": {
|
|
4007
|
+
"type": "object",
|
|
4008
|
+
"properties": {
|
|
4009
|
+
"email": {
|
|
4010
|
+
"type": "string",
|
|
4011
|
+
"format": "email"
|
|
4012
|
+
},
|
|
4013
|
+
"expires_in": {
|
|
4014
|
+
"type": "integer",
|
|
4015
|
+
"description": "Seconds until the pending signup expires"
|
|
4016
|
+
},
|
|
4017
|
+
"resend_after": {
|
|
4018
|
+
"type": "integer",
|
|
4019
|
+
"description": "Minimum seconds before requesting another verification email"
|
|
4020
|
+
},
|
|
4021
|
+
"verification_code_length": {
|
|
4022
|
+
"type": "integer",
|
|
4023
|
+
"description": "Number of digits in the emailed verification code"
|
|
4024
|
+
}
|
|
4025
|
+
},
|
|
4026
|
+
"required": [
|
|
4027
|
+
"email",
|
|
4028
|
+
"expires_in",
|
|
4029
|
+
"resend_after",
|
|
4030
|
+
"verification_code_length"
|
|
4031
|
+
]
|
|
4032
|
+
},
|
|
4033
|
+
"VerifyCliSignupInput": {
|
|
4034
|
+
"type": "object",
|
|
4035
|
+
"additionalProperties": false,
|
|
4036
|
+
"properties": {
|
|
4037
|
+
"signup_token": {
|
|
4038
|
+
"type": "string",
|
|
4039
|
+
"minLength": 1
|
|
4040
|
+
},
|
|
4041
|
+
"verification_code": {
|
|
4042
|
+
"type": "string",
|
|
4043
|
+
"minLength": 1,
|
|
4044
|
+
"maxLength": 32
|
|
4045
|
+
},
|
|
4046
|
+
"password": {
|
|
4047
|
+
"type": "string",
|
|
4048
|
+
"minLength": 1,
|
|
4049
|
+
"maxLength": 1024
|
|
4050
|
+
}
|
|
4051
|
+
},
|
|
4052
|
+
"required": [
|
|
4053
|
+
"signup_token",
|
|
4054
|
+
"verification_code",
|
|
4055
|
+
"password"
|
|
4056
|
+
]
|
|
4057
|
+
},
|
|
4058
|
+
"CliSignupVerifyResult": {
|
|
4059
|
+
"type": "object",
|
|
4060
|
+
"properties": {
|
|
4061
|
+
"api_key": {
|
|
4062
|
+
"type": "string",
|
|
4063
|
+
"description": "Newly-created API key for CLI authentication"
|
|
4064
|
+
},
|
|
4065
|
+
"key_id": {
|
|
4066
|
+
"type": "string",
|
|
4067
|
+
"format": "uuid"
|
|
4068
|
+
},
|
|
4069
|
+
"key_prefix": { "type": "string" },
|
|
4070
|
+
"org_id": {
|
|
4071
|
+
"type": "string",
|
|
4072
|
+
"format": "uuid"
|
|
4073
|
+
},
|
|
4074
|
+
"org_name": { "type": ["string", "null"] }
|
|
4075
|
+
},
|
|
4076
|
+
"required": [
|
|
4077
|
+
"api_key",
|
|
4078
|
+
"key_id",
|
|
4079
|
+
"key_prefix",
|
|
4080
|
+
"org_id",
|
|
4081
|
+
"org_name"
|
|
4082
|
+
]
|
|
4083
|
+
},
|
|
3761
4084
|
"CliLogoutInput": {
|
|
3762
4085
|
"type": "object",
|
|
3763
4086
|
"additionalProperties": false,
|
|
@@ -5224,7 +5547,7 @@ const openapiDocument = {
|
|
|
5224
5547
|
"type": "string",
|
|
5225
5548
|
"minLength": 1,
|
|
5226
5549
|
"maxLength": 5242880,
|
|
5227
|
-
"description": "Optional source map for the bundle. Up to 5 MiB UTF-8.\nStored
|
|
5550
|
+
"description": "Optional source map for the bundle. Up to 5 MiB UTF-8.\nStored with the deployment attempt and sent to the runtime\nto symbolicate stack traces in the function's logs.\n"
|
|
5228
5551
|
}
|
|
5229
5552
|
},
|
|
5230
5553
|
"required": ["name", "code"]
|
|
@@ -5756,6 +6079,58 @@ const operationManifest = [
|
|
|
5756
6079
|
"tag": "CLI",
|
|
5757
6080
|
"tagCommand": "cli"
|
|
5758
6081
|
},
|
|
6082
|
+
{
|
|
6083
|
+
"binaryResponse": false,
|
|
6084
|
+
"bodyRequired": true,
|
|
6085
|
+
"command": "resend-cli-signup-verification",
|
|
6086
|
+
"description": "Sends a new email verification code for a pending CLI signup session.\nThis endpoint does not require an API key.\n",
|
|
6087
|
+
"hasJsonBody": true,
|
|
6088
|
+
"method": "POST",
|
|
6089
|
+
"operationId": "resendCliSignupVerification",
|
|
6090
|
+
"path": "/cli/signup/resend",
|
|
6091
|
+
"pathParams": [],
|
|
6092
|
+
"queryParams": [],
|
|
6093
|
+
"requestSchema": {
|
|
6094
|
+
"type": "object",
|
|
6095
|
+
"additionalProperties": false,
|
|
6096
|
+
"properties": { "signup_token": {
|
|
6097
|
+
"type": "string",
|
|
6098
|
+
"minLength": 1
|
|
6099
|
+
} },
|
|
6100
|
+
"required": ["signup_token"]
|
|
6101
|
+
},
|
|
6102
|
+
"responseSchema": {
|
|
6103
|
+
"type": "object",
|
|
6104
|
+
"properties": {
|
|
6105
|
+
"email": {
|
|
6106
|
+
"type": "string",
|
|
6107
|
+
"format": "email"
|
|
6108
|
+
},
|
|
6109
|
+
"expires_in": {
|
|
6110
|
+
"type": "integer",
|
|
6111
|
+
"description": "Seconds until the pending signup expires"
|
|
6112
|
+
},
|
|
6113
|
+
"resend_after": {
|
|
6114
|
+
"type": "integer",
|
|
6115
|
+
"description": "Minimum seconds before requesting another verification email"
|
|
6116
|
+
},
|
|
6117
|
+
"verification_code_length": {
|
|
6118
|
+
"type": "integer",
|
|
6119
|
+
"description": "Number of digits in the emailed verification code"
|
|
6120
|
+
}
|
|
6121
|
+
},
|
|
6122
|
+
"required": [
|
|
6123
|
+
"email",
|
|
6124
|
+
"expires_in",
|
|
6125
|
+
"resend_after",
|
|
6126
|
+
"verification_code_length"
|
|
6127
|
+
]
|
|
6128
|
+
},
|
|
6129
|
+
"sdkName": "resendCliSignupVerification",
|
|
6130
|
+
"summary": "Resend CLI signup verification code",
|
|
6131
|
+
"tag": "CLI",
|
|
6132
|
+
"tagCommand": "cli"
|
|
6133
|
+
},
|
|
5759
6134
|
{
|
|
5760
6135
|
"binaryResponse": false,
|
|
5761
6136
|
"bodyRequired": false,
|
|
@@ -5822,8 +6197,160 @@ const operationManifest = [
|
|
|
5822
6197
|
"interval"
|
|
5823
6198
|
]
|
|
5824
6199
|
},
|
|
5825
|
-
"sdkName": "startCliLogin",
|
|
5826
|
-
"summary": "Start CLI browser login",
|
|
6200
|
+
"sdkName": "startCliLogin",
|
|
6201
|
+
"summary": "Start CLI browser login",
|
|
6202
|
+
"tag": "CLI",
|
|
6203
|
+
"tagCommand": "cli"
|
|
6204
|
+
},
|
|
6205
|
+
{
|
|
6206
|
+
"binaryResponse": false,
|
|
6207
|
+
"bodyRequired": true,
|
|
6208
|
+
"command": "start-cli-signup",
|
|
6209
|
+
"description": "Starts a terminal-native CLI signup. The API validates the signup code,\ncreates a pending signup session, sends an email verification code, and\nreturns an opaque signup token used by the resend and verify steps. This\nendpoint does not require an API key.\n",
|
|
6210
|
+
"hasJsonBody": true,
|
|
6211
|
+
"method": "POST",
|
|
6212
|
+
"operationId": "startCliSignup",
|
|
6213
|
+
"path": "/cli/signup/start",
|
|
6214
|
+
"pathParams": [],
|
|
6215
|
+
"queryParams": [],
|
|
6216
|
+
"requestSchema": {
|
|
6217
|
+
"type": "object",
|
|
6218
|
+
"additionalProperties": false,
|
|
6219
|
+
"properties": {
|
|
6220
|
+
"email": {
|
|
6221
|
+
"type": "string",
|
|
6222
|
+
"format": "email",
|
|
6223
|
+
"maxLength": 254
|
|
6224
|
+
},
|
|
6225
|
+
"signup_code": {
|
|
6226
|
+
"type": "string",
|
|
6227
|
+
"minLength": 1,
|
|
6228
|
+
"maxLength": 128
|
|
6229
|
+
},
|
|
6230
|
+
"terms_accepted": {
|
|
6231
|
+
"type": "boolean",
|
|
6232
|
+
"const": true,
|
|
6233
|
+
"description": "Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy"
|
|
6234
|
+
},
|
|
6235
|
+
"device_name": {
|
|
6236
|
+
"type": "string",
|
|
6237
|
+
"minLength": 1,
|
|
6238
|
+
"maxLength": 80,
|
|
6239
|
+
"description": "Human-readable device name used for the created CLI API key"
|
|
6240
|
+
},
|
|
6241
|
+
"metadata": {
|
|
6242
|
+
"type": "object",
|
|
6243
|
+
"additionalProperties": true,
|
|
6244
|
+
"description": "Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer"
|
|
6245
|
+
}
|
|
6246
|
+
},
|
|
6247
|
+
"required": [
|
|
6248
|
+
"email",
|
|
6249
|
+
"signup_code",
|
|
6250
|
+
"terms_accepted"
|
|
6251
|
+
]
|
|
6252
|
+
},
|
|
6253
|
+
"responseSchema": {
|
|
6254
|
+
"type": "object",
|
|
6255
|
+
"properties": {
|
|
6256
|
+
"signup_token": {
|
|
6257
|
+
"type": "string",
|
|
6258
|
+
"description": "Opaque token used to verify or resend the pending CLI signup"
|
|
6259
|
+
},
|
|
6260
|
+
"email": {
|
|
6261
|
+
"type": "string",
|
|
6262
|
+
"format": "email"
|
|
6263
|
+
},
|
|
6264
|
+
"expires_in": {
|
|
6265
|
+
"type": "integer",
|
|
6266
|
+
"description": "Seconds until the pending signup expires"
|
|
6267
|
+
},
|
|
6268
|
+
"resend_after": {
|
|
6269
|
+
"type": "integer",
|
|
6270
|
+
"description": "Minimum seconds before requesting another verification email"
|
|
6271
|
+
},
|
|
6272
|
+
"verification_code_length": {
|
|
6273
|
+
"type": "integer",
|
|
6274
|
+
"description": "Number of digits in the emailed verification code"
|
|
6275
|
+
}
|
|
6276
|
+
},
|
|
6277
|
+
"required": [
|
|
6278
|
+
"signup_token",
|
|
6279
|
+
"email",
|
|
6280
|
+
"expires_in",
|
|
6281
|
+
"resend_after",
|
|
6282
|
+
"verification_code_length"
|
|
6283
|
+
]
|
|
6284
|
+
},
|
|
6285
|
+
"sdkName": "startCliSignup",
|
|
6286
|
+
"summary": "Start CLI account signup",
|
|
6287
|
+
"tag": "CLI",
|
|
6288
|
+
"tagCommand": "cli"
|
|
6289
|
+
},
|
|
6290
|
+
{
|
|
6291
|
+
"binaryResponse": false,
|
|
6292
|
+
"bodyRequired": true,
|
|
6293
|
+
"command": "verify-cli-signup",
|
|
6294
|
+
"description": "Verifies the email code for a CLI signup session, creates the account,\nredeems the reserved signup code, mints an org-scoped CLI API key, and\nreturns the raw key exactly once. This endpoint does not require an API key.\n",
|
|
6295
|
+
"hasJsonBody": true,
|
|
6296
|
+
"method": "POST",
|
|
6297
|
+
"operationId": "verifyCliSignup",
|
|
6298
|
+
"path": "/cli/signup/verify",
|
|
6299
|
+
"pathParams": [],
|
|
6300
|
+
"queryParams": [],
|
|
6301
|
+
"requestSchema": {
|
|
6302
|
+
"type": "object",
|
|
6303
|
+
"additionalProperties": false,
|
|
6304
|
+
"properties": {
|
|
6305
|
+
"signup_token": {
|
|
6306
|
+
"type": "string",
|
|
6307
|
+
"minLength": 1
|
|
6308
|
+
},
|
|
6309
|
+
"verification_code": {
|
|
6310
|
+
"type": "string",
|
|
6311
|
+
"minLength": 1,
|
|
6312
|
+
"maxLength": 32
|
|
6313
|
+
},
|
|
6314
|
+
"password": {
|
|
6315
|
+
"type": "string",
|
|
6316
|
+
"minLength": 1,
|
|
6317
|
+
"maxLength": 1024
|
|
6318
|
+
}
|
|
6319
|
+
},
|
|
6320
|
+
"required": [
|
|
6321
|
+
"signup_token",
|
|
6322
|
+
"verification_code",
|
|
6323
|
+
"password"
|
|
6324
|
+
]
|
|
6325
|
+
},
|
|
6326
|
+
"responseSchema": {
|
|
6327
|
+
"type": "object",
|
|
6328
|
+
"properties": {
|
|
6329
|
+
"api_key": {
|
|
6330
|
+
"type": "string",
|
|
6331
|
+
"description": "Newly-created API key for CLI authentication"
|
|
6332
|
+
},
|
|
6333
|
+
"key_id": {
|
|
6334
|
+
"type": "string",
|
|
6335
|
+
"format": "uuid"
|
|
6336
|
+
},
|
|
6337
|
+
"key_prefix": { "type": "string" },
|
|
6338
|
+
"org_id": {
|
|
6339
|
+
"type": "string",
|
|
6340
|
+
"format": "uuid"
|
|
6341
|
+
},
|
|
6342
|
+
"org_name": { "type": ["string", "null"] }
|
|
6343
|
+
},
|
|
6344
|
+
"required": [
|
|
6345
|
+
"api_key",
|
|
6346
|
+
"key_id",
|
|
6347
|
+
"key_prefix",
|
|
6348
|
+
"org_id",
|
|
6349
|
+
"org_name"
|
|
6350
|
+
]
|
|
6351
|
+
},
|
|
6352
|
+
"sdkName": "verifyCliSignup",
|
|
6353
|
+
"summary": "Verify CLI signup and create API key",
|
|
5827
6354
|
"tag": "CLI",
|
|
5828
6355
|
"tagCommand": "cli"
|
|
5829
6356
|
},
|
|
@@ -7497,7 +8024,7 @@ const operationManifest = [
|
|
|
7497
8024
|
"binaryResponse": false,
|
|
7498
8025
|
"bodyRequired": true,
|
|
7499
8026
|
"command": "create-function",
|
|
7500
|
-
"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). The gateway\nHMAC-verifies the POST against the org's webhook secret before\ninvoking the handler; the request body parses to an\n`email.received` event (see `EmailReceivedEvent` and the\nWebhook payload section for the full schema). Code is bundled\nbefore being uploaded; ship a single self-contained file rather\nthan 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
|
|
8027
|
+
"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). The gateway\nHMAC-verifies the POST against the org's webhook secret before\ninvoking the handler; the request body parses to an\n`email.received` event (see `EmailReceivedEvent` and the\nWebhook payload section for the full schema). Code is bundled\nbefore being uploaded; ship a single self-contained file rather\nthan 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 gateway URL returned here is for\nreference only and is not directly callable from outside.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`) already\nbound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
|
|
7501
8028
|
"hasJsonBody": true,
|
|
7502
8029
|
"method": "POST",
|
|
7503
8030
|
"operationId": "createFunction",
|
|
@@ -7523,7 +8050,7 @@ const operationManifest = [
|
|
|
7523
8050
|
"type": "string",
|
|
7524
8051
|
"minLength": 1,
|
|
7525
8052
|
"maxLength": 5242880,
|
|
7526
|
-
"description": "Optional source map for the bundle. Up to 5 MiB UTF-8.\nStored
|
|
8053
|
+
"description": "Optional source map for the bundle. Up to 5 MiB UTF-8.\nStored with the deployment attempt and sent to the runtime\nto symbolicate stack traces in the function's logs.\n"
|
|
7527
8054
|
}
|
|
7528
8055
|
},
|
|
7529
8056
|
"required": ["name", "code"]
|
|
@@ -8127,7 +8654,7 @@ const operationManifest = [
|
|
|
8127
8654
|
"binaryResponse": false,
|
|
8128
8655
|
"bodyRequired": true,
|
|
8129
8656
|
"command": "update-function",
|
|
8130
|
-
"description": "Replaces the function's source code with the body's `code` and\ntriggers a redeploy. Same size limits as `POST /functions`.\nUse this verb to push secret writes into the running handler:\npassing the same `code` re-runs the deploy and refreshes the\nbinding set with the latest values from the secrets table.\n\nOn
|
|
8657
|
+
"description": "Replaces the function's source code with the body's `code` and\ntriggers a redeploy. Same size limits as `POST /functions`.\nUse this verb to push secret writes into the running handler:\npassing the same `code` re-runs the deploy and refreshes the\nbinding set with the latest values from the secrets table.\n\nOn deploy failure, the previously-deployed code stays live; the\nruntime never serves a half-built bundle. The response uses\n`error.code` `deploy_failed`, and the function's `deploy_error`\nfield carries the latest deploy error for dashboard/API reads.\n",
|
|
8131
8658
|
"hasJsonBody": true,
|
|
8132
8659
|
"method": "PUT",
|
|
8133
8660
|
"operationId": "updateFunction",
|
|
@@ -9386,7 +9913,7 @@ const CREDENTIALS_FILE = "credentials.json";
|
|
|
9386
9913
|
const CREDENTIALS_LOCK_DIR = "credentials.lock";
|
|
9387
9914
|
const CREDENTIALS_LOCK_STALE_MS = 1800 * 1e3;
|
|
9388
9915
|
const MALFORMED_CREDENTIALS_HINT = "Run `primitive logout` and then `primitive login`.";
|
|
9389
|
-
function isRecord(value) {
|
|
9916
|
+
function isRecord$1(value) {
|
|
9390
9917
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9391
9918
|
}
|
|
9392
9919
|
function requireString(value, key) {
|
|
@@ -9409,7 +9936,7 @@ var StaleCredentialFormatError = class extends Error {
|
|
|
9409
9936
|
}
|
|
9410
9937
|
};
|
|
9411
9938
|
function parseCredentials(raw) {
|
|
9412
|
-
if (!isRecord(raw)) throw new Error(`Stored Primitive CLI credentials are malformed: expected a JSON object. ${MALFORMED_CREDENTIALS_HINT}`);
|
|
9939
|
+
if (!isRecord$1(raw)) throw new Error(`Stored Primitive CLI credentials are malformed: expected a JSON object. ${MALFORMED_CREDENTIALS_HINT}`);
|
|
9413
9940
|
if (typeof raw.api_base_url_1 !== "string" && typeof raw.base_url === "string") throw new StaleCredentialFormatError();
|
|
9414
9941
|
const orgName = raw.org_name;
|
|
9415
9942
|
if (orgName !== null && typeof orgName !== "string") throw new Error(`Stored Primitive CLI credentials are malformed: org_name must be a string or null. ${MALFORMED_CREDENTIALS_HINT}`);
|
|
@@ -9582,7 +10109,7 @@ function formatFunctionEndpointRedirect(match) {
|
|
|
9582
10109
|
return [
|
|
9583
10110
|
"This is a function endpoint. Function endpoints are tested differently. Run:",
|
|
9584
10111
|
"",
|
|
9585
|
-
` primitive functions
|
|
10112
|
+
` primitive functions test --id ${match.functionId}`,
|
|
9586
10113
|
"",
|
|
9587
10114
|
`(pass the function id, not the endpoint id. endpoint_id=${match.endpointId} function_id=${match.functionId})`
|
|
9588
10115
|
].join("\n");
|
|
@@ -9597,6 +10124,32 @@ async function maybeWriteFunctionEndpointRedirect(inputs) {
|
|
|
9597
10124
|
return match;
|
|
9598
10125
|
}
|
|
9599
10126
|
//#endregion
|
|
10127
|
+
//#region src/oclif/idempotent-replay-banner.ts
|
|
10128
|
+
/**
|
|
10129
|
+
* If `data` carries `idempotent_replay: true`, write a multi-line
|
|
10130
|
+
* stderr banner explaining the replay condition and what the caller
|
|
10131
|
+
* can do to bypass it. Otherwise no-op.
|
|
10132
|
+
*/
|
|
10133
|
+
function writeIdempotentReplayBannerIfReplay(data, options) {
|
|
10134
|
+
if (!isReplay(data)) return;
|
|
10135
|
+
const probe = data;
|
|
10136
|
+
const rowId = typeof probe.id === "string" ? probe.id : null;
|
|
10137
|
+
const status = typeof probe.status === "string" ? probe.status : null;
|
|
10138
|
+
const deliveryStatus = typeof probe.delivery_status === "string" ? probe.delivery_status : null;
|
|
10139
|
+
const lines = ["note: idempotent replay. this exact send already happened earlier.", " no new MX traffic was generated by this call. nothing new will arrive in any inbox."];
|
|
10140
|
+
if (rowId) lines.push(` cached row id: ${rowId}`);
|
|
10141
|
+
if (status || deliveryStatus) {
|
|
10142
|
+
const parts = [status ? `status=${status}` : null, deliveryStatus && deliveryStatus !== status ? `delivery_status=${deliveryStatus}` : null].filter((part) => part !== null);
|
|
10143
|
+
if (parts.length > 0) lines.push(` original ${parts.join(", ")}`);
|
|
10144
|
+
}
|
|
10145
|
+
lines.push(" to send a fresh copy: vary any field (subject, body, etc.) or", " pass a unique Idempotency-Key on the underlying API call.");
|
|
10146
|
+
options.write(`${lines.join("\n")}\n`);
|
|
10147
|
+
}
|
|
10148
|
+
function isReplay(data) {
|
|
10149
|
+
if (data === null || typeof data !== "object") return false;
|
|
10150
|
+
return data.idempotent_replay === true;
|
|
10151
|
+
}
|
|
10152
|
+
//#endregion
|
|
9600
10153
|
//#region src/oclif/api-command.ts
|
|
9601
10154
|
const API_ERROR_CODES = {
|
|
9602
10155
|
accessDenied: "access_denied",
|
|
@@ -9727,26 +10280,26 @@ function coerceParameterValue(parameter, value) {
|
|
|
9727
10280
|
if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") return value;
|
|
9728
10281
|
throw new Errors.CLIError(`Unsupported flag value for --${parameter.name}`);
|
|
9729
10282
|
}
|
|
9730
|
-
function cliError$
|
|
10283
|
+
function cliError$5(message) {
|
|
9731
10284
|
return new Errors.CLIError(message, { exit: 1 });
|
|
9732
10285
|
}
|
|
9733
10286
|
function parseJson(source, flagLabel) {
|
|
9734
10287
|
try {
|
|
9735
10288
|
return JSON.parse(source);
|
|
9736
10289
|
} catch (error) {
|
|
9737
|
-
throw cliError$
|
|
10290
|
+
throw cliError$5(`${flagLabel} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
9738
10291
|
}
|
|
9739
10292
|
}
|
|
9740
10293
|
function readJsonBody(flags) {
|
|
9741
10294
|
const bodyFile = flags["body-file"];
|
|
9742
10295
|
const rawBody = flags["raw-body"];
|
|
9743
|
-
if (bodyFile && rawBody) throw cliError$
|
|
10296
|
+
if (bodyFile && rawBody) throw cliError$5("Use either --raw-body or --body-file, not both");
|
|
9744
10297
|
if (typeof bodyFile === "string") {
|
|
9745
10298
|
let contents;
|
|
9746
10299
|
try {
|
|
9747
10300
|
contents = readFileSync(bodyFile, "utf8");
|
|
9748
10301
|
} catch (error) {
|
|
9749
|
-
throw cliError$
|
|
10302
|
+
throw cliError$5(`Could not read --body-file ${bodyFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9750
10303
|
}
|
|
9751
10304
|
return parseJson(contents, `--body-file ${bodyFile}`);
|
|
9752
10305
|
}
|
|
@@ -9756,7 +10309,7 @@ function readTextFileFlag(path, flagLabel) {
|
|
|
9756
10309
|
try {
|
|
9757
10310
|
return readFileSync(path, "utf8");
|
|
9758
10311
|
} catch (error) {
|
|
9759
|
-
throw cliError$
|
|
10312
|
+
throw cliError$5(`Could not read ${flagLabel} ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9760
10313
|
}
|
|
9761
10314
|
}
|
|
9762
10315
|
function extractErrorPayload(raw) {
|
|
@@ -9928,14 +10481,14 @@ function collectValues(parameters, flags) {
|
|
|
9928
10481
|
return values;
|
|
9929
10482
|
}
|
|
9930
10483
|
const OPERATION_HINTS = {
|
|
9931
|
-
createFunction: "Tip: prefer `primitive functions
|
|
9932
|
-
updateFunction: "Tip: prefer `primitive functions
|
|
9933
|
-
createFunctionSecret: "Tip: prefer `primitive functions
|
|
9934
|
-
setFunctionSecret: "Tip: prefer `primitive functions
|
|
10484
|
+
createFunction: "Tip: prefer `primitive functions deploy --name <name> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
10485
|
+
updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
10486
|
+
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.",
|
|
10487
|
+
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."
|
|
9935
10488
|
};
|
|
9936
10489
|
function createOperationCommand(operation) {
|
|
9937
10490
|
const { flags, bodyFieldFlagToProperty } = buildFlags(operation);
|
|
9938
|
-
const baseDescription = operation.description
|
|
10491
|
+
const baseDescription = operation.description !== null && operation.description !== void 0 ? canonicalizeCliReferences(operation.description) : `${operation.method} ${operation.path}`;
|
|
9939
10492
|
const schemaSummary = operation.hasJsonBody ? renderRequestSchemaSummary(operation.requestSchema) : null;
|
|
9940
10493
|
const hint = OPERATION_HINTS[operation.sdkName];
|
|
9941
10494
|
const descriptionWithSchema = schemaSummary ? `${baseDescription}\n\n${schemaSummary}` : baseDescription;
|
|
@@ -10031,6 +10584,9 @@ function createOperationCommand(operation) {
|
|
|
10031
10584
|
const hint = EMPTY_RESULT_HINTS[operation.sdkName];
|
|
10032
10585
|
if (hint) process.stderr.write(`${hint}\n`);
|
|
10033
10586
|
}
|
|
10587
|
+
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
10588
|
+
process.stderr.write(chunk);
|
|
10589
|
+
} });
|
|
10034
10590
|
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
10035
10591
|
});
|
|
10036
10592
|
}
|
|
@@ -10038,12 +10594,15 @@ function createOperationCommand(operation) {
|
|
|
10038
10594
|
return OperationCommand;
|
|
10039
10595
|
}
|
|
10040
10596
|
const EMPTY_RESULT_HINTS = {
|
|
10041
|
-
listDeliveries: "(no results) No webhook deliveries logged yet. If you have an endpoint configured but expected to see test fires here: test deliveries from `primitive endpoints
|
|
10042
|
-
listEndpoints: "(no results) No webhook endpoints configured. Add one with `primitive endpoints
|
|
10043
|
-
listEmails: "(no results) No inbound emails received yet on this account. Send one to a verified domain to populate this list. For a compact view, prefer `primitive emails
|
|
10044
|
-
listDomains: "(no results) No domains on this account. Add one with `primitive domains
|
|
10597
|
+
listDeliveries: "(no results) No webhook deliveries logged yet. If you have an endpoint configured but expected to see test fires here: test deliveries from `primitive endpoints test` are NOT logged in this list, they're synchronous and visible only in the test-endpoint command's response. Real deliveries are logged when an inbound `email.received` event fans out to your endpoints. If you have no endpoints, run `primitive endpoints list` to check.",
|
|
10598
|
+
listEndpoints: "(no results) No webhook endpoints configured. Add one with `primitive endpoints create --url <your-url>`.",
|
|
10599
|
+
listEmails: "(no results) No inbound emails received yet on this account. Send one to a verified domain to populate this list. For a compact view, prefer `primitive emails latest`.",
|
|
10600
|
+
listDomains: "(no results) No domains on this account. Add one with `primitive domains add --domain <yourdomain.example>`.",
|
|
10045
10601
|
listFilters: "(no results) No filter rules configured."
|
|
10046
10602
|
};
|
|
10603
|
+
function canonicalizeCliReferences(description) {
|
|
10604
|
+
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'`");
|
|
10605
|
+
}
|
|
10047
10606
|
//#endregion
|
|
10048
10607
|
//#region src/oclif/commands/doctor.ts
|
|
10049
10608
|
const MIN_NODE_MAJOR = 22;
|
|
@@ -10204,7 +10763,7 @@ async function checkDomains(opts) {
|
|
|
10204
10763
|
if (result.error) return {
|
|
10205
10764
|
status: "warn",
|
|
10206
10765
|
message: "could not list domains",
|
|
10207
|
-
hint: "Run `primitive domains
|
|
10766
|
+
hint: "Run `primitive domains list` for the full error envelope."
|
|
10208
10767
|
};
|
|
10209
10768
|
const rows = result.data?.data ?? [];
|
|
10210
10769
|
const active = rows.filter((row) => row.is_active === true);
|
|
@@ -10216,7 +10775,7 @@ async function checkDomains(opts) {
|
|
|
10216
10775
|
if (active.length === 0) return {
|
|
10217
10776
|
status: "warn",
|
|
10218
10777
|
message: `${rows.length} domain(s), none active`,
|
|
10219
|
-
hint: "Run `primitive domains
|
|
10778
|
+
hint: "Run `primitive domains verify --id <id>` for any domain you intend to send / receive on."
|
|
10220
10779
|
};
|
|
10221
10780
|
return {
|
|
10222
10781
|
status: "ok",
|
|
@@ -10345,11 +10904,11 @@ function formatHeader(idWidth) {
|
|
|
10345
10904
|
return `${"ID".padEnd(idWidth)} ${"RECEIVED (UTC)".padEnd(RECEIVED_DISPLAY_WIDTH)} ${"FROM".padEnd(ADDRESS_DISPLAY_WIDTH)} ${"TO".padEnd(ADDRESS_DISPLAY_WIDTH)} SUBJECT`;
|
|
10346
10905
|
}
|
|
10347
10906
|
var EmailsLatestCommand = class EmailsLatestCommand extends Command {
|
|
10348
|
-
static description = `Print the N most recent inbound emails as a one-line-per-row text table. Designed for quick triage and visual scanning. For programmatic access, use \`primitive emails
|
|
10907
|
+
static description = `Print the N most recent inbound emails as a one-line-per-row text table. Designed for quick triage and visual scanning. For programmatic access, use \`primitive emails list\` (full JSON envelope, cursor pagination, filters) or pass \`--json\` here for the same raw shape without pagination/filters.
|
|
10349
10908
|
|
|
10350
10909
|
ID display is TTY-aware. When STDOUT is a terminal, the table truncates each row's id to the first ${ID_DISPLAY_WIDTH_SHORT} characters for readability. When STDOUT is piped or redirected (the row stream is being consumed by another command), the full UUID is printed so the id can be fed straight back into \`emails:get-email\`, \`emails:delete-email\`, etc. without a separate \`--json\` round-trip.
|
|
10351
10910
|
|
|
10352
|
-
Output streams: the column header line is written to STDERR so the row data on STDOUT stays grep/awk-friendly. \`--json\` writes everything (including the envelope) to STDOUT and is equivalent to running \`emails
|
|
10911
|
+
Output streams: the column header line is written to STDERR so the row data on STDOUT stays grep/awk-friendly. \`--json\` writes everything (including the envelope) to STDOUT and is equivalent to running \`emails list --limit N\` for the same N.`;
|
|
10353
10912
|
static summary = "Show the most recent inbound emails as a compact table";
|
|
10354
10913
|
static examples = [
|
|
10355
10914
|
"<%= config.bin %> emails latest",
|
|
@@ -10529,7 +11088,7 @@ function sleep$1(ms) {
|
|
|
10529
11088
|
//#endregion
|
|
10530
11089
|
//#region src/oclif/commands/emails-wait.ts
|
|
10531
11090
|
const DEFAULT_WAIT_TIMEOUT_SECONDS$1 = 300;
|
|
10532
|
-
function cliError$
|
|
11091
|
+
function cliError$4(message) {
|
|
10533
11092
|
return new Errors.CLIError(message, { exit: 1 });
|
|
10534
11093
|
}
|
|
10535
11094
|
var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
@@ -10609,7 +11168,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
10609
11168
|
try {
|
|
10610
11169
|
since = sinceFromFlags(flags);
|
|
10611
11170
|
} catch (error) {
|
|
10612
|
-
throw cliError$
|
|
11171
|
+
throw cliError$4(error instanceof Error ? error.message : String(error));
|
|
10613
11172
|
}
|
|
10614
11173
|
const filters = filtersFromFlags(flags);
|
|
10615
11174
|
const deadline = flags.timeout === 0 ? null : Date.now() + flags.timeout * 1e3;
|
|
@@ -10660,7 +11219,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
10660
11219
|
};
|
|
10661
11220
|
//#endregion
|
|
10662
11221
|
//#region src/oclif/commands/emails-watch.ts
|
|
10663
|
-
function cliError$
|
|
11222
|
+
function cliError$3(message) {
|
|
10664
11223
|
return new Errors.CLIError(message, { exit: 1 });
|
|
10665
11224
|
}
|
|
10666
11225
|
var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
@@ -10737,7 +11296,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
10737
11296
|
try {
|
|
10738
11297
|
since = sinceFromFlags(flags);
|
|
10739
11298
|
} catch (error) {
|
|
10740
|
-
throw cliError$
|
|
11299
|
+
throw cliError$3(error instanceof Error ? error.message : String(error));
|
|
10741
11300
|
}
|
|
10742
11301
|
const filters = filtersFromFlags(flags);
|
|
10743
11302
|
const deadline = flags.seconds ? Date.now() + flags.seconds * 1e3 : null;
|
|
@@ -10967,14 +11526,14 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
10967
11526
|
final update-function with the same bundle so the running handler
|
|
10968
11527
|
picks up the bindings. If a secret write fails after the create
|
|
10969
11528
|
step the function exists with whatever secrets succeeded and the
|
|
10970
|
-
redeploy has NOT fired; re-run \`primitive functions
|
|
10971
|
-
for the missing keys, then \`primitive functions
|
|
11529
|
+
redeploy has NOT fired; re-run \`primitive functions set-secret\`
|
|
11530
|
+
for the missing keys, then \`primitive functions redeploy\` to
|
|
10972
11531
|
push them live.`;
|
|
10973
11532
|
static summary = "Deploy a new function from a bundled handler file";
|
|
10974
11533
|
static examples = [
|
|
10975
|
-
"<%= config.bin %> functions
|
|
10976
|
-
"<%= config.bin %> functions
|
|
10977
|
-
"<%= config.bin %> functions
|
|
11534
|
+
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js",
|
|
11535
|
+
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
11536
|
+
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com"
|
|
10978
11537
|
];
|
|
10979
11538
|
static flags = {
|
|
10980
11539
|
"api-key": Flags.string({
|
|
@@ -10999,7 +11558,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
10999
11558
|
description: "Path to the bundled ESM handler file (single self-contained module). Loaded as the `code` body field.",
|
|
11000
11559
|
required: true
|
|
11001
11560
|
}),
|
|
11002
|
-
"source-map-file": Flags.string({ description: "Optional path to a source map for the bundle. Stored
|
|
11561
|
+
"source-map-file": Flags.string({ description: "Optional path to a source map for the bundle. Stored with the deployment attempt and used to symbolicate stack traces in function logs." }),
|
|
11003
11562
|
secret: Flags.string({
|
|
11004
11563
|
description: `Secret KEY=VALUE to seed on the deployed function. Repeatable. KEY must match \`^[A-Z_][A-Z0-9_]*$\`; VALUE may contain \`=\` (only the first \`=\` is treated as a delimiter). Each KEY may only appear once per command. Passing one or more --secret flags fans out the deploy to create-function, set-secret per pair, then a final redeploy so the running handler picks up the bindings. ${SECRET_FLAG_SECURITY_NOTE}`,
|
|
11005
11564
|
multiple: true
|
|
@@ -11074,10 +11633,10 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
11074
11633
|
const succeeded = outcome.succeededKeys.length > 0 ? outcome.succeededKeys.join(", ") : "(none)";
|
|
11075
11634
|
const pending = outcome.pendingKeys.length > 0 ? outcome.pendingKeys.join(", ") : "(none)";
|
|
11076
11635
|
const allMissing = [outcome.failedKey, ...outcome.pendingKeys].join(", ");
|
|
11077
|
-
process.stderr.write(`Function ${outcome.created.name} (${outcome.created.id}) was created, but writing secret ${outcome.failedKey} failed; succeeded keys so far: ${succeeded}; keys not yet attempted: ${pending}. The redeploy is NOT yet live. Re-run \`primitive functions
|
|
11636
|
+
process.stderr.write(`Function ${outcome.created.name} (${outcome.created.id}) was created, but writing secret ${outcome.failedKey} failed; succeeded keys so far: ${succeeded}; keys not yet attempted: ${pending}. The redeploy is NOT yet live. Re-run \`primitive functions set-secret\` for each of [${allMissing}], then \`primitive functions redeploy --id ${outcome.created.id} --file <bundle>\` to push them live.\n`);
|
|
11078
11637
|
} else if (outcome.stage === "redeploy") {
|
|
11079
11638
|
const succeeded = outcome.succeededKeys.length > 0 ? outcome.succeededKeys.join(", ") : "(none)";
|
|
11080
|
-
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
|
|
11639
|
+
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`);
|
|
11081
11640
|
}
|
|
11082
11641
|
writeErrorWithHints(outcome.payload);
|
|
11083
11642
|
removeStaleSavedCredentialOnUnauthorized({
|
|
@@ -11094,8 +11653,8 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
11094
11653
|
};
|
|
11095
11654
|
//#endregion
|
|
11096
11655
|
//#region src/oclif/commands/functions-init.ts
|
|
11097
|
-
const SDK_VERSION_RANGE = "^0.
|
|
11098
|
-
const CLI_VERSION_RANGE = "^0.
|
|
11656
|
+
const SDK_VERSION_RANGE = "^0.28.0";
|
|
11657
|
+
const CLI_VERSION_RANGE = "^0.28.0";
|
|
11099
11658
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
11100
11659
|
const VALID_NAME = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
11101
11660
|
function isValidFunctionName(name) {
|
|
@@ -11240,8 +11799,8 @@ function renderPackageJson(name) {
|
|
|
11240
11799
|
type: "module",
|
|
11241
11800
|
scripts: {
|
|
11242
11801
|
build: "node build.mjs",
|
|
11243
|
-
deploy: `npm run build && primitive functions
|
|
11244
|
-
redeploy: "npm run build && primitive functions
|
|
11802
|
+
deploy: `npm run build && primitive functions deploy --name ${name} --file ./dist/handler.js`,
|
|
11803
|
+
redeploy: "npm run build && primitive functions redeploy --id $PRIMITIVE_FUNCTION_ID --file ./dist/handler.js"
|
|
11245
11804
|
},
|
|
11246
11805
|
dependencies: { "@primitivedotdev/sdk": SDK_VERSION_RANGE },
|
|
11247
11806
|
devDependencies: {
|
|
@@ -11312,7 +11871,7 @@ npm run build
|
|
|
11312
11871
|
npm run deploy
|
|
11313
11872
|
\`\`\`
|
|
11314
11873
|
|
|
11315
|
-
The deploy step calls \`primitive functions
|
|
11874
|
+
The deploy step calls \`primitive functions deploy\` (provided by the
|
|
11316
11875
|
\`@primitivedotdev/cli\` package; install with
|
|
11317
11876
|
\`npm install -g @primitivedotdev/cli\` or run via
|
|
11318
11877
|
\`npx @primitivedotdev/cli@latest <command>\`). It requires
|
|
@@ -11388,12 +11947,12 @@ var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
|
11388
11947
|
\`@primitivedotdev/sdk/api\` and demonstrates the canonical pattern:
|
|
11389
11948
|
parse the email.received event, send a reply via the SDK, return a
|
|
11390
11949
|
JSON envelope. The build script uses esbuild's JS API and emits
|
|
11391
|
-
./dist/handler.js, ready to hand to \`primitive functions
|
|
11950
|
+
./dist/handler.js, ready to hand to \`primitive functions deploy --file\`.
|
|
11392
11951
|
|
|
11393
11952
|
Refuses to overwrite an existing directory. Use --out-dir to pick a
|
|
11394
11953
|
different target path than ./<name>/.`;
|
|
11395
|
-
static summary = "Scaffold a new Primitive Function project ready for functions
|
|
11396
|
-
static examples = ["<%= config.bin %> functions
|
|
11954
|
+
static summary = "Scaffold a new Primitive Function project ready for functions deploy";
|
|
11955
|
+
static examples = ["<%= config.bin %> functions init my-fn", "<%= config.bin %> functions init my-fn --out-dir ./functions/my-fn"];
|
|
11397
11956
|
static args = { name: Args.string({
|
|
11398
11957
|
description: "Function name. Lowercase letters, digits, hyphens, underscores. 1-63 chars. Used as the directory name (when --out-dir is not set) and as the package.json name.",
|
|
11399
11958
|
required: true
|
|
@@ -11411,7 +11970,7 @@ var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
|
11411
11970
|
this.log(` cd ${outDir}`);
|
|
11412
11971
|
this.log(" npm install");
|
|
11413
11972
|
this.log(" npm run build");
|
|
11414
|
-
this.log(` primitive functions
|
|
11973
|
+
this.log(` primitive functions deploy --name ${args.name} --file ./dist/handler.js`);
|
|
11415
11974
|
}
|
|
11416
11975
|
};
|
|
11417
11976
|
//#endregion
|
|
@@ -11495,9 +12054,9 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
11495
12054
|
API calls (set-secret per pair, then update-function).`;
|
|
11496
12055
|
static summary = "Redeploy a function from a bundled handler file";
|
|
11497
12056
|
static examples = [
|
|
11498
|
-
"<%= config.bin %> functions
|
|
11499
|
-
"<%= config.bin %> functions
|
|
11500
|
-
"<%= config.bin %> functions
|
|
12057
|
+
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js",
|
|
12058
|
+
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
12059
|
+
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com"
|
|
11501
12060
|
];
|
|
11502
12061
|
static flags = {
|
|
11503
12062
|
"api-key": Flags.string({
|
|
@@ -11588,10 +12147,10 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
11588
12147
|
const succeeded = outcome.succeededKeys.length > 0 ? outcome.succeededKeys.join(", ") : "(none)";
|
|
11589
12148
|
const pending = outcome.pendingKeys.length > 0 ? outcome.pendingKeys.join(", ") : "(none)";
|
|
11590
12149
|
const allMissing = [outcome.failedKey, ...outcome.pendingKeys].join(", ");
|
|
11591
|
-
process.stderr.write(`Writing secret ${outcome.failedKey} failed before the redeploy; succeeded keys so far: ${succeeded}; keys not yet attempted: ${pending}. The new bundle has NOT been deployed. Re-run \`primitive functions
|
|
12150
|
+
process.stderr.write(`Writing secret ${outcome.failedKey} failed before the redeploy; succeeded keys so far: ${succeeded}; keys not yet attempted: ${pending}. The new bundle has NOT been deployed. Re-run \`primitive functions set-secret\` for each of [${allMissing}], then \`primitive functions redeploy --id ${flags.id} --file <bundle>\` to push them live.\n`);
|
|
11592
12151
|
} else if (outcome.stage === "redeploy") {
|
|
11593
12152
|
const succeeded = outcome.succeededKeys.length > 0 ? outcome.succeededKeys.join(", ") : "(none)";
|
|
11594
|
-
process.stderr.write(`Secrets [${succeeded}] were written, but the redeploy step failed; the new bindings are NOT yet live. Re-run \`primitive functions
|
|
12153
|
+
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`);
|
|
11595
12154
|
}
|
|
11596
12155
|
writeErrorWithHints(outcome.payload);
|
|
11597
12156
|
removeStaleSavedCredentialOnUnauthorized({
|
|
@@ -11673,7 +12232,7 @@ async function runSetSecret(api, params) {
|
|
|
11673
12232
|
};
|
|
11674
12233
|
}
|
|
11675
12234
|
var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command {
|
|
11676
|
-
static description = `Write a function secret and optionally redeploy so the new value lands in the running handler. Agent-grade shortcut for functions
|
|
12235
|
+
static description = `Write a function secret and optionally redeploy so the new value lands in the running handler. Agent-grade shortcut for functions set-function-secret + functions redeploy.
|
|
11677
12236
|
|
|
11678
12237
|
Without --redeploy this is a plain secret upsert: the value is
|
|
11679
12238
|
encrypted at rest but is NOT visible to the running handler until
|
|
@@ -11685,7 +12244,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
11685
12244
|
underscores; first character is a letter or underscore). System-
|
|
11686
12245
|
managed keys are reserved and rejected.`;
|
|
11687
12246
|
static summary = "Write a function secret (optionally redeploying to push it live)";
|
|
11688
|
-
static examples = ["<%= config.bin %> functions
|
|
12247
|
+
static examples = ["<%= config.bin %> functions set-secret --id <fn-id> --key API_TOKEN --value abc123", "<%= config.bin %> functions set-secret --id <fn-id> --key API_TOKEN --value abc123 --redeploy"];
|
|
11689
12248
|
static flags = {
|
|
11690
12249
|
"api-key": Flags.string({
|
|
11691
12250
|
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
@@ -11713,7 +12272,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
11713
12272
|
description: "Secret value (up to 4096 UTF-8 bytes). Encrypted at rest.",
|
|
11714
12273
|
required: true
|
|
11715
12274
|
}),
|
|
11716
|
-
redeploy: Flags.boolean({ description: "Also redeploy the function with its current code so the new value lands in the running handler. Without this, the secret is written but not visible to the handler until the next deploy. Note:
|
|
12275
|
+
redeploy: Flags.boolean({ description: "Also redeploy the function with its current code so the new value lands in the running handler. Without this, the secret is written but not visible to the handler until the next deploy. Note: when --redeploy re-uploads the function's current live code without a sourceMap field, the API preserves the current stored source map. Use `functions redeploy --file <bundle.js> --source-map-file <bundle.js.map>` to replace or restore a map." }),
|
|
11717
12276
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
11718
12277
|
};
|
|
11719
12278
|
async run() {
|
|
@@ -11764,8 +12323,8 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
11764
12323
|
value: flags.value
|
|
11765
12324
|
});
|
|
11766
12325
|
if (outcome.kind === "error") {
|
|
11767
|
-
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
|
|
11768
|
-
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
|
|
12326
|
+
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");
|
|
12327
|
+
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");
|
|
11769
12328
|
writeErrorWithHints(outcome.payload);
|
|
11770
12329
|
removeStaleSavedCredentialOnUnauthorized({
|
|
11771
12330
|
...authFailureContext,
|
|
@@ -11786,10 +12345,10 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
11786
12345
|
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.";
|
|
11787
12346
|
static summary = "Trigger a test invocation; with --wait, watch it land";
|
|
11788
12347
|
static examples = [
|
|
11789
|
-
"<%= config.bin %> functions
|
|
11790
|
-
"<%= config.bin %> functions
|
|
11791
|
-
"<%= config.bin %> functions
|
|
11792
|
-
"<%= config.bin %> functions
|
|
12348
|
+
"<%= config.bin %> functions test --id <fn-id>",
|
|
12349
|
+
"<%= config.bin %> functions test --id <fn-id> --local-part summarize",
|
|
12350
|
+
"<%= config.bin %> functions test --id <fn-id> --wait --show-sends",
|
|
12351
|
+
"<%= config.bin %> functions test --id <fn-id> --local-part summarize --wait --timeout 120"
|
|
11793
12352
|
];
|
|
11794
12353
|
static flags = {
|
|
11795
12354
|
"api-key": Flags.string({
|
|
@@ -11946,7 +12505,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
11946
12505
|
//#endregion
|
|
11947
12506
|
//#region src/oclif/commands/login.ts
|
|
11948
12507
|
const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
|
|
11949
|
-
function cliError$
|
|
12508
|
+
function cliError$2(message) {
|
|
11950
12509
|
return new Errors.CLIError(message, { exit: 1 });
|
|
11951
12510
|
}
|
|
11952
12511
|
function sleep(ms) {
|
|
@@ -11965,10 +12524,10 @@ function openBrowser(url) {
|
|
|
11965
12524
|
child.on("error", () => void 0);
|
|
11966
12525
|
child.unref();
|
|
11967
12526
|
}
|
|
11968
|
-
function unwrapData$
|
|
12527
|
+
function unwrapData$2(value) {
|
|
11969
12528
|
return value?.data ?? null;
|
|
11970
12529
|
}
|
|
11971
|
-
function retryAfterSeconds(result) {
|
|
12530
|
+
function retryAfterSeconds$1(result) {
|
|
11972
12531
|
const raw = result.response?.headers.get("retry-after");
|
|
11973
12532
|
if (!raw) return null;
|
|
11974
12533
|
const parsed = Number.parseInt(raw, 10);
|
|
@@ -12032,7 +12591,7 @@ var LoginCommand = class LoginCommand extends Command {
|
|
|
12032
12591
|
try {
|
|
12033
12592
|
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
12034
12593
|
} catch (error) {
|
|
12035
|
-
throw cliError$
|
|
12594
|
+
throw cliError$2(error instanceof Error ? error.message : String(error));
|
|
12036
12595
|
}
|
|
12037
12596
|
try {
|
|
12038
12597
|
await this.runWithCredentialLock(flags);
|
|
@@ -12061,8 +12620,8 @@ var LoginCommand = class LoginCommand extends Command {
|
|
|
12061
12620
|
if (existingStatus.status === "removed_stale") process.stderr.write("Continuing with a new Primitive CLI login...\n");
|
|
12062
12621
|
else if (existingStatus.status === "blocked") {
|
|
12063
12622
|
writeErrorWithHints(existingStatus.payload);
|
|
12064
|
-
throw cliError$
|
|
12065
|
-
} else throw cliError$
|
|
12623
|
+
throw cliError$2(existingStatus.message);
|
|
12624
|
+
} else throw cliError$2(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before logging in again.`);
|
|
12066
12625
|
}
|
|
12067
12626
|
const apiClient = new PrimitiveApiClient({ apiBaseUrl1 });
|
|
12068
12627
|
const started = await startCliLogin({
|
|
@@ -12072,10 +12631,10 @@ var LoginCommand = class LoginCommand extends Command {
|
|
|
12072
12631
|
});
|
|
12073
12632
|
if (started.error) {
|
|
12074
12633
|
writeErrorWithHints(extractErrorPayload(started.error));
|
|
12075
|
-
throw cliError$
|
|
12634
|
+
throw cliError$2("Could not start Primitive CLI login.");
|
|
12076
12635
|
}
|
|
12077
|
-
const start = unwrapData$
|
|
12078
|
-
if (!start) throw cliError$
|
|
12636
|
+
const start = unwrapData$2(started.data);
|
|
12637
|
+
if (!start) throw cliError$2("Primitive API returned an empty CLI login response.");
|
|
12079
12638
|
process.stderr.write(`Your login code is: ${start.user_code}\n`);
|
|
12080
12639
|
if (!flags["no-browser"]) {
|
|
12081
12640
|
openBrowser(start.verification_uri_complete);
|
|
@@ -12095,8 +12654,8 @@ var LoginCommand = class LoginCommand extends Command {
|
|
|
12095
12654
|
responseStyle: "fields"
|
|
12096
12655
|
});
|
|
12097
12656
|
if (polled.data) {
|
|
12098
|
-
const login = unwrapData$
|
|
12099
|
-
if (!login) throw cliError$
|
|
12657
|
+
const login = unwrapData$2(polled.data);
|
|
12658
|
+
if (!login) throw cliError$2("Primitive API returned an empty CLI poll response.");
|
|
12100
12659
|
saveCliCredentials(this.config.configDir, {
|
|
12101
12660
|
api_key: login.api_key,
|
|
12102
12661
|
api_base_url_1: apiBaseUrl1,
|
|
@@ -12118,25 +12677,25 @@ var LoginCommand = class LoginCommand extends Command {
|
|
|
12118
12677
|
continue;
|
|
12119
12678
|
}
|
|
12120
12679
|
if (code === API_ERROR_CODES.slowDown) {
|
|
12121
|
-
interval = Math.min(retryAfterSeconds(polled) ?? interval + 5, MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS);
|
|
12680
|
+
interval = Math.min(retryAfterSeconds$1(polled) ?? interval + 5, MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS);
|
|
12122
12681
|
nextPollDelay = interval;
|
|
12123
12682
|
continue;
|
|
12124
12683
|
}
|
|
12125
|
-
if (code === API_ERROR_CODES.accessDenied) throw cliError$
|
|
12126
|
-
if (code === API_ERROR_CODES.expiredToken) throw cliError$
|
|
12127
|
-
if (code === API_ERROR_CODES.invalidDeviceCode) throw cliError$
|
|
12684
|
+
if (code === API_ERROR_CODES.accessDenied) throw cliError$2("Primitive CLI login was denied in the browser.");
|
|
12685
|
+
if (code === API_ERROR_CODES.expiredToken) throw cliError$2("Primitive CLI login expired. Run `primitive login` again.");
|
|
12686
|
+
if (code === API_ERROR_CODES.invalidDeviceCode) throw cliError$2("Primitive CLI login device code is invalid. Run `primitive login` again.");
|
|
12128
12687
|
writeErrorWithHints(payload);
|
|
12129
|
-
throw cliError$
|
|
12688
|
+
throw cliError$2("Primitive CLI login failed while polling for approval.");
|
|
12130
12689
|
}
|
|
12131
|
-
throw cliError$
|
|
12690
|
+
throw cliError$2("Primitive CLI login expired. Run `primitive login` again.");
|
|
12132
12691
|
}
|
|
12133
12692
|
};
|
|
12134
12693
|
//#endregion
|
|
12135
12694
|
//#region src/oclif/commands/logout.ts
|
|
12136
|
-
function cliError(message) {
|
|
12695
|
+
function cliError$1(message) {
|
|
12137
12696
|
return new Errors.CLIError(message, { exit: 1 });
|
|
12138
12697
|
}
|
|
12139
|
-
function unwrapData(value) {
|
|
12698
|
+
function unwrapData$1(value) {
|
|
12140
12699
|
return value?.data ?? null;
|
|
12141
12700
|
}
|
|
12142
12701
|
var LogoutCommand = class LogoutCommand extends Command {
|
|
@@ -12154,7 +12713,7 @@ var LogoutCommand = class LogoutCommand extends Command {
|
|
|
12154
12713
|
try {
|
|
12155
12714
|
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
12156
12715
|
} catch (error) {
|
|
12157
|
-
throw cliError(error instanceof Error ? error.message : String(error));
|
|
12716
|
+
throw cliError$1(error instanceof Error ? error.message : String(error));
|
|
12158
12717
|
}
|
|
12159
12718
|
try {
|
|
12160
12719
|
await this.runWithCredentialLock(flags);
|
|
@@ -12173,7 +12732,7 @@ var LogoutCommand = class LogoutCommand extends Command {
|
|
|
12173
12732
|
process.exitCode = 1;
|
|
12174
12733
|
return;
|
|
12175
12734
|
}
|
|
12176
|
-
if (!credentials) throw cliError("Not logged in. Run `primitive login` to create saved CLI credentials.");
|
|
12735
|
+
if (!credentials) throw cliError$1("Not logged in. Run `primitive login` to create saved CLI credentials.");
|
|
12177
12736
|
const apiBaseUrl1 = flags["api-base-url-1"] ? normalizeApiBaseUrl1(flags["api-base-url-1"]) : credentials.api_base_url_1;
|
|
12178
12737
|
const apiClient = new PrimitiveApiClient({
|
|
12179
12738
|
apiKey: credentials.api_key,
|
|
@@ -12195,15 +12754,101 @@ var LogoutCommand = class LogoutCommand extends Command {
|
|
|
12195
12754
|
return;
|
|
12196
12755
|
}
|
|
12197
12756
|
writeErrorWithHints(payload);
|
|
12198
|
-
throw cliError("Could not revoke the saved Primitive CLI API key.");
|
|
12757
|
+
throw cliError$1("Could not revoke the saved Primitive CLI API key.");
|
|
12199
12758
|
}
|
|
12200
|
-
const logout = unwrapData(result.data);
|
|
12759
|
+
const logout = unwrapData$1(result.data);
|
|
12201
12760
|
deleteCliCredentials(this.config.configDir);
|
|
12202
12761
|
const keyId = logout?.key_id ?? credentials.key_id;
|
|
12203
12762
|
process.stderr.write(`Logged out and revoked API key ${keyId}.\n`);
|
|
12204
12763
|
}
|
|
12205
12764
|
};
|
|
12206
12765
|
//#endregion
|
|
12766
|
+
//#region src/oclif/commands/reply.ts
|
|
12767
|
+
var ReplyCommand = class ReplyCommand extends Command {
|
|
12768
|
+
static description = `Reply to an inbound email.
|
|
12769
|
+
|
|
12770
|
+
The API derives recipients, the Re: subject, and threading headers from the inbound email id. Use \`primitive send --in-reply-to <message-id>\` only when you need to thread against a raw Message-Id instead of an inbound email stored by Primitive.`;
|
|
12771
|
+
static summary = "Reply to an inbound email";
|
|
12772
|
+
static examples = [
|
|
12773
|
+
"<%= config.bin %> reply --id <inbound-email-id> --body 'Thanks, got it.'",
|
|
12774
|
+
"<%= config.bin %> reply --id <inbound-email-id> --html '<p>Thanks, got it.</p>' --wait",
|
|
12775
|
+
"<%= config.bin %> reply --id <inbound-email-id> --from 'Support <support@example.com>' --body 'Thanks!'"
|
|
12776
|
+
];
|
|
12777
|
+
static flags = {
|
|
12778
|
+
"api-key": Flags.string({
|
|
12779
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
12780
|
+
env: "PRIMITIVE_API_KEY"
|
|
12781
|
+
}),
|
|
12782
|
+
"api-base-url-1": Flags.string({
|
|
12783
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
12784
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
12785
|
+
hidden: true
|
|
12786
|
+
}),
|
|
12787
|
+
"api-base-url-2": Flags.string({
|
|
12788
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
12789
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
12790
|
+
hidden: true
|
|
12791
|
+
}),
|
|
12792
|
+
id: Flags.string({
|
|
12793
|
+
description: "Inbound email id to reply to.",
|
|
12794
|
+
required: true
|
|
12795
|
+
}),
|
|
12796
|
+
body: Flags.string({ description: "Plain-text reply body. Either --body or --html (or both) is required." }),
|
|
12797
|
+
html: Flags.string({ description: "HTML reply body. Either --body or --html (or both) is required." }),
|
|
12798
|
+
from: Flags.string({ description: "Optional From header override. Defaults to the inbound recipient." }),
|
|
12799
|
+
wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the reply for delivery." }),
|
|
12800
|
+
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
12801
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
12802
|
+
};
|
|
12803
|
+
async run() {
|
|
12804
|
+
const { flags } = await this.parse(ReplyCommand);
|
|
12805
|
+
if (!flags.body && !flags.html) throw new Errors.CLIError("Either --body or --html (or both) is required.");
|
|
12806
|
+
await runWithTiming(flags.time, async () => {
|
|
12807
|
+
const baseUrlOverridden = flags["api-base-url-1"] !== void 0 || flags["api-base-url-2"] !== void 0;
|
|
12808
|
+
const auth = resolveCliAuth({
|
|
12809
|
+
apiKey: flags["api-key"],
|
|
12810
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
12811
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
12812
|
+
configDir: this.config.configDir
|
|
12813
|
+
});
|
|
12814
|
+
const apiClient = new PrimitiveApiClient({
|
|
12815
|
+
apiKey: auth.apiKey,
|
|
12816
|
+
apiBaseUrl1: auth.apiBaseUrl1,
|
|
12817
|
+
apiBaseUrl2: auth.apiBaseUrl2
|
|
12818
|
+
});
|
|
12819
|
+
const result = await replyToEmail({
|
|
12820
|
+
body: {
|
|
12821
|
+
...flags.body !== void 0 ? { body_text: flags.body } : {},
|
|
12822
|
+
...flags.html !== void 0 ? { body_html: flags.html } : {},
|
|
12823
|
+
...flags.from !== void 0 ? { from: flags.from } : {},
|
|
12824
|
+
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
12825
|
+
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
12826
|
+
},
|
|
12827
|
+
client: apiClient.client,
|
|
12828
|
+
path: { id: flags.id },
|
|
12829
|
+
responseStyle: "fields"
|
|
12830
|
+
});
|
|
12831
|
+
if (result.error) {
|
|
12832
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
12833
|
+
writeErrorWithHints(errorPayload);
|
|
12834
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
12835
|
+
auth,
|
|
12836
|
+
baseUrlOverridden,
|
|
12837
|
+
configDir: this.config.configDir,
|
|
12838
|
+
payload: errorPayload
|
|
12839
|
+
});
|
|
12840
|
+
process.exitCode = 1;
|
|
12841
|
+
return;
|
|
12842
|
+
}
|
|
12843
|
+
const envelope = result.data;
|
|
12844
|
+
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
12845
|
+
process.stderr.write(chunk);
|
|
12846
|
+
} });
|
|
12847
|
+
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
12848
|
+
});
|
|
12849
|
+
}
|
|
12850
|
+
};
|
|
12851
|
+
//#endregion
|
|
12207
12852
|
//#region src/oclif/commands/send.ts
|
|
12208
12853
|
const SUBJECT_MAX_LENGTH = 200;
|
|
12209
12854
|
function deriveSubject(body) {
|
|
@@ -12235,17 +12880,17 @@ async function pickDefaultFromAddress(apiClient, authFailureContext) {
|
|
|
12235
12880
|
throw new Errors.CLIError(`Could not look up your verified domains to default --from. Pass --from explicitly. Underlying error: ${formatErrorPayload(errorPayload)}`);
|
|
12236
12881
|
}
|
|
12237
12882
|
const first = result.data?.data?.find(isVerifiedDomain);
|
|
12238
|
-
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
|
|
12883
|
+
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.");
|
|
12239
12884
|
return `agent@${first.domain}`;
|
|
12240
12885
|
}
|
|
12241
12886
|
var SendCommand = class SendCommand extends Command {
|
|
12242
|
-
static description = `Send an outbound email. Agent-grade shortcut for sending
|
|
12887
|
+
static description = `Send an outbound email. Agent-grade shortcut for \`sending send\` with sensible defaults.
|
|
12243
12888
|
|
|
12244
12889
|
--from defaults to agent@<your-first-verified-outbound-domain> when omitted.
|
|
12245
12890
|
--subject defaults to the first line of the body when omitted.
|
|
12246
12891
|
|
|
12247
12892
|
For the full flag set (custom message-id threading on the wire,
|
|
12248
|
-
references arrays, etc.), use \`primitive sending
|
|
12893
|
+
references arrays, etc.), use \`primitive sending send\`.`;
|
|
12249
12894
|
static summary = "Send an email (simplified, agent-friendly)";
|
|
12250
12895
|
static examples = [
|
|
12251
12896
|
"<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
|
|
@@ -12277,7 +12922,7 @@ var SendCommand = class SendCommand extends Command {
|
|
|
12277
12922
|
subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
|
|
12278
12923
|
body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
|
|
12279
12924
|
html: Flags.string({ description: "HTML message body. Either --body or --html (or both) is required." }),
|
|
12280
|
-
"in-reply-to": Flags.string({ description: "Message-Id of the parent email when threading a reply on the wire. For replying to an inbound message you received, prefer `primitive
|
|
12925
|
+
"in-reply-to": Flags.string({ description: "Message-Id of the parent email when threading a reply on the wire. For replying to an inbound message you received, prefer `primitive reply --id <inbound-id>`." }),
|
|
12281
12926
|
wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the message for delivery." }),
|
|
12282
12927
|
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
12283
12928
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
@@ -12330,11 +12975,402 @@ var SendCommand = class SendCommand extends Command {
|
|
|
12330
12975
|
return;
|
|
12331
12976
|
}
|
|
12332
12977
|
const envelope = result.data;
|
|
12978
|
+
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
12979
|
+
process.stderr.write(chunk);
|
|
12980
|
+
} });
|
|
12333
12981
|
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
12334
12982
|
});
|
|
12335
12983
|
}
|
|
12336
12984
|
};
|
|
12337
12985
|
//#endregion
|
|
12986
|
+
//#region src/oclif/commands/signup.ts
|
|
12987
|
+
const INVALID_VERIFICATION_CODE = "invalid_verification_code";
|
|
12988
|
+
const CLERK_PASSWORD_REJECTED = "clerk_password_rejected";
|
|
12989
|
+
const EXPIRED_TOKEN = "expired_token";
|
|
12990
|
+
const INVALID_SIGNUP_TOKEN = "invalid_signup_token";
|
|
12991
|
+
const SLOW_DOWN = "slow_down";
|
|
12992
|
+
const PENDING_SIGNUP_FILE = "signup.json";
|
|
12993
|
+
function cliError(message) {
|
|
12994
|
+
return new Errors.CLIError(message, { exit: 1 });
|
|
12995
|
+
}
|
|
12996
|
+
function unwrapData(value) {
|
|
12997
|
+
return value?.data ?? null;
|
|
12998
|
+
}
|
|
12999
|
+
function isRecord(value) {
|
|
13000
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13001
|
+
}
|
|
13002
|
+
function pendingSignupFromJson(value) {
|
|
13003
|
+
if (!isRecord(value)) return null;
|
|
13004
|
+
if (typeof value.signup_token !== "string" || typeof value.email !== "string" || typeof value.expires_in !== "number" || typeof value.resend_after !== "number" || typeof value.verification_code_length !== "number" || typeof value.api_base_url_1 !== "string" || typeof value.created_at !== "string" || typeof value.expires_at !== "string") return null;
|
|
13005
|
+
return {
|
|
13006
|
+
api_base_url_1: value.api_base_url_1,
|
|
13007
|
+
created_at: value.created_at,
|
|
13008
|
+
email: value.email,
|
|
13009
|
+
expires_at: value.expires_at,
|
|
13010
|
+
expires_in: value.expires_in,
|
|
13011
|
+
resend_after: value.resend_after,
|
|
13012
|
+
signup_token: value.signup_token,
|
|
13013
|
+
verification_code_length: value.verification_code_length
|
|
13014
|
+
};
|
|
13015
|
+
}
|
|
13016
|
+
function pendingSignupPath(configDir) {
|
|
13017
|
+
return join(configDir, PENDING_SIGNUP_FILE);
|
|
13018
|
+
}
|
|
13019
|
+
function deletePendingCliSignup(configDir) {
|
|
13020
|
+
rmSync(pendingSignupPath(configDir), { force: true });
|
|
13021
|
+
}
|
|
13022
|
+
function pendingSignupFromStart(start, apiBaseUrl1) {
|
|
13023
|
+
return {
|
|
13024
|
+
...start,
|
|
13025
|
+
api_base_url_1: apiBaseUrl1,
|
|
13026
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13027
|
+
expires_at: new Date(Date.now() + start.expires_in * 1e3).toISOString()
|
|
13028
|
+
};
|
|
13029
|
+
}
|
|
13030
|
+
function savePendingCliSignup(configDir, start, apiBaseUrl1) {
|
|
13031
|
+
mkdirSync(configDir, {
|
|
13032
|
+
mode: 448,
|
|
13033
|
+
recursive: true
|
|
13034
|
+
});
|
|
13035
|
+
const pending = pendingSignupFromStart(start, apiBaseUrl1);
|
|
13036
|
+
const path = pendingSignupPath(configDir);
|
|
13037
|
+
const tempPath = join(configDir, `${PENDING_SIGNUP_FILE}.${process$1.pid}.${randomUUID()}.tmp`);
|
|
13038
|
+
try {
|
|
13039
|
+
writeFileSync(tempPath, `${JSON.stringify(pending, null, 2)}\n`, { mode: 384 });
|
|
13040
|
+
chmodSync(tempPath, 384);
|
|
13041
|
+
renameSync(tempPath, path);
|
|
13042
|
+
chmodSync(path, 384);
|
|
13043
|
+
return pending;
|
|
13044
|
+
} catch (error) {
|
|
13045
|
+
rmSync(tempPath, { force: true });
|
|
13046
|
+
throw error;
|
|
13047
|
+
}
|
|
13048
|
+
}
|
|
13049
|
+
function loadPendingCliSignup(configDir, apiBaseUrl1) {
|
|
13050
|
+
const path = pendingSignupPath(configDir);
|
|
13051
|
+
let contents;
|
|
13052
|
+
try {
|
|
13053
|
+
contents = readFileSync(path, "utf8");
|
|
13054
|
+
} catch (error) {
|
|
13055
|
+
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
13056
|
+
throw error;
|
|
13057
|
+
}
|
|
13058
|
+
let pending;
|
|
13059
|
+
try {
|
|
13060
|
+
pending = pendingSignupFromJson(JSON.parse(contents));
|
|
13061
|
+
} catch {
|
|
13062
|
+
pending = null;
|
|
13063
|
+
}
|
|
13064
|
+
if (!pending) {
|
|
13065
|
+
deletePendingCliSignup(configDir);
|
|
13066
|
+
return null;
|
|
13067
|
+
}
|
|
13068
|
+
if (pending.api_base_url_1 !== apiBaseUrl1) return null;
|
|
13069
|
+
if (new Date(pending.expires_at).getTime() <= Date.now()) {
|
|
13070
|
+
deletePendingCliSignup(configDir);
|
|
13071
|
+
return null;
|
|
13072
|
+
}
|
|
13073
|
+
return {
|
|
13074
|
+
...pending,
|
|
13075
|
+
expires_in: Math.max(0, Math.ceil((new Date(pending.expires_at).getTime() - Date.now()) / 1e3))
|
|
13076
|
+
};
|
|
13077
|
+
}
|
|
13078
|
+
function retryAfterSeconds(result) {
|
|
13079
|
+
const raw = result.response?.headers.get("retry-after");
|
|
13080
|
+
if (!raw) return null;
|
|
13081
|
+
const parsed = Number.parseInt(raw, 10);
|
|
13082
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
13083
|
+
}
|
|
13084
|
+
function normalizeAnswer(value) {
|
|
13085
|
+
return value.trim();
|
|
13086
|
+
}
|
|
13087
|
+
async function promptLine(question) {
|
|
13088
|
+
const rl = createInterface({
|
|
13089
|
+
input: process$1.stdin,
|
|
13090
|
+
output: process$1.stderr
|
|
13091
|
+
});
|
|
13092
|
+
try {
|
|
13093
|
+
return normalizeAnswer(await rl.question(question));
|
|
13094
|
+
} finally {
|
|
13095
|
+
rl.close();
|
|
13096
|
+
}
|
|
13097
|
+
}
|
|
13098
|
+
async function promptHidden(question) {
|
|
13099
|
+
if (!process$1.stdin.isTTY || !process$1.stderr.isTTY || !process$1.stdin.setRawMode) throw cliError("Password input requires an interactive terminal with hidden input support.");
|
|
13100
|
+
return new Promise((resolve, reject) => {
|
|
13101
|
+
const input = process$1.stdin;
|
|
13102
|
+
let value = "";
|
|
13103
|
+
const cleanup = () => {
|
|
13104
|
+
input.setRawMode(false);
|
|
13105
|
+
input.pause();
|
|
13106
|
+
input.off("data", onData);
|
|
13107
|
+
};
|
|
13108
|
+
const finish = () => {
|
|
13109
|
+
cleanup();
|
|
13110
|
+
process$1.stderr.write("\n");
|
|
13111
|
+
resolve(value);
|
|
13112
|
+
};
|
|
13113
|
+
const onData = (chunk) => {
|
|
13114
|
+
const text = chunk.toString("utf8");
|
|
13115
|
+
for (const char of text) {
|
|
13116
|
+
if (char === "") {
|
|
13117
|
+
cleanup();
|
|
13118
|
+
process$1.stderr.write("\n");
|
|
13119
|
+
reject(cliError("Signup cancelled."));
|
|
13120
|
+
return;
|
|
13121
|
+
}
|
|
13122
|
+
if (char === "\r" || char === "\n") {
|
|
13123
|
+
finish();
|
|
13124
|
+
return;
|
|
13125
|
+
}
|
|
13126
|
+
if (char === "\b" || char === "") {
|
|
13127
|
+
value = value.slice(0, -1);
|
|
13128
|
+
continue;
|
|
13129
|
+
}
|
|
13130
|
+
value += char;
|
|
13131
|
+
}
|
|
13132
|
+
};
|
|
13133
|
+
process$1.stderr.write(question);
|
|
13134
|
+
input.setRawMode(true);
|
|
13135
|
+
input.resume();
|
|
13136
|
+
input.on("data", onData);
|
|
13137
|
+
});
|
|
13138
|
+
}
|
|
13139
|
+
function formatSignupSeconds(seconds) {
|
|
13140
|
+
if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds <= 0) return "soon";
|
|
13141
|
+
if (seconds < 60) return `${Math.ceil(seconds)} seconds`;
|
|
13142
|
+
const minutes = Math.ceil(seconds / 60);
|
|
13143
|
+
return `${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
13144
|
+
}
|
|
13145
|
+
function shouldRetrySignupPassword(errorCode) {
|
|
13146
|
+
return errorCode === CLERK_PASSWORD_REJECTED;
|
|
13147
|
+
}
|
|
13148
|
+
function signupErrorMessage(payload) {
|
|
13149
|
+
if (!isRecord(payload)) return null;
|
|
13150
|
+
const message = (isRecord(payload.error) ? payload.error : payload).message;
|
|
13151
|
+
return typeof message === "string" && message.trim() ? message : null;
|
|
13152
|
+
}
|
|
13153
|
+
async function promptRequired(question) {
|
|
13154
|
+
while (true) {
|
|
13155
|
+
const value = await promptLine(question);
|
|
13156
|
+
if (value) return value;
|
|
13157
|
+
process$1.stderr.write("Please enter a value.\n");
|
|
13158
|
+
}
|
|
13159
|
+
}
|
|
13160
|
+
async function promptRequiredPassword(question) {
|
|
13161
|
+
while (true) {
|
|
13162
|
+
const value = await promptHidden(question);
|
|
13163
|
+
if (value) return value;
|
|
13164
|
+
process$1.stderr.write("Please enter a password.\n");
|
|
13165
|
+
}
|
|
13166
|
+
}
|
|
13167
|
+
async function promptNewPassword() {
|
|
13168
|
+
while (true) {
|
|
13169
|
+
const password = await promptRequiredPassword("Password: ");
|
|
13170
|
+
if (password === await promptRequiredPassword("Confirm password: ")) return password;
|
|
13171
|
+
process$1.stderr.write("Passwords did not match. Try again.\n");
|
|
13172
|
+
}
|
|
13173
|
+
}
|
|
13174
|
+
async function confirmTerms() {
|
|
13175
|
+
process$1.stderr.write("By creating an account, you agree to Primitive's Terms of Service and Privacy Policy:\n");
|
|
13176
|
+
process$1.stderr.write(" https://primitive.dev/terms\n");
|
|
13177
|
+
process$1.stderr.write(" https://primitive.dev/privacy\n");
|
|
13178
|
+
const answer = (await promptRequired("Type 'yes' to continue: ")).toLowerCase();
|
|
13179
|
+
if (answer !== "yes" && answer !== "y") throw cliError("You must accept the terms to create an account.");
|
|
13180
|
+
}
|
|
13181
|
+
async function resendVerificationCode(params) {
|
|
13182
|
+
const resent = await (params.deps.resendCliSignupVerification ?? resendCliSignupVerification)({
|
|
13183
|
+
body: { signup_token: params.start.signup_token },
|
|
13184
|
+
client: params.apiClient.client,
|
|
13185
|
+
responseStyle: "fields"
|
|
13186
|
+
});
|
|
13187
|
+
if (resent.data) {
|
|
13188
|
+
const resend = unwrapData(resent.data);
|
|
13189
|
+
const next = resend ? {
|
|
13190
|
+
email: resend.email,
|
|
13191
|
+
expires_in: resend.expires_in,
|
|
13192
|
+
resend_after: resend.resend_after,
|
|
13193
|
+
signup_token: params.start.signup_token,
|
|
13194
|
+
verification_code_length: resend.verification_code_length
|
|
13195
|
+
} : params.start;
|
|
13196
|
+
savePendingCliSignup(params.configDir, next, params.apiBaseUrl1);
|
|
13197
|
+
process$1.stderr.write(`Sent a new ${next.verification_code_length}-digit verification code. It expires in ${formatSignupSeconds(next.expires_in)}.\n`);
|
|
13198
|
+
return next;
|
|
13199
|
+
}
|
|
13200
|
+
const payload = extractErrorPayload(resent.error);
|
|
13201
|
+
if (extractErrorCode(payload) === SLOW_DOWN) {
|
|
13202
|
+
const suffix = ` Wait ${formatSignupSeconds(retryAfterSeconds(resent) ?? params.start.resend_after)} before trying again.`;
|
|
13203
|
+
process$1.stderr.write(`Verification email was sent recently.${suffix}\n`);
|
|
13204
|
+
return params.start;
|
|
13205
|
+
}
|
|
13206
|
+
writeErrorWithHints(payload);
|
|
13207
|
+
throw cliError("Could not resend Primitive CLI signup verification email.");
|
|
13208
|
+
}
|
|
13209
|
+
async function runSignupWithCredentialLock(params) {
|
|
13210
|
+
const { configDir, flags } = params;
|
|
13211
|
+
const deps = params.deps ?? {};
|
|
13212
|
+
const promptRequiredFn = deps.promptRequired ?? promptRequired;
|
|
13213
|
+
const promptNewPasswordFn = deps.promptNewPassword ?? promptNewPassword;
|
|
13214
|
+
const confirmTermsFn = deps.confirmTerms ?? confirmTerms;
|
|
13215
|
+
const startFn = deps.startCliSignup ?? startCliSignup;
|
|
13216
|
+
const verifyFn = deps.verifyCliSignup ?? verifyCliSignup;
|
|
13217
|
+
const checkExistingLoginFn = deps.checkExistingLogin ?? checkExistingLogin;
|
|
13218
|
+
const apiBaseUrl1 = normalizeApiBaseUrl1(flags["api-base-url-1"]);
|
|
13219
|
+
let existing;
|
|
13220
|
+
try {
|
|
13221
|
+
existing = loadCliCredentials(configDir);
|
|
13222
|
+
} catch (error) {
|
|
13223
|
+
if (!flags.force) throw error;
|
|
13224
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
13225
|
+
process$1.stderr.write(`Replacing unreadable Primitive CLI credentials because --force was set: ${detail}\n`);
|
|
13226
|
+
existing = null;
|
|
13227
|
+
}
|
|
13228
|
+
if (existing && flags.force) process$1.stderr.write("Replacing saved Primitive CLI credentials after signup because --force was set.\n");
|
|
13229
|
+
else if (existing) {
|
|
13230
|
+
const existingStatus = await checkExistingLoginFn({
|
|
13231
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
13232
|
+
configDir,
|
|
13233
|
+
credentials: existing
|
|
13234
|
+
});
|
|
13235
|
+
if (existingStatus.status === "removed_stale") process$1.stderr.write("Continuing with Primitive CLI signup...\n");
|
|
13236
|
+
else if (existingStatus.status === "blocked") {
|
|
13237
|
+
writeErrorWithHints(existingStatus.payload);
|
|
13238
|
+
throw cliError(existingStatus.message);
|
|
13239
|
+
} else throw cliError(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before creating a new account.`);
|
|
13240
|
+
}
|
|
13241
|
+
if (flags.force) deletePendingCliSignup(configDir);
|
|
13242
|
+
const apiClient = new PrimitiveApiClient({ apiBaseUrl1 });
|
|
13243
|
+
let start = flags.force ? null : loadPendingCliSignup(configDir, apiBaseUrl1);
|
|
13244
|
+
const resumed = Boolean(start);
|
|
13245
|
+
if (start) process$1.stderr.write(`Continuing pending Primitive CLI signup for ${start.email}.\n`);
|
|
13246
|
+
else {
|
|
13247
|
+
const signupCode = await promptRequiredFn("Signup code: ");
|
|
13248
|
+
await confirmTermsFn();
|
|
13249
|
+
const email = await promptRequiredFn("Email: ");
|
|
13250
|
+
const started = await startFn({
|
|
13251
|
+
body: {
|
|
13252
|
+
device_name: flags["device-name"] ?? hostname(),
|
|
13253
|
+
email,
|
|
13254
|
+
signup_code: signupCode,
|
|
13255
|
+
terms_accepted: true
|
|
13256
|
+
},
|
|
13257
|
+
client: apiClient.client,
|
|
13258
|
+
responseStyle: "fields"
|
|
13259
|
+
});
|
|
13260
|
+
if (started.error) {
|
|
13261
|
+
writeErrorWithHints(extractErrorPayload(started.error));
|
|
13262
|
+
throw cliError("Could not start Primitive CLI signup.");
|
|
13263
|
+
}
|
|
13264
|
+
const startResult = unwrapData(started.data);
|
|
13265
|
+
if (!startResult) throw cliError("Primitive API returned an empty CLI signup response.");
|
|
13266
|
+
start = savePendingCliSignup(configDir, startResult, apiBaseUrl1);
|
|
13267
|
+
}
|
|
13268
|
+
if (resumed) process$1.stderr.write(`Check your email for the ${start.verification_code_length}-digit verification code sent to ${start.email}.\n`);
|
|
13269
|
+
else process$1.stderr.write(`Sent a ${start.verification_code_length}-digit verification code to ${start.email}.\n`);
|
|
13270
|
+
process$1.stderr.write(`The code expires in ${formatSignupSeconds(start.expires_in)}.\n`);
|
|
13271
|
+
process$1.stderr.write(`Enter the code from the email, or type \`resend\` to send a new code after ${formatSignupSeconds(start.resend_after)}.\n`);
|
|
13272
|
+
while (true) {
|
|
13273
|
+
const verificationCode = await promptRequiredFn(`Verification code (${start.verification_code_length} digits): `);
|
|
13274
|
+
if (verificationCode.toLowerCase() === "resend") {
|
|
13275
|
+
start = await resendVerificationCode({
|
|
13276
|
+
apiBaseUrl1,
|
|
13277
|
+
apiClient,
|
|
13278
|
+
configDir,
|
|
13279
|
+
deps,
|
|
13280
|
+
start
|
|
13281
|
+
});
|
|
13282
|
+
continue;
|
|
13283
|
+
}
|
|
13284
|
+
let password = await promptNewPasswordFn();
|
|
13285
|
+
while (true) {
|
|
13286
|
+
const verified = await verifyFn({
|
|
13287
|
+
body: {
|
|
13288
|
+
password,
|
|
13289
|
+
signup_token: start.signup_token,
|
|
13290
|
+
verification_code: verificationCode
|
|
13291
|
+
},
|
|
13292
|
+
client: apiClient.client,
|
|
13293
|
+
responseStyle: "fields"
|
|
13294
|
+
});
|
|
13295
|
+
if (verified.data) {
|
|
13296
|
+
const signup = unwrapData(verified.data);
|
|
13297
|
+
if (!signup) throw cliError("Primitive API returned an empty CLI signup verification response.");
|
|
13298
|
+
saveCliCredentials(configDir, {
|
|
13299
|
+
api_key: signup.api_key,
|
|
13300
|
+
api_base_url_1: apiBaseUrl1,
|
|
13301
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13302
|
+
key_id: signup.key_id,
|
|
13303
|
+
key_prefix: signup.key_prefix,
|
|
13304
|
+
org_id: signup.org_id,
|
|
13305
|
+
org_name: signup.org_name
|
|
13306
|
+
});
|
|
13307
|
+
deletePendingCliSignup(configDir);
|
|
13308
|
+
const org = signup.org_name ? ` (${signup.org_name})` : "";
|
|
13309
|
+
process$1.stderr.write(`Created account and logged in to org ${signup.org_id}${org}.\n`);
|
|
13310
|
+
process$1.stderr.write(`Saved credentials to ${credentialsPath(configDir)}.\n`);
|
|
13311
|
+
return;
|
|
13312
|
+
}
|
|
13313
|
+
const payload = extractErrorPayload(verified.error);
|
|
13314
|
+
const code = extractErrorCode(payload);
|
|
13315
|
+
if (code === INVALID_VERIFICATION_CODE) {
|
|
13316
|
+
process$1.stderr.write("Invalid verification code. Try again or type `resend`.\n");
|
|
13317
|
+
break;
|
|
13318
|
+
}
|
|
13319
|
+
if (shouldRetrySignupPassword(code)) {
|
|
13320
|
+
const message = signupErrorMessage(payload);
|
|
13321
|
+
if (message) process$1.stderr.write(`Password rejected: ${message}\n`);
|
|
13322
|
+
process$1.stderr.write("Choose a different password and try again.\n");
|
|
13323
|
+
password = await promptNewPasswordFn();
|
|
13324
|
+
continue;
|
|
13325
|
+
}
|
|
13326
|
+
if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingCliSignup(configDir);
|
|
13327
|
+
writeErrorWithHints(payload);
|
|
13328
|
+
throw cliError("Primitive CLI signup failed while verifying the account.");
|
|
13329
|
+
}
|
|
13330
|
+
}
|
|
13331
|
+
}
|
|
13332
|
+
var SignupCommand = class SignupCommand extends Command {
|
|
13333
|
+
static description = "Create a Primitive account from the terminal, verify your email, and save an org-scoped CLI API key locally.";
|
|
13334
|
+
static summary = "Create an account and log in";
|
|
13335
|
+
static examples = [
|
|
13336
|
+
"<%= config.bin %> signup",
|
|
13337
|
+
"<%= config.bin %> signup --device-name work-laptop",
|
|
13338
|
+
"<%= config.bin %> signup --force"
|
|
13339
|
+
];
|
|
13340
|
+
static flags = {
|
|
13341
|
+
"api-base-url-1": Flags.string({
|
|
13342
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
13343
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
13344
|
+
hidden: true
|
|
13345
|
+
}),
|
|
13346
|
+
"device-name": Flags.string({ description: "Device name used for the created CLI API key" }),
|
|
13347
|
+
force: Flags.boolean({
|
|
13348
|
+
char: "f",
|
|
13349
|
+
description: "Replace saved credentials without first verifying the existing login"
|
|
13350
|
+
})
|
|
13351
|
+
};
|
|
13352
|
+
async run() {
|
|
13353
|
+
const { flags } = await this.parse(SignupCommand);
|
|
13354
|
+
let releaseCredentialsLock;
|
|
13355
|
+
try {
|
|
13356
|
+
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
13357
|
+
} catch (error) {
|
|
13358
|
+
throw cliError(error instanceof Error ? error.message : String(error));
|
|
13359
|
+
}
|
|
13360
|
+
try {
|
|
13361
|
+
await this.runWithCredentialLock(flags);
|
|
13362
|
+
} finally {
|
|
13363
|
+
releaseCredentialsLock();
|
|
13364
|
+
}
|
|
13365
|
+
}
|
|
13366
|
+
async runWithCredentialLock(flags) {
|
|
13367
|
+
await runSignupWithCredentialLock({
|
|
13368
|
+
configDir: this.config.configDir,
|
|
13369
|
+
flags
|
|
13370
|
+
});
|
|
13371
|
+
}
|
|
13372
|
+
};
|
|
13373
|
+
//#endregion
|
|
12338
13374
|
//#region src/oclif/commands/whoami.ts
|
|
12339
13375
|
var WhoamiCommand = class WhoamiCommand extends Command {
|
|
12340
13376
|
static description = `Print the account currently authenticated by the API key. Useful as a credentials smoke test: confirms the key is live and shows which account it belongs to.`;
|
|
@@ -12477,7 +13513,7 @@ var ListOperationsCommand = class extends Command {
|
|
|
12477
13513
|
}
|
|
12478
13514
|
};
|
|
12479
13515
|
function lookupOperation(id) {
|
|
12480
|
-
const trimmed = id.trim();
|
|
13516
|
+
const trimmed = resolveOperationAlias(id.trim());
|
|
12481
13517
|
const sep = trimmed.indexOf(":");
|
|
12482
13518
|
const tag = sep === -1 ? "" : trimmed.slice(0, sep);
|
|
12483
13519
|
const cmd = sep === -1 ? trimmed : trimmed.slice(sep + 1);
|
|
@@ -12493,7 +13529,7 @@ function lookupOperation(id) {
|
|
|
12493
13529
|
}
|
|
12494
13530
|
var DescribeCommand = class DescribeCommand extends Command {
|
|
12495
13531
|
static args = { command: Args.string({
|
|
12496
|
-
description: "Command id to describe,
|
|
13532
|
+
description: "Command id to describe, e.g. `emails:list` or `emails:get-email`. Run `primitive list-operations | jq -r '.[] | \"\\(.tagCommand):\\(.command)\"'` to enumerate generated operation ids.",
|
|
12497
13533
|
required: true
|
|
12498
13534
|
}) };
|
|
12499
13535
|
static description = `Print the full operation manifest entry for a single API command, including the path, request schema, response schema, and per-field descriptions sourced from the OpenAPI spec.
|
|
@@ -12501,15 +13537,15 @@ var DescribeCommand = class DescribeCommand extends Command {
|
|
|
12501
13537
|
The manifest entry's \`responseSchema\` carries the inlined JSON Schema for the operation's 200/201 \`data\` envelope contents (\`$ref\`s resolved). Use it to look up what specific response fields mean. Examples:
|
|
12502
13538
|
|
|
12503
13539
|
# Which of EmailDetail's sender-shaped fields is canonical?
|
|
12504
|
-
primitive describe emails:get
|
|
12505
|
-
primitive describe emails:get
|
|
13540
|
+
primitive describe emails:get | jq '.responseSchema.properties | keys'
|
|
13541
|
+
primitive describe emails:get | jq -r '.responseSchema.properties.from_email.description'
|
|
12506
13542
|
|
|
12507
13543
|
# What does each value of SentEmailStatus mean?
|
|
12508
|
-
primitive describe
|
|
13544
|
+
primitive describe sent:get | jq -r '.responseSchema.properties.status.description'
|
|
12509
13545
|
|
|
12510
13546
|
\`requestSchema\` is the same shape for the request body when one exists. For a single field across many operations at once, use \`primitive list-operations | jq\` instead.`;
|
|
12511
13547
|
static summary = "Describe a single API operation in detail";
|
|
12512
|
-
static examples = ["<%= config.bin %> describe emails:get
|
|
13548
|
+
static examples = ["<%= config.bin %> describe emails:get", "<%= config.bin %> describe sent:get"];
|
|
12513
13549
|
async run() {
|
|
12514
13550
|
const { args } = await this.parse(DescribeCommand);
|
|
12515
13551
|
const { match, candidates } = lookupOperation(args.command);
|
|
@@ -12545,13 +13581,66 @@ var CompletionCommand = class CompletionCommand extends Command {
|
|
|
12545
13581
|
function commandId(operation) {
|
|
12546
13582
|
return `${operation.tagCommand}:${operation.command}`;
|
|
12547
13583
|
}
|
|
13584
|
+
const CANONICAL_OPERATION_ALIASES = {
|
|
13585
|
+
"account:show": "account:get-account",
|
|
13586
|
+
"account:storage": "account:get-storage-stats",
|
|
13587
|
+
"account:webhook-secret": "account:get-webhook-secret",
|
|
13588
|
+
"deliveries:list": "webhook-deliveries:list-deliveries",
|
|
13589
|
+
"deliveries:replay": "webhook-deliveries:replay-delivery",
|
|
13590
|
+
"domains:add": "domains:add-domain",
|
|
13591
|
+
"domains:delete": "domains:delete-domain",
|
|
13592
|
+
"domains:list": "domains:list-domains",
|
|
13593
|
+
"domains:update": "domains:update-domain",
|
|
13594
|
+
"domains:verify": "domains:verify-domain",
|
|
13595
|
+
"emails:delete": "emails:delete-email",
|
|
13596
|
+
"emails:discard-content": "emails:discard-email-content",
|
|
13597
|
+
"emails:download-raw": "emails:download-raw-email",
|
|
13598
|
+
"emails:get": "emails:get-email",
|
|
13599
|
+
"emails:list": "emails:list-emails",
|
|
13600
|
+
"emails:replay-webhooks": "emails:replay-email-webhooks",
|
|
13601
|
+
"emails:search": "emails:search-emails",
|
|
13602
|
+
"endpoints:create": "endpoints:create-endpoint",
|
|
13603
|
+
"endpoints:delete": "endpoints:delete-endpoint",
|
|
13604
|
+
"endpoints:list": "endpoints:list-endpoints",
|
|
13605
|
+
"endpoints:test": "endpoints:test-endpoint",
|
|
13606
|
+
"endpoints:update": "endpoints:update-endpoint",
|
|
13607
|
+
"filters:create": "filters:create-filter",
|
|
13608
|
+
"filters:delete": "filters:delete-filter",
|
|
13609
|
+
"filters:list": "filters:list-filters",
|
|
13610
|
+
"filters:update": "filters:update-filter",
|
|
13611
|
+
"functions:delete": "functions:delete-function",
|
|
13612
|
+
"functions:delete-secret": "functions:delete-function-secret",
|
|
13613
|
+
"functions:get": "functions:get-function",
|
|
13614
|
+
"functions:list": "functions:list-functions",
|
|
13615
|
+
"functions:list-secrets": "functions:list-function-secrets",
|
|
13616
|
+
"functions:logs": "functions:list-function-logs",
|
|
13617
|
+
"sending:get": "sending:get-sent-email",
|
|
13618
|
+
"sending:list": "sending:list-sent-emails",
|
|
13619
|
+
"sending:permissions": "sending:get-send-permissions",
|
|
13620
|
+
"sending:reply": "sending:reply-to-email",
|
|
13621
|
+
"sending:send": "sending:send-email",
|
|
13622
|
+
"sent:get": "sending:get-sent-email",
|
|
13623
|
+
"sent:list": "sending:list-sent-emails",
|
|
13624
|
+
"webhook-deliveries:list": "webhook-deliveries:list-deliveries",
|
|
13625
|
+
"webhook-deliveries:replay": "webhook-deliveries:replay-delivery"
|
|
13626
|
+
};
|
|
13627
|
+
const DESCRIBE_OPERATION_ALIASES = {
|
|
13628
|
+
...CANONICAL_OPERATION_ALIASES,
|
|
13629
|
+
reply: "sending:reply-to-email"
|
|
13630
|
+
};
|
|
13631
|
+
function resolveOperationAlias(id) {
|
|
13632
|
+
return DESCRIBE_OPERATION_ALIASES[id] ?? id;
|
|
13633
|
+
}
|
|
12548
13634
|
const OVERRIDDEN_OPERATION_IDS = new Set(["functions:test-function"]);
|
|
13635
|
+
const generatedCommands = Object.fromEntries(operationManifest.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(commandId(operation))).map((operation) => [commandId(operation), createOperationCommand(operation)]));
|
|
12549
13636
|
const COMMANDS = {
|
|
12550
13637
|
completion: CompletionCommand,
|
|
12551
13638
|
"list-operations": ListOperationsCommand,
|
|
12552
13639
|
describe: DescribeCommand,
|
|
12553
13640
|
send: SendCommand,
|
|
13641
|
+
reply: ReplyCommand,
|
|
12554
13642
|
login: LoginCommand,
|
|
13643
|
+
signup: SignupCommand,
|
|
12555
13644
|
logout: LogoutCommand,
|
|
12556
13645
|
whoami: WhoamiCommand,
|
|
12557
13646
|
doctor: DoctorCommand,
|
|
@@ -12562,8 +13651,14 @@ const COMMANDS = {
|
|
|
12562
13651
|
"functions:deploy": FunctionsDeployCommand,
|
|
12563
13652
|
"functions:redeploy": FunctionsRedeployCommand,
|
|
12564
13653
|
"functions:set-secret": FunctionsSetSecretCommand,
|
|
13654
|
+
"functions:test": FunctionsTestFunctionCommand,
|
|
12565
13655
|
"functions:test-function": FunctionsTestFunctionCommand,
|
|
12566
|
-
...Object.fromEntries(
|
|
13656
|
+
...Object.fromEntries(Object.entries(CANONICAL_OPERATION_ALIASES).map(([alias, target]) => {
|
|
13657
|
+
const command = generatedCommands[target];
|
|
13658
|
+
if (!command) throw new Error(`Missing generated command target for alias ${alias}`);
|
|
13659
|
+
return [alias, command];
|
|
13660
|
+
})),
|
|
13661
|
+
...generatedCommands
|
|
12567
13662
|
};
|
|
12568
13663
|
//#endregion
|
|
12569
|
-
export { COMMANDS, lookupOperation };
|
|
13664
|
+
export { CANONICAL_OPERATION_ALIASES, COMMANDS, lookupOperation };
|