@samva/cli 0.17.0 → 0.19.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 +14 -0
  2. package/dist/bin.mjs +431 -48
  3. package/package.json +9 -9
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.
@@ -73,6 +79,14 @@ uploads again. `--dry-run` lists each attachment and uploads nothing.
73
79
  or the newest publication that still validates input identically. They are mutually exclusive and
74
80
  each requires `--template-id`, because a reference is a statement about one template's history.
75
81
 
82
+ `samva templates list --shared true|false` filters by whether the template's project is shared with
83
+ every namespace, and every listed template reports `shared`. Sharing belongs to an organization-level
84
+ project: `templates create --shared` shares the new project and refuses a namespace (exit `2`), and
85
+ `templates update <id> --data '{}' --shared true|false` shares or unshares the project for every
86
+ entry in it. `--shared` that disagrees with `shared` in `--data` exits `2`. A namespace-scoped key
87
+ lists, reads, and sends shared templates but gets 403 changing them, and the API refuses moving a
88
+ shared project into a namespace.
89
+
76
90
  `samva templates workspace <open|diff|patch|add-asset|check|fixture|save|history|publish>` is the
77
91
  hosted project workspace over the CLI, for a caller with no clone: an agent in a sandbox, a CI job,
78
92
  a script. It reaches the same operations the hosted MCP tools reach, so an agent can start a journey in one
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.19.0",
14110
14110
  description: "Samva CLI \u2014 email and platform operations from your terminal",
14111
14111
  keywords: [
14112
14112
  "cli",
@@ -14157,21 +14157,21 @@ var package_default = {
14157
14157
  "@samva/contracts": "workspace:*",
14158
14158
  "@samva/core": "workspace:*",
14159
14159
  "@samva/mailbox": "workspace:*",
14160
- "@samva/markup": "^0.13.1",
14161
- "@samva/vite": "^0.13.1",
14160
+ "@samva/markup": "catalog:",
14161
+ "@samva/vite": "catalog:",
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.22.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.19.0",
14172
+ "@samva/cli-darwin-x64": "workspace:0.19.0",
14173
+ "@samva/cli-linux-arm64": "workspace:0.19.0",
14174
+ "@samva/cli-linux-x64": "workspace:0.19.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({
@@ -24180,6 +24189,9 @@ var CreateWebhookPayload = /* @__PURE__ */ Struct({
24180
24189
  Literal2("webhook.test"),
24181
24190
  Literal2("domain.updated"),
24182
24191
  Literal2("domain.warming.updated"),
24192
+ Literal2("template.registration.updated"),
24193
+ Literal2("flow.registration.updated"),
24194
+ Literal2("whatsapp.number.connected"),
24183
24195
  Literal2("mailbox.message.received"),
24184
24196
  Literal2("mailbox.message.sent"),
24185
24197
  Literal2("mailbox.action.pending_approval"),
@@ -24224,6 +24236,9 @@ var UpdateWebhookPayload = /* @__PURE__ */ Struct({
24224
24236
  Literal2("webhook.test"),
24225
24237
  Literal2("domain.updated"),
24226
24238
  Literal2("domain.warming.updated"),
24239
+ Literal2("template.registration.updated"),
24240
+ Literal2("flow.registration.updated"),
24241
+ Literal2("whatsapp.number.connected"),
24227
24242
  Literal2("mailbox.message.received"),
24228
24243
  Literal2("mailbox.message.sent"),
24229
24244
  Literal2("mailbox.action.pending_approval"),
@@ -24462,7 +24477,8 @@ var UpdateTemplatePayload = /* @__PURE__ */ Struct({ data: Struct({
24462
24477
  ]), Null2]), Null2])),
24463
24478
  fromDefault: optionalKey2(Union2([Union2([TemplateFromDefault, Null2]), Null2])),
24464
24479
  replyToDefault: optionalKey2(Union2([Union2([ArraySchema(String4), Null2]), Null2])),
24465
- isActive: optionalKey2(Union2([Boolean3, Null2]))
24480
+ isActive: optionalKey2(Union2([Boolean3, Null2])),
24481
+ 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
24482
  }) }).annotate({ identifier: "UpdateTemplatePayload" });
24467
24483
  var ApiKeyAccess = /* @__PURE__ */ Union2([Struct({ mode: Literal2("full") }), Struct({
24468
24484
  mode: Literal2("restricted"),
@@ -24799,6 +24815,7 @@ var apiStatuses = {
24799
24815
  MailboxSendConflictError: 409,
24800
24816
  MailboxSendRefusedError: 403,
24801
24817
  CursorExpiredError: 410,
24818
+ WhatsAppSetupLinkUnavailableError: 410,
24802
24819
  MethodNotAllowedError: 405,
24803
24820
  UpgradeRequiredError: 426,
24804
24821
  RateLimitedError: 429,
@@ -25082,7 +25099,7 @@ var __exportAll = (all, no_symbols) => {
25082
25099
  };
25083
25100
 
25084
25101
  // ../sdk/dist/package.js
25085
- var version = "0.20.0";
25102
+ var version = "0.22.0";
25086
25103
 
25087
25104
  // ../sdk/dist/client-attribution.js
25088
25105
  var SDK_VERSION = version;
@@ -25421,6 +25438,7 @@ var commandTree = {
25421
25438
  list: {},
25422
25439
  get: {},
25423
25440
  create: {},
25441
+ "from-website": {},
25424
25442
  update: {},
25425
25443
  remove: {}
25426
25444
  },
@@ -25473,6 +25491,7 @@ var commandTree = {
25473
25491
  webhooks: {
25474
25492
  list: {},
25475
25493
  get: {},
25494
+ secret: {},
25476
25495
  create: {},
25477
25496
  update: {},
25478
25497
  test: {},
@@ -25903,7 +25922,12 @@ var getAuthenticatedJsonForContext = fn2("CliClient.getAuthenticatedJson")(funct
25903
25922
  };
25904
25923
  const url = `${config.baseUrl.replace(/\/$/, "")}${path}`;
25905
25924
  const response = yield* tryPromise2({
25906
- try: (signal) => fetch(url, request === undefined ? { headers, signal } : { method: "POST", headers, body: JSON.stringify(request.body), signal }),
25925
+ try: (signal) => fetch(url, request === undefined ? { headers, signal } : {
25926
+ method: request.method ?? "POST",
25927
+ headers,
25928
+ body: JSON.stringify(request.body),
25929
+ signal
25930
+ }),
25907
25931
  catch: (cause) => new SamvaCliError({ message: `Request to ${url} failed: ${String(cause)}` })
25908
25932
  });
25909
25933
  const body = yield* tryPromise2({
@@ -27045,6 +27069,7 @@ var SDK_OPERATION_CALLS = {
27045
27069
  },
27046
27070
  templatesList: { kind: "params" },
27047
27071
  templatesCreate: { kind: "payload" },
27072
+ templatesCreateFromWebsite: { kind: "payload" },
27048
27073
  templatesAddEntry: {
27049
27074
  kind: "pathPayload",
27050
27075
  paths: ["id"]
@@ -27166,6 +27191,10 @@ var SDK_OPERATION_CALLS = {
27166
27191
  whoamiGet: { kind: "none" },
27167
27192
  webhooksList: { kind: "params" },
27168
27193
  webhooksCreate: { kind: "payload" },
27194
+ webhooksGetSecret: {
27195
+ kind: "path",
27196
+ paths: ["id"]
27197
+ },
27169
27198
  webhooksGet: {
27170
27199
  kind: "path",
27171
27200
  paths: ["id"]
@@ -28532,6 +28561,7 @@ var SamvaOperations = {
28532
28561
  templatesAttachFamilyMember: operationMetadata85,
28533
28562
  templatesDetachFamilyMember: operationMetadata87,
28534
28563
  templatesCreateFamilyFromTemplate: operationMetadata85,
28564
+ templatesCreateFromWebsite: operationMetadata85,
28535
28565
  templatesAddEntry: operationMetadata85,
28536
28566
  unsubscribeList: operationMetadata90,
28537
28567
  unsubscribeCreate: operationMetadata91,
@@ -28558,6 +28588,7 @@ var SamvaOperations = {
28558
28588
  webhooksRetryDelivery: operationMetadata96,
28559
28589
  webhooksGetLogs: operationMetadata95,
28560
28590
  webhooksRegenerateSecret: operationMetadata96,
28591
+ webhooksGetSecret: operationMetadata97,
28561
28592
  webhooksGetStats: operationMetadata97,
28562
28593
  webhooksTest: operationMetadata96,
28563
28594
  whoamiGet: {
@@ -28804,6 +28835,7 @@ var ApiKey = /* @__PURE__ */ Struct({
28804
28835
  name: String4,
28805
28836
  key: optionalKey2(Union2([String4, Null2])),
28806
28837
  start: Union2([String4, Null2]),
28838
+ end: Union2([String4, Null2]),
28807
28839
  description: optionalKey2(Union2([String4, Null2])),
28808
28840
  createdAt: String4,
28809
28841
  expiresAt: Union2([String4, Null2]),
@@ -28817,6 +28849,7 @@ var ApiKeyWithSecret = /* @__PURE__ */ Struct({
28817
28849
  name: String4,
28818
28850
  key: SensitiveString,
28819
28851
  start: Union2([String4, Null2]),
28852
+ end: Union2([String4, Null2]),
28820
28853
  description: optionalKey2(Union2([String4, Null2])),
28821
28854
  createdAt: String4,
28822
28855
  expiresAt: Union2([String4, Null2]),
@@ -29427,6 +29460,10 @@ var apiKeysCommand = make29("api-keys").pipe(withDescription3("Create, inspect,
29427
29460
 
29428
29461
  // src/commands/brands.ts
29429
29462
  import { join as join3, relative, resolve as resolve5 } from "path";
29463
+ // ../../node_modules/.bun/@samva+markup@0.15.0+a19bcd7db259aaf7/node_modules/@samva/markup/dist/dark-scheme-CbCwGDWH.js
29464
+ var DARK_SCHEME_QUERY = /^(?:only\s+)?(?:screen\s+and\s+)?\(\s*prefers-color-scheme\s*:\s*dark\s*\)$/i;
29465
+ var isDarkSchemeQuery = (prelude) => DARK_SCHEME_QUERY.test(prelude.trim());
29466
+
29430
29467
  // ../../node_modules/.bun/css-tree@3.2.1/node_modules/css-tree/lib/tokenizer/types.js
29431
29468
  var EOF = 0;
29432
29469
  var Ident = 1;
@@ -53199,21 +53236,21 @@ var $ = (e) => {
53199
53236
  };
53200
53237
  var ae = { ok: l, isOk: u, err: d, isError: f, try: T, tryPromise: E, map: D, mapError: O, tryRecover: k, andThen: A, tryRecoverAsync: ee, andThenAsync: te, match: ne, tap: j, tapAsync: M, tapError: N6, tapErrorAsync: P, tapBoth: F4, tapBothAsync: I, unwrap: L, unwrapOr: z, gen: B, await: V, codec: re, all: Q, allAsync: ie, partition: $, partitionAsync: async (e) => $(await Z(e, `Result.partitionAsync input promise rejected`)), flatten: (e) => e.status === `ok` ? e.value : e };
53201
53238
 
53202
- // ../../node_modules/.bun/@samva+markup@0.13.1+a19bcd7db259aaf7/node_modules/@samva/markup/dist/theme-css-CEUrDpqq.js
53239
+ // ../../node_modules/.bun/@samva+markup@0.15.0+a19bcd7db259aaf7/node_modules/@samva/markup/dist/theme-css-s9QwD5Ko.js
53203
53240
  var BRAND_NAMESPACES = [
53204
53241
  "--color-",
53205
53242
  "--font-",
53206
53243
  "--text-",
53207
53244
  "--leading-",
53208
53245
  "--tracking-",
53209
- "--radius-",
53246
+ "--radius",
53210
53247
  "--spacing",
53211
53248
  "--container-",
53212
53249
  "--shadow-"
53213
53250
  ];
53251
+ var inNamespace = (property, namespace) => namespace.endsWith("-") ? property.startsWith(namespace) : property === namespace || property.startsWith(`${namespace}-`);
53214
53252
  var BRAND_PROPERTY = /^--[a-z][a-z0-9-]*$/;
53215
53253
  var BRAND_UNSAFE_VALUE = /[{};<>\\@]|url\s*\(|expression\s*\(|\/\*/i;
53216
- var BRAND_DARK_MEDIA = /^\(\s*prefers-color-scheme\s*:\s*dark\s*\)$/i;
53217
53254
  var BRAND_FONT_DESCRIPTORS = /* @__PURE__ */ new Set([
53218
53255
  "font-family",
53219
53256
  "src",
@@ -53222,7 +53259,6 @@ var BRAND_FONT_DESCRIPTORS = /* @__PURE__ */ new Set([
53222
53259
  "font-display",
53223
53260
  "unicode-range"
53224
53261
  ]);
53225
- var darkVariableName = (property) => `${property}-dark`;
53226
53262
  var spanOfNode = (node) => ({
53227
53263
  start: node.loc?.start.offset ?? 0,
53228
53264
  end: node.loc?.end.offset ?? 0
@@ -53232,6 +53268,7 @@ var unquoteFamily = (value) => {
53232
53268
  const quoted = /^(['"])(.*)\1$/s.exec(trimmed);
53233
53269
  return quoted === null ? trimmed : quoted[2].replace(/\\(.)/g, "$1");
53234
53270
  };
53271
+ var quoteFamily = (family) => `"${family.replace(/["\\]/g, "\\$&")}"`;
53235
53272
  var parseBrandCss = (css) => {
53236
53273
  const diagnostics = [];
53237
53274
  const report = (code, message, span) => {
@@ -53273,7 +53310,7 @@ var parseBrandCss = (css) => {
53273
53310
  report("brand-css-property", `${property} is not a theme variable; name theme variables --namespace-name.`, span);
53274
53311
  continue;
53275
53312
  }
53276
- if (!BRAND_NAMESPACES.some((namespace) => property.startsWith(namespace))) {
53313
+ if (!BRAND_NAMESPACES.some((namespace) => inNamespace(property, namespace))) {
53277
53314
  report("brand-css-namespace", `${property} is outside the brand namespaces (${BRAND_NAMESPACES.map((namespace) => namespace.endsWith("-") ? `${namespace}*` : namespace).join(", ")}).`, span);
53278
53315
  continue;
53279
53316
  }
@@ -53369,7 +53406,7 @@ var parseBrandCss = (css) => {
53369
53406
  readFontFace(node);
53370
53407
  continue;
53371
53408
  }
53372
- if (name === "media" && node.prelude !== null && BRAND_DARK_MEDIA.test(generate55(node.prelude).trim())) {
53409
+ if (name === "media" && node.prelude !== null && isDarkSchemeQuery(generate55(node.prelude))) {
53373
53410
  for (const child of node.block.children)
53374
53411
  if (child.type === "Atrule" && child.name.toLowerCase() === "theme")
53375
53412
  readTheme(child, dark, "dark");
@@ -53380,19 +53417,103 @@ var parseBrandCss = (css) => {
53380
53417
  }
53381
53418
  report("brand-css-unsupported", "A brand theme contains only @theme, a dark-mode @theme (@media (prefers-color-scheme: dark)), and @font-face.", spanOfNode(node));
53382
53419
  }
53383
- for (const property of Object.keys(dark))
53384
- if (darkVariableName(property) in variables)
53385
- report("brand-css-dark-conflict", `${darkVariableName(property)} and the dark-mode ${property} name the same theme variable; keep one.`, {
53386
- start: 0,
53387
- end: 0
53388
- });
53389
53420
  return diagnostics.length > 0 ? ae.err(diagnostics) : ae.ok({
53390
53421
  variables,
53391
53422
  dark,
53392
53423
  fontFaces
53393
53424
  });
53394
53425
  };
53395
- // ../../node_modules/.bun/@samva+markup@0.13.1+a19bcd7db259aaf7/node_modules/@samva/markup/dist/diagnostic-codes-DdZ4tka5.js
53426
+ var printDeclarations = (entries, indent) => Object.entries(entries).map(([property, value]) => `${indent}${property}: ${value};`).join(`
53427
+ `);
53428
+ var printFontFace = (face) => [
53429
+ "@font-face {",
53430
+ ` font-family: ${quoteFamily(face.family)};`,
53431
+ ` src: ${face.src};`,
53432
+ ...face.weight === undefined ? [] : [` font-weight: ${face.weight};`],
53433
+ ...face.style === undefined ? [] : [` font-style: ${face.style};`],
53434
+ ...face.display === undefined ? [] : [` font-display: ${face.display};`],
53435
+ ...face.unicodeRange === undefined ? [] : [` unicode-range: ${face.unicodeRange};`],
53436
+ "}"
53437
+ ].join(`
53438
+ `);
53439
+ var printBrandCss = (brand) => {
53440
+ const blocks = [
53441
+ ...brand.fontFaces.map(printFontFace),
53442
+ ...Object.keys(brand.variables).length === 0 ? [] : [`@theme {
53443
+ ${printDeclarations(brand.variables, " ")}
53444
+ }`],
53445
+ ...Object.keys(brand.dark).length === 0 ? [] : [`@media (prefers-color-scheme: dark) {
53446
+ @theme {
53447
+ ${printDeclarations(brand.dark, " ")}
53448
+ }
53449
+ }`]
53450
+ ];
53451
+ return blocks.length === 0 ? "" : `${blocks.join(`
53452
+
53453
+ `)}
53454
+ `;
53455
+ };
53456
+ var THEME_COLOR_ROLES = {
53457
+ background: "The page, behind everything.",
53458
+ foreground: "Text on the page.",
53459
+ card: "A surface that groups the message's content.",
53460
+ "card-foreground": "Text on a card.",
53461
+ primary: "The main action's fill (a button), and links.",
53462
+ "primary-foreground": "Text on a primary fill.",
53463
+ secondary: "A quieter action's fill.",
53464
+ "secondary-foreground": "Text on a secondary fill.",
53465
+ muted: "A subdued surface: a code block, a table header.",
53466
+ "muted-foreground": "Secondary text on the page, a card or a muted surface: labels, captions, the footer.",
53467
+ accent: "A highlighted surface: a callout, a selected row.",
53468
+ "accent-foreground": "Text on an accent surface.",
53469
+ border: "Rules, dividers and outlines."
53470
+ };
53471
+ var THEME_COLORS = Object.keys(THEME_COLOR_ROLES);
53472
+ var colorVariables = (colors) => Object.fromEntries(THEME_COLORS.map((name) => [`--color-${name}`, colors[name]]));
53473
+ var DEFAULT_THEME = {
53474
+ variables: {
53475
+ ...colorVariables({
53476
+ background: "#ffffff",
53477
+ foreground: "#0a0a0a",
53478
+ card: "#ffffff",
53479
+ "card-foreground": "#0a0a0a",
53480
+ primary: "#171717",
53481
+ "primary-foreground": "#fafafa",
53482
+ secondary: "#f5f5f5",
53483
+ "secondary-foreground": "#171717",
53484
+ muted: "#f5f5f5",
53485
+ "muted-foreground": "#737373",
53486
+ accent: "#f5f5f5",
53487
+ "accent-foreground": "#171717",
53488
+ border: "#e5e5e5"
53489
+ }),
53490
+ "--radius": "8px",
53491
+ "--radius-sm": "calc(var(--radius) - 4px)",
53492
+ "--radius-md": "calc(var(--radius) - 2px)",
53493
+ "--radius-lg": "var(--radius)",
53494
+ "--radius-xl": "calc(var(--radius) + 4px)",
53495
+ "--font-sans": '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
53496
+ "--font-heading": "var(--font-sans)"
53497
+ },
53498
+ dark: colorVariables({
53499
+ background: "#0a0a0a",
53500
+ foreground: "#fafafa",
53501
+ card: "#171717",
53502
+ "card-foreground": "#fafafa",
53503
+ primary: "#fafafa",
53504
+ "primary-foreground": "#171717",
53505
+ secondary: "#262626",
53506
+ "secondary-foreground": "#fafafa",
53507
+ muted: "#262626",
53508
+ "muted-foreground": "#a3a3a3",
53509
+ accent: "#262626",
53510
+ "accent-foreground": "#fafafa",
53511
+ border: "#2e2e2e"
53512
+ }),
53513
+ fontFaces: []
53514
+ };
53515
+ var DEFAULT_THEME_CSS = printBrandCss(DEFAULT_THEME);
53516
+ // ../../node_modules/.bun/@samva+markup@0.15.0+a19bcd7db259aaf7/node_modules/@samva/markup/dist/diagnostic-codes-UXwOO3A-.js
53396
53517
  var buildLineMap = (source) => {
53397
53518
  const lineStarts = [0];
53398
53519
  for (let index = 0;index < source.length; index++) {
@@ -53617,10 +53738,22 @@ var DIAGNOSTIC_CODES = codes2({
53617
53738
  summary: "<Columns per> is not a positive integer literal.",
53618
53739
  fix: "Write per={2}."
53619
53740
  },
53741
+ "invalid-columns-gap": {
53742
+ summary: "<Columns gap> is set on a Columns whose cells are repeated with each or rendered conditionally.",
53743
+ fix: "Write each Column out unconditionally, or pad the cells instead of using gap."
53744
+ },
53745
+ "invalid-columns-stack": {
53746
+ summary: "<Columns stack> is not a true or false literal.",
53747
+ fix: "Write stack={false}, or leave it out to stack on narrow screens."
53748
+ },
53620
53749
  "invalid-button-number": {
53621
53750
  summary: "A Button size prop is not a finite number.",
53622
53751
  fix: "Pass a finite number."
53623
53752
  },
53753
+ "invalid-number-prop": {
53754
+ summary: "A primitive's numeric prop, such as <Email gutter>, is not a finite number in range.",
53755
+ fix: "Pass a finite number the message allows, for example gutter={16}."
53756
+ },
53624
53757
  "unsupported-element": {
53625
53758
  summary: "An element outside the supported email vocabulary.",
53626
53759
  fix: "Use an element from the contract's element table."
@@ -53721,6 +53854,18 @@ var DIAGNOSTIC_CODES = codes2({
53721
53854
  summary: "A custom property is redefined inside a conditional at-rule.",
53722
53855
  fix: "Keep the override on a class rule."
53723
53856
  },
53857
+ "email-contrast": {
53858
+ summary: "Text, or a filled button against its surroundings, falls below the contrast ratio in light or dark mode (below 1.5:1 a button is indistinguishable from its surface; WCAG 1.4.11 asks 3:1).",
53859
+ fix: "Pair background and text tokens (bg-primary with text-primary-foreground), give the theme's dark-mode @theme a value for the token, or choose colors that reach the required ratio.",
53860
+ upgrade: [
53861
+ "Move the project's theme onto the theme vocabulary with one dark-mode block. In starter.css and theme.css, rename each color token to the vocabulary name for its role (reference.md, Theme): a page color to --color-background, the main text to --color-foreground, a surface to --color-card with --color-card-foreground, the brand or action color to --color-primary with --color-primary-foreground, subdued text to --color-muted-foreground, a highlight to --color-accent with --color-accent-foreground, rules to --color-border, body and heading fonts to --font-sans and --font-heading, and a corner radius to --radius. Keep every value.",
53862
+ "Move each --color-<name>-dark value into @media (prefers-color-scheme: dark) { @theme { --color-<name>: <value>; } } under the renamed token, and delete the -dark tokens. In every template and partial, rename the utilities to match (bg-surface to bg-card, text-muted to text-muted-foreground, and so on) and delete each dark:\u2026-dark class whose token now has a dark value; keep a dark: class only where it picks a different token in dark mode.",
53863
+ "Then fix what email-contrast still reports at its location: pair each fill with its -foreground token, or give the dark block a value that reaches the ratio. Do not weaken the design to pass: keep the brand's colors and adjust the paired token.",
53864
+ "Compile every entry and render every fixture in light and dark mode; confirm the light render is unchanged apart from fixes. Resolve errors before requesting one saved version for the whole project, approved by the user; do not publish."
53865
+ ].join(`
53866
+
53867
+ `)
53868
+ },
53724
53869
  "email-font-fallback": {
53725
53870
  summary: "A font stack names a project font without a generic fallback.",
53726
53871
  fix: "End the stack with sans-serif, serif or monospace."
@@ -54206,6 +54351,7 @@ var CampaignLatestRun = /* @__PURE__ */ Struct({
54206
54351
  dispatchedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54207
54352
  failedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54208
54353
  skippedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54354
+ pendingCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54209
54355
  estimatedCompletionAt: Union2([String4, Null2])
54210
54356
  }).annotate({ identifier: "CampaignLatestRun" });
54211
54357
  var CampaignRecipientPage = /* @__PURE__ */ Struct({
@@ -54304,11 +54450,14 @@ var CampaignRun = /* @__PURE__ */ Struct({
54304
54450
  dispatchedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54305
54451
  failedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54306
54452
  skippedCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54453
+ pendingCount: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
54307
54454
  skippedReasons: ArraySchema(CampaignRunSkippedReason),
54308
54455
  estimatedCompletionAt: Union2([String4, Null2]),
54309
54456
  failureReason: Union2([String4, Null2]),
54310
54457
  holdReason: Union2([String4, Null2]),
54311
54458
  holdDomainId: Union2([EmailDomainId2, Null2]),
54459
+ holdUntil: Union2([String4, Null2]),
54460
+ capacityWarning: Union2([String4, Null2]),
54312
54461
  lastContactId: Union2([String4, Null2]),
54313
54462
  createdBy: Union2([String4, Null2]),
54314
54463
  actorEmail: String4,
@@ -54659,6 +54808,23 @@ var resumeRun = /* @__PURE__ */ make42("campaignsResumeRun");
54659
54808
  var cancelRun = /* @__PURE__ */ make42("campaignsCancelRun");
54660
54809
  var listRecipients = /* @__PURE__ */ make42("campaignsListRecipients");
54661
54810
 
54811
+ // src/lib/run-hold.ts
54812
+ var WHATSAPP_MESSAGING_LIMIT_HOLD = "whatsapp_messaging_limit";
54813
+ var holdLabel = (reason) => reason === null || reason === undefined ? null : reason === WHATSAPP_MESSAGING_LIMIT_HOLD ? `${reason} (the messaging limit its business portfolio shares across numbers; the run resumes on its own, sooner if Meta raises the limit)` : reason;
54814
+ var holdFields = (run) => [
54815
+ ["Held", holdLabel(run.holdReason)],
54816
+ ["Resumes by", run.holdUntil]
54817
+ ];
54818
+ var campaignPacingFields = (run, options) => [
54819
+ ...holdFields(run),
54820
+ [
54821
+ "Pending",
54822
+ run.pendingCount !== undefined && (run.pendingCount > 0 || run.holdReason != null) ? String(run.pendingCount) : null
54823
+ ],
54824
+ ["Est. completion", run.estimatedCompletionAt],
54825
+ ...options.warning ? [["Capacity warning", run.capacityWarning]] : []
54826
+ ];
54827
+
54662
54828
  // src/lib/template-reference.ts
54663
54829
  var publicationIdFlag = String9("publication-id").pipe(withDescription2("Pin one exact publication, so nothing published later changes what this renders"), optional4);
54664
54830
  var inputContractIdFlag = String9("input-contract-id").pipe(withDescription2("Follow the newest publication that still validates input identically to this contract"), optional4);
@@ -55131,7 +55297,7 @@ var archive2 = make29("archive", {
55131
55297
  ["Definition", data.definition.state]
55132
55298
  ]);
55133
55299
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
55134
- var printRun = (run) => printFields([
55300
+ var printRun = (run, options = { warning: true }) => printFields([
55135
55301
  ["Run ID", run.id],
55136
55302
  ["Campaign", run.campaignId],
55137
55303
  ["Status", run.status],
@@ -55146,8 +55312,10 @@ var printRun = (run) => printFields([
55146
55312
  "Skipped by reason",
55147
55313
  (run.skippedReasons ?? []).map(({ reason, count }) => `${reason} ${count}`).join(", ")
55148
55314
  ],
55149
- ["Failure", run.failureReason]
55315
+ ["Failure", run.failureReason],
55316
+ ...campaignPacingFields(run, options)
55150
55317
  ]);
55318
+ var warnCapacity = (run) => run.capacityWarning ? error(`Warning: ${run.capacityWarning}`) : void_3;
55151
55319
  var runCreate = make29("create", {
55152
55320
  campaignId: String10("campaignId"),
55153
55321
  at: atFlag,
@@ -55186,7 +55354,8 @@ var runCreate = make29("create", {
55186
55354
  return yield* printJson(data);
55187
55355
  yield* logInfo(`Run scheduled.
55188
55356
  `);
55189
- yield* printRun(data);
55357
+ yield* printRun(data, { warning: false });
55358
+ yield* warnCapacity(data);
55190
55359
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
55191
55360
  var runList = make29("list", {
55192
55361
  campaignId: String10("campaignId"),
@@ -67136,6 +67305,18 @@ class WhatsAppTemplateIneligibleError extends TaggedError3()("WhatsAppTemplateIn
67136
67305
  message: String4
67137
67306
  }, { httpApiStatus: 422 }) {
67138
67307
  }
67308
+
67309
+ class WhatsAppSetupLinkUnavailableError extends TaggedError3()("WhatsAppSetupLinkUnavailableError", {
67310
+ reason: Literals(["completed", "revoked", "expired"]),
67311
+ message: String4
67312
+ }, { httpApiStatus: 410 }) {
67313
+ static new(reason) {
67314
+ return new WhatsAppSetupLinkUnavailableError({
67315
+ reason,
67316
+ 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."
67317
+ });
67318
+ }
67319
+ }
67139
67320
  var EmailChannelErrorReason = Literals([
67140
67321
  "transport",
67141
67322
  "rejected",
@@ -67678,6 +67859,8 @@ var JourneyRun = /* @__PURE__ */ Struct({
67678
67859
  exitReason: Union2([JourneyRunExitReason, Null2]),
67679
67860
  failureReason: Union2([String4, Null2]),
67680
67861
  currentNodeId: Union2([JourneyNodeId, Null2]),
67862
+ holdReason: Union2([String4, Null2]),
67863
+ holdUntil: Union2([String4, Null2]),
67681
67864
  trigger: JourneyRunTrigger,
67682
67865
  enteredAt: String4,
67683
67866
  endedAt: Union2([String4, Null2])
@@ -67701,6 +67884,8 @@ var JourneyRunDetail = /* @__PURE__ */ Struct({
67701
67884
  exitReason: Union2([JourneyRunExitReason, Null2]),
67702
67885
  failureReason: Union2([String4, Null2]),
67703
67886
  currentNodeId: Union2([JourneyNodeId, Null2]),
67887
+ holdReason: Union2([String4, Null2]),
67888
+ holdUntil: Union2([String4, Null2]),
67704
67889
  trigger: JourneyRunTrigger,
67705
67890
  enteredAt: String4,
67706
67891
  endedAt: Union2([String4, Null2]),
@@ -68329,6 +68514,7 @@ var printRun2 = (run) => printFields([
68329
68514
  ["Exit reason", run.exitReason],
68330
68515
  ["Failure", run.failureReason],
68331
68516
  ["Current node", run.currentNodeId],
68517
+ ...holdFields(run),
68332
68518
  ["Entered", run.enteredAt],
68333
68519
  ["Ended", run.endedAt]
68334
68520
  ]);
@@ -73851,7 +74037,8 @@ var TemplateFamilyId2 = /* @__PURE__ */ String4.check(isPattern2(/* @__PURE__ */
73851
74037
  var VersionCursor2 = /* @__PURE__ */ String4.annotate({ identifier: "VersionCursor" });
73852
74038
  var BrandFooterBlock = /* @__PURE__ */ Struct({
73853
74039
  html: String4,
73854
- text: String4
74040
+ text: String4,
74041
+ headCss: optionalKey2(Union2([String4, Null2]))
73855
74042
  }).annotate({ identifier: "BrandFooterBlock" });
73856
74043
  var TemplatePublicationRender = /* @__PURE__ */ Struct({
73857
74044
  subject: String4,
@@ -73894,7 +74081,8 @@ var TemplateSummary = /* @__PURE__ */ Struct({
73894
74081
  name: String4,
73895
74082
  channel: Literal2("email"),
73896
74083
  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." }),
74084
+ 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." }),
74085
+ 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
74086
  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
74087
  status: Union2([
73900
74088
  Literal2("draft"),
@@ -73919,7 +74107,8 @@ var Template = /* @__PURE__ */ Struct({
73919
74107
  name: String4,
73920
74108
  channel: Literal2("email"),
73921
74109
  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." }),
74110
+ 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." }),
74111
+ 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
74112
  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
74113
  status: Union2([
73925
74114
  Literal2("draft"),
@@ -73977,6 +74166,14 @@ var TemplatePage = /* @__PURE__ */ Struct({
73977
74166
  items: ArraySchema(TemplateSummary),
73978
74167
  nextCursor: Union2([String4, Null2])
73979
74168
  }).annotate({ identifier: "TemplatePage" });
74169
+ var TemplateFromWebsite = /* @__PURE__ */ Struct({
74170
+ template: Template,
74171
+ website: Struct({
74172
+ url: String4.annotate({ description: "The address Samva checked, normalized." }),
74173
+ host: String4
74174
+ }),
74175
+ ask: String4.annotate({ description: "The request to send the template assistant in the editor. The assistant reads the site, rebuilds the project's theme and blocks to match it, and proposes a brand that changes nothing until the customer applies it." })
74176
+ }).annotate({ identifier: "TemplateFromWebsite" });
73980
74177
  var TemplateFamilyPage = /* @__PURE__ */ Struct({
73981
74178
  items: ArraySchema(TemplateFamily),
73982
74179
  nextCursor: Union2([String4, Null2])
@@ -74004,7 +74201,8 @@ var TemplatesListQuery = /* @__PURE__ */ Struct({
74004
74201
  ]), Null2])),
74005
74202
  search: optionalKey2(Union2([String4, Null2])),
74006
74203
  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." }))
74204
+ 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." })),
74205
+ 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
74206
  });
74009
74207
  var TemplatesList200 = TemplatePage;
74010
74208
  var TemplatesList401 = UnauthorizedError2;
@@ -74030,6 +74228,7 @@ var TemplatesCreateRequestJson = /* @__PURE__ */ Struct({
74030
74228
  ]), Null2]), Null2])),
74031
74229
  isActive: optionalKey2(Union2([Boolean3, Null2])),
74032
74230
  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." })),
74231
+ 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
74232
  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
74233
  });
74035
74234
  var TemplatesCreate201 = Template;
@@ -74073,7 +74272,8 @@ var TemplatesUpdateRequestJson = /* @__PURE__ */ Struct({ data: Struct({
74073
74272
  ]), Null2]), Null2])),
74074
74273
  fromDefault: optionalKey2(Union2([Union2([TemplateFromDefault, Null2]), Null2])),
74075
74274
  replyToDefault: optionalKey2(Union2([Union2([ArraySchema(String4), Null2]), Null2])),
74076
- isActive: optionalKey2(Union2([Boolean3, Null2]))
74275
+ isActive: optionalKey2(Union2([Boolean3, Null2])),
74276
+ 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
74277
  }) });
74078
74278
  var TemplatesUpdate200 = Template;
74079
74279
  var TemplatesUpdate401 = UnauthorizedError2;
@@ -75621,6 +75821,20 @@ var TemplatesCreateFamilyFromTemplate409 = ConflictError2;
75621
75821
  var TemplatesCreateFamilyFromTemplate422 = ValidationError2;
75622
75822
  var TemplatesCreateFamilyFromTemplate500 = InternalError2;
75623
75823
  var TemplatesCreateFamilyFromTemplate504 = GatewayTimeoutError2;
75824
+ var TemplatesCreateFromWebsiteRequestJson = /* @__PURE__ */ Struct({
75825
+ url: String4.annotate({ description: "The website whose look the template should match: a public https address, or a bare domain such as `example.com`." }).check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxCodePoints(2048).annotate({ expected: "a string with at most 2048 code points" })).check(isMaxLength(2048).annotate({ expected: "a value with a length of at most 2048" })),
75826
+ name: optionalKey2(Union2([String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxCodePoints(200).annotate({ expected: "a string with at most 200 code points" })).check(isMaxLength(200).annotate({ expected: "a value with a length of at most 200" })), Null2])),
75827
+ namespaceId: optionalKey2(Union2([NamespaceId2, Null2]).annotate({ description: "The namespace the new template project belongs to. Omit for organization level. A namespace-scoped key always creates in its own namespace." })),
75828
+ idempotencyKey: String4.annotate({ description: "Repeating a request with the same key, address, name, and namespace returns the project the first one created, even if the site has since stopped answering. The same key with another address, name, or namespace is refused with a 409." }).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" }))
75829
+ });
75830
+ var TemplatesCreateFromWebsite201 = TemplateFromWebsite;
75831
+ var TemplatesCreateFromWebsite401 = UnauthorizedError2;
75832
+ var TemplatesCreateFromWebsite403 = ForbiddenError2;
75833
+ var TemplatesCreateFromWebsite404 = ResourceNotFoundError2;
75834
+ var TemplatesCreateFromWebsite409 = ConflictError2;
75835
+ var TemplatesCreateFromWebsite422 = ValidationError2;
75836
+ var TemplatesCreateFromWebsite500 = InternalError2;
75837
+ var TemplatesCreateFromWebsite504 = GatewayTimeoutError2;
75624
75838
  var TemplatesAddEntryPathParams = /* @__PURE__ */ Struct({ id: TemplateProjectId2 });
75625
75839
  var TemplatesAddEntryRequestJson = /* @__PURE__ */ Struct({
75626
75840
  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" })),
@@ -76033,7 +76247,19 @@ var TemplatesGroup = class extends make37("Templates").add(get5("templatesList",
76033
76247
  TemplatesCreateFamilyFromTemplate500,
76034
76248
  TemplatesCreateFamilyFromTemplate504.pipe(status(504))
76035
76249
  ]
76036
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.createFamilyFromTemplate").annotate(Summary, "Create a family from a template").annotate(Description, "Creates a template family containing the specified template and returns the new family with status 201."), post("templatesAddEntry", "/v1/templates/projects/:id/entries", {
76250
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.createFamilyFromTemplate").annotate(Summary, "Create a family from a template").annotate(Description, "Creates a template family containing the specified template and returns the new family with status 201."), post("templatesCreateFromWebsite", "/v1/templates/from-website", {
76251
+ payload: TemplatesCreateFromWebsiteRequestJson,
76252
+ success: TemplatesCreateFromWebsite201.pipe(status(201)),
76253
+ error: [
76254
+ TemplatesCreateFromWebsite401.pipe(status(401)),
76255
+ TemplatesCreateFromWebsite403.pipe(status(403)),
76256
+ TemplatesCreateFromWebsite404.pipe(status(404)),
76257
+ TemplatesCreateFromWebsite409.pipe(status(409)),
76258
+ TemplatesCreateFromWebsite422.pipe(status(422)),
76259
+ TemplatesCreateFromWebsite500,
76260
+ TemplatesCreateFromWebsite504.pipe(status(504))
76261
+ ]
76262
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.createFromWebsite").annotate(Summary, "Create a template from a website").annotate(Description, "Checks that the website is a public page Samva can read, creates an email template project from Samva's canonical TSX starter, and returns the template with status 201 and the request that makes the template assistant match the site. The assistant runs when the request is sent in the editor; it rebuilds the project's theme and blocks from the site and proposes a brand that applies only after the customer approves it. A 422 names why the site cannot be matched: not a web address, not public, no answer, a missing page, refused automated visits, or not a web page."), post("templatesAddEntry", "/v1/templates/projects/:id/entries", {
76037
76263
  params: TemplatesAddEntryPathParams,
76038
76264
  payload: TemplatesAddEntryRequestJson,
76039
76265
  success: TemplatesAddEntry201.pipe(status(201)),
@@ -76053,6 +76279,7 @@ var TemplatesGroup = class extends make37("Templates").add(get5("templatesList",
76053
76279
  var make54 = /* @__PURE__ */ forGroup(TemplatesGroup);
76054
76280
  var list26 = /* @__PURE__ */ make54("templatesList");
76055
76281
  var create15 = /* @__PURE__ */ make54("templatesCreate");
76282
+ var createFromWebsite = /* @__PURE__ */ make54("templatesCreateFromWebsite");
76056
76283
  var addEntry = /* @__PURE__ */ make54("templatesAddEntry");
76057
76284
  var getById = /* @__PURE__ */ make54("templatesGetById");
76058
76285
  var remove13 = /* @__PURE__ */ make54("templatesRemove");
@@ -76652,6 +76879,66 @@ var formatReportedDiagnostic = (diagnostic) => {
76652
76879
  Upgrade: ${diagnostic.upgrade}`;
76653
76880
  };
76654
76881
 
76882
+ // src/commands/templates/from-website-command.ts
76883
+ var webOriginForApi = (apiUrl) => {
76884
+ if (!URL.canParse(apiUrl))
76885
+ return null;
76886
+ const url = new URL(apiUrl);
76887
+ if (!url.hostname.startsWith("api."))
76888
+ return null;
76889
+ url.hostname = url.hostname.slice("api.".length);
76890
+ return url.origin;
76891
+ };
76892
+ var editorPath = (orgSlug, templateId, ask) => `/dashboard/${encodeURIComponent(orgSlug)}/templates/${encodeURIComponent(templateId)}/edit?ask=${encodeURIComponent(ask)}`;
76893
+ var urlArgument = String10("url").pipe(withDescription4("The website to match: a public https address or a bare domain"));
76894
+ var fromWebsiteCommand = make29("from-website", {
76895
+ url: urlArgument,
76896
+ name: String9("name").pipe(withDescription2("Name for the new template; defaults to one derived from the site"), optional4),
76897
+ namespace: namespaceFlag("create"),
76898
+ idempotencyKey: String9("idempotency-key").pipe(withDescription2("Key (1-255 characters) so a retry returns the original template; generated per run when omitted"), optional4),
76899
+ json: jsonFlag
76900
+ }).pipe(withDescription3("Create a template project from a website, then open the editor to have the assistant match its brand"), withExamples([
76901
+ {
76902
+ command: "samva templates from-website example.com",
76903
+ description: "Check the site, create the project, and print the editor link"
76904
+ },
76905
+ {
76906
+ command: 'samva templates from-website https://example.com --name "Example welcome" --json',
76907
+ description: "Machine-readable result with the template, website, ask, and editor link"
76908
+ }
76909
+ ]), withHandler(({ url, name, namespace, idempotencyKey, json }) => gen2(function* () {
76910
+ const key = getOrUndefined(idempotencyKey);
76911
+ if (key !== undefined && (key.length < 1 || key.length > 255))
76912
+ return yield* new SamvaCliError({
76913
+ message: "--idempotency-key must contain between 1 and 255 characters."
76914
+ });
76915
+ const client = yield* getClientProvider;
76916
+ const context = yield* resolveExecutionContext;
76917
+ const orgSlug = context.apiKey ? (yield* get25().pipe(client.provide, mapError2((error) => toCliApiError("Could not look up the organization", error)))).organization.slug : context.orgSlug;
76918
+ const result = yield* createFromWebsite({
76919
+ url,
76920
+ ...isSome2(name) ? { name: name.value } : {},
76921
+ ...namespaceField(namespace),
76922
+ idempotencyKey: key ?? crypto.randomUUID()
76923
+ }).pipe(client.provide, mapError2((error) => toCliApiError("Could not create a template from the website", error)));
76924
+ const path = orgSlug === undefined ? null : editorPath(orgSlug, result.template.id, result.ask);
76925
+ const origin = webOriginForApi(context.apiUrl);
76926
+ const editorUrl = path !== null && origin !== null ? `${origin}${path}` : null;
76927
+ if (json)
76928
+ return yield* printJson({
76929
+ template: { id: result.template.id, name: result.template.name },
76930
+ website: result.website,
76931
+ ask: result.ask,
76932
+ editorUrl
76933
+ });
76934
+ yield* printFields([
76935
+ ["Template", result.template.id],
76936
+ ["Website", result.website.host],
76937
+ ...editorUrl !== null ? [["Editor", editorUrl]] : [["Ask", result.ask]]
76938
+ ]);
76939
+ yield* log(editorUrl !== null ? "The brand proposal applies only when you approve it in the editor." : "Open the template in the dashboard editor and send the ask above; the brand proposal applies only when you approve it there.");
76940
+ }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
76941
+
76655
76942
  // src/commands/templates/local-build.ts
76656
76943
  import { resolve as resolve9 } from "path";
76657
76944
  var buildDir = (dir, assetBase) => runToolchain({
@@ -78776,13 +79063,18 @@ var build3 = make29("build", {
78776
79063
  yield* logInfo(`Built ${built.files.length} template file(s) with compiler ${artifact.compilerVersion} to ${target}`);
78777
79064
  }
78778
79065
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79066
+ var sharedFilterFlag = Literals3("shared", ["true", "false"]).pipe(withDescription2("Only templates shared with every namespace (true), or only unshared ones (false)"), optional4);
78779
79067
  var listRemote = make29("list", {
78780
79068
  cursor: String9("cursor").pipe(withDescription2("Resume after this nextCursor from a previous page"), optional4),
78781
79069
  limit: Int5("limit").pipe(withDescription2("Items per page"), optional4),
78782
79070
  namespace: namespaceFlag("list"),
79071
+ shared: sharedFilterFlag,
78783
79072
  all: Boolean6("all").pipe(withDescription2("Traverse every page (requires --jsonl)"), withDefault5(false)),
78784
79073
  json: jsonFlag
78785
- }).pipe(withDescription3("List remote templates"), withExamples([{ command: "samva templates list --json" }]), withHandler(({ cursor, limit, namespace, all, json }) => gen2(function* () {
79074
+ }).pipe(withDescription3("List remote templates"), withExamples([
79075
+ { command: "samva templates list --json" },
79076
+ { command: "samva templates list --shared true --json" }
79077
+ ]), withHandler(({ cursor, limit, namespace, shared, all, json }) => gen2(function* () {
78786
79078
  if (all && !isJsonLinesRequested())
78787
79079
  return yield* new CliUsageError({
78788
79080
  code: "all_requires_jsonl",
@@ -78794,7 +79086,8 @@ var listRemote = make29("list", {
78794
79086
  const query = {
78795
79087
  cursor: cursorValue,
78796
79088
  limit: limitValue?.toString(),
78797
- ...namespaceField(namespace)
79089
+ ...namespaceField(namespace),
79090
+ ...match(shared, { onNone: () => ({}), onSome: (value) => ({ shared: value }) })
78798
79091
  };
78799
79092
  if (all) {
78800
79093
  let nextCursor = cursorValue;
@@ -78821,7 +79114,8 @@ var listRemote = make29("list", {
78821
79114
  ["Name", item.name],
78822
79115
  ["Slug", item.slug],
78823
79116
  ["Status", item.status],
78824
- ["Namespace", item.namespaceId ?? "organization"]
79117
+ ["Namespace", item.namespaceId ?? "organization"],
79118
+ ["Shared", item.shared ? "yes" : "no"]
78825
79119
  ]);
78826
79120
  if (data.nextCursor !== null)
78827
79121
  yield* printFields([["Next cursor", data.nextCursor]]);
@@ -78836,20 +79130,35 @@ var getRemote = make29("get", { id: templateIdArgument2, json: jsonFlag }).pipe(
78836
79130
  var createRemote = make29("create", {
78837
79131
  data: String9("data").pipe(withDescription2("Complete template JSON payload")),
78838
79132
  namespace: namespaceFlag("create"),
79133
+ shared: Boolean6("shared").pipe(withDescription2("Share the new organization-level project with every namespace; namespace-scoped keys read and send it but cannot change it"), withDefault5(false)),
78839
79134
  dryRun: dryRunFlag6,
78840
79135
  json: jsonFlag
78841
79136
  }).pipe(withDescription3("Create a remote template from JSON"), withExamples([
78842
79137
  {
78843
79138
  command: `samva templates create --data '{"name":"Welcome","projectName":"welcome-email","idempotencyKey":"welcome-email-v1"}' --dry-run`
78844
79139
  }
78845
- ]), withHandler(({ data, namespace, dryRun, json }) => gen2(function* () {
79140
+ ]), withHandler(({ data, namespace, shared, dryRun, json }) => gen2(function* () {
78846
79141
  const decoded = yield* decodePayload(data, "--data", CreateTemplatePayload);
79142
+ if (shared && decoded.shared === false)
79143
+ return yield* new CliUsageError({
79144
+ code: "shared_conflict",
79145
+ message: "--shared and shared: false in --data disagree."
79146
+ });
79147
+ if (shared && (isSome2(namespace) || decoded.namespaceId != null))
79148
+ return yield* new CliUsageError({
79149
+ code: "shared_namespace_conflict",
79150
+ message: "--shared shares an organization-level project; it cannot have a namespace."
79151
+ });
78847
79152
  if (isSome2(namespace) && decoded.namespaceId != null && decoded.namespaceId !== namespace.value)
78848
79153
  return yield* new CliUsageError({
78849
79154
  code: "namespace_conflict",
78850
79155
  message: "--namespace and the namespaceId in --data name different namespaces."
78851
79156
  });
78852
- const payload = { ...decoded, ...namespaceField(namespace) };
79157
+ const payload = {
79158
+ ...decoded,
79159
+ ...namespaceField(namespace),
79160
+ ...shared ? { shared: true } : {}
79161
+ };
78853
79162
  if (dryRun)
78854
79163
  return yield* json ? printJson({ dryRun: true, action: "templates.create", request: payload }) : printFields([
78855
79164
  ["Action", "templates.create"],
@@ -78864,14 +79173,22 @@ var createRemote = make29("create", {
78864
79173
  var updateRemote = make29("update", {
78865
79174
  id: templateIdArgument2,
78866
79175
  data: String9("data").pipe(withDescription2("JSON object of template fields to update")),
79176
+ shared: Literals3("shared", ["true", "false"]).pipe(withDescription2("Share the template's organization-level project with every namespace (true) or stop sharing it (false); applies to every entry in the project"), optional4),
78867
79177
  dryRun: dryRunFlag6,
78868
79178
  json: jsonFlag
78869
79179
  }).pipe(withDescription3("Update a remote template from JSON"), withExamples([
78870
79180
  {
78871
79181
  command: `samva templates update tmpl_abc123 --data '{"name":"Welcome back"}' --dry-run`
78872
79182
  }
78873
- ]), withHandler(({ id, data, dryRun, json }) => gen2(function* () {
78874
- const changes = yield* parseObject(data, "--data");
79183
+ ]), withHandler(({ id, data, shared, dryRun, json }) => gen2(function* () {
79184
+ const parsed = yield* parseObject(data, "--data");
79185
+ const sharedValue = map(shared, (value) => value === "true");
79186
+ if (isSome2(sharedValue) && parsed.shared !== undefined && parsed.shared !== sharedValue.value)
79187
+ return yield* new CliUsageError({
79188
+ code: "shared_conflict",
79189
+ message: "--shared and shared in --data disagree."
79190
+ });
79191
+ const changes = isSome2(sharedValue) ? { ...parsed, shared: sharedValue.value } : parsed;
78875
79192
  const payload = yield* decodePayload(JSON.stringify({ data: changes }), "--data", UpdateTemplatePayload);
78876
79193
  if (dryRun)
78877
79194
  return yield* json ? printJson({ dryRun: true, action: "templates.update", id, request: payload }) : printFields([
@@ -78983,6 +79300,10 @@ var templatesCommand = make29("templates").pipe(withDescription3("Create, author
78983
79300
  command: "samva templates clone tmpl_abc123 --dir welcome-email",
78984
79301
  description: "Clone an existing project with its repository credential helper"
78985
79302
  },
79303
+ {
79304
+ command: "samva templates from-website example.com",
79305
+ description: "Create a project from a website and print the editor link that matches its brand"
79306
+ },
78986
79307
  {
78987
79308
  command: "samva templates dev",
78988
79309
  description: "Start the local visual editor for templates/ and emails/"
@@ -79023,6 +79344,7 @@ var templatesCommand = make29("templates").pipe(withDescription3("Create, author
79023
79344
  createRemote,
79024
79345
  updateRemote,
79025
79346
  removeRemote,
79347
+ fromWebsiteCommand,
79026
79348
  devCommand,
79027
79349
  check5,
79028
79350
  renderCommand,
@@ -79060,6 +79382,11 @@ var WebhookDailyDeliveries = /* @__PURE__ */ Struct({
79060
79382
  exhausted: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
79061
79383
  cancelled: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" }))
79062
79384
  }).annotate({ identifier: "WebhookDailyDeliveries" });
79385
+ var WebhookSigningSecret = /* @__PURE__ */ Struct({
79386
+ secret: SensitiveString,
79387
+ createdAt: String4,
79388
+ previousExpiresAt: Union2([String4, Null2])
79389
+ }).annotate({ identifier: "WebhookSigningSecret" });
79063
79390
  var WebhookEndpoint = /* @__PURE__ */ Struct({
79064
79391
  id: WebhookEndpointId2,
79065
79392
  name: String4,
@@ -79090,6 +79417,9 @@ var WebhookEndpoint = /* @__PURE__ */ Struct({
79090
79417
  Literal2("webhook.test"),
79091
79418
  Literal2("domain.updated"),
79092
79419
  Literal2("domain.warming.updated"),
79420
+ Literal2("template.registration.updated"),
79421
+ Literal2("flow.registration.updated"),
79422
+ Literal2("whatsapp.number.connected"),
79093
79423
  Literal2("mailbox.message.received"),
79094
79424
  Literal2("mailbox.message.sent"),
79095
79425
  Literal2("mailbox.action.pending_approval"),
@@ -79139,6 +79469,9 @@ var WebhookEndpointListItem = /* @__PURE__ */ Struct({
79139
79469
  Literal2("webhook.test"),
79140
79470
  Literal2("domain.updated"),
79141
79471
  Literal2("domain.warming.updated"),
79472
+ Literal2("template.registration.updated"),
79473
+ Literal2("flow.registration.updated"),
79474
+ Literal2("whatsapp.number.connected"),
79142
79475
  Literal2("mailbox.message.received"),
79143
79476
  Literal2("mailbox.message.sent"),
79144
79477
  Literal2("mailbox.action.pending_approval"),
@@ -79253,6 +79586,9 @@ var WebhooksCreateRequestJson = /* @__PURE__ */ Struct({
79253
79586
  Literal2("webhook.test"),
79254
79587
  Literal2("domain.updated"),
79255
79588
  Literal2("domain.warming.updated"),
79589
+ Literal2("template.registration.updated"),
79590
+ Literal2("flow.registration.updated"),
79591
+ Literal2("whatsapp.number.connected"),
79256
79592
  Literal2("mailbox.message.received"),
79257
79593
  Literal2("mailbox.message.sent"),
79258
79594
  Literal2("mailbox.action.pending_approval"),
@@ -79328,6 +79664,9 @@ var WebhooksUpdateRequestJson = /* @__PURE__ */ Struct({
79328
79664
  Literal2("webhook.test"),
79329
79665
  Literal2("domain.updated"),
79330
79666
  Literal2("domain.warming.updated"),
79667
+ Literal2("template.registration.updated"),
79668
+ Literal2("flow.registration.updated"),
79669
+ Literal2("whatsapp.number.connected"),
79331
79670
  Literal2("mailbox.message.received"),
79332
79671
  Literal2("mailbox.message.sent"),
79333
79672
  Literal2("mailbox.action.pending_approval"),
@@ -79388,6 +79727,14 @@ var WebhooksRegenerateSecret404 = ResourceNotFoundError2;
79388
79727
  var WebhooksRegenerateSecret422 = ValidationError2;
79389
79728
  var WebhooksRegenerateSecret500 = InternalError2;
79390
79729
  var WebhooksRegenerateSecret504 = GatewayTimeoutError2;
79730
+ var WebhooksGetSecretPathParams = /* @__PURE__ */ Struct({ id: WebhookEndpointId2 });
79731
+ var WebhooksGetSecret200 = WebhookSigningSecret;
79732
+ var WebhooksGetSecret401 = UnauthorizedError2;
79733
+ var WebhooksGetSecret403 = ForbiddenError2;
79734
+ var WebhooksGetSecret404 = ResourceNotFoundError2;
79735
+ var WebhooksGetSecret422 = ValidationError2;
79736
+ var WebhooksGetSecret500 = InternalError2;
79737
+ var WebhooksGetSecret504 = GatewayTimeoutError2;
79391
79738
  var WebhooksGetStatsPathParams = /* @__PURE__ */ Struct({ id: WebhookEndpointId2 });
79392
79739
  var WebhooksGetStatsQuery = /* @__PURE__ */ Struct({
79393
79740
  after: optionalKey2(Union2([String4, Null2])),
@@ -79437,7 +79784,7 @@ var WebhooksGroup = class extends make37("Webhooks").add(get5("webhooksList", "/
79437
79784
  WebhooksCreate502.pipe(status(502)),
79438
79785
  WebhooksCreate504.pipe(status(504))
79439
79786
  ]
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", {
79787
+ }).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
79788
  params: WebhooksGetPathParams,
79442
79789
  success: WebhooksGet200,
79443
79790
  error: [
@@ -79508,7 +79855,18 @@ var WebhooksGroup = class extends make37("Webhooks").add(get5("webhooksList", "/
79508
79855
  WebhooksRegenerateSecret500,
79509
79856
  WebhooksRegenerateSecret504.pipe(status(504))
79510
79857
  ]
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", {
79858
+ }).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", {
79859
+ params: WebhooksGetSecretPathParams,
79860
+ success: WebhooksGetSecret200,
79861
+ error: [
79862
+ WebhooksGetSecret401.pipe(status(401)),
79863
+ WebhooksGetSecret403.pipe(status(403)),
79864
+ WebhooksGetSecret404.pipe(status(404)),
79865
+ WebhooksGetSecret422.pipe(status(422)),
79866
+ WebhooksGetSecret500,
79867
+ WebhooksGetSecret504.pipe(status(504))
79868
+ ]
79869
+ }).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
79870
  params: WebhooksGetStatsPathParams,
79513
79871
  query: WebhooksGetStatsQuery,
79514
79872
  success: WebhooksGetStats200,
@@ -79539,6 +79897,7 @@ var WebhooksGroup = class extends make37("Webhooks").add(get5("webhooksList", "/
79539
79897
  var make55 = /* @__PURE__ */ forGroup(WebhooksGroup);
79540
79898
  var list27 = /* @__PURE__ */ make55("webhooksList");
79541
79899
  var create16 = /* @__PURE__ */ make55("webhooksCreate");
79900
+ var getSecret = /* @__PURE__ */ make55("webhooksGetSecret");
79542
79901
  var get28 = /* @__PURE__ */ make55("webhooksGet");
79543
79902
  var remove14 = /* @__PURE__ */ make55("webhooksRemove");
79544
79903
  var update13 = /* @__PURE__ */ make55("webhooksUpdate");
@@ -79628,6 +79987,18 @@ var get29 = make29("get", { id: idArgument4, json: jsonFlag }).pipe(withDescript
79628
79987
  ["URL", data.url]
79629
79988
  ]);
79630
79989
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79990
+ 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* () {
79991
+ const client = yield* getClientProvider;
79992
+ const data = yield* getSecret({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not read webhook signing secret", error)));
79993
+ if (json)
79994
+ return yield* printJson({ ...data, secret: value2(data.secret) });
79995
+ yield* printFields([
79996
+ ["Signing secret", value2(data.secret)],
79997
+ ["Created at", data.createdAt]
79998
+ ]);
79999
+ if (data.previousExpiresAt !== null)
80000
+ yield* log(`The previous secret keeps verifying until ${data.previousExpiresAt}.`);
80001
+ }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79631
80002
  var logs = make29("logs", {
79632
80003
  id: idArgument4,
79633
80004
  limit: Int5("limit").pipe(optional4),
@@ -79785,15 +80156,15 @@ var rotate3 = make29("rotate-secret", {
79785
80156
  yield* confirmDestructive({
79786
80157
  yes,
79787
80158
  json,
79788
- message: `Rotate the signing secret for webhook ${id}? The current secret will stop working.`,
80159
+ message: `Rotate the signing secret for webhook ${id}? The previous secret keeps verifying for 24 hours.`,
79789
80160
  nonInteractiveMessage: "Secret rotation requires --yes in non-interactive mode. Preview with --dry-run."
79790
80161
  });
79791
80162
  const client = yield* getClientProvider;
79792
80163
  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.";
80164
+ const warning = `Read this signing secret again with samva webhooks secret ${id}. The previous secret remains valid for 24 hours.`;
79794
80165
  const secret = value2(data.secret);
79795
80166
  if (json)
79796
- return yield* printJson({ secret, displayOnce: true, warning });
80167
+ return yield* printJson({ secret, warning });
79797
80168
  yield* error(`WARNING: ${warning}`);
79798
80169
  yield* log(secret);
79799
80170
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
@@ -79814,10 +80185,10 @@ var create17 = make29("create", {
79814
80185
  ]);
79815
80186
  const client = yield* getClientProvider;
79816
80187
  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.";
80188
+ const warning = `Read this signing secret again with samva webhooks secret ${result.endpoint.id}.`;
79818
80189
  const secret = value2(result.secret);
79819
80190
  if (json)
79820
- return yield* printJson({ endpoint: result.endpoint, secret, displayOnce: true, warning });
80191
+ return yield* printJson({ endpoint: result.endpoint, secret, warning });
79821
80192
  yield* error(`WARNING: ${warning}`);
79822
80193
  yield* printFields([
79823
80194
  ["ID", result.endpoint.id],
@@ -79873,7 +80244,19 @@ var remove15 = make29("remove", {
79873
80244
  return yield* printJson(result);
79874
80245
  yield* log(`Removed webhook ${id}.`);
79875
80246
  }).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]));
80247
+ var webhooksCommand = make29("webhooks").pipe(withDescription3("Inspect, test, and recover customer webhooks"), withExamples([{ command: "samva webhooks list --json" }]), withSubcommands([
80248
+ list28,
80249
+ get29,
80250
+ secret,
80251
+ create17,
80252
+ update14,
80253
+ test2,
80254
+ logs,
80255
+ stats3,
80256
+ retry4,
80257
+ rotate3,
80258
+ remove15
80259
+ ]));
79877
80260
 
79878
80261
  // src/lib/root-commands.ts
79879
80262
  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.19.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",
54
- "@samva/markup": "^0.13.1",
55
- "@samva/vite": "^0.13.1",
53
+ "@samva/mailbox": "0.5.2",
54
+ "@samva/markup": "0.15.0",
55
+ "@samva/vite": "0.15.0",
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.22.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.19.0",
66
+ "@samva/cli-darwin-x64": "0.19.0",
67
+ "@samva/cli-linux-arm64": "0.19.0",
68
+ "@samva/cli-linux-x64": "0.19.0"
69
69
  },
70
70
  "engines": {
71
71
  "node": ">=18"