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.
@@ -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.26.0";
32075
- const CLI_VERSION_RANGE = "^1.26.0";
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 ListenError) throw caught;
34697
- if (caught instanceof SyntaxError) throw new ListenError("The API returned malformed JSON.");
34698
- error = new RequestFailure(0, "transport", 0);
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(3e4)
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 () => pullWebhookEvent({
34777
- client: await freshClient(),
34778
- path: { id: endpoint.id },
34779
- body: { wait_seconds: 25 },
34780
- responseStyle: "fields",
34781
- signal: AbortSignal.any([signal, AbortSignal.timeout(4e4)])
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 () => completeWebhookEvent({
34826
- client: await freshClient(),
34827
- path: { id: endpoint.id },
34828
- body: completion,
34829
- responseStyle: "fields",
34830
- signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)])
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 http://127.0.0.1:3000/webhook"
37034
+ "<%= config.bin %> listen --forward-to localhost:3000",
37035
+ "<%= config.bin %> listen --once --timeout 60"
34856
37036
  ];
34857
37037
  static flags = {
34858
- subscription: Flags.string({ description: "Stable subscription name. Defaults to a saved name for this account and API environment." }),
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$1(ms) {
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$1(nextPollDelay * 1e3);
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}`);