@samva/cli 0.4.0 → 0.5.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 (4) hide show
  1. package/AGENTS.md +12 -2
  2. package/README.md +79 -32
  3. package/dist/bin.mjs +1024 -361
  4. package/package.json +8 -9
package/dist/bin.mjs CHANGED
@@ -13567,7 +13567,7 @@ var layer12 = /* @__PURE__ */ layer.pipe(/* @__PURE__ */ provideMerge(/* @__PURE
13567
13567
  // package.json
13568
13568
  var package_default = {
13569
13569
  name: "@samva/cli",
13570
- version: "0.4.0",
13570
+ version: "0.5.0",
13571
13571
  description: "Samva CLI \u2014 email and platform operations from your terminal",
13572
13572
  keywords: [
13573
13573
  "cli",
@@ -13615,21 +13615,20 @@ var package_default = {
13615
13615
  devDependencies: {
13616
13616
  "@effect/platform-bun": "catalog:effect",
13617
13617
  "@effect/vitest": "catalog:effect",
13618
- "@samva/markup": "workspace:0.8.0",
13619
- "@samva/vite": "workspace:0.8.0",
13618
+ "@samva/markup": "workspace:0.9.0",
13619
+ "@samva/vite": "workspace:0.9.0",
13620
13620
  "@types/node": "catalog:",
13621
13621
  "better-auth": "catalog:",
13622
- "better-result": "catalog:",
13623
13622
  effect: "catalog:effect",
13624
- samva: "workspace:0.7.0",
13623
+ samva: "workspace:0.8.0",
13625
13624
  typescript: "catalog:",
13626
13625
  vitest: "catalog:"
13627
13626
  },
13628
13627
  optionalDependencies: {
13629
- "@samva/cli-darwin-arm64": "workspace:0.4.0",
13630
- "@samva/cli-darwin-x64": "workspace:0.4.0",
13631
- "@samva/cli-linux-arm64": "workspace:0.4.0",
13632
- "@samva/cli-linux-x64": "workspace:0.4.0"
13628
+ "@samva/cli-darwin-arm64": "workspace:0.5.0",
13629
+ "@samva/cli-darwin-x64": "workspace:0.5.0",
13630
+ "@samva/cli-linux-arm64": "workspace:0.5.0",
13631
+ "@samva/cli-linux-x64": "workspace:0.5.0"
13633
13632
  },
13634
13633
  engines: {
13635
13634
  node: ">=18"
@@ -18534,6 +18533,32 @@ var AGENT_AUTH_HINT = "For CI and agents, set SAMVA_API_KEY instead of samva log
18534
18533
  // src/commands/brands.ts
18535
18534
  import { join as join3, relative, resolve as resolve5 } from "path";
18536
18535
 
18536
+ // ../markup/dist/diagnostics.js
18537
+ var buildLineMap = (source) => {
18538
+ const lineStarts = [0];
18539
+ for (let index = 0;index < source.length; index++) {
18540
+ const code = source.charCodeAt(index);
18541
+ if (code === 10 || code === 13 && source.charCodeAt(index + 1) !== 10 || code === 8232 || code === 8233)
18542
+ lineStarts.push(index + 1);
18543
+ }
18544
+ return lineStarts;
18545
+ };
18546
+ var lineColumn = (lineMap, offset) => {
18547
+ let low = 0;
18548
+ let high = lineMap.length - 1;
18549
+ while (low < high) {
18550
+ const middle = low + high + 1 >> 1;
18551
+ if (lineMap[middle] <= offset)
18552
+ low = middle;
18553
+ else
18554
+ high = middle - 1;
18555
+ }
18556
+ return {
18557
+ line: low + 1,
18558
+ column: offset - lineMap[low] + 1
18559
+ };
18560
+ };
18561
+
18537
18562
  // ../../node_modules/.bun/better-result@3.0.1/node_modules/better-result/dist/index.mjs
18538
18563
  function e(e, t) {
18539
18564
  return e === 2 ? (...e) => e.length >= 2 ? t(e[0], e[1]) : (n) => t(n, e[0]) : e === 3 ? (...e) => e.length >= 3 ? t(e[0], e[1], e[2]) : (n) => t(n, e[0], e[1]) : e === 4 ? (...e) => e.length >= 4 ? t(e[0], e[1], e[2], e[3]) : (n) => t(n, e[0], e[1], e[2]) : (...n) => n.length >= e ? t(...n) : (e) => t(e, ...n);
@@ -18973,50 +18998,6 @@ var $ = (e) => {
18973
18998
  };
18974
18999
  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: N, tapErrorAsync: P, tapBoth: F, 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 };
18975
19000
 
18976
- // ../markup/dist/diagnostics.js
18977
- var diagnosticsMessage = (diagnostics) => diagnostics.map((diagnostic) => diagnostic.message).join(`
18978
- `);
18979
- var SmlParseError = class extends h("SmlParseError") {
18980
- constructor(args) {
18981
- super({
18982
- diagnostics: args.diagnostics,
18983
- message: diagnosticsMessage(args.diagnostics)
18984
- });
18985
- }
18986
- };
18987
- var SmlCompileError = class extends h("SmlCompileError") {
18988
- constructor(args) {
18989
- super({
18990
- diagnostics: args.diagnostics,
18991
- message: diagnosticsMessage(args.diagnostics)
18992
- });
18993
- }
18994
- };
18995
- var buildLineMap = (source) => {
18996
- const lineStarts = [0];
18997
- for (let index = 0;index < source.length; index++) {
18998
- const code = source.charCodeAt(index);
18999
- if (code === 10 || code === 13 && source.charCodeAt(index + 1) !== 10 || code === 8232 || code === 8233)
19000
- lineStarts.push(index + 1);
19001
- }
19002
- return lineStarts;
19003
- };
19004
- var lineColumn = (lineMap, offset) => {
19005
- let low = 0;
19006
- let high = lineMap.length - 1;
19007
- while (low < high) {
19008
- const middle = low + high + 1 >> 1;
19009
- if (lineMap[middle] <= offset)
19010
- low = middle;
19011
- else
19012
- high = middle - 1;
19013
- }
19014
- return {
19015
- line: low + 1,
19016
- column: offset - lineMap[low] + 1
19017
- };
19018
- };
19019
-
19020
19001
  // ../../node_modules/.bun/css-tree@3.2.1/node_modules/css-tree/lib/tokenizer/types.js
19021
19002
  var EOF = 0;
19022
19003
  var Ident = 1;
@@ -42613,8 +42594,8 @@ function buildMessagesSendPayload(input) {
42613
42594
  };
42614
42595
  }
42615
42596
 
42616
- // ../sdk/dist/client-attribution-27MNycCV.js
42617
- var SDK_VERSION = "0.7.0";
42597
+ // ../sdk/dist/client-attribution-N5A784Zo.js
42598
+ var SDK_VERSION = "0.8.0";
42618
42599
  var CLIENT_ATTRIBUTION_HEADER = "x-samva-client";
42619
42600
  var clientAttribution = (surface) => `${surface}/${SDK_VERSION}`;
42620
42601
  // ../../node_modules/.bun/effect@4.0.0-rc.117/node_modules/effect/dist/ChannelSchema.js
@@ -44938,7 +44919,7 @@ var fetch2 = /* @__PURE__ */ make32((request, url, signal, fiber) => {
44938
44919
  });
44939
44920
  var layer15 = /* @__PURE__ */ layerMergedContext(/* @__PURE__ */ succeed6(fetch2));
44940
44921
 
44941
- // ../sdk/dist/client-5O8KsY5Z.js
44922
+ // ../sdk/dist/client-ByJHYp7r.js
44942
44923
  var Sensitive = (schema) => RedactedFromValue(schema);
44943
44924
  var SensitiveString = Sensitive(String4);
44944
44925
  var ABSOLUTE_ISO_INSTANT_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(Z|([+-])(\d{2}):(\d{2}))$/;
@@ -45083,6 +45064,14 @@ var TemplateId = String4.pipe(brand2("TemplateId")).check(isPattern2(/* @__PURE_
45083
45064
  identifier: "TemplateId"
45084
45065
  }));
45085
45066
  var JsonObject = Record(String4, JsonValue).annotate({ identifier: "JsonObject" });
45067
+ var RenderLocale = String4.check(isMaxLength(64).annotate({
45068
+ expected: "a value with a length of at most 64",
45069
+ identifier: "RenderLocale"
45070
+ }));
45071
+ var RenderTimeZone = String4.check(isMaxLength(64).annotate({
45072
+ expected: "a value with a length of at most 64",
45073
+ identifier: "RenderTimeZone"
45074
+ }));
45086
45075
  var ReplyToAddresses = Union2([Union2([ArraySchema(String4), Null2]), Null2]).annotate({ identifier: "ReplyToAddresses" });
45087
45076
  var UnsubscribeId = String4.pipe(brand2("UnsubscribeId")).check(isPattern2(/* @__PURE__ */ new RegExp("^unsub_[0-9a-hjkmnp-tv-z]{16,24}$")).annotate({
45088
45077
  expected: "a string matching the RegExp ^unsub_[0-9a-hjkmnp-tv-z]{16,24}$",
@@ -45477,6 +45466,29 @@ var OperationResource = Struct({
45477
45466
  type: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxLength(80).annotate({ expected: "a value with a length of at most 80" })),
45478
45467
  id: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxLength(255).annotate({ expected: "a value with a length of at most 255" }))
45479
45468
  }).annotate({ identifier: "OperationResource" });
45469
+ var Organization = Struct({
45470
+ id: String4,
45471
+ billingAccountId: String4,
45472
+ name: String4,
45473
+ slug: String4,
45474
+ countryCode: String4,
45475
+ timezone: String4,
45476
+ plan: String4,
45477
+ isActive: Boolean3,
45478
+ createdAt: String4,
45479
+ updatedAt: String4,
45480
+ metadata: Union2([Record(String4, JsonValue), Null2]),
45481
+ replyToDefault: Union2([ArraySchema(String4), Null2]),
45482
+ emailFooterEnabledDefault: Union2([Boolean3, Null2]),
45483
+ messagesUsed: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45484
+ messagesUsedThisMonth: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45485
+ emailUsed: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45486
+ emailUsedThisMonth: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45487
+ contactsUsed: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45488
+ conversationsUsed: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45489
+ teamMembersUsed: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45490
+ messagesResetAt: Union2([String4, Null2])
45491
+ }).annotate({ identifier: "Organization" });
45480
45492
  var TemplateProjectId = String4.pipe(brand2("TemplateProjectId")).check(isPattern2(/* @__PURE__ */ new RegExp("^tproj_[0-9a-hjkmnp-tv-z]{16,24}$")).annotate({
45481
45493
  expected: "a string matching the RegExp ^tproj_[0-9a-hjkmnp-tv-z]{16,24}$",
45482
45494
  identifier: "TemplateProjectId"
@@ -45494,6 +45506,10 @@ var TemplatePublicationBrand = Struct({
45494
45506
  digest: String4.annotate({ description: "The brand's digest when the publication was built." }),
45495
45507
  stale: Boolean3.annotate({ description: "Whether the brand has changed since this publication was built. Publishing the same commit again picks up the change." })
45496
45508
  }).annotate({ identifier: "TemplatePublicationBrand" });
45509
+ var BrandFooterBlock = Struct({
45510
+ html: String4,
45511
+ text: String4
45512
+ }).annotate({ identifier: "BrandFooterBlock" });
45497
45513
  var WebhookDeliveryCounts = Struct({
45498
45514
  total: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
45499
45515
  pending: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
@@ -45521,7 +45537,7 @@ var WebhookDailyDeliveries = Struct({
45521
45537
  cancelled: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" }))
45522
45538
  }).annotate({ identifier: "WebhookDailyDeliveries" });
45523
45539
  Struct({
45524
- entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })),
45540
+ entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })),
45525
45541
  name: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxLength(200).annotate({ expected: "a value with a length of at most 200" })),
45526
45542
  slug: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$")).annotate({ expected: "a string matching the RegExp ^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$" })),
45527
45543
  description: optionalKey2(Union2([Union2([String4, Null2]), Null2])),
@@ -45586,9 +45602,13 @@ var CreateWebhookPayload = Struct({
45586
45602
  "message.bounced",
45587
45603
  "message.complained",
45588
45604
  "message.received",
45605
+ "message.opened",
45606
+ "message.clicked",
45589
45607
  "contact.created",
45590
45608
  "contact.updated",
45591
45609
  "contact.deleted",
45610
+ "contact.unsubscribed",
45611
+ "contact.resubscribed",
45592
45612
  "conversation.created",
45593
45613
  "conversation.updated",
45594
45614
  "conversation.archived",
@@ -45623,9 +45643,13 @@ var UpdateWebhookPayload = Struct({
45623
45643
  "message.bounced",
45624
45644
  "message.complained",
45625
45645
  "message.received",
45646
+ "message.opened",
45647
+ "message.clicked",
45626
45648
  "contact.created",
45627
45649
  "contact.updated",
45628
45650
  "contact.deleted",
45651
+ "contact.unsubscribed",
45652
+ "contact.resubscribed",
45629
45653
  "conversation.created",
45630
45654
  "conversation.updated",
45631
45655
  "conversation.archived",
@@ -45704,30 +45728,6 @@ var CampaignPolicy = Struct({
45704
45728
  unsubscribeGroupId: optionalKey2(Union2([UnsubscribeId, Null2])),
45705
45729
  pageSize: optionalKey2(Union2([Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(100).annotate({ expected: "a value greater than or equal to 100" })).check(isLessThanOrEqualTo3(1000).annotate({ expected: "a value less than or equal to 1000" })), Null2]))
45706
45730
  }).annotate({ identifier: "CampaignPolicy" });
45707
- var Organization = Struct({
45708
- id: String4,
45709
- billingAccountId: String4,
45710
- name: String4,
45711
- slug: String4,
45712
- countryCode: String4,
45713
- timezone: String4,
45714
- plan: String4,
45715
- isActive: Boolean3,
45716
- createdAt: String4,
45717
- updatedAt: String4,
45718
- metadata: Union2([Record(String4, JsonValue), Null2]),
45719
- replyToDefault: Union2([ArraySchema(String4), Null2]),
45720
- defaultUnsubscribeGroupId: Union2([UnsubscribeId, Null2]),
45721
- emailFooterEnabledDefault: Union2([Boolean3, Null2]),
45722
- messagesUsed: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45723
- messagesUsedThisMonth: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45724
- emailUsed: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45725
- emailUsedThisMonth: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45726
- contactsUsed: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45727
- conversationsUsed: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45728
- teamMembersUsed: Finite.check(isFinite4().annotate({ expected: "a finite number" })),
45729
- messagesResetAt: Union2([String4, Null2])
45730
- }).annotate({ identifier: "Organization" });
45731
45731
  var MessageContent = Struct({
45732
45732
  text: optionalKey2(Union2([String4, Null2])),
45733
45733
  html: optionalKey2(Union2([String4, Null2])),
@@ -45888,7 +45888,7 @@ var ActorTracking = Struct({
45888
45888
  actorType: Literals([
45889
45889
  "contact",
45890
45890
  "system",
45891
- "sender_identity",
45891
+ "sender",
45892
45892
  "agent",
45893
45893
  "user"
45894
45894
  ]),
@@ -45910,7 +45910,7 @@ var ConversationParticipant = Struct({
45910
45910
  actorType: Literals([
45911
45911
  "contact",
45912
45912
  "system",
45913
- "sender_identity",
45913
+ "sender",
45914
45914
  "agent",
45915
45915
  "user"
45916
45916
  ]),
@@ -45928,9 +45928,13 @@ var WebhookEndpoint = Struct({
45928
45928
  "message.bounced",
45929
45929
  "message.complained",
45930
45930
  "message.received",
45931
+ "message.opened",
45932
+ "message.clicked",
45931
45933
  "contact.created",
45932
45934
  "contact.updated",
45933
45935
  "contact.deleted",
45936
+ "contact.unsubscribed",
45937
+ "contact.resubscribed",
45934
45938
  "conversation.created",
45935
45939
  "conversation.updated",
45936
45940
  "conversation.archived",
@@ -46259,7 +46263,7 @@ var TemplateFamily = Struct({
46259
46263
  var UpdateTemplatePayload = Struct({ data: Struct({
46260
46264
  name: optionalKey2(Union2([String4, Null2])),
46261
46265
  slug: optionalKey2(Union2([String4.check(isPattern2(/* @__PURE__ */ new RegExp("^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$")).annotate({ expected: "a string matching the RegExp ^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$" })), Null2])),
46262
- entryPath: optionalKey2(Union2([String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })), Null2])),
46266
+ entryPath: optionalKey2(Union2([String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })), Null2])),
46263
46267
  description: optionalKey2(Union2([Union2([String4, Null2]), Null2])),
46264
46268
  category: optionalKey2(Union2([Union2([Literals([
46265
46269
  "transactional",
@@ -46278,7 +46282,7 @@ var Template = Struct({
46278
46282
  id: TemplateId,
46279
46283
  organizationId: OrganizationId,
46280
46284
  projectId: TemplateProjectId,
46281
- entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })),
46285
+ entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })),
46282
46286
  name: String4,
46283
46287
  slug: String4,
46284
46288
  familyId: Union2([TemplateFamilyId, Null2]),
@@ -46310,6 +46314,10 @@ var Template = Struct({
46310
46314
  createdAt: String4,
46311
46315
  updatedAt: String4
46312
46316
  }).annotate({ identifier: "Template" });
46317
+ var BrandRenderedFooter = Struct({
46318
+ withUnsubscribe: BrandFooterBlock,
46319
+ withoutUnsubscribe: BrandFooterBlock
46320
+ }).annotate({ identifier: "BrandRenderedFooter" });
46313
46321
  var WebhookEndpointListItem = Struct({
46314
46322
  id: WebhookEndpointId,
46315
46323
  name: String4,
@@ -46322,9 +46330,13 @@ var WebhookEndpointListItem = Struct({
46322
46330
  "message.bounced",
46323
46331
  "message.complained",
46324
46332
  "message.received",
46333
+ "message.opened",
46334
+ "message.clicked",
46325
46335
  "contact.created",
46326
46336
  "contact.updated",
46327
46337
  "contact.deleted",
46338
+ "contact.unsubscribed",
46339
+ "contact.resubscribed",
46328
46340
  "conversation.created",
46329
46341
  "conversation.updated",
46330
46342
  "conversation.archived",
@@ -47014,6 +47026,8 @@ var EmailContent = Struct({
47014
47026
  templateId: optionalKey2(Union2([TemplateId, Null2])),
47015
47027
  templateSlug: optionalKey2(Union2([String4.check(isPattern2(/* @__PURE__ */ new RegExp("^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$")).annotate({ expected: "a string matching the RegExp ^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$" })), Null2])),
47016
47028
  templateData: optionalKey2(TemplateData),
47029
+ locale: optionalKey2(RenderLocale),
47030
+ timeZone: optionalKey2(RenderTimeZone),
47017
47031
  inReplyToMessageId: optionalKey2(Union2([MessageId, Null2])),
47018
47032
  replyTo: optionalKey2(ReplyToAddresses),
47019
47033
  unsubscribeGroupId: optionalKey2(Union2([UnsubscribeId, Null2])),
@@ -47030,6 +47044,8 @@ var EmailDispatchContent = Struct({
47030
47044
  templateId: optionalKey2(Union2([TemplateId, Null2])),
47031
47045
  templateSlug: optionalKey2(Union2([String4.check(isPattern2(/* @__PURE__ */ new RegExp("^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$")).annotate({ expected: "a string matching the RegExp ^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$" })), Null2])),
47032
47046
  templateData: optionalKey2(TemplateData),
47047
+ locale: optionalKey2(RenderLocale),
47048
+ timeZone: optionalKey2(RenderTimeZone),
47033
47049
  inReplyToMessageId: optionalKey2(Union2([MessageId, Null2])),
47034
47050
  replyTo: optionalKey2(ReplyToAddresses),
47035
47051
  unsubscribeGroupId: optionalKey2(Union2([UnsubscribeId, Null2])),
@@ -49940,7 +49956,6 @@ var OrganizationsUpdateRequestJson = Struct({
49940
49956
  isActive: optionalKey2(Union2([Boolean3, Null2])),
49941
49957
  metadata: optionalKey2(Union2([Record(String4, JsonValue), Null2])),
49942
49958
  replyToDefault: optionalKey2(Union2([Union2([ArraySchema(String4), Null2]), Null2])),
49943
- defaultUnsubscribeGroupId: optionalKey2(Union2([Union2([UnsubscribeId, Null2]), Null2])),
49944
49959
  emailFooterEnabledDefault: optionalKey2(Union2([Union2([Boolean3, Null2]), Null2]))
49945
49960
  });
49946
49961
  var OrganizationsUpdate200 = Organization;
@@ -49950,6 +49965,15 @@ var OrganizationsUpdate404 = ResourceNotFoundError;
49950
49965
  var OrganizationsUpdate422 = ValidationError;
49951
49966
  var OrganizationsUpdate500 = InternalError;
49952
49967
  var OrganizationsUpdate504 = GatewayTimeoutError;
49968
+ var OrganizationsGetProfile200 = Struct({
49969
+ id: String4.annotate({ description: "Opaque identity for your organization membership" }),
49970
+ name: String4.annotate({ description: "Your display name" }),
49971
+ nickname: String4.annotate({ description: "Your display name and organization name" })
49972
+ });
49973
+ var OrganizationsGetProfile401 = UnauthorizedError;
49974
+ var OrganizationsGetProfile403 = ForbiddenError;
49975
+ var OrganizationsGetProfile500 = InternalError;
49976
+ var OrganizationsGetProfile504 = GatewayTimeoutError;
49953
49977
  var OrganizationsDeleteHeaders = Struct({ "idempotency-key": String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxLength(255).annotate({ expected: "a value with a length of at most 255" })) });
49954
49978
  var OrganizationsDeleteRequestJson = Struct({ confirmationName: String4 });
49955
49979
  var OrganizationsDelete202 = OperationAccepted;
@@ -50069,7 +50093,7 @@ var TemplatesUpdatePathParams = Struct({ id: TemplateId });
50069
50093
  var TemplatesUpdateRequestJson = Struct({ data: Struct({
50070
50094
  name: optionalKey2(Union2([String4, Null2])),
50071
50095
  slug: optionalKey2(Union2([String4.check(isPattern2(/* @__PURE__ */ new RegExp("^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$")).annotate({ expected: "a string matching the RegExp ^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$" })), Null2])),
50072
- entryPath: optionalKey2(Union2([String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })), Null2])),
50096
+ entryPath: optionalKey2(Union2([String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })), Null2])),
50073
50097
  description: optionalKey2(Union2([Union2([String4, Null2]), Null2])),
50074
50098
  category: optionalKey2(Union2([Union2([Literals([
50075
50099
  "transactional",
@@ -50129,7 +50153,7 @@ var TemplatesPublishPathParams = Struct({ id: TemplateId });
50129
50153
  var TemplatesPublishRequestJson = Struct({
50130
50154
  projectId: TemplateProjectId,
50131
50155
  commitSha: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{40}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{40}$" })),
50132
- entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })),
50156
+ entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })),
50133
50157
  expectedWorkspaceRevision: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50134
50158
  ifCurrentPublicationId: optionalKey2(Union2([TemplatePublicationId, Null2]))
50135
50159
  });
@@ -50141,30 +50165,20 @@ var TemplatesPublish201 = Struct({
50141
50165
  inputContractId: TemplateInputContractId,
50142
50166
  commitSha: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{40}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{40}$" })),
50143
50167
  treeSha: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{40}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{40}$" })),
50144
- entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })),
50168
+ entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })),
50145
50169
  buildIdentity: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50146
50170
  manifest: Struct({
50147
50171
  templateKey: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50148
- executable: Struct({
50149
- codeDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50172
+ ir: Struct({
50173
+ version: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(1).annotate({ expected: "a value greater than or equal to 1" })),
50174
+ digest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50150
50175
  bytes: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" }))
50151
50176
  }),
50177
+ compiler: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50152
50178
  input: Struct({
50153
50179
  schemaDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50154
50180
  contractDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" }))
50155
50181
  }),
50156
- environment: Struct({
50157
- locale: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50158
- timeZone: Literal2("UTC"),
50159
- now: optionalKey2(Finite.check(isInt().annotate({ expected: "an integer" }))),
50160
- randomSeed: optionalKey2(Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })).check(isLessThanOrEqualTo3(4294967295).annotate({ expected: "a value less than or equal to 4294967295" }))),
50161
- compiler: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50162
- runtime: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50163
- checker: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50164
- lockKind: Literals(["bun", "npm"]),
50165
- lockDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50166
- digest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" }))
50167
- }),
50168
50182
  assets: Struct({
50169
50183
  base: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^https?:\\/\\/")).annotate({ expected: "a string matching the RegExp ^https?:\\/\\/" })),
50170
50184
  entries: ArraySchema(Struct({
@@ -50179,7 +50193,7 @@ var TemplatesPublish201 = Struct({
50179
50193
  source: Struct({
50180
50194
  commitSha: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{40}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{40}$" })),
50181
50195
  treeSha: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{40}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{40}$" })),
50182
- entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })),
50196
+ entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })),
50183
50197
  treeDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" }))
50184
50198
  }),
50185
50199
  brand: optionalKey2(Struct({
@@ -50187,6 +50201,7 @@ var TemplatesPublish201 = Struct({
50187
50201
  css: String4,
50188
50202
  assets: BrandAssets,
50189
50203
  footer: BrandFooter,
50204
+ renderedFooter: BrandRenderedFooter,
50190
50205
  digest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50191
50206
  rendersFooter: Literals([
50192
50207
  "every",
@@ -50195,7 +50210,7 @@ var TemplatesPublish201 = Struct({
50195
50210
  ])
50196
50211
  }))
50197
50212
  }),
50198
- executable: Struct({
50213
+ ir: Struct({
50199
50214
  key: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50200
50215
  digest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" }))
50201
50216
  }),
@@ -50295,30 +50310,20 @@ var TemplatesGetPublication200 = Struct({
50295
50310
  inputContractId: TemplateInputContractId,
50296
50311
  commitSha: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{40}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{40}$" })),
50297
50312
  treeSha: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{40}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{40}$" })),
50298
- entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })),
50313
+ entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })),
50299
50314
  buildIdentity: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50300
50315
  manifest: Struct({
50301
50316
  templateKey: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50302
- executable: Struct({
50303
- codeDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50317
+ ir: Struct({
50318
+ version: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(1).annotate({ expected: "a value greater than or equal to 1" })),
50319
+ digest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50304
50320
  bytes: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" }))
50305
50321
  }),
50322
+ compiler: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50306
50323
  input: Struct({
50307
50324
  schemaDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50308
50325
  contractDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" }))
50309
50326
  }),
50310
- environment: Struct({
50311
- locale: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50312
- timeZone: Literal2("UTC"),
50313
- now: optionalKey2(Finite.check(isInt().annotate({ expected: "an integer" }))),
50314
- randomSeed: optionalKey2(Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })).check(isLessThanOrEqualTo3(4294967295).annotate({ expected: "a value less than or equal to 4294967295" }))),
50315
- compiler: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50316
- runtime: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50317
- checker: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50318
- lockKind: Literals(["bun", "npm"]),
50319
- lockDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50320
- digest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" }))
50321
- }),
50322
50327
  assets: Struct({
50323
50328
  base: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^https?:\\/\\/")).annotate({ expected: "a string matching the RegExp ^https?:\\/\\/" })),
50324
50329
  entries: ArraySchema(Struct({
@@ -50333,7 +50338,7 @@ var TemplatesGetPublication200 = Struct({
50333
50338
  source: Struct({
50334
50339
  commitSha: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{40}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{40}$" })),
50335
50340
  treeSha: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{40}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{40}$" })),
50336
- entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })),
50341
+ entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })),
50337
50342
  treeDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" }))
50338
50343
  }),
50339
50344
  brand: optionalKey2(Struct({
@@ -50341,6 +50346,7 @@ var TemplatesGetPublication200 = Struct({
50341
50346
  css: String4,
50342
50347
  assets: BrandAssets,
50343
50348
  footer: BrandFooter,
50349
+ renderedFooter: BrandRenderedFooter,
50344
50350
  digest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50345
50351
  rendersFooter: Literals([
50346
50352
  "every",
@@ -50349,7 +50355,7 @@ var TemplatesGetPublication200 = Struct({
50349
50355
  ])
50350
50356
  }))
50351
50357
  }),
50352
- executable: Struct({
50358
+ ir: Struct({
50353
50359
  key: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })),
50354
50360
  digest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" }))
50355
50361
  }),
@@ -50778,7 +50784,7 @@ var TemplatesCheckWorkspaceRequestJson = Struct({ expectedRevision: String4 });
50778
50784
  var TemplatesCheckWorkspace200 = Struct({
50779
50785
  projectId: TemplateProjectId,
50780
50786
  revision: String4,
50781
- entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })),
50787
+ entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })),
50782
50788
  declaration: Union2([Struct({
50783
50789
  codeDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50784
50790
  templateId: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxLength(128).annotate({ expected: "a value with a length of at most 128" })),
@@ -50787,10 +50793,6 @@ var TemplatesCheckWorkspace200 = Struct({
50787
50793
  schemaDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
50788
50794
  contractDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" }))
50789
50795
  }),
50790
- runtime: Struct({
50791
- now: optionalKey2(Finite.check(isInt().annotate({ expected: "an integer" }))),
50792
- randomSeed: optionalKey2(Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })).check(isLessThanOrEqualTo3(4294967295).annotate({ expected: "a value less than or equal to 4294967295" })))
50793
- }),
50794
50796
  fixtures: ArraySchema(String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxLength(128).annotate({ expected: "a value with a length of at most 128" })))
50795
50797
  }), Null2]),
50796
50798
  diagnostics: ArraySchema(Struct({
@@ -51012,7 +51014,7 @@ var TemplatesRenderWorkspaceFixtureRequestJson = Struct({
51012
51014
  var TemplatesRenderWorkspaceFixture200 = Struct({
51013
51015
  projectId: TemplateProjectId,
51014
51016
  revision: String4,
51015
- entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })),
51017
+ entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })),
51016
51018
  declaration: Union2([Struct({
51017
51019
  codeDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
51018
51020
  templateId: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxLength(128).annotate({ expected: "a value with a length of at most 128" })),
@@ -51021,10 +51023,6 @@ var TemplatesRenderWorkspaceFixture200 = Struct({
51021
51023
  schemaDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" })),
51022
51024
  contractDigest: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^[a-f0-9]{64}$")).annotate({ expected: "a string matching the RegExp ^[a-f0-9]{64}$" }))
51023
51025
  }),
51024
- runtime: Struct({
51025
- now: optionalKey2(Finite.check(isInt().annotate({ expected: "an integer" }))),
51026
- randomSeed: optionalKey2(Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })).check(isLessThanOrEqualTo3(4294967295).annotate({ expected: "a value less than or equal to 4294967295" })))
51027
- }),
51028
51026
  fixtures: ArraySchema(String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxLength(128).annotate({ expected: "a value with a length of at most 128" })))
51029
51027
  }), Null2]),
51030
51028
  diagnostics: ArraySchema(Struct({
@@ -51733,7 +51731,7 @@ var TemplatesCreateFamilyFromTemplate500 = InternalError;
51733
51731
  var TemplatesCreateFamilyFromTemplate504 = GatewayTimeoutError;
51734
51732
  var TemplatesAddEntryPathParams = Struct({ projectId: TemplateProjectId });
51735
51733
  var TemplatesAddEntryRequestJson = Struct({
51736
- entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))emails\\/.+\\.tsx$" })),
51734
+ entryPath: String4.check(isMaxLength(1024).annotate({ expected: "a value with a length of at most 1024" })).check(isPattern2(/* @__PURE__ */ new RegExp("^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$")).annotate({ expected: "a string matching the RegExp ^(?!\\/)(?!.*\\\\)(?!.*(?:^|\\/)\\.{1,2}(?:\\/|$))(?!.*(?:^|\\/)\\.git(?:\\/|$))(?:templates|emails)\\/.+\\.tsx$" })),
51737
51735
  name: String4.check(isMinLength(1).annotate({ expected: "a value with a length of at least 1" })).check(isMaxLength(200).annotate({ expected: "a value with a length of at most 200" })),
51738
51736
  slug: String4.check(isPattern2(/* @__PURE__ */ new RegExp("^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$")).annotate({ expected: "a string matching the RegExp ^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$" })),
51739
51737
  description: optionalKey2(Union2([Union2([String4, Null2]), Null2])),
@@ -51765,6 +51763,7 @@ var UnsubscribeList200 = Struct({
51765
51763
  organizationId: String4,
51766
51764
  name: String4,
51767
51765
  description: Union2([String4, Null2]),
51766
+ isDefault: Boolean3,
51768
51767
  memberCount: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
51769
51768
  createdAt: String4,
51770
51769
  updatedAt: String4
@@ -51784,6 +51783,7 @@ var UnsubscribeCreate201 = Struct({
51784
51783
  organizationId: String4,
51785
51784
  name: String4,
51786
51785
  description: Union2([String4, Null2]),
51786
+ isDefault: Boolean3,
51787
51787
  memberCount: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
51788
51788
  createdAt: String4,
51789
51789
  updatedAt: String4
@@ -51850,6 +51850,7 @@ var UnsubscribeRemove200 = Struct({ success: Boolean3 });
51850
51850
  var UnsubscribeRemove401 = UnauthorizedError;
51851
51851
  var UnsubscribeRemove403 = ForbiddenError;
51852
51852
  var UnsubscribeRemove404 = ResourceNotFoundError;
51853
+ var UnsubscribeRemove409 = ConflictError;
51853
51854
  var UnsubscribeRemove500 = InternalError;
51854
51855
  var UnsubscribeRemove504 = GatewayTimeoutError;
51855
51856
  var UnsubscribeUpdatePathParams = Struct({ id: UnsubscribeId });
@@ -51862,6 +51863,7 @@ var UnsubscribeUpdate200 = Struct({
51862
51863
  organizationId: String4,
51863
51864
  name: String4,
51864
51865
  description: Union2([String4, Null2]),
51866
+ isDefault: Boolean3,
51865
51867
  memberCount: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
51866
51868
  createdAt: String4,
51867
51869
  updatedAt: String4
@@ -51871,6 +51873,22 @@ var UnsubscribeUpdate403 = ForbiddenError;
51871
51873
  var UnsubscribeUpdate404 = ResourceNotFoundError;
51872
51874
  var UnsubscribeUpdate500 = InternalError;
51873
51875
  var UnsubscribeUpdate504 = GatewayTimeoutError;
51876
+ var UnsubscribeSetDefaultPathParams = Struct({ id: UnsubscribeId });
51877
+ var UnsubscribeSetDefault200 = Struct({
51878
+ id: UnsubscribeId,
51879
+ organizationId: String4,
51880
+ name: String4,
51881
+ description: Union2([String4, Null2]),
51882
+ isDefault: Boolean3,
51883
+ memberCount: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
51884
+ createdAt: String4,
51885
+ updatedAt: String4
51886
+ });
51887
+ var UnsubscribeSetDefault401 = UnauthorizedError;
51888
+ var UnsubscribeSetDefault403 = ForbiddenError;
51889
+ var UnsubscribeSetDefault404 = ResourceNotFoundError;
51890
+ var UnsubscribeSetDefault500 = InternalError;
51891
+ var UnsubscribeSetDefault504 = GatewayTimeoutError;
51874
51892
  var UnsubscribeCheckSubscriptionQuery = Struct({
51875
51893
  email: String4,
51876
51894
  groupIds: optionalKey2(Union2([String4, Null2]))
@@ -51913,9 +51931,13 @@ var WebhooksCreateRequestJson = Struct({
51913
51931
  "message.bounced",
51914
51932
  "message.complained",
51915
51933
  "message.received",
51934
+ "message.opened",
51935
+ "message.clicked",
51916
51936
  "contact.created",
51917
51937
  "contact.updated",
51918
51938
  "contact.deleted",
51939
+ "contact.unsubscribed",
51940
+ "contact.resubscribed",
51919
51941
  "conversation.created",
51920
51942
  "conversation.updated",
51921
51943
  "conversation.archived",
@@ -51969,9 +51991,13 @@ var WebhooksUpdateRequestJson = Struct({
51969
51991
  "message.bounced",
51970
51992
  "message.complained",
51971
51993
  "message.received",
51994
+ "message.opened",
51995
+ "message.clicked",
51972
51996
  "contact.created",
51973
51997
  "contact.updated",
51974
51998
  "contact.deleted",
51999
+ "contact.unsubscribed",
52000
+ "contact.resubscribed",
51975
52001
  "conversation.created",
51976
52002
  "conversation.updated",
51977
52003
  "conversation.archived",
@@ -53427,7 +53453,7 @@ var EmailGroup = class extends make36("Email").add(get5("emailListBlocks", "/v1/
53427
53453
  EmailListSenders500,
53428
53454
  EmailListSenders504.pipe(status(504))
53429
53455
  ]
53430
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.listSenders").annotate(Summary, "List sender identities").annotate(Description, "Returns a paginated list of sender identities, optionally filtered by verification status, active state, or default flag. Each row carries `deliverability` (bounce and complaint rates over the last 24 hours) and `series`, the last 14 days of daily email sends from the address, oldest first."), post("emailAddSender", "/v1/email/senders", {
53456
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.listSenders").annotate(Summary, "List senders").annotate(Description, "Returns a paginated list of senders, optionally filtered by verification status, active state, or default flag. Each row carries `deliverability` (bounce and complaint rates over the last 24 hours) and `series`, the last 14 days of daily email sends from the address, oldest first."), post("emailAddSender", "/v1/email/senders", {
53431
53457
  headers: EmailAddSenderHeaders,
53432
53458
  payload: EmailAddSenderRequestJson,
53433
53459
  success: EmailAddSender201.pipe(status(201)),
@@ -53441,7 +53467,7 @@ var EmailGroup = class extends make36("Email").add(get5("emailListBlocks", "/v1/
53441
53467
  EmailAddSender502.pipe(status(502)),
53442
53468
  EmailAddSender504.pipe(status(504))
53443
53469
  ]
53444
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.addSender").annotate(Summary, "Add a sender identity").annotate(Description, "Creates a sender identity. The required Idempotency-Key replays the original sender for an identical request and conflicts when reused with different input."), get5("emailGetSender", "/v1/email/senders/:id", {
53470
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.addSender").annotate(Summary, "Add a sender").annotate(Description, "Creates a sender. The required Idempotency-Key replays the original sender for an identical request and conflicts when reused with different input."), get5("emailGetSender", "/v1/email/senders/:id", {
53445
53471
  params: EmailGetSenderPathParams,
53446
53472
  success: EmailGetSender200,
53447
53473
  error: [
@@ -53451,7 +53477,7 @@ var EmailGroup = class extends make36("Email").add(get5("emailListBlocks", "/v1/
53451
53477
  EmailGetSender500,
53452
53478
  EmailGetSender504.pipe(status(504))
53453
53479
  ]
53454
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.getSender").annotate(Summary, "Get a sender identity").annotate(Description, "Returns one sender identity owned by the authenticated organization. Other organizations' ids are reported as not found."), del("emailRemoveSender", "/v1/email/senders/:id", {
53480
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.getSender").annotate(Summary, "Get a sender").annotate(Description, "Returns one sender owned by the authenticated organization. Other organizations' ids are reported as not found."), del("emailRemoveSender", "/v1/email/senders/:id", {
53455
53481
  params: EmailRemoveSenderPathParams,
53456
53482
  success: EmailRemoveSender200,
53457
53483
  error: [
@@ -53462,7 +53488,7 @@ var EmailGroup = class extends make36("Email").add(get5("emailListBlocks", "/v1/
53462
53488
  EmailRemoveSender500,
53463
53489
  EmailRemoveSender504.pipe(status(504))
53464
53490
  ]
53465
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.removeSender").annotate(Summary, "Remove a sender identity").annotate(Description, "Accepts sender removal and marks the sender inactive. It reports the `deleting` lifecycle until removal finishes."), post("emailSetDefaultSender", "/v1/email/senders/:id/default", {
53491
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.removeSender").annotate(Summary, "Remove a sender").annotate(Description, "Accepts sender removal and marks the sender inactive. A default sender can be removed when no other active sender remains in the organization. It reports the `deleting` lifecycle until removal finishes."), post("emailSetDefaultSender", "/v1/email/senders/:id/default", {
53466
53492
  params: EmailSetDefaultSenderPathParams,
53467
53493
  success: EmailSetDefaultSender200,
53468
53494
  error: [
@@ -53483,7 +53509,7 @@ var EmailGroup = class extends make36("Email").add(get5("emailListBlocks", "/v1/
53483
53509
  EmailCheckSenderVerification502.pipe(status(502)),
53484
53510
  EmailCheckSenderVerification504.pipe(status(504))
53485
53511
  ]
53486
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.checkSenderVerification").annotate(Summary, "Check sender verification status").annotate(Description, "Returns the current verification status of a sender identity."), get5("emailGetStats", "/v1/email/stats", {
53512
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.checkSenderVerification").annotate(Summary, "Check sender verification status").annotate(Description, "Returns the current verification status of a sender."), get5("emailGetStats", "/v1/email/stats", {
53487
53513
  query: EmailGetStatsQuery,
53488
53514
  success: EmailGetStats200,
53489
53515
  error: [
@@ -53821,7 +53847,15 @@ var OrganizationsGroup = class extends make36("Organizations").add(post("organiz
53821
53847
  OrganizationsUpdate500,
53822
53848
  OrganizationsUpdate504.pipe(status(504))
53823
53849
  ]
53824
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "organizations.update").annotate(Summary, "Update the current organization").annotate(Description, "Updates the current organization's profile, regional defaults, reply-to, and unsubscribe-group defaults and returns the saved organization. `metadata` is merged into the stored value; every other field is replaced."), post("organizationsDelete", "/v1/organizations/deletion-operations", {
53850
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "organizations.update").annotate(Summary, "Update the current organization").annotate(Description, "Updates the current organization's profile, regional defaults, and reply-to default and returns the saved organization. `metadata` is merged into the stored value; every other field is replaced."), get5("organizationsGetProfile", "/v1/organizations/current/profile", {
53851
+ success: OrganizationsGetProfile200,
53852
+ error: [
53853
+ OrganizationsGetProfile401.pipe(status(401)),
53854
+ OrganizationsGetProfile403.pipe(status(403)),
53855
+ OrganizationsGetProfile500,
53856
+ OrganizationsGetProfile504.pipe(status(504))
53857
+ ]
53858
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "organizations.getProfile").annotate(Summary, "Get the current account profile").annotate(Description, "Returns your identity in the organization authorized by your credential. The ID remains stable across reconnects and display-name changes. Removing and re-adding your membership creates a new ID."), post("organizationsDelete", "/v1/organizations/deletion-operations", {
53825
53859
  headers: OrganizationsDeleteHeaders,
53826
53860
  payload: OrganizationsDeleteRequestJson,
53827
53861
  success: OrganizationsDelete202.pipe(status(202)),
@@ -53930,7 +53964,7 @@ var TemplatesGroup = class extends make36("Templates").add(get5("templatesList",
53930
53964
  TemplatesPublish500,
53931
53965
  TemplatesPublish504.pipe(status(504))
53932
53966
  ]
53933
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.publish").annotate(Summary, "Publish an exact template commit").annotate(Description, "Builds an exact Git commit and entry path, stores the immutable executable and its assets, records the input contract it validates against, and makes that publication current."), get5("templatesGetPublication", "/v1/templates/:id/publications/:publicationId", {
53967
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.publish").annotate(Summary, "Publish an exact template commit").annotate(Description, "Builds an exact Git commit and entry path, stores the immutable compiled IR and its assets, records the input contract it validates against, and makes that publication current."), get5("templatesGetPublication", "/v1/templates/:id/publications/:publicationId", {
53934
53968
  params: TemplatesGetPublicationPathParams,
53935
53969
  success: TemplatesGetPublication200,
53936
53970
  error: [
@@ -54219,7 +54253,7 @@ var TemplatesGroup = class extends make36("Templates").add(get5("templatesList",
54219
54253
  TemplatesAddEntry500,
54220
54254
  TemplatesAddEntry504.pipe(status(504))
54221
54255
  ]
54222
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.addEntry").annotate(Summary, "Add a template entry").annotate(Description, "Registers an `emails/*.tsx` file in an existing template project as its own template and returns it with status 201. The file does not need to exist yet; publishing fails until a commit contains it. Repeating the call with the same entry path, name, and slug returns the existing template.")).annotate(Description, "Git-backed email template projects, workspace editing, immutable publications, and delivery defaults") {
54256
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.addEntry").annotate(Summary, "Add a template entry").annotate(Description, "Registers a `.tsx` file under `templates/` or `emails/` in an existing template project as its own template and returns it with status 201. The file does not need to exist yet; publishing fails until a commit contains it. Repeating the call with the same entry path, name, and slug returns the existing template.")).annotate(Description, "Git-backed email template projects, workspace editing, immutable publications, and delivery defaults") {
54223
54257
  };
54224
54258
  var UnsubscribeGroupsGroup = class extends make36("Unsubscribe Groups").add(get5("unsubscribeList", "/v1/unsubscribe-groups", {
54225
54259
  query: UnsubscribeListQuery,
@@ -54230,7 +54264,7 @@ var UnsubscribeGroupsGroup = class extends make36("Unsubscribe Groups").add(get5
54230
54264
  UnsubscribeList500,
54231
54265
  UnsubscribeList504.pipe(status(504))
54232
54266
  ]
54233
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "unsubscribe.list").annotate(Summary, "List unsubscribe groups").annotate(Description, "Returns a paginated list of the organization's unsubscribe groups."), post("unsubscribeCreate", "/v1/unsubscribe-groups", {
54267
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "unsubscribe.list").annotate(Summary, "List unsubscribe groups").annotate(Description, "Returns a paginated list of the organization's unsubscribe groups, the default group first."), post("unsubscribeCreate", "/v1/unsubscribe-groups", {
54234
54268
  payload: UnsubscribeCreateRequestJson,
54235
54269
  success: UnsubscribeCreate201.pipe(status(201)),
54236
54270
  error: [
@@ -54279,10 +54313,11 @@ var UnsubscribeGroupsGroup = class extends make36("Unsubscribe Groups").add(get5
54279
54313
  UnsubscribeRemove401.pipe(status(401)),
54280
54314
  UnsubscribeRemove403.pipe(status(403)),
54281
54315
  UnsubscribeRemove404.pipe(status(404)),
54316
+ UnsubscribeRemove409.pipe(status(409)),
54282
54317
  UnsubscribeRemove500,
54283
54318
  UnsubscribeRemove504.pipe(status(504))
54284
54319
  ]
54285
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "unsubscribe.remove").annotate(Summary, "Delete an unsubscribe group").annotate(Description, "Permanently deletes an unsubscribe group by its ID."), patch("unsubscribeUpdate", "/v1/unsubscribe-groups/:id", {
54320
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "unsubscribe.remove").annotate(Summary, "Delete an unsubscribe group").annotate(Description, "Permanently deletes an unsubscribe group by its ID. The default group cannot be deleted; make another group the default first. A group a scheduled or running campaign uses cannot be deleted until that run finishes or is cancelled."), patch("unsubscribeUpdate", "/v1/unsubscribe-groups/:id", {
54286
54321
  params: UnsubscribeUpdatePathParams,
54287
54322
  payload: UnsubscribeUpdateRequestJson,
54288
54323
  success: UnsubscribeUpdate200,
@@ -54293,7 +54328,17 @@ var UnsubscribeGroupsGroup = class extends make36("Unsubscribe Groups").add(get5
54293
54328
  UnsubscribeUpdate500,
54294
54329
  UnsubscribeUpdate504.pipe(status(504))
54295
54330
  ]
54296
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "unsubscribe.update").annotate(Summary, "Update an unsubscribe group").annotate(Description, "Updates the name or description of an unsubscribe group."), get5("unsubscribeCheckSubscription", "/v1/unsubscribe-groups/check", {
54331
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "unsubscribe.update").annotate(Summary, "Update an unsubscribe group").annotate(Description, "Updates the name or description of an unsubscribe group."), post("unsubscribeSetDefault", "/v1/unsubscribe-groups/:id/default", {
54332
+ params: UnsubscribeSetDefaultPathParams,
54333
+ success: UnsubscribeSetDefault200,
54334
+ error: [
54335
+ UnsubscribeSetDefault401.pipe(status(401)),
54336
+ UnsubscribeSetDefault403.pipe(status(403)),
54337
+ UnsubscribeSetDefault404.pipe(status(404)),
54338
+ UnsubscribeSetDefault500,
54339
+ UnsubscribeSetDefault504.pipe(status(504))
54340
+ ]
54341
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "unsubscribe.setDefault").annotate(Summary, "Make an unsubscribe group the default").annotate(Description, "Makes this group the organization's default: campaigns that name no group, and whose template and sending domain name none, unsubscribe recipients from it."), get5("unsubscribeCheckSubscription", "/v1/unsubscribe-groups/check", {
54297
54342
  query: UnsubscribeCheckSubscriptionQuery,
54298
54343
  success: UnsubscribeCheckSubscription200,
54299
54344
  error: [
@@ -54418,7 +54463,7 @@ var WebhooksGroup = class extends make36("Webhooks").add(get5("webhooksList", "/
54418
54463
  ]
54419
54464
  }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "webhooks.getStats").annotate(Summary, "Get webhook delivery statistics").annotate(Description, "Returns aggregate pending, delivered, exhausted, and cancelled delivery counts for a webhook endpoint, plus the same counts per organization-local calendar day over the selected period of 7, 30, or 90 days. Deliveries are retained for 90 days, so 90d is the whole history.")).annotate(Description, "Customer event webhook management, testing, and delivery logs") {
54420
54465
  };
54421
- var SamvaApi = class extends make34("SamvaApi").annotate(Title, "Samva API").annotate(Version2, "1.0.0").annotate(Description, "Samva is a developer email API for product teams: deliverability, versioned templates, inbound threading, and signed webhooks behind one typed send call.").annotate(Servers, [{ url: "https://api.samva.dev" }]).add(AnalyticsGroup, APIKeysGroup, AttachmentsGroup, BrandsGroup, CampaignsGroup, ContactGroupsGroup, ContactsGroup, ConversationsGroup, CustomFieldsGroup, EmailGroup, MediaGroup, MessagesGroup, ScheduledMessagesGroup, OperationsGroup, OrganizationsGroup, TemplatesGroup, UnsubscribeGroupsGroup, WebhooksGroup) {
54466
+ var SamvaApi = class extends make34("SamvaApi").annotate(Title, "Samva API").annotate(Version2, "1.0.0").annotate(Description, "Samva is the email API for your app. Send transactional email in one typed call. Delivery events and customer replies come back as signed webhooks, and replies thread to the email they answer.").annotate(Servers, [{ url: "https://api.samva.dev" }]).add(AnalyticsGroup, APIKeysGroup, AttachmentsGroup, BrandsGroup, CampaignsGroup, ContactGroupsGroup, ContactsGroup, ConversationsGroup, CustomFieldsGroup, EmailGroup, MediaGroup, MessagesGroup, ScheduledMessagesGroup, OperationsGroup, OrganizationsGroup, TemplatesGroup, UnsubscribeGroupsGroup, WebhooksGroup) {
54422
54467
  };
54423
54468
  var DEFAULT_BASE_URL = "https://api.samva.dev";
54424
54469
  var Service3 = class extends Service()("samva/Client") {
@@ -54617,7 +54662,7 @@ var generateIdempotencyKey = gen2(function* () {
54617
54662
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
54618
54663
  });
54619
54664
 
54620
- // ../sdk/dist/operation-Cb5V9FJ7.js
54665
+ // ../sdk/dist/operation-D2nEeKBD.js
54621
54666
  var SDK_OPERATION_CALLS = {
54622
54667
  brandsList: { kind: "none" },
54623
54668
  brandsCreate: { kind: "payload" },
@@ -55122,6 +55167,7 @@ var SDK_OPERATION_CALLS = {
55122
55167
  },
55123
55168
  organizationsCreate: { kind: "payload" },
55124
55169
  organizationsGet: { kind: "none" },
55170
+ organizationsGetProfile: { kind: "none" },
55125
55171
  organizationsUpdate: { kind: "payload" },
55126
55172
  organizationsGetUsage: { kind: "params" },
55127
55173
  organizationsDelete: { kind: "idempotentPayload" },
@@ -55219,6 +55265,10 @@ var SDK_OPERATION_CALLS = {
55219
55265
  kind: "path",
55220
55266
  paths: ["id"]
55221
55267
  },
55268
+ unsubscribeSetDefault: {
55269
+ kind: "path",
55270
+ paths: ["id"]
55271
+ },
55222
55272
  unsubscribeListMembers: {
55223
55273
  kind: "pathParams",
55224
55274
  paths: ["groupId"]
@@ -55581,6 +55631,7 @@ var retry4 = make38("operationsRetry");
55581
55631
  // ../sdk/dist/effect/organizations.js
55582
55632
  var create9 = make38("organizationsCreate");
55583
55633
  var get13 = make38("organizationsGet");
55634
+ var getProfile = make38("organizationsGetProfile");
55584
55635
  var update9 = make38("organizationsUpdate");
55585
55636
  var getUsage = make38("organizationsGetUsage");
55586
55637
  var deleteOrganization = make38("organizationsDelete");
@@ -55632,6 +55683,7 @@ var list13 = make38("unsubscribeList");
55632
55683
  var create12 = make38("unsubscribeCreate");
55633
55684
  var update11 = make38("unsubscribeUpdate");
55634
55685
  var remove13 = make38("unsubscribeRemove");
55686
+ var setDefault2 = make38("unsubscribeSetDefault");
55635
55687
  var listMembers = make38("unsubscribeListMembers");
55636
55688
  var addEmails = make38("unsubscribeAddEmails");
55637
55689
  var removeEmails = make38("unsubscribeRemoveEmails");
@@ -55815,7 +55867,10 @@ var commandTree = {
55815
55867
  credential: {},
55816
55868
  add: {},
55817
55869
  move: {},
55870
+ dev: {},
55818
55871
  check: {},
55872
+ render: {},
55873
+ snapshot: {},
55819
55874
  build: {},
55820
55875
  export: {},
55821
55876
  types: {},
@@ -56169,18 +56224,6 @@ var decodeStoredCredentials = (profile, rawCredentials) => try_3({
56169
56224
  })));
56170
56225
  var resolveApiUrl = map7(resolveExecutionSelection, (selection) => selection.apiUrl);
56171
56226
  var isCredentialExpired = (credentials, nowMillis) => credentials.expiresAt <= Math.floor(nowMillis / 1000);
56172
- var resolveAuthToken = gen2(function* () {
56173
- const context = yield* resolveExecutionContext;
56174
- if (!context.credentials)
56175
- return;
56176
- if (isCredentialExpired(context.credentials, yield* currentTimeMillis2)) {
56177
- return yield* new CliAuthError({
56178
- code: "expired_credentials",
56179
- message: "Your session has expired. Run `samva login` to sign in again."
56180
- });
56181
- }
56182
- return context.credentials.accessToken;
56183
- });
56184
56227
  var writeCredentials = fn2("CliConfig.writeCredentials")(function* (credentials) {
56185
56228
  const context = yield* resolveExecutionSelection;
56186
56229
  const config = yield* readConfig;
@@ -56588,7 +56631,7 @@ var templateDataFlag = String8("template-data").pipe(withDescription2("JSON obje
56588
56631
  var includeTagsFlag = String8("include-tags").pipe(withDescription2("Contact tags to include (comma-separated or repeated)"), atLeast2(0));
56589
56632
  var excludeTagsFlag = String8("exclude-tags").pipe(withDescription2("Contact tags to exclude from the include set (comma-separated or repeated)"), atLeast2(0));
56590
56633
  var contactIdsFlag = String8("contact-ids").pipe(withDescription2("Explicit contact ids to target (comma-separated or repeated)"), atLeast2(0));
56591
- var unsubscribeGroupIdFlag = String8("unsubscribe-group-id").pipe(withDescription2("Unsubscribe group id applied to every send"), optional4);
56634
+ var unsubscribeGroupIdFlag = String8("unsubscribe-group-id").pipe(withDescription2("Unsubscribe group id applied to every send; overrides the organization's default group"), optional4);
56592
56635
  var pageSizeFlag = Int5("page-size").pipe(withDescription2("Recipients dispatched per batch"), optional4);
56593
56636
  var dryRunFlag = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault4(false));
56594
56637
  var trackOpensFlag = Literals3("track-opens", ["on", "off"]).pipe(withDescription2("Override open tracking for campaign sends; recipient opt-out wins"), optional4);
@@ -57263,14 +57306,26 @@ var check = (name, run) => run.pipe(match6({
57263
57306
  }));
57264
57307
  var reachability = (apiUrl) => check("API reachability", getText(`${apiUrl.replace(/\/+$/, "")}/v1/health/ping`).pipe(flatMap3((response) => response.ok ? succeed6("Health endpoint responded") : fail6(new SamvaCliError({ message: `Health endpoint returned ${response.status}` })))));
57265
57308
  var orgAccess = check("Organization permissions", getClientProvider.pipe(flatMap3((client) => get13().pipe(client.provide, map7((organization) => `${organization.name} (${organization.slug})`), mapError2((error) => new SamvaCliError({ message: formatApiError("Organization read failed", error) }))))));
57266
- var domains = check("Sending domains", getClientProvider.pipe(flatMap3((client) => listDomains({ limit: "1" }).pipe(client.provide, map7((result) => {
57267
- const verified = result.items.filter((domain) => domain.phase === "ready").length;
57268
- return `${result.pagination.total} configured; ${verified} verified on this page`;
57269
- }), mapError2((error) => new SamvaCliError({ message: formatApiError("Domain read failed", error) }))))));
57270
- var senders = check("Senders", getClientProvider.pipe(flatMap3((client) => listSenders({ limit: "1" }).pipe(client.provide, map7((result) => {
57271
- const verified = result.items.filter((sender) => sender.verificationStatus === "verified").length;
57272
- return `${result.pagination.total} configured; ${verified} verified on this page`;
57273
- }), mapError2((error) => new SamvaCliError({ message: formatApiError("Sender read failed", error) }))))));
57309
+ var sendReview = getClientProvider.pipe(flatMap3((client) => getOnboardingReview().pipe(client.provide, map7((review) => ({
57310
+ name: "Send review",
57311
+ status: review.canSend ? "passed" : review.code === "review_rejected" || review.code === "identity_review_rejected" ? "failed" : "unavailable",
57312
+ detail: review.canSend ? `${review.decision}; trust tier ${review.trustTier}` : `${review.code}; next action: ${review.nextAction}`
57313
+ })))), catch_2((error) => succeed6({
57314
+ name: "Send review",
57315
+ status: "failed",
57316
+ detail: formatApiError("Send review read failed", error)
57317
+ })));
57318
+ var identityCount = (name, noun, read) => getClientProvider.pipe(flatMap3((client) => client.provide(read)), map7(({ pagination }) => ({
57319
+ name,
57320
+ status: pagination.total > 0 ? "passed" : "unavailable",
57321
+ detail: `${pagination.total} ${noun}`
57322
+ })), catch_2((error) => succeed6({
57323
+ name,
57324
+ status: "failed",
57325
+ detail: formatApiError(`${name} read failed`, error)
57326
+ })));
57327
+ var domains = identityCount("Sending domains", "sendable", listDomains({ sendable: "true", limit: "1" }));
57328
+ var senders = identityCount("Senders", "verified and active", listSenders({ verificationStatus: "verified", isActive: "true", limit: "1" }));
57274
57329
  var domainVerificationReadback = getClientProvider.pipe(flatMap3((client) => listDomains({ limit: "1" }).pipe(client.provide, mapError2((error) => new SamvaCliError({ message: formatApiError("Domain verification read failed", error) })), flatMap3((result) => {
57275
57330
  const domain = result.items[0];
57276
57331
  if (!domain) {
@@ -57298,19 +57353,29 @@ var domainVerificationReadback = getClientProvider.pipe(flatMap3((client) => lis
57298
57353
  var doctorCommand = make28("doctor", { json: jsonFlag }).pipe(withDescription3("Read local configuration and remote email state"), withExamples([{ command: "samva email doctor --json" }]), withHandler(({ json }) => gen2(function* () {
57299
57354
  const context = yield* resolveExecutionContext;
57300
57355
  const authSource = context.apiKey ? "SAMVA_API_KEY" : context.credentials ? "stored OAuth credential" : "none";
57301
- const checks = yield* all2([
57356
+ const [reachable, org, review, domainCount, senderCount, verification] = yield* all2([
57302
57357
  reachability(context.apiUrl),
57303
57358
  orgAccess,
57359
+ sendReview,
57304
57360
  domains,
57305
57361
  senders,
57306
57362
  domainVerificationReadback
57307
- ]);
57363
+ ], { concurrency: "unbounded" });
57364
+ const checks = [
57365
+ reachable,
57366
+ org,
57367
+ review,
57368
+ domainCount,
57369
+ senderCount,
57370
+ verification
57371
+ ];
57308
57372
  const ok = checks.every((result) => result.status !== "failed");
57309
57373
  const payload = {
57310
57374
  schemaVersion: 1,
57311
57375
  mode: "read-only",
57312
57376
  ok,
57313
- limitations: "Checks CLI/API visibility only. It does not send, mutate, replay fixtures, verify DNS, or verify provider readiness.",
57377
+ readyToSend: review.status === "passed" && (domainCount.status === "passed" || senderCount.status === "passed"),
57378
+ limitations: "Reads the send fence and identity lists only. It does not send, mutate, replay fixtures, or check DNS itself.",
57314
57379
  resolved: {
57315
57380
  profile: context.profile,
57316
57381
  apiUrl: context.apiUrl,
@@ -57327,7 +57392,8 @@ var doctorCommand = make28("doctor", { json: jsonFlag }).pipe(withDescription3("
57327
57392
  ["Profile", payload.resolved.profile],
57328
57393
  ["API", payload.resolved.apiUrl],
57329
57394
  ["Auth source", payload.resolved.authSource],
57330
- ["Organization", payload.resolved.organization]
57395
+ ["Organization", payload.resolved.organization],
57396
+ ["Ready to send", payload.readyToSend ? "yes" : "no"]
57331
57397
  ]);
57332
57398
  yield* log(`
57333
57399
  Read-only checks:`);
@@ -57727,7 +57793,7 @@ var readStatus = gen2(function* () {
57727
57793
  message: formatApiError("Could not get email review status", error)
57728
57794
  })));
57729
57795
  });
57730
- var status3 = make28("status", { json: jsonFlag }).pipe(withDescription3("Get workspace and sending-identity review status"), withExamples([{ command: "samva email review status --json" }]), withHandler(({ json }) => gen2(function* () {
57796
+ var status3 = make28("status", { json: jsonFlag }).pipe(withDescription3("Get organization and sending-identity review status"), withExamples([{ command: "samva email review status --json" }]), withHandler(({ json }) => gen2(function* () {
57731
57797
  const data = yield* readStatus;
57732
57798
  if (json)
57733
57799
  return yield* printJson(data);
@@ -57747,7 +57813,7 @@ Sending identities:`);
57747
57813
  }
57748
57814
  }
57749
57815
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
57750
- var reapply = make28("reapply", { json: jsonFlag }).pipe(withDescription3("Resubmit a rejected workspace for email review"), withExamples([{ command: "samva email review reapply --json" }]), withHandler(({ json }) => gen2(function* () {
57816
+ var reapply = make28("reapply", { json: jsonFlag }).pipe(withDescription3("Resubmit a rejected organization for email review"), withExamples([{ command: "samva email review reapply --json" }]), withHandler(({ json }) => gen2(function* () {
57751
57817
  const client = yield* getClientProvider;
57752
57818
  const current = yield* readStatus;
57753
57819
  const data = yield* reapplyOnboardingReview({
@@ -57788,6 +57854,7 @@ var projectMessage = (message) => ({
57788
57854
  id: message.id,
57789
57855
  channel: message.channel,
57790
57856
  status: message.status,
57857
+ from: message.emailDeliveryDetails[0]?.fromEmail ?? null,
57791
57858
  conversationId: message.conversationId,
57792
57859
  createdAt: message.createdAt,
57793
57860
  updatedAt: message.updatedAt,
@@ -57808,6 +57875,7 @@ var printMessage = (message) => printFields([
57808
57875
  ["Message ID", message.id],
57809
57876
  ["Channel", message.channel],
57810
57877
  ["Status", message.status],
57878
+ ["From", message.from],
57811
57879
  ["Waiting", waitingLine(message)],
57812
57880
  ["Conversation", message.conversationId],
57813
57881
  ["Created", message.createdAt],
@@ -58040,6 +58108,8 @@ var textFlag2 = String8("text").pipe(withDescription2("Plain-text body (use `-`
58040
58108
  var templateIdFlag2 = String8("template-id").pipe(withDescription2("Template id to render instead of inline body"), optional4);
58041
58109
  var templateSlugFlag2 = String8("template-slug").pipe(withDescription2("Template handle to render instead of inline body, e.g. welcome-email"), optional4);
58042
58110
  var templateDataFlag2 = String8("template-data").pipe(withDescription2("JSON object of variables for the template"), optional4);
58111
+ var localeFlag = String8("locale").pipe(withDescription2("BCP 47 language tag (for example en-US) for dates, numbers and money in a template send; defaults to the template's locale"), optional4);
58112
+ var timeZoneFlag = String8("time-zone").pipe(withDescription2("IANA time zone (for example America/New_York) that a template send renders dates in; defaults to UTC"), optional4);
58043
58113
  var replyToMessageFlag = String8("reply-to-message-id").pipe(withDescription2("Message id this email replies to (threads the conversation)"), optional4);
58044
58114
  var replyToAddressFlag = String8("reply-to").pipe(withDescription2("Reply-To header email address"), optional4);
58045
58115
  var idempotencyKeyFlag2 = String8("idempotency-key").pipe(withDescription2("Key (1-255 characters) so identical retries return the original email"), optional4);
@@ -58074,6 +58144,8 @@ var send2 = make28("send", {
58074
58144
  inputContractId: inputContractIdFlag,
58075
58145
  templateSlug: templateSlugFlag2,
58076
58146
  templateData: templateDataFlag2,
58147
+ locale: localeFlag,
58148
+ timeZone: timeZoneFlag,
58077
58149
  replyTo: replyToMessageFlag,
58078
58150
  replyToAddress: replyToAddressFlag,
58079
58151
  idempotencyKey: idempotencyKeyFlag2,
@@ -58092,6 +58164,10 @@ var send2 = make28("send", {
58092
58164
  command: `samva email send --to you@example.com --template-slug welcome-email --template-data '{"name":"Ada"}'`,
58093
58165
  description: "Render a template by handle"
58094
58166
  },
58167
+ {
58168
+ command: `samva email send --to you@example.com --template-slug order-receipt --template-data '{"total":42}' --locale de-DE --time-zone Europe/Berlin`,
58169
+ description: "Render a template's dates and money for a locale and time zone"
58170
+ },
58095
58171
  {
58096
58172
  command: 'echo "Hello" | samva email send --to you@example.com --subject Hi --text -',
58097
58173
  description: "Pipe plain-text body from stdin"
@@ -58116,6 +58192,8 @@ var send2 = make28("send", {
58116
58192
  inputContractId,
58117
58193
  templateSlug,
58118
58194
  templateData,
58195
+ locale,
58196
+ timeZone,
58119
58197
  replyTo,
58120
58198
  replyToAddress,
58121
58199
  idempotencyKey,
@@ -58144,6 +58222,8 @@ var send2 = make28("send", {
58144
58222
  const ccValue = getOrUndefined(cc);
58145
58223
  const bccValue = getOrUndefined(bcc);
58146
58224
  const templateDataValue = yield* parseTemplateData2(templateData);
58225
+ const localeValue = getOrUndefined(locale);
58226
+ const timeZoneValue = getOrUndefined(timeZone);
58147
58227
  if (idempotencyKeyValue !== undefined && (idempotencyKeyValue.length < 1 || idempotencyKeyValue.length > 255)) {
58148
58228
  return yield* new SamvaCliError({
58149
58229
  message: "--idempotency-key must contain between 1 and 255 characters."
@@ -58155,6 +58235,11 @@ var send2 = make28("send", {
58155
58235
  });
58156
58236
  }
58157
58237
  const templateRefValue = templateSlugValue ?? templateIdValue;
58238
+ if ((localeValue !== undefined || timeZoneValue !== undefined) && !templateIdValue && !templateSlugValue) {
58239
+ return yield* new SamvaCliError({
58240
+ message: "--locale and --time-zone apply to template sends."
58241
+ });
58242
+ }
58158
58243
  if (templateRefValue && subjectValue) {
58159
58244
  return yield* new SamvaCliError({
58160
58245
  message: "Template sends derive their subject from the template. Omit --subject."
@@ -58196,6 +58281,8 @@ var send2 = make28("send", {
58196
58281
  ...reference,
58197
58282
  ...templateSlugValue === undefined ? {} : { templateSlug: templateSlugValue },
58198
58283
  ...templateDataValue === undefined ? {} : { templateData: templateDataValue },
58284
+ ...localeValue === undefined ? {} : { locale: localeValue },
58285
+ ...timeZoneValue === undefined ? {} : { timeZone: timeZoneValue },
58199
58286
  ...replyToValue === undefined ? {} : { inReplyToMessageId: replyToValue },
58200
58287
  ...replyToAddressValue === undefined ? {} : { replyTo: replyToAddressValue },
58201
58288
  ...trackOpensValue !== undefined || trackClicksValue !== undefined ? {
@@ -58453,7 +58540,7 @@ var remove16 = make28("remove", {
58453
58540
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the deletion plan without calling the API"), withDefault4(false)),
58454
58541
  yes: Boolean6("yes").pipe(withDescription2("Confirm permanent deletion (required for non-interactive use)"), withDefault4(false)),
58455
58542
  json: jsonFlag
58456
- }).pipe(withDescription3("Permanently remove a sender identity"), withExamples([
58543
+ }).pipe(withDescription3("Permanently remove a sender"), withExamples([
58457
58544
  { command: "samva email senders remove sender_abc123 --dry-run" },
58458
58545
  { command: "samva email senders remove sender_abc123 --yes" }
58459
58546
  ]), withHandler(({ id, dryRun, yes, json }) => gen2(function* () {
@@ -58613,13 +58700,13 @@ var emailCommand = make28("email").pipe(withDescription3("Send, inspect, and con
58613
58700
  trackingCommand
58614
58701
  ]));
58615
58702
 
58616
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/package.mjs
58703
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/package.mjs
58617
58704
  var version2 = "1.7.5";
58618
58705
 
58619
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/version.mjs
58706
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/version.mjs
58620
58707
  var PACKAGE_VERSION = version2;
58621
58708
 
58622
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/broadcast-channel.mjs
58709
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/broadcast-channel.mjs
58623
58710
  var kBroadcastChannel = Symbol.for("better-auth:broadcast-channel");
58624
58711
  var now = () => Math.floor(Date.now() / 1000);
58625
58712
  var WindowBroadcastChannel = class {
@@ -58865,7 +58952,7 @@ var onMount = ($store, initialize) => {
58865
58952
  };
58866
58953
  });
58867
58954
  };
58868
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/equality.mjs
58955
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/equality.mjs
58869
58956
  function isPlainObject(value) {
58870
58957
  if (typeof value !== "object" || value === null)
58871
58958
  return false;
@@ -58902,7 +58989,7 @@ function withEquality(store, isEqual) {
58902
58989
  });
58903
58990
  }
58904
58991
 
58905
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/focus-manager.mjs
58992
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/focus-manager.mjs
58906
58993
  var kFocusManager = Symbol.for("better-auth:focus-manager");
58907
58994
  var WindowFocusManager = class {
58908
58995
  listeners = /* @__PURE__ */ new Set;
@@ -58934,7 +59021,7 @@ function getGlobalFocusManager() {
58934
59021
  return globalThis[kFocusManager];
58935
59022
  }
58936
59023
 
58937
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/online-manager.mjs
59024
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/online-manager.mjs
58938
59025
  var kOnlineManager = Symbol.for("better-auth:online-manager");
58939
59026
  var WindowOnlineManager = class {
58940
59027
  listeners = /* @__PURE__ */ new Set;
@@ -58968,7 +59055,7 @@ function getGlobalOnlineManager() {
58968
59055
  return globalThis[kOnlineManager];
58969
59056
  }
58970
59057
 
58971
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/parser.mjs
59058
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/parser.mjs
58972
59059
  var PROTO_POLLUTION_PATTERNS = {
58973
59060
  proto: /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/,
58974
59061
  constructor: /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/,
@@ -59046,7 +59133,7 @@ function parseJSON(value, options = { strict: true }) {
59046
59133
  return betterJSONParse(value, options);
59047
59134
  }
59048
59135
 
59049
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/query.mjs
59136
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/query.mjs
59050
59137
  var isServer = () => typeof window === "undefined";
59051
59138
  function isAuthQueryStateEqual(a, b) {
59052
59139
  return isJsonEqual(a.data, b.data) && a.error === b.error && a.isPending === b.isPending && a.isRefetching === b.isRefetching && a.refetch === b.refetch;
@@ -59172,7 +59259,7 @@ var useAuthQuery = (initializedAtom, path, $fetch, options) => {
59172
59259
  return value;
59173
59260
  };
59174
59261
 
59175
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/session-refresh.mjs
59262
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/session-refresh.mjs
59176
59263
  var now2 = () => Math.floor(Date.now() / 1000);
59177
59264
  var FOCUS_REFETCH_RATE_LIMIT_SECONDS = 5;
59178
59265
  function createSessionRefreshManager(opts) {
@@ -59501,7 +59588,7 @@ var BetterAuthError = class extends Error {
59501
59588
  }
59502
59589
  };
59503
59590
 
59504
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/utils/url.mjs
59591
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/utils/url.mjs
59505
59592
  var SLASH_CHAR_CODE = 47;
59506
59593
  function trimTrailingSlashes(value) {
59507
59594
  let end = value.length;
@@ -59608,7 +59695,7 @@ function isSafeUrlScheme(value) {
59608
59695
  return !DANGEROUS_URL_SCHEMES.includes(parsed.protocol);
59609
59696
  }
59610
59697
 
59611
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/fetch-plugins.mjs
59698
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/fetch-plugins.mjs
59612
59699
  var redirectPlugin = {
59613
59700
  id: "redirect",
59614
59701
  name: "Redirect",
@@ -59624,7 +59711,7 @@ var redirectPlugin = {
59624
59711
  } }
59625
59712
  };
59626
59713
 
59627
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/session-atom.mjs
59714
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/session-atom.mjs
59628
59715
  var isServer2 = () => typeof window === "undefined";
59629
59716
  var SESSION_MOUNT_DEDUPE_INTERVAL = STORE_UNMOUNT_DELAY;
59630
59717
  function hydrateSessionAtom(sessionAtom, session) {
@@ -60488,7 +60575,7 @@ var createFetch = (config) => {
60488
60575
  return $fetch;
60489
60576
  };
60490
60577
 
60491
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/config.mjs
60578
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/config.mjs
60492
60579
  var resolvePublicAuthUrl = (basePath) => {
60493
60580
  if (typeof process === "undefined")
60494
60581
  return;
@@ -60607,7 +60694,7 @@ var getClientConfig = (options, loadEnv) => {
60607
60694
  };
60608
60695
  };
60609
60696
 
60610
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/utils/is-atom.mjs
60697
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/utils/is-atom.mjs
60611
60698
  function isAtom(value) {
60612
60699
  return typeof value === "object" && value !== null && "get" in value && typeof value.get === "function" && "lc" in value && typeof value.lc === "number";
60613
60700
  }
@@ -60625,7 +60712,7 @@ function toKebabCase(input) {
60625
60712
  return splitWords(input).map((word) => word.toLowerCase()).join("-");
60626
60713
  }
60627
60714
 
60628
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/proxy.mjs
60715
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/proxy.mjs
60629
60716
  function getMethod2(path, knownPathMethods, args) {
60630
60717
  const method = knownPathMethods[path];
60631
60718
  const { fetchOptions, query: _query, ...body } = args || {};
@@ -60707,7 +60794,7 @@ function createDynamicPathProxy(routes, client, knownPathMethods, atoms, atomLis
60707
60794
  return createProxy();
60708
60795
  }
60709
60796
 
60710
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/client/vanilla.mjs
60797
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/client/vanilla.mjs
60711
60798
  function createAuthClient(options) {
60712
60799
  const { pluginPathMethods, pluginsActions, pluginsAtoms, hydrateSession, $fetch, atomListeners, $store } = getClientConfig(options);
60713
60800
  const resolvedHooks = {};
@@ -60721,7 +60808,7 @@ function createAuthClient(options) {
60721
60808
  $store
60722
60809
  }, $fetch, pluginPathMethods, pluginsAtoms, atomListeners);
60723
60810
  }
60724
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/plugins/device-authorization/client.mjs
60811
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/plugins/device-authorization/client.mjs
60725
60812
  var deviceAuthorizationClient = () => {
60726
60813
  return {
60727
60814
  id: "device-authorization",
@@ -60736,7 +60823,7 @@ var deviceAuthorizationClient = () => {
60736
60823
  }
60737
60824
  };
60738
60825
  };
60739
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/plugins/access/access.mjs
60826
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/plugins/access/access.mjs
60740
60827
  function unknownResourceResponse(requestedResource) {
60741
60828
  return {
60742
60829
  success: false,
@@ -60822,7 +60909,7 @@ function createAccessControl(s) {
60822
60909
  };
60823
60910
  }
60824
60911
 
60825
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/plugins/organization/access/statement.mjs
60912
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/plugins/organization/access/statement.mjs
60826
60913
  var defaultStatements = {
60827
60914
  organization: ["update", "delete"],
60828
60915
  member: [
@@ -60897,7 +60984,7 @@ var defaultRoles = {
60897
60984
  member: memberAc
60898
60985
  };
60899
60986
 
60900
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/plugins/organization/error-codes.mjs
60987
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/plugins/organization/error-codes.mjs
60901
60988
  var ORGANIZATION_ERROR_CODES = defineErrorCodes({
60902
60989
  YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION: "You are not allowed to create a new organization",
60903
60990
  YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS: "You have reached the maximum number of organizations",
@@ -60960,7 +61047,7 @@ var ORGANIZATION_ERROR_CODES = defineErrorCodes({
60960
61047
  INVALID_TEAM_ID: "Team id contains a reserved character"
60961
61048
  });
60962
61049
 
60963
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/plugins/organization/permission.mjs
61050
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/plugins/organization/permission.mjs
60964
61051
  var hasPermissionFn = (input, acRoles) => {
60965
61052
  if (!input.permissions)
60966
61053
  return false;
@@ -60976,7 +61063,7 @@ var hasPermissionFn = (input, acRoles) => {
60976
61063
  return false;
60977
61064
  };
60978
61065
 
60979
- // ../../node_modules/.bun/better-auth@1.7.5+a39002ffdfd1704b/node_modules/better-auth/dist/plugins/organization/client.mjs
61066
+ // ../../node_modules/.bun/better-auth@1.7.5+d84e686d419eddf2/node_modules/better-auth/dist/plugins/organization/client.mjs
60980
61067
  var clientSideHasPermission = (input) => {
60981
61068
  return hasPermissionFn(input, input.options.roles || defaultRoles);
60982
61069
  };
@@ -61865,26 +61952,37 @@ var scheduledMessagesCommand = make28("scheduled-messages").pipe(withDescription
61865
61952
 
61866
61953
  // src/commands/templates.ts
61867
61954
  import { constants as constants2 } from "fs";
61868
- import { access as access3, stat as stat3 } from "fs/promises";
61869
- import { dirname as dirname3, resolve as resolve10 } from "path";
61870
- import { pathToFileURL as pathToFileURL2 } from "url";
61955
+ import { access as access3, stat as stat4 } from "fs/promises";
61956
+ import { dirname as dirname4, resolve as resolve14 } from "path";
61957
+ import { pathToFileURL as pathToFileURL3 } from "url";
61871
61958
 
61872
- // src/commands/templates/local-build.ts
61959
+ // src/commands/templates/dev-command.ts
61873
61960
  import { resolve as resolve6 } from "path";
61874
- var buildDir = (dir, assetBase) => tryPromise2({
61961
+
61962
+ // src/commands/templates/flags.ts
61963
+ var dirFlag = Directory2("dir").pipe(withDescription2("Directory containing your .tsx templates (default: templates/ and emails/, whichever exist)"), optional4);
61964
+ var assetBaseFlag = String8("asset-base").pipe(withDescription2("Actual HTTPS serving base for imported email assets"), optional4);
61965
+ var offlineFlag = Boolean6("offline").pipe(withDescription2("Use the cached brand in .samva/brands instead of fetching it"), withDefault4(false));
61966
+
61967
+ // src/commands/templates/toolchain.ts
61968
+ var TOOLCHAIN_INSTALL = "npm install --save-dev @samva/markup @samva/vite vite";
61969
+ var MISSING_MODULE = /Cannot find (?:package|module) '([^']+)'/;
61970
+ var toolchainFailure = (stderr, action) => {
61971
+ const missing = MISSING_MODULE.exec(stderr)?.[1];
61972
+ if (stderr.includes("`vite` is required")) {
61973
+ return `${action}: vite is not installed in this project. Install the authoring toolchain with \`${TOOLCHAIN_INSTALL}\`.`;
61974
+ }
61975
+ if (missing !== undefined && /^(@samva\/|vite)/.test(missing)) {
61976
+ return `${action}: ${missing} is not installed in this project. Install the authoring toolchain with \`${TOOLCHAIN_INSTALL}\`.`;
61977
+ }
61978
+ return `${action}: ${stderr.trim() || "the toolchain process failed."}`;
61979
+ };
61980
+ var NODE_MISSING = "Install Node.js 22+ to run template authoring commands.";
61981
+ var runToolchain = (input) => tryPromise2({
61875
61982
  try: async () => {
61876
- const child = Bun.spawn([
61877
- "node",
61878
- "--input-type=module",
61879
- "--eval",
61880
- 'import { buildTemplates } from "@samva/vite/build"; const dir = process.env.SAMVA_TEMPLATE_BUILD_DIR; if (dir === undefined) throw new Error("SAMVA_TEMPLATE_BUILD_DIR is required"); const result = await buildTemplates(dir, { root: process.cwd(), assetBase: process.env.SAMVA_TEMPLATE_ASSET_BASE }); process.stdout.write(JSON.stringify(result));'
61881
- ], {
61983
+ const child = Bun.spawn(["node", "--input-type=module", "--eval", input.script], {
61882
61984
  cwd: process.cwd(),
61883
- env: {
61884
- ...Bun.env,
61885
- SAMVA_TEMPLATE_BUILD_DIR: resolve6(dir),
61886
- SAMVA_TEMPLATE_ASSET_BASE: assetBase
61887
- },
61985
+ env: { ...Bun.env, SAMVA_TOOLCHAIN_INPUT: JSON.stringify(input.options) },
61888
61986
  stdout: "pipe",
61889
61987
  stderr: "pipe"
61890
61988
  });
@@ -61894,17 +61992,122 @@ var buildDir = (dir, assetBase) => tryPromise2({
61894
61992
  child.exited
61895
61993
  ]);
61896
61994
  if (exitCode !== 0) {
61897
- return Promise.reject(new SamvaCliError({ message: stderr.trim() || "Template build process failed." }));
61995
+ return Promise.reject(new SamvaCliError({ message: toolchainFailure(stderr, input.action) }));
61898
61996
  }
61899
61997
  return JSON.parse(stdout);
61900
61998
  },
61901
- catch: (cause) => new SamvaCliError({
61902
- message: `Could not build templates in ${dir}. Install Node.js 22+, @samva/markup, @samva/vite, and vite in this project: ${String(cause)}`
61999
+ catch: (cause) => cause instanceof SamvaCliError ? cause : new SamvaCliError({
62000
+ message: `${input.action}: ${NODE_MISSING} ${String(cause)}`
62001
+ })
62002
+ });
62003
+ var OPTIONS_PRELUDE = "const options = JSON.parse(process.env.SAMVA_TOOLCHAIN_INPUT);";
62004
+
62005
+ // src/commands/templates/dev-command.ts
62006
+ var DEV_SCRIPT = `import { startDevServer } from "@samva/vite/dev";
62007
+ ${OPTIONS_PRELUDE}
62008
+ const server = await startDevServer(options);
62009
+ process.stdout.write("Samva template editor: " + server.url + "\\nPress Ctrl+C to stop.\\n");
62010
+ const stop = () => { server.close().finally(() => process.exit(0)); };
62011
+ process.on("SIGINT", stop);
62012
+ process.on("SIGTERM", stop);`;
62013
+ var runDevServer = (input) => tryPromise2({
62014
+ try: async () => {
62015
+ const child = Bun.spawn(["node", "--input-type=module", "--eval", DEV_SCRIPT], {
62016
+ cwd: process.cwd(),
62017
+ env: {
62018
+ ...Bun.env,
62019
+ SAMVA_TOOLCHAIN_INPUT: JSON.stringify({
62020
+ root: process.cwd(),
62021
+ ...input.dir === undefined ? {} : { dir: input.dir },
62022
+ ...input.port === undefined ? {} : { port: input.port },
62023
+ ...input.host === undefined ? {} : { host: input.host },
62024
+ open: input.open
62025
+ })
62026
+ },
62027
+ stdout: "inherit",
62028
+ stderr: "pipe"
62029
+ });
62030
+ const forward = (signal) => () => child.kill(signal);
62031
+ const onInterrupt = forward("SIGINT");
62032
+ const onTerminate = forward("SIGTERM");
62033
+ process.on("SIGINT", onInterrupt);
62034
+ process.on("SIGTERM", onTerminate);
62035
+ let stderr = "";
62036
+ const drain = (async () => {
62037
+ for await (const chunk of child.stderr) {
62038
+ stderr += Buffer.from(chunk).toString();
62039
+ process.stderr.write(chunk);
62040
+ }
62041
+ })();
62042
+ const exitCode = await child.exited;
62043
+ await drain;
62044
+ process.off("SIGINT", onInterrupt);
62045
+ process.off("SIGTERM", onTerminate);
62046
+ if (exitCode !== 0 && child.signalCode === null) {
62047
+ return Promise.reject(new SamvaCliError({
62048
+ message: toolchainFailure(stderr, "Could not start the template editor")
62049
+ }));
62050
+ }
62051
+ },
62052
+ catch: (cause) => cause instanceof SamvaCliError ? cause : new SamvaCliError({
62053
+ message: `Could not start the template editor. Install Node.js 22+: ${String(cause)}`
61903
62054
  })
61904
62055
  });
62056
+ var devCommand = make28("dev", {
62057
+ dir: dirFlag,
62058
+ port: Int5("port").pipe(withDescription2("Port to listen on (default: Vite's, 5173, or the next free one)"), optional4),
62059
+ host: String8("host").pipe(withDescription2("Interface to listen on (default: loopback only)"), optional4),
62060
+ open: Boolean6("open").pipe(withDescription2("Open the editor in a browser once it is listening"), withDefault4(false))
62061
+ }).pipe(withDescription3("Start the local visual editor for the templates in this project; no vite.config.ts needed"), withExamples([
62062
+ { command: "samva templates dev", description: "Serve templates/ and emails/" },
62063
+ {
62064
+ command: "samva templates dev --dir templates --port 4000 --open",
62065
+ description: "Serve one directory on a fixed port and open the browser"
62066
+ }
62067
+ ]), withHandler(({ dir, port, host, open }) => runDevServer({
62068
+ dir: map(dir, (value) => resolve6(value)).pipe(getOrUndefined),
62069
+ port: getOrUndefined(port),
62070
+ host: getOrUndefined(host),
62071
+ open
62072
+ })));
61905
62073
 
61906
- // src/commands/templates/project.ts
62074
+ // src/commands/templates/diagnostics.ts
62075
+ var reportDiagnostic = (diagnostic, context) => {
62076
+ const origin = diagnostic.origins[0];
62077
+ const range = diagnostic.range ?? (origin === undefined ? undefined : { start: { line: origin.lineNumber, column: origin.columnNumber } });
62078
+ return {
62079
+ file: origin?.fileName ?? context.file,
62080
+ code: diagnostic.code,
62081
+ severity: diagnostic.severity,
62082
+ message: diagnostic.message,
62083
+ ...origin === undefined ? {} : { line: origin.lineNumber, column: origin.columnNumber },
62084
+ ...range === undefined ? {} : { range },
62085
+ ...diagnostic.fix === undefined ? {} : { fix: diagnostic.fix },
62086
+ ...diagnostic.edit === undefined ? {} : { edit: diagnostic.edit },
62087
+ ...context.fixture === undefined ? {} : { fixture: context.fixture }
62088
+ };
62089
+ };
62090
+ var formatReportedDiagnostic = (diagnostic) => {
62091
+ const at = diagnostic.line === undefined ? diagnostic.file : `${diagnostic.file}:${diagnostic.line}${diagnostic.column === undefined ? "" : `:${diagnostic.column}`}`;
62092
+ const head = `${at} ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}`;
62093
+ return diagnostic.fix === undefined ? head : `${head}
62094
+ Fix: ${diagnostic.fix}`;
62095
+ };
62096
+
62097
+ // src/commands/templates/local-build.ts
61907
62098
  import { resolve as resolve7 } from "path";
62099
+ var buildDir = (dir, assetBase) => runToolchain({
62100
+ action: `Could not build templates${dir === undefined ? "" : ` in ${dir}`}`,
62101
+ options: {
62102
+ root: process.cwd(),
62103
+ ...dir === undefined ? {} : { dir: resolve7(dir) },
62104
+ ...assetBase === undefined ? {} : { assetBase }
62105
+ },
62106
+ script: `import { buildTemplates } from "@samva/vite/build"; ${OPTIONS_PRELUDE} process.stdout.write(JSON.stringify(await buildTemplates(options)));`
62107
+ });
62108
+
62109
+ // src/commands/templates/project.ts
62110
+ import { resolve as resolve8 } from "path";
61908
62111
  var templateId = String9("id").pipe(withDescription4("Template id"));
61909
62112
  var projectOrTemplateId = String9("id").pipe(withDescription4("Template project id (tproj_...) or the id of any template in it"));
61910
62113
  var runGit = (args, options = {}) => tryPromise2({
@@ -61993,7 +62196,7 @@ var cloneProject = fn2("Templates.cloneProject")(function* (input) {
61993
62196
  ], { concurrency: 2 }).pipe(mapError2((error) => new SamvaCliError({
61994
62197
  message: formatApiError("Could not resolve template project", error)
61995
62198
  })));
61996
- const root = resolve7(input.dir);
62199
+ const root = resolve8(input.dir);
61997
62200
  const helper = yield* helperCommand(template.id, "read");
61998
62201
  yield* runGit([
61999
62202
  "-c",
@@ -62083,7 +62286,7 @@ var init = make28("init", {
62083
62286
  description: "Create deterministically for CI or a coding agent"
62084
62287
  }
62085
62288
  ]), withHandler(({ name, projectName, slug, dir, idempotencyKey, dryRun, json }) => gen2(function* () {
62086
- const root = resolve7(getOrElse(dir, () => projectDirectory(name)));
62289
+ const root = resolve8(getOrElse(dir, () => projectDirectory(name)));
62087
62290
  const key = getOrUndefined(idempotencyKey);
62088
62291
  const request = {
62089
62292
  name,
@@ -62179,37 +62382,36 @@ var resolveProjectId = fn2("Templates.resolveProjectId")(function* (flag) {
62179
62382
  }
62180
62383
  return projectId;
62181
62384
  });
62182
- var ENTRY_PATH = /^emails\/.+\.tsx$/;
62385
+ var ENTRY_ROOTS = ["templates", "emails"];
62386
+ var ENTRY_PATH = new RegExp(`^(?:${ENTRY_ROOTS.join("|")})/.+\\.tsx$`);
62387
+ var ENTRY_ROOTS_LABEL = ENTRY_ROOTS.map((root) => `${root}/*.tsx`).join(" or ");
62183
62388
  var TEMPLATE_SLUG = /^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$/;
62184
62389
  var entryPathArgument = (name, description) => String9(name).pipe(withDescription4(description), filterMap3((value) => {
62185
62390
  const normalized = value.replace(/^\.\//, "");
62186
62391
  return ENTRY_PATH.test(normalized) ? some2(normalized) : none2();
62187
- }, (value) => `${value} is not an emails/*.tsx entry path`));
62392
+ }, (value) => `${value} is not a ${ENTRY_ROOTS_LABEL} entry path`));
62188
62393
  var humanize2 = (id) => {
62189
62394
  const words = id.replace(/[-_.]+/g, " ").trim();
62190
62395
  return words.charAt(0).toUpperCase() + words.slice(1);
62191
62396
  };
62192
62397
  var declaredTemplateId = fn2("Templates.declaredTemplateId")(function* (entryPath) {
62193
- const built = yield* buildDir("emails", undefined);
62194
- const file = built.files.find(({ id }) => `emails/${id}` === entryPath);
62195
- if (file === undefined || file.preview.kind !== "email")
62196
- return;
62197
- return file.preview.templates[0]?.name;
62398
+ const built = yield* buildDir(undefined, undefined);
62399
+ return built.files.find(({ id }) => id === entryPath)?.template?.template;
62198
62400
  });
62199
62401
  var add4 = make28("add", {
62200
- entry: entryPathArgument("entry", "Entry file to register, such as emails/reset.tsx"),
62201
- name: String8("name").pipe(withDescription2("Template name (defaults from the entry's defineEmail id)"), optional4),
62202
- slug: String8("slug").pipe(withDescription2("Template handle (defaults to the entry's defineEmail id)"), optional4),
62402
+ entry: entryPathArgument("entry", "Entry file to register, such as templates/reset.tsx"),
62403
+ name: String8("name").pipe(withDescription2("Template name (defaults from the entry's defineTemplate id)"), optional4),
62404
+ slug: String8("slug").pipe(withDescription2("Template handle (defaults to the entry's defineTemplate id)"), optional4),
62203
62405
  projectId: projectIdFlag,
62204
62406
  dryRun: Boolean6("dry-run").pipe(withDescription2("Show the registration request without writing"), withDefault4(false)),
62205
62407
  json: jsonFlag
62206
- }).pipe(withDescription3("Register another email entry in a template project"), withExamples([
62408
+ }).pipe(withDescription3("Register another template entry in a template project"), withExamples([
62207
62409
  {
62208
- command: "samva templates add emails/reset.tsx",
62209
- description: "Register a local entry under the handle its defineEmail id declares"
62410
+ command: "samva templates add templates/reset.tsx",
62411
+ description: "Register a local entry under the handle its defineTemplate id declares"
62210
62412
  },
62211
62413
  {
62212
- command: 'samva templates add emails/reset.tsx --slug password-reset --name "Password reset"',
62414
+ command: 'samva templates add templates/reset.tsx --slug password-reset --name "Password reset"',
62213
62415
  description: "Register an entry that is not written yet, or override the defaults"
62214
62416
  }
62215
62417
  ]), withHandler(({ entry, name, slug, projectId, dryRun, json }) => gen2(function* () {
@@ -62220,12 +62422,12 @@ var add4 = make28("add", {
62220
62422
  const resolvedSlug = explicitSlug ?? declared;
62221
62423
  if (resolvedSlug === undefined) {
62222
62424
  return yield* new SamvaCliError({
62223
- message: `${entry} does not build to a defineEmail template here. Pass --slug and --name to register it before it exists.`
62425
+ message: `${entry} does not build to a defineTemplate template here. Pass --slug and --name to register it before it exists.`
62224
62426
  });
62225
62427
  }
62226
62428
  if (!TEMPLATE_SLUG.test(resolvedSlug)) {
62227
62429
  return yield* new SamvaCliError({
62228
- message: `defineEmail id ${JSON.stringify(resolvedSlug)} is not a valid handle (lowercase kebab-case). Pass --slug.`
62430
+ message: `defineTemplate id ${JSON.stringify(resolvedSlug)} is not a valid handle (lowercase kebab-case). Pass --slug.`
62229
62431
  });
62230
62432
  }
62231
62433
  const request = {
@@ -62267,7 +62469,7 @@ var move = make28("move", {
62267
62469
  json: jsonFlag
62268
62470
  }).pipe(withDescription3("Point a template at another entry file in its project"), withExamples([
62269
62471
  {
62270
- command: "samva templates move emails/reset.tsx emails/account/reset.tsx",
62472
+ command: "samva templates move templates/reset.tsx templates/account/reset.tsx",
62271
62473
  description: "Keep the template's id, handle, and publications after `git mv`"
62272
62474
  }
62273
62475
  ]), withHandler(({ from, to, projectId, json }) => gen2(function* () {
@@ -62291,7 +62493,14 @@ var move = make28("move", {
62291
62493
  Publish fails until a pushed commit has ${to}; move the file with \`git mv\` if you have not.`);
62292
62494
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
62293
62495
  var unregisteredEntries = fn2("Templates.unregisteredEntries")(function* (commit, templates) {
62294
- const matches = yield* runGit(["grep", "-l", "defineEmail", commit, "--", "emails/"]).pipe(catch_2(() => succeed6("")));
62496
+ const matches = yield* runGit([
62497
+ "grep",
62498
+ "-l",
62499
+ "defineTemplate",
62500
+ commit,
62501
+ "--",
62502
+ ...ENTRY_ROOTS.map((root) => `${root}/`)
62503
+ ]).pipe(catch_2(() => succeed6("")));
62295
62504
  const registered = new Set(templates.map(({ entryPath }) => entryPath));
62296
62505
  return matches.split(`
62297
62506
  `).map((line) => line.slice(line.indexOf(":") + 1)).filter((path) => ENTRY_PATH.test(path) && !registered.has(path));
@@ -62327,7 +62536,7 @@ var publish2 = make28("publish", {
62327
62536
  json: jsonFlag
62328
62537
  }).pipe(withDescription3("Publish template entries at an exact clean, pushed commit"), withExamples([
62329
62538
  {
62330
- command: "samva templates publish emails/reset.tsx",
62539
+ command: "samva templates publish templates/reset.tsx",
62331
62540
  description: "Publish one entry at the current commit"
62332
62541
  },
62333
62542
  {
@@ -62420,8 +62629,489 @@ ${lines.join(`
62420
62629
  `)}`);
62421
62630
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
62422
62631
 
62632
+ // src/commands/templates/render-command.ts
62633
+ import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
62634
+ import { dirname as dirname2, resolve as resolve10 } from "path";
62635
+
62636
+ // src/commands/templates/local-render.ts
62637
+ import { resolve as resolve9 } from "path";
62638
+ var renderLocal = (input) => runToolchain({
62639
+ action: `Could not render ${input.template}`,
62640
+ options: {
62641
+ root: process.cwd(),
62642
+ template: input.template,
62643
+ fixture: input.fixture,
62644
+ channel: input.channel,
62645
+ ...input.dir === undefined ? {} : { dir: resolve9(input.dir) },
62646
+ ...input.locale === undefined ? {} : { locale: input.locale },
62647
+ ...input.timeZone === undefined ? {} : { timeZone: input.timeZone },
62648
+ ...input.assetBase === undefined ? {} : { assetBase: input.assetBase },
62649
+ ...input.offline === true ? { offline: true } : {}
62650
+ },
62651
+ script: `import { renderChannel, renderTemplate } from "@samva/vite/render"; ${OPTIONS_PRELUDE} const { channel, ...rest } = options; const result = channel === "email" ? await renderTemplate(rest) : await renderChannel({ ...rest, channel }); process.stdout.write(JSON.stringify(result));`
62652
+ });
62653
+
62654
+ // src/commands/templates/render-command.ts
62655
+ var renderPayload = (result, out) => ({
62656
+ action: "templates.render",
62657
+ ...result,
62658
+ diagnostics: result.diagnostics.map((diagnostic) => reportDiagnostic(diagnostic, { file: "." })),
62659
+ ...out === undefined ? {} : { out }
62660
+ });
62661
+ var renderedBody = (result, format) => {
62662
+ if ("html" in result) {
62663
+ if (format === "html")
62664
+ return result.html;
62665
+ return [
62666
+ `Subject: ${result.subject}`,
62667
+ ...result.preheader === undefined ? [] : [`Preheader: ${result.preheader}`],
62668
+ "",
62669
+ result.text
62670
+ ].join(`
62671
+ `);
62672
+ }
62673
+ if ("message" in result) {
62674
+ const { header, body, footer, buttons } = result.message;
62675
+ return [
62676
+ ...header === undefined ? [] : [`Header (${header.type}): ${header.text}`, ""],
62677
+ body,
62678
+ ...footer === undefined ? [] : ["", footer],
62679
+ ...buttons.length === 0 ? [] : ["", ...buttons.map((button) => `[${JSON.stringify(button)}]`)]
62680
+ ].join(`
62681
+ `);
62682
+ }
62683
+ return result.text;
62684
+ };
62685
+ var renderCommand = make28("render", {
62686
+ template: String9("template").pipe(withDescription4("Template id, or the path of its .tsx entry")),
62687
+ fixture: String8("fixture").pipe(withDescription2("Fixture to render, such as no-tracking")),
62688
+ channel: Literals3("channel", ["email", "sms", "whatsapp"]).pipe(withDescription2("Channel to render"), withDefault4("email")),
62689
+ dir: dirFlag,
62690
+ locale: String8("locale").pipe(withDescription2("Locale for formatters (default: the template's)"), optional4),
62691
+ timeZone: String8("time-zone").pipe(withDescription2("IANA time zone for date and time formatters (default: UTC)"), optional4),
62692
+ assetBase: assetBaseFlag,
62693
+ offline: offlineFlag,
62694
+ format: Literals3("format", ["text", "html", "json"]).pipe(withDescription2("text: subject, preheader and plain-text body; html: the HTML; json: the full result"), withDefault4("text")),
62695
+ out: File3("out").pipe(withDescription2("Write the rendered output to a file instead of stdout"), optional4),
62696
+ json: jsonFlag
62697
+ }).pipe(withDescription3("Render one fixture of a local template and print the result"), withExamples([
62698
+ {
62699
+ command: "samva templates render welcome --fixture no-tracking",
62700
+ description: "Print the subject, preheader and plain-text body"
62701
+ },
62702
+ {
62703
+ command: "samva templates render welcome --fixture default --format html --out welcome.html",
62704
+ description: "Write the rendered HTML to a file"
62705
+ },
62706
+ {
62707
+ command: "samva templates render welcome --fixture default --json",
62708
+ description: "Print the full result with located findings, fixes and edits"
62709
+ },
62710
+ {
62711
+ command: "samva templates render order-shipped --fixture default --channel sms",
62712
+ description: "Render the SMS body of a multi-channel template"
62713
+ }
62714
+ ]), withHandler(({
62715
+ template,
62716
+ fixture,
62717
+ channel,
62718
+ dir,
62719
+ locale,
62720
+ timeZone,
62721
+ assetBase,
62722
+ offline,
62723
+ format,
62724
+ out,
62725
+ json
62726
+ }) => gen2(function* () {
62727
+ const machine = json || format === "json";
62728
+ const outPath = map(out, (value) => resolve10(value)).pipe(getOrUndefined);
62729
+ const result = yield* renderLocal({
62730
+ template,
62731
+ fixture,
62732
+ channel,
62733
+ dir: getOrUndefined(dir),
62734
+ locale: getOrUndefined(locale),
62735
+ timeZone: getOrUndefined(timeZone),
62736
+ assetBase: getOrUndefined(assetBase),
62737
+ offline
62738
+ });
62739
+ if (!result.ok) {
62740
+ const message = `Could not render ${template} (${fixture}): ${result.diagnostics.length} finding(s).`;
62741
+ if (machine) {
62742
+ return yield* new CliResultError({
62743
+ code: "template_render_failed",
62744
+ message,
62745
+ result: renderPayload(result, undefined),
62746
+ human: message
62747
+ });
62748
+ }
62749
+ for (const diagnostic of result.diagnostics) {
62750
+ yield* error(formatReportedDiagnostic(reportDiagnostic(diagnostic, { file: "." })));
62751
+ }
62752
+ return yield* new SamvaCliError({ message });
62753
+ }
62754
+ const payload = renderPayload(result, outPath);
62755
+ if (outPath !== undefined) {
62756
+ const content = machine ? `${JSON.stringify(payload, null, 2)}
62757
+ ` : `${renderedBody(result, format)}
62758
+ `;
62759
+ yield* tryPromise2({
62760
+ try: async () => {
62761
+ await mkdir2(dirname2(outPath), { recursive: true });
62762
+ await writeFile3(outPath, content);
62763
+ },
62764
+ catch: (cause) => new SamvaCliError({ message: `Could not write ${outPath}: ${String(cause)}` })
62765
+ });
62766
+ }
62767
+ if (machine)
62768
+ return yield* printJson(payload);
62769
+ for (const diagnostic of result.diagnostics) {
62770
+ yield* error(formatReportedDiagnostic(reportDiagnostic(diagnostic, { file: "." })));
62771
+ }
62772
+ if (outPath !== undefined)
62773
+ return yield* logInfo(`Wrote ${outPath}`);
62774
+ yield* log(renderedBody(result, format));
62775
+ }).pipe(json || format === "json" ? provideService2(MinimumLogLevel2, "None") : identity)));
62776
+
62777
+ // src/commands/templates/snapshot-command.ts
62778
+ import { resolve as resolve11 } from "path";
62779
+
62780
+ // src/commands/templates/snapshot-browser.ts
62781
+ import { existsSync } from "fs";
62782
+ import { copyFile as copyFile3, mkdir as mkdir3, mkdtemp as mkdtemp2, readFile as readFile4, rm as rm2, stat as stat3, writeFile as writeFile4 } from "fs/promises";
62783
+ import { tmpdir as tmpdir2 } from "os";
62784
+ import { join as join4 } from "path";
62785
+ import { pathToFileURL as pathToFileURL2 } from "url";
62786
+ var VIEWPORTS = {
62787
+ desktop: { width: 1280, height: 800 },
62788
+ mobile: { width: 390, height: 844 }
62789
+ };
62790
+ var SCHEMES = ["light", "dark"];
62791
+ var BROWSER_REMEDY = "Install a browser to screenshot with: add `playwright` to this project (`npm install --save-dev playwright && npx playwright install chromium`), or install Google Chrome or Chromium and set SAMVA_CHROME to its executable path if it is not in the usual location.";
62792
+ var snapshotFileName = (template, fixture, viewport, scheme) => `${template}-${fixture}-${viewport}-${scheme}.png`;
62793
+ var takeSnapshots = async (input) => {
62794
+ await mkdir3(input.outDir, { recursive: true });
62795
+ const scratch = await mkdtemp2(join4(tmpdir2(), "samva-snapshot-"));
62796
+ const htmlPath = join4(scratch, "page.html");
62797
+ await writeFile4(htmlPath, input.html);
62798
+ const requests = input.viewports.flatMap((viewport) => input.schemes.map((scheme) => ({
62799
+ htmlPath,
62800
+ viewport,
62801
+ width: VIEWPORTS[viewport].width,
62802
+ height: VIEWPORTS[viewport].height,
62803
+ scheme,
62804
+ outFile: join4(input.outDir, snapshotFileName(input.template, input.fixture, viewport, scheme))
62805
+ })));
62806
+ const reasons = [];
62807
+ try {
62808
+ for (const launcher of input.launchers) {
62809
+ const outcome = await launcher.capture(requests);
62810
+ if (outcome.status === "done")
62811
+ return {
62812
+ ok: true,
62813
+ browser: launcher.name,
62814
+ files: requests.map(({ viewport, scheme, width, outFile }) => ({
62815
+ viewport,
62816
+ scheme,
62817
+ width,
62818
+ path: outFile
62819
+ }))
62820
+ };
62821
+ reasons.push(`${launcher.name}: ${outcome.reason}`);
62822
+ }
62823
+ return { ok: false, reasons };
62824
+ } finally {
62825
+ await rm2(scratch, { recursive: true, force: true });
62826
+ }
62827
+ };
62828
+ var CHROME_PATHS = [
62829
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
62830
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
62831
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
62832
+ "/usr/bin/google-chrome",
62833
+ "/usr/bin/google-chrome-stable",
62834
+ "/usr/bin/chromium",
62835
+ "/usr/bin/chromium-browser",
62836
+ "/snap/bin/chromium"
62837
+ ];
62838
+ var CHROME_COMMANDS = ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"];
62839
+ var findChrome = (env, which = (command) => Bun.which(command), exists = existsSync) => {
62840
+ const override = env["SAMVA_CHROME"];
62841
+ if (override !== undefined && override !== "")
62842
+ return override;
62843
+ return CHROME_PATHS.find((path) => exists(path)) ?? CHROME_COMMANDS.map((command) => which(command)).find((path) => path !== null) ?? undefined;
62844
+ };
62845
+ var chromeScreenshotArgs = (input) => [
62846
+ "--headless=new",
62847
+ "--disable-gpu",
62848
+ "--hide-scrollbars",
62849
+ "--disable-background-networking",
62850
+ "--disable-component-update",
62851
+ "--no-first-run",
62852
+ "--no-default-browser-check",
62853
+ `--user-data-dir=${input.profile}`,
62854
+ `--window-size=${input.width},${input.height}`,
62855
+ `--blink-settings=preferredColorScheme=${input.scheme === "dark" ? 1 : 0}`,
62856
+ `--screenshot=${input.outFile}`,
62857
+ input.url
62858
+ ];
62859
+ var chromeMeasureArgs = (input) => [
62860
+ "--headless=new",
62861
+ "--disable-gpu",
62862
+ "--hide-scrollbars",
62863
+ "--disable-background-networking",
62864
+ "--disable-component-update",
62865
+ "--no-first-run",
62866
+ "--no-default-browser-check",
62867
+ `--user-data-dir=${input.profile}`,
62868
+ `--window-size=${input.width},${input.height}`,
62869
+ `--blink-settings=preferredColorScheme=${input.scheme === "dark" ? 1 : 0}`,
62870
+ "--dump-dom",
62871
+ input.url
62872
+ ];
62873
+ var MEASURE_SCRIPT = "<script>addEventListener('load',()=>{document.documentElement.setAttribute('data-samva-height',String(Math.ceil(Math.max(document.documentElement.scrollHeight,document.body?document.body.scrollHeight:0))))})</script>";
62874
+ var withMeasurement = (html) => /<\/body>/i.test(html) ? html.replace(/<\/body>/i, `${MEASURE_SCRIPT}</body>`) : `${html}${MEASURE_SCRIPT}`;
62875
+ var parseMeasuredHeight = (dom) => {
62876
+ const height = /data-samva-height="(\d+)"/.exec(dom)?.[1];
62877
+ return height === undefined ? undefined : Number(height);
62878
+ };
62879
+ var RUN_TIMEOUT_MS = 30000;
62880
+ var POLL_MS = 100;
62881
+ var runProcess = async (command, args, until) => {
62882
+ const child = Bun.spawn([command, ...args], { stdout: "pipe", stderr: "pipe" });
62883
+ let stdout = "";
62884
+ let stderr = "";
62885
+ const drain = async (stream, sink) => {
62886
+ for await (const chunk of stream)
62887
+ sink(Buffer.from(chunk).toString());
62888
+ };
62889
+ drain(child.stdout, (text) => {
62890
+ stdout += text;
62891
+ });
62892
+ drain(child.stderr, (text) => {
62893
+ stderr += text;
62894
+ });
62895
+ const deadline = Date.now() + RUN_TIMEOUT_MS;
62896
+ const produced = async () => until.stdoutIncludes !== undefined && stdout.includes(until.stdoutIncludes) || until.file !== undefined && existsSync(until.file) && (await stat3(until.file)).size > 0;
62897
+ const state = { exited: false };
62898
+ child.exited.then(() => {
62899
+ state.exited = true;
62900
+ });
62901
+ while (!state.exited && Date.now() < deadline && !await produced()) {
62902
+ await new Promise((done) => setTimeout(done, POLL_MS));
62903
+ }
62904
+ if (!state.exited) {
62905
+ await new Promise((done) => setTimeout(done, POLL_MS * 3));
62906
+ child.kill("SIGKILL");
62907
+ }
62908
+ await child.exited;
62909
+ return { code: state.exited ? child.exitCode : null, stdout, stderr };
62910
+ };
62911
+ var chromeLauncher = (binary, run = runProcess) => ({
62912
+ name: "Chrome",
62913
+ capture: async (requests) => {
62914
+ if (binary === undefined)
62915
+ return {
62916
+ status: "unavailable",
62917
+ reason: "no Chrome or Chromium found in the usual locations and SAMVA_CHROME is not set"
62918
+ };
62919
+ const profile = await mkdtemp2(join4(tmpdir2(), "samva-chrome-"));
62920
+ try {
62921
+ for (const request of requests) {
62922
+ const source = await readFile4(request.htmlPath, "utf8");
62923
+ const measured = request.htmlPath.replace(/\.html$/, `.${request.viewport}.${request.scheme}.html`);
62924
+ await writeFile4(measured, withMeasurement(source));
62925
+ const measure = await run(binary, chromeMeasureArgs({
62926
+ profile,
62927
+ width: request.width,
62928
+ height: request.height,
62929
+ scheme: request.scheme,
62930
+ url: pathToFileURL2(measured).href
62931
+ }), { stdoutIncludes: "</html>" });
62932
+ const height = Math.max(request.height, parseMeasuredHeight(measure.stdout) ?? request.height);
62933
+ const staged = join4(profile, `shot-${request.viewport}-${request.scheme}.png`);
62934
+ const shot = await run(binary, chromeScreenshotArgs({
62935
+ profile,
62936
+ width: request.width,
62937
+ height,
62938
+ scheme: request.scheme,
62939
+ outFile: staged,
62940
+ url: pathToFileURL2(request.htmlPath).href
62941
+ }), { file: staged });
62942
+ if (!existsSync(staged))
62943
+ return {
62944
+ status: "unavailable",
62945
+ reason: `${binary} did not produce a screenshot (exit ${shot.code ?? "killed"}): ${shot.stderr.trim().split(`
62946
+ `).slice(-3).join(" ")}`
62947
+ };
62948
+ await copyFile3(staged, request.outFile);
62949
+ }
62950
+ return { status: "done" };
62951
+ } finally {
62952
+ await rm2(profile, { recursive: true, force: true });
62953
+ }
62954
+ }
62955
+ });
62956
+ var PLAYWRIGHT_SCRIPT = `
62957
+ const options = JSON.parse(process.env.SAMVA_TOOLCHAIN_INPUT);
62958
+ const load = async () => {
62959
+ for (const name of ["playwright", "playwright-core"]) {
62960
+ try { return await import(name); } catch {}
62961
+ }
62962
+ return undefined;
62963
+ };
62964
+ const done = (value) => process.stdout.write(JSON.stringify(value));
62965
+ const playwright = await load();
62966
+ if (playwright === undefined) {
62967
+ done({ status: "unavailable", reason: "neither playwright nor playwright-core is installed in this project" });
62968
+ } else {
62969
+ const chromium = playwright.chromium ?? playwright.default?.chromium;
62970
+ let browser;
62971
+ let failure = "";
62972
+ for (const launch of [{}, { channel: "chrome" }]) {
62973
+ try { browser = await chromium.launch(launch); break; } catch (error) { failure = String(error?.message ?? error).split("\\n")[0]; }
62974
+ }
62975
+ if (browser === undefined) {
62976
+ done({ status: "unavailable", reason: "playwright could not launch a browser (" + failure + ")" });
62977
+ } else {
62978
+ try {
62979
+ for (const request of options.requests) {
62980
+ const context = await browser.newContext({
62981
+ viewport: { width: request.width, height: request.height },
62982
+ colorScheme: request.scheme,
62983
+ });
62984
+ const page = await context.newPage();
62985
+ await page.goto(options.urls[request.htmlPath]);
62986
+ await page.screenshot({ path: request.outFile, fullPage: true });
62987
+ await context.close();
62988
+ }
62989
+ done({ status: "done" });
62990
+ } finally {
62991
+ await browser.close();
62992
+ }
62993
+ }
62994
+ }
62995
+ `;
62996
+ var playwrightLauncher = () => ({
62997
+ name: "Playwright",
62998
+ capture: async (requests) => {
62999
+ const urls = Object.fromEntries([...new Set(requests.map((request) => request.htmlPath))].map((path) => [
63000
+ path,
63001
+ pathToFileURL2(path).href
63002
+ ]));
63003
+ const child = Bun.spawn(["node", "--input-type=module", "--eval", PLAYWRIGHT_SCRIPT], {
63004
+ cwd: process.cwd(),
63005
+ env: { ...Bun.env, SAMVA_TOOLCHAIN_INPUT: JSON.stringify({ requests, urls }) },
63006
+ stdout: "pipe",
63007
+ stderr: "pipe"
63008
+ });
63009
+ const [stdout, stderr, code] = await Promise.all([
63010
+ new Response(child.stdout).text(),
63011
+ new Response(child.stderr).text(),
63012
+ child.exited
63013
+ ]);
63014
+ if (code !== 0)
63015
+ return {
63016
+ status: "unavailable",
63017
+ reason: stderr.trim().split(`
63018
+ `).slice(-3).join(" ") || `node exited ${code}`
63019
+ };
63020
+ return JSON.parse(stdout);
63021
+ }
63022
+ });
63023
+ var defaultLaunchers = (env) => [
63024
+ playwrightLauncher(),
63025
+ chromeLauncher(findChrome(env))
63026
+ ];
63027
+
63028
+ // src/commands/templates/snapshot-command.ts
63029
+ var parseChoices = (flag, raw, allowed) => {
63030
+ const chosen = raw.split(",").map((value) => value.trim()).filter((value) => value !== "");
63031
+ const unknown = chosen.filter((value) => !allowed.includes(value));
63032
+ return unknown.length > 0 || chosen.length === 0 ? fail6(new CliUsageError({
63033
+ code: "invalid_snapshot_choice",
63034
+ message: `--${flag} takes a comma-separated list of ${allowed.join(", ")}${unknown.length > 0 ? `; got ${unknown.join(", ")}` : ""}.`
63035
+ })) : succeed6([...new Set(chosen)]);
63036
+ };
63037
+ var snapshotCommand = make28("snapshot", {
63038
+ template: String9("template").pipe(withDescription4("Template id, or the path of its .tsx entry")),
63039
+ fixture: String8("fixture").pipe(withDescription2("Fixture to screenshot")),
63040
+ dir: dirFlag,
63041
+ out: Directory2("out").pipe(withDescription2("Directory to write the PNGs into"), withDefault4(".samva/snapshots")),
63042
+ viewport: String8("viewport").pipe(withDescription2(`Comma-separated viewports: ${Object.entries(VIEWPORTS).map(([name, size]) => `${name} (${size.width}px)`).join(", ")}`), withDefault4("desktop,mobile")),
63043
+ scheme: String8("scheme").pipe(withDescription2("Comma-separated color schemes: light, dark"), withDefault4("light,dark")),
63044
+ assetBase: assetBaseFlag,
63045
+ offline: offlineFlag,
63046
+ json: jsonFlag
63047
+ }).pipe(withDescription3("Screenshot one fixture in a headless browser at desktop and mobile widths, light and dark"), withExamples([
63048
+ {
63049
+ command: "samva templates snapshot welcome --fixture no-tracking",
63050
+ description: "Write four PNGs to .samva/snapshots"
63051
+ },
63052
+ {
63053
+ command: "samva templates snapshot welcome --fixture default --viewport mobile --scheme dark --out shots",
63054
+ description: "One screenshot: mobile, dark"
63055
+ }
63056
+ ]), withHandler(({ template, fixture, dir, out, viewport, scheme, assetBase, offline, json }) => gen2(function* () {
63057
+ const viewports = yield* parseChoices("viewport", viewport, Object.keys(VIEWPORTS));
63058
+ const schemes = yield* parseChoices("scheme", scheme, SCHEMES);
63059
+ const rendered = yield* renderLocal({
63060
+ template,
63061
+ fixture,
63062
+ channel: "email",
63063
+ dir: getOrUndefined(dir),
63064
+ assetBase: getOrUndefined(assetBase),
63065
+ offline
63066
+ });
63067
+ if (!rendered.ok || !("html" in rendered)) {
63068
+ const message = `Could not render ${template} (${fixture}) to screenshot it.`;
63069
+ const diagnostics = rendered.diagnostics.map((diagnostic) => reportDiagnostic(diagnostic, { file: "." }));
63070
+ if (json) {
63071
+ return yield* new CliResultError({
63072
+ code: "template_render_failed",
63073
+ message,
63074
+ result: { action: "templates.snapshot", ok: false, diagnostics },
63075
+ human: message
63076
+ });
63077
+ }
63078
+ for (const diagnostic of diagnostics) {
63079
+ yield* error(formatReportedDiagnostic(diagnostic));
63080
+ }
63081
+ return yield* new SamvaCliError({ message });
63082
+ }
63083
+ const outDir = resolve11(out);
63084
+ const shots = yield* promise2(() => takeSnapshots({
63085
+ html: rendered.html,
63086
+ template: rendered.template,
63087
+ fixture,
63088
+ outDir,
63089
+ viewports,
63090
+ schemes,
63091
+ launchers: defaultLaunchers(Bun.env)
63092
+ }));
63093
+ if (!shots.ok) {
63094
+ return yield* new SamvaCliError({
63095
+ message: `No browser could take the screenshots (${shots.reasons.join("; ")}). ${BROWSER_REMEDY}`
63096
+ });
63097
+ }
63098
+ const result = {
63099
+ action: "templates.snapshot",
63100
+ ok: true,
63101
+ template: rendered.template,
63102
+ fixture,
63103
+ browser: shots.browser,
63104
+ out: outDir,
63105
+ files: shots.files
63106
+ };
63107
+ if (json)
63108
+ return yield* printJson(result);
63109
+ for (const file of shots.files)
63110
+ yield* log(file.path);
63111
+ }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
63112
+
62423
63113
  // src/commands/templates/types-command.ts
62424
- import { dirname as dirname2, resolve as resolve8 } from "path";
63114
+ import { dirname as dirname3, resolve as resolve12 } from "path";
62425
63115
 
62426
63116
  // src/commands/templates/schema-types.ts
62427
63117
  var RESERVED = new Set([
@@ -62866,7 +63556,7 @@ var typesCommand = make28("types", {
62866
63556
  });
62867
63557
  }
62868
63558
  const context = yield* resolveExecutionContext;
62869
- const path = resolve8(out);
63559
+ const path = resolve12(out);
62870
63560
  const entries = yield* readCatalog();
62871
63561
  const generated = renderTemplateTypes({
62872
63562
  entries,
@@ -62930,7 +63620,7 @@ var typesCommand = make28("types", {
62930
63620
  });
62931
63621
  }
62932
63622
  const fs = yield* FileSystem;
62933
- yield* fs.makeDirectory(dirname2(path), { recursive: true }).pipe(catch_2(() => void_3));
63623
+ yield* fs.makeDirectory(dirname3(path), { recursive: true }).pipe(catch_2(() => void_3));
62934
63624
  yield* fs.writeFileString(path, generated.source).pipe(mapError2((cause) => new SamvaCliError({ message: `Could not write ${path}: ${String(cause)}` })));
62935
63625
  const written = { ...result, written: true };
62936
63626
  if (json)
@@ -62946,7 +63636,7 @@ var typesCommand = make28("types", {
62946
63636
  })));
62947
63637
 
62948
63638
  // src/commands/templates/workspace.ts
62949
- import { basename, resolve as resolve9 } from "path";
63639
+ import { basename, resolve as resolve13 } from "path";
62950
63640
  var templateIdArgument = String9("template-id").pipe(withDescription4("Template whose project workspace to act on"));
62951
63641
  var revisionFlag = String8("revision").pipe(withDescription2("Workspace revision this call is fenced to; a workspace that moved refuses the call"));
62952
63642
  var idempotencyKeyFlag4 = String8("idempotency-key").pipe(withDescription2("Key so an identical retry returns the original result rather than repeating it"));
@@ -62980,7 +63670,7 @@ var patch2 = make28("patch", {
62980
63670
  json: jsonFlag
62981
63671
  }).pipe(withDescription3("Apply file replacements and deletions to the shared workspace"), withExamples([
62982
63672
  {
62983
- command: "samva templates workspace patch tmpl_abc123 --revision 4 --set emails/welcome.tsx=./welcome.tsx --idempotency-key edit-1",
63673
+ command: "samva templates workspace patch tmpl_abc123 --revision 4 --set templates/welcome.tsx=./welcome.tsx --idempotency-key edit-1",
62984
63674
  description: "Write one file, fenced to the revision you inspected"
62985
63675
  }
62986
63676
  ]), withHandler(fnUntraced2(function* ({ id, revision, set, remove, idempotencyKey, json }) {
@@ -62999,7 +63689,7 @@ var patch2 = make28("patch", {
62999
63689
  message: `--set must be <project-path>=<local-file>; got ${entry}.`
63000
63690
  });
63001
63691
  const path = entry.slice(0, separator);
63002
- const source = resolve9(entry.slice(separator + 1));
63692
+ const source = resolve13(entry.slice(separator + 1));
63003
63693
  const content = yield* fs.readFileString(source).pipe(mapError2((cause) => new SamvaCliError({ message: `Could not read ${source}: ${String(cause)}` })));
63004
63694
  written.push({ path, content });
63005
63695
  }
@@ -63033,7 +63723,7 @@ var addAsset = make28("add-asset", {
63033
63723
  }
63034
63724
  ]), withHandler(fnUntraced2(function* ({ id, file, revision, path, replace, idempotencyKey, json }) {
63035
63725
  const fs = yield* FileSystem;
63036
- const source = resolve9(file);
63726
+ const source = resolve13(file);
63037
63727
  const bytes = yield* fs.readFile(source).pipe(mapError2((cause) => new SamvaCliError({ message: `Could not read ${source}: ${String(cause)}` })));
63038
63728
  const client = yield* getClientProvider;
63039
63729
  const view = yield* putWorkspaceAsset({
@@ -63119,7 +63809,7 @@ var publish3 = make28("publish", {
63119
63809
  json: jsonFlag
63120
63810
  }).pipe(withDescription3("Publish an exact saved commit without a local clone"), withExamples([
63121
63811
  {
63122
- command: "samva templates workspace publish tmpl_abc123 --revision 6 --commit 0a1b\u2026 --entry emails/welcome.tsx --project-id tproj_abc123",
63812
+ command: "samva templates workspace publish tmpl_abc123 --revision 6 --commit 0a1b\u2026 --entry templates/welcome.tsx --project-id tproj_abc123",
63123
63813
  description: "Complete the workspace journey; `templates publish` is the Git-clone equivalent"
63124
63814
  }
63125
63815
  ]), withHandler(fnUntraced2(function* ({ id, revision, commit, entry, projectId, json }) {
@@ -63145,9 +63835,7 @@ var workspaceCommand = make28("workspace").pipe(withDescription3("Author a templ
63145
63835
  ]), withSubcommands([open3, diff, patch2, addAsset, check4, fixture, save, history, publish3]));
63146
63836
 
63147
63837
  // src/commands/templates.ts
63148
- var dirFlag = Directory2("dir").pipe(withDescription2("Directory containing your .tsx templates"), withDefault4("emails"));
63149
63838
  var tsconfigFlag = String8("tsconfig").pipe(withDescription2("TypeScript project used to check authored templates"), withDefault4("tsconfig.json"));
63150
- var assetBaseFlag = String8("asset-base").pipe(withDescription2("Actual HTTPS serving base for imported email assets"), optional4);
63151
63839
  var dryRunFlag4 = Boolean6("dry-run").pipe(withDescription2("Show the request without calling the API"), withDefault4(false));
63152
63840
  var templateIdArgument2 = String9("id").pipe(withDescription4("Remote template id"));
63153
63841
  var parseObject = (raw, flag) => try_3({
@@ -63158,14 +63846,15 @@ var decodePayload = (raw, flag, schema) => parseObject(raw, flag).pipe(flatMap3(
63158
63846
  var createProjectBuildSession = (dir, assetBase) => tryPromise2({
63159
63847
  try: async () => {
63160
63848
  const entry = Bun.resolveSync("@samva/vite/build", process.cwd());
63161
- const projectBuild = await import(pathToFileURL2(entry).href);
63162
- return projectBuild.createTemplateBuildSession(resolve10(dir), {
63849
+ const projectBuild = await import(pathToFileURL3(entry).href);
63850
+ return projectBuild.createTemplateBuildSession({
63163
63851
  root: process.cwd(),
63164
- assetBase
63852
+ ...dir === undefined ? {} : { dir: resolve14(dir) },
63853
+ ...assetBase === undefined ? {} : { assetBase }
63165
63854
  });
63166
63855
  },
63167
63856
  catch: (cause) => new SamvaCliError({
63168
- message: `Could not start template watch in ${dir}. Install @samva/vite and vite in this project: ${String(cause)}`
63857
+ message: toolchainFailure(String(cause), `Could not start template watch${dir === undefined ? "" : ` in ${dir}`}`)
63169
63858
  })
63170
63859
  });
63171
63860
  var TSC_DIAGNOSTIC_LINE = /^(?:(.*?)(?:\((\d+),(\d+)\))?: )?(?:error )?(TS\d+): (.*)$/;
@@ -63183,16 +63872,16 @@ var parseTypeScriptDiagnostics = (output, tsconfig) => output.split(`
63183
63872
  var typecheckProject = (tsconfig) => tryPromise2({
63184
63873
  try: async () => {
63185
63874
  const typescriptPackage = Bun.resolveSync("typescript/package.json", process.cwd());
63186
- const tsc = resolve10(dirname3(typescriptPackage), "bin/tsc");
63875
+ const tsc = resolve14(dirname4(typescriptPackage), "bin/tsc");
63187
63876
  const typescriptManifest = await Bun.file(typescriptPackage).json();
63188
- const child = Bun.spawn(["node", tsc, "--noEmit", "--pretty", "false", "--project", resolve10(tsconfig)], { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" });
63877
+ const child = Bun.spawn(["node", tsc, "--noEmit", "--pretty", "false", "--project", resolve14(tsconfig)], { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" });
63189
63878
  const [stdout, stderr, exitCode] = await Promise.all([
63190
63879
  new Response(child.stdout).text(),
63191
63880
  new Response(child.stderr).text(),
63192
63881
  child.exited
63193
63882
  ]);
63194
63883
  const output = `${stdout}${stderr}`.trim();
63195
- const diagnostics = parseTypeScriptDiagnostics(output, resolve10(tsconfig));
63884
+ const diagnostics = parseTypeScriptDiagnostics(output, resolve14(tsconfig));
63196
63885
  if (exitCode !== 0 && diagnostics.length === 0) {
63197
63886
  return Promise.reject(new SamvaCliError({
63198
63887
  message: output || `Could not run TypeScript from this project. Install typescript and check ${tsconfig}.`
@@ -63206,7 +63895,7 @@ var typecheckProject = (tsconfig) => tryPromise2({
63206
63895
  });
63207
63896
  var validateTemplatesDir = (dir) => tryPromise2({
63208
63897
  try: async () => {
63209
- const info = await stat3(dir);
63898
+ const info = await stat4(dir);
63210
63899
  if (!info.isDirectory()) {
63211
63900
  return Promise.reject(new SamvaCliError({ message: `${dir} is not a directory.` }));
63212
63901
  }
@@ -63227,69 +63916,28 @@ var validateTemplatesDir = (dir) => tryPromise2({
63227
63916
  });
63228
63917
  }
63229
63918
  });
63230
- var smlLocation = (source, span) => lineColumn(buildLineMap(source), span.start);
63231
- var diagnosticProblem = (file, phase, provenance, diagnostic, source) => ({
63232
- file,
63919
+ var problemOf = (phase, diagnostic, context) => ({
63920
+ ...reportDiagnostic(diagnostic, context),
63233
63921
  phase,
63234
- provenance,
63235
- code: diagnostic.code,
63236
- message: diagnostic.message,
63237
- span: diagnostic.span,
63238
- ...source === undefined ? {} : smlLocation(source, diagnostic.span),
63239
- repair: diagnostic.repair
63240
- });
63241
- var emailProblem = (file, diagnostic, fixture) => {
63242
- const origin = diagnostic.origins[0];
63243
- return {
63244
- file: origin?.fileName ?? file,
63245
- phase: diagnostic.code.startsWith("caniemail/") ? "compatibility" : "compile",
63246
- provenance: "tsx",
63247
- code: diagnostic.code,
63248
- message: diagnostic.message,
63249
- ...origin === undefined ? {} : { line: origin.lineNumber, column: origin.columnNumber },
63250
- ...fixture === undefined ? {} : { fixture }
63251
- };
63252
- };
63253
- var compatibilityWarnings = (result) => result.files.flatMap((file) => {
63254
- if (file.preview.kind !== "email")
63255
- return [];
63256
- return file.preview.templates.flatMap((template) => template.fixtures.flatMap((fixture) => fixture.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").map((diagnostic) => emailProblem(file.id, diagnostic, fixture.name))));
63257
- });
63258
- var compilerVersionOf = (result) => result.files[0]?.preview.compilerVersion ?? "unknown";
63259
- var collectBuildProblems = (result) => {
63260
- const files = result.files.flatMap((file) => {
63261
- if (file.preview.kind === "email")
63262
- return [
63263
- ...file.preview.diagnostics.filter((finding) => finding.severity === "error").map((finding) => emailProblem(file.id, finding)),
63264
- ...file.preview.templates.flatMap((template) => template.fixtures.flatMap((fixture) => fixture.diagnostics.filter((finding) => finding.severity === "error").map((finding) => emailProblem(file.id, finding, fixture.name))))
63265
- ];
63266
- const moduleDiagnostics = file.preview.diagnostics.map((diagnostic) => ({
63267
- file: file.id,
63268
- phase: "module",
63269
- provenance: "tsx",
63270
- code: diagnostic.code,
63271
- message: diagnostic.message,
63272
- span: diagnostic.span,
63273
- repair: diagnostic.repair
63274
- }));
63275
- const templateDiagnostics = file.preview.templates.flatMap((template) => {
63276
- const source = template.source;
63277
- return [
63278
- ...template.diagnostics.map((diagnostic) => diagnosticProblem(file.id, "emit", "sml", diagnostic, source)),
63279
- ...(template.compiled.ok ? [] : template.compiled.diagnostics ?? []).map((diagnostic) => diagnosticProblem(file.id, "compile", "sml", diagnostic, source))
63280
- ];
63281
- });
63282
- return [...moduleDiagnostics, ...templateDiagnostics];
63283
- });
63284
- return files;
63285
- };
63286
- var isSuccessfulBuild = (result) => result.files.length > 0 && result.files.every((file) => file.preview.ok) && collectBuildProblems(result).length === 0;
63922
+ provenance: "tsx"
63923
+ });
63924
+ var compatibilityFindings = (result, severity) => result.files.flatMap((file) => (file.template?.compatibility ?? []).filter((diagnostic) => diagnostic.severity === severity).map((diagnostic) => problemOf("compatibility", diagnostic, { file: file.id })));
63925
+ var compileWarnings = (result) => result.files.flatMap((file) => file.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").map((diagnostic) => problemOf("compile", diagnostic, { file: file.id })));
63926
+ var collectBuildProblems = (result) => [
63927
+ ...result.diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => problemOf("project", diagnostic, { file: "." })),
63928
+ ...result.files.flatMap((file) => [
63929
+ ...file.diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => problemOf("compile", diagnostic, { file: file.id })),
63930
+ ...(file.template?.fixtures ?? []).flatMap((fixture) => fixture.diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => problemOf("compile", diagnostic, { file: file.id, fixture: fixture.name })))
63931
+ ]),
63932
+ ...compatibilityFindings(result, "error")
63933
+ ];
63934
+ var isSuccessfulBuild = (result) => result.files.length > 0 && result.ok && collectBuildProblems(result).length === 0;
63287
63935
  var toTemplateBuildArtifact = (result) => ({
63288
- schemaVersion: 2,
63289
- compilerVersion: compilerVersionOf(result),
63936
+ schemaVersion: 3,
63937
+ compilerVersion: result.compilerVersion,
63290
63938
  result
63291
63939
  });
63292
- var problemLocation = (problem) => problem.line === undefined ? "" : `:${problem.line}${problem.column === undefined ? "" : `:${problem.column}`}`;
63940
+ var NO_TEMPLATES = (dir) => `No templates found in ${dir ?? "templates/ or emails/"}. A template is a .tsx file that default-exports defineTemplate with an input schema, fixtures, and an email, sms, or whatsapp body.`;
63293
63941
  var observeTemplateCheck = (dir, tsconfig, build) => gen2(function* () {
63294
63942
  const typecheck = yield* typecheckProject(tsconfig);
63295
63943
  const typeProblems = typecheck.diagnostics.map((diagnostic) => ({
@@ -63297,6 +63945,7 @@ var observeTemplateCheck = (dir, tsconfig, build) => gen2(function* () {
63297
63945
  phase: "typescript",
63298
63946
  provenance: "tsx",
63299
63947
  code: diagnostic.code,
63948
+ severity: "error",
63300
63949
  message: diagnostic.message,
63301
63950
  line: diagnostic.line,
63302
63951
  column: diagnostic.column
@@ -63320,9 +63969,9 @@ var observeTemplateCheck = (dir, tsconfig, build) => gen2(function* () {
63320
63969
  const built = yield* build();
63321
63970
  const problems = collectBuildProblems(built);
63322
63971
  const ok = isSuccessfulBuild(built);
63323
- const compilerVersion = compilerVersionOf(built);
63324
- const warnings = compatibilityWarnings(built);
63325
- const message = ok ? `TypeScript v${typecheck.version} \xB7 compiler v${compilerVersion} \xB7 ${built.files.length} template(s) compiled \xB7 ${warnings.length} compatibility warning(s)` : built.files.length === 0 ? `No templates found in ${dir}. Email entries must default-export defineEmail with an input schema and fixtures; SMS/WhatsApp entries use their structured channel function.` : `Template check failed (${problems.length} problem(s)).`;
63972
+ const compilerVersion = built.compilerVersion;
63973
+ const warnings = compatibilityFindings(built, "warning");
63974
+ const message = ok ? `TypeScript v${typecheck.version} \xB7 compiler ${compilerVersion} \xB7 ${built.files.length} template(s) compiled \xB7 ${warnings.length} compatibility warning(s)` : built.files.length === 0 ? NO_TEMPLATES(dir) : `Template check failed (${problems.length} problem(s)).`;
63326
63975
  return {
63327
63976
  ok,
63328
63977
  message,
@@ -63334,9 +63983,10 @@ var observeTemplateCheck = (dir, tsconfig, build) => gen2(function* () {
63334
63983
  tsconfig,
63335
63984
  typescriptVersion: typecheck.version,
63336
63985
  compilerVersion,
63337
- buildSchemaVersion: 2,
63986
+ buildSchemaVersion: 3,
63338
63987
  files: built.files,
63339
63988
  problems,
63989
+ warnings: compileWarnings(built),
63340
63990
  compatibility: {
63341
63991
  errors: problems.filter((problem) => problem.phase === "compatibility").length,
63342
63992
  warnings: warnings.length,
@@ -63353,10 +64003,11 @@ var watchTemplateChecks = (dir, tsconfig, assetBase) => acquireUseRelease2(creat
63353
64003
  });
63354
64004
  const observe = () => observeTemplateCheck(dir, tsconfig, build).pipe(catch_2((error) => {
63355
64005
  const problem = {
63356
- file: dir,
63357
- phase: "module",
64006
+ file: dir ?? ".",
64007
+ phase: "watch",
63358
64008
  provenance: "tsx",
63359
64009
  code: "watch-rebuild-error",
64010
+ severity: "error",
63360
64011
  message: error.message
63361
64012
  };
63362
64013
  return succeed6({
@@ -63382,12 +64033,11 @@ var watchTemplateChecks = (dir, tsconfig, assetBase) => acquireUseRelease2(creat
63382
64033
  files: files.map((file) => ({
63383
64034
  id: file.id,
63384
64035
  kind: file.kind,
63385
- ok: file.preview.ok,
63386
- templates: file.preview.templates.map((template) => ({
63387
- name: template.name,
63388
- channel: template.channel,
63389
- ok: template.ok
63390
- }))
64036
+ ok: file.ok,
64037
+ ...file.template === undefined ? {} : {
64038
+ template: file.template.template,
64039
+ channels: file.template.channels
64040
+ }
63391
64041
  }))
63392
64042
  },
63393
64043
  observation: sequence,
@@ -63401,7 +64051,7 @@ var watchTemplateChecks = (dir, tsconfig, assetBase) => acquireUseRelease2(creat
63401
64051
  process.stdout.write(`${status} ${observation.message}
63402
64052
  `);
63403
64053
  for (const problem of observation.problems) {
63404
- process.stdout.write(` ${problem.file}${problemLocation(problem)} [${problem.phase}/${problem.provenance}] ${problem.code} ${problem.message}
64054
+ process.stdout.write(` ${formatReportedDiagnostic(problem)}
63405
64055
  `);
63406
64056
  }
63407
64057
  });
@@ -63418,10 +64068,10 @@ var check5 = make28("check", {
63418
64068
  tsconfig: tsconfigFlag,
63419
64069
  assetBase: assetBaseFlag,
63420
64070
  watch: Boolean6("watch").pipe(withDescription2("Continuously re-run checks as project files change"), withDefault4(false))
63421
- }).pipe(withDescription3("Check local TSX templates without writing files"), withExamples([
64071
+ }).pipe(withDescription3("Type-check and compile local TSX templates, render every fixture, and report located findings"), withExamples([
63422
64072
  {
63423
- command: "samva templates check --dir emails",
63424
- description: "Check templates in emails/ without writing files"
64073
+ command: "samva templates check",
64074
+ description: "Check the templates in templates/ and emails/ without writing files"
63425
64075
  },
63426
64076
  {
63427
64077
  command: "samva templates check --json",
@@ -63444,7 +64094,9 @@ var check5 = make28("check", {
63444
64094
  description: "Stream one machine-readable result per observation"
63445
64095
  }
63446
64096
  ]), withHandler(({ dir, json, tsconfig, watch, assetBase }) => gen2(function* () {
63447
- yield* validateTemplatesDir(dir);
64097
+ const dirValue = getOrUndefined(dir);
64098
+ if (dirValue !== undefined)
64099
+ yield* validateTemplatesDir(dirValue);
63448
64100
  if (watch) {
63449
64101
  if (json && !isJsonLinesRequested()) {
63450
64102
  return yield* new CliUsageError({
@@ -63452,9 +64104,9 @@ var check5 = make28("check", {
63452
64104
  message: "--watch uses --jsonl for machine output; --json remains one-shot."
63453
64105
  });
63454
64106
  }
63455
- return yield* watchTemplateChecks(dir, tsconfig, getOrUndefined(assetBase));
64107
+ return yield* watchTemplateChecks(dirValue, tsconfig, getOrUndefined(assetBase));
63456
64108
  }
63457
- const observation = yield* observeTemplateCheck(dir, tsconfig, () => buildDir(dir, getOrUndefined(assetBase)));
64109
+ const observation = yield* observeTemplateCheck(dirValue, tsconfig, () => buildDir(dirValue, getOrUndefined(assetBase)));
63458
64110
  if (!observation.ok) {
63459
64111
  if (json) {
63460
64112
  return yield* new CliResultError({
@@ -63465,7 +64117,7 @@ var check5 = make28("check", {
63465
64117
  });
63466
64118
  }
63467
64119
  for (const problem of observation.problems) {
63468
- yield* error(`${problem.file}${problemLocation(problem)}: [${problem.phase}/${problem.provenance}] ${problem.code} ${problem.message}`);
64120
+ yield* error(formatReportedDiagnostic(problem));
63469
64121
  }
63470
64122
  return yield* new SamvaCliError({ message: observation.message });
63471
64123
  }
@@ -63477,7 +64129,7 @@ var check5 = make28("check", {
63477
64129
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
63478
64130
  var writeJsonFile = (target, value) => gen2(function* () {
63479
64131
  const fs = yield* FileSystem;
63480
- yield* fs.makeDirectory(dirname3(target), { recursive: true });
64132
+ yield* fs.makeDirectory(dirname4(target), { recursive: true });
63481
64133
  yield* fs.writeFileString(target, `${JSON.stringify(value, null, 2)}
63482
64134
  `);
63483
64135
  }).pipe(mapError2((cause) => new SamvaCliError({
@@ -63490,7 +64142,7 @@ var build2 = make28("build", {
63490
64142
  json: jsonFlag
63491
64143
  }).pipe(withDescription3("Build local TSX templates into a reusable JSON artifact"), withExamples([
63492
64144
  {
63493
- command: "samva templates build --dir emails --out dist/templates.json",
64145
+ command: "samva templates build --out dist/templates.json",
63494
64146
  description: "Compile local templates for another package or deployment step"
63495
64147
  },
63496
64148
  {
@@ -63498,10 +64150,11 @@ var build2 = make28("build", {
63498
64150
  description: "Build executable fixtures with their imported asset URLs"
63499
64151
  }
63500
64152
  ]), withHandler(({ dir, out, json, assetBase }) => gen2(function* () {
63501
- const built = yield* buildDir(dir, getOrUndefined(assetBase));
64153
+ const dirValue = getOrUndefined(dir);
64154
+ const built = yield* buildDir(dirValue, getOrUndefined(assetBase));
63502
64155
  const problems = collectBuildProblems(built);
63503
64156
  if (!isSuccessfulBuild(built)) {
63504
- const message = built.files.length === 0 ? `No templates found in ${dir}. Email entries must default-export defineEmail with an input schema and fixtures; SMS/WhatsApp entries use their structured channel function.` : `Template build failed (${problems.length} problem(s)).`;
64157
+ const message = built.files.length === 0 ? NO_TEMPLATES(dirValue) : `Template build failed (${problems.length} problem(s)).`;
63505
64158
  if (json) {
63506
64159
  return yield* new CliResultError({
63507
64160
  code: "template_build_failed",
@@ -63515,11 +64168,11 @@ var build2 = make28("build", {
63515
64168
  });
63516
64169
  }
63517
64170
  for (const problem of problems) {
63518
- yield* error(`${problem.file}: ${problem.code} ${problem.message}`);
64171
+ yield* error(formatReportedDiagnostic(problem));
63519
64172
  }
63520
64173
  return yield* new SamvaCliError({ message });
63521
64174
  }
63522
- const target = resolve10(out);
64175
+ const target = resolve14(out);
63523
64176
  const artifact = toTemplateBuildArtifact(built);
63524
64177
  yield* writeJsonFile(target, artifact);
63525
64178
  const result = {
@@ -63532,7 +64185,7 @@ var build2 = make28("build", {
63532
64185
  if (json) {
63533
64186
  yield* printJson(result);
63534
64187
  } else {
63535
- yield* logInfo(`Built ${built.files.length} template file(s) with compiler v${artifact.compilerVersion} to ${target}`);
64188
+ yield* logInfo(`Built ${built.files.length} template file(s) with compiler ${artifact.compilerVersion} to ${target}`);
63536
64189
  }
63537
64190
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
63538
64191
  var listRemote = make28("list", {
@@ -63646,34 +64299,29 @@ var removeRemote = make28("remove", {
63646
64299
  return yield* printJson(result);
63647
64300
  yield* printJson(result);
63648
64301
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
63649
- var exportProject = (input) => tryPromise2({
63650
- try: async () => {
63651
- const entry = Bun.resolveSync("@samva/vite/project", process.cwd());
63652
- const project = await import(pathToFileURL2(entry).href);
63653
- return project.exportEmailProject({
63654
- dir: resolve10(input.dir),
63655
- root: process.cwd(),
63656
- out: resolve10(input.out),
63657
- ...input.assetBase === undefined ? {} : { assetBase: input.assetBase }
63658
- });
64302
+ var exportProject = (input) => runToolchain({
64303
+ action: `Could not export templates${input.dir === undefined ? "" : ` in ${input.dir}`}`,
64304
+ options: {
64305
+ root: process.cwd(),
64306
+ out: resolve14(input.out),
64307
+ ...input.dir === undefined ? {} : { dir: resolve14(input.dir) },
64308
+ ...input.assetBase === undefined ? {} : { assetBase: input.assetBase }
63659
64309
  },
63660
- catch: (cause) => new SamvaCliError({
63661
- message: `Could not export templates in ${input.dir}. Install @samva/markup, @samva/vite, and vite in this project: ${String(cause)}`
63662
- })
64310
+ script: `import { exportProject } from "@samva/vite/project"; ${OPTIONS_PRELUDE} process.stdout.write(JSON.stringify(await exportProject(options)));`
63663
64311
  });
63664
64312
  var exportCommand = make28("export", {
63665
64313
  dir: dirFlag,
63666
64314
  out: Directory2("out").pipe(withDescription2("Directory to write rendered fixtures, assets, and the catalog into")),
63667
64315
  assetBase: String8("asset-base").pipe(withDescription2("Origin the exported assets will be served from; omit to point each exported page at the export's own assets directory"), optional4),
63668
64316
  json: jsonFlag
63669
- }).pipe(withDescription3("Render every local template's fixtures to HTML, text, and assets"), withExamples([
64317
+ }).pipe(withDescription3("Render every local template's fixtures to HTML, text, SMS, WhatsApp files, and assets"), withExamples([
63670
64318
  {
63671
- command: "samva templates export --dir emails --out dist/email",
64319
+ command: "samva templates export --out dist/email",
63672
64320
  description: "Render fixtures for review, images resolving from the export's assets directory"
63673
64321
  }
63674
64322
  ]), withHandler(fnUntraced2(function* ({ dir, out, assetBase, json }) {
63675
64323
  const receipt = yield* exportProject({
63676
- dir,
64324
+ dir: getOrUndefined(dir),
63677
64325
  out,
63678
64326
  assetBase: getOrUndefined(assetBase)
63679
64327
  });
@@ -63681,7 +64329,7 @@ var exportCommand = make28("export", {
63681
64329
  const result = {
63682
64330
  action: "templates.export",
63683
64331
  ok: errors.length === 0,
63684
- out: resolve10(out),
64332
+ out: resolve14(out),
63685
64333
  assetBase: receipt.assets.base,
63686
64334
  templates: receipt.templates.map((template) => ({
63687
64335
  id: template.id,
@@ -63689,7 +64337,7 @@ var exportCommand = make28("export", {
63689
64337
  fixtures: template.fixtures.map((fixture) => fixture.fixture)
63690
64338
  })),
63691
64339
  assets: receipt.assets.assets.length,
63692
- diagnostics: receipt.diagnostics
64340
+ diagnostics: receipt.diagnostics.map((diagnostic) => reportDiagnostic(diagnostic, { file: "." }))
63693
64341
  };
63694
64342
  if (errors.length > 0) {
63695
64343
  const message = `Template export found ${errors.length} error(s).`;
@@ -63714,15 +64362,27 @@ var templatesCommand = make28("templates").pipe(withDescription3("Create, author
63714
64362
  description: "Clone an existing project with its repository credential helper"
63715
64363
  },
63716
64364
  {
63717
- command: "samva templates check --dir emails",
64365
+ command: "samva templates dev",
64366
+ description: "Start the local visual editor for templates/ and emails/"
64367
+ },
64368
+ {
64369
+ command: "samva templates check",
63718
64370
  description: "Check local TSX templates without writing files"
63719
64371
  },
64372
+ {
64373
+ command: "samva templates render welcome --fixture default",
64374
+ description: "Print one fixture's subject, preheader and plain-text body"
64375
+ },
64376
+ {
64377
+ command: "samva templates snapshot welcome --fixture default",
64378
+ description: "Screenshot one fixture at desktop and mobile widths, light and dark"
64379
+ },
63720
64380
  {
63721
64381
  command: "samva templates publish --commit HEAD",
63722
64382
  description: "Publish the current clean commit, then continue with ordinary Git"
63723
64383
  },
63724
64384
  {
63725
- command: "samva templates export --dir emails --out dist/email",
64385
+ command: "samva templates export --out dist/email",
63726
64386
  description: "Render every template's fixtures to HTML, text, and assets"
63727
64387
  },
63728
64388
  {
@@ -63741,7 +64401,10 @@ var templatesCommand = make28("templates").pipe(withDescription3("Create, author
63741
64401
  createRemote,
63742
64402
  updateRemote,
63743
64403
  removeRemote,
64404
+ devCommand,
63744
64405
  check5,
64406
+ renderCommand,
64407
+ snapshotCommand,
63745
64408
  build2,
63746
64409
  exportCommand,
63747
64410
  typesCommand,