primcli 1.26.0 → 1.29.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 +49 -0
- package/dist/oclif/index.js +2255 -373
- package/man/primitive.1 +6 -0
- package/package.json +4 -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,
|
|
@@ -75,6 +75,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
75
75
|
getAgent: () => getAgent,
|
|
76
76
|
getChallenge: () => getChallenge,
|
|
77
77
|
getConversation: () => getConversation,
|
|
78
|
+
getCreditBalance: () => getCreditBalance,
|
|
78
79
|
getEmail: () => getEmail,
|
|
79
80
|
getFunction: () => getFunction,
|
|
80
81
|
getFunctionRouting: () => getFunctionRouting,
|
|
@@ -117,7 +118,8 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
117
118
|
payChallenge: () => payChallenge,
|
|
118
119
|
pollCliLogin: () => pollCliLogin,
|
|
119
120
|
publishAgent: () => publishAgent,
|
|
120
|
-
pullWebhookEvent: () => pullWebhookEvent,
|
|
121
|
+
pullWebhookEvent: () => pullWebhookEvent$1,
|
|
122
|
+
redeemCreditCode: () => redeemCreditCode,
|
|
121
123
|
registerPayoutAddress: () => registerPayoutAddress,
|
|
122
124
|
removeAgentConnection: () => removeAgentConnection,
|
|
123
125
|
reorderRoutes: () => reorderRoutes,
|
|
@@ -493,6 +495,55 @@ const rotateWebhookSecret = (options) => (options?.client ?? client).post({
|
|
|
493
495
|
...options
|
|
494
496
|
});
|
|
495
497
|
/**
|
|
498
|
+
* Redeem a credit code
|
|
499
|
+
*
|
|
500
|
+
* Redeem a credit code for the authenticated organization. The credit is
|
|
501
|
+
* added to the organization prepaid credit and shows in
|
|
502
|
+
* `GET /credits/balance` under `prepaid_credit`.
|
|
503
|
+
*
|
|
504
|
+
* Redeeming requires an organization owner or admin. An API key redeems
|
|
505
|
+
* with the authority of the user who created it, based on that user's
|
|
506
|
+
* current role; a key without that authority gets the same
|
|
507
|
+
* `credit_code_invalid` refusal as an unknown code.
|
|
508
|
+
*
|
|
509
|
+
* The `Idempotency-Key` header is required. Retrying with the same key
|
|
510
|
+
* and code returns the original grant with `replayed: true` and grants
|
|
511
|
+
* nothing new; the same key with a different code is refused with
|
|
512
|
+
* `idempotency_key_reused`. Every refusal carries a human-readable
|
|
513
|
+
* `error.message` that can be shown to the user as is.
|
|
514
|
+
*
|
|
515
|
+
*/
|
|
516
|
+
const redeemCreditCode = (options) => (options.client ?? client).post({
|
|
517
|
+
security: [{
|
|
518
|
+
scheme: "bearer",
|
|
519
|
+
type: "http"
|
|
520
|
+
}],
|
|
521
|
+
url: "/credits/redeem",
|
|
522
|
+
...options,
|
|
523
|
+
headers: {
|
|
524
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
525
|
+
...options.headers
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
/**
|
|
529
|
+
* Get credit balance
|
|
530
|
+
*
|
|
531
|
+
* Read the organization credit position. `prepaid_credit` is the prepaid
|
|
532
|
+
* usage credit (paid top-ups, redeemed credit codes and granted credit)
|
|
533
|
+
* that can still pay for usage. `budget` is the active agent spending
|
|
534
|
+
* budget an operator funded for top-ups, or null when there is none.
|
|
535
|
+
* Amounts are strings of integer micros (1 USD = 1000000 micros).
|
|
536
|
+
*
|
|
537
|
+
*/
|
|
538
|
+
const getCreditBalance = (options) => (options?.client ?? client).get({
|
|
539
|
+
security: [{
|
|
540
|
+
scheme: "bearer",
|
|
541
|
+
type: "http"
|
|
542
|
+
}],
|
|
543
|
+
url: "/credits/balance",
|
|
544
|
+
...options
|
|
545
|
+
});
|
|
546
|
+
/**
|
|
496
547
|
* List all domains
|
|
497
548
|
*
|
|
498
549
|
* Returns all verified and unverified domains for your organization,
|
|
@@ -969,7 +1020,7 @@ const listEndpoints = (options) => (options?.client ?? client).get({
|
|
|
969
1020
|
* accepts the signature.
|
|
970
1021
|
*
|
|
971
1022
|
*
|
|
972
|
-
* For local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. New unfiltered destinations receive all subsequently ready eligible event types. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.
|
|
1023
|
+
* For local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. New unfiltered destinations receive all subsequently ready eligible event types within their credential scope. Connected-agent credentials automatically create private subscriptions restricted to inbound email for their assigned address. Names are isolated per credential; revocation or replacement deletes its subscriptions. Address-scoped events contain parsed mail without raw MIME, signed download links, routing metadata, or sibling envelope recipients. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.
|
|
973
1024
|
*/
|
|
974
1025
|
const createEndpoint = (options) => (options.client ?? client).post({
|
|
975
1026
|
security: [{
|
|
@@ -1025,7 +1076,7 @@ const updateEndpoint = (options) => (options.client ?? client).patch({
|
|
|
1025
1076
|
*
|
|
1026
1077
|
* 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
1078
|
*/
|
|
1028
|
-
const pullWebhookEvent = (options) => (options.client ?? client).post({
|
|
1079
|
+
const pullWebhookEvent$1 = (options) => (options.client ?? client).post({
|
|
1029
1080
|
security: [{
|
|
1030
1081
|
scheme: "bearer",
|
|
1031
1082
|
type: "http"
|
|
@@ -1042,7 +1093,7 @@ const pullWebhookEvent = (options) => (options.client ?? client).post({
|
|
|
1042
1093
|
*
|
|
1043
1094
|
* 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
1095
|
*/
|
|
1045
|
-
const completeWebhookEvent = (options) => (options.client ?? client).post({
|
|
1096
|
+
const completeWebhookEvent$1 = (options) => (options.client ?? client).post({
|
|
1046
1097
|
security: [{
|
|
1047
1098
|
scheme: "bearer",
|
|
1048
1099
|
type: "http"
|
|
@@ -2783,6 +2834,10 @@ const openapiDocument = {
|
|
|
2783
2834
|
"name": "Account",
|
|
2784
2835
|
"description": "Manage your account settings, storage, and webhook secret"
|
|
2785
2836
|
},
|
|
2837
|
+
{
|
|
2838
|
+
"name": "Credits",
|
|
2839
|
+
"description": "Redeem credit codes and read the organization credit balance"
|
|
2840
|
+
},
|
|
2786
2841
|
{
|
|
2787
2842
|
"name": "Domains",
|
|
2788
2843
|
"description": "Claim, verify, and manage email domains"
|
|
@@ -3387,6 +3442,129 @@ const openapiDocument = {
|
|
|
3387
3442
|
"429": { "$ref": "#/components/responses/RateLimited" }
|
|
3388
3443
|
}
|
|
3389
3444
|
} },
|
|
3445
|
+
"/credits/redeem": { "post": {
|
|
3446
|
+
"operationId": "redeemCreditCode",
|
|
3447
|
+
"summary": "Redeem a credit code",
|
|
3448
|
+
"description": "Redeem a credit code for the authenticated organization. The credit is\nadded to the organization prepaid credit and shows in\n`GET /credits/balance` under `prepaid_credit`.\n\nRedeeming requires an organization owner or admin. An API key redeems\nwith the authority of the user who created it, based on that user's\ncurrent role; a key without that authority gets the same\n`credit_code_invalid` refusal as an unknown code.\n\nThe `Idempotency-Key` header is required. Retrying with the same key\nand code returns the original grant with `replayed: true` and grants\nnothing new; the same key with a different code is refused with\n`idempotency_key_reused`. Every refusal carries a human-readable\n`error.message` that can be shown to the user as is.\n",
|
|
3449
|
+
"tags": ["Credits"],
|
|
3450
|
+
"parameters": [{
|
|
3451
|
+
"name": "Idempotency-Key",
|
|
3452
|
+
"in": "header",
|
|
3453
|
+
"required": true,
|
|
3454
|
+
"description": "Required client-supplied key: 1 to 200 printable ASCII\ncharacters, without spaces. Use a new key for each code you\nredeem and reuse it only to retry the same request.\n",
|
|
3455
|
+
"schema": {
|
|
3456
|
+
"type": "string",
|
|
3457
|
+
"minLength": 1,
|
|
3458
|
+
"maxLength": 200,
|
|
3459
|
+
"pattern": "^[\\x21-\\x7E]{1,200}$"
|
|
3460
|
+
}
|
|
3461
|
+
}],
|
|
3462
|
+
"requestBody": {
|
|
3463
|
+
"required": true,
|
|
3464
|
+
"content": { "application/json": {
|
|
3465
|
+
"schema": { "$ref": "#/components/schemas/RedeemCreditCodeInput" },
|
|
3466
|
+
"example": { "code": "LAUNCH50" }
|
|
3467
|
+
} }
|
|
3468
|
+
},
|
|
3469
|
+
"responses": {
|
|
3470
|
+
"200": {
|
|
3471
|
+
"description": "The code was redeemed, or this request replayed an earlier\nredemption with the same Idempotency-Key.\n",
|
|
3472
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
3473
|
+
"type": "object",
|
|
3474
|
+
"properties": { "data": { "$ref": "#/components/schemas/CreditRedemption" } }
|
|
3475
|
+
}] } } }
|
|
3476
|
+
},
|
|
3477
|
+
"400": {
|
|
3478
|
+
"description": "`idempotency_key_required` means the Idempotency-Key header is\nmissing. `validation_error` means the header is empty, longer than\n200 characters or not printable non-space ASCII, or the body is\ninvalid.\n",
|
|
3479
|
+
"content": { "application/json": {
|
|
3480
|
+
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
3481
|
+
"example": {
|
|
3482
|
+
"success": false,
|
|
3483
|
+
"error": {
|
|
3484
|
+
"code": "idempotency_key_required",
|
|
3485
|
+
"message": "an Idempotency-Key header is required"
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
} }
|
|
3489
|
+
},
|
|
3490
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3491
|
+
"403": { "$ref": "#/components/responses/Forbidden" },
|
|
3492
|
+
"409": {
|
|
3493
|
+
"description": "The organization or user already redeemed this code, or already\nredeemed another code in the same exclusive group (a set of codes\nof which each organization or user may redeem only one).\n`error.code` is `credit_code_already_redeemed`.\n",
|
|
3494
|
+
"content": { "application/json": {
|
|
3495
|
+
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
3496
|
+
"example": {
|
|
3497
|
+
"success": false,
|
|
3498
|
+
"error": {
|
|
3499
|
+
"code": "credit_code_already_redeemed",
|
|
3500
|
+
"message": "Your organization has already redeemed this promotion."
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
} }
|
|
3504
|
+
},
|
|
3505
|
+
"422": {
|
|
3506
|
+
"description": "The code cannot be redeemed. `credit_code_invalid` covers unknown,\nexpired and exhausted codes and callers without authority to\nredeem; `credit_code_not_eligible` means the organization does not\nmeet the promotion terms; `credit_code_balance_cap` means the\norganization already holds the most promotional credit it can;\n`idempotency_key_reused` means this Idempotency-Key already\nredeemed a different code.\n",
|
|
3507
|
+
"content": { "application/json": {
|
|
3508
|
+
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
3509
|
+
"example": {
|
|
3510
|
+
"success": false,
|
|
3511
|
+
"error": {
|
|
3512
|
+
"code": "credit_code_invalid",
|
|
3513
|
+
"message": "That code isn't valid or is no longer available."
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
} }
|
|
3517
|
+
},
|
|
3518
|
+
"429": {
|
|
3519
|
+
"description": "Too many requests or redemption attempts. `error.code` is\n`rate_limited`. Wait for `Retry-After` when present. Like other\nrate-limited responses, it also carries the `ratelimit-limit`,\n`ratelimit-remaining`, `ratelimit-reset` and `ratelimit-policy`\nquota headers when the limiter reports them.\n",
|
|
3520
|
+
"headers": { "Retry-After": {
|
|
3521
|
+
"schema": { "type": "integer" },
|
|
3522
|
+
"description": "Seconds to wait before retrying"
|
|
3523
|
+
} },
|
|
3524
|
+
"content": { "application/json": {
|
|
3525
|
+
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
3526
|
+
"example": {
|
|
3527
|
+
"success": false,
|
|
3528
|
+
"error": {
|
|
3529
|
+
"code": "rate_limited",
|
|
3530
|
+
"message": "Rate limit exceeded. Try again shortly."
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
} }
|
|
3534
|
+
},
|
|
3535
|
+
"503": {
|
|
3536
|
+
"description": "The code could not be redeemed right now. Retry later with the\nsame Idempotency-Key. `error.code` is `service_unavailable`.\n",
|
|
3537
|
+
"content": { "application/json": {
|
|
3538
|
+
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
3539
|
+
"example": {
|
|
3540
|
+
"success": false,
|
|
3541
|
+
"error": {
|
|
3542
|
+
"code": "service_unavailable",
|
|
3543
|
+
"message": "We couldn't redeem that code right now. Try again in a few minutes."
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
} }
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
} },
|
|
3550
|
+
"/credits/balance": { "get": {
|
|
3551
|
+
"operationId": "getCreditBalance",
|
|
3552
|
+
"summary": "Get credit balance",
|
|
3553
|
+
"description": "Read the organization credit position. `prepaid_credit` is the prepaid\nusage credit (paid top-ups, redeemed credit codes and granted credit)\nthat can still pay for usage. `budget` is the active agent spending\nbudget an operator funded for top-ups, or null when there is none.\nAmounts are strings of integer micros (1 USD = 1000000 micros).\n",
|
|
3554
|
+
"tags": ["Credits"],
|
|
3555
|
+
"responses": {
|
|
3556
|
+
"200": {
|
|
3557
|
+
"description": "The organization credit balance.",
|
|
3558
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
3559
|
+
"type": "object",
|
|
3560
|
+
"properties": { "data": { "$ref": "#/components/schemas/CreditBalance" } }
|
|
3561
|
+
}] } } }
|
|
3562
|
+
},
|
|
3563
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3564
|
+
"403": { "$ref": "#/components/responses/Forbidden" },
|
|
3565
|
+
"429": { "$ref": "#/components/responses/RateLimited" }
|
|
3566
|
+
}
|
|
3567
|
+
} },
|
|
3390
3568
|
"/domains": {
|
|
3391
3569
|
"get": {
|
|
3392
3570
|
"operationId": "listDomains",
|
|
@@ -4221,7 +4399,7 @@ const openapiDocument = {
|
|
|
4221
4399
|
"post": {
|
|
4222
4400
|
"operationId": "createEndpoint",
|
|
4223
4401
|
"summary": "Create a webhook endpoint",
|
|
4224
|
-
"description": "Creates a new webhook endpoint. If a deactivated endpoint\nwith the same URL and domain exists, it is reactivated\ninstead. Subject to plan limits on the number of active\nendpoints.\n\n**Signing is account-scoped, not per-endpoint.** This call\ndoes not return any signing material; every endpoint on the\naccount uses the same webhook secret, fetched via\n`GET /account/webhook-secret`. See the API-level \"Webhook\nsigning\" section for the full wire format (header name,\nsigned string, hash algo, secret format, tolerance) and a\nlanguage-agnostic verification recipe.\n\nAfter creating the endpoint, fire a test delivery against\nit via `POST /endpoints/{id}/test` to confirm your verifier\naccepts the signature.\n\n\nFor local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. New unfiltered destinations receive all subsequently ready eligible event types. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.",
|
|
4402
|
+
"description": "Creates a new webhook endpoint. If a deactivated endpoint\nwith the same URL and domain exists, it is reactivated\ninstead. Subject to plan limits on the number of active\nendpoints.\n\n**Signing is account-scoped, not per-endpoint.** This call\ndoes not return any signing material; every endpoint on the\naccount uses the same webhook secret, fetched via\n`GET /account/webhook-secret`. See the API-level \"Webhook\nsigning\" section for the full wire format (header name,\nsigned string, hash algo, secret format, tolerance) and a\nlanguage-agnostic verification recipe.\n\nAfter creating the endpoint, fire a test delivery against\nit via `POST /endpoints/{id}/test` to confirm your verifier\naccepts the signature.\n\n\nFor local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. New unfiltered destinations receive all subsequently ready eligible event types within their credential scope. Connected-agent credentials automatically create private subscriptions restricted to inbound email for their assigned address. Names are isolated per credential; revocation or replacement deletes its subscriptions. Address-scoped events contain parsed mail without raw MIME, signed download links, routing metadata, or sibling envelope recipients. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.",
|
|
4225
4403
|
"tags": ["Endpoints"],
|
|
4226
4404
|
"requestBody": {
|
|
4227
4405
|
"required": true,
|
|
@@ -5110,6 +5288,22 @@ const openapiDocument = {
|
|
|
5110
5288
|
"format": "date-time"
|
|
5111
5289
|
},
|
|
5112
5290
|
"description": "Inclusive upper bound on `created_at`."
|
|
5291
|
+
},
|
|
5292
|
+
{
|
|
5293
|
+
"name": "q",
|
|
5294
|
+
"in": "query",
|
|
5295
|
+
"schema": { "type": "string" },
|
|
5296
|
+
"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"
|
|
5297
|
+
},
|
|
5298
|
+
{
|
|
5299
|
+
"name": "from",
|
|
5300
|
+
"in": "query",
|
|
5301
|
+
"schema": {
|
|
5302
|
+
"type": "string",
|
|
5303
|
+
"format": "email",
|
|
5304
|
+
"maxLength": 320
|
|
5305
|
+
},
|
|
5306
|
+
"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
5307
|
}
|
|
5114
5308
|
],
|
|
5115
5309
|
"responses": {
|
|
@@ -5124,7 +5318,20 @@ const openapiDocument = {
|
|
|
5124
5318
|
}] } } }
|
|
5125
5319
|
},
|
|
5126
5320
|
"400": { "$ref": "#/components/responses/ValidationError" },
|
|
5127
|
-
"401": { "$ref": "#/components/responses/Unauthorized" }
|
|
5321
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
5322
|
+
"503": {
|
|
5323
|
+
"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",
|
|
5324
|
+
"content": { "application/json": {
|
|
5325
|
+
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
5326
|
+
"example": {
|
|
5327
|
+
"success": false,
|
|
5328
|
+
"error": {
|
|
5329
|
+
"code": "sent_mail_search_unavailable",
|
|
5330
|
+
"message": "Sent-mail text search (`q`) is not enabled in this environment."
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
} }
|
|
5334
|
+
}
|
|
5128
5335
|
}
|
|
5129
5336
|
} },
|
|
5130
5337
|
"/sent-emails/{id}": {
|
|
@@ -8046,7 +8253,15 @@ const openapiDocument = {
|
|
|
8046
8253
|
"event_content_unavailable",
|
|
8047
8254
|
"event_preparation_failed",
|
|
8048
8255
|
"subscription_unavailable",
|
|
8049
|
-
"stale_delivery"
|
|
8256
|
+
"stale_delivery",
|
|
8257
|
+
"idempotency_key_required",
|
|
8258
|
+
"idempotency_key_reused",
|
|
8259
|
+
"credit_code_invalid",
|
|
8260
|
+
"credit_code_already_redeemed",
|
|
8261
|
+
"credit_code_not_eligible",
|
|
8262
|
+
"credit_code_balance_cap",
|
|
8263
|
+
"rate_limited",
|
|
8264
|
+
"service_unavailable"
|
|
8050
8265
|
]
|
|
8051
8266
|
},
|
|
8052
8267
|
"message": { "type": "string" },
|
|
@@ -9012,6 +9227,148 @@ const openapiDocument = {
|
|
|
9012
9227
|
} },
|
|
9013
9228
|
"required": ["secret"]
|
|
9014
9229
|
},
|
|
9230
|
+
"RedeemCreditCodeInput": {
|
|
9231
|
+
"type": "object",
|
|
9232
|
+
"additionalProperties": false,
|
|
9233
|
+
"required": ["code"],
|
|
9234
|
+
"properties": { "code": {
|
|
9235
|
+
"type": "string",
|
|
9236
|
+
"pattern": "^\\s*\\S(?:[\\s\\S]{0,254}\\S)?\\s*$",
|
|
9237
|
+
"description": "The credit code to redeem. Surrounding whitespace is ignored; the\nrest must be 1 to 256 characters.\n"
|
|
9238
|
+
} }
|
|
9239
|
+
},
|
|
9240
|
+
"CreditRedemption": {
|
|
9241
|
+
"type": "object",
|
|
9242
|
+
"additionalProperties": false,
|
|
9243
|
+
"required": [
|
|
9244
|
+
"redemption_id",
|
|
9245
|
+
"amount_micros",
|
|
9246
|
+
"currency",
|
|
9247
|
+
"granted_at",
|
|
9248
|
+
"expires_at",
|
|
9249
|
+
"label",
|
|
9250
|
+
"replayed",
|
|
9251
|
+
"message"
|
|
9252
|
+
],
|
|
9253
|
+
"properties": {
|
|
9254
|
+
"redemption_id": {
|
|
9255
|
+
"type": "string",
|
|
9256
|
+
"description": "Identifier of the redemption."
|
|
9257
|
+
},
|
|
9258
|
+
"amount_micros": {
|
|
9259
|
+
"type": "string",
|
|
9260
|
+
"pattern": "^[0-9]+$",
|
|
9261
|
+
"description": "Credit granted, in micros of `currency` (1 USD = 1000000 micros)."
|
|
9262
|
+
},
|
|
9263
|
+
"currency": {
|
|
9264
|
+
"type": "string",
|
|
9265
|
+
"description": "Currency of the granted credit, for example `usd`."
|
|
9266
|
+
},
|
|
9267
|
+
"granted_at": {
|
|
9268
|
+
"type": "string",
|
|
9269
|
+
"format": "date-time",
|
|
9270
|
+
"description": "When the credit was granted."
|
|
9271
|
+
},
|
|
9272
|
+
"expires_at": {
|
|
9273
|
+
"type": ["string", "null"],
|
|
9274
|
+
"format": "date-time",
|
|
9275
|
+
"description": "When the granted credit expires, or null when it does not expire."
|
|
9276
|
+
},
|
|
9277
|
+
"label": {
|
|
9278
|
+
"type": ["string", "null"],
|
|
9279
|
+
"description": "Customer-facing label of the promotion, when it has one."
|
|
9280
|
+
},
|
|
9281
|
+
"replayed": {
|
|
9282
|
+
"type": "boolean",
|
|
9283
|
+
"description": "True when this Idempotency-Key already redeemed this code and the\noriginal grant is returned. No second grant is made.\n"
|
|
9284
|
+
},
|
|
9285
|
+
"message": {
|
|
9286
|
+
"type": "string",
|
|
9287
|
+
"description": "Human-readable summary of the grant, suitable to show as is."
|
|
9288
|
+
}
|
|
9289
|
+
}
|
|
9290
|
+
},
|
|
9291
|
+
"CreditBalance": {
|
|
9292
|
+
"type": "object",
|
|
9293
|
+
"required": ["budget"],
|
|
9294
|
+
"properties": {
|
|
9295
|
+
"budget": {
|
|
9296
|
+
"description": "The active agent spending budget an operator funded for top-ups,\nor null when there is none.\n",
|
|
9297
|
+
"type": ["object", "null"],
|
|
9298
|
+
"additionalProperties": false,
|
|
9299
|
+
"required": [
|
|
9300
|
+
"currency",
|
|
9301
|
+
"max_amount_micros",
|
|
9302
|
+
"spent_micros",
|
|
9303
|
+
"remaining_micros",
|
|
9304
|
+
"per_topup_cap_micros",
|
|
9305
|
+
"expires_at",
|
|
9306
|
+
"status"
|
|
9307
|
+
],
|
|
9308
|
+
"properties": {
|
|
9309
|
+
"currency": {
|
|
9310
|
+
"type": "string",
|
|
9311
|
+
"description": "Currency of the budget, for example `usd`."
|
|
9312
|
+
},
|
|
9313
|
+
"max_amount_micros": {
|
|
9314
|
+
"type": "string",
|
|
9315
|
+
"pattern": "^[0-9]+$",
|
|
9316
|
+
"description": "Total allowance the operator funded, in micros."
|
|
9317
|
+
},
|
|
9318
|
+
"spent_micros": {
|
|
9319
|
+
"type": "string",
|
|
9320
|
+
"pattern": "^[0-9]+$",
|
|
9321
|
+
"description": "How much of the allowance has been drawn down, in micros."
|
|
9322
|
+
},
|
|
9323
|
+
"remaining_micros": {
|
|
9324
|
+
"type": "string",
|
|
9325
|
+
"pattern": "^[0-9]+$",
|
|
9326
|
+
"description": "`max_amount_micros - spent_micros`, floored at zero."
|
|
9327
|
+
},
|
|
9328
|
+
"per_topup_cap_micros": {
|
|
9329
|
+
"type": ["string", "null"],
|
|
9330
|
+
"pattern": "^[0-9]+$",
|
|
9331
|
+
"description": "Largest single top-up allowed, in micros, or null for no per-top-up cap."
|
|
9332
|
+
},
|
|
9333
|
+
"expires_at": {
|
|
9334
|
+
"type": ["string", "null"],
|
|
9335
|
+
"format": "date-time",
|
|
9336
|
+
"description": "When the allowance expires, or null for no expiry."
|
|
9337
|
+
},
|
|
9338
|
+
"status": {
|
|
9339
|
+
"type": "string",
|
|
9340
|
+
"description": "Budget status. An active budget reads `active`."
|
|
9341
|
+
}
|
|
9342
|
+
}
|
|
9343
|
+
},
|
|
9344
|
+
"prepaid_credit": {
|
|
9345
|
+
"description": "Prepaid usage credit (top-ups, redeemed credit codes and granted\ncredit) that can still pay for usage, or null when there is none.\nOmitted when an exact figure cannot be given right now; the budget\nis still returned.\n",
|
|
9346
|
+
"type": ["object", "null"],
|
|
9347
|
+
"additionalProperties": false,
|
|
9348
|
+
"required": [
|
|
9349
|
+
"currency",
|
|
9350
|
+
"remaining_micros",
|
|
9351
|
+
"next_expires_at"
|
|
9352
|
+
],
|
|
9353
|
+
"properties": {
|
|
9354
|
+
"currency": {
|
|
9355
|
+
"type": "string",
|
|
9356
|
+
"description": "Currency of the credit, which is the organization billing currency."
|
|
9357
|
+
},
|
|
9358
|
+
"remaining_micros": {
|
|
9359
|
+
"type": "string",
|
|
9360
|
+
"pattern": "^[0-9]+$",
|
|
9361
|
+
"description": "What the credit can still pay for, in micros."
|
|
9362
|
+
},
|
|
9363
|
+
"next_expires_at": {
|
|
9364
|
+
"type": ["string", "null"],
|
|
9365
|
+
"format": "date-time",
|
|
9366
|
+
"description": "Earliest expiry among credits with a balance, or null when none of them expires."
|
|
9367
|
+
}
|
|
9368
|
+
}
|
|
9369
|
+
}
|
|
9370
|
+
}
|
|
9371
|
+
},
|
|
9015
9372
|
"InboxStatus": {
|
|
9016
9373
|
"type": "object",
|
|
9017
9374
|
"additionalProperties": false,
|
|
@@ -11376,14 +11733,16 @@ const openapiDocument = {
|
|
|
11376
11733
|
"oneOf": [
|
|
11377
11734
|
{ "$ref": "#/components/schemas/CompleteWebhookHttpInput" },
|
|
11378
11735
|
{ "$ref": "#/components/schemas/CompleteWebhookExecInput" },
|
|
11379
|
-
{ "$ref": "#/components/schemas/CompleteWebhookStdoutInput" }
|
|
11736
|
+
{ "$ref": "#/components/schemas/CompleteWebhookStdoutInput" },
|
|
11737
|
+
{ "$ref": "#/components/schemas/CompleteWebhookSdkInput" }
|
|
11380
11738
|
],
|
|
11381
11739
|
"discriminator": {
|
|
11382
11740
|
"propertyName": "mode",
|
|
11383
11741
|
"mapping": {
|
|
11384
11742
|
"http": "#/components/schemas/CompleteWebhookHttpInput",
|
|
11385
11743
|
"exec": "#/components/schemas/CompleteWebhookExecInput",
|
|
11386
|
-
"stdout": "#/components/schemas/CompleteWebhookStdoutInput"
|
|
11744
|
+
"stdout": "#/components/schemas/CompleteWebhookStdoutInput",
|
|
11745
|
+
"sdk": "#/components/schemas/CompleteWebhookSdkInput"
|
|
11387
11746
|
}
|
|
11388
11747
|
}
|
|
11389
11748
|
},
|
|
@@ -11537,6 +11896,49 @@ const openapiDocument = {
|
|
|
11537
11896
|
],
|
|
11538
11897
|
"additionalProperties": false
|
|
11539
11898
|
},
|
|
11899
|
+
"CompleteWebhookSdkInput": {
|
|
11900
|
+
"type": "object",
|
|
11901
|
+
"properties": {
|
|
11902
|
+
"queue_id": {
|
|
11903
|
+
"type": "string",
|
|
11904
|
+
"format": "uuid",
|
|
11905
|
+
"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)$"
|
|
11906
|
+
},
|
|
11907
|
+
"delivery_id": {
|
|
11908
|
+
"type": "string",
|
|
11909
|
+
"format": "uuid",
|
|
11910
|
+
"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)$"
|
|
11911
|
+
},
|
|
11912
|
+
"lease_token": {
|
|
11913
|
+
"type": "string",
|
|
11914
|
+
"format": "uuid",
|
|
11915
|
+
"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)$"
|
|
11916
|
+
},
|
|
11917
|
+
"duration_ms": {
|
|
11918
|
+
"type": "integer",
|
|
11919
|
+
"minimum": 0,
|
|
11920
|
+
"maximum": 3e4
|
|
11921
|
+
},
|
|
11922
|
+
"mode": {
|
|
11923
|
+
"type": "string",
|
|
11924
|
+
"enum": ["sdk"]
|
|
11925
|
+
},
|
|
11926
|
+
"accepted": { "type": "boolean" },
|
|
11927
|
+
"transport_error": {
|
|
11928
|
+
"type": "string",
|
|
11929
|
+
"enum": ["timeout", "io"]
|
|
11930
|
+
}
|
|
11931
|
+
},
|
|
11932
|
+
"required": [
|
|
11933
|
+
"queue_id",
|
|
11934
|
+
"delivery_id",
|
|
11935
|
+
"lease_token",
|
|
11936
|
+
"duration_ms",
|
|
11937
|
+
"mode",
|
|
11938
|
+
"accepted"
|
|
11939
|
+
],
|
|
11940
|
+
"additionalProperties": false
|
|
11941
|
+
},
|
|
11540
11942
|
"CompleteWebhookResponse": {
|
|
11541
11943
|
"type": "object",
|
|
11542
11944
|
"properties": {
|
|
@@ -11570,6 +11972,35 @@ const openapiDocument = {
|
|
|
11570
11972
|
"Endpoint": {
|
|
11571
11973
|
"type": "object",
|
|
11572
11974
|
"properties": {
|
|
11975
|
+
"recipient": {
|
|
11976
|
+
"type": ["string", "null"],
|
|
11977
|
+
"description": "Server-enforced recipient for an address-scoped pull subscription; null for account-wide receiving."
|
|
11978
|
+
},
|
|
11979
|
+
"receiver_capabilities": {
|
|
11980
|
+
"type": "object",
|
|
11981
|
+
"properties": {
|
|
11982
|
+
"completion_modes": {
|
|
11983
|
+
"type": "array",
|
|
11984
|
+
"items": {
|
|
11985
|
+
"type": "string",
|
|
11986
|
+
"enum": [
|
|
11987
|
+
"http",
|
|
11988
|
+
"exec",
|
|
11989
|
+
"stdout",
|
|
11990
|
+
"sdk"
|
|
11991
|
+
]
|
|
11992
|
+
}
|
|
11993
|
+
},
|
|
11994
|
+
"stream_protocols": {
|
|
11995
|
+
"type": "array",
|
|
11996
|
+
"items": {
|
|
11997
|
+
"type": "string",
|
|
11998
|
+
"enum": ["primitive.events.v1"]
|
|
11999
|
+
}
|
|
12000
|
+
}
|
|
12001
|
+
},
|
|
12002
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
12003
|
+
},
|
|
11573
12004
|
"id": {
|
|
11574
12005
|
"type": "string",
|
|
11575
12006
|
"format": "uuid"
|
|
@@ -15380,6 +15811,181 @@ const operationManifest = [
|
|
|
15380
15811
|
"tag": "CLI",
|
|
15381
15812
|
"tagCommand": "cli"
|
|
15382
15813
|
},
|
|
15814
|
+
{
|
|
15815
|
+
"binaryResponse": false,
|
|
15816
|
+
"bodyRequired": false,
|
|
15817
|
+
"command": "get-credit-balance",
|
|
15818
|
+
"description": "Read the organization credit position. `prepaid_credit` is the prepaid\nusage credit (paid top-ups, redeemed credit codes and granted credit)\nthat can still pay for usage. `budget` is the active agent spending\nbudget an operator funded for top-ups, or null when there is none.\nAmounts are strings of integer micros (1 USD = 1000000 micros).\n",
|
|
15819
|
+
"hasJsonBody": false,
|
|
15820
|
+
"method": "GET",
|
|
15821
|
+
"operationId": "getCreditBalance",
|
|
15822
|
+
"path": "/credits/balance",
|
|
15823
|
+
"pathParams": [],
|
|
15824
|
+
"queryParams": [],
|
|
15825
|
+
"requestSchema": null,
|
|
15826
|
+
"responseSchema": {
|
|
15827
|
+
"type": "object",
|
|
15828
|
+
"required": ["budget"],
|
|
15829
|
+
"properties": {
|
|
15830
|
+
"budget": {
|
|
15831
|
+
"description": "The active agent spending budget an operator funded for top-ups,\nor null when there is none.\n",
|
|
15832
|
+
"type": ["object", "null"],
|
|
15833
|
+
"additionalProperties": false,
|
|
15834
|
+
"required": [
|
|
15835
|
+
"currency",
|
|
15836
|
+
"max_amount_micros",
|
|
15837
|
+
"spent_micros",
|
|
15838
|
+
"remaining_micros",
|
|
15839
|
+
"per_topup_cap_micros",
|
|
15840
|
+
"expires_at",
|
|
15841
|
+
"status"
|
|
15842
|
+
],
|
|
15843
|
+
"properties": {
|
|
15844
|
+
"currency": {
|
|
15845
|
+
"type": "string",
|
|
15846
|
+
"description": "Currency of the budget, for example `usd`."
|
|
15847
|
+
},
|
|
15848
|
+
"max_amount_micros": {
|
|
15849
|
+
"type": "string",
|
|
15850
|
+
"pattern": "^[0-9]+$",
|
|
15851
|
+
"description": "Total allowance the operator funded, in micros."
|
|
15852
|
+
},
|
|
15853
|
+
"spent_micros": {
|
|
15854
|
+
"type": "string",
|
|
15855
|
+
"pattern": "^[0-9]+$",
|
|
15856
|
+
"description": "How much of the allowance has been drawn down, in micros."
|
|
15857
|
+
},
|
|
15858
|
+
"remaining_micros": {
|
|
15859
|
+
"type": "string",
|
|
15860
|
+
"pattern": "^[0-9]+$",
|
|
15861
|
+
"description": "`max_amount_micros - spent_micros`, floored at zero."
|
|
15862
|
+
},
|
|
15863
|
+
"per_topup_cap_micros": {
|
|
15864
|
+
"type": ["string", "null"],
|
|
15865
|
+
"pattern": "^[0-9]+$",
|
|
15866
|
+
"description": "Largest single top-up allowed, in micros, or null for no per-top-up cap."
|
|
15867
|
+
},
|
|
15868
|
+
"expires_at": {
|
|
15869
|
+
"type": ["string", "null"],
|
|
15870
|
+
"format": "date-time",
|
|
15871
|
+
"description": "When the allowance expires, or null for no expiry."
|
|
15872
|
+
},
|
|
15873
|
+
"status": {
|
|
15874
|
+
"type": "string",
|
|
15875
|
+
"description": "Budget status. An active budget reads `active`."
|
|
15876
|
+
}
|
|
15877
|
+
}
|
|
15878
|
+
},
|
|
15879
|
+
"prepaid_credit": {
|
|
15880
|
+
"description": "Prepaid usage credit (top-ups, redeemed credit codes and granted\ncredit) that can still pay for usage, or null when there is none.\nOmitted when an exact figure cannot be given right now; the budget\nis still returned.\n",
|
|
15881
|
+
"type": ["object", "null"],
|
|
15882
|
+
"additionalProperties": false,
|
|
15883
|
+
"required": [
|
|
15884
|
+
"currency",
|
|
15885
|
+
"remaining_micros",
|
|
15886
|
+
"next_expires_at"
|
|
15887
|
+
],
|
|
15888
|
+
"properties": {
|
|
15889
|
+
"currency": {
|
|
15890
|
+
"type": "string",
|
|
15891
|
+
"description": "Currency of the credit, which is the organization billing currency."
|
|
15892
|
+
},
|
|
15893
|
+
"remaining_micros": {
|
|
15894
|
+
"type": "string",
|
|
15895
|
+
"pattern": "^[0-9]+$",
|
|
15896
|
+
"description": "What the credit can still pay for, in micros."
|
|
15897
|
+
},
|
|
15898
|
+
"next_expires_at": {
|
|
15899
|
+
"type": ["string", "null"],
|
|
15900
|
+
"format": "date-time",
|
|
15901
|
+
"description": "Earliest expiry among credits with a balance, or null when none of them expires."
|
|
15902
|
+
}
|
|
15903
|
+
}
|
|
15904
|
+
}
|
|
15905
|
+
}
|
|
15906
|
+
},
|
|
15907
|
+
"sdkName": "getCreditBalance",
|
|
15908
|
+
"summary": "Get credit balance",
|
|
15909
|
+
"tag": "Credits",
|
|
15910
|
+
"tagCommand": "credits"
|
|
15911
|
+
},
|
|
15912
|
+
{
|
|
15913
|
+
"binaryResponse": false,
|
|
15914
|
+
"bodyRequired": true,
|
|
15915
|
+
"command": "redeem-credit-code",
|
|
15916
|
+
"description": "Redeem a credit code for the authenticated organization. The credit is\nadded to the organization prepaid credit and shows in\n`GET /credits/balance` under `prepaid_credit`.\n\nRedeeming requires an organization owner or admin. An API key redeems\nwith the authority of the user who created it, based on that user's\ncurrent role; a key without that authority gets the same\n`credit_code_invalid` refusal as an unknown code.\n\nThe `Idempotency-Key` header is required. Retrying with the same key\nand code returns the original grant with `replayed: true` and grants\nnothing new; the same key with a different code is refused with\n`idempotency_key_reused`. Every refusal carries a human-readable\n`error.message` that can be shown to the user as is.\n",
|
|
15917
|
+
"hasJsonBody": true,
|
|
15918
|
+
"method": "POST",
|
|
15919
|
+
"operationId": "redeemCreditCode",
|
|
15920
|
+
"path": "/credits/redeem",
|
|
15921
|
+
"pathParams": [],
|
|
15922
|
+
"queryParams": [],
|
|
15923
|
+
"requestSchema": {
|
|
15924
|
+
"type": "object",
|
|
15925
|
+
"additionalProperties": false,
|
|
15926
|
+
"required": ["code"],
|
|
15927
|
+
"properties": { "code": {
|
|
15928
|
+
"type": "string",
|
|
15929
|
+
"pattern": "^\\s*\\S(?:[\\s\\S]{0,254}\\S)?\\s*$",
|
|
15930
|
+
"description": "The credit code to redeem. Surrounding whitespace is ignored; the\nrest must be 1 to 256 characters.\n"
|
|
15931
|
+
} }
|
|
15932
|
+
},
|
|
15933
|
+
"responseSchema": {
|
|
15934
|
+
"type": "object",
|
|
15935
|
+
"additionalProperties": false,
|
|
15936
|
+
"required": [
|
|
15937
|
+
"redemption_id",
|
|
15938
|
+
"amount_micros",
|
|
15939
|
+
"currency",
|
|
15940
|
+
"granted_at",
|
|
15941
|
+
"expires_at",
|
|
15942
|
+
"label",
|
|
15943
|
+
"replayed",
|
|
15944
|
+
"message"
|
|
15945
|
+
],
|
|
15946
|
+
"properties": {
|
|
15947
|
+
"redemption_id": {
|
|
15948
|
+
"type": "string",
|
|
15949
|
+
"description": "Identifier of the redemption."
|
|
15950
|
+
},
|
|
15951
|
+
"amount_micros": {
|
|
15952
|
+
"type": "string",
|
|
15953
|
+
"pattern": "^[0-9]+$",
|
|
15954
|
+
"description": "Credit granted, in micros of `currency` (1 USD = 1000000 micros)."
|
|
15955
|
+
},
|
|
15956
|
+
"currency": {
|
|
15957
|
+
"type": "string",
|
|
15958
|
+
"description": "Currency of the granted credit, for example `usd`."
|
|
15959
|
+
},
|
|
15960
|
+
"granted_at": {
|
|
15961
|
+
"type": "string",
|
|
15962
|
+
"format": "date-time",
|
|
15963
|
+
"description": "When the credit was granted."
|
|
15964
|
+
},
|
|
15965
|
+
"expires_at": {
|
|
15966
|
+
"type": ["string", "null"],
|
|
15967
|
+
"format": "date-time",
|
|
15968
|
+
"description": "When the granted credit expires, or null when it does not expire."
|
|
15969
|
+
},
|
|
15970
|
+
"label": {
|
|
15971
|
+
"type": ["string", "null"],
|
|
15972
|
+
"description": "Customer-facing label of the promotion, when it has one."
|
|
15973
|
+
},
|
|
15974
|
+
"replayed": {
|
|
15975
|
+
"type": "boolean",
|
|
15976
|
+
"description": "True when this Idempotency-Key already redeemed this code and the\noriginal grant is returned. No second grant is made.\n"
|
|
15977
|
+
},
|
|
15978
|
+
"message": {
|
|
15979
|
+
"type": "string",
|
|
15980
|
+
"description": "Human-readable summary of the grant, suitable to show as is."
|
|
15981
|
+
}
|
|
15982
|
+
}
|
|
15983
|
+
},
|
|
15984
|
+
"sdkName": "redeemCreditCode",
|
|
15985
|
+
"summary": "Redeem a credit code",
|
|
15986
|
+
"tag": "Credits",
|
|
15987
|
+
"tagCommand": "credits"
|
|
15988
|
+
},
|
|
15383
15989
|
{
|
|
15384
15990
|
"binaryResponse": false,
|
|
15385
15991
|
"bodyRequired": true,
|
|
@@ -17879,6 +18485,49 @@ const operationManifest = [
|
|
|
17879
18485
|
"write_succeeded"
|
|
17880
18486
|
],
|
|
17881
18487
|
"additionalProperties": false
|
|
18488
|
+
},
|
|
18489
|
+
{
|
|
18490
|
+
"type": "object",
|
|
18491
|
+
"properties": {
|
|
18492
|
+
"queue_id": {
|
|
18493
|
+
"type": "string",
|
|
18494
|
+
"format": "uuid",
|
|
18495
|
+
"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)$"
|
|
18496
|
+
},
|
|
18497
|
+
"delivery_id": {
|
|
18498
|
+
"type": "string",
|
|
18499
|
+
"format": "uuid",
|
|
18500
|
+
"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)$"
|
|
18501
|
+
},
|
|
18502
|
+
"lease_token": {
|
|
18503
|
+
"type": "string",
|
|
18504
|
+
"format": "uuid",
|
|
18505
|
+
"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)$"
|
|
18506
|
+
},
|
|
18507
|
+
"duration_ms": {
|
|
18508
|
+
"type": "integer",
|
|
18509
|
+
"minimum": 0,
|
|
18510
|
+
"maximum": 3e4
|
|
18511
|
+
},
|
|
18512
|
+
"mode": {
|
|
18513
|
+
"type": "string",
|
|
18514
|
+
"enum": ["sdk"]
|
|
18515
|
+
},
|
|
18516
|
+
"accepted": { "type": "boolean" },
|
|
18517
|
+
"transport_error": {
|
|
18518
|
+
"type": "string",
|
|
18519
|
+
"enum": ["timeout", "io"]
|
|
18520
|
+
}
|
|
18521
|
+
},
|
|
18522
|
+
"required": [
|
|
18523
|
+
"queue_id",
|
|
18524
|
+
"delivery_id",
|
|
18525
|
+
"lease_token",
|
|
18526
|
+
"duration_ms",
|
|
18527
|
+
"mode",
|
|
18528
|
+
"accepted"
|
|
18529
|
+
],
|
|
18530
|
+
"additionalProperties": false
|
|
17882
18531
|
}
|
|
17883
18532
|
],
|
|
17884
18533
|
"discriminator": {
|
|
@@ -17886,7 +18535,8 @@ const operationManifest = [
|
|
|
17886
18535
|
"mapping": {
|
|
17887
18536
|
"http": "#/components/schemas/CompleteWebhookHttpInput",
|
|
17888
18537
|
"exec": "#/components/schemas/CompleteWebhookExecInput",
|
|
17889
|
-
"stdout": "#/components/schemas/CompleteWebhookStdoutInput"
|
|
18538
|
+
"stdout": "#/components/schemas/CompleteWebhookStdoutInput",
|
|
18539
|
+
"sdk": "#/components/schemas/CompleteWebhookSdkInput"
|
|
17890
18540
|
}
|
|
17891
18541
|
}
|
|
17892
18542
|
},
|
|
@@ -17929,7 +18579,7 @@ const operationManifest = [
|
|
|
17929
18579
|
"binaryResponse": false,
|
|
17930
18580
|
"bodyRequired": true,
|
|
17931
18581
|
"command": "create-endpoint",
|
|
17932
|
-
"description": "Creates a new webhook endpoint. If a deactivated endpoint\nwith the same URL and domain exists, it is reactivated\ninstead. Subject to plan limits on the number of active\nendpoints.\n\n**Signing is account-scoped, not per-endpoint.** This call\ndoes not return any signing material; every endpoint on the\naccount uses the same webhook secret, fetched via\n`GET /account/webhook-secret`. See the API-level \"Webhook\nsigning\" section for the full wire format (header name,\nsigned string, hash algo, secret format, tolerance) and a\nlanguage-agnostic verification recipe.\n\nAfter creating the endpoint, fire a test delivery against\nit via `POST /endpoints/{id}/test` to confirm your verifier\naccepts the signature.\n\n\nFor local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. New unfiltered destinations receive all subsequently ready eligible event types. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.",
|
|
18582
|
+
"description": "Creates a new webhook endpoint. If a deactivated endpoint\nwith the same URL and domain exists, it is reactivated\ninstead. Subject to plan limits on the number of active\nendpoints.\n\n**Signing is account-scoped, not per-endpoint.** This call\ndoes not return any signing material; every endpoint on the\naccount uses the same webhook secret, fetched via\n`GET /account/webhook-secret`. See the API-level \"Webhook\nsigning\" section for the full wire format (header name,\nsigned string, hash algo, secret format, tolerance) and a\nlanguage-agnostic verification recipe.\n\nAfter creating the endpoint, fire a test delivery against\nit via `POST /endpoints/{id}/test` to confirm your verifier\naccepts the signature.\n\n\nFor local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. New unfiltered destinations receive all subsequently ready eligible event types within their credential scope. Connected-agent credentials automatically create private subscriptions restricted to inbound email for their assigned address. Names are isolated per credential; revocation or replacement deletes its subscriptions. Address-scoped events contain parsed mail without raw MIME, signed download links, routing metadata, or sibling envelope recipients. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.",
|
|
17933
18583
|
"hasJsonBody": true,
|
|
17934
18584
|
"method": "POST",
|
|
17935
18585
|
"operationId": "createEndpoint",
|
|
@@ -17997,6 +18647,35 @@ const operationManifest = [
|
|
|
17997
18647
|
"responseSchema": {
|
|
17998
18648
|
"type": "object",
|
|
17999
18649
|
"properties": {
|
|
18650
|
+
"recipient": {
|
|
18651
|
+
"type": ["string", "null"],
|
|
18652
|
+
"description": "Server-enforced recipient for an address-scoped pull subscription; null for account-wide receiving."
|
|
18653
|
+
},
|
|
18654
|
+
"receiver_capabilities": {
|
|
18655
|
+
"type": "object",
|
|
18656
|
+
"properties": {
|
|
18657
|
+
"completion_modes": {
|
|
18658
|
+
"type": "array",
|
|
18659
|
+
"items": {
|
|
18660
|
+
"type": "string",
|
|
18661
|
+
"enum": [
|
|
18662
|
+
"http",
|
|
18663
|
+
"exec",
|
|
18664
|
+
"stdout",
|
|
18665
|
+
"sdk"
|
|
18666
|
+
]
|
|
18667
|
+
}
|
|
18668
|
+
},
|
|
18669
|
+
"stream_protocols": {
|
|
18670
|
+
"type": "array",
|
|
18671
|
+
"items": {
|
|
18672
|
+
"type": "string",
|
|
18673
|
+
"enum": ["primitive.events.v1"]
|
|
18674
|
+
}
|
|
18675
|
+
}
|
|
18676
|
+
},
|
|
18677
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
18678
|
+
},
|
|
18000
18679
|
"id": {
|
|
18001
18680
|
"type": "string",
|
|
18002
18681
|
"format": "uuid"
|
|
@@ -18148,6 +18827,35 @@ const operationManifest = [
|
|
|
18148
18827
|
"items": {
|
|
18149
18828
|
"type": "object",
|
|
18150
18829
|
"properties": {
|
|
18830
|
+
"recipient": {
|
|
18831
|
+
"type": ["string", "null"],
|
|
18832
|
+
"description": "Server-enforced recipient for an address-scoped pull subscription; null for account-wide receiving."
|
|
18833
|
+
},
|
|
18834
|
+
"receiver_capabilities": {
|
|
18835
|
+
"type": "object",
|
|
18836
|
+
"properties": {
|
|
18837
|
+
"completion_modes": {
|
|
18838
|
+
"type": "array",
|
|
18839
|
+
"items": {
|
|
18840
|
+
"type": "string",
|
|
18841
|
+
"enum": [
|
|
18842
|
+
"http",
|
|
18843
|
+
"exec",
|
|
18844
|
+
"stdout",
|
|
18845
|
+
"sdk"
|
|
18846
|
+
]
|
|
18847
|
+
}
|
|
18848
|
+
},
|
|
18849
|
+
"stream_protocols": {
|
|
18850
|
+
"type": "array",
|
|
18851
|
+
"items": {
|
|
18852
|
+
"type": "string",
|
|
18853
|
+
"enum": ["primitive.events.v1"]
|
|
18854
|
+
}
|
|
18855
|
+
}
|
|
18856
|
+
},
|
|
18857
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
18858
|
+
},
|
|
18151
18859
|
"id": {
|
|
18152
18860
|
"type": "string",
|
|
18153
18861
|
"format": "uuid"
|
|
@@ -18589,6 +19297,35 @@ const operationManifest = [
|
|
|
18589
19297
|
"responseSchema": {
|
|
18590
19298
|
"type": "object",
|
|
18591
19299
|
"properties": {
|
|
19300
|
+
"recipient": {
|
|
19301
|
+
"type": ["string", "null"],
|
|
19302
|
+
"description": "Server-enforced recipient for an address-scoped pull subscription; null for account-wide receiving."
|
|
19303
|
+
},
|
|
19304
|
+
"receiver_capabilities": {
|
|
19305
|
+
"type": "object",
|
|
19306
|
+
"properties": {
|
|
19307
|
+
"completion_modes": {
|
|
19308
|
+
"type": "array",
|
|
19309
|
+
"items": {
|
|
19310
|
+
"type": "string",
|
|
19311
|
+
"enum": [
|
|
19312
|
+
"http",
|
|
19313
|
+
"exec",
|
|
19314
|
+
"stdout",
|
|
19315
|
+
"sdk"
|
|
19316
|
+
]
|
|
19317
|
+
}
|
|
19318
|
+
},
|
|
19319
|
+
"stream_protocols": {
|
|
19320
|
+
"type": "array",
|
|
19321
|
+
"items": {
|
|
19322
|
+
"type": "string",
|
|
19323
|
+
"enum": ["primitive.events.v1"]
|
|
19324
|
+
}
|
|
19325
|
+
}
|
|
19326
|
+
},
|
|
19327
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
19328
|
+
},
|
|
18592
19329
|
"id": {
|
|
18593
19330
|
"type": "string",
|
|
18594
19331
|
"format": "uuid"
|
|
@@ -24914,6 +25651,20 @@ const operationManifest = [
|
|
|
24914
25651
|
"name": "date_to",
|
|
24915
25652
|
"required": false,
|
|
24916
25653
|
"type": "string"
|
|
25654
|
+
},
|
|
25655
|
+
{
|
|
25656
|
+
"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",
|
|
25657
|
+
"enum": null,
|
|
25658
|
+
"name": "q",
|
|
25659
|
+
"required": false,
|
|
25660
|
+
"type": "string"
|
|
25661
|
+
},
|
|
25662
|
+
{
|
|
25663
|
+
"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",
|
|
25664
|
+
"enum": null,
|
|
25665
|
+
"name": "from",
|
|
25666
|
+
"required": false,
|
|
25667
|
+
"type": "string"
|
|
24917
25668
|
}
|
|
24918
25669
|
],
|
|
24919
25670
|
"requestSchema": null,
|
|
@@ -27601,7 +28352,7 @@ function isMemoryJsonValue(value, seen = /* @__PURE__ */ new Set()) {
|
|
|
27601
28352
|
const API_HEADERS_ENV = "PRIMITIVE_API_HEADERS";
|
|
27602
28353
|
const OAUTH_REFRESH_SKEW_MS = 60 * 1e3;
|
|
27603
28354
|
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) {
|
|
28355
|
+
function mergeHeaders$1(...headers) {
|
|
27605
28356
|
const merged = {};
|
|
27606
28357
|
for (const headerSet of headers) {
|
|
27607
28358
|
if (!headerSet) continue;
|
|
@@ -27639,7 +28390,7 @@ function resolveCliApiRequestConfig(params) {
|
|
|
27639
28390
|
apiBaseUrl,
|
|
27640
28391
|
baseUrlOverridden: apiBaseUrl !== void 0,
|
|
27641
28392
|
environmentName: currentEnvironment?.name ?? null,
|
|
27642
|
-
headers: mergeHeaders(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
|
|
28393
|
+
headers: mergeHeaders$1(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
|
|
27643
28394
|
resolvedApiBaseUrl: normalizeApiBaseUrl(apiBaseUrl)
|
|
27644
28395
|
};
|
|
27645
28396
|
}
|
|
@@ -30189,6 +30940,240 @@ var ConfigCommand = class extends ConfigListCommand {
|
|
|
30189
30940
|
static description = "Manage local Primitive CLI request environments for API endpoint overrides and request headers.";
|
|
30190
30941
|
};
|
|
30191
30942
|
//#endregion
|
|
30943
|
+
//#region src/oclif/commands/credits.ts
|
|
30944
|
+
const API_KEY_FLAG$1 = Flags.string({
|
|
30945
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
30946
|
+
env: "PRIMITIVE_API_KEY"
|
|
30947
|
+
});
|
|
30948
|
+
const API_BASE_URL_FLAG$1 = Flags.string({
|
|
30949
|
+
description: API_BASE_URL_FLAG_DESCRIPTION,
|
|
30950
|
+
env: "PRIMITIVE_API_BASE_URL",
|
|
30951
|
+
hidden: true
|
|
30952
|
+
});
|
|
30953
|
+
const MICROS_PER_UNIT = 1000000n;
|
|
30954
|
+
/**
|
|
30955
|
+
* Format an integer micros string as a currency amount. USD renders as
|
|
30956
|
+
* `$12.50`; other currencies as `12.50 EUR`. Two decimals are always shown,
|
|
30957
|
+
* and up to six when the amount has sub-cent precision. Input that is not a
|
|
30958
|
+
* non-negative integer string is returned unchanged with the currency code.
|
|
30959
|
+
*/
|
|
30960
|
+
function formatMicros(micros, currency) {
|
|
30961
|
+
const code = currency.toUpperCase();
|
|
30962
|
+
if (!/^[0-9]+$/.test(micros)) return `${micros} micros ${code}`.trim();
|
|
30963
|
+
const value = BigInt(micros);
|
|
30964
|
+
const whole = value / MICROS_PER_UNIT;
|
|
30965
|
+
const fraction = (value % MICROS_PER_UNIT).toString().padStart(6, "0").replace(/0+$/, "").padEnd(2, "0");
|
|
30966
|
+
const amount = `${whole.toLocaleString("en-US")}.${fraction}`;
|
|
30967
|
+
return code === "USD" ? `$${amount}` : `${amount} ${code}`;
|
|
30968
|
+
}
|
|
30969
|
+
function formatExpiry(value) {
|
|
30970
|
+
if (!value) return "never";
|
|
30971
|
+
const date = new Date(value);
|
|
30972
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
30973
|
+
return date.toISOString().slice(0, 10);
|
|
30974
|
+
}
|
|
30975
|
+
function formatRedemptionSummary(redemption) {
|
|
30976
|
+
const lines = [redemption.replayed ? `Already redeemed with this idempotency key: ${formatMicros(redemption.amount_micros, redemption.currency)} in credit. No new credit was added.` : `Redeemed ${formatMicros(redemption.amount_micros, redemption.currency)} in credit.`, `Expires: ${formatExpiry(redemption.expires_at)}`];
|
|
30977
|
+
if (redemption.label?.trim()) lines.push(`Promotion: ${redemption.label}`);
|
|
30978
|
+
lines.push(`Redemption id: ${redemption.redemption_id}`);
|
|
30979
|
+
return lines.join("\n");
|
|
30980
|
+
}
|
|
30981
|
+
function formatBalanceSummary(balance) {
|
|
30982
|
+
const lines = [];
|
|
30983
|
+
const prepaid = balance.prepaid_credit;
|
|
30984
|
+
if (prepaid === void 0) lines.push("Prepaid credit: not available right now, try again shortly");
|
|
30985
|
+
else if (prepaid === null) lines.push("Prepaid credit: none");
|
|
30986
|
+
else {
|
|
30987
|
+
const expiry = prepaid.next_expires_at ? `next expiry ${formatExpiry(prepaid.next_expires_at)}` : "no expiry";
|
|
30988
|
+
lines.push(`Prepaid credit: ${formatMicros(prepaid.remaining_micros, prepaid.currency)} (${expiry})`);
|
|
30989
|
+
}
|
|
30990
|
+
const budget = balance.budget;
|
|
30991
|
+
if (!budget) lines.push("Spending budget: none");
|
|
30992
|
+
else {
|
|
30993
|
+
const details = [`status ${budget.status}`];
|
|
30994
|
+
if (budget.per_topup_cap_micros) details.push(`per top-up cap ${formatMicros(budget.per_topup_cap_micros, budget.currency)}`);
|
|
30995
|
+
details.push(`expires ${formatExpiry(budget.expires_at)}`);
|
|
30996
|
+
lines.push(`Spending budget: ${formatMicros(budget.remaining_micros, budget.currency)} of ${formatMicros(budget.max_amount_micros, budget.currency)} remaining (${details.join(", ")})`);
|
|
30997
|
+
}
|
|
30998
|
+
return lines.join("\n");
|
|
30999
|
+
}
|
|
31000
|
+
/**
|
|
31001
|
+
* Lines printed after a failed redemption so the user can retry without risk
|
|
31002
|
+
* of a second grant: reusing the same key and code returns the original grant
|
|
31003
|
+
* if the first attempt did land. No shell command is printed, since quoting
|
|
31004
|
+
* differs between shells.
|
|
31005
|
+
*/
|
|
31006
|
+
function formatRetryHint(idempotencyKey) {
|
|
31007
|
+
return [`Idempotency-Key: ${idempotencyKey}`, `To retry this redemption safely, run the same command again with --idempotency-key ${idempotencyKey}.`].join("\n");
|
|
31008
|
+
}
|
|
31009
|
+
/**
|
|
31010
|
+
* Print a failed request. A server refusal (an error envelope with a
|
|
31011
|
+
* message) prints the message as the primary line, since the redeem route
|
|
31012
|
+
* writes it to be shown to the user; anything else falls back to the shared
|
|
31013
|
+
* error renderer. Always sets a non-zero exit code.
|
|
31014
|
+
*/
|
|
31015
|
+
function reportCreditsError(error, context, options) {
|
|
31016
|
+
const payload = extractErrorPayload(error);
|
|
31017
|
+
const body = payload;
|
|
31018
|
+
if (!options.json && body && typeof body === "object" && typeof body.message === "string" && typeof body.code === "string" && body.code !== "unauthorized") {
|
|
31019
|
+
process.stderr.write(`${options.prefix}: ${body.message}\n`);
|
|
31020
|
+
process.stderr.write(`Error code: ${body.code}\n`);
|
|
31021
|
+
} else writeErrorWithHints(payload);
|
|
31022
|
+
surfaceUnauthorizedHint({
|
|
31023
|
+
...context,
|
|
31024
|
+
payload
|
|
31025
|
+
});
|
|
31026
|
+
process.exitCode = 1;
|
|
31027
|
+
}
|
|
31028
|
+
var CreditsRedeemCommand = class CreditsRedeemCommand extends Command {
|
|
31029
|
+
static description = `Redeem a credit code for your organization.
|
|
31030
|
+
|
|
31031
|
+
Prints the credit added and when it expires. On a refusal (an invalid,
|
|
31032
|
+
already redeemed or ineligible code, for example) the server's message is
|
|
31033
|
+
printed and the command exits non-zero.
|
|
31034
|
+
|
|
31035
|
+
Redeeming needs an organization owner or admin. With an API key, the key's
|
|
31036
|
+
creator must currently be an owner or admin.
|
|
31037
|
+
|
|
31038
|
+
The request carries an Idempotency-Key. One is generated for each run; pass
|
|
31039
|
+
--idempotency-key to retry the same redemption safely, for example after a
|
|
31040
|
+
network error. Retrying with the same key and code returns the original
|
|
31041
|
+
grant and adds no second credit. On any failure the key in use is printed to
|
|
31042
|
+
stderr so the same command can be rerun with --idempotency-key.`;
|
|
31043
|
+
static summary = "Redeem a credit code";
|
|
31044
|
+
static examples = [
|
|
31045
|
+
"<%= config.bin %> credits redeem LAUNCH50",
|
|
31046
|
+
"<%= config.bin %> credits redeem LAUNCH50 --idempotency-key redeem-launch50-1",
|
|
31047
|
+
"<%= config.bin %> credits redeem LAUNCH50 --json"
|
|
31048
|
+
];
|
|
31049
|
+
static args = { code: Args.string({
|
|
31050
|
+
description: "The credit code to redeem.",
|
|
31051
|
+
required: true
|
|
31052
|
+
}) };
|
|
31053
|
+
static flags = {
|
|
31054
|
+
"api-key": API_KEY_FLAG$1,
|
|
31055
|
+
"api-base-url": API_BASE_URL_FLAG$1,
|
|
31056
|
+
"idempotency-key": Flags.string({ description: "Idempotency key for the request, at most 200 characters. Defaults to a new random key per run; reuse a key only to retry the same code." }),
|
|
31057
|
+
json: Flags.boolean({ description: "Print the full redemption JSON on success, or the error JSON on failure." }),
|
|
31058
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
31059
|
+
};
|
|
31060
|
+
async run() {
|
|
31061
|
+
const { args, flags } = await this.parse(CreditsRedeemCommand);
|
|
31062
|
+
const code = args.code.trim();
|
|
31063
|
+
if (!code) {
|
|
31064
|
+
process.stderr.write("The credit code must not be empty.\n");
|
|
31065
|
+
process.exitCode = 1;
|
|
31066
|
+
return;
|
|
31067
|
+
}
|
|
31068
|
+
const idempotencyKey = flags["idempotency-key"]?.trim() || `cli-redeem-${randomUUID()}`;
|
|
31069
|
+
if (idempotencyKey.length > 200) {
|
|
31070
|
+
process.stderr.write("--idempotency-key must be at most 200 characters.\n");
|
|
31071
|
+
process.exitCode = 1;
|
|
31072
|
+
return;
|
|
31073
|
+
}
|
|
31074
|
+
if (!/^[\x21-\x7E]+$/.test(idempotencyKey)) {
|
|
31075
|
+
process.stderr.write("--idempotency-key must contain only printable ASCII characters, without spaces.\n");
|
|
31076
|
+
process.exitCode = 1;
|
|
31077
|
+
return;
|
|
31078
|
+
}
|
|
31079
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
31080
|
+
apiKey: flags["api-key"],
|
|
31081
|
+
apiBaseUrl: flags["api-base-url"],
|
|
31082
|
+
configDir: this.config.configDir
|
|
31083
|
+
});
|
|
31084
|
+
const writeRetryHint = () => process.stderr.write(`${formatRetryHint(idempotencyKey)}\n`);
|
|
31085
|
+
await runWithTiming(flags.time, async () => {
|
|
31086
|
+
let result;
|
|
31087
|
+
try {
|
|
31088
|
+
result = await redeemCreditCode({
|
|
31089
|
+
client: apiClient.client,
|
|
31090
|
+
body: { code },
|
|
31091
|
+
headers: { "Idempotency-Key": idempotencyKey },
|
|
31092
|
+
responseStyle: "fields"
|
|
31093
|
+
});
|
|
31094
|
+
} catch (error) {
|
|
31095
|
+
writeRetryHint();
|
|
31096
|
+
throw error;
|
|
31097
|
+
}
|
|
31098
|
+
if (result.error) {
|
|
31099
|
+
reportCreditsError(result.error, {
|
|
31100
|
+
auth,
|
|
31101
|
+
baseUrlOverridden,
|
|
31102
|
+
configDir: this.config.configDir
|
|
31103
|
+
}, {
|
|
31104
|
+
json: Boolean(flags.json),
|
|
31105
|
+
prefix: `Could not redeem ${code}`
|
|
31106
|
+
});
|
|
31107
|
+
writeRetryHint();
|
|
31108
|
+
return;
|
|
31109
|
+
}
|
|
31110
|
+
const redemption = result.data?.data;
|
|
31111
|
+
if (!redemption) {
|
|
31112
|
+
process.stderr.write("Server returned an empty redemption body.\n");
|
|
31113
|
+
writeRetryHint();
|
|
31114
|
+
process.exitCode = 1;
|
|
31115
|
+
return;
|
|
31116
|
+
}
|
|
31117
|
+
if (flags.json) {
|
|
31118
|
+
this.log(JSON.stringify(redemption, null, 2));
|
|
31119
|
+
return;
|
|
31120
|
+
}
|
|
31121
|
+
this.log(formatRedemptionSummary(redemption));
|
|
31122
|
+
});
|
|
31123
|
+
}
|
|
31124
|
+
};
|
|
31125
|
+
var CreditsBalanceCommand = class CreditsBalanceCommand extends Command {
|
|
31126
|
+
static description = `Show your organization's credit balance.
|
|
31127
|
+
|
|
31128
|
+
Prints the prepaid credit (top-ups, redeemed credit codes and granted
|
|
31129
|
+
credit) that can still pay for usage, and the agent spending budget when an
|
|
31130
|
+
operator has funded one. Pass --json for the raw response.`;
|
|
31131
|
+
static summary = "Show the credit balance";
|
|
31132
|
+
static examples = ["<%= config.bin %> credits balance", "<%= config.bin %> credits balance --json"];
|
|
31133
|
+
static flags = {
|
|
31134
|
+
"api-key": API_KEY_FLAG$1,
|
|
31135
|
+
"api-base-url": API_BASE_URL_FLAG$1,
|
|
31136
|
+
json: Flags.boolean({ description: "Print the full balance JSON response." }),
|
|
31137
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
31138
|
+
};
|
|
31139
|
+
async run() {
|
|
31140
|
+
const { flags } = await this.parse(CreditsBalanceCommand);
|
|
31141
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
31142
|
+
apiKey: flags["api-key"],
|
|
31143
|
+
apiBaseUrl: flags["api-base-url"],
|
|
31144
|
+
configDir: this.config.configDir
|
|
31145
|
+
});
|
|
31146
|
+
await runWithTiming(flags.time, async () => {
|
|
31147
|
+
const result = await getCreditBalance({
|
|
31148
|
+
client: apiClient.client,
|
|
31149
|
+
responseStyle: "fields"
|
|
31150
|
+
});
|
|
31151
|
+
if (result.error) {
|
|
31152
|
+
reportCreditsError(result.error, {
|
|
31153
|
+
auth,
|
|
31154
|
+
baseUrlOverridden,
|
|
31155
|
+
configDir: this.config.configDir
|
|
31156
|
+
}, {
|
|
31157
|
+
json: Boolean(flags.json),
|
|
31158
|
+
prefix: "Could not read the balance"
|
|
31159
|
+
});
|
|
31160
|
+
return;
|
|
31161
|
+
}
|
|
31162
|
+
const balance = result.data?.data;
|
|
31163
|
+
if (!balance) {
|
|
31164
|
+
process.stderr.write("Server returned an empty balance body.\n");
|
|
31165
|
+
process.exitCode = 1;
|
|
31166
|
+
return;
|
|
31167
|
+
}
|
|
31168
|
+
if (flags.json) {
|
|
31169
|
+
this.log(JSON.stringify(balance, null, 2));
|
|
31170
|
+
return;
|
|
31171
|
+
}
|
|
31172
|
+
this.log(formatBalanceSummary(balance));
|
|
31173
|
+
});
|
|
31174
|
+
}
|
|
31175
|
+
};
|
|
31176
|
+
//#endregion
|
|
30192
31177
|
//#region src/oclif/commands/doctor.ts
|
|
30193
31178
|
const MIN_NODE_MAJOR = 22;
|
|
30194
31179
|
function renderRow({ label, outcome }) {
|
|
@@ -32071,8 +33056,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
32071
33056
|
name: "Primitive Team",
|
|
32072
33057
|
url: "https://primitive.dev"
|
|
32073
33058
|
};
|
|
32074
|
-
const SDK_VERSION_RANGE = "^1.
|
|
32075
|
-
const CLI_VERSION_RANGE = "^1.
|
|
33059
|
+
const SDK_VERSION_RANGE = "^1.29.0";
|
|
33060
|
+
const CLI_VERSION_RANGE = "^1.29.0";
|
|
32076
33061
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
32077
33062
|
function renderHandler() {
|
|
32078
33063
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -34106,6 +35091,11 @@ const FORWARDED_HEADERS = new Set([
|
|
|
34106
35091
|
"x-primitive-email-id",
|
|
34107
35092
|
"x-primitive-endpoint-id"
|
|
34108
35093
|
]);
|
|
35094
|
+
function normalizeForwardTarget(target) {
|
|
35095
|
+
if (/^\d+$/.test(target)) return `http://localhost:${target}`;
|
|
35096
|
+
if (/^(localhost|127\.0\.0\.1|\[::1\])(?::|\/|$)/.test(target)) return `http://${target}`;
|
|
35097
|
+
return target;
|
|
35098
|
+
}
|
|
34109
35099
|
function validateListenHandlerOptions(options) {
|
|
34110
35100
|
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
35101
|
if (options.exec !== void 0 && options.forwardTo !== void 0) throw new Error("Choose either --exec or --forward-to.");
|
|
@@ -34113,7 +35103,7 @@ function validateListenHandlerOptions(options) {
|
|
|
34113
35103
|
if (options.forwardTo !== void 0) {
|
|
34114
35104
|
let url;
|
|
34115
35105
|
try {
|
|
34116
|
-
url = new URL(options.forwardTo);
|
|
35106
|
+
url = new URL(normalizeForwardTarget(options.forwardTo));
|
|
34117
35107
|
} catch {
|
|
34118
35108
|
throw new Error("--forward-to requires an HTTP or HTTPS URL.");
|
|
34119
35109
|
}
|
|
@@ -34258,7 +35248,7 @@ var ForwardFailure = class extends Error {
|
|
|
34258
35248
|
};
|
|
34259
35249
|
async function forward(target, delivery, signal) {
|
|
34260
35250
|
signal.throwIfAborted();
|
|
34261
|
-
const url = new URL(target);
|
|
35251
|
+
const url = new URL(normalizeForwardTarget(target));
|
|
34262
35252
|
const body = Buffer.from(delivery.body, "utf8");
|
|
34263
35253
|
const headers = {};
|
|
34264
35254
|
for (const [name, value] of Object.entries(delivery.headers)) if (FORWARDED_HEADERS.has(name.toLowerCase())) headers[name.toLowerCase()] = value;
|
|
@@ -34398,6 +35388,1152 @@ function createListenHandler(options = {}) {
|
|
|
34398
35388
|
};
|
|
34399
35389
|
}
|
|
34400
35390
|
//#endregion
|
|
35391
|
+
//#region ../sdk-node/dist/payloads-BMngnJlE.js
|
|
35392
|
+
/**
|
|
35393
|
+
* Load a Node builtin at call time. Exported for direct testing of the
|
|
35394
|
+
* unavailable-module error path; production code goes through the typed
|
|
35395
|
+
* wrappers below.
|
|
35396
|
+
*/
|
|
35397
|
+
async function loadBuiltin(specifier, operation) {
|
|
35398
|
+
let builtin;
|
|
35399
|
+
if (typeof process !== "undefined" && typeof process.getBuiltinModule === "function") try {
|
|
35400
|
+
builtin = process.getBuiltinModule(specifier);
|
|
35401
|
+
} catch {
|
|
35402
|
+
builtin = void 0;
|
|
35403
|
+
}
|
|
35404
|
+
if (builtin !== void 0 && builtin !== null) return builtin;
|
|
35405
|
+
try {
|
|
35406
|
+
return await import(specifier);
|
|
35407
|
+
} catch (cause) {
|
|
35408
|
+
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 });
|
|
35409
|
+
}
|
|
35410
|
+
}
|
|
35411
|
+
/** `node:fs`, loaded lazily so Workers-style bundles never see the import. */
|
|
35412
|
+
function loadNodeFs(operation) {
|
|
35413
|
+
return loadBuiltin("node:fs", operation);
|
|
35414
|
+
}
|
|
35415
|
+
/** `node:fs/promises`, loaded lazily for the same reason as {@link loadNodeFs}. */
|
|
35416
|
+
function loadNodeFsPromises(operation) {
|
|
35417
|
+
return loadBuiltin("node:fs/promises", operation);
|
|
35418
|
+
}
|
|
35419
|
+
const CHUNK_SIZE = 64 * 1024 * 1024;
|
|
35420
|
+
const MANIFEST_VERSION = 1;
|
|
35421
|
+
const CHUNK_KDF_INFO = "payloads-chunk";
|
|
35422
|
+
const OBJECT_ID_BYTES = 16;
|
|
35423
|
+
const CEK_BYTES = 32;
|
|
35424
|
+
function subtle() {
|
|
35425
|
+
return globalThis.crypto.subtle;
|
|
35426
|
+
}
|
|
35427
|
+
const textEncoder = new TextEncoder();
|
|
35428
|
+
function randomBytes(length) {
|
|
35429
|
+
const out = new Uint8Array(length);
|
|
35430
|
+
globalThis.crypto.getRandomValues(out);
|
|
35431
|
+
return out;
|
|
35432
|
+
}
|
|
35433
|
+
function toHex(bytes) {
|
|
35434
|
+
let out = "";
|
|
35435
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
35436
|
+
return out;
|
|
35437
|
+
}
|
|
35438
|
+
function fromHex(hex) {
|
|
35439
|
+
if (hex.length % 2 !== 0) throw new Error("invalid hex: odd length");
|
|
35440
|
+
if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) throw new Error("invalid hex: non-hex character");
|
|
35441
|
+
const out = new Uint8Array(hex.length / 2);
|
|
35442
|
+
for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
35443
|
+
return out;
|
|
35444
|
+
}
|
|
35445
|
+
async function contentHash(bytes) {
|
|
35446
|
+
return new Uint8Array(await subtle().digest("SHA-256", bytes));
|
|
35447
|
+
}
|
|
35448
|
+
async function contentHashHex(bytes) {
|
|
35449
|
+
return toHex(await contentHash(bytes));
|
|
35450
|
+
}
|
|
35451
|
+
/** Binary Merkle root over ordered hex leaf hashes (odd node promoted). */
|
|
35452
|
+
async function merkleRoot(leafHashesHex) {
|
|
35453
|
+
if (leafHashesHex.length === 0) return contentHashHex(new Uint8Array(0));
|
|
35454
|
+
let level = leafHashesHex.map(fromHex);
|
|
35455
|
+
while (level.length > 1) {
|
|
35456
|
+
const next = [];
|
|
35457
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
35458
|
+
const left = level[i];
|
|
35459
|
+
const right = i + 1 < level.length ? level[i + 1] : level[i];
|
|
35460
|
+
const combined = new Uint8Array(left.length + right.length);
|
|
35461
|
+
combined.set(left, 0);
|
|
35462
|
+
combined.set(right, left.length);
|
|
35463
|
+
next.push(await contentHash(combined));
|
|
35464
|
+
}
|
|
35465
|
+
level = next;
|
|
35466
|
+
}
|
|
35467
|
+
return toHex(level[0]);
|
|
35468
|
+
}
|
|
35469
|
+
async function deriveChunkKey(cek, index) {
|
|
35470
|
+
const baseKey = await subtle().importKey("raw", cek, "HKDF", false, ["deriveKey"]);
|
|
35471
|
+
const info = textEncoder.encode(`${CHUNK_KDF_INFO}:${index}`);
|
|
35472
|
+
return subtle().deriveKey({
|
|
35473
|
+
name: "HKDF",
|
|
35474
|
+
hash: "SHA-256",
|
|
35475
|
+
salt: new Uint8Array(32),
|
|
35476
|
+
info
|
|
35477
|
+
}, baseKey, {
|
|
35478
|
+
name: "AES-GCM",
|
|
35479
|
+
length: 256
|
|
35480
|
+
}, false, ["encrypt", "decrypt"]);
|
|
35481
|
+
}
|
|
35482
|
+
function chunkNonce(index) {
|
|
35483
|
+
const nonce = new Uint8Array(12);
|
|
35484
|
+
new DataView(nonce.buffer).setUint32(8, index >>> 0, false);
|
|
35485
|
+
return nonce;
|
|
35486
|
+
}
|
|
35487
|
+
function chunkAad(objectId, index) {
|
|
35488
|
+
const aad = new Uint8Array(objectId.length + 4);
|
|
35489
|
+
aad.set(objectId, 0);
|
|
35490
|
+
new DataView(aad.buffer).setUint32(objectId.length, index >>> 0, false);
|
|
35491
|
+
return aad;
|
|
35492
|
+
}
|
|
35493
|
+
async function encryptChunk(cek, objectId, index, plaintext) {
|
|
35494
|
+
const key = await deriveChunkKey(cek, index);
|
|
35495
|
+
const ct = await subtle().encrypt({
|
|
35496
|
+
name: "AES-GCM",
|
|
35497
|
+
iv: chunkNonce(index),
|
|
35498
|
+
additionalData: chunkAad(objectId, index)
|
|
35499
|
+
}, key, plaintext);
|
|
35500
|
+
return new Uint8Array(ct);
|
|
35501
|
+
}
|
|
35502
|
+
async function decryptChunk(cek, objectId, index, ciphertext) {
|
|
35503
|
+
const key = await deriveChunkKey(cek, index);
|
|
35504
|
+
const pt = await subtle().decrypt({
|
|
35505
|
+
name: "AES-GCM",
|
|
35506
|
+
iv: chunkNonce(index),
|
|
35507
|
+
additionalData: chunkAad(objectId, index)
|
|
35508
|
+
}, key, ciphertext);
|
|
35509
|
+
return new Uint8Array(pt);
|
|
35510
|
+
}
|
|
35511
|
+
function authHeaders(apiKey) {
|
|
35512
|
+
return { authorization: `Bearer ${apiKey}` };
|
|
35513
|
+
}
|
|
35514
|
+
function apiRoot(baseUrl) {
|
|
35515
|
+
let end = baseUrl.length;
|
|
35516
|
+
while (end > 0 && baseUrl.charCodeAt(end - 1) === 47) end--;
|
|
35517
|
+
return `${baseUrl.slice(0, end)}/v1/payloads`;
|
|
35518
|
+
}
|
|
35519
|
+
const DEFAULT_MAX_RETRIES = 6;
|
|
35520
|
+
const RETRYABLE_STATUS = new Set([
|
|
35521
|
+
429,
|
|
35522
|
+
500,
|
|
35523
|
+
502,
|
|
35524
|
+
503,
|
|
35525
|
+
504
|
|
35526
|
+
]);
|
|
35527
|
+
function sleep$1(ms) {
|
|
35528
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
35529
|
+
}
|
|
35530
|
+
/**
|
|
35531
|
+
* fetch with exponential backoff on transient failures. Request bodies here are
|
|
35532
|
+
* in-memory buffers/strings (never streams), so a retry can safely resend them.
|
|
35533
|
+
*/
|
|
35534
|
+
async function retryingFetch(url, init, label, maxRetries = DEFAULT_MAX_RETRIES) {
|
|
35535
|
+
for (let attempt = 0;; attempt++) {
|
|
35536
|
+
let res;
|
|
35537
|
+
let networkError;
|
|
35538
|
+
try {
|
|
35539
|
+
res = await fetch(url, init);
|
|
35540
|
+
} catch (err) {
|
|
35541
|
+
networkError = err;
|
|
35542
|
+
}
|
|
35543
|
+
if (res && !RETRYABLE_STATUS.has(res.status)) return res;
|
|
35544
|
+
if (attempt >= maxRetries) {
|
|
35545
|
+
if (res) return res;
|
|
35546
|
+
throw new Error(`${label}: network error after ${attempt} retries: ${networkError?.message ?? networkError}`);
|
|
35547
|
+
}
|
|
35548
|
+
if (res) await res.text().catch(() => {});
|
|
35549
|
+
await sleep$1(Math.min(1e3 * 2 ** attempt, 15e3) + Math.floor(Math.random() * 250));
|
|
35550
|
+
}
|
|
35551
|
+
}
|
|
35552
|
+
async function initiate(opts, manifest) {
|
|
35553
|
+
const res = await retryingFetch(apiRoot(opts.baseUrl), {
|
|
35554
|
+
method: "POST",
|
|
35555
|
+
headers: {
|
|
35556
|
+
...authHeaders(opts.apiKey),
|
|
35557
|
+
"content-type": "application/json"
|
|
35558
|
+
},
|
|
35559
|
+
body: JSON.stringify({ manifest })
|
|
35560
|
+
}, "initiate");
|
|
35561
|
+
if (!res.ok) throw new Error(`initiate failed: HTTP ${res.status} ${await res.text()}`);
|
|
35562
|
+
await res.text();
|
|
35563
|
+
}
|
|
35564
|
+
async function putChunk(opts, root, d, bytes) {
|
|
35565
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, {
|
|
35566
|
+
method: "PUT",
|
|
35567
|
+
headers: {
|
|
35568
|
+
...authHeaders(opts.apiKey),
|
|
35569
|
+
"content-type": "application/octet-stream"
|
|
35570
|
+
},
|
|
35571
|
+
body: bytes
|
|
35572
|
+
}, `chunk ${d.index} upload`);
|
|
35573
|
+
if (!res.ok) throw new Error(`chunk ${d.index} upload failed: HTTP ${res.status} ${await res.text()}`);
|
|
35574
|
+
await res.text();
|
|
35575
|
+
}
|
|
35576
|
+
async function finalize(opts, root) {
|
|
35577
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/finalize`, {
|
|
35578
|
+
method: "POST",
|
|
35579
|
+
headers: authHeaders(opts.apiKey)
|
|
35580
|
+
}, "finalize");
|
|
35581
|
+
if (!res.ok) throw new Error(`finalize failed: HTTP ${res.status} ${await res.text()}`);
|
|
35582
|
+
await res.text();
|
|
35583
|
+
}
|
|
35584
|
+
async function fetchManifest(opts, root) {
|
|
35585
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/manifest`, { headers: authHeaders(opts.apiKey) }, "get manifest");
|
|
35586
|
+
if (!res.ok) throw new Error(`get manifest failed: HTTP ${res.status} ${await res.text()}`);
|
|
35587
|
+
return (await res.json()).data.manifest;
|
|
35588
|
+
}
|
|
35589
|
+
async function getChunkBytes(opts, root, d) {
|
|
35590
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, { headers: authHeaders(opts.apiKey) }, `get chunk ${d.index}`);
|
|
35591
|
+
if (!res.ok) throw new Error(`get chunk ${d.index} failed: HTTP ${res.status}`);
|
|
35592
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
35593
|
+
}
|
|
35594
|
+
async function readWindow(fh, position, length) {
|
|
35595
|
+
const buf = Buffer.allocUnsafe(length);
|
|
35596
|
+
let read = 0;
|
|
35597
|
+
while (read < length) {
|
|
35598
|
+
const { bytesRead } = await fh.read(buf, read, length - read, position + read);
|
|
35599
|
+
if (bytesRead === 0) break;
|
|
35600
|
+
read += bytesRead;
|
|
35601
|
+
}
|
|
35602
|
+
return read === length ? buf : buf.subarray(0, read);
|
|
35603
|
+
}
|
|
35604
|
+
async function mapLimit(items, limit, fn) {
|
|
35605
|
+
let next = 0;
|
|
35606
|
+
let firstError;
|
|
35607
|
+
const worker = async () => {
|
|
35608
|
+
while (next < items.length && firstError === void 0) {
|
|
35609
|
+
const i = next++;
|
|
35610
|
+
try {
|
|
35611
|
+
await fn(items[i], i);
|
|
35612
|
+
} catch (err) {
|
|
35613
|
+
if (firstError === void 0) firstError = err;
|
|
35614
|
+
return;
|
|
35615
|
+
}
|
|
35616
|
+
}
|
|
35617
|
+
};
|
|
35618
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length || 1) }, () => worker()));
|
|
35619
|
+
if (firstError !== void 0) throw firstError;
|
|
35620
|
+
}
|
|
35621
|
+
/**
|
|
35622
|
+
* Stream a file up as a Primitive Payload. Two passes over the file (encrypt to
|
|
35623
|
+
* build the manifest, then encrypt again to upload) keep memory bounded to a few
|
|
35624
|
+
* chunks — the whole object is never resident. Returns the content address
|
|
35625
|
+
* (merkleRoot) and the hex CEK needed to download it.
|
|
35626
|
+
*/
|
|
35627
|
+
async function pushFile(filePath, opts) {
|
|
35628
|
+
const { open, stat } = await loadNodeFsPromises("pushFile");
|
|
35629
|
+
const chunkSize = opts.chunkSize ?? CHUNK_SIZE;
|
|
35630
|
+
const concurrency = opts.concurrency ?? 3;
|
|
35631
|
+
const { size } = await stat(filePath);
|
|
35632
|
+
const cek = randomBytes(CEK_BYTES);
|
|
35633
|
+
const objectId = randomBytes(OBJECT_ID_BYTES);
|
|
35634
|
+
const chunkCount = size === 0 ? 0 : Math.ceil(size / chunkSize);
|
|
35635
|
+
const fh = await open(filePath, "r");
|
|
35636
|
+
try {
|
|
35637
|
+
const descriptors = [];
|
|
35638
|
+
for (let index = 0; index < chunkCount; index++) {
|
|
35639
|
+
const position = index * chunkSize;
|
|
35640
|
+
const length = Math.min(chunkSize, size - position);
|
|
35641
|
+
const plaintext = await readWindow(fh, position, length);
|
|
35642
|
+
const ciphertext = await encryptChunk(cek, objectId, index, plaintext);
|
|
35643
|
+
descriptors.push({
|
|
35644
|
+
index,
|
|
35645
|
+
ciphertextHash: await contentHashHex(ciphertext),
|
|
35646
|
+
plaintextSize: length,
|
|
35647
|
+
ciphertextSize: ciphertext.length
|
|
35648
|
+
});
|
|
35649
|
+
opts.onProgress?.("encrypt", index + 1, chunkCount);
|
|
35650
|
+
}
|
|
35651
|
+
const root = await merkleRoot(descriptors.map((d) => d.ciphertextHash));
|
|
35652
|
+
await initiate(opts, {
|
|
35653
|
+
version: MANIFEST_VERSION,
|
|
35654
|
+
objectId: toHex(objectId),
|
|
35655
|
+
chunkSize,
|
|
35656
|
+
totalPlaintextSize: size,
|
|
35657
|
+
chunkCount,
|
|
35658
|
+
chunks: descriptors,
|
|
35659
|
+
merkleRoot: root
|
|
35660
|
+
});
|
|
35661
|
+
let uploaded = 0;
|
|
35662
|
+
await mapLimit(descriptors, concurrency, async (d) => {
|
|
35663
|
+
const plaintext = await readWindow(fh, d.index * chunkSize, d.plaintextSize);
|
|
35664
|
+
await putChunk(opts, root, d, await encryptChunk(cek, objectId, d.index, plaintext));
|
|
35665
|
+
opts.onProgress?.("upload", ++uploaded, chunkCount);
|
|
35666
|
+
});
|
|
35667
|
+
await finalize(opts, root);
|
|
35668
|
+
return {
|
|
35669
|
+
merkleRoot: root,
|
|
35670
|
+
cek: toHex(cek),
|
|
35671
|
+
chunkCount,
|
|
35672
|
+
totalBytes: size
|
|
35673
|
+
};
|
|
35674
|
+
} finally {
|
|
35675
|
+
await fh.close();
|
|
35676
|
+
}
|
|
35677
|
+
}
|
|
35678
|
+
/**
|
|
35679
|
+
* Stream a Primitive Payload down to a file, verifying and decrypting one chunk
|
|
35680
|
+
* at a time (bounded memory). Every chunk is content-address-checked before
|
|
35681
|
+
* decryption, so a corrupt or substituted chunk throws.
|
|
35682
|
+
*/
|
|
35683
|
+
async function pullFile(root, outPath, opts) {
|
|
35684
|
+
const { createWriteStream } = await loadNodeFs("pullFile");
|
|
35685
|
+
const { rm } = await loadNodeFsPromises("pullFile");
|
|
35686
|
+
const manifest = await fetchManifest(opts, root);
|
|
35687
|
+
const computedRoot = await merkleRoot(manifest.chunks.map((c) => c.ciphertextHash));
|
|
35688
|
+
if (computedRoot !== root) throw new Error(`manifest does not match the requested content address (got ${computedRoot})`);
|
|
35689
|
+
const cek = fromHex(opts.cek);
|
|
35690
|
+
const objectId = fromHex(manifest.objectId);
|
|
35691
|
+
const out = createWriteStream(outPath);
|
|
35692
|
+
let onWriteError = () => {};
|
|
35693
|
+
const writeErrored = new Promise((_, reject) => {
|
|
35694
|
+
onWriteError = reject;
|
|
35695
|
+
});
|
|
35696
|
+
writeErrored.catch(() => {});
|
|
35697
|
+
out.on("error", (err) => onWriteError(err));
|
|
35698
|
+
try {
|
|
35699
|
+
let done = 0;
|
|
35700
|
+
for (const d of manifest.chunks) {
|
|
35701
|
+
const ciphertext = await getChunkBytes(opts, root, d);
|
|
35702
|
+
if (await contentHashHex(ciphertext) !== d.ciphertextHash) throw new Error(`chunk ${d.index} failed integrity check`);
|
|
35703
|
+
const plaintext = await decryptChunk(cek, objectId, d.index, ciphertext);
|
|
35704
|
+
if (!out.write(plaintext)) await Promise.race([new Promise((resolve) => out.once("drain", resolve)), writeErrored]);
|
|
35705
|
+
opts.onProgress?.("download", ++done, manifest.chunkCount);
|
|
35706
|
+
}
|
|
35707
|
+
await Promise.race([new Promise((resolve) => out.end(resolve)), writeErrored]);
|
|
35708
|
+
} catch (err) {
|
|
35709
|
+
out.destroy();
|
|
35710
|
+
await rm(outPath, { force: true });
|
|
35711
|
+
throw err;
|
|
35712
|
+
}
|
|
35713
|
+
return manifest;
|
|
35714
|
+
}
|
|
35715
|
+
//#endregion
|
|
35716
|
+
//#region ../sdk-node/dist/api-27BvnkVA.js
|
|
35717
|
+
const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
|
|
35718
|
+
function createSseClient({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url, ...options }) {
|
|
35719
|
+
let lastEventId;
|
|
35720
|
+
const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
35721
|
+
const createStream = async function* () {
|
|
35722
|
+
let retryDelay = sseDefaultRetryDelay ?? 3e3;
|
|
35723
|
+
let attempt = 0;
|
|
35724
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
35725
|
+
while (true) {
|
|
35726
|
+
if (signal.aborted) break;
|
|
35727
|
+
attempt++;
|
|
35728
|
+
const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
|
|
35729
|
+
if (lastEventId !== void 0) headers.set("Last-Event-ID", lastEventId);
|
|
35730
|
+
try {
|
|
35731
|
+
const requestInit = {
|
|
35732
|
+
redirect: "follow",
|
|
35733
|
+
...options,
|
|
35734
|
+
body: options.serializedBody,
|
|
35735
|
+
headers,
|
|
35736
|
+
signal
|
|
35737
|
+
};
|
|
35738
|
+
let request = new Request(url, requestInit);
|
|
35739
|
+
if (onRequest) request = await onRequest(url, requestInit);
|
|
35740
|
+
const response = await (options.fetch ?? globalThis.fetch)(request);
|
|
35741
|
+
if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
|
|
35742
|
+
if (!response.body) throw new Error("No body in SSE response");
|
|
35743
|
+
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
35744
|
+
let buffer = "";
|
|
35745
|
+
const abortHandler = () => {
|
|
35746
|
+
try {
|
|
35747
|
+
reader.cancel();
|
|
35748
|
+
} catch {}
|
|
35749
|
+
};
|
|
35750
|
+
signal.addEventListener("abort", abortHandler);
|
|
35751
|
+
try {
|
|
35752
|
+
while (true) {
|
|
35753
|
+
const { done, value } = await reader.read();
|
|
35754
|
+
if (done) break;
|
|
35755
|
+
buffer += value;
|
|
35756
|
+
buffer = buffer.replace(/\r\n?/g, "\n");
|
|
35757
|
+
const chunks = buffer.split("\n\n");
|
|
35758
|
+
buffer = chunks.pop() ?? "";
|
|
35759
|
+
for (const chunk of chunks) {
|
|
35760
|
+
const lines = chunk.split("\n");
|
|
35761
|
+
const dataLines = [];
|
|
35762
|
+
let eventName;
|
|
35763
|
+
for (const line of lines) if (line.startsWith("data:")) dataLines.push(line.replace(/^data:\s*/, ""));
|
|
35764
|
+
else if (line.startsWith("event:")) eventName = line.replace(/^event:\s*/, "");
|
|
35765
|
+
else if (line.startsWith("id:")) lastEventId = line.replace(/^id:\s*/, "");
|
|
35766
|
+
else if (line.startsWith("retry:")) {
|
|
35767
|
+
const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
|
|
35768
|
+
if (!Number.isNaN(parsed)) retryDelay = parsed;
|
|
35769
|
+
}
|
|
35770
|
+
let data;
|
|
35771
|
+
let parsedJson = false;
|
|
35772
|
+
if (dataLines.length) {
|
|
35773
|
+
const rawData = dataLines.join("\n");
|
|
35774
|
+
try {
|
|
35775
|
+
data = JSON.parse(rawData);
|
|
35776
|
+
parsedJson = true;
|
|
35777
|
+
} catch {
|
|
35778
|
+
data = rawData;
|
|
35779
|
+
}
|
|
35780
|
+
}
|
|
35781
|
+
if (parsedJson) {
|
|
35782
|
+
if (responseValidator) await responseValidator(data);
|
|
35783
|
+
if (responseTransformer) data = await responseTransformer(data);
|
|
35784
|
+
}
|
|
35785
|
+
onSseEvent?.({
|
|
35786
|
+
data,
|
|
35787
|
+
event: eventName,
|
|
35788
|
+
id: lastEventId,
|
|
35789
|
+
retry: retryDelay
|
|
35790
|
+
});
|
|
35791
|
+
if (dataLines.length) yield data;
|
|
35792
|
+
}
|
|
35793
|
+
}
|
|
35794
|
+
} finally {
|
|
35795
|
+
signal.removeEventListener("abort", abortHandler);
|
|
35796
|
+
reader.releaseLock();
|
|
35797
|
+
}
|
|
35798
|
+
break;
|
|
35799
|
+
} catch (error) {
|
|
35800
|
+
onSseError?.(error);
|
|
35801
|
+
if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) break;
|
|
35802
|
+
await sleep(Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 3e4));
|
|
35803
|
+
}
|
|
35804
|
+
}
|
|
35805
|
+
};
|
|
35806
|
+
return { stream: createStream() };
|
|
35807
|
+
}
|
|
35808
|
+
const separatorArrayExplode = (style) => {
|
|
35809
|
+
switch (style) {
|
|
35810
|
+
case "label": return ".";
|
|
35811
|
+
case "matrix": return ";";
|
|
35812
|
+
case "simple": return ",";
|
|
35813
|
+
default: return "&";
|
|
35814
|
+
}
|
|
35815
|
+
};
|
|
35816
|
+
const separatorArrayNoExplode = (style) => {
|
|
35817
|
+
switch (style) {
|
|
35818
|
+
case "form": return ",";
|
|
35819
|
+
case "pipeDelimited": return "|";
|
|
35820
|
+
case "spaceDelimited": return "%20";
|
|
35821
|
+
default: return ",";
|
|
35822
|
+
}
|
|
35823
|
+
};
|
|
35824
|
+
const separatorObjectExplode = (style) => {
|
|
35825
|
+
switch (style) {
|
|
35826
|
+
case "label": return ".";
|
|
35827
|
+
case "matrix": return ";";
|
|
35828
|
+
case "simple": return ",";
|
|
35829
|
+
default: return "&";
|
|
35830
|
+
}
|
|
35831
|
+
};
|
|
35832
|
+
const serializeArrayParam = ({ allowReserved, explode, name, style, value }) => {
|
|
35833
|
+
if (!explode) {
|
|
35834
|
+
const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
|
|
35835
|
+
switch (style) {
|
|
35836
|
+
case "label": return `.${joinedValues}`;
|
|
35837
|
+
case "matrix": return `;${name}=${joinedValues}`;
|
|
35838
|
+
case "simple": return joinedValues;
|
|
35839
|
+
default: return `${name}=${joinedValues}`;
|
|
35840
|
+
}
|
|
35841
|
+
}
|
|
35842
|
+
const separator = separatorArrayExplode(style);
|
|
35843
|
+
const joinedValues = value.map((v) => {
|
|
35844
|
+
if (style === "label" || style === "simple") return allowReserved ? v : encodeURIComponent(v);
|
|
35845
|
+
return serializePrimitiveParam({
|
|
35846
|
+
allowReserved,
|
|
35847
|
+
name,
|
|
35848
|
+
value: v
|
|
35849
|
+
});
|
|
35850
|
+
}).join(separator);
|
|
35851
|
+
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
|
35852
|
+
};
|
|
35853
|
+
const serializePrimitiveParam = ({ allowReserved, name, value }) => {
|
|
35854
|
+
if (value === void 0 || value === null) return "";
|
|
35855
|
+
if (typeof value === "object") throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
|
|
35856
|
+
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
|
|
35857
|
+
};
|
|
35858
|
+
const serializeObjectParam = ({ allowReserved, explode, name, style, value, valueOnly }) => {
|
|
35859
|
+
if (value instanceof Date) return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
|
|
35860
|
+
if (style !== "deepObject" && !explode) {
|
|
35861
|
+
let values = [];
|
|
35862
|
+
Object.entries(value).forEach(([key, v]) => {
|
|
35863
|
+
values = [
|
|
35864
|
+
...values,
|
|
35865
|
+
key,
|
|
35866
|
+
allowReserved ? v : encodeURIComponent(v)
|
|
35867
|
+
];
|
|
35868
|
+
});
|
|
35869
|
+
const joinedValues = values.join(",");
|
|
35870
|
+
switch (style) {
|
|
35871
|
+
case "form": return `${name}=${joinedValues}`;
|
|
35872
|
+
case "label": return `.${joinedValues}`;
|
|
35873
|
+
case "matrix": return `;${name}=${joinedValues}`;
|
|
35874
|
+
default: return joinedValues;
|
|
35875
|
+
}
|
|
35876
|
+
}
|
|
35877
|
+
const separator = separatorObjectExplode(style);
|
|
35878
|
+
const joinedValues = Object.entries(value).map(([key, v]) => serializePrimitiveParam({
|
|
35879
|
+
allowReserved,
|
|
35880
|
+
name: style === "deepObject" ? `${name}[${key}]` : key,
|
|
35881
|
+
value: v
|
|
35882
|
+
})).join(separator);
|
|
35883
|
+
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
|
35884
|
+
};
|
|
35885
|
+
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
35886
|
+
const defaultPathSerializer = ({ path, url: _url }) => {
|
|
35887
|
+
let url = _url;
|
|
35888
|
+
const matches = _url.match(PATH_PARAM_RE);
|
|
35889
|
+
if (matches) for (const match of matches) {
|
|
35890
|
+
let explode = false;
|
|
35891
|
+
let name = match.substring(1, match.length - 1);
|
|
35892
|
+
let style = "simple";
|
|
35893
|
+
if (name.endsWith("*")) {
|
|
35894
|
+
explode = true;
|
|
35895
|
+
name = name.substring(0, name.length - 1);
|
|
35896
|
+
}
|
|
35897
|
+
if (name.startsWith(".")) {
|
|
35898
|
+
name = name.substring(1);
|
|
35899
|
+
style = "label";
|
|
35900
|
+
} else if (name.startsWith(";")) {
|
|
35901
|
+
name = name.substring(1);
|
|
35902
|
+
style = "matrix";
|
|
35903
|
+
}
|
|
35904
|
+
const value = path[name];
|
|
35905
|
+
if (value === void 0 || value === null) continue;
|
|
35906
|
+
if (Array.isArray(value)) {
|
|
35907
|
+
url = url.replace(match, serializeArrayParam({
|
|
35908
|
+
explode,
|
|
35909
|
+
name,
|
|
35910
|
+
style,
|
|
35911
|
+
value
|
|
35912
|
+
}));
|
|
35913
|
+
continue;
|
|
35914
|
+
}
|
|
35915
|
+
if (typeof value === "object") {
|
|
35916
|
+
url = url.replace(match, serializeObjectParam({
|
|
35917
|
+
explode,
|
|
35918
|
+
name,
|
|
35919
|
+
style,
|
|
35920
|
+
value,
|
|
35921
|
+
valueOnly: true
|
|
35922
|
+
}));
|
|
35923
|
+
continue;
|
|
35924
|
+
}
|
|
35925
|
+
if (style === "matrix") {
|
|
35926
|
+
url = url.replace(match, `;${serializePrimitiveParam({
|
|
35927
|
+
name,
|
|
35928
|
+
value
|
|
35929
|
+
})}`);
|
|
35930
|
+
continue;
|
|
35931
|
+
}
|
|
35932
|
+
const replaceValue = encodeURIComponent(style === "label" ? `.${value}` : value);
|
|
35933
|
+
url = url.replace(match, replaceValue);
|
|
35934
|
+
}
|
|
35935
|
+
return url;
|
|
35936
|
+
};
|
|
35937
|
+
const getUrl = ({ baseUrl, path, query, querySerializer, url: _url }) => {
|
|
35938
|
+
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
|
|
35939
|
+
let url = (baseUrl ?? "") + pathUrl;
|
|
35940
|
+
if (path) url = defaultPathSerializer({
|
|
35941
|
+
path,
|
|
35942
|
+
url
|
|
35943
|
+
});
|
|
35944
|
+
let search = query ? querySerializer(query) : "";
|
|
35945
|
+
if (search.startsWith("?")) search = search.substring(1);
|
|
35946
|
+
if (search) url += `?${search}`;
|
|
35947
|
+
return url;
|
|
35948
|
+
};
|
|
35949
|
+
function getValidRequestBody(options) {
|
|
35950
|
+
const hasBody = options.body !== void 0;
|
|
35951
|
+
if (hasBody && options.bodySerializer) {
|
|
35952
|
+
if ("serializedBody" in options) return options.serializedBody !== void 0 && options.serializedBody !== "" ? options.serializedBody : null;
|
|
35953
|
+
return options.body !== "" ? options.body : null;
|
|
35954
|
+
}
|
|
35955
|
+
if (hasBody) return options.body;
|
|
35956
|
+
}
|
|
35957
|
+
const getAuthToken = async (auth, callback) => {
|
|
35958
|
+
const token = typeof callback === "function" ? await callback(auth) : callback;
|
|
35959
|
+
if (!token) return;
|
|
35960
|
+
if (auth.scheme === "bearer") return `Bearer ${token}`;
|
|
35961
|
+
if (auth.scheme === "basic") return `Basic ${btoa(token)}`;
|
|
35962
|
+
return token;
|
|
35963
|
+
};
|
|
35964
|
+
const createQuerySerializer = ({ parameters = {}, ...args } = {}) => {
|
|
35965
|
+
const querySerializer = (queryParams) => {
|
|
35966
|
+
const search = [];
|
|
35967
|
+
if (queryParams && typeof queryParams === "object") for (const name in queryParams) {
|
|
35968
|
+
const value = queryParams[name];
|
|
35969
|
+
if (value === void 0 || value === null) continue;
|
|
35970
|
+
const options = parameters[name] || args;
|
|
35971
|
+
if (Array.isArray(value)) {
|
|
35972
|
+
const serializedArray = serializeArrayParam({
|
|
35973
|
+
allowReserved: options.allowReserved,
|
|
35974
|
+
explode: true,
|
|
35975
|
+
name,
|
|
35976
|
+
style: "form",
|
|
35977
|
+
value,
|
|
35978
|
+
...options.array
|
|
35979
|
+
});
|
|
35980
|
+
if (serializedArray) search.push(serializedArray);
|
|
35981
|
+
} else if (typeof value === "object") {
|
|
35982
|
+
const serializedObject = serializeObjectParam({
|
|
35983
|
+
allowReserved: options.allowReserved,
|
|
35984
|
+
explode: true,
|
|
35985
|
+
name,
|
|
35986
|
+
style: "deepObject",
|
|
35987
|
+
value,
|
|
35988
|
+
...options.object
|
|
35989
|
+
});
|
|
35990
|
+
if (serializedObject) search.push(serializedObject);
|
|
35991
|
+
} else {
|
|
35992
|
+
const serializedPrimitive = serializePrimitiveParam({
|
|
35993
|
+
allowReserved: options.allowReserved,
|
|
35994
|
+
name,
|
|
35995
|
+
value
|
|
35996
|
+
});
|
|
35997
|
+
if (serializedPrimitive) search.push(serializedPrimitive);
|
|
35998
|
+
}
|
|
35999
|
+
}
|
|
36000
|
+
return search.join("&");
|
|
36001
|
+
};
|
|
36002
|
+
return querySerializer;
|
|
36003
|
+
};
|
|
36004
|
+
/**
|
|
36005
|
+
* Infers parseAs value from provided Content-Type header.
|
|
36006
|
+
*/
|
|
36007
|
+
const getParseAs = (contentType) => {
|
|
36008
|
+
if (!contentType) return "stream";
|
|
36009
|
+
const cleanContent = contentType.split(";")[0]?.trim();
|
|
36010
|
+
if (!cleanContent) return;
|
|
36011
|
+
if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) return "json";
|
|
36012
|
+
if (cleanContent === "multipart/form-data") return "formData";
|
|
36013
|
+
if ([
|
|
36014
|
+
"application/",
|
|
36015
|
+
"audio/",
|
|
36016
|
+
"image/",
|
|
36017
|
+
"video/"
|
|
36018
|
+
].some((type) => cleanContent.startsWith(type))) return "blob";
|
|
36019
|
+
if (cleanContent.startsWith("text/")) return "text";
|
|
36020
|
+
};
|
|
36021
|
+
const checkForExistence = (options, name) => {
|
|
36022
|
+
if (!name) return false;
|
|
36023
|
+
if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) return true;
|
|
36024
|
+
return false;
|
|
36025
|
+
};
|
|
36026
|
+
async function setAuthParams(options) {
|
|
36027
|
+
for (const auth of options.security ?? []) {
|
|
36028
|
+
if (checkForExistence(options, auth.name)) continue;
|
|
36029
|
+
const token = await getAuthToken(auth, options.auth);
|
|
36030
|
+
if (!token) continue;
|
|
36031
|
+
const name = auth.name ?? "Authorization";
|
|
36032
|
+
switch (auth.in) {
|
|
36033
|
+
case "query":
|
|
36034
|
+
if (!options.query) options.query = {};
|
|
36035
|
+
options.query[name] = token;
|
|
36036
|
+
break;
|
|
36037
|
+
case "cookie":
|
|
36038
|
+
options.headers.append("Cookie", `${name}=${token}`);
|
|
36039
|
+
break;
|
|
36040
|
+
default:
|
|
36041
|
+
options.headers.set(name, token);
|
|
36042
|
+
break;
|
|
36043
|
+
}
|
|
36044
|
+
}
|
|
36045
|
+
}
|
|
36046
|
+
const buildUrl = (options) => getUrl({
|
|
36047
|
+
baseUrl: options.baseUrl,
|
|
36048
|
+
path: options.path,
|
|
36049
|
+
query: options.query,
|
|
36050
|
+
querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
|
|
36051
|
+
url: options.url
|
|
36052
|
+
});
|
|
36053
|
+
const mergeConfigs = (a, b) => {
|
|
36054
|
+
const config = {
|
|
36055
|
+
...a,
|
|
36056
|
+
...b
|
|
36057
|
+
};
|
|
36058
|
+
if (config.baseUrl?.endsWith("/")) config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
|
|
36059
|
+
config.headers = mergeHeaders(a.headers, b.headers);
|
|
36060
|
+
return config;
|
|
36061
|
+
};
|
|
36062
|
+
const headersEntries = (headers) => {
|
|
36063
|
+
const entries = [];
|
|
36064
|
+
headers.forEach((value, key) => {
|
|
36065
|
+
entries.push([key, value]);
|
|
36066
|
+
});
|
|
36067
|
+
return entries;
|
|
36068
|
+
};
|
|
36069
|
+
const mergeHeaders = (...headers) => {
|
|
36070
|
+
const mergedHeaders = new Headers();
|
|
36071
|
+
for (const header of headers) {
|
|
36072
|
+
if (!header) continue;
|
|
36073
|
+
const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
|
|
36074
|
+
for (const [key, value] of iterator) if (value === null) mergedHeaders.delete(key);
|
|
36075
|
+
else if (Array.isArray(value)) for (const v of value) mergedHeaders.append(key, v);
|
|
36076
|
+
else if (value !== void 0) mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : value);
|
|
36077
|
+
}
|
|
36078
|
+
return mergedHeaders;
|
|
36079
|
+
};
|
|
36080
|
+
var Interceptors = class {
|
|
36081
|
+
fns = [];
|
|
36082
|
+
clear() {
|
|
36083
|
+
this.fns = [];
|
|
36084
|
+
}
|
|
36085
|
+
eject(id) {
|
|
36086
|
+
const index = this.getInterceptorIndex(id);
|
|
36087
|
+
if (this.fns[index]) this.fns[index] = null;
|
|
36088
|
+
}
|
|
36089
|
+
exists(id) {
|
|
36090
|
+
const index = this.getInterceptorIndex(id);
|
|
36091
|
+
return Boolean(this.fns[index]);
|
|
36092
|
+
}
|
|
36093
|
+
getInterceptorIndex(id) {
|
|
36094
|
+
if (typeof id === "number") return this.fns[id] ? id : -1;
|
|
36095
|
+
return this.fns.indexOf(id);
|
|
36096
|
+
}
|
|
36097
|
+
update(id, fn) {
|
|
36098
|
+
const index = this.getInterceptorIndex(id);
|
|
36099
|
+
if (this.fns[index]) {
|
|
36100
|
+
this.fns[index] = fn;
|
|
36101
|
+
return id;
|
|
36102
|
+
}
|
|
36103
|
+
return false;
|
|
36104
|
+
}
|
|
36105
|
+
use(fn) {
|
|
36106
|
+
this.fns.push(fn);
|
|
36107
|
+
return this.fns.length - 1;
|
|
36108
|
+
}
|
|
36109
|
+
};
|
|
36110
|
+
const createInterceptors = () => ({
|
|
36111
|
+
error: new Interceptors(),
|
|
36112
|
+
request: new Interceptors(),
|
|
36113
|
+
response: new Interceptors()
|
|
36114
|
+
});
|
|
36115
|
+
const defaultQuerySerializer = createQuerySerializer({
|
|
36116
|
+
allowReserved: false,
|
|
36117
|
+
array: {
|
|
36118
|
+
explode: true,
|
|
36119
|
+
style: "form"
|
|
36120
|
+
},
|
|
36121
|
+
object: {
|
|
36122
|
+
explode: true,
|
|
36123
|
+
style: "deepObject"
|
|
36124
|
+
}
|
|
36125
|
+
});
|
|
36126
|
+
const defaultHeaders = { "Content-Type": "application/json" };
|
|
36127
|
+
const createConfig = (override = {}) => ({
|
|
36128
|
+
...jsonBodySerializer,
|
|
36129
|
+
headers: defaultHeaders,
|
|
36130
|
+
parseAs: "auto",
|
|
36131
|
+
querySerializer: defaultQuerySerializer,
|
|
36132
|
+
...override
|
|
36133
|
+
});
|
|
36134
|
+
const createClient = (config = {}) => {
|
|
36135
|
+
let _config = mergeConfigs(createConfig(), config);
|
|
36136
|
+
const getConfig = () => ({ ..._config });
|
|
36137
|
+
const setConfig = (config) => {
|
|
36138
|
+
_config = mergeConfigs(_config, config);
|
|
36139
|
+
return getConfig();
|
|
36140
|
+
};
|
|
36141
|
+
const interceptors = createInterceptors();
|
|
36142
|
+
const beforeRequest = async (options) => {
|
|
36143
|
+
const opts = {
|
|
36144
|
+
..._config,
|
|
36145
|
+
...options,
|
|
36146
|
+
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
|
36147
|
+
headers: mergeHeaders(_config.headers, options.headers),
|
|
36148
|
+
serializedBody: void 0
|
|
36149
|
+
};
|
|
36150
|
+
if (opts.security) await setAuthParams(opts);
|
|
36151
|
+
if (opts.requestValidator) await opts.requestValidator(opts);
|
|
36152
|
+
if (opts.body !== void 0 && opts.bodySerializer) opts.serializedBody = opts.bodySerializer(opts.body);
|
|
36153
|
+
if (opts.body === void 0 || opts.serializedBody === "") opts.headers.delete("Content-Type");
|
|
36154
|
+
const resolvedOpts = opts;
|
|
36155
|
+
return {
|
|
36156
|
+
opts: resolvedOpts,
|
|
36157
|
+
url: buildUrl(resolvedOpts)
|
|
36158
|
+
};
|
|
36159
|
+
};
|
|
36160
|
+
const request = async (options) => {
|
|
36161
|
+
const throwOnError = options.throwOnError ?? _config.throwOnError;
|
|
36162
|
+
const responseStyle = options.responseStyle ?? _config.responseStyle;
|
|
36163
|
+
let request;
|
|
36164
|
+
let response;
|
|
36165
|
+
try {
|
|
36166
|
+
const { opts, url } = await beforeRequest(options);
|
|
36167
|
+
const requestInit = {
|
|
36168
|
+
redirect: "follow",
|
|
36169
|
+
...opts,
|
|
36170
|
+
body: getValidRequestBody(opts)
|
|
36171
|
+
};
|
|
36172
|
+
request = new Request(url, requestInit);
|
|
36173
|
+
for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
|
|
36174
|
+
const _fetch = opts.fetch;
|
|
36175
|
+
response = await _fetch(request);
|
|
36176
|
+
for (const fn of interceptors.response.fns) if (fn) response = await fn(response, request, opts);
|
|
36177
|
+
const result = {
|
|
36178
|
+
request,
|
|
36179
|
+
response
|
|
36180
|
+
};
|
|
36181
|
+
if (response.ok) {
|
|
36182
|
+
const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
|
|
36183
|
+
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
|
36184
|
+
let emptyData;
|
|
36185
|
+
switch (parseAs) {
|
|
36186
|
+
case "arrayBuffer":
|
|
36187
|
+
case "blob":
|
|
36188
|
+
case "text":
|
|
36189
|
+
emptyData = await response[parseAs]();
|
|
36190
|
+
break;
|
|
36191
|
+
case "formData":
|
|
36192
|
+
emptyData = new FormData();
|
|
36193
|
+
break;
|
|
36194
|
+
case "stream":
|
|
36195
|
+
emptyData = response.body;
|
|
36196
|
+
break;
|
|
36197
|
+
default:
|
|
36198
|
+
emptyData = {};
|
|
36199
|
+
break;
|
|
36200
|
+
}
|
|
36201
|
+
return opts.responseStyle === "data" ? emptyData : {
|
|
36202
|
+
data: emptyData,
|
|
36203
|
+
...result
|
|
36204
|
+
};
|
|
36205
|
+
}
|
|
36206
|
+
let data;
|
|
36207
|
+
switch (parseAs) {
|
|
36208
|
+
case "arrayBuffer":
|
|
36209
|
+
case "blob":
|
|
36210
|
+
case "formData":
|
|
36211
|
+
case "text":
|
|
36212
|
+
data = await response[parseAs]();
|
|
36213
|
+
break;
|
|
36214
|
+
case "json": {
|
|
36215
|
+
const text = await response.text();
|
|
36216
|
+
data = text ? JSON.parse(text) : {};
|
|
36217
|
+
break;
|
|
36218
|
+
}
|
|
36219
|
+
case "stream": return opts.responseStyle === "data" ? response.body : {
|
|
36220
|
+
data: response.body,
|
|
36221
|
+
...result
|
|
36222
|
+
};
|
|
36223
|
+
}
|
|
36224
|
+
if (parseAs === "json") {
|
|
36225
|
+
if (opts.responseValidator) await opts.responseValidator(data);
|
|
36226
|
+
if (opts.responseTransformer) data = await opts.responseTransformer(data);
|
|
36227
|
+
}
|
|
36228
|
+
return opts.responseStyle === "data" ? data : {
|
|
36229
|
+
data,
|
|
36230
|
+
...result
|
|
36231
|
+
};
|
|
36232
|
+
}
|
|
36233
|
+
const textError = await response.text();
|
|
36234
|
+
let jsonError;
|
|
36235
|
+
try {
|
|
36236
|
+
jsonError = JSON.parse(textError);
|
|
36237
|
+
} catch {}
|
|
36238
|
+
throw jsonError ?? textError;
|
|
36239
|
+
} catch (error) {
|
|
36240
|
+
let finalError = error;
|
|
36241
|
+
for (const fn of interceptors.error.fns) if (fn) finalError = await fn(finalError, response, request, options);
|
|
36242
|
+
finalError = finalError || {};
|
|
36243
|
+
if (throwOnError) throw finalError;
|
|
36244
|
+
return responseStyle === "data" ? void 0 : {
|
|
36245
|
+
error: finalError,
|
|
36246
|
+
request,
|
|
36247
|
+
response
|
|
36248
|
+
};
|
|
36249
|
+
}
|
|
36250
|
+
};
|
|
36251
|
+
const makeMethodFn = (method) => (options) => request({
|
|
36252
|
+
...options,
|
|
36253
|
+
method
|
|
36254
|
+
});
|
|
36255
|
+
const makeSseFn = (method) => async (options) => {
|
|
36256
|
+
const { opts, url } = await beforeRequest(options);
|
|
36257
|
+
return createSseClient({
|
|
36258
|
+
...opts,
|
|
36259
|
+
body: opts.body,
|
|
36260
|
+
method,
|
|
36261
|
+
onRequest: async (url, init) => {
|
|
36262
|
+
let request = new Request(url, init);
|
|
36263
|
+
for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
|
|
36264
|
+
return request;
|
|
36265
|
+
},
|
|
36266
|
+
serializedBody: getValidRequestBody(opts),
|
|
36267
|
+
url
|
|
36268
|
+
});
|
|
36269
|
+
};
|
|
36270
|
+
const _buildUrl = (options) => buildUrl({
|
|
36271
|
+
..._config,
|
|
36272
|
+
...options
|
|
36273
|
+
});
|
|
36274
|
+
return {
|
|
36275
|
+
buildUrl: _buildUrl,
|
|
36276
|
+
connect: makeMethodFn("CONNECT"),
|
|
36277
|
+
delete: makeMethodFn("DELETE"),
|
|
36278
|
+
get: makeMethodFn("GET"),
|
|
36279
|
+
getConfig,
|
|
36280
|
+
head: makeMethodFn("HEAD"),
|
|
36281
|
+
interceptors,
|
|
36282
|
+
options: makeMethodFn("OPTIONS"),
|
|
36283
|
+
patch: makeMethodFn("PATCH"),
|
|
36284
|
+
post: makeMethodFn("POST"),
|
|
36285
|
+
put: makeMethodFn("PUT"),
|
|
36286
|
+
request,
|
|
36287
|
+
setConfig,
|
|
36288
|
+
sse: {
|
|
36289
|
+
connect: makeSseFn("CONNECT"),
|
|
36290
|
+
delete: makeSseFn("DELETE"),
|
|
36291
|
+
get: makeSseFn("GET"),
|
|
36292
|
+
head: makeSseFn("HEAD"),
|
|
36293
|
+
options: makeSseFn("OPTIONS"),
|
|
36294
|
+
patch: makeSseFn("PATCH"),
|
|
36295
|
+
post: makeSseFn("POST"),
|
|
36296
|
+
put: makeSseFn("PUT"),
|
|
36297
|
+
trace: makeSseFn("TRACE")
|
|
36298
|
+
},
|
|
36299
|
+
trace: makeMethodFn("TRACE")
|
|
36300
|
+
};
|
|
36301
|
+
};
|
|
36302
|
+
const client$1 = createClient(createConfig({ baseUrl: "https://api.primitive.dev/v1" }));
|
|
36303
|
+
/**
|
|
36304
|
+
* Receive a pending webhook event without a public endpoint
|
|
36305
|
+
*
|
|
36306
|
+
* 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.
|
|
36307
|
+
*/
|
|
36308
|
+
const pullWebhookEvent = (options) => (options.client ?? client$1).post({
|
|
36309
|
+
security: [{
|
|
36310
|
+
scheme: "bearer",
|
|
36311
|
+
type: "http"
|
|
36312
|
+
}],
|
|
36313
|
+
url: "/endpoints/{id}/pull",
|
|
36314
|
+
...options,
|
|
36315
|
+
headers: {
|
|
36316
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
36317
|
+
...options.headers
|
|
36318
|
+
}
|
|
36319
|
+
});
|
|
36320
|
+
/**
|
|
36321
|
+
* Report the outcome of a local webhook delivery attempt
|
|
36322
|
+
*
|
|
36323
|
+
* 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.
|
|
36324
|
+
*/
|
|
36325
|
+
const completeWebhookEvent = (options) => (options.client ?? client$1).post({
|
|
36326
|
+
security: [{
|
|
36327
|
+
scheme: "bearer",
|
|
36328
|
+
type: "http"
|
|
36329
|
+
}],
|
|
36330
|
+
url: "/endpoints/{id}/complete",
|
|
36331
|
+
...options,
|
|
36332
|
+
headers: {
|
|
36333
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
36334
|
+
...options.headers
|
|
36335
|
+
}
|
|
36336
|
+
});
|
|
36337
|
+
var EventReceiverError = class extends Error {
|
|
36338
|
+
constructor(message, code, status = 0, retryAfterMs = 0) {
|
|
36339
|
+
super(message);
|
|
36340
|
+
this.code = code;
|
|
36341
|
+
this.status = status;
|
|
36342
|
+
this.retryAfterMs = retryAfterMs;
|
|
36343
|
+
this.name = "EventReceiverError";
|
|
36344
|
+
}
|
|
36345
|
+
};
|
|
36346
|
+
function record(value) {
|
|
36347
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
36348
|
+
}
|
|
36349
|
+
function validateOffer(value) {
|
|
36350
|
+
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");
|
|
36351
|
+
const d = value.delivery;
|
|
36352
|
+
if (d !== null && (!record(d) || ![
|
|
36353
|
+
"queue_id",
|
|
36354
|
+
"event_id",
|
|
36355
|
+
"delivery_id",
|
|
36356
|
+
"lease_token",
|
|
36357
|
+
"event_type",
|
|
36358
|
+
"body",
|
|
36359
|
+
"lease_expires_at"
|
|
36360
|
+
].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");
|
|
36361
|
+
return value;
|
|
36362
|
+
}
|
|
36363
|
+
function retryAfter(value) {
|
|
36364
|
+
if (!value) return 0;
|
|
36365
|
+
const ms = /^\d+(\.\d+)?$/.test(value) ? Number(value) * 1e3 : Date.parse(value) - Date.now();
|
|
36366
|
+
return Number.isFinite(ms) ? Math.max(0, ms) : 0;
|
|
36367
|
+
}
|
|
36368
|
+
function unwrap(result) {
|
|
36369
|
+
if (result.data !== void 0 && !result.error) return result.data;
|
|
36370
|
+
const code = record(result.error) && record(result.error.error) && typeof result.error.error.code === "string" ? result.error.error.code : "request_failed";
|
|
36371
|
+
throw new EventReceiverError(`Event request failed (${result.response?.status ?? 0}, ${code})`, code, result.response?.status ?? 0, retryAfter(result.response?.headers.get("retry-after") ?? null));
|
|
36372
|
+
}
|
|
36373
|
+
/** Shared by the SDK and CLI. Completion retries preserve the original evidence. */
|
|
36374
|
+
var EventConnection = class {
|
|
36375
|
+
socket;
|
|
36376
|
+
pending;
|
|
36377
|
+
origin;
|
|
36378
|
+
touch;
|
|
36379
|
+
constructor(client, endpointId, options = {}) {
|
|
36380
|
+
this.client = client;
|
|
36381
|
+
this.endpointId = endpointId;
|
|
36382
|
+
this.options = options;
|
|
36383
|
+
}
|
|
36384
|
+
close() {
|
|
36385
|
+
const socket = this.socket;
|
|
36386
|
+
this.socket = void 0;
|
|
36387
|
+
this.pending?.reject(new EventReceiverError("Event connection closed", "disconnected"));
|
|
36388
|
+
this.pending = void 0;
|
|
36389
|
+
socket?.close();
|
|
36390
|
+
}
|
|
36391
|
+
async open(signal) {
|
|
36392
|
+
if (this.options.transport === "poll" || this.socket?.readyState === 1) return;
|
|
36393
|
+
const config = this.client.getConfig();
|
|
36394
|
+
if (this.origin !== void 0 && this.origin !== config.baseUrl) throw new EventReceiverError("The API environment changed; restart the receiver", "identity_changed", 403);
|
|
36395
|
+
this.origin = config.baseUrl;
|
|
36396
|
+
const auth = typeof config.auth === "function" ? await config.auth({
|
|
36397
|
+
type: "http",
|
|
36398
|
+
scheme: "bearer"
|
|
36399
|
+
}) : config.auth;
|
|
36400
|
+
const authorization = new Headers(config.headers).get("authorization");
|
|
36401
|
+
const token = typeof auth === "string" ? auth.replace(/^Bearer /i, "") : authorization?.replace(/^Bearer /i, "");
|
|
36402
|
+
if (!token) throw new EventReceiverError("Event receiver requires bearer credentials", "unauthorized", 401);
|
|
36403
|
+
const url = new URL(`${config.baseUrl?.replace(/\/+$/, "")}/endpoints/${encodeURIComponent(this.endpointId)}/stream`);
|
|
36404
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && [
|
|
36405
|
+
"localhost",
|
|
36406
|
+
"127.0.0.1",
|
|
36407
|
+
"[::1]"
|
|
36408
|
+
].includes(url.hostname))) throw new EventReceiverError("Event connections require HTTPS (except loopback development)", "unsupported");
|
|
36409
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
36410
|
+
const socket = (this.options.webSocketFactory ?? ((address, protocol) => new WebSocket(address, protocol)))(url.href, "primitive.events.v1");
|
|
36411
|
+
this.socket = socket;
|
|
36412
|
+
socket.addEventListener("message", (message) => {
|
|
36413
|
+
if (this.socket !== socket) return;
|
|
36414
|
+
let frame;
|
|
36415
|
+
try {
|
|
36416
|
+
frame = JSON.parse(String(message.data));
|
|
36417
|
+
} catch {
|
|
36418
|
+
this.fail(new EventReceiverError("Invalid event frame", "invalid_response"));
|
|
36419
|
+
return;
|
|
36420
|
+
}
|
|
36421
|
+
this.touch?.();
|
|
36422
|
+
if (!record(frame)) {
|
|
36423
|
+
this.fail(new EventReceiverError("Invalid event frame", "invalid_response"));
|
|
36424
|
+
return;
|
|
36425
|
+
}
|
|
36426
|
+
if (frame.type === "ping") {
|
|
36427
|
+
socket.send(JSON.stringify({ type: "pong" }));
|
|
36428
|
+
return;
|
|
36429
|
+
}
|
|
36430
|
+
if (frame.type === "status") {
|
|
36431
|
+
try {
|
|
36432
|
+
this.options.onStatus?.(validateOffer(frame.data));
|
|
36433
|
+
} catch (error) {
|
|
36434
|
+
this.fail(error);
|
|
36435
|
+
}
|
|
36436
|
+
return;
|
|
36437
|
+
}
|
|
36438
|
+
if (frame.type === "error") {
|
|
36439
|
+
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)));
|
|
36440
|
+
return;
|
|
36441
|
+
}
|
|
36442
|
+
this.pending?.resolve(frame);
|
|
36443
|
+
});
|
|
36444
|
+
socket.addEventListener("close", () => {
|
|
36445
|
+
if (this.socket === socket) this.fail(new EventReceiverError("Event stream disconnected", "disconnected"));
|
|
36446
|
+
});
|
|
36447
|
+
socket.addEventListener("error", () => {
|
|
36448
|
+
if (this.socket === socket) this.fail(new EventReceiverError("Event stream connection failed", "disconnected"));
|
|
36449
|
+
});
|
|
36450
|
+
const ready = await this.exchange(signal, void 0, () => {
|
|
36451
|
+
socket.addEventListener("open", () => socket.send(JSON.stringify({
|
|
36452
|
+
type: "authenticate",
|
|
36453
|
+
token
|
|
36454
|
+
})), { once: true });
|
|
36455
|
+
}, 15e3);
|
|
36456
|
+
if (!record(ready) || ready.type !== "ready" || ready.protocol !== "primitive.events.v1") {
|
|
36457
|
+
this.close();
|
|
36458
|
+
throw new EventReceiverError("Unsupported event stream protocol", "unsupported");
|
|
36459
|
+
}
|
|
36460
|
+
}
|
|
36461
|
+
fail(error) {
|
|
36462
|
+
this.pending?.reject(error);
|
|
36463
|
+
this.pending = void 0;
|
|
36464
|
+
this.close();
|
|
36465
|
+
}
|
|
36466
|
+
async exchange(signal, frame, start, timeoutMs = 6e4) {
|
|
36467
|
+
signal.throwIfAborted();
|
|
36468
|
+
if (this.pending) throw new EventReceiverError("Concurrent event operation", "busy", 409);
|
|
36469
|
+
let cleanup = () => {};
|
|
36470
|
+
try {
|
|
36471
|
+
return await new Promise((resolve, reject) => {
|
|
36472
|
+
const abort = () => {
|
|
36473
|
+
reject(signal.reason);
|
|
36474
|
+
this.close();
|
|
36475
|
+
};
|
|
36476
|
+
let timer;
|
|
36477
|
+
this.touch = () => {
|
|
36478
|
+
clearTimeout(timer);
|
|
36479
|
+
timer = setTimeout(() => this.fail(new EventReceiverError("Event stream timed out", "disconnected")), timeoutMs);
|
|
36480
|
+
};
|
|
36481
|
+
this.touch();
|
|
36482
|
+
cleanup = () => {
|
|
36483
|
+
this.touch = void 0;
|
|
36484
|
+
clearTimeout(timer);
|
|
36485
|
+
signal.removeEventListener("abort", abort);
|
|
36486
|
+
this.pending = void 0;
|
|
36487
|
+
};
|
|
36488
|
+
this.pending = {
|
|
36489
|
+
resolve,
|
|
36490
|
+
reject
|
|
36491
|
+
};
|
|
36492
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
36493
|
+
if (frame) this.socket?.send(JSON.stringify(frame));
|
|
36494
|
+
start?.();
|
|
36495
|
+
});
|
|
36496
|
+
} finally {
|
|
36497
|
+
cleanup();
|
|
36498
|
+
}
|
|
36499
|
+
}
|
|
36500
|
+
async receive(signal) {
|
|
36501
|
+
if (this.options.transport === "poll") return validateOffer(unwrap(await pullWebhookEvent({
|
|
36502
|
+
client: this.client,
|
|
36503
|
+
path: { id: this.endpointId },
|
|
36504
|
+
body: { wait_seconds: 25 },
|
|
36505
|
+
responseStyle: "fields",
|
|
36506
|
+
throwOnError: false,
|
|
36507
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(45e3)])
|
|
36508
|
+
})).data);
|
|
36509
|
+
await this.open(signal);
|
|
36510
|
+
const frame = await this.exchange(signal, { type: "receive" }, void 0, 9e4);
|
|
36511
|
+
if (!record(frame) || frame.type !== "event") throw new EventReceiverError("Invalid event frame", "invalid_response");
|
|
36512
|
+
return validateOffer(frame.data);
|
|
36513
|
+
}
|
|
36514
|
+
async complete(body, signal) {
|
|
36515
|
+
let receipt;
|
|
36516
|
+
if (this.options.transport === "poll") receipt = unwrap(await completeWebhookEvent({
|
|
36517
|
+
client: this.client,
|
|
36518
|
+
path: { id: this.endpointId },
|
|
36519
|
+
body,
|
|
36520
|
+
responseStyle: "fields",
|
|
36521
|
+
throwOnError: false,
|
|
36522
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
|
|
36523
|
+
})).data;
|
|
36524
|
+
else {
|
|
36525
|
+
await this.open(signal);
|
|
36526
|
+
const frame = await this.exchange(signal, {
|
|
36527
|
+
type: "complete",
|
|
36528
|
+
body
|
|
36529
|
+
}, void 0, 15e3);
|
|
36530
|
+
if (!record(frame) || frame.type !== "receipt") throw new EventReceiverError("Invalid event receipt", "invalid_response");
|
|
36531
|
+
receipt = frame.data;
|
|
36532
|
+
}
|
|
36533
|
+
if (!record(receipt) || !["completed", "already_completed"].includes(String(receipt.result))) throw new EventReceiverError("Invalid event receipt", "invalid_response");
|
|
36534
|
+
}
|
|
36535
|
+
};
|
|
36536
|
+
//#endregion
|
|
34401
36537
|
//#region src/oclif/listen-state.ts
|
|
34402
36538
|
var ListenStateError = class extends Error {};
|
|
34403
36539
|
function normalizeListenOrigin(value) {
|
|
@@ -34621,7 +36757,7 @@ function resolveListenSubscription(configDir, identity, explicit) {
|
|
|
34621
36757
|
var ListenError = class extends Error {};
|
|
34622
36758
|
var RequestFailure = class extends ListenError {
|
|
34623
36759
|
constructor(status, code, retryAfter) {
|
|
34624
|
-
super(status === 401 || status === 403 ? "Listener authorization failed. Run primitive signin or check your API key and account permissions." : status === 409 && code === "subscription_conflict" ? "This subscription has different event filters. Omit --events to resume it, or use primitive listen --subscription new-subscription (add your --events selection)." : `Listener API request failed (HTTP ${status || "transport"}, ${code}).`);
|
|
36760
|
+
super(code === "agent_connection_scope_forbidden" ? "This operation is outside the connected address grant. Use inbound email events and this credential’s own subscription." : status === 401 || status === 403 ? "Listener authorization failed. Run primitive signin or check your API key and account permissions." : status === 409 && code === "subscription_conflict" ? "This subscription has different event filters. Omit --events to resume it, or use primitive listen --subscription new-subscription (add your --events selection)." : `Listener API request failed (HTTP ${status || "transport"}, ${code}).`);
|
|
34625
36761
|
this.status = status;
|
|
34626
36762
|
this.code = code;
|
|
34627
36763
|
this.retryAfter = retryAfter;
|
|
@@ -34681,8 +36817,10 @@ async function runListen(options) {
|
|
|
34681
36817
|
let accountId;
|
|
34682
36818
|
let verifiedKey;
|
|
34683
36819
|
let verified = false;
|
|
36820
|
+
let connectedCredential = false;
|
|
34684
36821
|
let confirmed = 0;
|
|
34685
36822
|
let release;
|
|
36823
|
+
let stream;
|
|
34686
36824
|
async function retry(operation) {
|
|
34687
36825
|
for (let attempt = 0;; attempt++) {
|
|
34688
36826
|
signal.throwIfAborted();
|
|
@@ -34693,9 +36831,14 @@ async function runListen(options) {
|
|
|
34693
36831
|
error = failure(result, now());
|
|
34694
36832
|
} catch (caught) {
|
|
34695
36833
|
if (signal.aborted) throw signal.reason;
|
|
34696
|
-
if (caught instanceof
|
|
34697
|
-
|
|
34698
|
-
|
|
36834
|
+
if (caught instanceof EventReceiverError) {
|
|
36835
|
+
if (["invalid_response", "unsupported"].includes(caught.code)) throw new ListenError(caught.message);
|
|
36836
|
+
error = new RequestFailure(caught.status, caught.code, caught.retryAfterMs);
|
|
36837
|
+
} else {
|
|
36838
|
+
if (caught instanceof ListenError) throw caught;
|
|
36839
|
+
if (caught instanceof SyntaxError) throw new ListenError("The API returned malformed JSON.");
|
|
36840
|
+
error = new RequestFailure(0, "transport", 0);
|
|
36841
|
+
}
|
|
34699
36842
|
}
|
|
34700
36843
|
if (![
|
|
34701
36844
|
0,
|
|
@@ -34719,7 +36862,7 @@ async function runListen(options) {
|
|
|
34719
36862
|
signal: AbortSignal.any([
|
|
34720
36863
|
signal,
|
|
34721
36864
|
...init?.signal ? [init.signal] : [],
|
|
34722
|
-
AbortSignal.timeout(
|
|
36865
|
+
AbortSignal.timeout(45e3)
|
|
34723
36866
|
])
|
|
34724
36867
|
})
|
|
34725
36868
|
});
|
|
@@ -34730,6 +36873,15 @@ async function runListen(options) {
|
|
|
34730
36873
|
if (origin !== void 0 && origin !== base) throw new ListenError("The API environment changed while listening. Restart the listener.");
|
|
34731
36874
|
origin = base;
|
|
34732
36875
|
if (!verified || auth.auth.apiKey !== verifiedKey) {
|
|
36876
|
+
if (auth.auth.apiKey?.startsWith("pconn_")) {
|
|
36877
|
+
connectedCredential = true;
|
|
36878
|
+
const identity = `connection:${scryptSync(auth.auth.apiKey, "primitive-listener-identity-v1", 32).toString("hex")}`;
|
|
36879
|
+
if (accountId !== void 0 && accountId !== identity) throw new ListenError("The connected credential changed while listening. Restart the listener.");
|
|
36880
|
+
accountId = identity;
|
|
36881
|
+
verifiedKey = auth.auth.apiKey;
|
|
36882
|
+
verified = true;
|
|
36883
|
+
return auth.apiClient.client;
|
|
36884
|
+
}
|
|
34733
36885
|
const account = await retry(() => getAccount({
|
|
34734
36886
|
client: auth.apiClient.client,
|
|
34735
36887
|
responseStyle: "fields",
|
|
@@ -34766,20 +36918,35 @@ async function runListen(options) {
|
|
|
34766
36918
|
});
|
|
34767
36919
|
const endpoint = registered.data;
|
|
34768
36920
|
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.");
|
|
36921
|
+
const streamClient = await freshClient();
|
|
36922
|
+
if (options.transport !== "poll") {
|
|
36923
|
+
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.");
|
|
36924
|
+
if (!endpoint.receiver_capabilities.completion_modes.includes(options.mode ?? "stdout")) throw new ListenError("This API does not support the selected listener handler mode.");
|
|
36925
|
+
stream = new EventConnection(streamClient, endpoint.id);
|
|
36926
|
+
}
|
|
34769
36927
|
const selected = endpoint.rules?.event_types;
|
|
34770
|
-
const selection = Array.isArray(selected) && selected.every((value) => typeof value === "string" && /^[a-zA-Z0-9_.-]+$/.test(value)) ? selected.join(", ") : "all events";
|
|
36928
|
+
const selection = Array.isArray(selected) && selected.every((value) => typeof value === "string" && /^[a-zA-Z0-9_.-]+$/.test(value)) ? selected.join(", ") : endpoint.recipient ? `inbound email for ${JSON.stringify(endpoint.recipient)}` : "all events";
|
|
34771
36929
|
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`);
|
|
34772
36930
|
let lastGap = -1;
|
|
34773
36931
|
let lastBacklog = -1;
|
|
34774
36932
|
while (!signal.aborted && (options.number === void 0 || confirmed < options.number)) {
|
|
34775
36933
|
const pollStarted = now();
|
|
34776
|
-
const result = await retry(async () =>
|
|
34777
|
-
|
|
34778
|
-
|
|
34779
|
-
|
|
34780
|
-
|
|
34781
|
-
|
|
34782
|
-
|
|
36934
|
+
const result = await retry(async () => {
|
|
36935
|
+
if (stream) {
|
|
36936
|
+
streamClient.setConfig((await freshClient()).getConfig());
|
|
36937
|
+
return { data: {
|
|
36938
|
+
success: true,
|
|
36939
|
+
data: await stream.receive(signal)
|
|
36940
|
+
} };
|
|
36941
|
+
}
|
|
36942
|
+
return pullWebhookEvent$1({
|
|
36943
|
+
client: await freshClient(),
|
|
36944
|
+
path: { id: endpoint.id },
|
|
36945
|
+
body: { wait_seconds: 25 },
|
|
36946
|
+
responseStyle: "fields",
|
|
36947
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(45e3)])
|
|
36948
|
+
});
|
|
36949
|
+
});
|
|
34783
36950
|
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
36951
|
const data = result.data;
|
|
34785
36952
|
const gapReasons = {
|
|
@@ -34821,14 +36988,25 @@ async function runListen(options) {
|
|
|
34821
36988
|
delivery_id: delivery.delivery_id,
|
|
34822
36989
|
lease_token: delivery.lease_token
|
|
34823
36990
|
};
|
|
36991
|
+
if ((connectedCredential || endpoint.recipient) && completion.mode === "http") completion.confirmed = false;
|
|
34824
36992
|
try {
|
|
34825
|
-
const receipt = await retry(async () =>
|
|
34826
|
-
|
|
34827
|
-
|
|
34828
|
-
|
|
34829
|
-
|
|
34830
|
-
|
|
34831
|
-
|
|
36993
|
+
const receipt = await retry(async () => {
|
|
36994
|
+
if (stream) {
|
|
36995
|
+
streamClient.setConfig((await freshClient()).getConfig());
|
|
36996
|
+
await stream.complete(completion, signal);
|
|
36997
|
+
return { data: {
|
|
36998
|
+
success: true,
|
|
36999
|
+
data: { result: "completed" }
|
|
37000
|
+
} };
|
|
37001
|
+
}
|
|
37002
|
+
return completeWebhookEvent$1({
|
|
37003
|
+
client: await freshClient(),
|
|
37004
|
+
path: { id: endpoint.id },
|
|
37005
|
+
body: completion,
|
|
37006
|
+
responseStyle: "fields",
|
|
37007
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)])
|
|
37008
|
+
});
|
|
37009
|
+
});
|
|
34832
37010
|
if (receipt.success !== true || !["completed", "already_completed"].includes(receipt.data?.result)) throw new ListenError("The API returned an invalid completion receipt.");
|
|
34833
37011
|
if (handled.succeeded) confirmed++;
|
|
34834
37012
|
else stderr.write("Handler reported failure; the server will apply its retry policy.\n");
|
|
@@ -34840,6 +37018,7 @@ async function runListen(options) {
|
|
|
34840
37018
|
} catch (error) {
|
|
34841
37019
|
if (!signal.aborted) throw error;
|
|
34842
37020
|
} finally {
|
|
37021
|
+
stream?.close();
|
|
34843
37022
|
release?.();
|
|
34844
37023
|
}
|
|
34845
37024
|
return confirmed;
|
|
@@ -34848,14 +37027,29 @@ async function runListen(options) {
|
|
|
34848
37027
|
//#region src/oclif/commands/listen.ts
|
|
34849
37028
|
var ListenCommand = class ListenCommand extends Command {
|
|
34850
37029
|
static summary = "Receive webhook events locally without a public endpoint";
|
|
34851
|
-
static description = "Subscribe once and reconnect using the same durable server queue. Use a short --exec hook to durably accept an event, --forward-to for a local webhook, or newline-delimited JSON on stdout.";
|
|
37030
|
+
static description = "Subscribe once and reconnect using the same durable server queue. Connected-agent credentials automatically receive only their assigned address. Use a short --exec hook to durably accept an event, --forward-to for a local webhook, or newline-delimited JSON on stdout.";
|
|
34852
37031
|
static examples = [
|
|
34853
37032
|
"<%= config.bin %> listen",
|
|
34854
37033
|
"<%= config.bin %> listen --subscription my-agent --exec \"python3 accept.py\"",
|
|
34855
|
-
"<%= config.bin %> listen --forward-to
|
|
37034
|
+
"<%= config.bin %> listen --forward-to localhost:3000",
|
|
37035
|
+
"<%= config.bin %> listen --once --timeout 60"
|
|
34856
37036
|
];
|
|
34857
37037
|
static flags = {
|
|
34858
|
-
|
|
37038
|
+
transport: Flags.string({
|
|
37039
|
+
description: "Event transport; WebSocket is the default.",
|
|
37040
|
+
options: ["websocket", "poll"],
|
|
37041
|
+
default: "websocket"
|
|
37042
|
+
}),
|
|
37043
|
+
once: Flags.boolean({
|
|
37044
|
+
description: "Exit after one successfully handled and confirmed event.",
|
|
37045
|
+
exclusive: ["number"]
|
|
37046
|
+
}),
|
|
37047
|
+
timeout: Flags.integer({
|
|
37048
|
+
description: "Stop after this many seconds; exit 2 if the requested count was not reached.",
|
|
37049
|
+
min: 1,
|
|
37050
|
+
max: 2147483
|
|
37051
|
+
}),
|
|
37052
|
+
subscription: Flags.string({ description: "Stable subscription name. Defaults to a saved name for this credential scope and API environment." }),
|
|
34859
37053
|
exec: Flags.string({
|
|
34860
37054
|
description: "Short POSIX shell command accepting each complete event on stdin.",
|
|
34861
37055
|
exclusive: ["forward-to"]
|
|
@@ -34888,17 +37082,23 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
34888
37082
|
forwardTo: flags["forward-to"]
|
|
34889
37083
|
});
|
|
34890
37084
|
const controller = new AbortController();
|
|
37085
|
+
let timedOut = false;
|
|
37086
|
+
const timer = flags.timeout === void 0 ? void 0 : setTimeout(() => {
|
|
37087
|
+
timedOut = true;
|
|
37088
|
+
controller.abort();
|
|
37089
|
+
}, flags.timeout * 1e3);
|
|
34891
37090
|
const cancel = () => controller.abort();
|
|
34892
37091
|
process.on("SIGINT", cancel);
|
|
34893
37092
|
process.on("SIGTERM", cancel);
|
|
34894
37093
|
try {
|
|
34895
37094
|
await runListen({
|
|
37095
|
+
transport: flags.transport === "poll" ? "poll" : "websocket",
|
|
34896
37096
|
configDir: this.config.configDir,
|
|
34897
37097
|
apiKey: flags["api-key"],
|
|
34898
37098
|
apiBaseUrl: flags["api-base-url"],
|
|
34899
37099
|
subscription: flags.subscription,
|
|
34900
37100
|
events: events === void 0 ? void 0 : [...new Set(events)],
|
|
34901
|
-
number: flags.number,
|
|
37101
|
+
number: flags.once ? 1 : flags.number,
|
|
34902
37102
|
mode: flags.exec ? "exec" : flags["forward-to"] ? "http" : "stdout",
|
|
34903
37103
|
handler,
|
|
34904
37104
|
signal: controller.signal
|
|
@@ -34906,10 +37106,11 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
34906
37106
|
} catch (error) {
|
|
34907
37107
|
throw new Errors.CLIError(error instanceof ListenError || error instanceof ListenStateError ? error.message : "The listener stopped because of a local state or connection error.");
|
|
34908
37108
|
} finally {
|
|
37109
|
+
clearTimeout(timer);
|
|
34909
37110
|
process.removeListener("SIGINT", cancel);
|
|
34910
37111
|
process.removeListener("SIGTERM", cancel);
|
|
34911
37112
|
}
|
|
34912
|
-
if (controller.signal.aborted) process.exitCode = 130;
|
|
37113
|
+
if (controller.signal.aborted) process.exitCode = timedOut ? 2 : 130;
|
|
34913
37114
|
}
|
|
34914
37115
|
};
|
|
34915
37116
|
//#endregion
|
|
@@ -35172,7 +37373,7 @@ const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
|
|
|
35172
37373
|
function cliError$3(message) {
|
|
35173
37374
|
return new Errors.CLIError(message, { exit: 1 });
|
|
35174
37375
|
}
|
|
35175
|
-
function sleep
|
|
37376
|
+
function sleep(ms) {
|
|
35176
37377
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
35177
37378
|
}
|
|
35178
37379
|
function openBrowser(url) {
|
|
@@ -35337,7 +37538,7 @@ var LoginCommand$1 = class extends Command {
|
|
|
35337
37538
|
let interval = Math.min(Math.max(1, start.interval), MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS);
|
|
35338
37539
|
let nextPollDelay = 1;
|
|
35339
37540
|
while (Date.now() < deadline) {
|
|
35340
|
-
await sleep
|
|
37541
|
+
await sleep(nextPollDelay * 1e3);
|
|
35341
37542
|
nextPollDelay = interval;
|
|
35342
37543
|
const polled = await pollCliLogin({
|
|
35343
37544
|
body: { device_code: start.device_code },
|
|
@@ -36967,331 +39168,6 @@ var OrgSecretsSetCommand = class OrgSecretsSetCommand extends Command {
|
|
|
36967
39168
|
}
|
|
36968
39169
|
};
|
|
36969
39170
|
//#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
39171
|
//#region src/oclif/commands/payloads.ts
|
|
37296
39172
|
const DEFAULT_API_BASE_URL = "https://api.primitive.dev";
|
|
37297
39173
|
const authFlags = {
|
|
@@ -41739,6 +43615,8 @@ const CANONICAL_OPERATION_ALIASES = {
|
|
|
41739
43615
|
};
|
|
41740
43616
|
const DESCRIBE_OPERATION_ALIASES = {
|
|
41741
43617
|
...CANONICAL_OPERATION_ALIASES,
|
|
43618
|
+
"credits:balance": "credits:get-credit-balance",
|
|
43619
|
+
"credits:redeem": "credits:redeem-credit-code",
|
|
41742
43620
|
"domains:zone-file": "domains:download-domain-zone-file",
|
|
41743
43621
|
"functions:logs": "functions:list-function-logs",
|
|
41744
43622
|
"memories:delete": "memories:delete-memory",
|
|
@@ -41756,7 +43634,8 @@ const OVERRIDDEN_OPERATION_IDS = new Set([
|
|
|
41756
43634
|
"inbox:get-inbox-status",
|
|
41757
43635
|
"search:semantic-search",
|
|
41758
43636
|
"payments:register-payout-address",
|
|
41759
|
-
"payments:pay-challenge"
|
|
43637
|
+
"payments:pay-challenge",
|
|
43638
|
+
"credits:redeem-credit-code"
|
|
41760
43639
|
]);
|
|
41761
43640
|
const generatedCommands = Object.fromEntries(operationManifest.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(operationId(operation))).map((operation) => [operationId(operation), createOperationCommand(operation)]));
|
|
41762
43641
|
const COMMANDS = {
|
|
@@ -41837,6 +43716,9 @@ const COMMANDS = {
|
|
|
41837
43716
|
"memories:get": MemoriesGetCommand,
|
|
41838
43717
|
"memories:delete": MemoriesDeleteCommand,
|
|
41839
43718
|
"memories:search": MemoriesSearchCommand,
|
|
43719
|
+
"credits:redeem": CreditsRedeemCommand,
|
|
43720
|
+
"credits:redeem-credit-code": CreditsRedeemCommand,
|
|
43721
|
+
"credits:balance": CreditsBalanceCommand,
|
|
41840
43722
|
...Object.fromEntries(Object.entries(CANONICAL_OPERATION_ALIASES).map(([alias, target]) => {
|
|
41841
43723
|
const command = generatedCommands[target];
|
|
41842
43724
|
if (!command) throw new Error(`Missing generated command target for alias ${alias}`);
|