primcli 1.26.0 → 1.27.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 +17 -0
- package/dist/oclif/index.js +1500 -371
- package/package.json +1 -1
package/dist/oclif/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as saveActiveChatState, C as resolveCliAuth, D as deleteChatState, E as chatStatePath, M as createClient, N as createConfig, O as loadActiveChatState, S as normalizeApiBaseUrl, T as saveSignupCredentials, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as hasStoredCliLogin, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as PrimitiveApiClient, k as loadChatConversationByLocalId, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveCliCredentials, x as loadCliCredentials, y as detectPrimitiveKeyEnvMisname } from "../cli-config-BKso8oJu.js";
|
|
1
|
+
import { A as saveActiveChatState, C as resolveCliAuth, D as deleteChatState, E as chatStatePath, M as createClient$1, N as createConfig$1, O as loadActiveChatState, S as normalizeApiBaseUrl, T as saveSignupCredentials, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as hasStoredCliLogin, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as PrimitiveApiClient, k as loadChatConversationByLocalId, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveCliCredentials, x as loadCliCredentials, y as detectPrimitiveKeyEnvMisname } from "../cli-config-BKso8oJu.js";
|
|
2
2
|
import { Args, Command, Errors, Flags, ux } from "@oclif/core";
|
|
3
3
|
import { chmodSync, closeSync, constants, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { createHash, randomUUID, scryptSync } from "node:crypto";
|
|
@@ -27,7 +27,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
27
27
|
};
|
|
28
28
|
//#endregion
|
|
29
29
|
//#region ../packages/api-core/src/api/client.gen.ts
|
|
30
|
-
const client = createClient(createConfig({ baseUrl: "https://api.primitive.dev/v1" }));
|
|
30
|
+
const client = createClient$1(createConfig$1({ baseUrl: "https://api.primitive.dev/v1" }));
|
|
31
31
|
//#endregion
|
|
32
32
|
//#region ../packages/api-core/src/api/sdk.gen.ts
|
|
33
33
|
var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -36,7 +36,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
36
36
|
cancelSentEmail: () => cancelSentEmail,
|
|
37
37
|
checkDomainDns: () => checkDomainDns,
|
|
38
38
|
cliLogout: () => cliLogout,
|
|
39
|
-
completeWebhookEvent: () => completeWebhookEvent,
|
|
39
|
+
completeWebhookEvent: () => completeWebhookEvent$1,
|
|
40
40
|
createAgentAccount: () => createAgentAccount,
|
|
41
41
|
createAgentClaimLink: () => createAgentClaimLink,
|
|
42
42
|
createChallenge: () => createChallenge,
|
|
@@ -71,7 +71,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
71
71
|
downloadEmailAttachmentPart: () => downloadEmailAttachmentPart,
|
|
72
72
|
downloadRawEmail: () => downloadRawEmail,
|
|
73
73
|
downloadSentAttachmentPart: () => downloadSentAttachmentPart,
|
|
74
|
-
getAccount: () => getAccount,
|
|
74
|
+
getAccount: () => getAccount$1,
|
|
75
75
|
getAgent: () => getAgent,
|
|
76
76
|
getChallenge: () => getChallenge,
|
|
77
77
|
getConversation: () => getConversation,
|
|
@@ -117,7 +117,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
117
117
|
payChallenge: () => payChallenge,
|
|
118
118
|
pollCliLogin: () => pollCliLogin,
|
|
119
119
|
publishAgent: () => publishAgent,
|
|
120
|
-
pullWebhookEvent: () => pullWebhookEvent,
|
|
120
|
+
pullWebhookEvent: () => pullWebhookEvent$1,
|
|
121
121
|
registerPayoutAddress: () => registerPayoutAddress,
|
|
122
122
|
removeAgentConnection: () => removeAgentConnection,
|
|
123
123
|
reorderRoutes: () => reorderRoutes,
|
|
@@ -411,7 +411,7 @@ const cliLogout = (options) => (options?.client ?? client).post({
|
|
|
411
411
|
/**
|
|
412
412
|
* Get account info
|
|
413
413
|
*/
|
|
414
|
-
const getAccount = (options) => (options?.client ?? client).get({
|
|
414
|
+
const getAccount$1 = (options) => (options?.client ?? client).get({
|
|
415
415
|
security: [{
|
|
416
416
|
scheme: "bearer",
|
|
417
417
|
type: "http"
|
|
@@ -1025,7 +1025,7 @@ const updateEndpoint = (options) => (options.client ?? client).patch({
|
|
|
1025
1025
|
*
|
|
1026
1026
|
* Wait up to 30 seconds for one existing event. The body string preserves the exact webhook serialization; headers and canonical occurrence/type metadata travel alongside it. A fixed lease protects the attempt while a short handler accepts input. Retry may redeliver an occurrence: deduplicate event_id. Empty delivery means no offer now, not proof all input was delivered; inspect backlog and persistent gap_count/last_gap_reason. Queue retention is 24 hours and does not extend source-content retention. Disconnecting preserves pending work. Same named destination shares consumption; different destinations get independent copies.
|
|
1027
1027
|
*/
|
|
1028
|
-
const pullWebhookEvent = (options) => (options.client ?? client).post({
|
|
1028
|
+
const pullWebhookEvent$1 = (options) => (options.client ?? client).post({
|
|
1029
1029
|
security: [{
|
|
1030
1030
|
scheme: "bearer",
|
|
1031
1031
|
type: "http"
|
|
@@ -1042,7 +1042,7 @@ const pullWebhookEvent = (options) => (options.client ?? client).post({
|
|
|
1042
1042
|
*
|
|
1043
1043
|
* Report normalized handling evidence for the queue, attempt and lease. Required JSON fields: queue_id, delivery_id, lease_token, mode, duration_ms. For mode=exec include exit_code; stdout includes write_succeeded; http includes status_code and optional transport_error, error_code, confirmed. Supply this discriminated request with --raw-body in the generated CLI. The server classifies success and retry. Duplicate identical completion is idempotent; a stale lease cannot finish a newer attempt. Exec success means durable input acceptance, not completion of agent reasoning. Only successful HTTP handling with recognized confirmation evidence can confirm content discard. Never send arbitrary handler output or HTTP response bodies.
|
|
1044
1044
|
*/
|
|
1045
|
-
const completeWebhookEvent = (options) => (options.client ?? client).post({
|
|
1045
|
+
const completeWebhookEvent$1 = (options) => (options.client ?? client).post({
|
|
1046
1046
|
security: [{
|
|
1047
1047
|
scheme: "bearer",
|
|
1048
1048
|
type: "http"
|
|
@@ -5110,6 +5110,22 @@ const openapiDocument = {
|
|
|
5110
5110
|
"format": "date-time"
|
|
5111
5111
|
},
|
|
5112
5112
|
"description": "Inclusive upper bound on `created_at`."
|
|
5113
|
+
},
|
|
5114
|
+
{
|
|
5115
|
+
"name": "q",
|
|
5116
|
+
"in": "query",
|
|
5117
|
+
"schema": { "type": "string" },
|
|
5118
|
+
"description": "Literal case-insensitive substring filter over the\nsubject, the retained plain-text body, the sender, and\nthe To, CC and BCC recipients of each send. Matched\nverbatim: `%`, `_` and `\\` are ordinary characters and\ndo not act as wildcards, so a search for `10%` finds the\nliteral string `10%` rather than everything beginning\n`10`.\n\nLeading and trailing whitespace is trimmed, and the\nresult must be 3 to 500 UTF-16 code units. The\nthree-character minimum is the search index's: a shorter\npattern cannot be answered from the index and would scan\nthe whole mailbox, so it is rejected rather than served\nas a request that times out.\n\nThose bounds are deliberately NOT declared as\n`minLength`/`maxLength`. Both count Unicode code points\non the value as sent, while the server trims first and\ncounts UTF-16 code units, and the two disagree in both\ndirections: two astral characters are 2 code points but\n4 code units, so a declared minimum would reject a query\nthe server accepts, and `\"<499 chars> \"` is 500 code\npoints before trimming and 499 after, so a declared\nmaximum would reject another. Declaring a bound the\nserver does not enforce is worse than declaring none,\nbecause the client refuses locally and the caller never\nlearns why. The server validates; a violation is a `400`.\n\nRecipients are matched one address at a time rather than\nagainst the serialized recipient list, so a query\ncontaining a quote, comma or bracket matches only where\nthat character occurs inside an address. Bodies are\nsearched only where the plain-text body was retained;\nHTML-only bodies are not searched.\n\nNot served by every deployment. Where sent-mail search\nis not enabled the endpoint answers `503` with code\n`sent_mail_search_unavailable`, which callers should\ntreat as a capability answer and fall back on rather\nthan as an outage.\n"
|
|
5119
|
+
},
|
|
5120
|
+
{
|
|
5121
|
+
"name": "from",
|
|
5122
|
+
"in": "query",
|
|
5123
|
+
"schema": {
|
|
5124
|
+
"type": "string",
|
|
5125
|
+
"format": "email",
|
|
5126
|
+
"maxLength": 320
|
|
5127
|
+
},
|
|
5128
|
+
"description": "Exact case-insensitive sender mailbox filter. Returns\nonly sends whose from address equals this value; it is\nnot a substring match, so a partial mailbox matches\nnothing. Use `q` to search sender text loosely.\nCombines with every other filter, and unlike `q` it is\nserved by every deployment.\n"
|
|
5113
5129
|
}
|
|
5114
5130
|
],
|
|
5115
5131
|
"responses": {
|
|
@@ -5124,7 +5140,20 @@ const openapiDocument = {
|
|
|
5124
5140
|
}] } } }
|
|
5125
5141
|
},
|
|
5126
5142
|
"400": { "$ref": "#/components/responses/ValidationError" },
|
|
5127
|
-
"401": { "$ref": "#/components/responses/Unauthorized" }
|
|
5143
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
5144
|
+
"503": {
|
|
5145
|
+
"description": "Sent-mail text search is not enabled in this deployment,\nso `q` cannot be served. A capability answer, not an\noutage: it will not clear on retry, and the same request\nwithout `q` succeeds. Fall back to whatever local\nmatching the caller has. Distinguished from the generic\n503 by its `sent_mail_search_unavailable` code.\n",
|
|
5146
|
+
"content": { "application/json": {
|
|
5147
|
+
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
5148
|
+
"example": {
|
|
5149
|
+
"success": false,
|
|
5150
|
+
"error": {
|
|
5151
|
+
"code": "sent_mail_search_unavailable",
|
|
5152
|
+
"message": "Sent-mail text search (`q`) is not enabled in this environment."
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
} }
|
|
5156
|
+
}
|
|
5128
5157
|
}
|
|
5129
5158
|
} },
|
|
5130
5159
|
"/sent-emails/{id}": {
|
|
@@ -11376,14 +11405,16 @@ const openapiDocument = {
|
|
|
11376
11405
|
"oneOf": [
|
|
11377
11406
|
{ "$ref": "#/components/schemas/CompleteWebhookHttpInput" },
|
|
11378
11407
|
{ "$ref": "#/components/schemas/CompleteWebhookExecInput" },
|
|
11379
|
-
{ "$ref": "#/components/schemas/CompleteWebhookStdoutInput" }
|
|
11408
|
+
{ "$ref": "#/components/schemas/CompleteWebhookStdoutInput" },
|
|
11409
|
+
{ "$ref": "#/components/schemas/CompleteWebhookSdkInput" }
|
|
11380
11410
|
],
|
|
11381
11411
|
"discriminator": {
|
|
11382
11412
|
"propertyName": "mode",
|
|
11383
11413
|
"mapping": {
|
|
11384
11414
|
"http": "#/components/schemas/CompleteWebhookHttpInput",
|
|
11385
11415
|
"exec": "#/components/schemas/CompleteWebhookExecInput",
|
|
11386
|
-
"stdout": "#/components/schemas/CompleteWebhookStdoutInput"
|
|
11416
|
+
"stdout": "#/components/schemas/CompleteWebhookStdoutInput",
|
|
11417
|
+
"sdk": "#/components/schemas/CompleteWebhookSdkInput"
|
|
11387
11418
|
}
|
|
11388
11419
|
}
|
|
11389
11420
|
},
|
|
@@ -11537,6 +11568,49 @@ const openapiDocument = {
|
|
|
11537
11568
|
],
|
|
11538
11569
|
"additionalProperties": false
|
|
11539
11570
|
},
|
|
11571
|
+
"CompleteWebhookSdkInput": {
|
|
11572
|
+
"type": "object",
|
|
11573
|
+
"properties": {
|
|
11574
|
+
"queue_id": {
|
|
11575
|
+
"type": "string",
|
|
11576
|
+
"format": "uuid",
|
|
11577
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
11578
|
+
},
|
|
11579
|
+
"delivery_id": {
|
|
11580
|
+
"type": "string",
|
|
11581
|
+
"format": "uuid",
|
|
11582
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
11583
|
+
},
|
|
11584
|
+
"lease_token": {
|
|
11585
|
+
"type": "string",
|
|
11586
|
+
"format": "uuid",
|
|
11587
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
11588
|
+
},
|
|
11589
|
+
"duration_ms": {
|
|
11590
|
+
"type": "integer",
|
|
11591
|
+
"minimum": 0,
|
|
11592
|
+
"maximum": 3e4
|
|
11593
|
+
},
|
|
11594
|
+
"mode": {
|
|
11595
|
+
"type": "string",
|
|
11596
|
+
"enum": ["sdk"]
|
|
11597
|
+
},
|
|
11598
|
+
"accepted": { "type": "boolean" },
|
|
11599
|
+
"transport_error": {
|
|
11600
|
+
"type": "string",
|
|
11601
|
+
"enum": ["timeout", "io"]
|
|
11602
|
+
}
|
|
11603
|
+
},
|
|
11604
|
+
"required": [
|
|
11605
|
+
"queue_id",
|
|
11606
|
+
"delivery_id",
|
|
11607
|
+
"lease_token",
|
|
11608
|
+
"duration_ms",
|
|
11609
|
+
"mode",
|
|
11610
|
+
"accepted"
|
|
11611
|
+
],
|
|
11612
|
+
"additionalProperties": false
|
|
11613
|
+
},
|
|
11540
11614
|
"CompleteWebhookResponse": {
|
|
11541
11615
|
"type": "object",
|
|
11542
11616
|
"properties": {
|
|
@@ -11570,6 +11644,31 @@ const openapiDocument = {
|
|
|
11570
11644
|
"Endpoint": {
|
|
11571
11645
|
"type": "object",
|
|
11572
11646
|
"properties": {
|
|
11647
|
+
"receiver_capabilities": {
|
|
11648
|
+
"type": "object",
|
|
11649
|
+
"properties": {
|
|
11650
|
+
"completion_modes": {
|
|
11651
|
+
"type": "array",
|
|
11652
|
+
"items": {
|
|
11653
|
+
"type": "string",
|
|
11654
|
+
"enum": [
|
|
11655
|
+
"http",
|
|
11656
|
+
"exec",
|
|
11657
|
+
"stdout",
|
|
11658
|
+
"sdk"
|
|
11659
|
+
]
|
|
11660
|
+
}
|
|
11661
|
+
},
|
|
11662
|
+
"stream_protocols": {
|
|
11663
|
+
"type": "array",
|
|
11664
|
+
"items": {
|
|
11665
|
+
"type": "string",
|
|
11666
|
+
"enum": ["primitive.events.v1"]
|
|
11667
|
+
}
|
|
11668
|
+
}
|
|
11669
|
+
},
|
|
11670
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
11671
|
+
},
|
|
11573
11672
|
"id": {
|
|
11574
11673
|
"type": "string",
|
|
11575
11674
|
"format": "uuid"
|
|
@@ -17879,6 +17978,49 @@ const operationManifest = [
|
|
|
17879
17978
|
"write_succeeded"
|
|
17880
17979
|
],
|
|
17881
17980
|
"additionalProperties": false
|
|
17981
|
+
},
|
|
17982
|
+
{
|
|
17983
|
+
"type": "object",
|
|
17984
|
+
"properties": {
|
|
17985
|
+
"queue_id": {
|
|
17986
|
+
"type": "string",
|
|
17987
|
+
"format": "uuid",
|
|
17988
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
17989
|
+
},
|
|
17990
|
+
"delivery_id": {
|
|
17991
|
+
"type": "string",
|
|
17992
|
+
"format": "uuid",
|
|
17993
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
17994
|
+
},
|
|
17995
|
+
"lease_token": {
|
|
17996
|
+
"type": "string",
|
|
17997
|
+
"format": "uuid",
|
|
17998
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
17999
|
+
},
|
|
18000
|
+
"duration_ms": {
|
|
18001
|
+
"type": "integer",
|
|
18002
|
+
"minimum": 0,
|
|
18003
|
+
"maximum": 3e4
|
|
18004
|
+
},
|
|
18005
|
+
"mode": {
|
|
18006
|
+
"type": "string",
|
|
18007
|
+
"enum": ["sdk"]
|
|
18008
|
+
},
|
|
18009
|
+
"accepted": { "type": "boolean" },
|
|
18010
|
+
"transport_error": {
|
|
18011
|
+
"type": "string",
|
|
18012
|
+
"enum": ["timeout", "io"]
|
|
18013
|
+
}
|
|
18014
|
+
},
|
|
18015
|
+
"required": [
|
|
18016
|
+
"queue_id",
|
|
18017
|
+
"delivery_id",
|
|
18018
|
+
"lease_token",
|
|
18019
|
+
"duration_ms",
|
|
18020
|
+
"mode",
|
|
18021
|
+
"accepted"
|
|
18022
|
+
],
|
|
18023
|
+
"additionalProperties": false
|
|
17882
18024
|
}
|
|
17883
18025
|
],
|
|
17884
18026
|
"discriminator": {
|
|
@@ -17886,7 +18028,8 @@ const operationManifest = [
|
|
|
17886
18028
|
"mapping": {
|
|
17887
18029
|
"http": "#/components/schemas/CompleteWebhookHttpInput",
|
|
17888
18030
|
"exec": "#/components/schemas/CompleteWebhookExecInput",
|
|
17889
|
-
"stdout": "#/components/schemas/CompleteWebhookStdoutInput"
|
|
18031
|
+
"stdout": "#/components/schemas/CompleteWebhookStdoutInput",
|
|
18032
|
+
"sdk": "#/components/schemas/CompleteWebhookSdkInput"
|
|
17890
18033
|
}
|
|
17891
18034
|
}
|
|
17892
18035
|
},
|
|
@@ -17997,6 +18140,31 @@ const operationManifest = [
|
|
|
17997
18140
|
"responseSchema": {
|
|
17998
18141
|
"type": "object",
|
|
17999
18142
|
"properties": {
|
|
18143
|
+
"receiver_capabilities": {
|
|
18144
|
+
"type": "object",
|
|
18145
|
+
"properties": {
|
|
18146
|
+
"completion_modes": {
|
|
18147
|
+
"type": "array",
|
|
18148
|
+
"items": {
|
|
18149
|
+
"type": "string",
|
|
18150
|
+
"enum": [
|
|
18151
|
+
"http",
|
|
18152
|
+
"exec",
|
|
18153
|
+
"stdout",
|
|
18154
|
+
"sdk"
|
|
18155
|
+
]
|
|
18156
|
+
}
|
|
18157
|
+
},
|
|
18158
|
+
"stream_protocols": {
|
|
18159
|
+
"type": "array",
|
|
18160
|
+
"items": {
|
|
18161
|
+
"type": "string",
|
|
18162
|
+
"enum": ["primitive.events.v1"]
|
|
18163
|
+
}
|
|
18164
|
+
}
|
|
18165
|
+
},
|
|
18166
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
18167
|
+
},
|
|
18000
18168
|
"id": {
|
|
18001
18169
|
"type": "string",
|
|
18002
18170
|
"format": "uuid"
|
|
@@ -18148,6 +18316,31 @@ const operationManifest = [
|
|
|
18148
18316
|
"items": {
|
|
18149
18317
|
"type": "object",
|
|
18150
18318
|
"properties": {
|
|
18319
|
+
"receiver_capabilities": {
|
|
18320
|
+
"type": "object",
|
|
18321
|
+
"properties": {
|
|
18322
|
+
"completion_modes": {
|
|
18323
|
+
"type": "array",
|
|
18324
|
+
"items": {
|
|
18325
|
+
"type": "string",
|
|
18326
|
+
"enum": [
|
|
18327
|
+
"http",
|
|
18328
|
+
"exec",
|
|
18329
|
+
"stdout",
|
|
18330
|
+
"sdk"
|
|
18331
|
+
]
|
|
18332
|
+
}
|
|
18333
|
+
},
|
|
18334
|
+
"stream_protocols": {
|
|
18335
|
+
"type": "array",
|
|
18336
|
+
"items": {
|
|
18337
|
+
"type": "string",
|
|
18338
|
+
"enum": ["primitive.events.v1"]
|
|
18339
|
+
}
|
|
18340
|
+
}
|
|
18341
|
+
},
|
|
18342
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
18343
|
+
},
|
|
18151
18344
|
"id": {
|
|
18152
18345
|
"type": "string",
|
|
18153
18346
|
"format": "uuid"
|
|
@@ -18589,6 +18782,31 @@ const operationManifest = [
|
|
|
18589
18782
|
"responseSchema": {
|
|
18590
18783
|
"type": "object",
|
|
18591
18784
|
"properties": {
|
|
18785
|
+
"receiver_capabilities": {
|
|
18786
|
+
"type": "object",
|
|
18787
|
+
"properties": {
|
|
18788
|
+
"completion_modes": {
|
|
18789
|
+
"type": "array",
|
|
18790
|
+
"items": {
|
|
18791
|
+
"type": "string",
|
|
18792
|
+
"enum": [
|
|
18793
|
+
"http",
|
|
18794
|
+
"exec",
|
|
18795
|
+
"stdout",
|
|
18796
|
+
"sdk"
|
|
18797
|
+
]
|
|
18798
|
+
}
|
|
18799
|
+
},
|
|
18800
|
+
"stream_protocols": {
|
|
18801
|
+
"type": "array",
|
|
18802
|
+
"items": {
|
|
18803
|
+
"type": "string",
|
|
18804
|
+
"enum": ["primitive.events.v1"]
|
|
18805
|
+
}
|
|
18806
|
+
}
|
|
18807
|
+
},
|
|
18808
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
18809
|
+
},
|
|
18592
18810
|
"id": {
|
|
18593
18811
|
"type": "string",
|
|
18594
18812
|
"format": "uuid"
|
|
@@ -24914,6 +25132,20 @@ const operationManifest = [
|
|
|
24914
25132
|
"name": "date_to",
|
|
24915
25133
|
"required": false,
|
|
24916
25134
|
"type": "string"
|
|
25135
|
+
},
|
|
25136
|
+
{
|
|
25137
|
+
"description": "Literal case-insensitive substring filter over the\nsubject, the retained plain-text body, the sender, and\nthe To, CC and BCC recipients of each send. Matched\nverbatim: `%`, `_` and `\\` are ordinary characters and\ndo not act as wildcards, so a search for `10%` finds the\nliteral string `10%` rather than everything beginning\n`10`.\n\nLeading and trailing whitespace is trimmed, and the\nresult must be 3 to 500 UTF-16 code units. The\nthree-character minimum is the search index's: a shorter\npattern cannot be answered from the index and would scan\nthe whole mailbox, so it is rejected rather than served\nas a request that times out.\n\nThose bounds are deliberately NOT declared as\n`minLength`/`maxLength`. Both count Unicode code points\non the value as sent, while the server trims first and\ncounts UTF-16 code units, and the two disagree in both\ndirections: two astral characters are 2 code points but\n4 code units, so a declared minimum would reject a query\nthe server accepts, and `\"<499 chars> \"` is 500 code\npoints before trimming and 499 after, so a declared\nmaximum would reject another. Declaring a bound the\nserver does not enforce is worse than declaring none,\nbecause the client refuses locally and the caller never\nlearns why. The server validates; a violation is a `400`.\n\nRecipients are matched one address at a time rather than\nagainst the serialized recipient list, so a query\ncontaining a quote, comma or bracket matches only where\nthat character occurs inside an address. Bodies are\nsearched only where the plain-text body was retained;\nHTML-only bodies are not searched.\n\nNot served by every deployment. Where sent-mail search\nis not enabled the endpoint answers `503` with code\n`sent_mail_search_unavailable`, which callers should\ntreat as a capability answer and fall back on rather\nthan as an outage.\n",
|
|
25138
|
+
"enum": null,
|
|
25139
|
+
"name": "q",
|
|
25140
|
+
"required": false,
|
|
25141
|
+
"type": "string"
|
|
25142
|
+
},
|
|
25143
|
+
{
|
|
25144
|
+
"description": "Exact case-insensitive sender mailbox filter. Returns\nonly sends whose from address equals this value; it is\nnot a substring match, so a partial mailbox matches\nnothing. Use `q` to search sender text loosely.\nCombines with every other filter, and unlike `q` it is\nserved by every deployment.\n",
|
|
25145
|
+
"enum": null,
|
|
25146
|
+
"name": "from",
|
|
25147
|
+
"required": false,
|
|
25148
|
+
"type": "string"
|
|
24917
25149
|
}
|
|
24918
25150
|
],
|
|
24919
25151
|
"requestSchema": null,
|
|
@@ -27601,7 +27833,7 @@ function isMemoryJsonValue(value, seen = /* @__PURE__ */ new Set()) {
|
|
|
27601
27833
|
const API_HEADERS_ENV = "PRIMITIVE_API_HEADERS";
|
|
27602
27834
|
const OAUTH_REFRESH_SKEW_MS = 60 * 1e3;
|
|
27603
27835
|
const SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE = "Saved Primitive CLI OAuth session expired or was revoked. Run `primitive signin` to authenticate again.";
|
|
27604
|
-
function mergeHeaders(...headers) {
|
|
27836
|
+
function mergeHeaders$1(...headers) {
|
|
27605
27837
|
const merged = {};
|
|
27606
27838
|
for (const headerSet of headers) {
|
|
27607
27839
|
if (!headerSet) continue;
|
|
@@ -27639,7 +27871,7 @@ function resolveCliApiRequestConfig(params) {
|
|
|
27639
27871
|
apiBaseUrl,
|
|
27640
27872
|
baseUrlOverridden: apiBaseUrl !== void 0,
|
|
27641
27873
|
environmentName: currentEnvironment?.name ?? null,
|
|
27642
|
-
headers: mergeHeaders(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
|
|
27874
|
+
headers: mergeHeaders$1(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
|
|
27643
27875
|
resolvedApiBaseUrl: normalizeApiBaseUrl(apiBaseUrl)
|
|
27644
27876
|
};
|
|
27645
27877
|
}
|
|
@@ -30292,7 +30524,7 @@ function checkApiKey(opts) {
|
|
|
30292
30524
|
}
|
|
30293
30525
|
async function checkAccount(client) {
|
|
30294
30526
|
try {
|
|
30295
|
-
const result = await getAccount({
|
|
30527
|
+
const result = await getAccount$1({
|
|
30296
30528
|
client: client.client,
|
|
30297
30529
|
responseStyle: "fields"
|
|
30298
30530
|
});
|
|
@@ -32071,8 +32303,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
32071
32303
|
name: "Primitive Team",
|
|
32072
32304
|
url: "https://primitive.dev"
|
|
32073
32305
|
};
|
|
32074
|
-
const SDK_VERSION_RANGE = "^1.
|
|
32075
|
-
const CLI_VERSION_RANGE = "^1.
|
|
32306
|
+
const SDK_VERSION_RANGE = "^1.27.0";
|
|
32307
|
+
const CLI_VERSION_RANGE = "^1.27.0";
|
|
32076
32308
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
32077
32309
|
function renderHandler() {
|
|
32078
32310
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -34106,6 +34338,11 @@ const FORWARDED_HEADERS = new Set([
|
|
|
34106
34338
|
"x-primitive-email-id",
|
|
34107
34339
|
"x-primitive-endpoint-id"
|
|
34108
34340
|
]);
|
|
34341
|
+
function normalizeForwardTarget(target) {
|
|
34342
|
+
if (/^\d+$/.test(target)) return `http://localhost:${target}`;
|
|
34343
|
+
if (/^(localhost|127\.0\.0\.1|\[::1\])(?::|\/|$)/.test(target)) return `http://${target}`;
|
|
34344
|
+
return target;
|
|
34345
|
+
}
|
|
34109
34346
|
function validateListenHandlerOptions(options) {
|
|
34110
34347
|
if (options.exec !== void 0 && process.platform === "win32") throw new Error("--exec requires a POSIX shell and process groups. On Windows, use --forward-to with a local HTTP handler.");
|
|
34111
34348
|
if (options.exec !== void 0 && options.forwardTo !== void 0) throw new Error("Choose either --exec or --forward-to.");
|
|
@@ -34113,7 +34350,7 @@ function validateListenHandlerOptions(options) {
|
|
|
34113
34350
|
if (options.forwardTo !== void 0) {
|
|
34114
34351
|
let url;
|
|
34115
34352
|
try {
|
|
34116
|
-
url = new URL(options.forwardTo);
|
|
34353
|
+
url = new URL(normalizeForwardTarget(options.forwardTo));
|
|
34117
34354
|
} catch {
|
|
34118
34355
|
throw new Error("--forward-to requires an HTTP or HTTPS URL.");
|
|
34119
34356
|
}
|
|
@@ -34258,7 +34495,7 @@ var ForwardFailure = class extends Error {
|
|
|
34258
34495
|
};
|
|
34259
34496
|
async function forward(target, delivery, signal) {
|
|
34260
34497
|
signal.throwIfAborted();
|
|
34261
|
-
const url = new URL(target);
|
|
34498
|
+
const url = new URL(normalizeForwardTarget(target));
|
|
34262
34499
|
const body = Buffer.from(delivery.body, "utf8");
|
|
34263
34500
|
const headers = {};
|
|
34264
34501
|
for (const [name, value] of Object.entries(delivery.headers)) if (FORWARDED_HEADERS.has(name.toLowerCase())) headers[name.toLowerCase()] = value;
|
|
@@ -34398,6 +34635,1169 @@ function createListenHandler(options = {}) {
|
|
|
34398
34635
|
};
|
|
34399
34636
|
}
|
|
34400
34637
|
//#endregion
|
|
34638
|
+
//#region ../sdk-node/dist/payloads-BMngnJlE.js
|
|
34639
|
+
/**
|
|
34640
|
+
* Load a Node builtin at call time. Exported for direct testing of the
|
|
34641
|
+
* unavailable-module error path; production code goes through the typed
|
|
34642
|
+
* wrappers below.
|
|
34643
|
+
*/
|
|
34644
|
+
async function loadBuiltin(specifier, operation) {
|
|
34645
|
+
let builtin;
|
|
34646
|
+
if (typeof process !== "undefined" && typeof process.getBuiltinModule === "function") try {
|
|
34647
|
+
builtin = process.getBuiltinModule(specifier);
|
|
34648
|
+
} catch {
|
|
34649
|
+
builtin = void 0;
|
|
34650
|
+
}
|
|
34651
|
+
if (builtin !== void 0 && builtin !== null) return builtin;
|
|
34652
|
+
try {
|
|
34653
|
+
return await import(specifier);
|
|
34654
|
+
} catch (cause) {
|
|
34655
|
+
throw new Error(`${operation} requires file system access, which this runtime does not provide. Pass in-memory bytes instead of a file path when running outside Node.js.`, { cause });
|
|
34656
|
+
}
|
|
34657
|
+
}
|
|
34658
|
+
/** `node:fs`, loaded lazily so Workers-style bundles never see the import. */
|
|
34659
|
+
function loadNodeFs(operation) {
|
|
34660
|
+
return loadBuiltin("node:fs", operation);
|
|
34661
|
+
}
|
|
34662
|
+
/** `node:fs/promises`, loaded lazily for the same reason as {@link loadNodeFs}. */
|
|
34663
|
+
function loadNodeFsPromises(operation) {
|
|
34664
|
+
return loadBuiltin("node:fs/promises", operation);
|
|
34665
|
+
}
|
|
34666
|
+
const CHUNK_SIZE = 64 * 1024 * 1024;
|
|
34667
|
+
const MANIFEST_VERSION = 1;
|
|
34668
|
+
const CHUNK_KDF_INFO = "payloads-chunk";
|
|
34669
|
+
const OBJECT_ID_BYTES = 16;
|
|
34670
|
+
const CEK_BYTES = 32;
|
|
34671
|
+
function subtle() {
|
|
34672
|
+
return globalThis.crypto.subtle;
|
|
34673
|
+
}
|
|
34674
|
+
const textEncoder = new TextEncoder();
|
|
34675
|
+
function randomBytes(length) {
|
|
34676
|
+
const out = new Uint8Array(length);
|
|
34677
|
+
globalThis.crypto.getRandomValues(out);
|
|
34678
|
+
return out;
|
|
34679
|
+
}
|
|
34680
|
+
function toHex(bytes) {
|
|
34681
|
+
let out = "";
|
|
34682
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
34683
|
+
return out;
|
|
34684
|
+
}
|
|
34685
|
+
function fromHex(hex) {
|
|
34686
|
+
if (hex.length % 2 !== 0) throw new Error("invalid hex: odd length");
|
|
34687
|
+
if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) throw new Error("invalid hex: non-hex character");
|
|
34688
|
+
const out = new Uint8Array(hex.length / 2);
|
|
34689
|
+
for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
34690
|
+
return out;
|
|
34691
|
+
}
|
|
34692
|
+
async function contentHash(bytes) {
|
|
34693
|
+
return new Uint8Array(await subtle().digest("SHA-256", bytes));
|
|
34694
|
+
}
|
|
34695
|
+
async function contentHashHex(bytes) {
|
|
34696
|
+
return toHex(await contentHash(bytes));
|
|
34697
|
+
}
|
|
34698
|
+
/** Binary Merkle root over ordered hex leaf hashes (odd node promoted). */
|
|
34699
|
+
async function merkleRoot(leafHashesHex) {
|
|
34700
|
+
if (leafHashesHex.length === 0) return contentHashHex(new Uint8Array(0));
|
|
34701
|
+
let level = leafHashesHex.map(fromHex);
|
|
34702
|
+
while (level.length > 1) {
|
|
34703
|
+
const next = [];
|
|
34704
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
34705
|
+
const left = level[i];
|
|
34706
|
+
const right = i + 1 < level.length ? level[i + 1] : level[i];
|
|
34707
|
+
const combined = new Uint8Array(left.length + right.length);
|
|
34708
|
+
combined.set(left, 0);
|
|
34709
|
+
combined.set(right, left.length);
|
|
34710
|
+
next.push(await contentHash(combined));
|
|
34711
|
+
}
|
|
34712
|
+
level = next;
|
|
34713
|
+
}
|
|
34714
|
+
return toHex(level[0]);
|
|
34715
|
+
}
|
|
34716
|
+
async function deriveChunkKey(cek, index) {
|
|
34717
|
+
const baseKey = await subtle().importKey("raw", cek, "HKDF", false, ["deriveKey"]);
|
|
34718
|
+
const info = textEncoder.encode(`${CHUNK_KDF_INFO}:${index}`);
|
|
34719
|
+
return subtle().deriveKey({
|
|
34720
|
+
name: "HKDF",
|
|
34721
|
+
hash: "SHA-256",
|
|
34722
|
+
salt: new Uint8Array(32),
|
|
34723
|
+
info
|
|
34724
|
+
}, baseKey, {
|
|
34725
|
+
name: "AES-GCM",
|
|
34726
|
+
length: 256
|
|
34727
|
+
}, false, ["encrypt", "decrypt"]);
|
|
34728
|
+
}
|
|
34729
|
+
function chunkNonce(index) {
|
|
34730
|
+
const nonce = new Uint8Array(12);
|
|
34731
|
+
new DataView(nonce.buffer).setUint32(8, index >>> 0, false);
|
|
34732
|
+
return nonce;
|
|
34733
|
+
}
|
|
34734
|
+
function chunkAad(objectId, index) {
|
|
34735
|
+
const aad = new Uint8Array(objectId.length + 4);
|
|
34736
|
+
aad.set(objectId, 0);
|
|
34737
|
+
new DataView(aad.buffer).setUint32(objectId.length, index >>> 0, false);
|
|
34738
|
+
return aad;
|
|
34739
|
+
}
|
|
34740
|
+
async function encryptChunk(cek, objectId, index, plaintext) {
|
|
34741
|
+
const key = await deriveChunkKey(cek, index);
|
|
34742
|
+
const ct = await subtle().encrypt({
|
|
34743
|
+
name: "AES-GCM",
|
|
34744
|
+
iv: chunkNonce(index),
|
|
34745
|
+
additionalData: chunkAad(objectId, index)
|
|
34746
|
+
}, key, plaintext);
|
|
34747
|
+
return new Uint8Array(ct);
|
|
34748
|
+
}
|
|
34749
|
+
async function decryptChunk(cek, objectId, index, ciphertext) {
|
|
34750
|
+
const key = await deriveChunkKey(cek, index);
|
|
34751
|
+
const pt = await subtle().decrypt({
|
|
34752
|
+
name: "AES-GCM",
|
|
34753
|
+
iv: chunkNonce(index),
|
|
34754
|
+
additionalData: chunkAad(objectId, index)
|
|
34755
|
+
}, key, ciphertext);
|
|
34756
|
+
return new Uint8Array(pt);
|
|
34757
|
+
}
|
|
34758
|
+
function authHeaders(apiKey) {
|
|
34759
|
+
return { authorization: `Bearer ${apiKey}` };
|
|
34760
|
+
}
|
|
34761
|
+
function apiRoot(baseUrl) {
|
|
34762
|
+
let end = baseUrl.length;
|
|
34763
|
+
while (end > 0 && baseUrl.charCodeAt(end - 1) === 47) end--;
|
|
34764
|
+
return `${baseUrl.slice(0, end)}/v1/payloads`;
|
|
34765
|
+
}
|
|
34766
|
+
const DEFAULT_MAX_RETRIES = 6;
|
|
34767
|
+
const RETRYABLE_STATUS = new Set([
|
|
34768
|
+
429,
|
|
34769
|
+
500,
|
|
34770
|
+
502,
|
|
34771
|
+
503,
|
|
34772
|
+
504
|
|
34773
|
+
]);
|
|
34774
|
+
function sleep$1(ms) {
|
|
34775
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
34776
|
+
}
|
|
34777
|
+
/**
|
|
34778
|
+
* fetch with exponential backoff on transient failures. Request bodies here are
|
|
34779
|
+
* in-memory buffers/strings (never streams), so a retry can safely resend them.
|
|
34780
|
+
*/
|
|
34781
|
+
async function retryingFetch(url, init, label, maxRetries = DEFAULT_MAX_RETRIES) {
|
|
34782
|
+
for (let attempt = 0;; attempt++) {
|
|
34783
|
+
let res;
|
|
34784
|
+
let networkError;
|
|
34785
|
+
try {
|
|
34786
|
+
res = await fetch(url, init);
|
|
34787
|
+
} catch (err) {
|
|
34788
|
+
networkError = err;
|
|
34789
|
+
}
|
|
34790
|
+
if (res && !RETRYABLE_STATUS.has(res.status)) return res;
|
|
34791
|
+
if (attempt >= maxRetries) {
|
|
34792
|
+
if (res) return res;
|
|
34793
|
+
throw new Error(`${label}: network error after ${attempt} retries: ${networkError?.message ?? networkError}`);
|
|
34794
|
+
}
|
|
34795
|
+
if (res) await res.text().catch(() => {});
|
|
34796
|
+
await sleep$1(Math.min(1e3 * 2 ** attempt, 15e3) + Math.floor(Math.random() * 250));
|
|
34797
|
+
}
|
|
34798
|
+
}
|
|
34799
|
+
async function initiate(opts, manifest) {
|
|
34800
|
+
const res = await retryingFetch(apiRoot(opts.baseUrl), {
|
|
34801
|
+
method: "POST",
|
|
34802
|
+
headers: {
|
|
34803
|
+
...authHeaders(opts.apiKey),
|
|
34804
|
+
"content-type": "application/json"
|
|
34805
|
+
},
|
|
34806
|
+
body: JSON.stringify({ manifest })
|
|
34807
|
+
}, "initiate");
|
|
34808
|
+
if (!res.ok) throw new Error(`initiate failed: HTTP ${res.status} ${await res.text()}`);
|
|
34809
|
+
await res.text();
|
|
34810
|
+
}
|
|
34811
|
+
async function putChunk(opts, root, d, bytes) {
|
|
34812
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, {
|
|
34813
|
+
method: "PUT",
|
|
34814
|
+
headers: {
|
|
34815
|
+
...authHeaders(opts.apiKey),
|
|
34816
|
+
"content-type": "application/octet-stream"
|
|
34817
|
+
},
|
|
34818
|
+
body: bytes
|
|
34819
|
+
}, `chunk ${d.index} upload`);
|
|
34820
|
+
if (!res.ok) throw new Error(`chunk ${d.index} upload failed: HTTP ${res.status} ${await res.text()}`);
|
|
34821
|
+
await res.text();
|
|
34822
|
+
}
|
|
34823
|
+
async function finalize(opts, root) {
|
|
34824
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/finalize`, {
|
|
34825
|
+
method: "POST",
|
|
34826
|
+
headers: authHeaders(opts.apiKey)
|
|
34827
|
+
}, "finalize");
|
|
34828
|
+
if (!res.ok) throw new Error(`finalize failed: HTTP ${res.status} ${await res.text()}`);
|
|
34829
|
+
await res.text();
|
|
34830
|
+
}
|
|
34831
|
+
async function fetchManifest(opts, root) {
|
|
34832
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/manifest`, { headers: authHeaders(opts.apiKey) }, "get manifest");
|
|
34833
|
+
if (!res.ok) throw new Error(`get manifest failed: HTTP ${res.status} ${await res.text()}`);
|
|
34834
|
+
return (await res.json()).data.manifest;
|
|
34835
|
+
}
|
|
34836
|
+
async function getChunkBytes(opts, root, d) {
|
|
34837
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, { headers: authHeaders(opts.apiKey) }, `get chunk ${d.index}`);
|
|
34838
|
+
if (!res.ok) throw new Error(`get chunk ${d.index} failed: HTTP ${res.status}`);
|
|
34839
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
34840
|
+
}
|
|
34841
|
+
async function readWindow(fh, position, length) {
|
|
34842
|
+
const buf = Buffer.allocUnsafe(length);
|
|
34843
|
+
let read = 0;
|
|
34844
|
+
while (read < length) {
|
|
34845
|
+
const { bytesRead } = await fh.read(buf, read, length - read, position + read);
|
|
34846
|
+
if (bytesRead === 0) break;
|
|
34847
|
+
read += bytesRead;
|
|
34848
|
+
}
|
|
34849
|
+
return read === length ? buf : buf.subarray(0, read);
|
|
34850
|
+
}
|
|
34851
|
+
async function mapLimit(items, limit, fn) {
|
|
34852
|
+
let next = 0;
|
|
34853
|
+
let firstError;
|
|
34854
|
+
const worker = async () => {
|
|
34855
|
+
while (next < items.length && firstError === void 0) {
|
|
34856
|
+
const i = next++;
|
|
34857
|
+
try {
|
|
34858
|
+
await fn(items[i], i);
|
|
34859
|
+
} catch (err) {
|
|
34860
|
+
if (firstError === void 0) firstError = err;
|
|
34861
|
+
return;
|
|
34862
|
+
}
|
|
34863
|
+
}
|
|
34864
|
+
};
|
|
34865
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length || 1) }, () => worker()));
|
|
34866
|
+
if (firstError !== void 0) throw firstError;
|
|
34867
|
+
}
|
|
34868
|
+
/**
|
|
34869
|
+
* Stream a file up as a Primitive Payload. Two passes over the file (encrypt to
|
|
34870
|
+
* build the manifest, then encrypt again to upload) keep memory bounded to a few
|
|
34871
|
+
* chunks — the whole object is never resident. Returns the content address
|
|
34872
|
+
* (merkleRoot) and the hex CEK needed to download it.
|
|
34873
|
+
*/
|
|
34874
|
+
async function pushFile(filePath, opts) {
|
|
34875
|
+
const { open, stat } = await loadNodeFsPromises("pushFile");
|
|
34876
|
+
const chunkSize = opts.chunkSize ?? CHUNK_SIZE;
|
|
34877
|
+
const concurrency = opts.concurrency ?? 3;
|
|
34878
|
+
const { size } = await stat(filePath);
|
|
34879
|
+
const cek = randomBytes(CEK_BYTES);
|
|
34880
|
+
const objectId = randomBytes(OBJECT_ID_BYTES);
|
|
34881
|
+
const chunkCount = size === 0 ? 0 : Math.ceil(size / chunkSize);
|
|
34882
|
+
const fh = await open(filePath, "r");
|
|
34883
|
+
try {
|
|
34884
|
+
const descriptors = [];
|
|
34885
|
+
for (let index = 0; index < chunkCount; index++) {
|
|
34886
|
+
const position = index * chunkSize;
|
|
34887
|
+
const length = Math.min(chunkSize, size - position);
|
|
34888
|
+
const plaintext = await readWindow(fh, position, length);
|
|
34889
|
+
const ciphertext = await encryptChunk(cek, objectId, index, plaintext);
|
|
34890
|
+
descriptors.push({
|
|
34891
|
+
index,
|
|
34892
|
+
ciphertextHash: await contentHashHex(ciphertext),
|
|
34893
|
+
plaintextSize: length,
|
|
34894
|
+
ciphertextSize: ciphertext.length
|
|
34895
|
+
});
|
|
34896
|
+
opts.onProgress?.("encrypt", index + 1, chunkCount);
|
|
34897
|
+
}
|
|
34898
|
+
const root = await merkleRoot(descriptors.map((d) => d.ciphertextHash));
|
|
34899
|
+
await initiate(opts, {
|
|
34900
|
+
version: MANIFEST_VERSION,
|
|
34901
|
+
objectId: toHex(objectId),
|
|
34902
|
+
chunkSize,
|
|
34903
|
+
totalPlaintextSize: size,
|
|
34904
|
+
chunkCount,
|
|
34905
|
+
chunks: descriptors,
|
|
34906
|
+
merkleRoot: root
|
|
34907
|
+
});
|
|
34908
|
+
let uploaded = 0;
|
|
34909
|
+
await mapLimit(descriptors, concurrency, async (d) => {
|
|
34910
|
+
const plaintext = await readWindow(fh, d.index * chunkSize, d.plaintextSize);
|
|
34911
|
+
await putChunk(opts, root, d, await encryptChunk(cek, objectId, d.index, plaintext));
|
|
34912
|
+
opts.onProgress?.("upload", ++uploaded, chunkCount);
|
|
34913
|
+
});
|
|
34914
|
+
await finalize(opts, root);
|
|
34915
|
+
return {
|
|
34916
|
+
merkleRoot: root,
|
|
34917
|
+
cek: toHex(cek),
|
|
34918
|
+
chunkCount,
|
|
34919
|
+
totalBytes: size
|
|
34920
|
+
};
|
|
34921
|
+
} finally {
|
|
34922
|
+
await fh.close();
|
|
34923
|
+
}
|
|
34924
|
+
}
|
|
34925
|
+
/**
|
|
34926
|
+
* Stream a Primitive Payload down to a file, verifying and decrypting one chunk
|
|
34927
|
+
* at a time (bounded memory). Every chunk is content-address-checked before
|
|
34928
|
+
* decryption, so a corrupt or substituted chunk throws.
|
|
34929
|
+
*/
|
|
34930
|
+
async function pullFile(root, outPath, opts) {
|
|
34931
|
+
const { createWriteStream } = await loadNodeFs("pullFile");
|
|
34932
|
+
const { rm } = await loadNodeFsPromises("pullFile");
|
|
34933
|
+
const manifest = await fetchManifest(opts, root);
|
|
34934
|
+
const computedRoot = await merkleRoot(manifest.chunks.map((c) => c.ciphertextHash));
|
|
34935
|
+
if (computedRoot !== root) throw new Error(`manifest does not match the requested content address (got ${computedRoot})`);
|
|
34936
|
+
const cek = fromHex(opts.cek);
|
|
34937
|
+
const objectId = fromHex(manifest.objectId);
|
|
34938
|
+
const out = createWriteStream(outPath);
|
|
34939
|
+
let onWriteError = () => {};
|
|
34940
|
+
const writeErrored = new Promise((_, reject) => {
|
|
34941
|
+
onWriteError = reject;
|
|
34942
|
+
});
|
|
34943
|
+
writeErrored.catch(() => {});
|
|
34944
|
+
out.on("error", (err) => onWriteError(err));
|
|
34945
|
+
try {
|
|
34946
|
+
let done = 0;
|
|
34947
|
+
for (const d of manifest.chunks) {
|
|
34948
|
+
const ciphertext = await getChunkBytes(opts, root, d);
|
|
34949
|
+
if (await contentHashHex(ciphertext) !== d.ciphertextHash) throw new Error(`chunk ${d.index} failed integrity check`);
|
|
34950
|
+
const plaintext = await decryptChunk(cek, objectId, d.index, ciphertext);
|
|
34951
|
+
if (!out.write(plaintext)) await Promise.race([new Promise((resolve) => out.once("drain", resolve)), writeErrored]);
|
|
34952
|
+
opts.onProgress?.("download", ++done, manifest.chunkCount);
|
|
34953
|
+
}
|
|
34954
|
+
await Promise.race([new Promise((resolve) => out.end(resolve)), writeErrored]);
|
|
34955
|
+
} catch (err) {
|
|
34956
|
+
out.destroy();
|
|
34957
|
+
await rm(outPath, { force: true });
|
|
34958
|
+
throw err;
|
|
34959
|
+
}
|
|
34960
|
+
return manifest;
|
|
34961
|
+
}
|
|
34962
|
+
//#endregion
|
|
34963
|
+
//#region ../sdk-node/dist/api-D4xf448r.js
|
|
34964
|
+
const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
|
|
34965
|
+
function createSseClient({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url, ...options }) {
|
|
34966
|
+
let lastEventId;
|
|
34967
|
+
const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
34968
|
+
const createStream = async function* () {
|
|
34969
|
+
let retryDelay = sseDefaultRetryDelay ?? 3e3;
|
|
34970
|
+
let attempt = 0;
|
|
34971
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
34972
|
+
while (true) {
|
|
34973
|
+
if (signal.aborted) break;
|
|
34974
|
+
attempt++;
|
|
34975
|
+
const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
|
|
34976
|
+
if (lastEventId !== void 0) headers.set("Last-Event-ID", lastEventId);
|
|
34977
|
+
try {
|
|
34978
|
+
const requestInit = {
|
|
34979
|
+
redirect: "follow",
|
|
34980
|
+
...options,
|
|
34981
|
+
body: options.serializedBody,
|
|
34982
|
+
headers,
|
|
34983
|
+
signal
|
|
34984
|
+
};
|
|
34985
|
+
let request = new Request(url, requestInit);
|
|
34986
|
+
if (onRequest) request = await onRequest(url, requestInit);
|
|
34987
|
+
const response = await (options.fetch ?? globalThis.fetch)(request);
|
|
34988
|
+
if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
|
|
34989
|
+
if (!response.body) throw new Error("No body in SSE response");
|
|
34990
|
+
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
34991
|
+
let buffer = "";
|
|
34992
|
+
const abortHandler = () => {
|
|
34993
|
+
try {
|
|
34994
|
+
reader.cancel();
|
|
34995
|
+
} catch {}
|
|
34996
|
+
};
|
|
34997
|
+
signal.addEventListener("abort", abortHandler);
|
|
34998
|
+
try {
|
|
34999
|
+
while (true) {
|
|
35000
|
+
const { done, value } = await reader.read();
|
|
35001
|
+
if (done) break;
|
|
35002
|
+
buffer += value;
|
|
35003
|
+
buffer = buffer.replace(/\r\n?/g, "\n");
|
|
35004
|
+
const chunks = buffer.split("\n\n");
|
|
35005
|
+
buffer = chunks.pop() ?? "";
|
|
35006
|
+
for (const chunk of chunks) {
|
|
35007
|
+
const lines = chunk.split("\n");
|
|
35008
|
+
const dataLines = [];
|
|
35009
|
+
let eventName;
|
|
35010
|
+
for (const line of lines) if (line.startsWith("data:")) dataLines.push(line.replace(/^data:\s*/, ""));
|
|
35011
|
+
else if (line.startsWith("event:")) eventName = line.replace(/^event:\s*/, "");
|
|
35012
|
+
else if (line.startsWith("id:")) lastEventId = line.replace(/^id:\s*/, "");
|
|
35013
|
+
else if (line.startsWith("retry:")) {
|
|
35014
|
+
const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
|
|
35015
|
+
if (!Number.isNaN(parsed)) retryDelay = parsed;
|
|
35016
|
+
}
|
|
35017
|
+
let data;
|
|
35018
|
+
let parsedJson = false;
|
|
35019
|
+
if (dataLines.length) {
|
|
35020
|
+
const rawData = dataLines.join("\n");
|
|
35021
|
+
try {
|
|
35022
|
+
data = JSON.parse(rawData);
|
|
35023
|
+
parsedJson = true;
|
|
35024
|
+
} catch {
|
|
35025
|
+
data = rawData;
|
|
35026
|
+
}
|
|
35027
|
+
}
|
|
35028
|
+
if (parsedJson) {
|
|
35029
|
+
if (responseValidator) await responseValidator(data);
|
|
35030
|
+
if (responseTransformer) data = await responseTransformer(data);
|
|
35031
|
+
}
|
|
35032
|
+
onSseEvent?.({
|
|
35033
|
+
data,
|
|
35034
|
+
event: eventName,
|
|
35035
|
+
id: lastEventId,
|
|
35036
|
+
retry: retryDelay
|
|
35037
|
+
});
|
|
35038
|
+
if (dataLines.length) yield data;
|
|
35039
|
+
}
|
|
35040
|
+
}
|
|
35041
|
+
} finally {
|
|
35042
|
+
signal.removeEventListener("abort", abortHandler);
|
|
35043
|
+
reader.releaseLock();
|
|
35044
|
+
}
|
|
35045
|
+
break;
|
|
35046
|
+
} catch (error) {
|
|
35047
|
+
onSseError?.(error);
|
|
35048
|
+
if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) break;
|
|
35049
|
+
await sleep(Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 3e4));
|
|
35050
|
+
}
|
|
35051
|
+
}
|
|
35052
|
+
};
|
|
35053
|
+
return { stream: createStream() };
|
|
35054
|
+
}
|
|
35055
|
+
const separatorArrayExplode = (style) => {
|
|
35056
|
+
switch (style) {
|
|
35057
|
+
case "label": return ".";
|
|
35058
|
+
case "matrix": return ";";
|
|
35059
|
+
case "simple": return ",";
|
|
35060
|
+
default: return "&";
|
|
35061
|
+
}
|
|
35062
|
+
};
|
|
35063
|
+
const separatorArrayNoExplode = (style) => {
|
|
35064
|
+
switch (style) {
|
|
35065
|
+
case "form": return ",";
|
|
35066
|
+
case "pipeDelimited": return "|";
|
|
35067
|
+
case "spaceDelimited": return "%20";
|
|
35068
|
+
default: return ",";
|
|
35069
|
+
}
|
|
35070
|
+
};
|
|
35071
|
+
const separatorObjectExplode = (style) => {
|
|
35072
|
+
switch (style) {
|
|
35073
|
+
case "label": return ".";
|
|
35074
|
+
case "matrix": return ";";
|
|
35075
|
+
case "simple": return ",";
|
|
35076
|
+
default: return "&";
|
|
35077
|
+
}
|
|
35078
|
+
};
|
|
35079
|
+
const serializeArrayParam = ({ allowReserved, explode, name, style, value }) => {
|
|
35080
|
+
if (!explode) {
|
|
35081
|
+
const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
|
|
35082
|
+
switch (style) {
|
|
35083
|
+
case "label": return `.${joinedValues}`;
|
|
35084
|
+
case "matrix": return `;${name}=${joinedValues}`;
|
|
35085
|
+
case "simple": return joinedValues;
|
|
35086
|
+
default: return `${name}=${joinedValues}`;
|
|
35087
|
+
}
|
|
35088
|
+
}
|
|
35089
|
+
const separator = separatorArrayExplode(style);
|
|
35090
|
+
const joinedValues = value.map((v) => {
|
|
35091
|
+
if (style === "label" || style === "simple") return allowReserved ? v : encodeURIComponent(v);
|
|
35092
|
+
return serializePrimitiveParam({
|
|
35093
|
+
allowReserved,
|
|
35094
|
+
name,
|
|
35095
|
+
value: v
|
|
35096
|
+
});
|
|
35097
|
+
}).join(separator);
|
|
35098
|
+
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
|
35099
|
+
};
|
|
35100
|
+
const serializePrimitiveParam = ({ allowReserved, name, value }) => {
|
|
35101
|
+
if (value === void 0 || value === null) return "";
|
|
35102
|
+
if (typeof value === "object") throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
|
|
35103
|
+
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
|
|
35104
|
+
};
|
|
35105
|
+
const serializeObjectParam = ({ allowReserved, explode, name, style, value, valueOnly }) => {
|
|
35106
|
+
if (value instanceof Date) return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
|
|
35107
|
+
if (style !== "deepObject" && !explode) {
|
|
35108
|
+
let values = [];
|
|
35109
|
+
Object.entries(value).forEach(([key, v]) => {
|
|
35110
|
+
values = [
|
|
35111
|
+
...values,
|
|
35112
|
+
key,
|
|
35113
|
+
allowReserved ? v : encodeURIComponent(v)
|
|
35114
|
+
];
|
|
35115
|
+
});
|
|
35116
|
+
const joinedValues = values.join(",");
|
|
35117
|
+
switch (style) {
|
|
35118
|
+
case "form": return `${name}=${joinedValues}`;
|
|
35119
|
+
case "label": return `.${joinedValues}`;
|
|
35120
|
+
case "matrix": return `;${name}=${joinedValues}`;
|
|
35121
|
+
default: return joinedValues;
|
|
35122
|
+
}
|
|
35123
|
+
}
|
|
35124
|
+
const separator = separatorObjectExplode(style);
|
|
35125
|
+
const joinedValues = Object.entries(value).map(([key, v]) => serializePrimitiveParam({
|
|
35126
|
+
allowReserved,
|
|
35127
|
+
name: style === "deepObject" ? `${name}[${key}]` : key,
|
|
35128
|
+
value: v
|
|
35129
|
+
})).join(separator);
|
|
35130
|
+
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
|
35131
|
+
};
|
|
35132
|
+
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
35133
|
+
const defaultPathSerializer = ({ path, url: _url }) => {
|
|
35134
|
+
let url = _url;
|
|
35135
|
+
const matches = _url.match(PATH_PARAM_RE);
|
|
35136
|
+
if (matches) for (const match of matches) {
|
|
35137
|
+
let explode = false;
|
|
35138
|
+
let name = match.substring(1, match.length - 1);
|
|
35139
|
+
let style = "simple";
|
|
35140
|
+
if (name.endsWith("*")) {
|
|
35141
|
+
explode = true;
|
|
35142
|
+
name = name.substring(0, name.length - 1);
|
|
35143
|
+
}
|
|
35144
|
+
if (name.startsWith(".")) {
|
|
35145
|
+
name = name.substring(1);
|
|
35146
|
+
style = "label";
|
|
35147
|
+
} else if (name.startsWith(";")) {
|
|
35148
|
+
name = name.substring(1);
|
|
35149
|
+
style = "matrix";
|
|
35150
|
+
}
|
|
35151
|
+
const value = path[name];
|
|
35152
|
+
if (value === void 0 || value === null) continue;
|
|
35153
|
+
if (Array.isArray(value)) {
|
|
35154
|
+
url = url.replace(match, serializeArrayParam({
|
|
35155
|
+
explode,
|
|
35156
|
+
name,
|
|
35157
|
+
style,
|
|
35158
|
+
value
|
|
35159
|
+
}));
|
|
35160
|
+
continue;
|
|
35161
|
+
}
|
|
35162
|
+
if (typeof value === "object") {
|
|
35163
|
+
url = url.replace(match, serializeObjectParam({
|
|
35164
|
+
explode,
|
|
35165
|
+
name,
|
|
35166
|
+
style,
|
|
35167
|
+
value,
|
|
35168
|
+
valueOnly: true
|
|
35169
|
+
}));
|
|
35170
|
+
continue;
|
|
35171
|
+
}
|
|
35172
|
+
if (style === "matrix") {
|
|
35173
|
+
url = url.replace(match, `;${serializePrimitiveParam({
|
|
35174
|
+
name,
|
|
35175
|
+
value
|
|
35176
|
+
})}`);
|
|
35177
|
+
continue;
|
|
35178
|
+
}
|
|
35179
|
+
const replaceValue = encodeURIComponent(style === "label" ? `.${value}` : value);
|
|
35180
|
+
url = url.replace(match, replaceValue);
|
|
35181
|
+
}
|
|
35182
|
+
return url;
|
|
35183
|
+
};
|
|
35184
|
+
const getUrl = ({ baseUrl, path, query, querySerializer, url: _url }) => {
|
|
35185
|
+
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
|
|
35186
|
+
let url = (baseUrl ?? "") + pathUrl;
|
|
35187
|
+
if (path) url = defaultPathSerializer({
|
|
35188
|
+
path,
|
|
35189
|
+
url
|
|
35190
|
+
});
|
|
35191
|
+
let search = query ? querySerializer(query) : "";
|
|
35192
|
+
if (search.startsWith("?")) search = search.substring(1);
|
|
35193
|
+
if (search) url += `?${search}`;
|
|
35194
|
+
return url;
|
|
35195
|
+
};
|
|
35196
|
+
function getValidRequestBody(options) {
|
|
35197
|
+
const hasBody = options.body !== void 0;
|
|
35198
|
+
if (hasBody && options.bodySerializer) {
|
|
35199
|
+
if ("serializedBody" in options) return options.serializedBody !== void 0 && options.serializedBody !== "" ? options.serializedBody : null;
|
|
35200
|
+
return options.body !== "" ? options.body : null;
|
|
35201
|
+
}
|
|
35202
|
+
if (hasBody) return options.body;
|
|
35203
|
+
}
|
|
35204
|
+
const getAuthToken = async (auth, callback) => {
|
|
35205
|
+
const token = typeof callback === "function" ? await callback(auth) : callback;
|
|
35206
|
+
if (!token) return;
|
|
35207
|
+
if (auth.scheme === "bearer") return `Bearer ${token}`;
|
|
35208
|
+
if (auth.scheme === "basic") return `Basic ${btoa(token)}`;
|
|
35209
|
+
return token;
|
|
35210
|
+
};
|
|
35211
|
+
const createQuerySerializer = ({ parameters = {}, ...args } = {}) => {
|
|
35212
|
+
const querySerializer = (queryParams) => {
|
|
35213
|
+
const search = [];
|
|
35214
|
+
if (queryParams && typeof queryParams === "object") for (const name in queryParams) {
|
|
35215
|
+
const value = queryParams[name];
|
|
35216
|
+
if (value === void 0 || value === null) continue;
|
|
35217
|
+
const options = parameters[name] || args;
|
|
35218
|
+
if (Array.isArray(value)) {
|
|
35219
|
+
const serializedArray = serializeArrayParam({
|
|
35220
|
+
allowReserved: options.allowReserved,
|
|
35221
|
+
explode: true,
|
|
35222
|
+
name,
|
|
35223
|
+
style: "form",
|
|
35224
|
+
value,
|
|
35225
|
+
...options.array
|
|
35226
|
+
});
|
|
35227
|
+
if (serializedArray) search.push(serializedArray);
|
|
35228
|
+
} else if (typeof value === "object") {
|
|
35229
|
+
const serializedObject = serializeObjectParam({
|
|
35230
|
+
allowReserved: options.allowReserved,
|
|
35231
|
+
explode: true,
|
|
35232
|
+
name,
|
|
35233
|
+
style: "deepObject",
|
|
35234
|
+
value,
|
|
35235
|
+
...options.object
|
|
35236
|
+
});
|
|
35237
|
+
if (serializedObject) search.push(serializedObject);
|
|
35238
|
+
} else {
|
|
35239
|
+
const serializedPrimitive = serializePrimitiveParam({
|
|
35240
|
+
allowReserved: options.allowReserved,
|
|
35241
|
+
name,
|
|
35242
|
+
value
|
|
35243
|
+
});
|
|
35244
|
+
if (serializedPrimitive) search.push(serializedPrimitive);
|
|
35245
|
+
}
|
|
35246
|
+
}
|
|
35247
|
+
return search.join("&");
|
|
35248
|
+
};
|
|
35249
|
+
return querySerializer;
|
|
35250
|
+
};
|
|
35251
|
+
/**
|
|
35252
|
+
* Infers parseAs value from provided Content-Type header.
|
|
35253
|
+
*/
|
|
35254
|
+
const getParseAs = (contentType) => {
|
|
35255
|
+
if (!contentType) return "stream";
|
|
35256
|
+
const cleanContent = contentType.split(";")[0]?.trim();
|
|
35257
|
+
if (!cleanContent) return;
|
|
35258
|
+
if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) return "json";
|
|
35259
|
+
if (cleanContent === "multipart/form-data") return "formData";
|
|
35260
|
+
if ([
|
|
35261
|
+
"application/",
|
|
35262
|
+
"audio/",
|
|
35263
|
+
"image/",
|
|
35264
|
+
"video/"
|
|
35265
|
+
].some((type) => cleanContent.startsWith(type))) return "blob";
|
|
35266
|
+
if (cleanContent.startsWith("text/")) return "text";
|
|
35267
|
+
};
|
|
35268
|
+
const checkForExistence = (options, name) => {
|
|
35269
|
+
if (!name) return false;
|
|
35270
|
+
if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) return true;
|
|
35271
|
+
return false;
|
|
35272
|
+
};
|
|
35273
|
+
async function setAuthParams(options) {
|
|
35274
|
+
for (const auth of options.security ?? []) {
|
|
35275
|
+
if (checkForExistence(options, auth.name)) continue;
|
|
35276
|
+
const token = await getAuthToken(auth, options.auth);
|
|
35277
|
+
if (!token) continue;
|
|
35278
|
+
const name = auth.name ?? "Authorization";
|
|
35279
|
+
switch (auth.in) {
|
|
35280
|
+
case "query":
|
|
35281
|
+
if (!options.query) options.query = {};
|
|
35282
|
+
options.query[name] = token;
|
|
35283
|
+
break;
|
|
35284
|
+
case "cookie":
|
|
35285
|
+
options.headers.append("Cookie", `${name}=${token}`);
|
|
35286
|
+
break;
|
|
35287
|
+
default:
|
|
35288
|
+
options.headers.set(name, token);
|
|
35289
|
+
break;
|
|
35290
|
+
}
|
|
35291
|
+
}
|
|
35292
|
+
}
|
|
35293
|
+
const buildUrl = (options) => getUrl({
|
|
35294
|
+
baseUrl: options.baseUrl,
|
|
35295
|
+
path: options.path,
|
|
35296
|
+
query: options.query,
|
|
35297
|
+
querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
|
|
35298
|
+
url: options.url
|
|
35299
|
+
});
|
|
35300
|
+
const mergeConfigs = (a, b) => {
|
|
35301
|
+
const config = {
|
|
35302
|
+
...a,
|
|
35303
|
+
...b
|
|
35304
|
+
};
|
|
35305
|
+
if (config.baseUrl?.endsWith("/")) config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
|
|
35306
|
+
config.headers = mergeHeaders(a.headers, b.headers);
|
|
35307
|
+
return config;
|
|
35308
|
+
};
|
|
35309
|
+
const headersEntries = (headers) => {
|
|
35310
|
+
const entries = [];
|
|
35311
|
+
headers.forEach((value, key) => {
|
|
35312
|
+
entries.push([key, value]);
|
|
35313
|
+
});
|
|
35314
|
+
return entries;
|
|
35315
|
+
};
|
|
35316
|
+
const mergeHeaders = (...headers) => {
|
|
35317
|
+
const mergedHeaders = new Headers();
|
|
35318
|
+
for (const header of headers) {
|
|
35319
|
+
if (!header) continue;
|
|
35320
|
+
const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
|
|
35321
|
+
for (const [key, value] of iterator) if (value === null) mergedHeaders.delete(key);
|
|
35322
|
+
else if (Array.isArray(value)) for (const v of value) mergedHeaders.append(key, v);
|
|
35323
|
+
else if (value !== void 0) mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : value);
|
|
35324
|
+
}
|
|
35325
|
+
return mergedHeaders;
|
|
35326
|
+
};
|
|
35327
|
+
var Interceptors = class {
|
|
35328
|
+
fns = [];
|
|
35329
|
+
clear() {
|
|
35330
|
+
this.fns = [];
|
|
35331
|
+
}
|
|
35332
|
+
eject(id) {
|
|
35333
|
+
const index = this.getInterceptorIndex(id);
|
|
35334
|
+
if (this.fns[index]) this.fns[index] = null;
|
|
35335
|
+
}
|
|
35336
|
+
exists(id) {
|
|
35337
|
+
const index = this.getInterceptorIndex(id);
|
|
35338
|
+
return Boolean(this.fns[index]);
|
|
35339
|
+
}
|
|
35340
|
+
getInterceptorIndex(id) {
|
|
35341
|
+
if (typeof id === "number") return this.fns[id] ? id : -1;
|
|
35342
|
+
return this.fns.indexOf(id);
|
|
35343
|
+
}
|
|
35344
|
+
update(id, fn) {
|
|
35345
|
+
const index = this.getInterceptorIndex(id);
|
|
35346
|
+
if (this.fns[index]) {
|
|
35347
|
+
this.fns[index] = fn;
|
|
35348
|
+
return id;
|
|
35349
|
+
}
|
|
35350
|
+
return false;
|
|
35351
|
+
}
|
|
35352
|
+
use(fn) {
|
|
35353
|
+
this.fns.push(fn);
|
|
35354
|
+
return this.fns.length - 1;
|
|
35355
|
+
}
|
|
35356
|
+
};
|
|
35357
|
+
const createInterceptors = () => ({
|
|
35358
|
+
error: new Interceptors(),
|
|
35359
|
+
request: new Interceptors(),
|
|
35360
|
+
response: new Interceptors()
|
|
35361
|
+
});
|
|
35362
|
+
const defaultQuerySerializer = createQuerySerializer({
|
|
35363
|
+
allowReserved: false,
|
|
35364
|
+
array: {
|
|
35365
|
+
explode: true,
|
|
35366
|
+
style: "form"
|
|
35367
|
+
},
|
|
35368
|
+
object: {
|
|
35369
|
+
explode: true,
|
|
35370
|
+
style: "deepObject"
|
|
35371
|
+
}
|
|
35372
|
+
});
|
|
35373
|
+
const defaultHeaders = { "Content-Type": "application/json" };
|
|
35374
|
+
const createConfig = (override = {}) => ({
|
|
35375
|
+
...jsonBodySerializer,
|
|
35376
|
+
headers: defaultHeaders,
|
|
35377
|
+
parseAs: "auto",
|
|
35378
|
+
querySerializer: defaultQuerySerializer,
|
|
35379
|
+
...override
|
|
35380
|
+
});
|
|
35381
|
+
const createClient = (config = {}) => {
|
|
35382
|
+
let _config = mergeConfigs(createConfig(), config);
|
|
35383
|
+
const getConfig = () => ({ ..._config });
|
|
35384
|
+
const setConfig = (config) => {
|
|
35385
|
+
_config = mergeConfigs(_config, config);
|
|
35386
|
+
return getConfig();
|
|
35387
|
+
};
|
|
35388
|
+
const interceptors = createInterceptors();
|
|
35389
|
+
const beforeRequest = async (options) => {
|
|
35390
|
+
const opts = {
|
|
35391
|
+
..._config,
|
|
35392
|
+
...options,
|
|
35393
|
+
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
|
35394
|
+
headers: mergeHeaders(_config.headers, options.headers),
|
|
35395
|
+
serializedBody: void 0
|
|
35396
|
+
};
|
|
35397
|
+
if (opts.security) await setAuthParams(opts);
|
|
35398
|
+
if (opts.requestValidator) await opts.requestValidator(opts);
|
|
35399
|
+
if (opts.body !== void 0 && opts.bodySerializer) opts.serializedBody = opts.bodySerializer(opts.body);
|
|
35400
|
+
if (opts.body === void 0 || opts.serializedBody === "") opts.headers.delete("Content-Type");
|
|
35401
|
+
const resolvedOpts = opts;
|
|
35402
|
+
return {
|
|
35403
|
+
opts: resolvedOpts,
|
|
35404
|
+
url: buildUrl(resolvedOpts)
|
|
35405
|
+
};
|
|
35406
|
+
};
|
|
35407
|
+
const request = async (options) => {
|
|
35408
|
+
const throwOnError = options.throwOnError ?? _config.throwOnError;
|
|
35409
|
+
const responseStyle = options.responseStyle ?? _config.responseStyle;
|
|
35410
|
+
let request;
|
|
35411
|
+
let response;
|
|
35412
|
+
try {
|
|
35413
|
+
const { opts, url } = await beforeRequest(options);
|
|
35414
|
+
const requestInit = {
|
|
35415
|
+
redirect: "follow",
|
|
35416
|
+
...opts,
|
|
35417
|
+
body: getValidRequestBody(opts)
|
|
35418
|
+
};
|
|
35419
|
+
request = new Request(url, requestInit);
|
|
35420
|
+
for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
|
|
35421
|
+
const _fetch = opts.fetch;
|
|
35422
|
+
response = await _fetch(request);
|
|
35423
|
+
for (const fn of interceptors.response.fns) if (fn) response = await fn(response, request, opts);
|
|
35424
|
+
const result = {
|
|
35425
|
+
request,
|
|
35426
|
+
response
|
|
35427
|
+
};
|
|
35428
|
+
if (response.ok) {
|
|
35429
|
+
const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
|
|
35430
|
+
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
|
35431
|
+
let emptyData;
|
|
35432
|
+
switch (parseAs) {
|
|
35433
|
+
case "arrayBuffer":
|
|
35434
|
+
case "blob":
|
|
35435
|
+
case "text":
|
|
35436
|
+
emptyData = await response[parseAs]();
|
|
35437
|
+
break;
|
|
35438
|
+
case "formData":
|
|
35439
|
+
emptyData = new FormData();
|
|
35440
|
+
break;
|
|
35441
|
+
case "stream":
|
|
35442
|
+
emptyData = response.body;
|
|
35443
|
+
break;
|
|
35444
|
+
default:
|
|
35445
|
+
emptyData = {};
|
|
35446
|
+
break;
|
|
35447
|
+
}
|
|
35448
|
+
return opts.responseStyle === "data" ? emptyData : {
|
|
35449
|
+
data: emptyData,
|
|
35450
|
+
...result
|
|
35451
|
+
};
|
|
35452
|
+
}
|
|
35453
|
+
let data;
|
|
35454
|
+
switch (parseAs) {
|
|
35455
|
+
case "arrayBuffer":
|
|
35456
|
+
case "blob":
|
|
35457
|
+
case "formData":
|
|
35458
|
+
case "text":
|
|
35459
|
+
data = await response[parseAs]();
|
|
35460
|
+
break;
|
|
35461
|
+
case "json": {
|
|
35462
|
+
const text = await response.text();
|
|
35463
|
+
data = text ? JSON.parse(text) : {};
|
|
35464
|
+
break;
|
|
35465
|
+
}
|
|
35466
|
+
case "stream": return opts.responseStyle === "data" ? response.body : {
|
|
35467
|
+
data: response.body,
|
|
35468
|
+
...result
|
|
35469
|
+
};
|
|
35470
|
+
}
|
|
35471
|
+
if (parseAs === "json") {
|
|
35472
|
+
if (opts.responseValidator) await opts.responseValidator(data);
|
|
35473
|
+
if (opts.responseTransformer) data = await opts.responseTransformer(data);
|
|
35474
|
+
}
|
|
35475
|
+
return opts.responseStyle === "data" ? data : {
|
|
35476
|
+
data,
|
|
35477
|
+
...result
|
|
35478
|
+
};
|
|
35479
|
+
}
|
|
35480
|
+
const textError = await response.text();
|
|
35481
|
+
let jsonError;
|
|
35482
|
+
try {
|
|
35483
|
+
jsonError = JSON.parse(textError);
|
|
35484
|
+
} catch {}
|
|
35485
|
+
throw jsonError ?? textError;
|
|
35486
|
+
} catch (error) {
|
|
35487
|
+
let finalError = error;
|
|
35488
|
+
for (const fn of interceptors.error.fns) if (fn) finalError = await fn(finalError, response, request, options);
|
|
35489
|
+
finalError = finalError || {};
|
|
35490
|
+
if (throwOnError) throw finalError;
|
|
35491
|
+
return responseStyle === "data" ? void 0 : {
|
|
35492
|
+
error: finalError,
|
|
35493
|
+
request,
|
|
35494
|
+
response
|
|
35495
|
+
};
|
|
35496
|
+
}
|
|
35497
|
+
};
|
|
35498
|
+
const makeMethodFn = (method) => (options) => request({
|
|
35499
|
+
...options,
|
|
35500
|
+
method
|
|
35501
|
+
});
|
|
35502
|
+
const makeSseFn = (method) => async (options) => {
|
|
35503
|
+
const { opts, url } = await beforeRequest(options);
|
|
35504
|
+
return createSseClient({
|
|
35505
|
+
...opts,
|
|
35506
|
+
body: opts.body,
|
|
35507
|
+
method,
|
|
35508
|
+
onRequest: async (url, init) => {
|
|
35509
|
+
let request = new Request(url, init);
|
|
35510
|
+
for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
|
|
35511
|
+
return request;
|
|
35512
|
+
},
|
|
35513
|
+
serializedBody: getValidRequestBody(opts),
|
|
35514
|
+
url
|
|
35515
|
+
});
|
|
35516
|
+
};
|
|
35517
|
+
const _buildUrl = (options) => buildUrl({
|
|
35518
|
+
..._config,
|
|
35519
|
+
...options
|
|
35520
|
+
});
|
|
35521
|
+
return {
|
|
35522
|
+
buildUrl: _buildUrl,
|
|
35523
|
+
connect: makeMethodFn("CONNECT"),
|
|
35524
|
+
delete: makeMethodFn("DELETE"),
|
|
35525
|
+
get: makeMethodFn("GET"),
|
|
35526
|
+
getConfig,
|
|
35527
|
+
head: makeMethodFn("HEAD"),
|
|
35528
|
+
interceptors,
|
|
35529
|
+
options: makeMethodFn("OPTIONS"),
|
|
35530
|
+
patch: makeMethodFn("PATCH"),
|
|
35531
|
+
post: makeMethodFn("POST"),
|
|
35532
|
+
put: makeMethodFn("PUT"),
|
|
35533
|
+
request,
|
|
35534
|
+
setConfig,
|
|
35535
|
+
sse: {
|
|
35536
|
+
connect: makeSseFn("CONNECT"),
|
|
35537
|
+
delete: makeSseFn("DELETE"),
|
|
35538
|
+
get: makeSseFn("GET"),
|
|
35539
|
+
head: makeSseFn("HEAD"),
|
|
35540
|
+
options: makeSseFn("OPTIONS"),
|
|
35541
|
+
patch: makeSseFn("PATCH"),
|
|
35542
|
+
post: makeSseFn("POST"),
|
|
35543
|
+
put: makeSseFn("PUT"),
|
|
35544
|
+
trace: makeSseFn("TRACE")
|
|
35545
|
+
},
|
|
35546
|
+
trace: makeMethodFn("TRACE")
|
|
35547
|
+
};
|
|
35548
|
+
};
|
|
35549
|
+
const client$1 = createClient(createConfig({ baseUrl: "https://api.primitive.dev/v1" }));
|
|
35550
|
+
/**
|
|
35551
|
+
* Get account info
|
|
35552
|
+
*/
|
|
35553
|
+
const getAccount = (options) => (options?.client ?? client$1).get({
|
|
35554
|
+
security: [{
|
|
35555
|
+
scheme: "bearer",
|
|
35556
|
+
type: "http"
|
|
35557
|
+
}],
|
|
35558
|
+
url: "/account",
|
|
35559
|
+
...options
|
|
35560
|
+
});
|
|
35561
|
+
/**
|
|
35562
|
+
* Receive a pending webhook event without a public endpoint
|
|
35563
|
+
*
|
|
35564
|
+
* Wait up to 30 seconds for one existing event. The body string preserves the exact webhook serialization; headers and canonical occurrence/type metadata travel alongside it. A fixed lease protects the attempt while a short handler accepts input. Retry may redeliver an occurrence: deduplicate event_id. Empty delivery means no offer now, not proof all input was delivered; inspect backlog and persistent gap_count/last_gap_reason. Queue retention is 24 hours and does not extend source-content retention. Disconnecting preserves pending work. Same named destination shares consumption; different destinations get independent copies.
|
|
35565
|
+
*/
|
|
35566
|
+
const pullWebhookEvent = (options) => (options.client ?? client$1).post({
|
|
35567
|
+
security: [{
|
|
35568
|
+
scheme: "bearer",
|
|
35569
|
+
type: "http"
|
|
35570
|
+
}],
|
|
35571
|
+
url: "/endpoints/{id}/pull",
|
|
35572
|
+
...options,
|
|
35573
|
+
headers: {
|
|
35574
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
35575
|
+
...options.headers
|
|
35576
|
+
}
|
|
35577
|
+
});
|
|
35578
|
+
/**
|
|
35579
|
+
* Report the outcome of a local webhook delivery attempt
|
|
35580
|
+
*
|
|
35581
|
+
* Report normalized handling evidence for the queue, attempt and lease. Required JSON fields: queue_id, delivery_id, lease_token, mode, duration_ms. For mode=exec include exit_code; stdout includes write_succeeded; http includes status_code and optional transport_error, error_code, confirmed. Supply this discriminated request with --raw-body in the generated CLI. The server classifies success and retry. Duplicate identical completion is idempotent; a stale lease cannot finish a newer attempt. Exec success means durable input acceptance, not completion of agent reasoning. Only successful HTTP handling with recognized confirmation evidence can confirm content discard. Never send arbitrary handler output or HTTP response bodies.
|
|
35582
|
+
*/
|
|
35583
|
+
const completeWebhookEvent = (options) => (options.client ?? client$1).post({
|
|
35584
|
+
security: [{
|
|
35585
|
+
scheme: "bearer",
|
|
35586
|
+
type: "http"
|
|
35587
|
+
}],
|
|
35588
|
+
url: "/endpoints/{id}/complete",
|
|
35589
|
+
...options,
|
|
35590
|
+
headers: {
|
|
35591
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
35592
|
+
...options.headers
|
|
35593
|
+
}
|
|
35594
|
+
});
|
|
35595
|
+
var EventReceiverError = class extends Error {
|
|
35596
|
+
constructor(message, code, status = 0, retryAfterMs = 0) {
|
|
35597
|
+
super(message);
|
|
35598
|
+
this.code = code;
|
|
35599
|
+
this.status = status;
|
|
35600
|
+
this.retryAfterMs = retryAfterMs;
|
|
35601
|
+
this.name = "EventReceiverError";
|
|
35602
|
+
}
|
|
35603
|
+
};
|
|
35604
|
+
function record(value) {
|
|
35605
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
35606
|
+
}
|
|
35607
|
+
function validateOffer(value) {
|
|
35608
|
+
if (!record(value) || !Number.isSafeInteger(value.backlog) || Number(value.backlog) < 0 || !Number.isSafeInteger(value.gap_count) || Number(value.gap_count) < 0 || value.last_gap_reason !== null && typeof value.last_gap_reason !== "string" || value.retention_seconds !== 86400 || value.handler_timeout_seconds !== 30) throw new EventReceiverError("Invalid event status response", "invalid_response");
|
|
35609
|
+
const d = value.delivery;
|
|
35610
|
+
if (d !== null && (!record(d) || ![
|
|
35611
|
+
"queue_id",
|
|
35612
|
+
"event_id",
|
|
35613
|
+
"delivery_id",
|
|
35614
|
+
"lease_token",
|
|
35615
|
+
"event_type",
|
|
35616
|
+
"body",
|
|
35617
|
+
"lease_expires_at"
|
|
35618
|
+
].every((key) => typeof d[key] === "string") || !Number.isFinite(Date.parse(String(d.lease_expires_at))) || !record(d.headers) || !Object.values(d.headers).every((header) => typeof header === "string"))) throw new EventReceiverError("Invalid event delivery response", "invalid_response");
|
|
35619
|
+
return value;
|
|
35620
|
+
}
|
|
35621
|
+
function retryAfter(value) {
|
|
35622
|
+
if (!value) return 0;
|
|
35623
|
+
const ms = /^\d+(\.\d+)?$/.test(value) ? Number(value) * 1e3 : Date.parse(value) - Date.now();
|
|
35624
|
+
return Number.isFinite(ms) ? Math.max(0, ms) : 0;
|
|
35625
|
+
}
|
|
35626
|
+
function unwrap(result) {
|
|
35627
|
+
if (result.data !== void 0 && !result.error) return result.data;
|
|
35628
|
+
const code = record(result.error) && record(result.error.error) && typeof result.error.error.code === "string" ? result.error.error.code : "request_failed";
|
|
35629
|
+
throw new EventReceiverError(`Event request failed (${result.response?.status ?? 0}, ${code})`, code, result.response?.status ?? 0, retryAfter(result.response?.headers.get("retry-after") ?? null));
|
|
35630
|
+
}
|
|
35631
|
+
/** Shared by the SDK and CLI. Completion retries preserve the original evidence. */
|
|
35632
|
+
var EventConnection = class {
|
|
35633
|
+
socket;
|
|
35634
|
+
pending;
|
|
35635
|
+
accountId;
|
|
35636
|
+
touch;
|
|
35637
|
+
constructor(client, endpointId, options = {}) {
|
|
35638
|
+
this.client = client;
|
|
35639
|
+
this.endpointId = endpointId;
|
|
35640
|
+
this.options = options;
|
|
35641
|
+
}
|
|
35642
|
+
close() {
|
|
35643
|
+
const socket = this.socket;
|
|
35644
|
+
this.socket = void 0;
|
|
35645
|
+
this.pending?.reject(new EventReceiverError("Event connection closed", "disconnected"));
|
|
35646
|
+
this.pending = void 0;
|
|
35647
|
+
socket?.close();
|
|
35648
|
+
}
|
|
35649
|
+
async open(signal) {
|
|
35650
|
+
if (this.options.transport === "poll" || this.socket?.readyState === 1) return;
|
|
35651
|
+
const account = unwrap(await getAccount({
|
|
35652
|
+
client: this.client,
|
|
35653
|
+
responseStyle: "fields",
|
|
35654
|
+
throwOnError: false,
|
|
35655
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
|
|
35656
|
+
}));
|
|
35657
|
+
if (!account.data?.id || this.accountId && account.data.id !== this.accountId) throw new EventReceiverError("The authenticated account changed; restart the receiver", "identity_changed", 403);
|
|
35658
|
+
this.accountId = account.data.id;
|
|
35659
|
+
const config = this.client.getConfig();
|
|
35660
|
+
const auth = typeof config.auth === "function" ? await config.auth({
|
|
35661
|
+
type: "http",
|
|
35662
|
+
scheme: "bearer"
|
|
35663
|
+
}) : config.auth;
|
|
35664
|
+
const authorization = new Headers(config.headers).get("authorization");
|
|
35665
|
+
const token = typeof auth === "string" ? auth.replace(/^Bearer /i, "") : authorization?.replace(/^Bearer /i, "");
|
|
35666
|
+
if (!token) throw new EventReceiverError("Event receiver requires bearer credentials", "unauthorized", 401);
|
|
35667
|
+
const url = new URL(`${config.baseUrl?.replace(/\/+$/, "")}/endpoints/${encodeURIComponent(this.endpointId)}/stream`);
|
|
35668
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && [
|
|
35669
|
+
"localhost",
|
|
35670
|
+
"127.0.0.1",
|
|
35671
|
+
"[::1]"
|
|
35672
|
+
].includes(url.hostname))) throw new EventReceiverError("Event connections require HTTPS (except loopback development)", "unsupported");
|
|
35673
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
35674
|
+
const socket = (this.options.webSocketFactory ?? ((address, protocol) => new WebSocket(address, protocol)))(url.href, "primitive.events.v1");
|
|
35675
|
+
this.socket = socket;
|
|
35676
|
+
socket.addEventListener("message", (message) => {
|
|
35677
|
+
if (this.socket !== socket) return;
|
|
35678
|
+
let frame;
|
|
35679
|
+
try {
|
|
35680
|
+
frame = JSON.parse(String(message.data));
|
|
35681
|
+
} catch {
|
|
35682
|
+
this.fail(new EventReceiverError("Invalid event frame", "invalid_response"));
|
|
35683
|
+
return;
|
|
35684
|
+
}
|
|
35685
|
+
this.touch?.();
|
|
35686
|
+
if (!record(frame)) {
|
|
35687
|
+
this.fail(new EventReceiverError("Invalid event frame", "invalid_response"));
|
|
35688
|
+
return;
|
|
35689
|
+
}
|
|
35690
|
+
if (frame.type === "ping") {
|
|
35691
|
+
socket.send(JSON.stringify({ type: "pong" }));
|
|
35692
|
+
return;
|
|
35693
|
+
}
|
|
35694
|
+
if (frame.type === "status") {
|
|
35695
|
+
try {
|
|
35696
|
+
this.options.onStatus?.(validateOffer(frame.data));
|
|
35697
|
+
} catch (error) {
|
|
35698
|
+
this.fail(error);
|
|
35699
|
+
}
|
|
35700
|
+
return;
|
|
35701
|
+
}
|
|
35702
|
+
if (frame.type === "error") {
|
|
35703
|
+
this.fail(new EventReceiverError("Event stream request failed", typeof frame.code === "string" ? frame.code : "request_failed", typeof frame.status === "number" ? frame.status : 0, retryAfter(typeof frame.retry_after === "string" ? frame.retry_after : null)));
|
|
35704
|
+
return;
|
|
35705
|
+
}
|
|
35706
|
+
this.pending?.resolve(frame);
|
|
35707
|
+
});
|
|
35708
|
+
socket.addEventListener("close", () => {
|
|
35709
|
+
if (this.socket === socket) this.fail(new EventReceiverError("Event stream disconnected", "disconnected"));
|
|
35710
|
+
});
|
|
35711
|
+
socket.addEventListener("error", () => {
|
|
35712
|
+
if (this.socket === socket) this.fail(new EventReceiverError("Event stream connection failed", "disconnected"));
|
|
35713
|
+
});
|
|
35714
|
+
const ready = await this.exchange(signal, void 0, () => {
|
|
35715
|
+
socket.addEventListener("open", () => socket.send(JSON.stringify({
|
|
35716
|
+
type: "authenticate",
|
|
35717
|
+
token
|
|
35718
|
+
})), { once: true });
|
|
35719
|
+
}, 15e3);
|
|
35720
|
+
if (!record(ready) || ready.type !== "ready" || ready.protocol !== "primitive.events.v1") {
|
|
35721
|
+
this.close();
|
|
35722
|
+
throw new EventReceiverError("Unsupported event stream protocol", "unsupported");
|
|
35723
|
+
}
|
|
35724
|
+
}
|
|
35725
|
+
fail(error) {
|
|
35726
|
+
this.pending?.reject(error);
|
|
35727
|
+
this.pending = void 0;
|
|
35728
|
+
this.close();
|
|
35729
|
+
}
|
|
35730
|
+
async exchange(signal, frame, start, timeoutMs = 6e4) {
|
|
35731
|
+
signal.throwIfAborted();
|
|
35732
|
+
if (this.pending) throw new EventReceiverError("Concurrent event operation", "busy", 409);
|
|
35733
|
+
let cleanup = () => {};
|
|
35734
|
+
try {
|
|
35735
|
+
return await new Promise((resolve, reject) => {
|
|
35736
|
+
const abort = () => {
|
|
35737
|
+
reject(signal.reason);
|
|
35738
|
+
this.close();
|
|
35739
|
+
};
|
|
35740
|
+
let timer;
|
|
35741
|
+
this.touch = () => {
|
|
35742
|
+
clearTimeout(timer);
|
|
35743
|
+
timer = setTimeout(() => this.fail(new EventReceiverError("Event stream timed out", "disconnected")), timeoutMs);
|
|
35744
|
+
};
|
|
35745
|
+
this.touch();
|
|
35746
|
+
cleanup = () => {
|
|
35747
|
+
this.touch = void 0;
|
|
35748
|
+
clearTimeout(timer);
|
|
35749
|
+
signal.removeEventListener("abort", abort);
|
|
35750
|
+
this.pending = void 0;
|
|
35751
|
+
};
|
|
35752
|
+
this.pending = {
|
|
35753
|
+
resolve,
|
|
35754
|
+
reject
|
|
35755
|
+
};
|
|
35756
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
35757
|
+
if (frame) this.socket?.send(JSON.stringify(frame));
|
|
35758
|
+
start?.();
|
|
35759
|
+
});
|
|
35760
|
+
} finally {
|
|
35761
|
+
cleanup();
|
|
35762
|
+
}
|
|
35763
|
+
}
|
|
35764
|
+
async receive(signal) {
|
|
35765
|
+
if (this.options.transport === "poll") return validateOffer(unwrap(await pullWebhookEvent({
|
|
35766
|
+
client: this.client,
|
|
35767
|
+
path: { id: this.endpointId },
|
|
35768
|
+
body: { wait_seconds: 25 },
|
|
35769
|
+
responseStyle: "fields",
|
|
35770
|
+
throwOnError: false,
|
|
35771
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(45e3)])
|
|
35772
|
+
})).data);
|
|
35773
|
+
await this.open(signal);
|
|
35774
|
+
const frame = await this.exchange(signal, { type: "receive" }, void 0, 9e4);
|
|
35775
|
+
if (!record(frame) || frame.type !== "event") throw new EventReceiverError("Invalid event frame", "invalid_response");
|
|
35776
|
+
return validateOffer(frame.data);
|
|
35777
|
+
}
|
|
35778
|
+
async complete(body, signal) {
|
|
35779
|
+
let receipt;
|
|
35780
|
+
if (this.options.transport === "poll") receipt = unwrap(await completeWebhookEvent({
|
|
35781
|
+
client: this.client,
|
|
35782
|
+
path: { id: this.endpointId },
|
|
35783
|
+
body,
|
|
35784
|
+
responseStyle: "fields",
|
|
35785
|
+
throwOnError: false,
|
|
35786
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
|
|
35787
|
+
})).data;
|
|
35788
|
+
else {
|
|
35789
|
+
await this.open(signal);
|
|
35790
|
+
const frame = await this.exchange(signal, {
|
|
35791
|
+
type: "complete",
|
|
35792
|
+
body
|
|
35793
|
+
}, void 0, 15e3);
|
|
35794
|
+
if (!record(frame) || frame.type !== "receipt") throw new EventReceiverError("Invalid event receipt", "invalid_response");
|
|
35795
|
+
receipt = frame.data;
|
|
35796
|
+
}
|
|
35797
|
+
if (!record(receipt) || !["completed", "already_completed"].includes(String(receipt.result))) throw new EventReceiverError("Invalid event receipt", "invalid_response");
|
|
35798
|
+
}
|
|
35799
|
+
};
|
|
35800
|
+
//#endregion
|
|
34401
35801
|
//#region src/oclif/listen-state.ts
|
|
34402
35802
|
var ListenStateError = class extends Error {};
|
|
34403
35803
|
function normalizeListenOrigin(value) {
|
|
@@ -34683,6 +36083,7 @@ async function runListen(options) {
|
|
|
34683
36083
|
let verified = false;
|
|
34684
36084
|
let confirmed = 0;
|
|
34685
36085
|
let release;
|
|
36086
|
+
let stream;
|
|
34686
36087
|
async function retry(operation) {
|
|
34687
36088
|
for (let attempt = 0;; attempt++) {
|
|
34688
36089
|
signal.throwIfAborted();
|
|
@@ -34693,9 +36094,14 @@ async function runListen(options) {
|
|
|
34693
36094
|
error = failure(result, now());
|
|
34694
36095
|
} catch (caught) {
|
|
34695
36096
|
if (signal.aborted) throw signal.reason;
|
|
34696
|
-
if (caught instanceof
|
|
34697
|
-
|
|
34698
|
-
|
|
36097
|
+
if (caught instanceof EventReceiverError) {
|
|
36098
|
+
if (["invalid_response", "unsupported"].includes(caught.code)) throw new ListenError(caught.message);
|
|
36099
|
+
error = new RequestFailure(caught.status, caught.code, caught.retryAfterMs);
|
|
36100
|
+
} else {
|
|
36101
|
+
if (caught instanceof ListenError) throw caught;
|
|
36102
|
+
if (caught instanceof SyntaxError) throw new ListenError("The API returned malformed JSON.");
|
|
36103
|
+
error = new RequestFailure(0, "transport", 0);
|
|
36104
|
+
}
|
|
34699
36105
|
}
|
|
34700
36106
|
if (![
|
|
34701
36107
|
0,
|
|
@@ -34719,7 +36125,7 @@ async function runListen(options) {
|
|
|
34719
36125
|
signal: AbortSignal.any([
|
|
34720
36126
|
signal,
|
|
34721
36127
|
...init?.signal ? [init.signal] : [],
|
|
34722
|
-
AbortSignal.timeout(
|
|
36128
|
+
AbortSignal.timeout(45e3)
|
|
34723
36129
|
])
|
|
34724
36130
|
})
|
|
34725
36131
|
});
|
|
@@ -34730,7 +36136,7 @@ async function runListen(options) {
|
|
|
34730
36136
|
if (origin !== void 0 && origin !== base) throw new ListenError("The API environment changed while listening. Restart the listener.");
|
|
34731
36137
|
origin = base;
|
|
34732
36138
|
if (!verified || auth.auth.apiKey !== verifiedKey) {
|
|
34733
|
-
const account = await retry(() => getAccount({
|
|
36139
|
+
const account = await retry(() => getAccount$1({
|
|
34734
36140
|
client: auth.apiClient.client,
|
|
34735
36141
|
responseStyle: "fields",
|
|
34736
36142
|
signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)])
|
|
@@ -34766,6 +36172,12 @@ async function runListen(options) {
|
|
|
34766
36172
|
});
|
|
34767
36173
|
const endpoint = registered.data;
|
|
34768
36174
|
if (registered.success !== true || !endpoint || typeof endpoint.id !== "string" || endpoint.kind !== "pull" || endpoint.enabled === false) throw new ListenError("The API did not return an enabled pull subscription.");
|
|
36175
|
+
const streamClient = await freshClient();
|
|
36176
|
+
if (options.transport !== "poll") {
|
|
36177
|
+
if (!endpoint.receiver_capabilities?.stream_protocols.includes("primitive.events.v1")) throw new ListenError("This API does not support WebSocket events. Use --transport poll explicitly for an older API.");
|
|
36178
|
+
if (!endpoint.receiver_capabilities.completion_modes.includes(options.mode ?? "stdout")) throw new ListenError("This API does not support the selected listener handler mode.");
|
|
36179
|
+
stream = new EventConnection(streamClient, endpoint.id);
|
|
36180
|
+
}
|
|
34769
36181
|
const selected = endpoint.rules?.event_types;
|
|
34770
36182
|
const selection = Array.isArray(selected) && selected.every((value) => typeof value === "string" && /^[a-zA-Z0-9_.-]+$/.test(value)) ? selected.join(", ") : "all events";
|
|
34771
36183
|
stderr.write(`Listening on subscription ${subscription.name} (${endpoint.id}); ${resumed ? "resumed" : "created"}, mode ${options.mode ?? "stdout"}, selection: ${selection}. Retention: 24 hours; handler limit: 30 seconds. Pending count follows the first poll. Ctrl-C disconnects; delete with primitive endpoints delete --id ${endpoint.id}.\n`);
|
|
@@ -34773,13 +36185,22 @@ async function runListen(options) {
|
|
|
34773
36185
|
let lastBacklog = -1;
|
|
34774
36186
|
while (!signal.aborted && (options.number === void 0 || confirmed < options.number)) {
|
|
34775
36187
|
const pollStarted = now();
|
|
34776
|
-
const result = await retry(async () =>
|
|
34777
|
-
|
|
34778
|
-
|
|
34779
|
-
|
|
34780
|
-
|
|
34781
|
-
|
|
34782
|
-
|
|
36188
|
+
const result = await retry(async () => {
|
|
36189
|
+
if (stream) {
|
|
36190
|
+
streamClient.setConfig((await freshClient()).getConfig());
|
|
36191
|
+
return { data: {
|
|
36192
|
+
success: true,
|
|
36193
|
+
data: await stream.receive(signal)
|
|
36194
|
+
} };
|
|
36195
|
+
}
|
|
36196
|
+
return pullWebhookEvent$1({
|
|
36197
|
+
client: await freshClient(),
|
|
36198
|
+
path: { id: endpoint.id },
|
|
36199
|
+
body: { wait_seconds: 25 },
|
|
36200
|
+
responseStyle: "fields",
|
|
36201
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(45e3)])
|
|
36202
|
+
});
|
|
36203
|
+
});
|
|
34783
36204
|
if (result.success !== true || !result.data || !Number.isSafeInteger(result.data.gap_count) || result.data.gap_count < 0 || result.data.last_gap_reason !== null && typeof result.data.last_gap_reason !== "string" || !Number.isSafeInteger(result.data.backlog) || result.data.backlog < 0 || result.data.handler_timeout_seconds !== 30 || result.data.retention_seconds !== 86400) throw new ListenError("The API returned an invalid pull response.");
|
|
34784
36205
|
const data = result.data;
|
|
34785
36206
|
const gapReasons = {
|
|
@@ -34822,13 +36243,23 @@ async function runListen(options) {
|
|
|
34822
36243
|
lease_token: delivery.lease_token
|
|
34823
36244
|
};
|
|
34824
36245
|
try {
|
|
34825
|
-
const receipt = await retry(async () =>
|
|
34826
|
-
|
|
34827
|
-
|
|
34828
|
-
|
|
34829
|
-
|
|
34830
|
-
|
|
34831
|
-
|
|
36246
|
+
const receipt = await retry(async () => {
|
|
36247
|
+
if (stream) {
|
|
36248
|
+
streamClient.setConfig((await freshClient()).getConfig());
|
|
36249
|
+
await stream.complete(completion, signal);
|
|
36250
|
+
return { data: {
|
|
36251
|
+
success: true,
|
|
36252
|
+
data: { result: "completed" }
|
|
36253
|
+
} };
|
|
36254
|
+
}
|
|
36255
|
+
return completeWebhookEvent$1({
|
|
36256
|
+
client: await freshClient(),
|
|
36257
|
+
path: { id: endpoint.id },
|
|
36258
|
+
body: completion,
|
|
36259
|
+
responseStyle: "fields",
|
|
36260
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)])
|
|
36261
|
+
});
|
|
36262
|
+
});
|
|
34832
36263
|
if (receipt.success !== true || !["completed", "already_completed"].includes(receipt.data?.result)) throw new ListenError("The API returned an invalid completion receipt.");
|
|
34833
36264
|
if (handled.succeeded) confirmed++;
|
|
34834
36265
|
else stderr.write("Handler reported failure; the server will apply its retry policy.\n");
|
|
@@ -34840,6 +36271,7 @@ async function runListen(options) {
|
|
|
34840
36271
|
} catch (error) {
|
|
34841
36272
|
if (!signal.aborted) throw error;
|
|
34842
36273
|
} finally {
|
|
36274
|
+
stream?.close();
|
|
34843
36275
|
release?.();
|
|
34844
36276
|
}
|
|
34845
36277
|
return confirmed;
|
|
@@ -34852,9 +36284,24 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
34852
36284
|
static examples = [
|
|
34853
36285
|
"<%= config.bin %> listen",
|
|
34854
36286
|
"<%= config.bin %> listen --subscription my-agent --exec \"python3 accept.py\"",
|
|
34855
|
-
"<%= config.bin %> listen --forward-to
|
|
36287
|
+
"<%= config.bin %> listen --forward-to localhost:3000",
|
|
36288
|
+
"<%= config.bin %> listen --once --timeout 60"
|
|
34856
36289
|
];
|
|
34857
36290
|
static flags = {
|
|
36291
|
+
transport: Flags.string({
|
|
36292
|
+
description: "Event transport; WebSocket is the default.",
|
|
36293
|
+
options: ["websocket", "poll"],
|
|
36294
|
+
default: "websocket"
|
|
36295
|
+
}),
|
|
36296
|
+
once: Flags.boolean({
|
|
36297
|
+
description: "Exit after one successfully handled and confirmed event.",
|
|
36298
|
+
exclusive: ["number"]
|
|
36299
|
+
}),
|
|
36300
|
+
timeout: Flags.integer({
|
|
36301
|
+
description: "Stop after this many seconds; exit 2 if the requested count was not reached.",
|
|
36302
|
+
min: 1,
|
|
36303
|
+
max: 2147483
|
|
36304
|
+
}),
|
|
34858
36305
|
subscription: Flags.string({ description: "Stable subscription name. Defaults to a saved name for this account and API environment." }),
|
|
34859
36306
|
exec: Flags.string({
|
|
34860
36307
|
description: "Short POSIX shell command accepting each complete event on stdin.",
|
|
@@ -34888,17 +36335,23 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
34888
36335
|
forwardTo: flags["forward-to"]
|
|
34889
36336
|
});
|
|
34890
36337
|
const controller = new AbortController();
|
|
36338
|
+
let timedOut = false;
|
|
36339
|
+
const timer = flags.timeout === void 0 ? void 0 : setTimeout(() => {
|
|
36340
|
+
timedOut = true;
|
|
36341
|
+
controller.abort();
|
|
36342
|
+
}, flags.timeout * 1e3);
|
|
34891
36343
|
const cancel = () => controller.abort();
|
|
34892
36344
|
process.on("SIGINT", cancel);
|
|
34893
36345
|
process.on("SIGTERM", cancel);
|
|
34894
36346
|
try {
|
|
34895
36347
|
await runListen({
|
|
36348
|
+
transport: flags.transport === "poll" ? "poll" : "websocket",
|
|
34896
36349
|
configDir: this.config.configDir,
|
|
34897
36350
|
apiKey: flags["api-key"],
|
|
34898
36351
|
apiBaseUrl: flags["api-base-url"],
|
|
34899
36352
|
subscription: flags.subscription,
|
|
34900
36353
|
events: events === void 0 ? void 0 : [...new Set(events)],
|
|
34901
|
-
number: flags.number,
|
|
36354
|
+
number: flags.once ? 1 : flags.number,
|
|
34902
36355
|
mode: flags.exec ? "exec" : flags["forward-to"] ? "http" : "stdout",
|
|
34903
36356
|
handler,
|
|
34904
36357
|
signal: controller.signal
|
|
@@ -34906,10 +36359,11 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
34906
36359
|
} catch (error) {
|
|
34907
36360
|
throw new Errors.CLIError(error instanceof ListenError || error instanceof ListenStateError ? error.message : "The listener stopped because of a local state or connection error.");
|
|
34908
36361
|
} finally {
|
|
36362
|
+
clearTimeout(timer);
|
|
34909
36363
|
process.removeListener("SIGINT", cancel);
|
|
34910
36364
|
process.removeListener("SIGTERM", cancel);
|
|
34911
36365
|
}
|
|
34912
|
-
if (controller.signal.aborted) process.exitCode = 130;
|
|
36366
|
+
if (controller.signal.aborted) process.exitCode = timedOut ? 2 : 130;
|
|
34913
36367
|
}
|
|
34914
36368
|
};
|
|
34915
36369
|
//#endregion
|
|
@@ -35172,7 +36626,7 @@ const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
|
|
|
35172
36626
|
function cliError$3(message) {
|
|
35173
36627
|
return new Errors.CLIError(message, { exit: 1 });
|
|
35174
36628
|
}
|
|
35175
|
-
function sleep
|
|
36629
|
+
function sleep(ms) {
|
|
35176
36630
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
35177
36631
|
}
|
|
35178
36632
|
function openBrowser(url) {
|
|
@@ -35225,7 +36679,7 @@ async function checkExistingLogin(params) {
|
|
|
35225
36679
|
apiBaseUrl: probeApiBaseUrl,
|
|
35226
36680
|
headers: requestConfig.headers
|
|
35227
36681
|
});
|
|
35228
|
-
const result = await (params.checkAccount ?? ((client) => getAccount({
|
|
36682
|
+
const result = await (params.checkAccount ?? ((client) => getAccount$1({
|
|
35229
36683
|
client: client.client,
|
|
35230
36684
|
responseStyle: "fields"
|
|
35231
36685
|
})))(apiClient);
|
|
@@ -35337,7 +36791,7 @@ var LoginCommand$1 = class extends Command {
|
|
|
35337
36791
|
let interval = Math.min(Math.max(1, start.interval), MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS);
|
|
35338
36792
|
let nextPollDelay = 1;
|
|
35339
36793
|
while (Date.now() < deadline) {
|
|
35340
|
-
await sleep
|
|
36794
|
+
await sleep(nextPollDelay * 1e3);
|
|
35341
36795
|
nextPollDelay = interval;
|
|
35342
36796
|
const polled = await pollCliLogin({
|
|
35343
36797
|
body: { device_code: start.device_code },
|
|
@@ -36967,331 +38421,6 @@ var OrgSecretsSetCommand = class OrgSecretsSetCommand extends Command {
|
|
|
36967
38421
|
}
|
|
36968
38422
|
};
|
|
36969
38423
|
//#endregion
|
|
36970
|
-
//#region ../sdk-node/dist/payloads-BfJAtqW9.js
|
|
36971
|
-
/**
|
|
36972
|
-
* Load a Node builtin at call time. Exported for direct testing of the
|
|
36973
|
-
* unavailable-module error path; production code goes through the typed
|
|
36974
|
-
* wrappers below.
|
|
36975
|
-
*/
|
|
36976
|
-
async function loadBuiltin(specifier, operation) {
|
|
36977
|
-
let builtin;
|
|
36978
|
-
if (typeof process !== "undefined" && typeof process.getBuiltinModule === "function") try {
|
|
36979
|
-
builtin = process.getBuiltinModule(specifier);
|
|
36980
|
-
} catch {
|
|
36981
|
-
builtin = void 0;
|
|
36982
|
-
}
|
|
36983
|
-
if (builtin !== void 0 && builtin !== null) return builtin;
|
|
36984
|
-
try {
|
|
36985
|
-
return await import(specifier);
|
|
36986
|
-
} catch (cause) {
|
|
36987
|
-
throw new Error(`${operation} requires file system access, which this runtime does not provide. Pass in-memory bytes instead of a file path when running outside Node.js.`, { cause });
|
|
36988
|
-
}
|
|
36989
|
-
}
|
|
36990
|
-
/** `node:fs`, loaded lazily so Workers-style bundles never see the import. */
|
|
36991
|
-
function loadNodeFs(operation) {
|
|
36992
|
-
return loadBuiltin("node:fs", operation);
|
|
36993
|
-
}
|
|
36994
|
-
/** `node:fs/promises`, loaded lazily for the same reason as {@link loadNodeFs}. */
|
|
36995
|
-
function loadNodeFsPromises(operation) {
|
|
36996
|
-
return loadBuiltin("node:fs/promises", operation);
|
|
36997
|
-
}
|
|
36998
|
-
const CHUNK_SIZE = 64 * 1024 * 1024;
|
|
36999
|
-
const MANIFEST_VERSION = 1;
|
|
37000
|
-
const CHUNK_KDF_INFO = "payloads-chunk";
|
|
37001
|
-
const OBJECT_ID_BYTES = 16;
|
|
37002
|
-
const CEK_BYTES = 32;
|
|
37003
|
-
function subtle() {
|
|
37004
|
-
return globalThis.crypto.subtle;
|
|
37005
|
-
}
|
|
37006
|
-
const textEncoder = new TextEncoder();
|
|
37007
|
-
function randomBytes(length) {
|
|
37008
|
-
const out = new Uint8Array(length);
|
|
37009
|
-
globalThis.crypto.getRandomValues(out);
|
|
37010
|
-
return out;
|
|
37011
|
-
}
|
|
37012
|
-
function toHex(bytes) {
|
|
37013
|
-
let out = "";
|
|
37014
|
-
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
37015
|
-
return out;
|
|
37016
|
-
}
|
|
37017
|
-
function fromHex(hex) {
|
|
37018
|
-
if (hex.length % 2 !== 0) throw new Error("invalid hex: odd length");
|
|
37019
|
-
if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) throw new Error("invalid hex: non-hex character");
|
|
37020
|
-
const out = new Uint8Array(hex.length / 2);
|
|
37021
|
-
for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
37022
|
-
return out;
|
|
37023
|
-
}
|
|
37024
|
-
async function contentHash(bytes) {
|
|
37025
|
-
return new Uint8Array(await subtle().digest("SHA-256", bytes));
|
|
37026
|
-
}
|
|
37027
|
-
async function contentHashHex(bytes) {
|
|
37028
|
-
return toHex(await contentHash(bytes));
|
|
37029
|
-
}
|
|
37030
|
-
/** Binary Merkle root over ordered hex leaf hashes (odd node promoted). */
|
|
37031
|
-
async function merkleRoot(leafHashesHex) {
|
|
37032
|
-
if (leafHashesHex.length === 0) return contentHashHex(new Uint8Array(0));
|
|
37033
|
-
let level = leafHashesHex.map(fromHex);
|
|
37034
|
-
while (level.length > 1) {
|
|
37035
|
-
const next = [];
|
|
37036
|
-
for (let i = 0; i < level.length; i += 2) {
|
|
37037
|
-
const left = level[i];
|
|
37038
|
-
const right = i + 1 < level.length ? level[i + 1] : level[i];
|
|
37039
|
-
const combined = new Uint8Array(left.length + right.length);
|
|
37040
|
-
combined.set(left, 0);
|
|
37041
|
-
combined.set(right, left.length);
|
|
37042
|
-
next.push(await contentHash(combined));
|
|
37043
|
-
}
|
|
37044
|
-
level = next;
|
|
37045
|
-
}
|
|
37046
|
-
return toHex(level[0]);
|
|
37047
|
-
}
|
|
37048
|
-
async function deriveChunkKey(cek, index) {
|
|
37049
|
-
const baseKey = await subtle().importKey("raw", cek, "HKDF", false, ["deriveKey"]);
|
|
37050
|
-
const info = textEncoder.encode(`${CHUNK_KDF_INFO}:${index}`);
|
|
37051
|
-
return subtle().deriveKey({
|
|
37052
|
-
name: "HKDF",
|
|
37053
|
-
hash: "SHA-256",
|
|
37054
|
-
salt: new Uint8Array(32),
|
|
37055
|
-
info
|
|
37056
|
-
}, baseKey, {
|
|
37057
|
-
name: "AES-GCM",
|
|
37058
|
-
length: 256
|
|
37059
|
-
}, false, ["encrypt", "decrypt"]);
|
|
37060
|
-
}
|
|
37061
|
-
function chunkNonce(index) {
|
|
37062
|
-
const nonce = new Uint8Array(12);
|
|
37063
|
-
new DataView(nonce.buffer).setUint32(8, index >>> 0, false);
|
|
37064
|
-
return nonce;
|
|
37065
|
-
}
|
|
37066
|
-
function chunkAad(objectId, index) {
|
|
37067
|
-
const aad = new Uint8Array(objectId.length + 4);
|
|
37068
|
-
aad.set(objectId, 0);
|
|
37069
|
-
new DataView(aad.buffer).setUint32(objectId.length, index >>> 0, false);
|
|
37070
|
-
return aad;
|
|
37071
|
-
}
|
|
37072
|
-
async function encryptChunk(cek, objectId, index, plaintext) {
|
|
37073
|
-
const key = await deriveChunkKey(cek, index);
|
|
37074
|
-
const ct = await subtle().encrypt({
|
|
37075
|
-
name: "AES-GCM",
|
|
37076
|
-
iv: chunkNonce(index),
|
|
37077
|
-
additionalData: chunkAad(objectId, index)
|
|
37078
|
-
}, key, plaintext);
|
|
37079
|
-
return new Uint8Array(ct);
|
|
37080
|
-
}
|
|
37081
|
-
async function decryptChunk(cek, objectId, index, ciphertext) {
|
|
37082
|
-
const key = await deriveChunkKey(cek, index);
|
|
37083
|
-
const pt = await subtle().decrypt({
|
|
37084
|
-
name: "AES-GCM",
|
|
37085
|
-
iv: chunkNonce(index),
|
|
37086
|
-
additionalData: chunkAad(objectId, index)
|
|
37087
|
-
}, key, ciphertext);
|
|
37088
|
-
return new Uint8Array(pt);
|
|
37089
|
-
}
|
|
37090
|
-
function authHeaders(apiKey) {
|
|
37091
|
-
return { authorization: `Bearer ${apiKey}` };
|
|
37092
|
-
}
|
|
37093
|
-
function apiRoot(baseUrl) {
|
|
37094
|
-
let end = baseUrl.length;
|
|
37095
|
-
while (end > 0 && baseUrl.charCodeAt(end - 1) === 47) end--;
|
|
37096
|
-
return `${baseUrl.slice(0, end)}/v1/payloads`;
|
|
37097
|
-
}
|
|
37098
|
-
const DEFAULT_MAX_RETRIES = 6;
|
|
37099
|
-
const RETRYABLE_STATUS = new Set([
|
|
37100
|
-
429,
|
|
37101
|
-
500,
|
|
37102
|
-
502,
|
|
37103
|
-
503,
|
|
37104
|
-
504
|
|
37105
|
-
]);
|
|
37106
|
-
function sleep(ms) {
|
|
37107
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
37108
|
-
}
|
|
37109
|
-
/**
|
|
37110
|
-
* fetch with exponential backoff on transient failures. Request bodies here are
|
|
37111
|
-
* in-memory buffers/strings (never streams), so a retry can safely resend them.
|
|
37112
|
-
*/
|
|
37113
|
-
async function retryingFetch(url, init, label, maxRetries = DEFAULT_MAX_RETRIES) {
|
|
37114
|
-
for (let attempt = 0;; attempt++) {
|
|
37115
|
-
let res;
|
|
37116
|
-
let networkError;
|
|
37117
|
-
try {
|
|
37118
|
-
res = await fetch(url, init);
|
|
37119
|
-
} catch (err) {
|
|
37120
|
-
networkError = err;
|
|
37121
|
-
}
|
|
37122
|
-
if (res && !RETRYABLE_STATUS.has(res.status)) return res;
|
|
37123
|
-
if (attempt >= maxRetries) {
|
|
37124
|
-
if (res) return res;
|
|
37125
|
-
throw new Error(`${label}: network error after ${attempt} retries: ${networkError?.message ?? networkError}`);
|
|
37126
|
-
}
|
|
37127
|
-
if (res) await res.text().catch(() => {});
|
|
37128
|
-
await sleep(Math.min(1e3 * 2 ** attempt, 15e3) + Math.floor(Math.random() * 250));
|
|
37129
|
-
}
|
|
37130
|
-
}
|
|
37131
|
-
async function initiate(opts, manifest) {
|
|
37132
|
-
const res = await retryingFetch(apiRoot(opts.baseUrl), {
|
|
37133
|
-
method: "POST",
|
|
37134
|
-
headers: {
|
|
37135
|
-
...authHeaders(opts.apiKey),
|
|
37136
|
-
"content-type": "application/json"
|
|
37137
|
-
},
|
|
37138
|
-
body: JSON.stringify({ manifest })
|
|
37139
|
-
}, "initiate");
|
|
37140
|
-
if (!res.ok) throw new Error(`initiate failed: HTTP ${res.status} ${await res.text()}`);
|
|
37141
|
-
await res.text();
|
|
37142
|
-
}
|
|
37143
|
-
async function putChunk(opts, root, d, bytes) {
|
|
37144
|
-
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, {
|
|
37145
|
-
method: "PUT",
|
|
37146
|
-
headers: {
|
|
37147
|
-
...authHeaders(opts.apiKey),
|
|
37148
|
-
"content-type": "application/octet-stream"
|
|
37149
|
-
},
|
|
37150
|
-
body: bytes
|
|
37151
|
-
}, `chunk ${d.index} upload`);
|
|
37152
|
-
if (!res.ok) throw new Error(`chunk ${d.index} upload failed: HTTP ${res.status} ${await res.text()}`);
|
|
37153
|
-
await res.text();
|
|
37154
|
-
}
|
|
37155
|
-
async function finalize(opts, root) {
|
|
37156
|
-
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/finalize`, {
|
|
37157
|
-
method: "POST",
|
|
37158
|
-
headers: authHeaders(opts.apiKey)
|
|
37159
|
-
}, "finalize");
|
|
37160
|
-
if (!res.ok) throw new Error(`finalize failed: HTTP ${res.status} ${await res.text()}`);
|
|
37161
|
-
await res.text();
|
|
37162
|
-
}
|
|
37163
|
-
async function fetchManifest(opts, root) {
|
|
37164
|
-
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/manifest`, { headers: authHeaders(opts.apiKey) }, "get manifest");
|
|
37165
|
-
if (!res.ok) throw new Error(`get manifest failed: HTTP ${res.status} ${await res.text()}`);
|
|
37166
|
-
return (await res.json()).data.manifest;
|
|
37167
|
-
}
|
|
37168
|
-
async function getChunkBytes(opts, root, d) {
|
|
37169
|
-
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, { headers: authHeaders(opts.apiKey) }, `get chunk ${d.index}`);
|
|
37170
|
-
if (!res.ok) throw new Error(`get chunk ${d.index} failed: HTTP ${res.status}`);
|
|
37171
|
-
return new Uint8Array(await res.arrayBuffer());
|
|
37172
|
-
}
|
|
37173
|
-
async function readWindow(fh, position, length) {
|
|
37174
|
-
const buf = Buffer.allocUnsafe(length);
|
|
37175
|
-
let read = 0;
|
|
37176
|
-
while (read < length) {
|
|
37177
|
-
const { bytesRead } = await fh.read(buf, read, length - read, position + read);
|
|
37178
|
-
if (bytesRead === 0) break;
|
|
37179
|
-
read += bytesRead;
|
|
37180
|
-
}
|
|
37181
|
-
return read === length ? buf : buf.subarray(0, read);
|
|
37182
|
-
}
|
|
37183
|
-
async function mapLimit(items, limit, fn) {
|
|
37184
|
-
let next = 0;
|
|
37185
|
-
let firstError;
|
|
37186
|
-
const worker = async () => {
|
|
37187
|
-
while (next < items.length && firstError === void 0) {
|
|
37188
|
-
const i = next++;
|
|
37189
|
-
try {
|
|
37190
|
-
await fn(items[i], i);
|
|
37191
|
-
} catch (err) {
|
|
37192
|
-
if (firstError === void 0) firstError = err;
|
|
37193
|
-
return;
|
|
37194
|
-
}
|
|
37195
|
-
}
|
|
37196
|
-
};
|
|
37197
|
-
await Promise.all(Array.from({ length: Math.min(limit, items.length || 1) }, () => worker()));
|
|
37198
|
-
if (firstError !== void 0) throw firstError;
|
|
37199
|
-
}
|
|
37200
|
-
/**
|
|
37201
|
-
* Stream a file up as a Primitive Payload. Two passes over the file (encrypt to
|
|
37202
|
-
* build the manifest, then encrypt again to upload) keep memory bounded to a few
|
|
37203
|
-
* chunks — the whole object is never resident. Returns the content address
|
|
37204
|
-
* (merkleRoot) and the hex CEK needed to download it.
|
|
37205
|
-
*/
|
|
37206
|
-
async function pushFile(filePath, opts) {
|
|
37207
|
-
const { open, stat } = await loadNodeFsPromises("pushFile");
|
|
37208
|
-
const chunkSize = opts.chunkSize ?? CHUNK_SIZE;
|
|
37209
|
-
const concurrency = opts.concurrency ?? 3;
|
|
37210
|
-
const { size } = await stat(filePath);
|
|
37211
|
-
const cek = randomBytes(CEK_BYTES);
|
|
37212
|
-
const objectId = randomBytes(OBJECT_ID_BYTES);
|
|
37213
|
-
const chunkCount = size === 0 ? 0 : Math.ceil(size / chunkSize);
|
|
37214
|
-
const fh = await open(filePath, "r");
|
|
37215
|
-
try {
|
|
37216
|
-
const descriptors = [];
|
|
37217
|
-
for (let index = 0; index < chunkCount; index++) {
|
|
37218
|
-
const position = index * chunkSize;
|
|
37219
|
-
const length = Math.min(chunkSize, size - position);
|
|
37220
|
-
const plaintext = await readWindow(fh, position, length);
|
|
37221
|
-
const ciphertext = await encryptChunk(cek, objectId, index, plaintext);
|
|
37222
|
-
descriptors.push({
|
|
37223
|
-
index,
|
|
37224
|
-
ciphertextHash: await contentHashHex(ciphertext),
|
|
37225
|
-
plaintextSize: length,
|
|
37226
|
-
ciphertextSize: ciphertext.length
|
|
37227
|
-
});
|
|
37228
|
-
opts.onProgress?.("encrypt", index + 1, chunkCount);
|
|
37229
|
-
}
|
|
37230
|
-
const root = await merkleRoot(descriptors.map((d) => d.ciphertextHash));
|
|
37231
|
-
await initiate(opts, {
|
|
37232
|
-
version: MANIFEST_VERSION,
|
|
37233
|
-
objectId: toHex(objectId),
|
|
37234
|
-
chunkSize,
|
|
37235
|
-
totalPlaintextSize: size,
|
|
37236
|
-
chunkCount,
|
|
37237
|
-
chunks: descriptors,
|
|
37238
|
-
merkleRoot: root
|
|
37239
|
-
});
|
|
37240
|
-
let uploaded = 0;
|
|
37241
|
-
await mapLimit(descriptors, concurrency, async (d) => {
|
|
37242
|
-
const plaintext = await readWindow(fh, d.index * chunkSize, d.plaintextSize);
|
|
37243
|
-
await putChunk(opts, root, d, await encryptChunk(cek, objectId, d.index, plaintext));
|
|
37244
|
-
opts.onProgress?.("upload", ++uploaded, chunkCount);
|
|
37245
|
-
});
|
|
37246
|
-
await finalize(opts, root);
|
|
37247
|
-
return {
|
|
37248
|
-
merkleRoot: root,
|
|
37249
|
-
cek: toHex(cek),
|
|
37250
|
-
chunkCount,
|
|
37251
|
-
totalBytes: size
|
|
37252
|
-
};
|
|
37253
|
-
} finally {
|
|
37254
|
-
await fh.close();
|
|
37255
|
-
}
|
|
37256
|
-
}
|
|
37257
|
-
/**
|
|
37258
|
-
* Stream a Primitive Payload down to a file, verifying and decrypting one chunk
|
|
37259
|
-
* at a time (bounded memory). Every chunk is content-address-checked before
|
|
37260
|
-
* decryption, so a corrupt or substituted chunk throws.
|
|
37261
|
-
*/
|
|
37262
|
-
async function pullFile(root, outPath, opts) {
|
|
37263
|
-
const { createWriteStream } = await loadNodeFs("pullFile");
|
|
37264
|
-
const { rm } = await loadNodeFsPromises("pullFile");
|
|
37265
|
-
const manifest = await fetchManifest(opts, root);
|
|
37266
|
-
const computedRoot = await merkleRoot(manifest.chunks.map((c) => c.ciphertextHash));
|
|
37267
|
-
if (computedRoot !== root) throw new Error(`manifest does not match the requested content address (got ${computedRoot})`);
|
|
37268
|
-
const cek = fromHex(opts.cek);
|
|
37269
|
-
const objectId = fromHex(manifest.objectId);
|
|
37270
|
-
const out = createWriteStream(outPath);
|
|
37271
|
-
let onWriteError = () => {};
|
|
37272
|
-
const writeErrored = new Promise((_, reject) => {
|
|
37273
|
-
onWriteError = reject;
|
|
37274
|
-
});
|
|
37275
|
-
writeErrored.catch(() => {});
|
|
37276
|
-
out.on("error", (err) => onWriteError(err));
|
|
37277
|
-
try {
|
|
37278
|
-
let done = 0;
|
|
37279
|
-
for (const d of manifest.chunks) {
|
|
37280
|
-
const ciphertext = await getChunkBytes(opts, root, d);
|
|
37281
|
-
if (await contentHashHex(ciphertext) !== d.ciphertextHash) throw new Error(`chunk ${d.index} failed integrity check`);
|
|
37282
|
-
const plaintext = await decryptChunk(cek, objectId, d.index, ciphertext);
|
|
37283
|
-
if (!out.write(plaintext)) await Promise.race([new Promise((resolve) => out.once("drain", resolve)), writeErrored]);
|
|
37284
|
-
opts.onProgress?.("download", ++done, manifest.chunkCount);
|
|
37285
|
-
}
|
|
37286
|
-
await Promise.race([new Promise((resolve) => out.end(resolve)), writeErrored]);
|
|
37287
|
-
} catch (err) {
|
|
37288
|
-
out.destroy();
|
|
37289
|
-
await rm(outPath, { force: true });
|
|
37290
|
-
throw err;
|
|
37291
|
-
}
|
|
37292
|
-
return manifest;
|
|
37293
|
-
}
|
|
37294
|
-
//#endregion
|
|
37295
38424
|
//#region src/oclif/commands/payloads.ts
|
|
37296
38425
|
const DEFAULT_API_BASE_URL = "https://api.primitive.dev";
|
|
37297
38426
|
const authFlags = {
|
|
@@ -39189,7 +40318,7 @@ var PaymentsRegisterPayoutAddressCommand = class PaymentsRegisterPayoutAddressCo
|
|
|
39189
40318
|
configDir: this.config.configDir
|
|
39190
40319
|
});
|
|
39191
40320
|
await runWithTiming(flags.time, async () => {
|
|
39192
|
-
const account = await getAccount({
|
|
40321
|
+
const account = await getAccount$1({
|
|
39193
40322
|
client: apiClient.client,
|
|
39194
40323
|
responseStyle: "fields"
|
|
39195
40324
|
});
|
|
@@ -41382,7 +42511,7 @@ var WhoamiCommand = class WhoamiCommand extends Command {
|
|
|
41382
42511
|
apiBaseUrl: flags["api-base-url"],
|
|
41383
42512
|
configDir: this.config.configDir
|
|
41384
42513
|
});
|
|
41385
|
-
const result = await getAccount({
|
|
42514
|
+
const result = await getAccount$1({
|
|
41386
42515
|
client: apiClient.client,
|
|
41387
42516
|
responseStyle: "fields"
|
|
41388
42517
|
});
|