primcli 1.27.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -206,6 +206,24 @@ primitive memories get state --function <function-id>
206
206
  Values must be valid JSON. Strings must be quoted as JSON strings, so use
207
207
  `'"hello"'`, not `hello`.
208
208
 
209
+ ## Credits
210
+
211
+ Redeem a credit code for your organization and check the credit balance.
212
+
213
+ ```bash
214
+ primitive credits redeem LAUNCH50
215
+ primitive credits balance
216
+ primitive credits balance --json
217
+ ```
218
+
219
+ `credits redeem` prints the credit added and its expiry. On a refusal it prints
220
+ the server's message (for example an invalid or already redeemed code) and
221
+ exits non-zero. Redeeming needs an organization owner or admin; with an API key,
222
+ the key's creator must currently be an owner or admin. Each run sends a new
223
+ Idempotency-Key; pass `--idempotency-key <key>` to retry the same redemption
224
+ safely. On any failure the command prints the key it used to stderr, so you can
225
+ run the same command again with `--idempotency-key <key>`.
226
+
209
227
  ## Recipient routing
210
228
 
211
229
  Bind a recipient address to a destination so inbound mail resolves to a single
@@ -317,3 +335,17 @@ timeout; Ctrl-C exits 130. Bare `primitive listen` prints one raw JSON event per
317
335
  line. Use `--transport poll` explicitly for HTTP polling. Accept or enqueue each
318
336
  event within 30 seconds. Closing preserves pending work, and retries can deliver
319
337
  an event more than once.
338
+
339
+ ### Connected-agent listeners
340
+
341
+ Use the agent's existing connected-address credential with the same listener API.
342
+ The server automatically restricts its private subscription to inbound email for
343
+ that address. No owner credential, recipient filter, or relay is required. Names
344
+ are isolated per credential. Revoking or replacing the credential removes its
345
+ subscriptions and queued deliveries; reconnect with the new credential to start
346
+ receiving new events.
347
+
348
+ Address-scoped events contain parsed message content in `email.parsed`.
349
+ `email.content.raw` and `email.content.download` are null. Signed download links,
350
+ account routing metadata, and other SMTP envelope recipients are not exposed.
351
+ Attachments can be fetched through the authenticated email attachment API.
@@ -71,10 +71,11 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
71
71
  downloadEmailAttachmentPart: () => downloadEmailAttachmentPart,
72
72
  downloadRawEmail: () => downloadRawEmail,
73
73
  downloadSentAttachmentPart: () => downloadSentAttachmentPart,
74
- getAccount: () => getAccount$1,
74
+ getAccount: () => getAccount,
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,
@@ -118,6 +119,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
118
119
  pollCliLogin: () => pollCliLogin,
119
120
  publishAgent: () => publishAgent,
120
121
  pullWebhookEvent: () => pullWebhookEvent$1,
122
+ redeemCreditCode: () => redeemCreditCode,
121
123
  registerPayoutAddress: () => registerPayoutAddress,
122
124
  removeAgentConnection: () => removeAgentConnection,
123
125
  reorderRoutes: () => reorderRoutes,
@@ -411,7 +413,7 @@ const cliLogout = (options) => (options?.client ?? client).post({
411
413
  /**
412
414
  * Get account info
413
415
  */
414
- const getAccount$1 = (options) => (options?.client ?? client).get({
416
+ const getAccount = (options) => (options?.client ?? client).get({
415
417
  security: [{
416
418
  scheme: "bearer",
417
419
  type: "http"
@@ -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: [{
@@ -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,
@@ -11644,6 +11972,10 @@ const openapiDocument = {
11644
11972
  "Endpoint": {
11645
11973
  "type": "object",
11646
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
+ },
11647
11979
  "receiver_capabilities": {
11648
11980
  "type": "object",
11649
11981
  "properties": {
@@ -15479,6 +15811,181 @@ const operationManifest = [
15479
15811
  "tag": "CLI",
15480
15812
  "tagCommand": "cli"
15481
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
+ },
15482
15989
  {
15483
15990
  "binaryResponse": false,
15484
15991
  "bodyRequired": true,
@@ -18072,7 +18579,7 @@ const operationManifest = [
18072
18579
  "binaryResponse": false,
18073
18580
  "bodyRequired": true,
18074
18581
  "command": "create-endpoint",
18075
- "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.",
18076
18583
  "hasJsonBody": true,
18077
18584
  "method": "POST",
18078
18585
  "operationId": "createEndpoint",
@@ -18140,6 +18647,10 @@ const operationManifest = [
18140
18647
  "responseSchema": {
18141
18648
  "type": "object",
18142
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
+ },
18143
18654
  "receiver_capabilities": {
18144
18655
  "type": "object",
18145
18656
  "properties": {
@@ -18316,6 +18827,10 @@ const operationManifest = [
18316
18827
  "items": {
18317
18828
  "type": "object",
18318
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
+ },
18319
18834
  "receiver_capabilities": {
18320
18835
  "type": "object",
18321
18836
  "properties": {
@@ -18782,6 +19297,10 @@ const operationManifest = [
18782
19297
  "responseSchema": {
18783
19298
  "type": "object",
18784
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
+ },
18785
19304
  "receiver_capabilities": {
18786
19305
  "type": "object",
18787
19306
  "properties": {
@@ -30421,6 +30940,240 @@ var ConfigCommand = class extends ConfigListCommand {
30421
30940
  static description = "Manage local Primitive CLI request environments for API endpoint overrides and request headers.";
30422
30941
  };
30423
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
30424
31177
  //#region src/oclif/commands/doctor.ts
30425
31178
  const MIN_NODE_MAJOR = 22;
30426
31179
  function renderRow({ label, outcome }) {
@@ -30524,7 +31277,7 @@ function checkApiKey(opts) {
30524
31277
  }
30525
31278
  async function checkAccount(client) {
30526
31279
  try {
30527
- const result = await getAccount$1({
31280
+ const result = await getAccount({
30528
31281
  client: client.client,
30529
31282
  responseStyle: "fields"
30530
31283
  });
@@ -32303,8 +33056,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
32303
33056
  name: "Primitive Team",
32304
33057
  url: "https://primitive.dev"
32305
33058
  };
32306
- const SDK_VERSION_RANGE = "^1.27.0";
32307
- const CLI_VERSION_RANGE = "^1.27.0";
33059
+ const SDK_VERSION_RANGE = "^1.29.0";
33060
+ const CLI_VERSION_RANGE = "^1.29.0";
32308
33061
  const ESBUILD_VERSION_RANGE = "^0.27.0";
32309
33062
  function renderHandler() {
32310
33063
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -34960,7 +35713,7 @@ async function pullFile(root, outPath, opts) {
34960
35713
  return manifest;
34961
35714
  }
34962
35715
  //#endregion
34963
- //#region ../sdk-node/dist/api-D4xf448r.js
35716
+ //#region ../sdk-node/dist/api-27BvnkVA.js
34964
35717
  const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
34965
35718
  function createSseClient({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url, ...options }) {
34966
35719
  let lastEventId;
@@ -35548,17 +36301,6 @@ const createClient = (config = {}) => {
35548
36301
  };
35549
36302
  const client$1 = createClient(createConfig({ baseUrl: "https://api.primitive.dev/v1" }));
35550
36303
  /**
35551
- * Get account info
35552
- */
35553
- const getAccount = (options) => (options?.client ?? client$1).get({
35554
- security: [{
35555
- scheme: "bearer",
35556
- type: "http"
35557
- }],
35558
- url: "/account",
35559
- ...options
35560
- });
35561
- /**
35562
36304
  * Receive a pending webhook event without a public endpoint
35563
36305
  *
35564
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.
@@ -35632,7 +36374,7 @@ function unwrap(result) {
35632
36374
  var EventConnection = class {
35633
36375
  socket;
35634
36376
  pending;
35635
- accountId;
36377
+ origin;
35636
36378
  touch;
35637
36379
  constructor(client, endpointId, options = {}) {
35638
36380
  this.client = client;
@@ -35648,15 +36390,9 @@ var EventConnection = class {
35648
36390
  }
35649
36391
  async open(signal) {
35650
36392
  if (this.options.transport === "poll" || this.socket?.readyState === 1) return;
35651
- const account = unwrap(await getAccount({
35652
- client: this.client,
35653
- responseStyle: "fields",
35654
- throwOnError: false,
35655
- signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
35656
- }));
35657
- if (!account.data?.id || this.accountId && account.data.id !== this.accountId) throw new EventReceiverError("The authenticated account changed; restart the receiver", "identity_changed", 403);
35658
- this.accountId = account.data.id;
35659
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;
35660
36396
  const auth = typeof config.auth === "function" ? await config.auth({
35661
36397
  type: "http",
35662
36398
  scheme: "bearer"
@@ -36021,7 +36757,7 @@ function resolveListenSubscription(configDir, identity, explicit) {
36021
36757
  var ListenError = class extends Error {};
36022
36758
  var RequestFailure = class extends ListenError {
36023
36759
  constructor(status, code, retryAfter) {
36024
- 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}).`);
36025
36761
  this.status = status;
36026
36762
  this.code = code;
36027
36763
  this.retryAfter = retryAfter;
@@ -36081,6 +36817,7 @@ async function runListen(options) {
36081
36817
  let accountId;
36082
36818
  let verifiedKey;
36083
36819
  let verified = false;
36820
+ let connectedCredential = false;
36084
36821
  let confirmed = 0;
36085
36822
  let release;
36086
36823
  let stream;
@@ -36136,7 +36873,16 @@ async function runListen(options) {
36136
36873
  if (origin !== void 0 && origin !== base) throw new ListenError("The API environment changed while listening. Restart the listener.");
36137
36874
  origin = base;
36138
36875
  if (!verified || auth.auth.apiKey !== verifiedKey) {
36139
- const account = await retry(() => getAccount$1({
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
+ }
36885
+ const account = await retry(() => getAccount({
36140
36886
  client: auth.apiClient.client,
36141
36887
  responseStyle: "fields",
36142
36888
  signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)])
@@ -36179,7 +36925,7 @@ async function runListen(options) {
36179
36925
  stream = new EventConnection(streamClient, endpoint.id);
36180
36926
  }
36181
36927
  const selected = endpoint.rules?.event_types;
36182
- 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";
36183
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`);
36184
36930
  let lastGap = -1;
36185
36931
  let lastBacklog = -1;
@@ -36242,6 +36988,7 @@ async function runListen(options) {
36242
36988
  delivery_id: delivery.delivery_id,
36243
36989
  lease_token: delivery.lease_token
36244
36990
  };
36991
+ if ((connectedCredential || endpoint.recipient) && completion.mode === "http") completion.confirmed = false;
36245
36992
  try {
36246
36993
  const receipt = await retry(async () => {
36247
36994
  if (stream) {
@@ -36280,7 +37027,7 @@ async function runListen(options) {
36280
37027
  //#region src/oclif/commands/listen.ts
36281
37028
  var ListenCommand = class ListenCommand extends Command {
36282
37029
  static summary = "Receive webhook events locally without a public endpoint";
36283
- 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.";
36284
37031
  static examples = [
36285
37032
  "<%= config.bin %> listen",
36286
37033
  "<%= config.bin %> listen --subscription my-agent --exec \"python3 accept.py\"",
@@ -36302,7 +37049,7 @@ var ListenCommand = class ListenCommand extends Command {
36302
37049
  min: 1,
36303
37050
  max: 2147483
36304
37051
  }),
36305
- subscription: Flags.string({ description: "Stable subscription name. Defaults to a saved name for this account and API environment." }),
37052
+ subscription: Flags.string({ description: "Stable subscription name. Defaults to a saved name for this credential scope and API environment." }),
36306
37053
  exec: Flags.string({
36307
37054
  description: "Short POSIX shell command accepting each complete event on stdin.",
36308
37055
  exclusive: ["forward-to"]
@@ -36679,7 +37426,7 @@ async function checkExistingLogin(params) {
36679
37426
  apiBaseUrl: probeApiBaseUrl,
36680
37427
  headers: requestConfig.headers
36681
37428
  });
36682
- const result = await (params.checkAccount ?? ((client) => getAccount$1({
37429
+ const result = await (params.checkAccount ?? ((client) => getAccount({
36683
37430
  client: client.client,
36684
37431
  responseStyle: "fields"
36685
37432
  })))(apiClient);
@@ -40318,7 +41065,7 @@ var PaymentsRegisterPayoutAddressCommand = class PaymentsRegisterPayoutAddressCo
40318
41065
  configDir: this.config.configDir
40319
41066
  });
40320
41067
  await runWithTiming(flags.time, async () => {
40321
- const account = await getAccount$1({
41068
+ const account = await getAccount({
40322
41069
  client: apiClient.client,
40323
41070
  responseStyle: "fields"
40324
41071
  });
@@ -42511,7 +43258,7 @@ var WhoamiCommand = class WhoamiCommand extends Command {
42511
43258
  apiBaseUrl: flags["api-base-url"],
42512
43259
  configDir: this.config.configDir
42513
43260
  });
42514
- const result = await getAccount$1({
43261
+ const result = await getAccount({
42515
43262
  client: apiClient.client,
42516
43263
  responseStyle: "fields"
42517
43264
  });
@@ -42868,6 +43615,8 @@ const CANONICAL_OPERATION_ALIASES = {
42868
43615
  };
42869
43616
  const DESCRIBE_OPERATION_ALIASES = {
42870
43617
  ...CANONICAL_OPERATION_ALIASES,
43618
+ "credits:balance": "credits:get-credit-balance",
43619
+ "credits:redeem": "credits:redeem-credit-code",
42871
43620
  "domains:zone-file": "domains:download-domain-zone-file",
42872
43621
  "functions:logs": "functions:list-function-logs",
42873
43622
  "memories:delete": "memories:delete-memory",
@@ -42885,7 +43634,8 @@ const OVERRIDDEN_OPERATION_IDS = new Set([
42885
43634
  "inbox:get-inbox-status",
42886
43635
  "search:semantic-search",
42887
43636
  "payments:register-payout-address",
42888
- "payments:pay-challenge"
43637
+ "payments:pay-challenge",
43638
+ "credits:redeem-credit-code"
42889
43639
  ]);
42890
43640
  const generatedCommands = Object.fromEntries(operationManifest.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(operationId(operation))).map((operation) => [operationId(operation), createOperationCommand(operation)]));
42891
43641
  const COMMANDS = {
@@ -42966,6 +43716,9 @@ const COMMANDS = {
42966
43716
  "memories:get": MemoriesGetCommand,
42967
43717
  "memories:delete": MemoriesDeleteCommand,
42968
43718
  "memories:search": MemoriesSearchCommand,
43719
+ "credits:redeem": CreditsRedeemCommand,
43720
+ "credits:redeem-credit-code": CreditsRedeemCommand,
43721
+ "credits:balance": CreditsBalanceCommand,
42969
43722
  ...Object.fromEntries(Object.entries(CANONICAL_OPERATION_ALIASES).map(([alias, target]) => {
42970
43723
  const command = generatedCommands[target];
42971
43724
  if (!command) throw new Error(`Missing generated command target for alias ${alias}`);
package/man/primitive.1 CHANGED
@@ -133,6 +133,12 @@ Read a durable memory by key.
133
133
  .B primitive memories search PREFIX
134
134
  List memories by key prefix.
135
135
  .TP
136
+ .B primitive credits redeem CODE
137
+ Redeem a credit code for your organization.
138
+ .TP
139
+ .B primitive credits balance
140
+ Show the organization credit balance.
141
+ .TP
136
142
  .B primitive list-operations
137
143
  Print the generated API operation manifest.
138
144
  .TP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primcli",
3
- "version": "1.27.0",
3
+ "version": "1.29.0",
4
4
  "description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -55,6 +55,9 @@
55
55
  "agent": {
56
56
  "description": "Agent signup and authentication API operations"
57
57
  },
58
+ "credits": {
59
+ "description": "Redeem credit codes and check the organization credit balance"
60
+ },
58
61
  "domains": {
59
62
  "description": "Claim, verify, manage email domains, and download DNS zone files"
60
63
  },