primitive 1.26.1 → 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,
@@ -8075,7 +8253,15 @@ const openapiDocument = {
8075
8253
  "event_content_unavailable",
8076
8254
  "event_preparation_failed",
8077
8255
  "subscription_unavailable",
8078
- "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"
8079
8265
  ]
8080
8266
  },
8081
8267
  "message": { "type": "string" },
@@ -9041,6 +9227,148 @@ const openapiDocument = {
9041
9227
  } },
9042
9228
  "required": ["secret"]
9043
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
+ },
9044
9372
  "InboxStatus": {
9045
9373
  "type": "object",
9046
9374
  "additionalProperties": false,
@@ -11405,14 +11733,16 @@ const openapiDocument = {
11405
11733
  "oneOf": [
11406
11734
  { "$ref": "#/components/schemas/CompleteWebhookHttpInput" },
11407
11735
  { "$ref": "#/components/schemas/CompleteWebhookExecInput" },
11408
- { "$ref": "#/components/schemas/CompleteWebhookStdoutInput" }
11736
+ { "$ref": "#/components/schemas/CompleteWebhookStdoutInput" },
11737
+ { "$ref": "#/components/schemas/CompleteWebhookSdkInput" }
11409
11738
  ],
11410
11739
  "discriminator": {
11411
11740
  "propertyName": "mode",
11412
11741
  "mapping": {
11413
11742
  "http": "#/components/schemas/CompleteWebhookHttpInput",
11414
11743
  "exec": "#/components/schemas/CompleteWebhookExecInput",
11415
- "stdout": "#/components/schemas/CompleteWebhookStdoutInput"
11744
+ "stdout": "#/components/schemas/CompleteWebhookStdoutInput",
11745
+ "sdk": "#/components/schemas/CompleteWebhookSdkInput"
11416
11746
  }
11417
11747
  }
11418
11748
  },
@@ -11566,6 +11896,49 @@ const openapiDocument = {
11566
11896
  ],
11567
11897
  "additionalProperties": false
11568
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
+ },
11569
11942
  "CompleteWebhookResponse": {
11570
11943
  "type": "object",
11571
11944
  "properties": {
@@ -11599,6 +11972,35 @@ const openapiDocument = {
11599
11972
  "Endpoint": {
11600
11973
  "type": "object",
11601
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
+ },
11602
12004
  "id": {
11603
12005
  "type": "string",
11604
12006
  "format": "uuid"
@@ -15409,6 +15811,181 @@ const operationManifest = [
15409
15811
  "tag": "CLI",
15410
15812
  "tagCommand": "cli"
15411
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
+ },
15412
15989
  {
15413
15990
  "binaryResponse": false,
15414
15991
  "bodyRequired": true,
@@ -17908,6 +18485,49 @@ const operationManifest = [
17908
18485
  "write_succeeded"
17909
18486
  ],
17910
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
17911
18531
  }
17912
18532
  ],
17913
18533
  "discriminator": {
@@ -17915,7 +18535,8 @@ const operationManifest = [
17915
18535
  "mapping": {
17916
18536
  "http": "#/components/schemas/CompleteWebhookHttpInput",
17917
18537
  "exec": "#/components/schemas/CompleteWebhookExecInput",
17918
- "stdout": "#/components/schemas/CompleteWebhookStdoutInput"
18538
+ "stdout": "#/components/schemas/CompleteWebhookStdoutInput",
18539
+ "sdk": "#/components/schemas/CompleteWebhookSdkInput"
17919
18540
  }
17920
18541
  }
17921
18542
  },
@@ -17958,7 +18579,7 @@ const operationManifest = [
17958
18579
  "binaryResponse": false,
17959
18580
  "bodyRequired": true,
17960
18581
  "command": "create-endpoint",
17961
- "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.",
17962
18583
  "hasJsonBody": true,
17963
18584
  "method": "POST",
17964
18585
  "operationId": "createEndpoint",
@@ -18026,6 +18647,35 @@ const operationManifest = [
18026
18647
  "responseSchema": {
18027
18648
  "type": "object",
18028
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
+ },
18029
18679
  "id": {
18030
18680
  "type": "string",
18031
18681
  "format": "uuid"
@@ -18177,6 +18827,35 @@ const operationManifest = [
18177
18827
  "items": {
18178
18828
  "type": "object",
18179
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
+ },
18180
18859
  "id": {
18181
18860
  "type": "string",
18182
18861
  "format": "uuid"
@@ -18618,6 +19297,35 @@ const operationManifest = [
18618
19297
  "responseSchema": {
18619
19298
  "type": "object",
18620
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
+ },
18621
19329
  "id": {
18622
19330
  "type": "string",
18623
19331
  "format": "uuid"
@@ -27644,7 +28352,7 @@ function isMemoryJsonValue(value, seen = /* @__PURE__ */ new Set()) {
27644
28352
  const API_HEADERS_ENV = "PRIMITIVE_API_HEADERS";
27645
28353
  const OAUTH_REFRESH_SKEW_MS = 60 * 1e3;
27646
28354
  const SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE = "Saved Primitive CLI OAuth session expired or was revoked. Run `primitive signin` to authenticate again.";
27647
- function mergeHeaders(...headers) {
28355
+ function mergeHeaders$1(...headers) {
27648
28356
  const merged = {};
27649
28357
  for (const headerSet of headers) {
27650
28358
  if (!headerSet) continue;
@@ -27682,7 +28390,7 @@ function resolveCliApiRequestConfig(params) {
27682
28390
  apiBaseUrl,
27683
28391
  baseUrlOverridden: apiBaseUrl !== void 0,
27684
28392
  environmentName: currentEnvironment?.name ?? null,
27685
- headers: mergeHeaders(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
28393
+ headers: mergeHeaders$1(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
27686
28394
  resolvedApiBaseUrl: normalizeApiBaseUrl(apiBaseUrl)
27687
28395
  };
27688
28396
  }
@@ -30232,6 +30940,240 @@ var ConfigCommand = class extends ConfigListCommand {
30232
30940
  static description = "Manage local Primitive CLI request environments for API endpoint overrides and request headers.";
30233
30941
  };
30234
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
30235
31177
  //#region src/oclif/commands/doctor.ts
30236
31178
  const MIN_NODE_MAJOR = 22;
30237
31179
  function renderRow({ label, outcome }) {
@@ -32114,8 +33056,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
32114
33056
  name: "Primitive Team",
32115
33057
  url: "https://primitive.dev"
32116
33058
  };
32117
- const SDK_VERSION_RANGE = "^1.26.1";
32118
- const CLI_VERSION_RANGE = "^1.26.1";
33059
+ const SDK_VERSION_RANGE = "^1.29.0";
33060
+ const CLI_VERSION_RANGE = "^1.29.0";
32119
33061
  const ESBUILD_VERSION_RANGE = "^0.27.0";
32120
33062
  function renderHandler() {
32121
33063
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -34149,6 +35091,11 @@ const FORWARDED_HEADERS = new Set([
34149
35091
  "x-primitive-email-id",
34150
35092
  "x-primitive-endpoint-id"
34151
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
+ }
34152
35099
  function validateListenHandlerOptions(options) {
34153
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.");
34154
35101
  if (options.exec !== void 0 && options.forwardTo !== void 0) throw new Error("Choose either --exec or --forward-to.");
@@ -34156,7 +35103,7 @@ function validateListenHandlerOptions(options) {
34156
35103
  if (options.forwardTo !== void 0) {
34157
35104
  let url;
34158
35105
  try {
34159
- url = new URL(options.forwardTo);
35106
+ url = new URL(normalizeForwardTarget(options.forwardTo));
34160
35107
  } catch {
34161
35108
  throw new Error("--forward-to requires an HTTP or HTTPS URL.");
34162
35109
  }
@@ -34301,7 +35248,7 @@ var ForwardFailure = class extends Error {
34301
35248
  };
34302
35249
  async function forward(target, delivery, signal) {
34303
35250
  signal.throwIfAborted();
34304
- const url = new URL(target);
35251
+ const url = new URL(normalizeForwardTarget(target));
34305
35252
  const body = Buffer.from(delivery.body, "utf8");
34306
35253
  const headers = {};
34307
35254
  for (const [name, value] of Object.entries(delivery.headers)) if (FORWARDED_HEADERS.has(name.toLowerCase())) headers[name.toLowerCase()] = value;
@@ -34441,6 +35388,1152 @@ function createListenHandler(options = {}) {
34441
35388
  };
34442
35389
  }
34443
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
34444
36537
  //#region src/oclif/listen-state.ts
34445
36538
  var ListenStateError = class extends Error {};
34446
36539
  function normalizeListenOrigin(value) {
@@ -34664,7 +36757,7 @@ function resolveListenSubscription(configDir, identity, explicit) {
34664
36757
  var ListenError = class extends Error {};
34665
36758
  var RequestFailure = class extends ListenError {
34666
36759
  constructor(status, code, retryAfter) {
34667
- 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}).`);
34668
36761
  this.status = status;
34669
36762
  this.code = code;
34670
36763
  this.retryAfter = retryAfter;
@@ -34724,8 +36817,10 @@ async function runListen(options) {
34724
36817
  let accountId;
34725
36818
  let verifiedKey;
34726
36819
  let verified = false;
36820
+ let connectedCredential = false;
34727
36821
  let confirmed = 0;
34728
36822
  let release;
36823
+ let stream;
34729
36824
  async function retry(operation) {
34730
36825
  for (let attempt = 0;; attempt++) {
34731
36826
  signal.throwIfAborted();
@@ -34736,9 +36831,14 @@ async function runListen(options) {
34736
36831
  error = failure(result, now());
34737
36832
  } catch (caught) {
34738
36833
  if (signal.aborted) throw signal.reason;
34739
- if (caught instanceof ListenError) throw caught;
34740
- if (caught instanceof SyntaxError) throw new ListenError("The API returned malformed JSON.");
34741
- 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
+ }
34742
36842
  }
34743
36843
  if (![
34744
36844
  0,
@@ -34762,7 +36862,7 @@ async function runListen(options) {
34762
36862
  signal: AbortSignal.any([
34763
36863
  signal,
34764
36864
  ...init?.signal ? [init.signal] : [],
34765
- AbortSignal.timeout(3e4)
36865
+ AbortSignal.timeout(45e3)
34766
36866
  ])
34767
36867
  })
34768
36868
  });
@@ -34773,6 +36873,15 @@ async function runListen(options) {
34773
36873
  if (origin !== void 0 && origin !== base) throw new ListenError("The API environment changed while listening. Restart the listener.");
34774
36874
  origin = base;
34775
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
+ }
34776
36885
  const account = await retry(() => getAccount({
34777
36886
  client: auth.apiClient.client,
34778
36887
  responseStyle: "fields",
@@ -34809,20 +36918,35 @@ async function runListen(options) {
34809
36918
  });
34810
36919
  const endpoint = registered.data;
34811
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
+ }
34812
36927
  const selected = endpoint.rules?.event_types;
34813
- 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";
34814
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`);
34815
36930
  let lastGap = -1;
34816
36931
  let lastBacklog = -1;
34817
36932
  while (!signal.aborted && (options.number === void 0 || confirmed < options.number)) {
34818
36933
  const pollStarted = now();
34819
- const result = await retry(async () => pullWebhookEvent({
34820
- client: await freshClient(),
34821
- path: { id: endpoint.id },
34822
- body: { wait_seconds: 25 },
34823
- responseStyle: "fields",
34824
- signal: AbortSignal.any([signal, AbortSignal.timeout(4e4)])
34825
- }));
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
+ });
34826
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.");
34827
36951
  const data = result.data;
34828
36952
  const gapReasons = {
@@ -34864,14 +36988,25 @@ async function runListen(options) {
34864
36988
  delivery_id: delivery.delivery_id,
34865
36989
  lease_token: delivery.lease_token
34866
36990
  };
36991
+ if ((connectedCredential || endpoint.recipient) && completion.mode === "http") completion.confirmed = false;
34867
36992
  try {
34868
- const receipt = await retry(async () => completeWebhookEvent({
34869
- client: await freshClient(),
34870
- path: { id: endpoint.id },
34871
- body: completion,
34872
- responseStyle: "fields",
34873
- signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)])
34874
- }));
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
+ });
34875
37010
  if (receipt.success !== true || !["completed", "already_completed"].includes(receipt.data?.result)) throw new ListenError("The API returned an invalid completion receipt.");
34876
37011
  if (handled.succeeded) confirmed++;
34877
37012
  else stderr.write("Handler reported failure; the server will apply its retry policy.\n");
@@ -34883,6 +37018,7 @@ async function runListen(options) {
34883
37018
  } catch (error) {
34884
37019
  if (!signal.aborted) throw error;
34885
37020
  } finally {
37021
+ stream?.close();
34886
37022
  release?.();
34887
37023
  }
34888
37024
  return confirmed;
@@ -34891,14 +37027,29 @@ async function runListen(options) {
34891
37027
  //#region src/oclif/commands/listen.ts
34892
37028
  var ListenCommand = class ListenCommand extends Command {
34893
37029
  static summary = "Receive webhook events locally without a public endpoint";
34894
- 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.";
34895
37031
  static examples = [
34896
37032
  "<%= config.bin %> listen",
34897
37033
  "<%= config.bin %> listen --subscription my-agent --exec \"python3 accept.py\"",
34898
- "<%= 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"
34899
37036
  ];
34900
37037
  static flags = {
34901
- 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." }),
34902
37053
  exec: Flags.string({
34903
37054
  description: "Short POSIX shell command accepting each complete event on stdin.",
34904
37055
  exclusive: ["forward-to"]
@@ -34931,17 +37082,23 @@ var ListenCommand = class ListenCommand extends Command {
34931
37082
  forwardTo: flags["forward-to"]
34932
37083
  });
34933
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);
34934
37090
  const cancel = () => controller.abort();
34935
37091
  process.on("SIGINT", cancel);
34936
37092
  process.on("SIGTERM", cancel);
34937
37093
  try {
34938
37094
  await runListen({
37095
+ transport: flags.transport === "poll" ? "poll" : "websocket",
34939
37096
  configDir: this.config.configDir,
34940
37097
  apiKey: flags["api-key"],
34941
37098
  apiBaseUrl: flags["api-base-url"],
34942
37099
  subscription: flags.subscription,
34943
37100
  events: events === void 0 ? void 0 : [...new Set(events)],
34944
- number: flags.number,
37101
+ number: flags.once ? 1 : flags.number,
34945
37102
  mode: flags.exec ? "exec" : flags["forward-to"] ? "http" : "stdout",
34946
37103
  handler,
34947
37104
  signal: controller.signal
@@ -34949,10 +37106,11 @@ var ListenCommand = class ListenCommand extends Command {
34949
37106
  } catch (error) {
34950
37107
  throw new Errors.CLIError(error instanceof ListenError || error instanceof ListenStateError ? error.message : "The listener stopped because of a local state or connection error.");
34951
37108
  } finally {
37109
+ clearTimeout(timer);
34952
37110
  process.removeListener("SIGINT", cancel);
34953
37111
  process.removeListener("SIGTERM", cancel);
34954
37112
  }
34955
- if (controller.signal.aborted) process.exitCode = 130;
37113
+ if (controller.signal.aborted) process.exitCode = timedOut ? 2 : 130;
34956
37114
  }
34957
37115
  };
34958
37116
  //#endregion
@@ -35215,7 +37373,7 @@ const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
35215
37373
  function cliError$3(message) {
35216
37374
  return new Errors.CLIError(message, { exit: 1 });
35217
37375
  }
35218
- function sleep$1(ms) {
37376
+ function sleep(ms) {
35219
37377
  return new Promise((resolve) => setTimeout(resolve, ms));
35220
37378
  }
35221
37379
  function openBrowser(url) {
@@ -35380,7 +37538,7 @@ var LoginCommand$1 = class extends Command {
35380
37538
  let interval = Math.min(Math.max(1, start.interval), MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS);
35381
37539
  let nextPollDelay = 1;
35382
37540
  while (Date.now() < deadline) {
35383
- await sleep$1(nextPollDelay * 1e3);
37541
+ await sleep(nextPollDelay * 1e3);
35384
37542
  nextPollDelay = interval;
35385
37543
  const polled = await pollCliLogin({
35386
37544
  body: { device_code: start.device_code },
@@ -37010,331 +39168,6 @@ var OrgSecretsSetCommand = class OrgSecretsSetCommand extends Command {
37010
39168
  }
37011
39169
  };
37012
39170
  //#endregion
37013
- //#region ../sdk-node/dist/payloads-BfJAtqW9.js
37014
- /**
37015
- * Load a Node builtin at call time. Exported for direct testing of the
37016
- * unavailable-module error path; production code goes through the typed
37017
- * wrappers below.
37018
- */
37019
- async function loadBuiltin(specifier, operation) {
37020
- let builtin;
37021
- if (typeof process !== "undefined" && typeof process.getBuiltinModule === "function") try {
37022
- builtin = process.getBuiltinModule(specifier);
37023
- } catch {
37024
- builtin = void 0;
37025
- }
37026
- if (builtin !== void 0 && builtin !== null) return builtin;
37027
- try {
37028
- return await import(specifier);
37029
- } catch (cause) {
37030
- 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 });
37031
- }
37032
- }
37033
- /** `node:fs`, loaded lazily so Workers-style bundles never see the import. */
37034
- function loadNodeFs(operation) {
37035
- return loadBuiltin("node:fs", operation);
37036
- }
37037
- /** `node:fs/promises`, loaded lazily for the same reason as {@link loadNodeFs}. */
37038
- function loadNodeFsPromises(operation) {
37039
- return loadBuiltin("node:fs/promises", operation);
37040
- }
37041
- const CHUNK_SIZE = 64 * 1024 * 1024;
37042
- const MANIFEST_VERSION = 1;
37043
- const CHUNK_KDF_INFO = "payloads-chunk";
37044
- const OBJECT_ID_BYTES = 16;
37045
- const CEK_BYTES = 32;
37046
- function subtle() {
37047
- return globalThis.crypto.subtle;
37048
- }
37049
- const textEncoder = new TextEncoder();
37050
- function randomBytes(length) {
37051
- const out = new Uint8Array(length);
37052
- globalThis.crypto.getRandomValues(out);
37053
- return out;
37054
- }
37055
- function toHex(bytes) {
37056
- let out = "";
37057
- for (const b of bytes) out += b.toString(16).padStart(2, "0");
37058
- return out;
37059
- }
37060
- function fromHex(hex) {
37061
- if (hex.length % 2 !== 0) throw new Error("invalid hex: odd length");
37062
- if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) throw new Error("invalid hex: non-hex character");
37063
- const out = new Uint8Array(hex.length / 2);
37064
- for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
37065
- return out;
37066
- }
37067
- async function contentHash(bytes) {
37068
- return new Uint8Array(await subtle().digest("SHA-256", bytes));
37069
- }
37070
- async function contentHashHex(bytes) {
37071
- return toHex(await contentHash(bytes));
37072
- }
37073
- /** Binary Merkle root over ordered hex leaf hashes (odd node promoted). */
37074
- async function merkleRoot(leafHashesHex) {
37075
- if (leafHashesHex.length === 0) return contentHashHex(new Uint8Array(0));
37076
- let level = leafHashesHex.map(fromHex);
37077
- while (level.length > 1) {
37078
- const next = [];
37079
- for (let i = 0; i < level.length; i += 2) {
37080
- const left = level[i];
37081
- const right = i + 1 < level.length ? level[i + 1] : level[i];
37082
- const combined = new Uint8Array(left.length + right.length);
37083
- combined.set(left, 0);
37084
- combined.set(right, left.length);
37085
- next.push(await contentHash(combined));
37086
- }
37087
- level = next;
37088
- }
37089
- return toHex(level[0]);
37090
- }
37091
- async function deriveChunkKey(cek, index) {
37092
- const baseKey = await subtle().importKey("raw", cek, "HKDF", false, ["deriveKey"]);
37093
- const info = textEncoder.encode(`${CHUNK_KDF_INFO}:${index}`);
37094
- return subtle().deriveKey({
37095
- name: "HKDF",
37096
- hash: "SHA-256",
37097
- salt: new Uint8Array(32),
37098
- info
37099
- }, baseKey, {
37100
- name: "AES-GCM",
37101
- length: 256
37102
- }, false, ["encrypt", "decrypt"]);
37103
- }
37104
- function chunkNonce(index) {
37105
- const nonce = new Uint8Array(12);
37106
- new DataView(nonce.buffer).setUint32(8, index >>> 0, false);
37107
- return nonce;
37108
- }
37109
- function chunkAad(objectId, index) {
37110
- const aad = new Uint8Array(objectId.length + 4);
37111
- aad.set(objectId, 0);
37112
- new DataView(aad.buffer).setUint32(objectId.length, index >>> 0, false);
37113
- return aad;
37114
- }
37115
- async function encryptChunk(cek, objectId, index, plaintext) {
37116
- const key = await deriveChunkKey(cek, index);
37117
- const ct = await subtle().encrypt({
37118
- name: "AES-GCM",
37119
- iv: chunkNonce(index),
37120
- additionalData: chunkAad(objectId, index)
37121
- }, key, plaintext);
37122
- return new Uint8Array(ct);
37123
- }
37124
- async function decryptChunk(cek, objectId, index, ciphertext) {
37125
- const key = await deriveChunkKey(cek, index);
37126
- const pt = await subtle().decrypt({
37127
- name: "AES-GCM",
37128
- iv: chunkNonce(index),
37129
- additionalData: chunkAad(objectId, index)
37130
- }, key, ciphertext);
37131
- return new Uint8Array(pt);
37132
- }
37133
- function authHeaders(apiKey) {
37134
- return { authorization: `Bearer ${apiKey}` };
37135
- }
37136
- function apiRoot(baseUrl) {
37137
- let end = baseUrl.length;
37138
- while (end > 0 && baseUrl.charCodeAt(end - 1) === 47) end--;
37139
- return `${baseUrl.slice(0, end)}/v1/payloads`;
37140
- }
37141
- const DEFAULT_MAX_RETRIES = 6;
37142
- const RETRYABLE_STATUS = new Set([
37143
- 429,
37144
- 500,
37145
- 502,
37146
- 503,
37147
- 504
37148
- ]);
37149
- function sleep(ms) {
37150
- return new Promise((resolve) => setTimeout(resolve, ms));
37151
- }
37152
- /**
37153
- * fetch with exponential backoff on transient failures. Request bodies here are
37154
- * in-memory buffers/strings (never streams), so a retry can safely resend them.
37155
- */
37156
- async function retryingFetch(url, init, label, maxRetries = DEFAULT_MAX_RETRIES) {
37157
- for (let attempt = 0;; attempt++) {
37158
- let res;
37159
- let networkError;
37160
- try {
37161
- res = await fetch(url, init);
37162
- } catch (err) {
37163
- networkError = err;
37164
- }
37165
- if (res && !RETRYABLE_STATUS.has(res.status)) return res;
37166
- if (attempt >= maxRetries) {
37167
- if (res) return res;
37168
- throw new Error(`${label}: network error after ${attempt} retries: ${networkError?.message ?? networkError}`);
37169
- }
37170
- if (res) await res.text().catch(() => {});
37171
- await sleep(Math.min(1e3 * 2 ** attempt, 15e3) + Math.floor(Math.random() * 250));
37172
- }
37173
- }
37174
- async function initiate(opts, manifest) {
37175
- const res = await retryingFetch(apiRoot(opts.baseUrl), {
37176
- method: "POST",
37177
- headers: {
37178
- ...authHeaders(opts.apiKey),
37179
- "content-type": "application/json"
37180
- },
37181
- body: JSON.stringify({ manifest })
37182
- }, "initiate");
37183
- if (!res.ok) throw new Error(`initiate failed: HTTP ${res.status} ${await res.text()}`);
37184
- await res.text();
37185
- }
37186
- async function putChunk(opts, root, d, bytes) {
37187
- const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, {
37188
- method: "PUT",
37189
- headers: {
37190
- ...authHeaders(opts.apiKey),
37191
- "content-type": "application/octet-stream"
37192
- },
37193
- body: bytes
37194
- }, `chunk ${d.index} upload`);
37195
- if (!res.ok) throw new Error(`chunk ${d.index} upload failed: HTTP ${res.status} ${await res.text()}`);
37196
- await res.text();
37197
- }
37198
- async function finalize(opts, root) {
37199
- const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/finalize`, {
37200
- method: "POST",
37201
- headers: authHeaders(opts.apiKey)
37202
- }, "finalize");
37203
- if (!res.ok) throw new Error(`finalize failed: HTTP ${res.status} ${await res.text()}`);
37204
- await res.text();
37205
- }
37206
- async function fetchManifest(opts, root) {
37207
- const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/manifest`, { headers: authHeaders(opts.apiKey) }, "get manifest");
37208
- if (!res.ok) throw new Error(`get manifest failed: HTTP ${res.status} ${await res.text()}`);
37209
- return (await res.json()).data.manifest;
37210
- }
37211
- async function getChunkBytes(opts, root, d) {
37212
- const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, { headers: authHeaders(opts.apiKey) }, `get chunk ${d.index}`);
37213
- if (!res.ok) throw new Error(`get chunk ${d.index} failed: HTTP ${res.status}`);
37214
- return new Uint8Array(await res.arrayBuffer());
37215
- }
37216
- async function readWindow(fh, position, length) {
37217
- const buf = Buffer.allocUnsafe(length);
37218
- let read = 0;
37219
- while (read < length) {
37220
- const { bytesRead } = await fh.read(buf, read, length - read, position + read);
37221
- if (bytesRead === 0) break;
37222
- read += bytesRead;
37223
- }
37224
- return read === length ? buf : buf.subarray(0, read);
37225
- }
37226
- async function mapLimit(items, limit, fn) {
37227
- let next = 0;
37228
- let firstError;
37229
- const worker = async () => {
37230
- while (next < items.length && firstError === void 0) {
37231
- const i = next++;
37232
- try {
37233
- await fn(items[i], i);
37234
- } catch (err) {
37235
- if (firstError === void 0) firstError = err;
37236
- return;
37237
- }
37238
- }
37239
- };
37240
- await Promise.all(Array.from({ length: Math.min(limit, items.length || 1) }, () => worker()));
37241
- if (firstError !== void 0) throw firstError;
37242
- }
37243
- /**
37244
- * Stream a file up as a Primitive Payload. Two passes over the file (encrypt to
37245
- * build the manifest, then encrypt again to upload) keep memory bounded to a few
37246
- * chunks — the whole object is never resident. Returns the content address
37247
- * (merkleRoot) and the hex CEK needed to download it.
37248
- */
37249
- async function pushFile(filePath, opts) {
37250
- const { open, stat } = await loadNodeFsPromises("pushFile");
37251
- const chunkSize = opts.chunkSize ?? CHUNK_SIZE;
37252
- const concurrency = opts.concurrency ?? 3;
37253
- const { size } = await stat(filePath);
37254
- const cek = randomBytes(CEK_BYTES);
37255
- const objectId = randomBytes(OBJECT_ID_BYTES);
37256
- const chunkCount = size === 0 ? 0 : Math.ceil(size / chunkSize);
37257
- const fh = await open(filePath, "r");
37258
- try {
37259
- const descriptors = [];
37260
- for (let index = 0; index < chunkCount; index++) {
37261
- const position = index * chunkSize;
37262
- const length = Math.min(chunkSize, size - position);
37263
- const plaintext = await readWindow(fh, position, length);
37264
- const ciphertext = await encryptChunk(cek, objectId, index, plaintext);
37265
- descriptors.push({
37266
- index,
37267
- ciphertextHash: await contentHashHex(ciphertext),
37268
- plaintextSize: length,
37269
- ciphertextSize: ciphertext.length
37270
- });
37271
- opts.onProgress?.("encrypt", index + 1, chunkCount);
37272
- }
37273
- const root = await merkleRoot(descriptors.map((d) => d.ciphertextHash));
37274
- await initiate(opts, {
37275
- version: MANIFEST_VERSION,
37276
- objectId: toHex(objectId),
37277
- chunkSize,
37278
- totalPlaintextSize: size,
37279
- chunkCount,
37280
- chunks: descriptors,
37281
- merkleRoot: root
37282
- });
37283
- let uploaded = 0;
37284
- await mapLimit(descriptors, concurrency, async (d) => {
37285
- const plaintext = await readWindow(fh, d.index * chunkSize, d.plaintextSize);
37286
- await putChunk(opts, root, d, await encryptChunk(cek, objectId, d.index, plaintext));
37287
- opts.onProgress?.("upload", ++uploaded, chunkCount);
37288
- });
37289
- await finalize(opts, root);
37290
- return {
37291
- merkleRoot: root,
37292
- cek: toHex(cek),
37293
- chunkCount,
37294
- totalBytes: size
37295
- };
37296
- } finally {
37297
- await fh.close();
37298
- }
37299
- }
37300
- /**
37301
- * Stream a Primitive Payload down to a file, verifying and decrypting one chunk
37302
- * at a time (bounded memory). Every chunk is content-address-checked before
37303
- * decryption, so a corrupt or substituted chunk throws.
37304
- */
37305
- async function pullFile(root, outPath, opts) {
37306
- const { createWriteStream } = await loadNodeFs("pullFile");
37307
- const { rm } = await loadNodeFsPromises("pullFile");
37308
- const manifest = await fetchManifest(opts, root);
37309
- const computedRoot = await merkleRoot(manifest.chunks.map((c) => c.ciphertextHash));
37310
- if (computedRoot !== root) throw new Error(`manifest does not match the requested content address (got ${computedRoot})`);
37311
- const cek = fromHex(opts.cek);
37312
- const objectId = fromHex(manifest.objectId);
37313
- const out = createWriteStream(outPath);
37314
- let onWriteError = () => {};
37315
- const writeErrored = new Promise((_, reject) => {
37316
- onWriteError = reject;
37317
- });
37318
- writeErrored.catch(() => {});
37319
- out.on("error", (err) => onWriteError(err));
37320
- try {
37321
- let done = 0;
37322
- for (const d of manifest.chunks) {
37323
- const ciphertext = await getChunkBytes(opts, root, d);
37324
- if (await contentHashHex(ciphertext) !== d.ciphertextHash) throw new Error(`chunk ${d.index} failed integrity check`);
37325
- const plaintext = await decryptChunk(cek, objectId, d.index, ciphertext);
37326
- if (!out.write(plaintext)) await Promise.race([new Promise((resolve) => out.once("drain", resolve)), writeErrored]);
37327
- opts.onProgress?.("download", ++done, manifest.chunkCount);
37328
- }
37329
- await Promise.race([new Promise((resolve) => out.end(resolve)), writeErrored]);
37330
- } catch (err) {
37331
- out.destroy();
37332
- await rm(outPath, { force: true });
37333
- throw err;
37334
- }
37335
- return manifest;
37336
- }
37337
- //#endregion
37338
39171
  //#region src/oclif/commands/payloads.ts
37339
39172
  const DEFAULT_API_BASE_URL = "https://api.primitive.dev";
37340
39173
  const authFlags = {
@@ -41782,6 +43615,8 @@ const CANONICAL_OPERATION_ALIASES = {
41782
43615
  };
41783
43616
  const DESCRIBE_OPERATION_ALIASES = {
41784
43617
  ...CANONICAL_OPERATION_ALIASES,
43618
+ "credits:balance": "credits:get-credit-balance",
43619
+ "credits:redeem": "credits:redeem-credit-code",
41785
43620
  "domains:zone-file": "domains:download-domain-zone-file",
41786
43621
  "functions:logs": "functions:list-function-logs",
41787
43622
  "memories:delete": "memories:delete-memory",
@@ -41799,7 +43634,8 @@ const OVERRIDDEN_OPERATION_IDS = new Set([
41799
43634
  "inbox:get-inbox-status",
41800
43635
  "search:semantic-search",
41801
43636
  "payments:register-payout-address",
41802
- "payments:pay-challenge"
43637
+ "payments:pay-challenge",
43638
+ "credits:redeem-credit-code"
41803
43639
  ]);
41804
43640
  const generatedCommands = Object.fromEntries(operationManifest.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(operationId(operation))).map((operation) => [operationId(operation), createOperationCommand(operation)]));
41805
43641
  const COMMANDS = {
@@ -41880,6 +43716,9 @@ const COMMANDS = {
41880
43716
  "memories:get": MemoriesGetCommand,
41881
43717
  "memories:delete": MemoriesDeleteCommand,
41882
43718
  "memories:search": MemoriesSearchCommand,
43719
+ "credits:redeem": CreditsRedeemCommand,
43720
+ "credits:redeem-credit-code": CreditsRedeemCommand,
43721
+ "credits:balance": CreditsBalanceCommand,
41883
43722
  ...Object.fromEntries(Object.entries(CANONICAL_OPERATION_ALIASES).map(([alias, target]) => {
41884
43723
  const command = generatedCommands[target];
41885
43724
  if (!command) throw new Error(`Missing generated command target for alias ${alias}`);