@samva/cli 0.17.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 +6 -0
  2. package/dist/bin.mjs +145 -25
  3. package/package.json +7 -7
package/AGENTS.md CHANGED
@@ -37,6 +37,12 @@ confirmation (`--yes` outside a terminal), and every mutation supports `--dry-ru
37
37
  `SAMVA_API_KEY`, `samva org current` asks the API who the key is (`GET /v1/whoami`) and reports its
38
38
  organization and key; with OAuth it reports the profile's selected organization.
39
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
+
40
46
  `samva templates export --out <dir>` renders every local template's fixtures to HTML
41
47
  and text with the asset manifest, needing a Samva credential only to resolve a live `samva:brand`; `--asset-base <origin>` is for an
42
48
  export that will be delivered, which cannot carry a relative reference.
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.17.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.17.0",
14172
- "@samva/cli-darwin-x64": "workspace:0.17.0",
14173
- "@samva/cli-linux-arm64": "workspace:0.17.0",
14174
- "@samva/cli-linux-x64": "workspace:0.17.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"
@@ -19596,6 +19596,9 @@ var ENTITY_PREFIXES = {
19596
19596
  waflow: "WhatsAppFlowId",
19597
19597
  wafver: "WhatsAppFlowVersionId",
19598
19598
  wafreg: "WhatsAppFlowRegistrationId",
19599
+ wafkey: "WhatsAppFlowEncryptionKeyId",
19600
+ wafx: "WhatsAppFlowExchangeId",
19601
+ wasl: "WhatsAppSetupLinkId",
19599
19602
  mbx: "MailboxId",
19600
19603
  mbxep: "MailboxEndpointId",
19601
19604
  thread: "MailboxThreadId",
@@ -19691,6 +19694,9 @@ var WhatsAppBusinessAccountId = entityId("waba").pipe(brand(ENTITY_PREFIXES.waba
19691
19694
  var WhatsAppFlowId = entityId("waflow").pipe(brand(ENTITY_PREFIXES.waflow));
19692
19695
  var WhatsAppFlowVersionId = entityId("wafver").pipe(brand(ENTITY_PREFIXES.wafver));
19693
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));
19694
19700
  var WhatsAppServiceWindowId = entityId("wasw").pipe(brand(ENTITY_PREFIXES.wasw));
19695
19701
  var WebhookEndpointId = entityId("wep").pipe(brand(ENTITY_PREFIXES.wep));
19696
19702
  var WebhookCredentialId = entityId("whcred").pipe(brand(ENTITY_PREFIXES.whcred));
@@ -20398,6 +20404,7 @@ var DOMAIN_WEBHOOK_EVENT_TYPE_VALUES = [
20398
20404
  var domainWebhookEventTypeSchema = Literals(DOMAIN_WEBHOOK_EVENT_TYPE_VALUES);
20399
20405
  var TEMPLATE_WEBHOOK_EVENT_TYPE_VALUES = ["template.registration.updated"];
20400
20406
  var FLOW_WEBHOOK_EVENT_TYPE_VALUES = ["flow.registration.updated"];
20407
+ var WHATSAPP_NUMBER_WEBHOOK_EVENT_TYPE_VALUES = ["whatsapp.number.connected"];
20401
20408
  var MAILBOX_WEBHOOK_EVENT_TYPE_VALUES = [
20402
20409
  "mailbox.message.received",
20403
20410
  "mailbox.message.sent",
@@ -20422,6 +20429,7 @@ var WEBHOOK_EVENT_TYPE_VALUES = [
20422
20429
  ...DOMAIN_WEBHOOK_EVENT_TYPE_VALUES,
20423
20430
  ...TEMPLATE_WEBHOOK_EVENT_TYPE_VALUES,
20424
20431
  ...FLOW_WEBHOOK_EVENT_TYPE_VALUES,
20432
+ ...WHATSAPP_NUMBER_WEBHOOK_EVENT_TYPE_VALUES,
20425
20433
  ...MAILBOX_WEBHOOK_EVENT_TYPE_VALUES,
20426
20434
  ...JOURNEY_WEBHOOK_EVENT_TYPE_VALUES
20427
20435
  ];
@@ -24149,6 +24157,7 @@ var CreateTemplatePayload = /* @__PURE__ */ Struct({
24149
24157
  ]), Null2]), Null2])),
24150
24158
  isActive: optionalKey2(Union2([Boolean3, Null2])),
24151
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." })),
24152
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" }))
24153
24162
  }).annotate({ identifier: "CreateTemplatePayload" });
24154
24163
  var CreateWebhookPayload = /* @__PURE__ */ Struct({
@@ -24462,7 +24471,8 @@ var UpdateTemplatePayload = /* @__PURE__ */ Struct({ data: Struct({
24462
24471
  ]), Null2]), Null2])),
24463
24472
  fromDefault: optionalKey2(Union2([Union2([TemplateFromDefault, Null2]), Null2])),
24464
24473
  replyToDefault: optionalKey2(Union2([Union2([ArraySchema(String4), Null2]), Null2])),
24465
- 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." }))
24466
24476
  }) }).annotate({ identifier: "UpdateTemplatePayload" });
24467
24477
  var ApiKeyAccess = /* @__PURE__ */ Union2([Struct({ mode: Literal2("full") }), Struct({
24468
24478
  mode: Literal2("restricted"),
@@ -24799,6 +24809,7 @@ var apiStatuses = {
24799
24809
  MailboxSendConflictError: 409,
24800
24810
  MailboxSendRefusedError: 403,
24801
24811
  CursorExpiredError: 410,
24812
+ WhatsAppSetupLinkUnavailableError: 410,
24802
24813
  MethodNotAllowedError: 405,
24803
24814
  UpgradeRequiredError: 426,
24804
24815
  RateLimitedError: 429,
@@ -25082,7 +25093,7 @@ var __exportAll = (all, no_symbols) => {
25082
25093
  };
25083
25094
 
25084
25095
  // ../sdk/dist/package.js
25085
- var version = "0.20.0";
25096
+ var version = "0.21.0";
25086
25097
 
25087
25098
  // ../sdk/dist/client-attribution.js
25088
25099
  var SDK_VERSION = version;
@@ -25473,6 +25484,7 @@ var commandTree = {
25473
25484
  webhooks: {
25474
25485
  list: {},
25475
25486
  get: {},
25487
+ secret: {},
25476
25488
  create: {},
25477
25489
  update: {},
25478
25490
  test: {},
@@ -25903,7 +25915,12 @@ var getAuthenticatedJsonForContext = fn2("CliClient.getAuthenticatedJson")(funct
25903
25915
  };
25904
25916
  const url = `${config.baseUrl.replace(/\/$/, "")}${path}`;
25905
25917
  const response = yield* tryPromise2({
25906
- 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
+ }),
25907
25924
  catch: (cause) => new SamvaCliError({ message: `Request to ${url} failed: ${String(cause)}` })
25908
25925
  });
25909
25926
  const body = yield* tryPromise2({
@@ -27166,6 +27183,10 @@ var SDK_OPERATION_CALLS = {
27166
27183
  whoamiGet: { kind: "none" },
27167
27184
  webhooksList: { kind: "params" },
27168
27185
  webhooksCreate: { kind: "payload" },
27186
+ webhooksGetSecret: {
27187
+ kind: "path",
27188
+ paths: ["id"]
27189
+ },
27169
27190
  webhooksGet: {
27170
27191
  kind: "path",
27171
27192
  paths: ["id"]
@@ -28558,6 +28579,7 @@ var SamvaOperations = {
28558
28579
  webhooksRetryDelivery: operationMetadata96,
28559
28580
  webhooksGetLogs: operationMetadata95,
28560
28581
  webhooksRegenerateSecret: operationMetadata96,
28582
+ webhooksGetSecret: operationMetadata97,
28561
28583
  webhooksGetStats: operationMetadata97,
28562
28584
  webhooksTest: operationMetadata96,
28563
28585
  whoamiGet: {
@@ -28804,6 +28826,7 @@ var ApiKey = /* @__PURE__ */ Struct({
28804
28826
  name: String4,
28805
28827
  key: optionalKey2(Union2([String4, Null2])),
28806
28828
  start: Union2([String4, Null2]),
28829
+ end: Union2([String4, Null2]),
28807
28830
  description: optionalKey2(Union2([String4, Null2])),
28808
28831
  createdAt: String4,
28809
28832
  expiresAt: Union2([String4, Null2]),
@@ -28817,6 +28840,7 @@ var ApiKeyWithSecret = /* @__PURE__ */ Struct({
28817
28840
  name: String4,
28818
28841
  key: SensitiveString,
28819
28842
  start: Union2([String4, Null2]),
28843
+ end: Union2([String4, Null2]),
28820
28844
  description: optionalKey2(Union2([String4, Null2])),
28821
28845
  createdAt: String4,
28822
28846
  expiresAt: Union2([String4, Null2]),
@@ -54206,6 +54230,7 @@ var CampaignLatestRun = /* @__PURE__ */ Struct({
54206
54230
  dispatchedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54207
54231
  failedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54208
54232
  skippedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54233
+ pendingCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54209
54234
  estimatedCompletionAt: Union2([String4, Null2])
54210
54235
  }).annotate({ identifier: "CampaignLatestRun" });
54211
54236
  var CampaignRecipientPage = /* @__PURE__ */ Struct({
@@ -54304,11 +54329,14 @@ var CampaignRun = /* @__PURE__ */ Struct({
54304
54329
  dispatchedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54305
54330
  failedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54306
54331
  skippedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54332
+ pendingCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54307
54333
  skippedReasons: ArraySchema(CampaignRunSkippedReason),
54308
54334
  estimatedCompletionAt: Union2([String4, Null2]),
54309
54335
  failureReason: Union2([String4, Null2]),
54310
54336
  holdReason: Union2([String4, Null2]),
54311
54337
  holdDomainId: Union2([EmailDomainId2, Null2]),
54338
+ holdUntil: Union2([String4, Null2]),
54339
+ capacityWarning: Union2([String4, Null2]),
54312
54340
  lastContactId: Union2([String4, Null2]),
54313
54341
  createdBy: Union2([String4, Null2]),
54314
54342
  actorEmail: String4,
@@ -54659,6 +54687,23 @@ var resumeRun = /* @__PURE__ */ make42("campaignsResumeRun");
54659
54687
  var cancelRun = /* @__PURE__ */ make42("campaignsCancelRun");
54660
54688
  var listRecipients = /* @__PURE__ */ make42("campaignsListRecipients");
54661
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
+
54662
54707
  // src/lib/template-reference.ts
54663
54708
  var publicationIdFlag = String9("publication-id").pipe(withDescription2("Pin one exact publication, so nothing published later changes what this renders"), optional4);
54664
54709
  var inputContractIdFlag = String9("input-contract-id").pipe(withDescription2("Follow the newest publication that still validates input identically to this contract"), optional4);
@@ -55131,7 +55176,7 @@ var archive2 = make29("archive", {
55131
55176
  ["Definition", data.definition.state]
55132
55177
  ]);
55133
55178
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
55134
- var printRun = (run) => printFields([
55179
+ var printRun = (run, options = { warning: true }) => printFields([
55135
55180
  ["Run ID", run.id],
55136
55181
  ["Campaign", run.campaignId],
55137
55182
  ["Status", run.status],
@@ -55146,8 +55191,10 @@ var printRun = (run) => printFields([
55146
55191
  "Skipped by reason",
55147
55192
  (run.skippedReasons ?? []).map(({ reason, count }) => `${reason} ${count}`).join(", ")
55148
55193
  ],
55149
- ["Failure", run.failureReason]
55194
+ ["Failure", run.failureReason],
55195
+ ...campaignPacingFields(run, options)
55150
55196
  ]);
55197
+ var warnCapacity = (run) => run.capacityWarning ? error(`Warning: ${run.capacityWarning}`) : void_3;
55151
55198
  var runCreate = make29("create", {
55152
55199
  campaignId: String10("campaignId"),
55153
55200
  at: atFlag,
@@ -55186,7 +55233,8 @@ var runCreate = make29("create", {
55186
55233
  return yield* printJson(data);
55187
55234
  yield* logInfo(`Run scheduled.
55188
55235
  `);
55189
- yield* printRun(data);
55236
+ yield* printRun(data, { warning: false });
55237
+ yield* warnCapacity(data);
55190
55238
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
55191
55239
  var runList = make29("list", {
55192
55240
  campaignId: String10("campaignId"),
@@ -67136,6 +67184,18 @@ class WhatsAppTemplateIneligibleError extends TaggedError3()("WhatsAppTemplateIn
67136
67184
  message: String4
67137
67185
  }, { httpApiStatus: 422 }) {
67138
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
+ }
67139
67199
  var EmailChannelErrorReason = Literals([
67140
67200
  "transport",
67141
67201
  "rejected",
@@ -67678,6 +67738,8 @@ var JourneyRun = /* @__PURE__ */ Struct({
67678
67738
  exitReason: Union2([JourneyRunExitReason, Null2]),
67679
67739
  failureReason: Union2([String4, Null2]),
67680
67740
  currentNodeId: Union2([JourneyNodeId, Null2]),
67741
+ holdReason: Union2([String4, Null2]),
67742
+ holdUntil: Union2([String4, Null2]),
67681
67743
  trigger: JourneyRunTrigger,
67682
67744
  enteredAt: String4,
67683
67745
  endedAt: Union2([String4, Null2])
@@ -67701,6 +67763,8 @@ var JourneyRunDetail = /* @__PURE__ */ Struct({
67701
67763
  exitReason: Union2([JourneyRunExitReason, Null2]),
67702
67764
  failureReason: Union2([String4, Null2]),
67703
67765
  currentNodeId: Union2([JourneyNodeId, Null2]),
67766
+ holdReason: Union2([String4, Null2]),
67767
+ holdUntil: Union2([String4, Null2]),
67704
67768
  trigger: JourneyRunTrigger,
67705
67769
  enteredAt: String4,
67706
67770
  endedAt: Union2([String4, Null2]),
@@ -68329,6 +68393,7 @@ var printRun2 = (run) => printFields([
68329
68393
  ["Exit reason", run.exitReason],
68330
68394
  ["Failure", run.failureReason],
68331
68395
  ["Current node", run.currentNodeId],
68396
+ ...holdFields(run),
68332
68397
  ["Entered", run.enteredAt],
68333
68398
  ["Ended", run.endedAt]
68334
68399
  ]);
@@ -73851,7 +73916,8 @@ var TemplateFamilyId2 = /* @__PURE__ */ String4.check(isPattern2(/* @__PURE__ */
73851
73916
  var VersionCursor2 = /* @__PURE__ */ String4.annotate({ identifier: "VersionCursor" });
73852
73917
  var BrandFooterBlock = /* @__PURE__ */ Struct({
73853
73918
  html: String4,
73854
- text: String4
73919
+ text: String4,
73920
+ headCss: optionalKey2(Union2([String4, Null2]))
73855
73921
  }).annotate({ identifier: "BrandFooterBlock" });
73856
73922
  var TemplatePublicationRender = /* @__PURE__ */ Struct({
73857
73923
  subject: String4,
@@ -73894,7 +73960,8 @@ var TemplateSummary = /* @__PURE__ */ Struct({
73894
73960
  name: String4,
73895
73961
  channel: Literal2("email"),
73896
73962
  projectId: TemplateProjectId2,
73897
- 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." }),
73898
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" })),
73899
73966
  status: Union2([
73900
73967
  Literal2("draft"),
@@ -73919,7 +73986,8 @@ var Template = /* @__PURE__ */ Struct({
73919
73986
  name: String4,
73920
73987
  channel: Literal2("email"),
73921
73988
  projectId: TemplateProjectId2,
73922
- 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." }),
73923
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" })),
73924
73992
  status: Union2([
73925
73993
  Literal2("draft"),
@@ -74004,7 +74072,8 @@ var TemplatesListQuery = /* @__PURE__ */ Struct({
74004
74072
  ]), Null2])),
74005
74073
  search: optionalKey2(Union2([String4, Null2])),
74006
74074
  projectId: optionalKey2(Union2([TemplateProjectId2, Null2])),
74007
- 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)." }))
74008
74077
  });
74009
74078
  var TemplatesList200 = TemplatePage;
74010
74079
  var TemplatesList401 = UnauthorizedError2;
@@ -74030,6 +74099,7 @@ var TemplatesCreateRequestJson = /* @__PURE__ */ Struct({
74030
74099
  ]), Null2]), Null2])),
74031
74100
  isActive: optionalKey2(Union2([Boolean3, Null2])),
74032
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." })),
74033
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" }))
74034
74104
  });
74035
74105
  var TemplatesCreate201 = Template;
@@ -74073,7 +74143,8 @@ var TemplatesUpdateRequestJson = /* @__PURE__ */ Struct({ data: Struct({
74073
74143
  ]), Null2]), Null2])),
74074
74144
  fromDefault: optionalKey2(Union2([Union2([TemplateFromDefault, Null2]), Null2])),
74075
74145
  replyToDefault: optionalKey2(Union2([Union2([ArraySchema(String4), Null2]), Null2])),
74076
- 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." }))
74077
74148
  }) });
74078
74149
  var TemplatesUpdate200 = Template;
74079
74150
  var TemplatesUpdate401 = UnauthorizedError2;
@@ -79060,6 +79131,11 @@ var WebhookDailyDeliveries = /* @__PURE__ */ Struct({
79060
79131
  exhausted: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
79061
79132
  cancelled: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" }))
79062
79133
  }).annotate({ identifier: "WebhookDailyDeliveries" });
79134
+ var WebhookSigningSecret = /* @__PURE__ */ Struct({
79135
+ secret: SensitiveString,
79136
+ createdAt: String4,
79137
+ previousExpiresAt: Union2([String4, Null2])
79138
+ }).annotate({ identifier: "WebhookSigningSecret" });
79063
79139
  var WebhookEndpoint = /* @__PURE__ */ Struct({
79064
79140
  id: WebhookEndpointId2,
79065
79141
  name: String4,
@@ -79388,6 +79464,14 @@ var WebhooksRegenerateSecret404 = ResourceNotFoundError2;
79388
79464
  var WebhooksRegenerateSecret422 = ValidationError2;
79389
79465
  var WebhooksRegenerateSecret500 = InternalError2;
79390
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;
79391
79475
  var WebhooksGetStatsPathParams = /* @__PURE__ */ Struct({ id: WebhookEndpointId2 });
79392
79476
  var WebhooksGetStatsQuery = /* @__PURE__ */ Struct({
79393
79477
  after: optionalKey2(Union2([String4, Null2])),
@@ -79437,7 +79521,7 @@ var WebhooksGroup = class extends make37("Webhooks").add(get5("webhooksList", "/
79437
79521
  WebhooksCreate502.pipe(status(502)),
79438
79522
  WebhooksCreate504.pipe(status(504))
79439
79523
  ]
79440
- }).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", {
79441
79525
  params: WebhooksGetPathParams,
79442
79526
  success: WebhooksGet200,
79443
79527
  error: [
@@ -79508,7 +79592,18 @@ var WebhooksGroup = class extends make37("Webhooks").add(get5("webhooksList", "/
79508
79592
  WebhooksRegenerateSecret500,
79509
79593
  WebhooksRegenerateSecret504.pipe(status(504))
79510
79594
  ]
79511
- }).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", {
79512
79607
  params: WebhooksGetStatsPathParams,
79513
79608
  query: WebhooksGetStatsQuery,
79514
79609
  success: WebhooksGetStats200,
@@ -79539,6 +79634,7 @@ var WebhooksGroup = class extends make37("Webhooks").add(get5("webhooksList", "/
79539
79634
  var make55 = /* @__PURE__ */ forGroup(WebhooksGroup);
79540
79635
  var list27 = /* @__PURE__ */ make55("webhooksList");
79541
79636
  var create16 = /* @__PURE__ */ make55("webhooksCreate");
79637
+ var getSecret = /* @__PURE__ */ make55("webhooksGetSecret");
79542
79638
  var get28 = /* @__PURE__ */ make55("webhooksGet");
79543
79639
  var remove14 = /* @__PURE__ */ make55("webhooksRemove");
79544
79640
  var update13 = /* @__PURE__ */ make55("webhooksUpdate");
@@ -79628,6 +79724,18 @@ var get29 = make29("get", { id: idArgument4, json: jsonFlag }).pipe(withDescript
79628
79724
  ["URL", data.url]
79629
79725
  ]);
79630
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)));
79631
79739
  var logs = make29("logs", {
79632
79740
  id: idArgument4,
79633
79741
  limit: Int5("limit").pipe(optional4),
@@ -79785,15 +79893,15 @@ var rotate3 = make29("rotate-secret", {
79785
79893
  yield* confirmDestructive({
79786
79894
  yes,
79787
79895
  json,
79788
- 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.`,
79789
79897
  nonInteractiveMessage: "Secret rotation requires --yes in non-interactive mode. Preview with --dry-run."
79790
79898
  });
79791
79899
  const client = yield* getClientProvider;
79792
79900
  const data = yield* regenerateSecret({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not rotate webhook secret", error)));
79793
- 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.`;
79794
79902
  const secret = value2(data.secret);
79795
79903
  if (json)
79796
- return yield* printJson({ secret, displayOnce: true, warning });
79904
+ return yield* printJson({ secret, warning });
79797
79905
  yield* error(`WARNING: ${warning}`);
79798
79906
  yield* log(secret);
79799
79907
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
@@ -79814,10 +79922,10 @@ var create17 = make29("create", {
79814
79922
  ]);
79815
79923
  const client = yield* getClientProvider;
79816
79924
  const result = yield* create16(payload).pipe(client.provide, mapError2((error) => toCliApiError("Could not create webhook", error)));
79817
- 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}.`;
79818
79926
  const secret = value2(result.secret);
79819
79927
  if (json)
79820
- return yield* printJson({ endpoint: result.endpoint, secret, displayOnce: true, warning });
79928
+ return yield* printJson({ endpoint: result.endpoint, secret, warning });
79821
79929
  yield* error(`WARNING: ${warning}`);
79822
79930
  yield* printFields([
79823
79931
  ["ID", result.endpoint.id],
@@ -79873,7 +79981,19 @@ var remove15 = make29("remove", {
79873
79981
  return yield* printJson(result);
79874
79982
  yield* log(`Removed webhook ${id}.`);
79875
79983
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79876
- 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
+ ]));
79877
79997
 
79878
79998
  // src/lib/root-commands.ts
79879
79999
  var rootCommands = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samva/cli",
3
- "version": "0.17.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.17.0",
66
- "@samva/cli-darwin-x64": "0.17.0",
67
- "@samva/cli-linux-arm64": "0.17.0",
68
- "@samva/cli-linux-x64": "0.17.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"