@samva/cli 0.16.0 → 0.18.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.
Files changed (3) hide show
  1. package/AGENTS.md +22 -7
  2. package/dist/bin.mjs +298 -99
  3. package/package.json +7 -7
package/AGENTS.md CHANGED
@@ -21,9 +21,15 @@ response writes its body to stderr and exits `4` for 401 and 403, otherwise `1`.
21
21
  is no built-in filter.
22
22
 
23
23
  `samva api-keys` manages the organization's keys: `list`, `get`, `create`, `update`, `rotate`, and
24
- `revoke`. `create` takes `--name` and exactly one of `--full-access` or repeatable `--grant
25
- <resource>=read|write` (neither or both exits `2`), plus optional `--description` and `--expires-at`;
26
- mailbox and namespace scope go through `samva api`. The API refuses a key with more access than the
24
+ `revoke`. `create` takes `--name` and either `--full-access` or restricted access built from repeatable
25
+ `--grant <resource>=read|write` and mailbox access: repeatable `--mailbox-permission
26
+ read|update|send|quarantine.read`, optionally narrowed to repeatable `--mailbox <id>` (default:
27
+ every mailbox the owner reaches) with `--send-mode approval` (each send waits for a person other
28
+ than the key). `--namespace <id>` confines a restricted key to one namespace. Passing no access, or both
29
+ `--full-access` and restricted access, exits `2`, as does `--mailbox` or `--send-mode` without a
30
+ `--mailbox-permission`. `--description` and `--expires-at` are optional. `update` renames a key,
31
+ sets its description, or changes a mailbox key's `--send-mode` (a person signed in with `samva
32
+ login`); every other part of a key's access is fixed at creation. The API refuses a key with more access than the
27
33
  caller holds. `create` and `rotate` return the secret once: `--json` carries `{apiKey, key,
28
34
  displayOnce, warning}`, so `samva api-keys create ... --json | jq -r .data.key` yields the key, and
29
35
  human mode prints it to stdout with the warning on stderr. `rotate` and `revoke` require
@@ -31,6 +37,12 @@ confirmation (`--yes` outside a terminal), and every mutation supports `--dry-ru
31
37
  `SAMVA_API_KEY`, `samva org current` asks the API who the key is (`GET /v1/whoami`) and reports its
32
38
  organization and key; with OAuth it reports the profile's selected organization.
33
39
 
40
+ `samva webhooks secret <id>` reads the current signing secret with `webhooks:update` permission.
41
+ Human output includes the secret, its creation time, and when the previous secret stops verifying
42
+ if one exists. `--json` returns `{secret, createdAt, previousExpiresAt}` inside the result envelope.
43
+ `create` and `rotate-secret` return readable secrets too; retrieve them again with `secret <id>`.
44
+ Their JSON results contain `secret` and `warning` (`create` also includes `endpoint`).
45
+
34
46
  `samva templates export --out <dir>` renders every local template's fixtures to HTML
35
47
  and text with the asset manifest, needing a Samva credential only to resolve a live `samva:brand`; `--asset-base <origin>` is for an
36
48
  export that will be delivered, which cannot carry a relative reference.
@@ -136,7 +148,10 @@ calls to you, rather than to an installation. Read what exists with `list`, `ver
136
148
  `run <id> <run-id>` (adds the handler's log lines); the list commands page with `--limit`,
137
149
  `--cursor`, and `--all --jsonl`.
138
150
 
139
- `samva mailboxes` reads and sends as a mailbox: `list` pages with `--limit`, `--cursor`, `--after`,
151
+ `samva mailboxes` reads and sends as a mailbox. Every command except `list` and `create`
152
+ takes the mailbox ID as its first argument and the message, action, or endpoint ID second,
153
+ as in `read <mailbox-id> <message-id>` and `endpoints update <mailbox-id> <endpoint-id>`; `events`
154
+ takes any number of mailbox IDs, or none for every mailbox the credential reads. `list` pages with `--limit`, `--cursor`, `--after`,
140
155
  and `--before`. `create --address <address> --name <name>` makes a mailbox with one email endpoint; the
141
156
  slug defaults to the address's local part, and a collision asks for `--slug` rather than suffixing.
142
157
  `get <id>` inspects a mailbox; `update <id> --data <JSON-or-@file>` changes
@@ -156,9 +171,9 @@ public SDK describes email only.
156
171
  Quarantine `list` pages the same way, bounded by `--after` and `--before` on when a message was
157
172
  held; `release` and `discard` take the held message's `id` and require a person signed in with
158
173
  `samva login`. Discard requires confirmation (`--yes` outside a terminal) and supports `--dry-run`.
159
- `actions list --mailbox <id>` filters by `--state` and pages with `--limit`, `--cursor`, and
160
- `--all --jsonl`. `actions approve <action-id> --mailbox <id>` and `actions deny <action-id>
161
- --mailbox <id> [--reason <text>]` require a person different from the proposer, confirmation
174
+ `actions list <mailbox-id>` filters by `--state` and pages with `--limit`, `--cursor`, and
175
+ `--all --jsonl`. `actions approve <mailbox-id> <action-id>` and `actions deny <mailbox-id>
176
+ <action-id> [--reason <text>]` require a person different from the proposer, confirmation
162
177
  (`--yes` outside a terminal), and offer `--dry-run`.
163
178
  Human mailbox and extension results retain the same facts as JSON: mailbox endpoints (channel, address,
164
179
  sender name, default), thread state and activity, message bodies, attachments, authentication and actor,
package/dist/bin.mjs CHANGED
@@ -14106,7 +14106,7 @@ var layer12 = /* @__PURE__ */ layer.pipe(/* @__PURE__ */ provideMerge(/* @__PURE
14106
14106
  // package.json
14107
14107
  var package_default = {
14108
14108
  name: "@samva/cli",
14109
- version: "0.16.0",
14109
+ version: "0.18.0",
14110
14110
  description: "Samva CLI \u2014 email and platform operations from your terminal",
14111
14111
  keywords: [
14112
14112
  "cli",
@@ -14162,16 +14162,16 @@ var package_default = {
14162
14162
  "@types/node": "catalog:",
14163
14163
  "better-auth": "catalog:",
14164
14164
  effect: "catalog:effect",
14165
- samva: "workspace:0.20.0",
14165
+ samva: "workspace:0.21.0",
14166
14166
  typescript: "catalog:",
14167
14167
  vite: "~8.3.0",
14168
14168
  vitest: "catalog:"
14169
14169
  },
14170
14170
  optionalDependencies: {
14171
- "@samva/cli-darwin-arm64": "workspace:0.16.0",
14172
- "@samva/cli-darwin-x64": "workspace:0.16.0",
14173
- "@samva/cli-linux-arm64": "workspace:0.16.0",
14174
- "@samva/cli-linux-x64": "workspace:0.16.0"
14171
+ "@samva/cli-darwin-arm64": "workspace:0.18.0",
14172
+ "@samva/cli-darwin-x64": "workspace:0.18.0",
14173
+ "@samva/cli-linux-arm64": "workspace:0.18.0",
14174
+ "@samva/cli-linux-x64": "workspace:0.18.0"
14175
14175
  },
14176
14176
  engines: {
14177
14177
  node: ">=18"
@@ -19353,6 +19353,7 @@ var Literals4 = (name, choices) => Literals2(argumentKind, name, choices);
19353
19353
  var optional5 = (arg) => optional3(arg);
19354
19354
  var withDescription4 = /* @__PURE__ */ dual(2, (self, description) => withDescription(self, description));
19355
19355
  var withDefault6 = withDefault4;
19356
+ var atLeast3 = /* @__PURE__ */ dual(2, (self, min) => atLeast(self, min));
19356
19357
  var withSchema3 = /* @__PURE__ */ dual(2, (self, schema) => withSchema(self, schema));
19357
19358
  var filterMap3 = /* @__PURE__ */ dual(3, (self, f, onNone) => filterMap2(self, f, onNone));
19358
19359
 
@@ -19595,6 +19596,9 @@ var ENTITY_PREFIXES = {
19595
19596
  waflow: "WhatsAppFlowId",
19596
19597
  wafver: "WhatsAppFlowVersionId",
19597
19598
  wafreg: "WhatsAppFlowRegistrationId",
19599
+ wafkey: "WhatsAppFlowEncryptionKeyId",
19600
+ wafx: "WhatsAppFlowExchangeId",
19601
+ wasl: "WhatsAppSetupLinkId",
19598
19602
  mbx: "MailboxId",
19599
19603
  mbxep: "MailboxEndpointId",
19600
19604
  thread: "MailboxThreadId",
@@ -19690,6 +19694,9 @@ var WhatsAppBusinessAccountId = entityId("waba").pipe(brand(ENTITY_PREFIXES.waba
19690
19694
  var WhatsAppFlowId = entityId("waflow").pipe(brand(ENTITY_PREFIXES.waflow));
19691
19695
  var WhatsAppFlowVersionId = entityId("wafver").pipe(brand(ENTITY_PREFIXES.wafver));
19692
19696
  var WhatsAppFlowRegistrationId = entityId("wafreg").pipe(brand(ENTITY_PREFIXES.wafreg));
19697
+ var WhatsAppFlowEncryptionKeyId = entityId("wafkey").pipe(brand(ENTITY_PREFIXES.wafkey));
19698
+ var WhatsAppFlowExchangeId = entityId("wafx").pipe(brand(ENTITY_PREFIXES.wafx));
19699
+ var WhatsAppSetupLinkId = entityId("wasl").pipe(brand(ENTITY_PREFIXES.wasl));
19693
19700
  var WhatsAppServiceWindowId = entityId("wasw").pipe(brand(ENTITY_PREFIXES.wasw));
19694
19701
  var WebhookEndpointId = entityId("wep").pipe(brand(ENTITY_PREFIXES.wep));
19695
19702
  var WebhookCredentialId = entityId("whcred").pipe(brand(ENTITY_PREFIXES.whcred));
@@ -20397,6 +20404,7 @@ var DOMAIN_WEBHOOK_EVENT_TYPE_VALUES = [
20397
20404
  var domainWebhookEventTypeSchema = Literals(DOMAIN_WEBHOOK_EVENT_TYPE_VALUES);
20398
20405
  var TEMPLATE_WEBHOOK_EVENT_TYPE_VALUES = ["template.registration.updated"];
20399
20406
  var FLOW_WEBHOOK_EVENT_TYPE_VALUES = ["flow.registration.updated"];
20407
+ var WHATSAPP_NUMBER_WEBHOOK_EVENT_TYPE_VALUES = ["whatsapp.number.connected"];
20400
20408
  var MAILBOX_WEBHOOK_EVENT_TYPE_VALUES = [
20401
20409
  "mailbox.message.received",
20402
20410
  "mailbox.message.sent",
@@ -20421,6 +20429,7 @@ var WEBHOOK_EVENT_TYPE_VALUES = [
20421
20429
  ...DOMAIN_WEBHOOK_EVENT_TYPE_VALUES,
20422
20430
  ...TEMPLATE_WEBHOOK_EVENT_TYPE_VALUES,
20423
20431
  ...FLOW_WEBHOOK_EVENT_TYPE_VALUES,
20432
+ ...WHATSAPP_NUMBER_WEBHOOK_EVENT_TYPE_VALUES,
20424
20433
  ...MAILBOX_WEBHOOK_EVENT_TYPE_VALUES,
20425
20434
  ...JOURNEY_WEBHOOK_EVENT_TYPE_VALUES
20426
20435
  ];
@@ -24148,6 +24157,7 @@ var CreateTemplatePayload = /* @__PURE__ */ Struct({
24148
24157
  ]), Null2]), Null2])),
24149
24158
  isActive: optionalKey2(Union2([Boolean3, Null2])),
24150
24159
  namespaceId: optionalKey2(Union2([NamespaceId2, Null2]).annotate({ description: "The namespace the new template project belongs to; the project and every entry added to it share it. Omit for organization level. A namespace-scoped key always creates in its own namespace." })),
24160
+ shared: optionalKey2(Union2([Boolean3, Null2]).annotate({ description: "Share the new organization-level project with every namespace: namespace-scoped keys read and send its entries but cannot change them. Defaults to false. Not with `namespaceId`, and not from a namespace-scoped key." })),
24151
24161
  idempotencyKey: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxCodePoints(255).annotate({ expected: "a string with at most 255 code points" })).check(isMaxLength(255).annotate({ expected: "a value with a length of at most 255" }))
24152
24162
  }).annotate({ identifier: "CreateTemplatePayload" });
24153
24163
  var CreateWebhookPayload = /* @__PURE__ */ Struct({
@@ -24461,7 +24471,8 @@ var UpdateTemplatePayload = /* @__PURE__ */ Struct({ data: Struct({
24461
24471
  ]), Null2]), Null2])),
24462
24472
  fromDefault: optionalKey2(Union2([Union2([TemplateFromDefault, Null2]), Null2])),
24463
24473
  replyToDefault: optionalKey2(Union2([Union2([ArraySchema(String4), Null2]), Null2])),
24464
- isActive: optionalKey2(Union2([Boolean3, Null2]))
24474
+ isActive: optionalKey2(Union2([Boolean3, Null2])),
24475
+ shared: optionalKey2(Union2([Boolean3, Null2]).annotate({ description: "Share the template's project with every namespace, or stop sharing it. Applies to every entry in the project. Only an organization-level project is shared." }))
24465
24476
  }) }).annotate({ identifier: "UpdateTemplatePayload" });
24466
24477
  var ApiKeyAccess = /* @__PURE__ */ Union2([Struct({ mode: Literal2("full") }), Struct({
24467
24478
  mode: Literal2("restricted"),
@@ -24798,6 +24809,7 @@ var apiStatuses = {
24798
24809
  MailboxSendConflictError: 409,
24799
24810
  MailboxSendRefusedError: 403,
24800
24811
  CursorExpiredError: 410,
24812
+ WhatsAppSetupLinkUnavailableError: 410,
24801
24813
  MethodNotAllowedError: 405,
24802
24814
  UpgradeRequiredError: 426,
24803
24815
  RateLimitedError: 429,
@@ -25081,7 +25093,7 @@ var __exportAll = (all, no_symbols) => {
25081
25093
  };
25082
25094
 
25083
25095
  // ../sdk/dist/package.js
25084
- var version = "0.20.0";
25096
+ var version = "0.21.0";
25085
25097
 
25086
25098
  // ../sdk/dist/client-attribution.js
25087
25099
  var SDK_VERSION = version;
@@ -25472,6 +25484,7 @@ var commandTree = {
25472
25484
  webhooks: {
25473
25485
  list: {},
25474
25486
  get: {},
25487
+ secret: {},
25475
25488
  create: {},
25476
25489
  update: {},
25477
25490
  test: {},
@@ -25902,7 +25915,12 @@ var getAuthenticatedJsonForContext = fn2("CliClient.getAuthenticatedJson")(funct
25902
25915
  };
25903
25916
  const url = `${config.baseUrl.replace(/\/$/, "")}${path}`;
25904
25917
  const response = yield* tryPromise2({
25905
- try: (signal) => fetch(url, request === undefined ? { headers, signal } : { method: "POST", headers, body: JSON.stringify(request.body), signal }),
25918
+ try: (signal) => fetch(url, request === undefined ? { headers, signal } : {
25919
+ method: request.method ?? "POST",
25920
+ headers,
25921
+ body: JSON.stringify(request.body),
25922
+ signal
25923
+ }),
25906
25924
  catch: (cause) => new SamvaCliError({ message: `Request to ${url} failed: ${String(cause)}` })
25907
25925
  });
25908
25926
  const body = yield* tryPromise2({
@@ -26569,6 +26587,7 @@ var API_KEY_STATEMENT = {
26569
26587
  };
26570
26588
  var API_KEY_RESOURCES = Object.keys(API_KEY_STATEMENT);
26571
26589
  var API_KEY_LEVEL_RESOURCES = API_KEY_RESOURCES.filter((resource) => resource !== "mailboxes");
26590
+ var API_KEY_MAILBOX_ACTIONS = mailboxKeyActions;
26572
26591
  function apiKeyAccessLevels(resource) {
26573
26592
  return API_KEY_STATEMENT[resource].some((action) => action !== "read") ? ["read", "write"] : ["read"];
26574
26593
  }
@@ -27164,6 +27183,10 @@ var SDK_OPERATION_CALLS = {
27164
27183
  whoamiGet: { kind: "none" },
27165
27184
  webhooksList: { kind: "params" },
27166
27185
  webhooksCreate: { kind: "payload" },
27186
+ webhooksGetSecret: {
27187
+ kind: "path",
27188
+ paths: ["id"]
27189
+ },
27167
27190
  webhooksGet: {
27168
27191
  kind: "path",
27169
27192
  paths: ["id"]
@@ -28556,6 +28579,7 @@ var SamvaOperations = {
28556
28579
  webhooksRetryDelivery: operationMetadata96,
28557
28580
  webhooksGetLogs: operationMetadata95,
28558
28581
  webhooksRegenerateSecret: operationMetadata96,
28582
+ webhooksGetSecret: operationMetadata97,
28559
28583
  webhooksGetStats: operationMetadata97,
28560
28584
  webhooksTest: operationMetadata96,
28561
28585
  whoamiGet: {
@@ -28802,6 +28826,7 @@ var ApiKey = /* @__PURE__ */ Struct({
28802
28826
  name: String4,
28803
28827
  key: optionalKey2(Union2([String4, Null2])),
28804
28828
  start: Union2([String4, Null2]),
28829
+ end: Union2([String4, Null2]),
28805
28830
  description: optionalKey2(Union2([String4, Null2])),
28806
28831
  createdAt: String4,
28807
28832
  expiresAt: Union2([String4, Null2]),
@@ -28815,6 +28840,7 @@ var ApiKeyWithSecret = /* @__PURE__ */ Struct({
28815
28840
  name: String4,
28816
28841
  key: SensitiveString,
28817
28842
  start: Union2([String4, Null2]),
28843
+ end: Union2([String4, Null2]),
28818
28844
  description: optionalKey2(Union2([String4, Null2])),
28819
28845
  createdAt: String4,
28820
28846
  expiresAt: Union2([String4, Null2]),
@@ -29066,7 +29092,7 @@ var parseGrant = (raw) => {
29066
29092
  if (resource === "mailboxes") {
29067
29093
  return new CliUsageError({
29068
29094
  code: "invalid_grant",
29069
- message: "--grant does not cover mailboxes; mailbox and namespace access are set with `samva api`."
29095
+ message: "--grant does not cover mailboxes; use --mailbox-permission <action> (with optional --mailbox and --send-mode) instead."
29070
29096
  });
29071
29097
  }
29072
29098
  if (!isLevelResource(resource) || !isGrantLevel(level)) {
@@ -29083,19 +29109,52 @@ var parseGrant = (raw) => {
29083
29109
  }
29084
29110
  return { resource, level };
29085
29111
  };
29086
- var accessFromFlags = (grants, fullAccess) => gen2(function* () {
29087
- if (fullAccess && grants.length > 0) {
29112
+ var repeated = (values) => values.find((value, index) => values.indexOf(value) !== index);
29113
+ var mailboxAccessFromFlags = (flags) => gen2(function* () {
29114
+ if (flags.permissions.length === 0) {
29115
+ if (flags.mailboxIds.length > 0 || isSome2(flags.sendMode)) {
29116
+ return yield* new CliUsageError({
29117
+ code: "mailbox_permission_required",
29118
+ message: `--mailbox and --send-mode narrow mailbox access; also pass --mailbox-permission ${API_KEY_MAILBOX_ACTIONS.join("|")}.`
29119
+ });
29120
+ }
29121
+ return;
29122
+ }
29123
+ const duplicatePermission = repeated(flags.permissions);
29124
+ if (duplicatePermission !== undefined) {
29125
+ return yield* new CliUsageError({
29126
+ code: "invalid_mailbox_access",
29127
+ message: `--mailbox-permission names ${duplicatePermission} more than once.`
29128
+ });
29129
+ }
29130
+ const duplicateMailbox = repeated(flags.mailboxIds);
29131
+ if (duplicateMailbox !== undefined) {
29132
+ return yield* new CliUsageError({
29133
+ code: "invalid_mailbox_access",
29134
+ message: `--mailbox names ${duplicateMailbox} more than once.`
29135
+ });
29136
+ }
29137
+ return {
29138
+ permissions: flags.permissions,
29139
+ ...flags.mailboxIds.length > 0 ? { mailboxIds: flags.mailboxIds } : {},
29140
+ ...isSome2(flags.sendMode) ? { sendMode: flags.sendMode.value } : {}
29141
+ };
29142
+ });
29143
+ var accessFromFlags = (grants, fullAccess, mailbox, namespace) => gen2(function* () {
29144
+ const restrictedFlags = grants.length > 0 || mailbox.permissions.length > 0 || mailbox.mailboxIds.length > 0 || isSome2(mailbox.sendMode) || isSome2(namespace);
29145
+ if (fullAccess && restrictedFlags) {
29088
29146
  return yield* new CliUsageError({
29089
29147
  code: "access_conflict",
29090
- message: "Pass either --full-access or --grant <resource>=read|write, not both."
29148
+ message: "Pass either --full-access or restricted access (--grant, --mailbox-permission, --namespace), not both."
29091
29149
  });
29092
29150
  }
29093
29151
  if (fullAccess)
29094
29152
  return { mode: "full" };
29095
- if (grants.length === 0) {
29153
+ const mailboxes = yield* mailboxAccessFromFlags(mailbox);
29154
+ if (grants.length === 0 && mailboxes === undefined) {
29096
29155
  return yield* new CliUsageError({
29097
29156
  code: "access_required",
29098
- message: "Choose the key's access: --full-access, or one or more --grant <resource>=read|write."
29157
+ message: "Choose the key's access: --full-access, or one or more --grant <resource>=read|write and/or --mailbox-permission <action>."
29099
29158
  });
29100
29159
  }
29101
29160
  const resources = {};
@@ -29111,7 +29170,12 @@ var accessFromFlags = (grants, fullAccess) => gen2(function* () {
29111
29170
  }
29112
29171
  resources[grant.resource] = grant.level;
29113
29172
  }
29114
- return { mode: "restricted", resources };
29173
+ return {
29174
+ mode: "restricted",
29175
+ resources,
29176
+ ...mailboxes === undefined ? {} : { mailboxes },
29177
+ ...isSome2(namespace) ? { namespaceId: namespace.value } : {}
29178
+ };
29115
29179
  });
29116
29180
  var parseExpiresAt = (raw) => Number.isNaN(Date.parse(raw)) ? fail6(new CliUsageError({
29117
29181
  code: "invalid_expires_at",
@@ -29125,7 +29189,12 @@ var describeAccess = (access) => {
29125
29189
  return "full";
29126
29190
  const grants = Object.entries(access.resources).filter(([, level]) => level !== undefined).map(([resource, level]) => `${resource}:${level}`);
29127
29191
  if (access.mailboxes !== undefined && access.mailboxes !== null) {
29128
- grants.push(`mailboxes:${access.mailboxes.permissions.join("+")}`);
29192
+ const { permissions, mailboxIds, sendMode } = access.mailboxes;
29193
+ const scope = [
29194
+ ...mailboxIds === undefined || mailboxIds === null ? [] : [mailboxIds.join("+")],
29195
+ ...sendMode === undefined || sendMode === "send" ? [] : [`${sendMode} sends`]
29196
+ ];
29197
+ grants.push(`mailboxes:${permissions.join("+")}${scope.length === 0 ? "" : ` [${scope.join("; ")}]`}`);
29129
29198
  }
29130
29199
  if (access.namespaceId !== undefined && access.namespaceId !== null)
29131
29200
  grants.push(`namespace:${access.namespaceId}`);
@@ -29204,11 +29273,19 @@ var create2 = make29("create", {
29204
29273
  description: String9("description").pipe(withDescription2("What the key is for"), optional4),
29205
29274
  expiresAt: String9("expires-at").pipe(withDescription2("When the key stops working, an ISO 8601 instant"), optional4),
29206
29275
  grant: String9("grant").pipe(withDescription2(`Give a restricted key a resource at a level, <resource>=read|write (repeatable). Resources: ${GRANTABLE_RESOURCES.join(", ")}`), atLeast2(0)),
29207
- fullAccess: Boolean6("full-access").pipe(withDescription2("Give the key its owner's full access instead of --grant"), withDefault5(false)),
29276
+ mailboxPermission: Literals3("mailbox-permission", API_KEY_MAILBOX_ACTIONS).pipe(withDescription2(`Let a restricted key work in mailboxes with this action (repeatable): ${API_KEY_MAILBOX_ACTIONS.join(", ")}`), atLeast2(0)),
29277
+ mailbox: String9("mailbox").pipe(withSchema2(MailboxId), withDescription2("Confine the mailbox access to this mailbox (repeatable); omit for every mailbox the owner reaches"), atLeast2(0)),
29278
+ sendMode: Literals3("send-mode", mailboxSendModes).pipe(withDescription2("With approval, each send waits until a person other than the key approves it (default send)"), optional4),
29279
+ namespace: String9("namespace").pipe(withSchema2(NamespaceId), withDescription2("Confine a restricted key to one namespace (ns_...)"), optional4),
29280
+ fullAccess: Boolean6("full-access").pipe(withDescription2("Give the key its owner's full access instead of restricted access"), withDefault5(false)),
29208
29281
  dryRun: dryRunFlag,
29209
29282
  json: jsonFlag
29210
29283
  }).pipe(withDescription3("Create an API key. The secret is shown once; the API refuses access you do not hold."), withExamples([
29211
29284
  { command: "samva api-keys create --name ci --grant email=write --dry-run" },
29285
+ {
29286
+ command: "samva api-keys create --name agent --mailbox-permission read --mailbox-permission send --mailbox mbx_1234567890123456 --send-mode approval",
29287
+ description: "A key that works in one mailbox and needs a person to approve each send"
29288
+ },
29212
29289
  {
29213
29290
  command: "samva api-keys create --name ci --grant email=write --json | jq -r .data.key",
29214
29291
  description: "Pipe the secret into another tool"
@@ -29216,8 +29293,20 @@ var create2 = make29("create", {
29216
29293
  {
29217
29294
  command: "samva api-keys create --name deploy --full-access --expires-at 2027-01-01T00:00:00Z"
29218
29295
  }
29219
- ]), withHandler(({ name, description, expiresAt, grant, fullAccess, dryRun, json }) => withJsonQuiet(json, gen2(function* () {
29220
- const access = yield* accessFromFlags(grant, fullAccess);
29296
+ ]), withHandler(({
29297
+ name,
29298
+ description,
29299
+ expiresAt,
29300
+ grant,
29301
+ mailboxPermission,
29302
+ mailbox,
29303
+ sendMode,
29304
+ namespace,
29305
+ fullAccess,
29306
+ dryRun,
29307
+ json
29308
+ }) => withJsonQuiet(json, gen2(function* () {
29309
+ const access = yield* accessFromFlags(grant, fullAccess, { permissions: mailboxPermission, mailboxIds: mailbox, sendMode }, namespace);
29221
29310
  const expires = yield* match(expiresAt, {
29222
29311
  onNone: () => void_3,
29223
29312
  onSome: parseExpiresAt
@@ -29233,7 +29322,11 @@ var create2 = make29("create", {
29233
29322
  });
29234
29323
  if (dryRun) {
29235
29324
  if (json) {
29236
- return yield* printJson({ dryRun: true, action: "api-keys.create", request: payload });
29325
+ return yield* printJson({
29326
+ dryRun: true,
29327
+ action: "api-keys.create",
29328
+ request: payload
29329
+ });
29237
29330
  }
29238
29331
  return yield* printFields([
29239
29332
  ["Action", "api-keys.create"],
@@ -29256,13 +29349,17 @@ var update3 = make29("update", {
29256
29349
  id: idArgument,
29257
29350
  name: String9("name").pipe(withDescription2("New key name"), optional4),
29258
29351
  description: String9("description").pipe(withDescription2("New description"), optional4),
29352
+ sendMode: Literals3("send-mode", mailboxSendModes).pipe(withDescription2("Change a mailbox key's send mode: approval holds each send for a person other than the key, send releases them. Takes a person signed in with samva login"), optional4),
29259
29353
  dryRun: dryRunFlag,
29260
29354
  json: jsonFlag
29261
- }).pipe(withDescription3("Rename an API key or change its description. Access is fixed at creation; create a new key to change it."), withExamples([{ command: "samva api-keys update key_abc123 --name ci-prod --dry-run" }]), withHandler(({ id, name, description, dryRun, json }) => withJsonQuiet(json, gen2(function* () {
29262
- if (isNone2(name) && isNone2(description)) {
29355
+ }).pipe(withDescription3("Rename an API key, change its description, or change a mailbox key's send mode. Its other access is fixed at creation; create a new key to change it."), withExamples([
29356
+ { command: "samva api-keys update key_abc123 --name ci-prod --dry-run" },
29357
+ { command: "samva api-keys update key_abc123 --send-mode approval" }
29358
+ ]), withHandler(({ id, name, description, sendMode, dryRun, json }) => withJsonQuiet(json, gen2(function* () {
29359
+ if (isNone2(name) && isNone2(description) && isNone2(sendMode)) {
29263
29360
  return yield* new CliUsageError({
29264
29361
  code: "update_required",
29265
- message: "Pass --name, --description, or both."
29362
+ message: "Pass --name, --description, --send-mode, or a combination."
29266
29363
  });
29267
29364
  }
29268
29365
  const payload = yield* decodeRequest(ApiKeysUpdateRequestJson, {
@@ -29270,6 +29367,10 @@ var update3 = make29("update", {
29270
29367
  ...match(description, {
29271
29368
  onNone: () => ({}),
29272
29369
  onSome: (value) => ({ description: value })
29370
+ }),
29371
+ ...match(sendMode, {
29372
+ onNone: () => ({}),
29373
+ onSome: (value) => ({ mailboxes: { sendMode: value } })
29273
29374
  })
29274
29375
  });
29275
29376
  if (dryRun) {
@@ -29285,7 +29386,8 @@ var update3 = make29("update", {
29285
29386
  ["Action", "api-keys.update"],
29286
29387
  ["API key", id],
29287
29388
  ["Name", payload.name],
29288
- ["Description", payload.description]
29389
+ ["Description", payload.description],
29390
+ ["Send mode", payload.mailboxes?.sendMode]
29289
29391
  ]);
29290
29392
  }
29291
29393
  const client = yield* getClientProvider;
@@ -54128,6 +54230,7 @@ var CampaignLatestRun = /* @__PURE__ */ Struct({
54128
54230
  dispatchedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54129
54231
  failedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54130
54232
  skippedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54233
+ pendingCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54131
54234
  estimatedCompletionAt: Union2([String4, Null2])
54132
54235
  }).annotate({ identifier: "CampaignLatestRun" });
54133
54236
  var CampaignRecipientPage = /* @__PURE__ */ Struct({
@@ -54226,11 +54329,14 @@ var CampaignRun = /* @__PURE__ */ Struct({
54226
54329
  dispatchedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54227
54330
  failedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54228
54331
  skippedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54332
+ pendingCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54229
54333
  skippedReasons: ArraySchema(CampaignRunSkippedReason),
54230
54334
  estimatedCompletionAt: Union2([String4, Null2]),
54231
54335
  failureReason: Union2([String4, Null2]),
54232
54336
  holdReason: Union2([String4, Null2]),
54233
54337
  holdDomainId: Union2([EmailDomainId2, Null2]),
54338
+ holdUntil: Union2([String4, Null2]),
54339
+ capacityWarning: Union2([String4, Null2]),
54234
54340
  lastContactId: Union2([String4, Null2]),
54235
54341
  createdBy: Union2([String4, Null2]),
54236
54342
  actorEmail: String4,
@@ -54581,6 +54687,23 @@ var resumeRun = /* @__PURE__ */ make42("campaignsResumeRun");
54581
54687
  var cancelRun = /* @__PURE__ */ make42("campaignsCancelRun");
54582
54688
  var listRecipients = /* @__PURE__ */ make42("campaignsListRecipients");
54583
54689
 
54690
+ // src/lib/run-hold.ts
54691
+ var WHATSAPP_MESSAGING_LIMIT_HOLD = "whatsapp_messaging_limit";
54692
+ var holdLabel = (reason) => reason === null || reason === undefined ? null : reason === WHATSAPP_MESSAGING_LIMIT_HOLD ? `${reason} (the number's messaging limit; the run resumes on its own)` : reason;
54693
+ var holdFields = (run) => [
54694
+ ["Held", holdLabel(run.holdReason)],
54695
+ ["Resumes at", run.holdUntil]
54696
+ ];
54697
+ var campaignPacingFields = (run, options) => [
54698
+ ...holdFields(run),
54699
+ [
54700
+ "Pending",
54701
+ run.pendingCount !== undefined && (run.pendingCount > 0 || run.holdReason != null) ? String(run.pendingCount) : null
54702
+ ],
54703
+ ["Est. completion", run.estimatedCompletionAt],
54704
+ ...options.warning ? [["Capacity warning", run.capacityWarning]] : []
54705
+ ];
54706
+
54584
54707
  // src/lib/template-reference.ts
54585
54708
  var publicationIdFlag = String9("publication-id").pipe(withDescription2("Pin one exact publication, so nothing published later changes what this renders"), optional4);
54586
54709
  var inputContractIdFlag = String9("input-contract-id").pipe(withDescription2("Follow the newest publication that still validates input identically to this contract"), optional4);
@@ -55053,7 +55176,7 @@ var archive2 = make29("archive", {
55053
55176
  ["Definition", data.definition.state]
55054
55177
  ]);
55055
55178
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
55056
- var printRun = (run) => printFields([
55179
+ var printRun = (run, options = { warning: true }) => printFields([
55057
55180
  ["Run ID", run.id],
55058
55181
  ["Campaign", run.campaignId],
55059
55182
  ["Status", run.status],
@@ -55068,8 +55191,10 @@ var printRun = (run) => printFields([
55068
55191
  "Skipped by reason",
55069
55192
  (run.skippedReasons ?? []).map(({ reason, count }) => `${reason} ${count}`).join(", ")
55070
55193
  ],
55071
- ["Failure", run.failureReason]
55194
+ ["Failure", run.failureReason],
55195
+ ...campaignPacingFields(run, options)
55072
55196
  ]);
55197
+ var warnCapacity = (run) => run.capacityWarning ? error(`Warning: ${run.capacityWarning}`) : void_3;
55073
55198
  var runCreate = make29("create", {
55074
55199
  campaignId: String10("campaignId"),
55075
55200
  at: atFlag,
@@ -55108,7 +55233,8 @@ var runCreate = make29("create", {
55108
55233
  return yield* printJson(data);
55109
55234
  yield* logInfo(`Run scheduled.
55110
55235
  `);
55111
- yield* printRun(data);
55236
+ yield* printRun(data, { warning: false });
55237
+ yield* warnCapacity(data);
55112
55238
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
55113
55239
  var runList = make29("list", {
55114
55240
  campaignId: String10("campaignId"),
@@ -66402,7 +66528,7 @@ var refusalNext = (failure) => {
66402
66528
  case "ForbiddenError":
66403
66529
  return "Use a credential that may read mailboxes and follow the API message.";
66404
66530
  case "ResourceNotFoundError":
66405
- return "Run samva mailboxes list to find the mailboxes this credential can read, then retry with those --mailbox ids.";
66531
+ return "Run samva mailboxes list to find the mailboxes this credential can read, then retry with those mailbox ids.";
66406
66532
  case "CursorExpiredError":
66407
66533
  return "Events after your cursor expired. Read current state from the API and reconnect without --cursor.";
66408
66534
  case "ValidationError":
@@ -66529,12 +66655,16 @@ var tailEvents = (options) => gen2(function* () {
66529
66655
  })), mapError4(streamError), options.limit === undefined ? (stream) => stream : take3(options.limit), interruptWhen2(interrupted), runForEach2(options.onEvent), raceFirst2(interrupted.pipe(andThen2(sleep2(HANDLER_DRAIN)))))));
66530
66656
  });
66531
66657
  var mailboxesEventsCommand = make29("events", {
66658
+ mailbox: String10("mailbox-id").pipe(withDescription4("Mailboxes to follow; omit to follow every mailbox the credential reads"), withSchema3(MailboxId), atLeast3(0)),
66532
66659
  cursor: String9("cursor").pipe(withDescription2("Replay events after a cursor from an earlier event frame, then go live"), optional4),
66533
- mailbox: String9("mailbox").pipe(withSchema2(MailboxId), atLeast2(0)),
66534
66660
  limit: Int5("limit").pipe(optional4),
66535
66661
  noReconnect: Boolean6("no-reconnect").pipe(withDefault5(false), withDescription2("Exit non-zero with the last cursor when the connection drops, instead of reconnecting from it"))
66536
66662
  }).pipe(withDescription3("Tail mailbox events, one record per line; --jsonl wraps each in a result envelope; a dropped connection reconnects from the last cursor; Ctrl+C exits cleanly"), withExamples([
66537
- { command: "samva mailboxes events --mailbox mbx_1234567890123456 --limit 10" },
66663
+ { command: "samva mailboxes events mbx_1234567890123456 --limit 10" },
66664
+ {
66665
+ command: "samva mailboxes events mbx_1234567890123456 mbx_6543210987654321 --jsonl",
66666
+ description: "Follow several mailboxes"
66667
+ },
66538
66668
  { command: "samva mailboxes events --jsonl", description: "Stream envelopes for automation" },
66539
66669
  { command: "samva mailboxes events --cursor <cursor-from-an-event-frame>" },
66540
66670
  {
@@ -67054,6 +67184,18 @@ class WhatsAppTemplateIneligibleError extends TaggedError3()("WhatsAppTemplateIn
67054
67184
  message: String4
67055
67185
  }, { httpApiStatus: 422 }) {
67056
67186
  }
67187
+
67188
+ class WhatsAppSetupLinkUnavailableError extends TaggedError3()("WhatsAppSetupLinkUnavailableError", {
67189
+ reason: Literals(["completed", "revoked", "expired"]),
67190
+ message: String4
67191
+ }, { httpApiStatus: 410 }) {
67192
+ static new(reason) {
67193
+ return new WhatsAppSetupLinkUnavailableError({
67194
+ reason,
67195
+ message: reason === "completed" ? "This setup link already connected a WhatsApp number. Ask for a new link to connect another." : reason === "revoked" ? "This setup link was revoked. Ask for a new link." : "This setup link expired. Ask for a new link."
67196
+ });
67197
+ }
67198
+ }
67057
67199
  var EmailChannelErrorReason = Literals([
67058
67200
  "transport",
67059
67201
  "rejected",
@@ -67596,6 +67738,8 @@ var JourneyRun = /* @__PURE__ */ Struct({
67596
67738
  exitReason: Union2([JourneyRunExitReason, Null2]),
67597
67739
  failureReason: Union2([String4, Null2]),
67598
67740
  currentNodeId: Union2([JourneyNodeId, Null2]),
67741
+ holdReason: Union2([String4, Null2]),
67742
+ holdUntil: Union2([String4, Null2]),
67599
67743
  trigger: JourneyRunTrigger,
67600
67744
  enteredAt: String4,
67601
67745
  endedAt: Union2([String4, Null2])
@@ -67619,6 +67763,8 @@ var JourneyRunDetail = /* @__PURE__ */ Struct({
67619
67763
  exitReason: Union2([JourneyRunExitReason, Null2]),
67620
67764
  failureReason: Union2([String4, Null2]),
67621
67765
  currentNodeId: Union2([JourneyNodeId, Null2]),
67766
+ holdReason: Union2([String4, Null2]),
67767
+ holdUntil: Union2([String4, Null2]),
67622
67768
  trigger: JourneyRunTrigger,
67623
67769
  enteredAt: String4,
67624
67770
  endedAt: Union2([String4, Null2]),
@@ -68247,6 +68393,7 @@ var printRun2 = (run) => printFields([
68247
68393
  ["Exit reason", run.exitReason],
68248
68394
  ["Failure", run.failureReason],
68249
68395
  ["Current node", run.currentNodeId],
68396
+ ...holdFields(run),
68250
68397
  ["Entered", run.enteredAt],
68251
68398
  ["Ended", run.endedAt]
68252
68399
  ]);
@@ -71573,7 +71720,7 @@ var decodeThreadPage = decoder3(WireThreadPage, "thread page");
71573
71720
  var decodeMessage = decoder3(WireMessage, "message");
71574
71721
 
71575
71722
  // src/commands/mailboxes.ts
71576
- var mailboxFlag = String9("mailbox").pipe(withDescription2("Mailbox ID"), withSchema2(MailboxId));
71723
+ var mailboxIdArgument = String10("mailbox-id").pipe(withSchema3(MailboxId));
71577
71724
  var messageIdArgument = String10("message-id").pipe(withSchema3(MessageId));
71578
71725
  var optional6 = (name) => String9(name).pipe(optional4);
71579
71726
  var pageFlags2 = {
@@ -71616,7 +71763,7 @@ var chatContent = (channel, flags) => gen2(function* () {
71616
71763
  }
71617
71764
  if (channel !== "whatsapp")
71618
71765
  return yield* usage2("mailbox_chat_template", "A template sends on WhatsApp only.");
71619
- const templateId = yield* decodeUnknownEffect2(TemplateId)(flags.template.value).pipe(mapError2(() => usage2("mailbox_chat_template", "--template must be an approved template id (tmpl_\u2026).")));
71766
+ const templateId = yield* decodeEffect2(TemplateId)(flags.template.value).pipe(mapError2(() => usage2("mailbox_chat_template", "--template must be an approved template id (tmpl_\u2026).")));
71620
71767
  const dataText = getOrUndefined(flags.data);
71621
71768
  if (dataText === undefined)
71622
71769
  return { templateId };
@@ -71734,7 +71881,6 @@ var create9 = make29("create", {
71734
71881
  return yield* printJson(data);
71735
71882
  yield* printMailbox(data);
71736
71883
  })));
71737
- var mailboxIdArgument = String10("mailbox-id").pipe(withSchema3(MailboxId));
71738
71884
  var get22 = make29("get", { id: mailboxIdArgument, json: jsonFlag }).pipe(withDescription3("Inspect a mailbox's endpoints and settings"), withHandler(({ id, json }) => gen2(function* () {
71739
71885
  const raw = yield* getMailboxJson("Could not read mailbox", `/v1/mailboxes/${id}`);
71740
71886
  const result = yield* decodeMailbox(raw);
@@ -71793,7 +71939,7 @@ var update9 = make29("update", {
71793
71939
  yield* json ? printJson(result) : printMailbox(result);
71794
71940
  })));
71795
71941
  var endpointAdd = make29("add", {
71796
- mailbox: mailboxFlag,
71942
+ mailbox: mailboxIdArgument,
71797
71943
  channel: channelFlag2,
71798
71944
  address: String9("address").pipe(withDescription2("The address to add: an email address such as billing@acme.samva.email, or on whatsapp and sms a number you connected, in E.164 form such as +14155550123")),
71799
71945
  displayName: String9("display-name").pipe(withDescription2("The sender name messages from this endpoint carry"), optional4),
@@ -71801,10 +71947,10 @@ var endpointAdd = make29("add", {
71801
71947
  json: jsonFlag
71802
71948
  }).pipe(withDescription3("Add an endpoint on a channel (email by default) to a mailbox"), withExamples([
71803
71949
  {
71804
- command: "samva mailboxes endpoints add --mailbox mbx_1234567890123456 --address billing@acme.samva.email"
71950
+ command: "samva mailboxes endpoints add mbx_1234567890123456 --address billing@acme.samva.email"
71805
71951
  },
71806
71952
  {
71807
- command: "samva mailboxes endpoints add --mailbox mbx_1234567890123456 --channel whatsapp --address +14155550123",
71953
+ command: "samva mailboxes endpoints add mbx_1234567890123456 --channel whatsapp --address +14155550123",
71808
71954
  description: "Add a connected WhatsApp number"
71809
71955
  }
71810
71956
  ]), withHandler(({ mailbox, channel, address, displayName, dryRun, json }) => gen2(function* () {
@@ -71836,8 +71982,8 @@ var endpointAdd = make29("add", {
71836
71982
  })));
71837
71983
  var endpointIdArgument = String10("endpoint-id").pipe(withSchema3(MailboxEndpointId));
71838
71984
  var endpointUpdate = make29("update", {
71985
+ mailbox: mailboxIdArgument,
71839
71986
  endpointId: endpointIdArgument,
71840
- mailbox: mailboxFlag,
71841
71987
  displayName: String9("display-name").pipe(withDescription2("The sender name messages from this endpoint carry"), optional4),
71842
71988
  clearDisplayName: Boolean6("clear-display-name").pipe(withDescription2("Send under the mailbox's name again"), withDefault5(false)),
71843
71989
  makeDefault: Boolean6("default").pipe(withDescription2("Start new conversations from this endpoint on its channel"), withDefault5(false)),
@@ -71845,7 +71991,7 @@ var endpointUpdate = make29("update", {
71845
71991
  json: jsonFlag
71846
71992
  }).pipe(withDescription3("Rename an endpoint's sender or make it the mailbox's default"), withExamples([
71847
71993
  {
71848
- command: 'samva mailboxes endpoints update mbxep_1234567890123456 --mailbox mbx_1234567890123456 --display-name "Acme Billing" --default'
71994
+ command: 'samva mailboxes endpoints update mbx_1234567890123456 mbxep_1234567890123456 --display-name "Acme Billing" --default'
71849
71995
  }
71850
71996
  ]), withHandler(({ endpointId, mailbox, displayName, clearDisplayName, makeDefault, dryRun, json }) => gen2(function* () {
71851
71997
  if (isSome2(displayName) && clearDisplayName)
@@ -71879,14 +72025,14 @@ var endpointUpdate = make29("update", {
71879
72025
  yield* json ? printJson(result) : printMailbox(result);
71880
72026
  })));
71881
72027
  var endpointRemove = make29("remove", {
72028
+ mailbox: mailboxIdArgument,
71882
72029
  endpointId: endpointIdArgument,
71883
- mailbox: mailboxFlag,
71884
72030
  dryRun: Boolean6("dry-run").pipe(withDefault5(false)),
71885
72031
  yes: Boolean6("yes").pipe(withDefault5(false)),
71886
72032
  json: jsonFlag
71887
72033
  }).pipe(withDescription3("Remove an endpoint from a mailbox; mail sent to it stops reaching the mailbox"), withExamples([
71888
72034
  {
71889
- command: "samva mailboxes endpoints remove mbxep_1234567890123456 --mailbox mbx_1234567890123456 --yes"
72035
+ command: "samva mailboxes endpoints remove mbx_1234567890123456 mbxep_1234567890123456 --yes"
71890
72036
  }
71891
72037
  ]), withHandler(({ endpointId, mailbox, dryRun, yes, json }) => gen2(function* () {
71892
72038
  if (dryRun)
@@ -71918,7 +72064,7 @@ var lifecycle = (verb) => make29(verb, {
71918
72064
  yield* json ? printJson(result) : printMailbox(result);
71919
72065
  })));
71920
72066
  var actionsList = make29("list", {
71921
- mailbox: mailboxFlag,
72067
+ mailbox: mailboxIdArgument,
71922
72068
  ...pageFlags2,
71923
72069
  state: Literals3("state", MailboxActionState.literals).pipe(optional4),
71924
72070
  after: instantFlag("after", "Only actions created after this ISO 8601 instant"),
@@ -71943,8 +72089,8 @@ var actionsList = make29("list", {
71943
72089
  });
71944
72090
  })));
71945
72091
  var actionDecision = (verb) => make29(verb, {
72092
+ mailbox: mailboxIdArgument,
71946
72093
  actionId: String10("action-id").pipe(withSchema3(MailboxActionId)),
71947
- mailbox: mailboxFlag,
71948
72094
  ...verb === "deny" ? {
71949
72095
  reason: optional6("reason").pipe(withDescription2("Denial reason; defaults to Denied by reviewer."))
71950
72096
  } : {},
@@ -71999,7 +72145,7 @@ var remove9 = make29("remove", {
71999
72145
  yield* log(`Removed mailbox ${id}. The messages it received stay with the organization.`);
72000
72146
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
72001
72147
  var threads = make29("threads", {
72002
- mailbox: mailboxFlag,
72148
+ mailbox: mailboxIdArgument,
72003
72149
  ...pageFlags2,
72004
72150
  query: optional6("query"),
72005
72151
  folder: Literals3("folder", MailboxThreadFolder2.literals).pipe(optional4),
@@ -72010,37 +72156,35 @@ var threads = make29("threads", {
72010
72156
  before: instantFlag("before", "Only threads whose last activity is before this ISO 8601 instant"),
72011
72157
  json: jsonFlag
72012
72158
  }).pipe(withDescription3("Search and list mailbox threads"), withExamples([
72013
- { command: "samva mailboxes threads --mailbox mbx_1234567890123456 --query invoice --json" },
72159
+ { command: "samva mailboxes threads mbx_1234567890123456 --query invoice --json" },
72014
72160
  {
72015
- command: "samva mailboxes threads --mailbox mbx_1234567890123456 --starred --after 2026-10-01T00:00:00Z"
72161
+ command: "samva mailboxes threads mbx_1234567890123456 --starred --after 2026-10-01T00:00:00Z"
72016
72162
  },
72017
- { command: "samva mailboxes threads --mailbox mbx_1234567890123456 --all --jsonl" }
72018
- ]), withHandler(({ mailbox, limit, cursor, all, query, folder, label, unread, starred, after, before, json }) => gen2(function* () {
72019
- yield* followPages({
72020
- all,
72021
- json,
72022
- cursor,
72023
- page: (pageCursor) => getMailboxJson("Could not list threads", `/v1/mailboxes/${mailbox}/threads${searchParams({
72024
- cursor: pageCursor,
72025
- limit: pageLimit(limit),
72026
- q: getOrUndefined(query),
72027
- folder: getOrUndefined(folder),
72028
- label: getOrUndefined(label),
72029
- unread: unread ? "true" : undefined,
72030
- starred: starred ? "true" : undefined,
72031
- after: getOrUndefined(after),
72032
- before: getOrUndefined(before)
72033
- })}`).pipe(flatMap3(decodeThreadPage)),
72034
- empty: "No threads found.",
72035
- human: printThread
72036
- });
72163
+ { command: "samva mailboxes threads mbx_1234567890123456 --all --jsonl" }
72164
+ ]), withHandler(({ mailbox, limit, cursor, all, query, folder, label, unread, starred, after, before, json }) => followPages({
72165
+ all,
72166
+ json,
72167
+ cursor,
72168
+ page: (pageCursor) => getMailboxJson("Could not list threads", `/v1/mailboxes/${mailbox}/threads${searchParams({
72169
+ cursor: pageCursor,
72170
+ limit: pageLimit(limit),
72171
+ q: getOrUndefined(query),
72172
+ folder: getOrUndefined(folder),
72173
+ label: getOrUndefined(label),
72174
+ unread: unread ? "true" : undefined,
72175
+ starred: starred ? "true" : undefined,
72176
+ after: getOrUndefined(after),
72177
+ before: getOrUndefined(before)
72178
+ })}`).pipe(flatMap3(decodeThreadPage)),
72179
+ empty: "No threads found.",
72180
+ human: printThread
72037
72181
  })));
72038
72182
  var read2 = make29("read", {
72183
+ mailbox: mailboxIdArgument,
72039
72184
  messageId: messageIdArgument,
72040
- mailbox: mailboxFlag,
72041
72185
  json: jsonFlag
72042
72186
  }).pipe(withDescription3("Read a mailbox message"), withExamples([
72043
- { command: "samva mailboxes read msg_1234567890123456 --mailbox mbx_1234567890123456 --json" }
72187
+ { command: "samva mailboxes read mbx_1234567890123456 msg_1234567890123456 --json" }
72044
72188
  ]), withHandler(({ messageId, mailbox, json }) => gen2(function* () {
72045
72189
  const raw = yield* getMailboxJson("Could not read message", `/v1/mailboxes/${mailbox}/messages/${messageId}`);
72046
72190
  const data = yield* decodeMessage(raw);
@@ -72049,7 +72193,7 @@ var read2 = make29("read", {
72049
72193
  yield* printMessage2(data);
72050
72194
  })));
72051
72195
  var send7 = make29("send", {
72052
- mailbox: mailboxFlag,
72196
+ mailbox: mailboxIdArgument,
72053
72197
  channel: channelFlag2,
72054
72198
  to: String9("to").pipe(withDescription2("Recipient email address (repeatable), or one phone number in E.164 form"), atLeast2(1)),
72055
72199
  subject: String9("subject").pipe(withDescription2("Email only"), optional4),
@@ -72060,14 +72204,14 @@ var send7 = make29("send", {
72060
72204
  json: jsonFlag
72061
72205
  }).pipe(withDescription3("Send a message from a mailbox: email by default, or to one phone number with --channel whatsapp or sms"), withExamples([
72062
72206
  {
72063
- command: 'samva mailboxes send --mailbox mbx_1234567890123456 --to recipient@example.com --subject Hello --text "Hello"'
72207
+ command: 'samva mailboxes send mbx_1234567890123456 --to recipient@example.com --subject Hello --text "Hello"'
72064
72208
  },
72065
72209
  {
72066
- command: `samva mailboxes send --mailbox mbx_1234567890123456 --channel whatsapp --to +14155550123 --template tmpl_1234567890123456 --data '{"name":"Ada"}'`,
72210
+ command: `samva mailboxes send mbx_1234567890123456 --channel whatsapp --to +14155550123 --template tmpl_1234567890123456 --data '{"name":"Ada"}'`,
72067
72211
  description: "Open a WhatsApp conversation with an approved template"
72068
72212
  },
72069
72213
  {
72070
- command: 'samva mailboxes send --mailbox mbx_1234567890123456 --channel sms --to +14155550123 --text "Your order shipped."'
72214
+ command: 'samva mailboxes send mbx_1234567890123456 --channel sms --to +14155550123 --text "Your order shipped."'
72071
72215
  }
72072
72216
  ]), withHandler(({
72073
72217
  mailbox,
@@ -72119,18 +72263,18 @@ var send7 = make29("send", {
72119
72263
  })));
72120
72264
  var messageChannel = (mailbox, messageId) => getMailboxJson("Could not read message", `/v1/mailboxes/${mailbox}/messages/${messageId}`).pipe(flatMap3((raw) => decodeUnknownEffect2(Struct({ channel: Literals(["email", "whatsapp", "sms"]) }))(raw).pipe(map7(({ channel }) => channel), mapError2(() => new SamvaCliError({ message: "The API answered with an unreadable message." })))));
72121
72265
  var reply2 = make29("reply", {
72266
+ mailbox: mailboxIdArgument,
72122
72267
  messageId: messageIdArgument,
72123
- mailbox: mailboxFlag,
72124
72268
  ...bodyFlags,
72125
72269
  ...chatOnlyFlags,
72126
72270
  all: Boolean6("all").pipe(withDefault5(false)),
72127
72271
  json: jsonFlag
72128
72272
  }).pipe(withDescription3("Reply to a message; --all includes the other recipients. A WhatsApp or SMS message is answered on its thread, with --text or a WhatsApp --template"), withExamples([
72129
72273
  {
72130
- command: 'samva mailboxes reply msg_1234567890123456 --mailbox mbx_1234567890123456 --text "Thanks" --all'
72274
+ command: 'samva mailboxes reply mbx_1234567890123456 msg_1234567890123456 --text "Thanks" --all'
72131
72275
  },
72132
72276
  {
72133
- command: "samva mailboxes reply msg_1234567890123456 --mailbox mbx_1234567890123456 --template tmpl_1234567890123456",
72277
+ command: "samva mailboxes reply mbx_1234567890123456 msg_1234567890123456 --template tmpl_1234567890123456",
72134
72278
  description: "Answer a WhatsApp thread whose 24-hour service window has closed"
72135
72279
  }
72136
72280
  ]), withHandler(({ messageId, mailbox, text, html, template, data, idempotencyKey, all, json }) => gen2(function* () {
@@ -72158,8 +72302,8 @@ var reply2 = make29("reply", {
72158
72302
  })));
72159
72303
  var actionIdArgument = String10("action-id").pipe(withSchema3(MailboxActionId));
72160
72304
  var actionGet = make29("get", {
72305
+ mailbox: mailboxIdArgument,
72161
72306
  actionId: actionIdArgument,
72162
- mailbox: mailboxFlag,
72163
72307
  json: jsonFlag
72164
72308
  }).pipe(withDescription3("Read a mailbox action and its current send progress"), withHandler(({ actionId, mailbox, json }) => gen2(function* () {
72165
72309
  const client = yield* getClientProvider;
@@ -72169,8 +72313,8 @@ var actionGet = make29("get", {
72169
72313
  yield* printAction(data);
72170
72314
  })));
72171
72315
  var actionCancel = make29("cancel", {
72316
+ mailbox: mailboxIdArgument,
72172
72317
  actionId: actionIdArgument,
72173
- mailbox: mailboxFlag,
72174
72318
  json: jsonFlag
72175
72319
  }).pipe(withDescription3("Cancel an unaccepted send as a permitted person signed in with samva login"), withHandler(({ actionId, mailbox, json }) => gen2(function* () {
72176
72320
  const client = yield* getClientProvider;
@@ -72184,8 +72328,8 @@ var actionCancel = make29("cancel", {
72184
72328
  ]);
72185
72329
  })));
72186
72330
  var actionRetry = make29("retry", {
72331
+ mailbox: mailboxIdArgument,
72187
72332
  actionId: actionIdArgument,
72188
- mailbox: mailboxFlag,
72189
72333
  json: jsonFlag
72190
72334
  }).pipe(withDescription3("Resume an eligible ended execution as a permitted person; preserves content and deadline"), withHandler(({ actionId, mailbox, json }) => gen2(function* () {
72191
72335
  const client = yield* getClientProvider;
@@ -72199,14 +72343,14 @@ var actionRetry = make29("retry", {
72199
72343
  ]);
72200
72344
  })));
72201
72345
  var quarantineList = make29("list", {
72202
- mailbox: mailboxFlag,
72346
+ mailbox: mailboxIdArgument,
72203
72347
  ...pageFlags2,
72204
72348
  after: instantFlag("after", "Only messages held after this ISO 8601 instant"),
72205
72349
  before: instantFlag("before", "Only messages held before this ISO 8601 instant"),
72206
72350
  json: jsonFlag
72207
72351
  }).pipe(withDescription3("List held messages without their bodies"), withExamples([
72208
- { command: "samva mailboxes quarantine list --mailbox mbx_1234567890123456 --json" },
72209
- { command: "samva mailboxes quarantine list --mailbox mbx_1234567890123456 --all --jsonl" }
72352
+ { command: "samva mailboxes quarantine list mbx_1234567890123456 --json" },
72353
+ { command: "samva mailboxes quarantine list mbx_1234567890123456 --all --jsonl" }
72210
72354
  ]), withHandler(({ mailbox, limit, cursor, all, after, before, json }) => gen2(function* () {
72211
72355
  const client = yield* getClientProvider;
72212
72356
  yield* followPages({
@@ -72225,12 +72369,12 @@ var quarantineList = make29("list", {
72225
72369
  });
72226
72370
  })));
72227
72371
  var release2 = make29("release", {
72372
+ mailbox: mailboxIdArgument,
72228
72373
  messageId: messageIdArgument,
72229
- mailbox: mailboxFlag,
72230
72374
  json: jsonFlag
72231
72375
  }).pipe(withDescription3("Release a held message as a person signed in with samva login"), withExamples([
72232
72376
  {
72233
- command: "samva mailboxes quarantine release msg_1234567890123456 --mailbox mbx_1234567890123456"
72377
+ command: "samva mailboxes quarantine release mbx_1234567890123456 msg_1234567890123456"
72234
72378
  }
72235
72379
  ]), withHandler(({ messageId, mailbox, json }) => gen2(function* () {
72236
72380
  const client = yield* getClientProvider;
@@ -72242,12 +72386,12 @@ var release2 = make29("release", {
72242
72386
  var discard = make29("discard", {
72243
72387
  dryRun: Boolean6("dry-run").pipe(withDefault5(false)),
72244
72388
  yes: Boolean6("yes").pipe(withDefault5(false)),
72389
+ mailbox: mailboxIdArgument,
72245
72390
  messageId: messageIdArgument,
72246
- mailbox: mailboxFlag,
72247
72391
  json: jsonFlag
72248
72392
  }).pipe(withDescription3("Discard a held message for good as a person signed in with samva login"), withExamples([
72249
72393
  {
72250
- command: "samva mailboxes quarantine discard msg_1234567890123456 --mailbox mbx_1234567890123456"
72394
+ command: "samva mailboxes quarantine discard mbx_1234567890123456 msg_1234567890123456"
72251
72395
  }
72252
72396
  ]), withHandler(({ messageId, mailbox, dryRun, yes, json }) => gen2(function* () {
72253
72397
  if (dryRun)
@@ -73772,7 +73916,8 @@ var TemplateFamilyId2 = /* @__PURE__ */ String4.check(isPattern2(/* @__PURE__ */
73772
73916
  var VersionCursor2 = /* @__PURE__ */ String4.annotate({ identifier: "VersionCursor" });
73773
73917
  var BrandFooterBlock = /* @__PURE__ */ Struct({
73774
73918
  html: String4,
73775
- text: String4
73919
+ text: String4,
73920
+ headCss: optionalKey2(Union2([String4, Null2]))
73776
73921
  }).annotate({ identifier: "BrandFooterBlock" });
73777
73922
  var TemplatePublicationRender = /* @__PURE__ */ Struct({
73778
73923
  subject: String4,
@@ -73815,7 +73960,8 @@ var TemplateSummary = /* @__PURE__ */ Struct({
73815
73960
  name: String4,
73816
73961
  channel: Literal2("email"),
73817
73962
  projectId: TemplateProjectId2,
73818
- namespaceId: Union2([NamespaceId2, Null2]).annotate({ description: "The namespace the template's project belongs to; every entry in a project shares it. Null at organization level. A namespace-scoped key reaches only its own namespace's templates." }),
73963
+ namespaceId: Union2([NamespaceId2, Null2]).annotate({ description: "The namespace the template's project belongs to; every entry in a project shares it. Null at organization level. A namespace-scoped key reaches only its own namespace's templates and shared ones." }),
73964
+ shared: Boolean3.annotate({ description: "Whether the template's project is shared with every namespace. Every entry in a project shares it. A namespace-scoped key reads and sends a shared template in its own namespace but cannot change it. Only an organization-level project is shared." }),
73819
73965
  entryPath: String4.check(isMaxCodePoints(1024).annotate({ expected: "a string with at most 1024 code points" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$", "u")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })).check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })),
73820
73966
  status: Union2([
73821
73967
  Literal2("draft"),
@@ -73840,7 +73986,8 @@ var Template = /* @__PURE__ */ Struct({
73840
73986
  name: String4,
73841
73987
  channel: Literal2("email"),
73842
73988
  projectId: TemplateProjectId2,
73843
- namespaceId: Union2([NamespaceId2, Null2]).annotate({ description: "The namespace the template's project belongs to; every entry in a project shares it. Null at organization level. A namespace-scoped key reaches only its own namespace's templates." }),
73989
+ namespaceId: Union2([NamespaceId2, Null2]).annotate({ description: "The namespace the template's project belongs to; every entry in a project shares it. Null at organization level. A namespace-scoped key reaches only its own namespace's templates and shared ones." }),
73990
+ shared: Boolean3.annotate({ description: "Whether the template's project is shared with every namespace. Every entry in a project shares it. A namespace-scoped key reads and sends a shared template in its own namespace but cannot change it. Only an organization-level project is shared." }),
73844
73991
  entryPath: String4.check(isMaxCodePoints(1024).annotate({ expected: "a string with at most 1024 code points" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$", "u")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })).check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })),
73845
73992
  status: Union2([
73846
73993
  Literal2("draft"),
@@ -73925,7 +74072,8 @@ var TemplatesListQuery = /* @__PURE__ */ Struct({
73925
74072
  ]), Null2])),
73926
74073
  search: optionalKey2(Union2([String4, Null2])),
73927
74074
  projectId: optionalKey2(Union2([TemplateProjectId2, Null2])),
73928
- namespaceId: optionalKey2(Union2([NamespaceId2, Null2]).annotate({ description: "Only templates in this namespace. A namespace-scoped key lists its own namespace's templates only." }))
74075
+ namespaceId: optionalKey2(Union2([NamespaceId2, Null2]).annotate({ description: "Only the templates this namespace uses: its own and those shared with every namespace (add `shared=false` for its own only). A namespace-scoped key lists its own namespace's and shared templates." })),
74076
+ shared: optionalKey2(Union2([Union2([Literal2("true"), Literal2("false")]), Null2]).annotate({ description: "Only templates whose project is shared with every namespace (true), or only unshared ones (false)." }))
73929
74077
  });
73930
74078
  var TemplatesList200 = TemplatePage;
73931
74079
  var TemplatesList401 = UnauthorizedError2;
@@ -73951,6 +74099,7 @@ var TemplatesCreateRequestJson = /* @__PURE__ */ Struct({
73951
74099
  ]), Null2]), Null2])),
73952
74100
  isActive: optionalKey2(Union2([Boolean3, Null2])),
73953
74101
  namespaceId: optionalKey2(Union2([NamespaceId2, Null2]).annotate({ description: "The namespace the new template project belongs to; the project and every entry added to it share it. Omit for organization level. A namespace-scoped key always creates in its own namespace." })),
74102
+ shared: optionalKey2(Union2([Boolean3, Null2]).annotate({ description: "Share the new organization-level project with every namespace: namespace-scoped keys read and send its entries but cannot change them. Defaults to false. Not with `namespaceId`, and not from a namespace-scoped key." })),
73954
74103
  idempotencyKey: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxCodePoints(255).annotate({ expected: "a string with at most 255 code points" })).check(isMaxLength(255).annotate({ expected: "a value with a length of at most 255" }))
73955
74104
  });
73956
74105
  var TemplatesCreate201 = Template;
@@ -73994,7 +74143,8 @@ var TemplatesUpdateRequestJson = /* @__PURE__ */ Struct({ data: Struct({
73994
74143
  ]), Null2]), Null2])),
73995
74144
  fromDefault: optionalKey2(Union2([Union2([TemplateFromDefault, Null2]), Null2])),
73996
74145
  replyToDefault: optionalKey2(Union2([Union2([ArraySchema(String4), Null2]), Null2])),
73997
- isActive: optionalKey2(Union2([Boolean3, Null2]))
74146
+ isActive: optionalKey2(Union2([Boolean3, Null2])),
74147
+ shared: optionalKey2(Union2([Boolean3, Null2]).annotate({ description: "Share the template's project with every namespace, or stop sharing it. Applies to every entry in the project. Only an organization-level project is shared." }))
73998
74148
  }) });
73999
74149
  var TemplatesUpdate200 = Template;
74000
74150
  var TemplatesUpdate401 = UnauthorizedError2;
@@ -75659,7 +75809,7 @@ var TemplatesGroup = class extends make37("Templates").add(get5("templatesList",
75659
75809
  TemplatesPublish500,
75660
75810
  TemplatesPublish504.pipe(status(504))
75661
75811
  ]
75662
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.publish").annotate(Summary, "Publish an exact template commit").annotate(Description, "Builds an exact Git commit and entry path, stores the immutable compiled IR and its assets, records the input contract it validates against, and makes that publication current."), get5("templatesGetPublication", "/v1/templates/:id/publications/:publicationId", {
75812
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.publish").annotate(Summary, "Publish an exact template commit").annotate(Description, "Builds an exact Git commit and entry path, stores the immutable compiled IR and its assets, records the input contract it validates against, and makes that publication current. A commit that compiles to the same build as an existing publication makes that publication current again instead of recording another."), get5("templatesGetPublication", "/v1/templates/:id/publications/:publicationId", {
75663
75813
  params: TemplatesGetPublicationPathParams,
75664
75814
  success: TemplatesGetPublication200,
75665
75815
  error: [
@@ -78981,6 +79131,11 @@ var WebhookDailyDeliveries = /* @__PURE__ */ Struct({
78981
79131
  exhausted: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
78982
79132
  cancelled: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" }))
78983
79133
  }).annotate({ identifier: "WebhookDailyDeliveries" });
79134
+ var WebhookSigningSecret = /* @__PURE__ */ Struct({
79135
+ secret: SensitiveString,
79136
+ createdAt: String4,
79137
+ previousExpiresAt: Union2([String4, Null2])
79138
+ }).annotate({ identifier: "WebhookSigningSecret" });
78984
79139
  var WebhookEndpoint = /* @__PURE__ */ Struct({
78985
79140
  id: WebhookEndpointId2,
78986
79141
  name: String4,
@@ -79309,6 +79464,14 @@ var WebhooksRegenerateSecret404 = ResourceNotFoundError2;
79309
79464
  var WebhooksRegenerateSecret422 = ValidationError2;
79310
79465
  var WebhooksRegenerateSecret500 = InternalError2;
79311
79466
  var WebhooksRegenerateSecret504 = GatewayTimeoutError2;
79467
+ var WebhooksGetSecretPathParams = /* @__PURE__ */ Struct({ id: WebhookEndpointId2 });
79468
+ var WebhooksGetSecret200 = WebhookSigningSecret;
79469
+ var WebhooksGetSecret401 = UnauthorizedError2;
79470
+ var WebhooksGetSecret403 = ForbiddenError2;
79471
+ var WebhooksGetSecret404 = ResourceNotFoundError2;
79472
+ var WebhooksGetSecret422 = ValidationError2;
79473
+ var WebhooksGetSecret500 = InternalError2;
79474
+ var WebhooksGetSecret504 = GatewayTimeoutError2;
79312
79475
  var WebhooksGetStatsPathParams = /* @__PURE__ */ Struct({ id: WebhookEndpointId2 });
79313
79476
  var WebhooksGetStatsQuery = /* @__PURE__ */ Struct({
79314
79477
  after: optionalKey2(Union2([String4, Null2])),
@@ -79358,7 +79521,7 @@ var WebhooksGroup = class extends make37("Webhooks").add(get5("webhooksList", "/
79358
79521
  WebhooksCreate502.pipe(status(502)),
79359
79522
  WebhooksCreate504.pipe(status(504))
79360
79523
  ]
79361
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "webhooks.create").annotate(Summary, "Create a webhook").annotate(Description, "Creates a signed webhook endpoint for Samva delivery and inbound events, stores subscriptions, and returns the endpoint and signing secret with a 201 response. `mailboxIds` and `namespaceId` confine mailbox events; when both are set, every mailbox must belong to the namespace."), get5("webhooksGet", "/v1/webhooks/:id", {
79524
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "webhooks.create").annotate(Summary, "Create a webhook").annotate(Description, "Creates a signed webhook endpoint for Samva delivery and inbound events, stores subscriptions, and returns the endpoint and signing secret with a 201 response. `namespaceId` confines the endpoint to one namespace's events and `mailboxIds` to those mailboxes' events; when both are set, every mailbox must belong to the namespace."), get5("webhooksGet", "/v1/webhooks/:id", {
79362
79525
  params: WebhooksGetPathParams,
79363
79526
  success: WebhooksGet200,
79364
79527
  error: [
@@ -79429,7 +79592,18 @@ var WebhooksGroup = class extends make37("Webhooks").add(get5("webhooksList", "/
79429
79592
  WebhooksRegenerateSecret500,
79430
79593
  WebhooksRegenerateSecret504.pipe(status(504))
79431
79594
  ]
79432
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "webhooks.regenerateSecret").annotate(Summary, "Rotate a webhook signing secret").annotate(Description, "Generates and returns a new signing secret. The previous secret remains valid for a 24-hour rotation overlap."), get5("webhooksGetStats", "/v1/webhooks/:id/stats", {
79595
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "webhooks.regenerateSecret").annotate(Summary, "Rotate a webhook signing secret").annotate(Description, "Generates and returns a new signing secret. The previous secret remains valid for a 24-hour rotation overlap."), get5("webhooksGetSecret", "/v1/webhooks/:id/secret", {
79596
+ params: WebhooksGetSecretPathParams,
79597
+ success: WebhooksGetSecret200,
79598
+ error: [
79599
+ WebhooksGetSecret401.pipe(status(401)),
79600
+ WebhooksGetSecret403.pipe(status(403)),
79601
+ WebhooksGetSecret404.pipe(status(404)),
79602
+ WebhooksGetSecret422.pipe(status(422)),
79603
+ WebhooksGetSecret500,
79604
+ WebhooksGetSecret504.pipe(status(504))
79605
+ ]
79606
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "webhooks.getSecret").annotate(Summary, "Get a webhook signing secret").annotate(Description, "Returns the endpoint's current signing secret and when a rotated-out secret stops verifying. It needs the same permission as rotating the secret."), get5("webhooksGetStats", "/v1/webhooks/:id/stats", {
79433
79607
  params: WebhooksGetStatsPathParams,
79434
79608
  query: WebhooksGetStatsQuery,
79435
79609
  success: WebhooksGetStats200,
@@ -79460,6 +79634,7 @@ var WebhooksGroup = class extends make37("Webhooks").add(get5("webhooksList", "/
79460
79634
  var make55 = /* @__PURE__ */ forGroup(WebhooksGroup);
79461
79635
  var list27 = /* @__PURE__ */ make55("webhooksList");
79462
79636
  var create16 = /* @__PURE__ */ make55("webhooksCreate");
79637
+ var getSecret = /* @__PURE__ */ make55("webhooksGetSecret");
79463
79638
  var get28 = /* @__PURE__ */ make55("webhooksGet");
79464
79639
  var remove14 = /* @__PURE__ */ make55("webhooksRemove");
79465
79640
  var update13 = /* @__PURE__ */ make55("webhooksUpdate");
@@ -79549,6 +79724,18 @@ var get29 = make29("get", { id: idArgument4, json: jsonFlag }).pipe(withDescript
79549
79724
  ["URL", data.url]
79550
79725
  ]);
79551
79726
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79727
+ var secret = make29("secret", { id: idArgument4, json: jsonFlag }).pipe(withDescription3("Read the current webhook signing secret (requires webhooks:update)"), withExamples([{ command: "samva webhooks secret wep_abc123 --json" }]), withHandler(({ id, json }) => gen2(function* () {
79728
+ const client = yield* getClientProvider;
79729
+ const data = yield* getSecret({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not read webhook signing secret", error)));
79730
+ if (json)
79731
+ return yield* printJson({ ...data, secret: value2(data.secret) });
79732
+ yield* printFields([
79733
+ ["Signing secret", value2(data.secret)],
79734
+ ["Created at", data.createdAt]
79735
+ ]);
79736
+ if (data.previousExpiresAt !== null)
79737
+ yield* log(`The previous secret keeps verifying until ${data.previousExpiresAt}.`);
79738
+ }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79552
79739
  var logs = make29("logs", {
79553
79740
  id: idArgument4,
79554
79741
  limit: Int5("limit").pipe(optional4),
@@ -79706,15 +79893,15 @@ var rotate3 = make29("rotate-secret", {
79706
79893
  yield* confirmDestructive({
79707
79894
  yes,
79708
79895
  json,
79709
- message: `Rotate the signing secret for webhook ${id}? The current secret will stop working.`,
79896
+ message: `Rotate the signing secret for webhook ${id}? The previous secret keeps verifying for 24 hours.`,
79710
79897
  nonInteractiveMessage: "Secret rotation requires --yes in non-interactive mode. Preview with --dry-run."
79711
79898
  });
79712
79899
  const client = yield* getClientProvider;
79713
79900
  const data = yield* regenerateSecret({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not rotate webhook secret", error)));
79714
- const warning = "This signing secret is shown once. Store it now; the previous secret remains valid for 24 hours.";
79901
+ const warning = `Read this signing secret again with samva webhooks secret ${id}. The previous secret remains valid for 24 hours.`;
79715
79902
  const secret = value2(data.secret);
79716
79903
  if (json)
79717
- return yield* printJson({ secret, displayOnce: true, warning });
79904
+ return yield* printJson({ secret, warning });
79718
79905
  yield* error(`WARNING: ${warning}`);
79719
79906
  yield* log(secret);
79720
79907
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
@@ -79735,10 +79922,10 @@ var create17 = make29("create", {
79735
79922
  ]);
79736
79923
  const client = yield* getClientProvider;
79737
79924
  const result = yield* create16(payload).pipe(client.provide, mapError2((error) => toCliApiError("Could not create webhook", error)));
79738
- const warning = "This signing secret is shown once. Store it now; `samva webhooks rotate-secret` issues a new one.";
79925
+ const warning = `Read this signing secret again with samva webhooks secret ${result.endpoint.id}.`;
79739
79926
  const secret = value2(result.secret);
79740
79927
  if (json)
79741
- return yield* printJson({ endpoint: result.endpoint, secret, displayOnce: true, warning });
79928
+ return yield* printJson({ endpoint: result.endpoint, secret, warning });
79742
79929
  yield* error(`WARNING: ${warning}`);
79743
79930
  yield* printFields([
79744
79931
  ["ID", result.endpoint.id],
@@ -79794,7 +79981,19 @@ var remove15 = make29("remove", {
79794
79981
  return yield* printJson(result);
79795
79982
  yield* log(`Removed webhook ${id}.`);
79796
79983
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79797
- var webhooksCommand = make29("webhooks").pipe(withDescription3("Inspect, test, and recover customer webhooks"), withExamples([{ command: "samva webhooks list --json" }]), withSubcommands([list28, get29, create17, update14, test2, logs, stats3, retry4, rotate3, remove15]));
79984
+ var webhooksCommand = make29("webhooks").pipe(withDescription3("Inspect, test, and recover customer webhooks"), withExamples([{ command: "samva webhooks list --json" }]), withSubcommands([
79985
+ list28,
79986
+ get29,
79987
+ secret,
79988
+ create17,
79989
+ update14,
79990
+ test2,
79991
+ logs,
79992
+ stats3,
79993
+ retry4,
79994
+ rotate3,
79995
+ remove15
79996
+ ]));
79798
79997
 
79799
79998
  // src/lib/root-commands.ts
79800
79999
  var rootCommands = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samva/cli",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Samva CLI — email and platform operations from your terminal",
5
5
  "keywords": [
6
6
  "cli",
@@ -50,22 +50,22 @@
50
50
  "@effect/vitest": "4.0.0",
51
51
  "@samva/contracts": "0.0.0",
52
52
  "@samva/core": "0.0.0",
53
- "@samva/mailbox": "0.5.0",
53
+ "@samva/mailbox": "0.5.1",
54
54
  "@samva/markup": "^0.13.1",
55
55
  "@samva/vite": "^0.13.1",
56
56
  "@types/node": "^26.6.2",
57
57
  "better-auth": "1.7.5",
58
58
  "effect": "4.0.0",
59
- "samva": "0.20.0",
59
+ "samva": "0.21.0",
60
60
  "typescript": "^7.0.2",
61
61
  "vite": "~8.3.0",
62
62
  "vitest": "^5.0.3"
63
63
  },
64
64
  "optionalDependencies": {
65
- "@samva/cli-darwin-arm64": "0.16.0",
66
- "@samva/cli-darwin-x64": "0.16.0",
67
- "@samva/cli-linux-arm64": "0.16.0",
68
- "@samva/cli-linux-x64": "0.16.0"
65
+ "@samva/cli-darwin-arm64": "0.18.0",
66
+ "@samva/cli-darwin-x64": "0.18.0",
67
+ "@samva/cli-linux-arm64": "0.18.0",
68
+ "@samva/cli-linux-x64": "0.18.0"
69
69
  },
70
70
  "engines": {
71
71
  "node": ">=18"