@samva/cli 0.21.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/AGENTS.md +14 -4
  2. package/dist/bin.mjs +847 -260
  3. package/package.json +9 -9
package/dist/bin.mjs CHANGED
@@ -677,6 +677,7 @@ var identity = (a) => a;
677
677
  var constant = (value) => () => value;
678
678
  var constTrue = /* @__PURE__ */ constant(true);
679
679
  var constFalse = /* @__PURE__ */ constant(false);
680
+ var constNull = /* @__PURE__ */ constant(null);
680
681
  var constUndefined = /* @__PURE__ */ constant(undefined);
681
682
  var constVoid = constUndefined;
682
683
  function pipe(a, ...args) {
@@ -2012,6 +2013,7 @@ var getOrElse = /* @__PURE__ */ dual(2, (self, onNone) => isNone2(self) ? onNone
2012
2013
  var orElse = /* @__PURE__ */ dual(2, (self, that) => isNone2(self) ? that() : self);
2013
2014
  var fromNullishOr = (a) => a == null ? none2() : some2(a);
2014
2015
  var fromUndefinedOr = (a) => a === undefined ? none2() : some2(a);
2016
+ var getOrNull = /* @__PURE__ */ getOrElse(constNull);
2015
2017
  var getOrUndefined = /* @__PURE__ */ getOrElse(constUndefined);
2016
2018
  var liftThrowable = (f) => (...a) => {
2017
2019
  try {
@@ -4139,10 +4141,6 @@ var ignore = /* @__PURE__ */ dual((args) => isEffect(args[0]), (self, options) =
4139
4141
  onSuccess: (_) => void_
4140
4142
  });
4141
4143
  });
4142
- var option = (self) => match5(self, {
4143
- onFailure: none2,
4144
- onSuccess: some2
4145
- });
4146
4144
  var result = (self) => matchEager(self, {
4147
4145
  onFailure: fail2,
4148
4146
  onSuccess: succeed2
@@ -5744,10 +5742,10 @@ var flatMap3 = flatMap2;
5744
5742
  var andThen2 = andThen;
5745
5743
  var tap2 = tap;
5746
5744
  var result2 = result;
5747
- var option2 = option;
5748
5745
  var exit2 = exit;
5749
5746
  var map7 = map5;
5750
5747
  var as2 = as;
5748
+ var asSome2 = asSome;
5751
5749
  var asVoid2 = asVoid;
5752
5750
  var catch_2 = catch_;
5753
5751
  var catchTag2 = catchTag;
@@ -14278,7 +14276,7 @@ var layer12 = /* @__PURE__ */ layer.pipe(/* @__PURE__ */ provideMerge(/* @__PURE
14278
14276
  // package.json
14279
14277
  var package_default = {
14280
14278
  name: "@samva/cli",
14281
- version: "0.21.0",
14279
+ version: "0.22.1",
14282
14280
  description: "Samva CLI \u2014 email and platform operations from your terminal",
14283
14281
  keywords: [
14284
14282
  "cli",
@@ -14321,7 +14319,7 @@ var package_default = {
14321
14319
  "build:release-artifacts": "bun run scripts/build-release-artifacts.ts",
14322
14320
  test: "vitest run --root ../.. --project cli",
14323
14321
  "test:package": "vitest run --root ../.. --project cli packages/cli/tests/package-install.e2e.test.ts",
14324
- typecheck: "tsc --noEmit && tsc --noEmit -p tests/consumer/tsconfig.json"
14322
+ typecheck: "tsc-rs --noEmit && tsc-rs --noEmit -p tests/consumer/tsconfig.json && tsc-rs --noEmit -p tests/consumer/tsconfig.default.json"
14325
14323
  },
14326
14324
  devDependencies: {
14327
14325
  "@effect/platform-bun": "catalog:effect",
@@ -14334,16 +14332,16 @@ var package_default = {
14334
14332
  "@types/node": "catalog:",
14335
14333
  "better-auth": "catalog:",
14336
14334
  effect: "catalog:effect",
14337
- samva: "workspace:0.23.0",
14338
- typescript: "catalog:",
14335
+ samva: "workspace:0.23.1",
14336
+ "tsc-rs": "catalog:",
14339
14337
  vite: "~8.3.0",
14340
14338
  vitest: "catalog:"
14341
14339
  },
14342
14340
  optionalDependencies: {
14343
- "@samva/cli-darwin-arm64": "workspace:0.21.0",
14344
- "@samva/cli-darwin-x64": "workspace:0.21.0",
14345
- "@samva/cli-linux-arm64": "workspace:0.21.0",
14346
- "@samva/cli-linux-x64": "workspace:0.21.0"
14341
+ "@samva/cli-darwin-arm64": "workspace:0.22.1",
14342
+ "@samva/cli-darwin-x64": "workspace:0.22.1",
14343
+ "@samva/cli-linux-arm64": "workspace:0.22.1",
14344
+ "@samva/cli-linux-x64": "workspace:0.22.1"
14347
14345
  },
14348
14346
  engines: {
14349
14347
  node: ">=18"
@@ -15841,7 +15839,7 @@ var map10 = /* @__PURE__ */ dual(2, (self, f) => {
15841
15839
  var withDefault3 = /* @__PURE__ */ dual(2, (self, defaultValue) => {
15842
15840
  return make28((provider, pathPrefix) => mapEager2(evaluateAt(self, provider, pathPrefix), (resolution) => isFailure2(resolution) ? succeed2(defaultValue) : resolution));
15843
15841
  });
15844
- var option3 = (self) => self.pipe(map10(some2), withDefault3(none2()));
15842
+ var option2 = (self) => self.pipe(map10(some2), withDefault3(none2()));
15845
15843
  var isSourceError = (u) => isTagged(u, "SourceError");
15846
15844
  var cursorToString = () => "<configuration>";
15847
15845
  var mapReasons = (f) => (self) => catchCause2(self, (cause) => failCause3(fromReasons(cause.reasons.map(f))));
@@ -25566,7 +25564,7 @@ var __exportAll = (all, no_symbols) => {
25566
25564
  };
25567
25565
 
25568
25566
  // ../sdk/dist/package.js
25569
- var version = "0.23.0";
25567
+ var version = "0.23.1";
25570
25568
 
25571
25569
  // ../sdk/dist/client-attribution.js
25572
25570
  var SDK_VERSION = version;
@@ -25909,7 +25907,7 @@ var commandTree = {
25909
25907
  update: {},
25910
25908
  remove: {}
25911
25909
  },
25912
- brands: { list: {}, get: {}, pull: {}, push: {} },
25910
+ brands: { list: {}, get: {}, pull: {}, push: {}, default: {}, remove: {} },
25913
25911
  mailboxes: {
25914
25912
  events: {},
25915
25913
  list: {},
@@ -54599,6 +54597,8 @@ var make41 = /* @__PURE__ */ forGroup(BrandsGroup);
54599
54597
  var list3 = /* @__PURE__ */ make41("brandsList");
54600
54598
  var get9 = /* @__PURE__ */ make41("brandsGet");
54601
54599
  var update4 = /* @__PURE__ */ make41("brandsUpdate");
54600
+ var remove4 = /* @__PURE__ */ make41("brandsRemove");
54601
+ var setDefault = /* @__PURE__ */ make41("brandsSetDefault");
54602
54602
 
54603
54603
  // src/commands/brands.ts
54604
54604
  var SNAPSHOT_DIR = [".samva", "brands"];
@@ -54755,11 +54755,62 @@ var push = make29("push", {
54755
54755
  yield* log(`Updated brand ${updated.slug} from ${file}.`);
54756
54756
  yield* logInfo("Published templates keep the brand they were built against until they are republished.");
54757
54757
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
54758
- var brandsCommand = make29("brands").pipe(withDescription3("List, snapshot, and update the organization's brands (what samva:brand resolves to)"), withExamples([
54758
+ var requiredSlugArgument = String10("slug").pipe(withDescription4("Brand slug"));
54759
+ var dryRunFlag2 = Boolean6("dry-run").pipe(withDescription2("Print the request without sending it"), withDefault5(false));
54760
+ var yesFlag2 = Boolean6("yes").pipe(withDescription2("Confirm without prompting (required outside a terminal)"), withDefault5(false));
54761
+ var makeDefault = make29("default", {
54762
+ slug: requiredSlugArgument,
54763
+ dryRun: dryRunFlag2,
54764
+ json: jsonFlag
54765
+ }).pipe(withDescription3("Make a brand the organization's default: bare samva:brand and domains without a brand resolve to it"), withExamples([
54766
+ { command: "samva brands default acme" },
54767
+ { command: "samva brands default acme --dry-run --json" }
54768
+ ]), withHandler(({ slug, dryRun, json }) => gen2(function* () {
54769
+ if (dryRun)
54770
+ return yield* json ? printJson({ dryRun: true, action: "brands.default", slug }) : log(`[dry-run] Would make brand ${slug} the default.`);
54771
+ const client = yield* getClientProvider;
54772
+ const brand = yield* setDefault({ slug }).pipe(client.provide, mapError2(apiFailure(`Could not make brand ${slug} the default`)));
54773
+ const result = {
54774
+ action: "brands.default",
54775
+ slug: brand.slug,
54776
+ name: brand.name,
54777
+ isDefault: brand.isDefault
54778
+ };
54779
+ if (json)
54780
+ return yield* printJson(result);
54781
+ yield* log(`Brand ${brand.slug} (${brand.name}) is now the default.`);
54782
+ }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
54783
+ var remove5 = make29("remove", {
54784
+ slug: requiredSlugArgument,
54785
+ dryRun: dryRunFlag2,
54786
+ yes: yesFlag2,
54787
+ json: jsonFlag
54788
+ }).pipe(withDescription3("Permanently delete a brand. The default brand cannot be deleted; make another brand the default first. Domains that used it fall back to the default."), withExamples([
54789
+ { command: "samva brands remove promo --dry-run" },
54790
+ { command: "samva brands remove promo --yes" }
54791
+ ]), withHandler(({ slug, dryRun, yes, json }) => gen2(function* () {
54792
+ if (dryRun)
54793
+ return yield* json ? printJson({ dryRun: true, action: "brands.remove", slug }) : log(`[dry-run] Would permanently remove brand ${slug}.`);
54794
+ yield* confirmDestructive({
54795
+ yes,
54796
+ json,
54797
+ message: `Permanently remove brand ${slug}?`,
54798
+ nonInteractiveMessage: "Permanent brand deletion requires --yes in non-interactive mode. Preview with --dry-run."
54799
+ });
54800
+ const client = yield* getClientProvider;
54801
+ yield* remove4({ slug }).pipe(client.provide, mapError2(apiFailure(`Could not remove brand ${slug}`)));
54802
+ const result = { action: "brands.remove", slug, removed: true };
54803
+ if (json)
54804
+ return yield* printJson(result);
54805
+ yield* log(`Removed brand ${slug}.`);
54806
+ }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
54807
+ var brandsCommand = make29("brands").pipe(withDescription3("List, snapshot, update, and delete the organization's brands (what samva:brand resolves to)"), withExamples([
54759
54808
  { command: "samva brands list" },
54760
54809
  { command: "samva brands pull", description: "Snapshot the default brand for offline builds" },
54761
- { command: "samva brands push brand.css --brand acme" }
54762
- ]), withSubcommands([list4, get10, pull, push]));
54810
+ { command: "samva brands push brand.css --brand acme" },
54811
+ { command: "samva brands default acme", description: "Make acme the default brand" },
54812
+ { command: "samva brands remove promo --yes" }
54813
+ ]), withSubcommands([list4, get10, pull, push, makeDefault, remove5]));
54763
54814
 
54764
54815
  // ../sdk/dist/effect/generated/campaigns.js
54765
54816
  var CampaignId2 = /* @__PURE__ */ String4.check(isPattern2(/* @__PURE__ */ new RegExp("^camp_[0-9a-hjkmnp-tv-z]{16,24}$", "u")).annotate({
@@ -55387,7 +55438,7 @@ var excludeTagsFlag = String9("exclude-tags").pipe(withDescription2("Contact tag
55387
55438
  var contactIdsFlag = String9("contact-ids").pipe(withDescription2("Explicit contact ids to target (comma-separated or repeated)"), atLeast2(0));
55388
55439
  var unsubscribeGroupIdFlag = String9("unsubscribe-group-id").pipe(withDescription2("Unsubscribe group id applied to every send; overrides the organization's default group"), optional4);
55389
55440
  var pageSizeFlag = Int5("page-size").pipe(withDescription2("Recipients dispatched per batch"), optional4);
55390
- var dryRunFlag2 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
55441
+ var dryRunFlag3 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
55391
55442
  var trackOpensFlag = Literals3("track-opens", ["on", "off"]).pipe(withDescription2("Override open tracking for campaign sends; recipient opt-out wins"), optional4);
55392
55443
  var trackClicksFlag = Literals3("track-clicks", ["on", "off"]).pipe(withDescription2("Override click tracking for campaign sends; recipient opt-out wins"), optional4);
55393
55444
  var atFlag = String9("at").pipe(withDescription2("Absolute run start instant as ISO 8601 (defaults to now)"), optional4);
@@ -55415,7 +55466,7 @@ var create4 = make29("create", {
55415
55466
  pageSize: pageSizeFlag,
55416
55467
  trackOpens: trackOpensFlag,
55417
55468
  trackClicks: trackClicksFlag,
55418
- dryRun: dryRunFlag2,
55469
+ dryRun: dryRunFlag3,
55419
55470
  json: jsonFlag
55420
55471
  }).pipe(withDescription3("Create an email campaign targeting tags or explicit contacts"), withExamples([
55421
55472
  {
@@ -55794,7 +55845,7 @@ var runCreate = make29("create", {
55794
55845
  campaignId: String10("campaignId"),
55795
55846
  at: atFlag,
55796
55847
  idempotencyKey: idempotencyKeyFlag,
55797
- dryRun: dryRunFlag2,
55848
+ dryRun: dryRunFlag3,
55798
55849
  json: jsonFlag
55799
55850
  }).pipe(withDescription3("Create a run for a campaign (starts immediately without --at)"), withExamples([
55800
55851
  { command: "samva campaigns runs create camp_abc123", description: "Start now" },
@@ -55907,7 +55958,7 @@ var runGet = make29("get", {
55907
55958
  var runPause = make29("pause", {
55908
55959
  campaignId: String10("campaignId"),
55909
55960
  runId: String10("runId"),
55910
- dryRun: dryRunFlag2,
55961
+ dryRun: dryRunFlag3,
55911
55962
  json: jsonFlag
55912
55963
  }).pipe(withDescription3("Pause a running campaign run"), withExamples([{ command: "samva campaigns runs pause camp_abc123 crun_def456" }]), withHandler(({ campaignId, runId, dryRun, json }) => gen2(function* () {
55913
55964
  if (dryRun)
@@ -55923,7 +55974,7 @@ var runPause = make29("pause", {
55923
55974
  var runResume = make29("resume", {
55924
55975
  campaignId: String10("campaignId"),
55925
55976
  runId: String10("runId"),
55926
- dryRun: dryRunFlag2,
55977
+ dryRun: dryRunFlag3,
55927
55978
  json: jsonFlag
55928
55979
  }).pipe(withDescription3("Resume a paused campaign run"), withExamples([{ command: "samva campaigns runs resume camp_abc123 crun_def456" }]), withHandler(({ campaignId, runId, dryRun, json }) => gen2(function* () {
55929
55980
  if (dryRun)
@@ -61003,7 +61054,7 @@ var rotateDkim = make29("rotate-dkim", {
61003
61054
  yield* log("The minimum retention date does not authorize deletion. Retirement requires separate approval and verification that at least two actual former-record TTLs have elapsed.");
61004
61055
  yield* log(`After DNS publication, advance verification: samva email domains check ${id}`);
61005
61056
  })));
61006
- var remove4 = make29("remove", {
61057
+ var remove6 = make29("remove", {
61007
61058
  id: String10("id").pipe(withDescription4("Domain id to permanently remove")),
61008
61059
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the deletion plan without calling the API"), withDefault5(false)),
61009
61060
  yes: Boolean6("yes").pipe(withDescription2("Confirm permanent deletion (required for non-interactive use)"), withDefault5(false)),
@@ -61026,7 +61077,7 @@ var remove4 = make29("remove", {
61026
61077
  return yield* printJson(data);
61027
61078
  yield* log(`Removed domain ${id}.`);
61028
61079
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
61029
- var setDefault = make29("set-default", {
61080
+ var setDefault2 = make29("set-default", {
61030
61081
  id: String10("id").pipe(withDescription4("Domain id to make the default")),
61031
61082
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the planned change without calling the API"), withDefault5(false)),
61032
61083
  json: jsonFlag
@@ -61144,7 +61195,7 @@ var customDisable = make29("disable", {
61144
61195
  var customTracking = make29("custom-tracking").pipe(withDescription3("Manage a domain's custom tracking (custom open/click subdomain)"), withExamples([{ command: "samva email domains custom-tracking enable dom_abc123" }]), withSubcommands([customEnable, customStatus, customRecheck, customDisable]));
61145
61196
  var domainsCommand = make29("domains").pipe(withDescription3("Manage email sending domains"), withExamples([
61146
61197
  { command: "samva email domains list", description: "List configured domains" }
61147
- ]), withSubcommands([list9, add2, check2, rotateDkim, setDefault, remove4, customTracking]));
61198
+ ]), withSubcommands([list9, add2, check2, rotateDkim, setDefault2, remove6, customTracking]));
61148
61199
 
61149
61200
  // src/commands/email/receiving.ts
61150
61201
  var destinationLabel = (destination) => {
@@ -61683,7 +61734,7 @@ var idempotencyKeyFlag2 = String9("idempotency-key").pipe(withDescription2("Key
61683
61734
  var trackOpensFlag2 = Literals3("track-opens", ["on", "off"]).pipe(withDescription2("Override open tracking for this send; recipient opt-out still wins"), optional4);
61684
61735
  var trackClicksFlag2 = Literals3("track-clicks", ["on", "off"]).pipe(withDescription2("Override click tracking for this send; recipient opt-out still wins"), optional4);
61685
61736
  var attachFlag = String9("attach").pipe(withDescription2("File to attach (repeatable); small files travel inline, larger ones upload through media first"), atLeast2(0));
61686
- var dryRunFlag3 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
61737
+ var dryRunFlag4 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
61687
61738
  var bodySourceName = (input) => {
61688
61739
  if (input.templateRefValue)
61689
61740
  return "template";
@@ -61720,7 +61771,7 @@ var send3 = make29("send", {
61720
61771
  trackOpens: trackOpensFlag2,
61721
61772
  trackClicks: trackClicksFlag2,
61722
61773
  attach: attachFlag,
61723
- dryRun: dryRunFlag3,
61774
+ dryRun: dryRunFlag4,
61724
61775
  wait: Boolean6("wait").pipe(withDescription2("Wait for delivered/read or a terminal delivery failure"), withDefault5(false)),
61725
61776
  timeout: timeoutFlag,
61726
61777
  json: jsonFlag
@@ -62130,7 +62181,7 @@ var check3 = make29("check", {
62130
62181
  ["Verified at", data.verifiedAt ?? null]
62131
62182
  ]);
62132
62183
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
62133
- var setDefault2 = make29("set-default", {
62184
+ var setDefault3 = make29("set-default", {
62134
62185
  id: String10("id").pipe(withDescription4("Sender id to make the default")),
62135
62186
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the planned change without calling the API"), withDefault5(false)),
62136
62187
  json: jsonFlag
@@ -62146,7 +62197,7 @@ var setDefault2 = make29("set-default", {
62146
62197
  return yield* printJson(data);
62147
62198
  yield* log(`Sender ${id} is now the default sender.`);
62148
62199
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
62149
- var remove5 = make29("remove", {
62200
+ var remove7 = make29("remove", {
62150
62201
  id: String10("id").pipe(withDescription4("Sender id to permanently remove")),
62151
62202
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the deletion plan without calling the API"), withDefault5(false)),
62152
62203
  yes: Boolean6("yes").pipe(withDescription2("Confirm permanent deletion (required for non-interactive use)"), withDefault5(false)),
@@ -62171,7 +62222,7 @@ var remove5 = make29("remove", {
62171
62222
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
62172
62223
  var sendersCommand = make29("senders").pipe(withDescription3("Manage sender addresses"), withExamples([
62173
62224
  { command: "samva email senders list", description: "List sender addresses" }
62174
- ]), withSubcommands([list12, add3, check3, setDefault2, remove5]));
62225
+ ]), withSubcommands([list12, add3, check3, setDefault3, remove7]));
62175
62226
 
62176
62227
  // src/commands/email/tracking.ts
62177
62228
  var toggle = (name, description) => Literals3(name, ["on", "off"]).pipe(withDescription2(description), optional4);
@@ -64012,7 +64063,7 @@ __export(exports_mailboxes, {
64012
64063
  mailboxes_exports: () => mailboxes_exports2,
64013
64064
  pause: () => pause,
64014
64065
  releaseMessage: () => releaseMessage,
64015
- remove: () => remove6,
64066
+ remove: () => remove8,
64016
64067
  removeEndpoint: () => removeEndpoint,
64017
64068
  removeThreadLabels: () => removeThreadLabels,
64018
64069
  reply: () => reply,
@@ -67061,7 +67112,7 @@ var mailboxes_exports2 = /* @__PURE__ */ __exportAll({
67061
67112
  listThreads: () => listThreads,
67062
67113
  pause: () => pause,
67063
67114
  releaseMessage: () => releaseMessage,
67064
- remove: () => remove6,
67115
+ remove: () => remove8,
67065
67116
  removeEndpoint: () => removeEndpoint,
67066
67117
  removeThreadLabels: () => removeThreadLabels,
67067
67118
  reply: () => reply,
@@ -67085,7 +67136,7 @@ var list16 = /* @__PURE__ */ make50("mailboxesList");
67085
67136
  var create6 = /* @__PURE__ */ make50("mailboxesCreate");
67086
67137
  var get19 = /* @__PURE__ */ make50("mailboxesGet");
67087
67138
  var update8 = /* @__PURE__ */ make50("mailboxesUpdate");
67088
- var remove6 = /* @__PURE__ */ make50("mailboxesRemove");
67139
+ var remove8 = /* @__PURE__ */ make50("mailboxesRemove");
67089
67140
  var createEndpoint = /* @__PURE__ */ make50("mailboxesCreateEndpoint");
67090
67141
  var updateEndpoint = /* @__PURE__ */ make50("mailboxesUpdateEndpoint");
67091
67142
  var removeEndpoint = /* @__PURE__ */ make50("mailboxesRemoveEndpoint");
@@ -67253,7 +67304,7 @@ var reconnectNotice = (output, reconnect) => {
67253
67304
  };
67254
67305
  var tailEvents = (options) => gen2(function* () {
67255
67306
  const provider = yield* getClientProvider;
67256
- const override = yield* option3(String5("SAMVA_EVENTS_URL"));
67307
+ const override = yield* option2(String5("SAMVA_EVENTS_URL"));
67257
67308
  const eventsUrl = getOrUndefined(override);
67258
67309
  yield* untilInterrupted((interrupted) => provider.provide(events3({
67259
67310
  cursor: options.cursor,
@@ -68747,7 +68798,7 @@ var list17 = /* @__PURE__ */ make51("journeysList");
68747
68798
  var create7 = /* @__PURE__ */ make51("journeysCreate");
68748
68799
  var get20 = /* @__PURE__ */ make51("journeysGet");
68749
68800
  var update9 = /* @__PURE__ */ make51("journeysUpdate");
68750
- var remove7 = /* @__PURE__ */ make51("journeysRemove");
68801
+ var remove9 = /* @__PURE__ */ make51("journeysRemove");
68751
68802
  var replaceDraft = /* @__PURE__ */ make51("journeysReplaceDraft");
68752
68803
  var publish3 = /* @__PURE__ */ make51("journeysPublish");
68753
68804
  var pause2 = /* @__PURE__ */ make51("journeysPause");
@@ -68974,7 +69025,7 @@ var destructive = (name, description, warning, run, failure) => make29(name, {
68974
69025
  ]);
68975
69026
  }).pipe(quietWhenJson(json))));
68976
69027
  var archive4 = destructive("archive", "Archive a journey and exit its active runs", "Archive and exit every active run of", (id) => archive3({ id }), "Could not archive journey");
68977
- var remove8 = destructive("delete", "Delete a never-published or archived journey with its versions and runs", "Permanently delete", (id) => remove7({ id }), "Could not delete journey");
69028
+ var remove10 = destructive("delete", "Delete a never-published or archived journey with its versions and runs", "Permanently delete", (id) => remove9({ id }), "Could not delete journey");
68978
69029
  var getVersion3 = make29("version", {
68979
69030
  id: idArgument2,
68980
69031
  version: Int6("version").pipe(withDescription4("Published version number")),
@@ -69101,7 +69152,7 @@ var journeysCommand = make29("journeys").pipe(withDescription3("Create, publish,
69101
69152
  pause3,
69102
69153
  resume3,
69103
69154
  archive4,
69104
- remove8,
69155
+ remove10,
69105
69156
  getVersion3,
69106
69157
  stats2,
69107
69158
  runs2
@@ -72743,7 +72794,7 @@ var actionDecision = (verb) => make29(verb, {
72743
72794
  })).pipe(client.provide, mapError2((e) => mailboxApiError(`Could not ${verb} action`, e, { personOnly: verb })));
72744
72795
  yield* json ? printJson(result) : printAction(result);
72745
72796
  })));
72746
- var remove9 = make29("remove", {
72797
+ var remove11 = make29("remove", {
72747
72798
  id: String10("mailbox-id").pipe(withSchema3(MailboxId)),
72748
72799
  dryRun: Boolean6("dry-run").pipe(withDefault5(false)),
72749
72800
  yes: Boolean6("yes").pipe(withDefault5(false)),
@@ -72761,7 +72812,7 @@ var remove9 = make29("remove", {
72761
72812
  nonInteractiveMessage: "Permanent mailbox deletion requires --yes in non-interactive mode. Preview with --dry-run."
72762
72813
  });
72763
72814
  const client = yield* getClientProvider;
72764
- const result = yield* remove6({ id }).pipe(client.provide, mapError2((e) => mailboxApiError("Could not remove mailbox", e)));
72815
+ const result = yield* remove8({ id }).pipe(client.provide, mapError2((e) => mailboxApiError("Could not remove mailbox", e)));
72765
72816
  if (json)
72766
72817
  return yield* printJson(result);
72767
72818
  yield* log(`Removed mailbox ${id}. The messages it received stay with the organization.`);
@@ -73052,7 +73103,7 @@ var mailboxesCommand = make29("mailboxes").pipe(withDescription3("Mailbox readin
73052
73103
  actionCancel,
73053
73104
  actionRetry
73054
73105
  ])),
73055
- remove9,
73106
+ remove11,
73056
73107
  threads,
73057
73108
  read2,
73058
73109
  send7,
@@ -73283,7 +73334,7 @@ var list20 = /* @__PURE__ */ make52("namespacesList");
73283
73334
  var create10 = /* @__PURE__ */ make52("namespacesCreate");
73284
73335
  var get23 = /* @__PURE__ */ make52("namespacesGet");
73285
73336
  var update11 = /* @__PURE__ */ make52("namespacesUpdate");
73286
- var remove10 = /* @__PURE__ */ make52("namespacesRemove");
73337
+ var remove12 = /* @__PURE__ */ make52("namespacesRemove");
73287
73338
  var assignResource = /* @__PURE__ */ make52("namespacesAssignResource");
73288
73339
  var removeResource = /* @__PURE__ */ make52("namespacesRemoveResource");
73289
73340
  var getUsage2 = /* @__PURE__ */ make52("namespacesGetUsage");
@@ -73291,7 +73342,7 @@ var getUsage2 = /* @__PURE__ */ make52("namespacesGetUsage");
73291
73342
  // src/commands/namespaces.ts
73292
73343
  var idArgument3 = String10("id").pipe(withDescription4("Namespace id (ns_...)"));
73293
73344
  var optionalString2 = (name, description) => String9(name).pipe(withDescription2(description), optional4);
73294
- var dryRunFlag4 = Boolean6("dry-run").pipe(withDescription2("Print the planned change without calling the API"), withDefault5(false));
73345
+ var dryRunFlag5 = Boolean6("dry-run").pipe(withDescription2("Print the planned change without calling the API"), withDefault5(false));
73295
73346
  var RESOURCE_FLAGS = ["domain", "sender", "contact", "template"];
73296
73347
  var resourceFlags = {
73297
73348
  domain: optionalString2("domain", "Domain id (dom_...)"),
@@ -73381,7 +73432,7 @@ var list21 = make29("list", {
73381
73432
  var create11 = make29("create", {
73382
73433
  name: String10("name").pipe(withDescription4("Namespace name")),
73383
73434
  metadata: String9("metadata").pipe(withDescription2("Your own label, key=value (repeatable); Samva stores and never reads it"), atLeast2(0)),
73384
- dryRun: dryRunFlag4,
73435
+ dryRun: dryRunFlag5,
73385
73436
  json: jsonFlag
73386
73437
  }).pipe(withDescription3("Create a namespace, a sub-tenant for one of your customers"), withExamples([
73387
73438
  { command: "samva namespaces create Acme --metadata customerId=cus_123" },
@@ -73420,7 +73471,7 @@ var update12 = make29("update", {
73420
73471
  name: optionalString2("name", "New name"),
73421
73472
  metadata: String9("metadata").pipe(withDescription2("Set a metadata label, key=value (repeatable); other keys are kept"), atLeast2(0)),
73422
73473
  unsetMetadata: String9("unset-metadata").pipe(withDescription2("Delete a metadata key (repeatable)"), atLeast2(0)),
73423
- dryRun: dryRunFlag4,
73474
+ dryRun: dryRunFlag5,
73424
73475
  json: jsonFlag
73425
73476
  }).pipe(withDescription3("Rename a namespace or change its metadata"), withExamples([
73426
73477
  { command: "samva namespaces update ns_7q2xk9mvt4znw8rh --name Acme --metadata plan=pro" },
@@ -73467,9 +73518,9 @@ var update12 = make29("update", {
73467
73518
  ["Updated", updated.updatedAt]
73468
73519
  ]);
73469
73520
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
73470
- var remove11 = make29("delete", {
73521
+ var remove13 = make29("delete", {
73471
73522
  id: idArgument3,
73472
- dryRun: dryRunFlag4,
73523
+ dryRun: dryRunFlag5,
73473
73524
  yes: Boolean6("yes").pipe(withDefault5(false)),
73474
73525
  json: jsonFlag
73475
73526
  }).pipe(withDescription3("Delete an empty namespace; one that still holds anything is refused with a count of what remains"), withExamples([
@@ -73485,7 +73536,7 @@ var remove11 = make29("delete", {
73485
73536
  nonInteractiveMessage: "Namespace deletion requires --yes in non-interactive mode. Preview with --dry-run."
73486
73537
  });
73487
73538
  const client = yield* getClientProvider;
73488
- const result = yield* remove10({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not delete namespace", error)));
73539
+ const result = yield* remove12({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not delete namespace", error)));
73489
73540
  if (json)
73490
73541
  return yield* printJson(result);
73491
73542
  yield* log(`Deleted namespace ${id}.`);
@@ -73493,7 +73544,7 @@ var remove11 = make29("delete", {
73493
73544
  var assign2 = make29("assign", {
73494
73545
  id: idArgument3,
73495
73546
  ...resourceFlags,
73496
- dryRun: dryRunFlag4,
73547
+ dryRun: dryRunFlag5,
73497
73548
  json: jsonFlag
73498
73549
  }).pipe(withDescription3("Move a domain, sender, contact, or template into a namespace; pass exactly one resource flag"), withExamples([
73499
73550
  { command: "samva namespaces assign ns_7q2xk9mvt4znw8rh --domain dom_xyz789" },
@@ -73523,7 +73574,7 @@ var assign2 = make29("assign", {
73523
73574
  var unassign = make29("unassign", {
73524
73575
  id: idArgument3,
73525
73576
  ...resourceFlags,
73526
- dryRun: dryRunFlag4,
73577
+ dryRun: dryRunFlag5,
73527
73578
  json: jsonFlag
73528
73579
  }).pipe(withDescription3("Return a domain, sender, contact, or template to organization level; pass exactly one resource flag"), withExamples([
73529
73580
  { command: "samva namespaces unassign ns_7q2xk9mvt4znw8rh --domain dom_xyz789" }
@@ -73571,7 +73622,7 @@ var usage3 = make29("usage", {
73571
73622
  ["Total", data.totalMessages]
73572
73623
  ]);
73573
73624
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
73574
- var namespacesCommand = make29("namespaces").pipe(withDescription3("Manage namespaces, the sub-tenants of your organization"), withExamples([{ command: "samva namespaces list --json" }]), withSubcommands([list21, create11, get24, update12, remove11, assign2, unassign, usage3]));
73625
+ var namespacesCommand = make29("namespaces").pipe(withDescription3("Manage namespaces, the sub-tenants of your organization"), withExamples([{ command: "samva namespaces list --json" }]), withSubcommands([list21, create11, get24, update12, remove13, assign2, unassign, usage3]));
73575
73626
 
73576
73627
  // ../sdk/dist/effect/generated/whoami.js
73577
73628
  var WhoamiUser = /* @__PURE__ */ Struct({
@@ -73912,7 +73963,7 @@ var use2 = make29("use", { name: String10("name") }).pipe(withDescription3("Sele
73912
73963
  yield* writeConfig({ ...config, activeProfile: name });
73913
73964
  yield* printResult({ action: "profile.use", name }, `Active profile set to ${name}.`);
73914
73965
  })));
73915
- var remove12 = make29("delete", {
73966
+ var remove14 = make29("delete", {
73916
73967
  name: String10("name"),
73917
73968
  yes: Boolean6("yes").pipe(withDefault5(false))
73918
73969
  }).pipe(withDescription3("Delete a profile and its OS-keyring credential"), withHandler(fnUntraced2(function* ({ name, yes }) {
@@ -73937,7 +73988,7 @@ var remove12 = make29("delete", {
73937
73988
  yield* (yield* Service4).clear(name);
73938
73989
  yield* printResult({ action: "profile.delete", name }, `Deleted profile ${name}.`);
73939
73990
  })));
73940
- var profileCommand = make29("profile").pipe(withDescription3("Manage user-level credential identity and organization contexts"), withSubcommands([list23, show, create12, use2, remove12]));
73991
+ var profileCommand = make29("profile").pipe(withDescription3("Manage user-level credential identity and organization contexts"), withSubcommands([list23, show, create12, use2, remove14]));
73941
73992
 
73942
73993
  // ../sdk/dist/effect/generated/scheduled-messages.js
73943
73994
  var ScheduledMessageId2 = /* @__PURE__ */ String4.check(isPattern2(/* @__PURE__ */ new RegExp("^sched_[0-9a-hjkmnp-tv-z]{16,24}$", "u")).annotate({
@@ -74194,7 +74245,7 @@ var atFlag2 = String9("at").pipe(withDescription2("Absolute send instant as an I
74194
74245
  var timezoneFlag = String9("timezone").pipe(withDescription2("IANA timezone for the schedule, e.g. America/New_York"), optional4);
74195
74246
  var idempotencyKeyFlag3 = String9("idempotency-key").pipe(withDescription2("Idempotency key so retries do not schedule duplicates"), optional4);
74196
74247
  var referenceIdFlag = String9("reference-id").pipe(withDescription2("Your own reference id to correlate this schedule"), optional4);
74197
- var dryRunFlag5 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
74248
+ var dryRunFlag6 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
74198
74249
  var trackOpensFlag3 = Literals3("track-opens", ["on", "off"]).pipe(withDescription2("Override open tracking for this scheduled send; recipient opt-out wins"), optional4);
74199
74250
  var trackClicksFlag3 = Literals3("track-clicks", ["on", "off"]).pipe(withDescription2("Override click tracking for this scheduled send; recipient opt-out wins"), optional4);
74200
74251
  var statusFlag2 = Literals3("status", SCHEDULED_STATUSES).pipe(withDescription2("Filter by status"), optional4);
@@ -74219,7 +74270,7 @@ var create14 = make29("create", {
74219
74270
  referenceId: referenceIdFlag,
74220
74271
  trackOpens: trackOpensFlag3,
74221
74272
  trackClicks: trackClicksFlag3,
74222
- dryRun: dryRunFlag5,
74273
+ dryRun: dryRunFlag6,
74223
74274
  json: jsonFlag
74224
74275
  }).pipe(withDescription3("Schedule an email to send at a future time"), withExamples([
74225
74276
  {
@@ -74598,7 +74649,7 @@ var subscriptionsCommand = make29("subscriptions").pipe(withDescription3("Reques
74598
74649
  // src/commands/templates.ts
74599
74650
  import { constants as constants2 } from "fs";
74600
74651
  import { access as access3, stat as stat5 } from "fs/promises";
74601
- import { dirname as dirname5, resolve as resolve16 } from "path";
74652
+ import { basename as basename3, dirname as dirname5, resolve as resolve17 } from "path";
74602
74653
  import { pathToFileURL as pathToFileURL5 } from "url";
74603
74654
 
74604
74655
  // ../sdk/dist/effect/generated/templates.js
@@ -76862,7 +76913,7 @@ var create15 = /* @__PURE__ */ make54("templatesCreate");
76862
76913
  var createFromWebsite = /* @__PURE__ */ make54("templatesCreateFromWebsite");
76863
76914
  var addEntry = /* @__PURE__ */ make54("templatesAddEntry");
76864
76915
  var getById = /* @__PURE__ */ make54("templatesGetById");
76865
- var remove13 = /* @__PURE__ */ make54("templatesRemove");
76916
+ var remove15 = /* @__PURE__ */ make54("templatesRemove");
76866
76917
  var update13 = /* @__PURE__ */ make54("templatesUpdate");
76867
76918
  var openWorkspace = /* @__PURE__ */ make54("templatesOpenWorkspace");
76868
76919
  var getGitRepository = /* @__PURE__ */ make54("templatesGetGitRepository");
@@ -76886,6 +76937,146 @@ var offlineFlag = Boolean6("offline").pipe(withDescription2("Use the cached bran
76886
76937
 
76887
76938
  // src/commands/templates/toolchain.ts
76888
76939
  import { pathToFileURL as pathToFileURL3 } from "url";
76940
+ // ../../node_modules/.bun/@samva+markup@0.17.3+a19bcd7db259aaf7/node_modules/@samva/markup/package.json
76941
+ var package_default2 = {
76942
+ name: "@samva/markup",
76943
+ version: "0.17.3",
76944
+ description: "Type-safe authoring and compilation primitives for Samva templates",
76945
+ keywords: [
76946
+ "email",
76947
+ "jsx",
76948
+ "samva",
76949
+ "sml",
76950
+ "sms",
76951
+ "templates",
76952
+ "tsx",
76953
+ "whatsapp"
76954
+ ],
76955
+ homepage: "https://samva.dev",
76956
+ bugs: {
76957
+ url: "https://github.com/SamvaHQ/SML/issues"
76958
+ },
76959
+ license: "MIT",
76960
+ author: "Arya Labs, Inc.",
76961
+ repository: {
76962
+ type: "git",
76963
+ url: "https://github.com/SamvaHQ/SML.git",
76964
+ directory: "packages/markup"
76965
+ },
76966
+ files: [
76967
+ "dist",
76968
+ "samva-brand.d.ts",
76969
+ "generated",
76970
+ "skills",
76971
+ "package.json",
76972
+ "README.md",
76973
+ "CHANGELOG.md",
76974
+ "AGENTS.md",
76975
+ "LICENSE"
76976
+ ],
76977
+ type: "module",
76978
+ sideEffects: false,
76979
+ exports: {
76980
+ ".": {
76981
+ types: "./dist/index.d.ts",
76982
+ import: "./dist/index.js"
76983
+ },
76984
+ "./email": {
76985
+ types: "./dist/email.d.ts",
76986
+ import: "./dist/email.js"
76987
+ },
76988
+ "./email/jsx-runtime": {
76989
+ types: "./dist/email/jsx-runtime.d.ts",
76990
+ import: "./dist/email/jsx-runtime.js"
76991
+ },
76992
+ "./email/jsx-dev-runtime": {
76993
+ types: "./dist/email/jsx-dev-runtime.d.ts",
76994
+ import: "./dist/email/jsx-dev-runtime.js"
76995
+ },
76996
+ "./fmt": {
76997
+ types: "./dist/fmt.d.ts",
76998
+ import: "./dist/fmt.js"
76999
+ },
77000
+ "./sms": {
77001
+ types: "./dist/sms.d.ts",
77002
+ import: "./dist/sms.js"
77003
+ },
77004
+ "./whatsapp": {
77005
+ types: "./dist/whatsapp.d.ts",
77006
+ import: "./dist/whatsapp.js"
77007
+ },
77008
+ "./input-schema": {
77009
+ types: "./dist/input-schema.d.ts",
77010
+ import: "./dist/input-schema.js"
77011
+ },
77012
+ "./compiler": {
77013
+ types: "./dist/compiler.d.ts",
77014
+ import: "./dist/compiler.js"
77015
+ },
77016
+ "./compatibility": {
77017
+ types: "./dist/compatibility.d.ts",
77018
+ import: "./dist/compatibility.js"
77019
+ },
77020
+ "./diagnostics": {
77021
+ types: "./dist/diagnostics.d.ts",
77022
+ import: "./dist/diagnostics.js"
77023
+ },
77024
+ "./render": {
77025
+ types: "./dist/render.d.ts",
77026
+ import: "./dist/render.js"
77027
+ },
77028
+ "./edit": {
77029
+ types: "./dist/edit.d.ts",
77030
+ import: "./dist/edit.js"
77031
+ },
77032
+ "./brand": {
77033
+ types: "./dist/brand.d.ts",
77034
+ import: "./dist/brand.js"
77035
+ },
77036
+ "./samva-brand": {
77037
+ types: "./samva-brand.d.ts"
77038
+ },
77039
+ "./contract.json": "./generated/contract.json",
77040
+ "./reference.md": "./generated/reference.md",
77041
+ "./package.json": "./package.json"
77042
+ },
77043
+ publishConfig: {
77044
+ access: "public"
77045
+ },
77046
+ scripts: {
77047
+ build: "tsdown",
77048
+ prepack: "bun run build",
77049
+ "corpus:render": "bun run scripts/render-compatibility-corpus.ts",
77050
+ "codegen:tailwind": "bun run scripts/codegen-tailwind.ts && oxfmt src/email/tailwind-stylesheets.gen.ts",
77051
+ "codegen:contract": "bun run scripts/generate-contract.ts",
77052
+ test: "vitest run",
77053
+ typecheck: "tsc --noEmit && tsc --noEmit -p tests/consumer/tsconfig.json"
77054
+ },
77055
+ dependencies: {
77056
+ "@babel/parser": "^8.0.4",
77057
+ "@cfworker/json-schema": "4.1.1",
77058
+ "@standard-schema/spec": "1.1.0",
77059
+ "better-result": "3.0.1",
77060
+ caniemail: "2.0.2",
77061
+ "css-tree": "3.2.1",
77062
+ tailwindcss: "^4.3.3"
77063
+ },
77064
+ devDependencies: {
77065
+ "@css-inline/css-inline-wasm": "0.15.0",
77066
+ "@effect/vitest": "4.0.0",
77067
+ "@types/css-tree": "3.2.0",
77068
+ "@types/node": "^26.6.2",
77069
+ effect: "4.0.0",
77070
+ tsdown: "^0.22.13",
77071
+ typescript: "^7.0.2",
77072
+ vitest: "^5.0.3",
77073
+ zod: "4.6.5"
77074
+ }
77075
+ };
77076
+
77077
+ // src/lib/authoring-release.ts
77078
+ var minorOf = (version) => version.split(".").slice(0, 2).join(".");
77079
+ var AUTHORING_MINOR = minorOf(package_default2.version);
76889
77080
 
76890
77081
  // src/commands/templates/brand-resolver.ts
76891
77082
  var brand_resolver_default = `import { execFile } from "node:child_process";
@@ -77316,6 +77507,30 @@ export const createBrandResolver = (options: {
77316
77507
 
77317
77508
  // src/commands/templates/toolchain.ts
77318
77509
  var TOOLCHAIN_INSTALL = "npm install --save-dev @samva/markup @samva/vite vite";
77510
+ var AUTHORING_PACKAGES = ["@samva/markup", "@samva/vite"];
77511
+ var toolchainSkew = (installed, action) => {
77512
+ for (const name of AUTHORING_PACKAGES) {
77513
+ const version = installed[name];
77514
+ if (version === undefined || minorOf(version) === AUTHORING_MINOR)
77515
+ continue;
77516
+ const behind = Number(minorOf(version).split(".")[1]) < Number(AUTHORING_MINOR.split(".")[1]);
77517
+ return behind ? `${action}: this project has ${name} ${version}, but this CLI drives ${AUTHORING_MINOR}.x. Update the project with \`npm install --save-dev @samva/markup@~${AUTHORING_MINOR}.0 @samva/vite@~${AUTHORING_MINOR}.0\`.` : `${action}: this project has ${name} ${version}, which is newer than the ${AUTHORING_MINOR}.x this CLI drives. Update the CLI with \`npm install -g @samva/cli@latest\`.`;
77518
+ }
77519
+ return;
77520
+ };
77521
+ var installedAuthoring = async (root) => Object.fromEntries(await Promise.all(AUTHORING_PACKAGES.map(async (name) => {
77522
+ const manifest = await Promise.resolve().then(async () => await Bun.file(Bun.resolveSync(`${name}/package.json`, root)).json()).catch(() => {
77523
+ return;
77524
+ });
77525
+ return [name, manifest?.version];
77526
+ })));
77527
+ var requireCompatibleToolchain = (action) => tryPromise2({
77528
+ try: () => installedAuthoring(process.cwd()),
77529
+ catch: () => new SamvaCliError({ message: `${action}: could not read the project's installed packages.` })
77530
+ }).pipe(flatMap3((installed) => {
77531
+ const skew = toolchainSkew(installed, action);
77532
+ return skew === undefined ? void_3 : fail6(new SamvaCliError({ message: skew }));
77533
+ }));
77319
77534
  var MISSING_MODULE = /Cannot find (?:package|module) '([^']+)'/;
77320
77535
  var toolchainFailure = (stderr, action) => {
77321
77536
  const missing = MISSING_MODULE.exec(stderr)?.[1];
@@ -77327,28 +77542,42 @@ var toolchainFailure = (stderr, action) => {
77327
77542
  }
77328
77543
  return `${action}: ${stderr.trim() || "the toolchain process failed."}`;
77329
77544
  };
77545
+ var invocationEnv = gen2(function* () {
77546
+ const profile = getOrUndefined(yield* Profile);
77547
+ const org = getOrUndefined(yield* Org);
77548
+ const apiUrl = getOrUndefined(yield* ApiUrl);
77549
+ return {
77550
+ ...profile === undefined ? {} : { SAMVA_PROFILE: profile },
77551
+ ...org === undefined ? {} : { SAMVA_ORG: org },
77552
+ ...apiUrl === undefined ? {} : { SAMVA_API_URL: apiUrl }
77553
+ };
77554
+ });
77330
77555
  var NODE_MISSING = "Install Node.js 22+ to run template authoring commands.";
77331
- var runToolchain = (input) => tryPromise2({
77332
- try: async () => {
77333
- const child = Bun.spawn(["node", "--input-type=module", "--eval", input.script], {
77334
- cwd: process.cwd(),
77335
- env: { ...Bun.env, SAMVA_TOOLCHAIN_INPUT: JSON.stringify(input.options) },
77336
- stdout: "pipe",
77337
- stderr: "pipe"
77338
- });
77339
- const [stdout, stderr, exitCode] = await Promise.all([
77340
- new Response(child.stdout).text(),
77341
- new Response(child.stderr).text(),
77342
- child.exited
77343
- ]);
77344
- if (exitCode !== 0) {
77345
- return Promise.reject(new SamvaCliError({ message: toolchainFailure(stderr, input.action) }));
77346
- }
77347
- return JSON.parse(stdout);
77348
- },
77349
- catch: (cause) => cause instanceof SamvaCliError ? cause : new SamvaCliError({
77350
- message: `${input.action}: ${NODE_MISSING} ${String(cause)}`
77351
- })
77556
+ var runToolchain = (input) => gen2(function* () {
77557
+ yield* requireCompatibleToolchain(input.action);
77558
+ const selection = yield* invocationEnv;
77559
+ return yield* tryPromise2({
77560
+ try: async () => {
77561
+ const child = Bun.spawn(["node", "--input-type=module", "--eval", input.script], {
77562
+ cwd: process.cwd(),
77563
+ env: { ...Bun.env, ...selection, SAMVA_TOOLCHAIN_INPUT: JSON.stringify(input.options) },
77564
+ stdout: "pipe",
77565
+ stderr: "pipe"
77566
+ });
77567
+ const [stdout, stderr, exitCode] = await Promise.all([
77568
+ new Response(child.stdout).text(),
77569
+ new Response(child.stderr).text(),
77570
+ child.exited
77571
+ ]);
77572
+ if (exitCode !== 0) {
77573
+ return Promise.reject(new SamvaCliError({ message: toolchainFailure(stderr, input.action) }));
77574
+ }
77575
+ return JSON.parse(stdout);
77576
+ },
77577
+ catch: (cause) => cause instanceof SamvaCliError ? cause : new SamvaCliError({
77578
+ message: `${input.action}: ${NODE_MISSING} ${String(cause)}`
77579
+ })
77580
+ });
77352
77581
  });
77353
77582
  var OPTIONS_PRELUDE = "const options = JSON.parse(process.env.SAMVA_TOOLCHAIN_INPUT);";
77354
77583
  var brandResolverPrelude = () => `${new Bun.Transpiler({ loader: "ts" }).transformSync(brand_resolver_default)}
@@ -77539,8 +77768,24 @@ var buildDir = (dir, assetBase) => runToolchain({
77539
77768
  ${brandResolverPrelude()} process.stdout.write(JSON.stringify(await buildTemplates(options)));`
77540
77769
  });
77541
77770
 
77542
- // src/commands/templates/project.ts
77771
+ // src/commands/templates/local-export.ts
77543
77772
  import { resolve as resolve10 } from "path";
77773
+ var exportProject = (input) => runToolchain({
77774
+ action: `Could not export templates${input.dir === undefined ? "" : ` in ${input.dir}`}`,
77775
+ options: {
77776
+ root: process.cwd(),
77777
+ out: resolve10(input.out),
77778
+ ...input.dir === undefined ? {} : { dir: resolve10(input.dir) },
77779
+ ...input.assetBase === undefined ? {} : { assetBase: input.assetBase },
77780
+ ...input.offline === true ? { offline: true } : {}
77781
+ },
77782
+ script: `import { exportProject } from "@samva/vite/project"; ${OPTIONS_PRELUDE}
77783
+ ${brandResolverPrelude()}
77784
+ process.stdout.write(JSON.stringify(await exportProject(options)));`
77785
+ });
77786
+
77787
+ // src/commands/templates/project.ts
77788
+ import { resolve as resolve11 } from "path";
77544
77789
  var templateId = String10("id").pipe(withDescription4("Template id"));
77545
77790
  var projectOrTemplateId = String10("id").pipe(withDescription4("Template project id (tproj_...) or the id of any template in it"));
77546
77791
  var runGit = (args, options = {}) => tryPromise2({
@@ -77627,7 +77872,7 @@ var cloneProject = fn2("Templates.cloneProject")(function* (input) {
77627
77872
  getById({ id: cloneTemplateId }).pipe(client.provide),
77628
77873
  getGitRepository({ id: cloneTemplateId }).pipe(client.provide)
77629
77874
  ], { concurrency: 2 }).pipe(mapError2((error) => toCliApiError("Could not resolve template project", error)));
77630
- const root = resolve10(input.dir);
77875
+ const root = resolve11(input.dir);
77631
77876
  const helper = yield* helperCommand(template.id, "read");
77632
77877
  yield* runGit([
77633
77878
  "-c",
@@ -77725,7 +77970,7 @@ var init2 = make29("init", {
77725
77970
  description: "Create deterministically for CI or a coding agent"
77726
77971
  }
77727
77972
  ]), withHandler(({ name, projectName, slug, dir, starter, idempotencyKey, dryRun, json }) => gen2(function* () {
77728
- const root = resolve10(getOrElse(dir, () => projectDirectory(name)));
77973
+ const root = resolve11(getOrElse(dir, () => projectDirectory(name)));
77729
77974
  const key = getOrUndefined(idempotencyKey);
77730
77975
  const request = {
77731
77976
  name,
@@ -78080,10 +78325,12 @@ ${lines.join(`
78080
78325
 
78081
78326
  // src/commands/templates/render-command.ts
78082
78327
  import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
78083
- import { dirname as dirname3, resolve as resolve12 } from "path";
78328
+ import { dirname as dirname3, resolve as resolve13 } from "path";
78084
78329
 
78085
78330
  // src/commands/templates/local-render.ts
78086
- import { resolve as resolve11 } from "path";
78331
+ import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm2 } from "fs/promises";
78332
+ import { tmpdir as tmpdir2 } from "os";
78333
+ import { join as join4, resolve as resolve12 } from "path";
78087
78334
  var renderLocal = (input) => runToolchain({
78088
78335
  action: `Could not render ${input.template}`,
78089
78336
  options: {
@@ -78091,7 +78338,7 @@ var renderLocal = (input) => runToolchain({
78091
78338
  template: input.template,
78092
78339
  fixture: input.fixture,
78093
78340
  channel: input.channel,
78094
- ...input.dir === undefined ? {} : { dir: resolve11(input.dir) },
78341
+ ...input.dir === undefined ? {} : { dir: resolve12(input.dir) },
78095
78342
  ...input.locale === undefined ? {} : { locale: input.locale },
78096
78343
  ...input.timeZone === undefined ? {} : { timeZone: input.timeZone },
78097
78344
  ...input.assetBase === undefined ? {} : { assetBase: input.assetBase },
@@ -78100,6 +78347,55 @@ var renderLocal = (input) => runToolchain({
78100
78347
  script: `import { renderChannel, renderTemplate } from "@samva/vite/render"; ${OPTIONS_PRELUDE}
78101
78348
  ${brandResolverPrelude()} const { channel, ...rest } = options; const result = channel === "email" ? await renderTemplate(rest) : await renderChannel({ ...rest, channel }); process.stdout.write(JSON.stringify(result));`
78102
78349
  });
78350
+ var finding = (code, message) => ({
78351
+ code,
78352
+ severity: "error",
78353
+ message,
78354
+ origins: []
78355
+ });
78356
+ var renderWithProjectAssets = (input) => gen2(function* () {
78357
+ const scratch = yield* acquireRelease2(tryPromise2({
78358
+ try: () => mkdtemp2(join4(tmpdir2(), "samva-snapshot-export-")),
78359
+ catch: (cause) => new SamvaCliError({ message: `Could not create a scratch directory: ${String(cause)}` })
78360
+ }), (path) => promise2(() => rm2(path, { recursive: true, force: true })));
78361
+ const receipt = yield* exportProject({
78362
+ dir: input.dir,
78363
+ out: scratch,
78364
+ assetBase: join4(scratch, "assets"),
78365
+ offline: input.offline
78366
+ });
78367
+ const entry = input.template.replace(/^\.\//, "");
78368
+ const template = receipt.templates.find((candidate) => candidate.id === entry || candidate.entryPath === entry);
78369
+ const others = receipt.templates.filter((candidate) => candidate !== template).map((candidate) => candidate.entryPath);
78370
+ const diagnostics = receipt.diagnostics.filter((diagnostic) => {
78371
+ const file = diagnostic.origins[0]?.fileName ?? "";
78372
+ return !others.some((path) => file === path || file.endsWith(`/${path}`));
78373
+ });
78374
+ if (template === undefined)
78375
+ return {
78376
+ ok: false,
78377
+ diagnostics: [
78378
+ ...diagnostics,
78379
+ finding("template-not-found", `No template ${JSON.stringify(input.template)} in the project. Known templates: ${receipt.templates.map((candidate) => candidate.id).join(", ") || "none"}.`)
78380
+ ]
78381
+ };
78382
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error"))
78383
+ return { ok: false, diagnostics };
78384
+ const page = template.fixtures.find((candidate) => candidate.fixture === input.fixture)?.html;
78385
+ if (page === undefined)
78386
+ return {
78387
+ ok: false,
78388
+ diagnostics: [
78389
+ ...diagnostics,
78390
+ finding("unknown-fixture", `Template ${template.id} has no email fixture ${JSON.stringify(input.fixture)}. Known fixtures: ${template.fixtures.map((candidate) => candidate.fixture).join(", ") || "none"}.`)
78391
+ ]
78392
+ };
78393
+ const html = yield* tryPromise2({
78394
+ try: () => readFile5(join4(scratch, page), "utf8"),
78395
+ catch: (cause) => new SamvaCliError({ message: `Could not read the exported page: ${String(cause)}` })
78396
+ });
78397
+ return { ok: true, template: template.id, html, diagnostics };
78398
+ });
78103
78399
 
78104
78400
  // src/commands/templates/render-command.ts
78105
78401
  var renderPayload = (result, out) => ({
@@ -78175,7 +78471,7 @@ var renderCommand = make29("render", {
78175
78471
  json
78176
78472
  }) => gen2(function* () {
78177
78473
  const machine = json || format === "json";
78178
- const outPath = map(out, (value) => resolve12(value)).pipe(getOrUndefined);
78474
+ const outPath = map(out, (value) => resolve13(value)).pipe(getOrUndefined);
78179
78475
  const result = yield* renderLocal({
78180
78476
  template,
78181
78477
  fixture,
@@ -78225,13 +78521,13 @@ var renderCommand = make29("render", {
78225
78521
  }).pipe(json || format === "json" ? provideService2(MinimumLogLevel2, "None") : identity)));
78226
78522
 
78227
78523
  // src/commands/templates/snapshot-command.ts
78228
- import { resolve as resolve13 } from "path";
78524
+ import { resolve as resolve14 } from "path";
78229
78525
 
78230
78526
  // src/commands/templates/snapshot-browser.ts
78231
78527
  import { existsSync } from "fs";
78232
- import { copyFile as copyFile3, mkdir as mkdir4, mkdtemp as mkdtemp2, readFile as readFile5, rm as rm2, stat as stat4, writeFile as writeFile5 } from "fs/promises";
78233
- import { tmpdir as tmpdir2 } from "os";
78234
- import { join as join4 } from "path";
78528
+ import { copyFile as copyFile3, mkdir as mkdir4, mkdtemp as mkdtemp3, readFile as readFile6, rm as rm3, stat as stat4, writeFile as writeFile5 } from "fs/promises";
78529
+ import { tmpdir as tmpdir3 } from "os";
78530
+ import { join as join5 } from "path";
78235
78531
  import { pathToFileURL as pathToFileURL4 } from "url";
78236
78532
  var VIEWPORTS = {
78237
78533
  desktop: { width: 1280, height: 800 },
@@ -78242,8 +78538,8 @@ var BROWSER_REMEDY = "Install a browser to screenshot with: add `playwright` to
78242
78538
  var snapshotFileName = (template, fixture, viewport, scheme) => `${template}-${fixture}-${viewport}-${scheme}.png`;
78243
78539
  var takeSnapshots = async (input) => {
78244
78540
  await mkdir4(input.outDir, { recursive: true });
78245
- const scratch = await mkdtemp2(join4(tmpdir2(), "samva-snapshot-"));
78246
- const htmlPath = join4(scratch, "page.html");
78541
+ const scratch = await mkdtemp3(join5(tmpdir3(), "samva-snapshot-"));
78542
+ const htmlPath = join5(scratch, "page.html");
78247
78543
  await writeFile5(htmlPath, input.html);
78248
78544
  const requests = input.viewports.flatMap((viewport) => input.schemes.map((scheme) => ({
78249
78545
  htmlPath,
@@ -78251,7 +78547,7 @@ var takeSnapshots = async (input) => {
78251
78547
  width: VIEWPORTS[viewport].width,
78252
78548
  height: VIEWPORTS[viewport].height,
78253
78549
  scheme,
78254
- outFile: join4(input.outDir, snapshotFileName(input.template, input.fixture, viewport, scheme))
78550
+ outFile: join5(input.outDir, snapshotFileName(input.template, input.fixture, viewport, scheme))
78255
78551
  })));
78256
78552
  const reasons = [];
78257
78553
  try {
@@ -78272,7 +78568,7 @@ var takeSnapshots = async (input) => {
78272
78568
  }
78273
78569
  return { ok: false, reasons };
78274
78570
  } finally {
78275
- await rm2(scratch, { recursive: true, force: true });
78571
+ await rm3(scratch, { recursive: true, force: true });
78276
78572
  }
78277
78573
  };
78278
78574
  var CHROME_PATHS = [
@@ -78366,10 +78662,10 @@ var chromeLauncher = (binary, run = runProcess) => ({
78366
78662
  status: "unavailable",
78367
78663
  reason: "no Chrome or Chromium found in the usual locations and SAMVA_CHROME is not set"
78368
78664
  };
78369
- const profile = await mkdtemp2(join4(tmpdir2(), "samva-chrome-"));
78665
+ const profile = await mkdtemp3(join5(tmpdir3(), "samva-chrome-"));
78370
78666
  try {
78371
78667
  for (const request of requests) {
78372
- const source = await readFile5(request.htmlPath, "utf8");
78668
+ const source = await readFile6(request.htmlPath, "utf8");
78373
78669
  const measured = request.htmlPath.replace(/\.html$/, `.${request.viewport}.${request.scheme}.html`);
78374
78670
  await writeFile5(measured, withMeasurement(source));
78375
78671
  const measure = await run(binary, chromeMeasureArgs({
@@ -78380,7 +78676,7 @@ var chromeLauncher = (binary, run = runProcess) => ({
78380
78676
  url: pathToFileURL4(measured).href
78381
78677
  }), { stdoutIncludes: "</html>" });
78382
78678
  const height = Math.max(request.height, parseMeasuredHeight(measure.stdout) ?? request.height);
78383
- const staged = join4(profile, `shot-${request.viewport}-${request.scheme}.png`);
78679
+ const staged = join5(profile, `shot-${request.viewport}-${request.scheme}.png`);
78384
78680
  const shot = await run(binary, chromeScreenshotArgs({
78385
78681
  profile,
78386
78682
  width: request.width,
@@ -78399,7 +78695,7 @@ var chromeLauncher = (binary, run = runProcess) => ({
78399
78695
  }
78400
78696
  return { status: "done" };
78401
78697
  } finally {
78402
- await rm2(profile, { recursive: true, force: true });
78698
+ await rm3(profile, { recursive: true, force: true });
78403
78699
  }
78404
78700
  }
78405
78701
  });
@@ -78491,7 +78787,7 @@ var snapshotCommand = make29("snapshot", {
78491
78787
  out: Directory2("out").pipe(withDescription2("Directory to write the PNGs into"), withDefault5(".samva/snapshots")),
78492
78788
  viewport: String9("viewport").pipe(withDescription2(`Comma-separated viewports: ${Object.entries(VIEWPORTS).map(([name, size]) => `${name} (${size.width}px)`).join(", ")}`), withDefault5("desktop,mobile")),
78493
78789
  scheme: String9("scheme").pipe(withDescription2("Comma-separated color schemes: light, dark"), withDefault5("light,dark")),
78494
- assetBase: assetBaseFlag,
78790
+ assetBase: String9("asset-base").pipe(withDescription2("HTTPS origin the template's imported assets are served from; omit to use the project's own asset files"), optional4),
78495
78791
  offline: offlineFlag,
78496
78792
  json: jsonFlag
78497
78793
  }).pipe(withDescription3("Screenshot one fixture in a headless browser at desktop and mobile widths, light and dark"), withExamples([
@@ -78503,17 +78799,18 @@ var snapshotCommand = make29("snapshot", {
78503
78799
  command: "samva templates snapshot welcome --fixture default --viewport mobile --scheme dark --out shots",
78504
78800
  description: "One screenshot: mobile, dark"
78505
78801
  }
78506
- ]), withHandler(({ template, fixture, dir, out, viewport, scheme, assetBase, offline, json }) => gen2(function* () {
78802
+ ]), withHandler(({ template, fixture, dir, out, viewport, scheme, assetBase, offline, json }) => scoped2(gen2(function* () {
78507
78803
  const viewports = yield* parseChoices("viewport", viewport, Object.keys(VIEWPORTS));
78508
78804
  const schemes = yield* parseChoices("scheme", scheme, SCHEMES);
78509
- const rendered = yield* renderLocal({
78510
- template,
78511
- fixture,
78512
- channel: "email",
78513
- dir: getOrUndefined(dir),
78514
- assetBase: getOrUndefined(assetBase),
78515
- offline
78516
- });
78805
+ const base = getOrUndefined(assetBase);
78806
+ if (base !== undefined && !/^https:\/\//i.test(base)) {
78807
+ return yield* new CliUsageError({
78808
+ code: "invalid_asset_base",
78809
+ message: `--asset-base must be an HTTPS origin; got ${base}. Omit it to screenshot with the project's own asset files.`
78810
+ });
78811
+ }
78812
+ const input = { template, fixture, dir: getOrUndefined(dir) };
78813
+ const rendered = base === undefined ? yield* renderWithProjectAssets({ ...input, channel: "email", offline }) : yield* renderLocal({ ...input, channel: "email", assetBase: base, offline });
78517
78814
  if (!rendered.ok || !("html" in rendered)) {
78518
78815
  const message = `Could not render ${template} (${fixture}) to screenshot it.`;
78519
78816
  const diagnostics = rendered.diagnostics.map((diagnostic) => reportDiagnostic(diagnostic, { file: "." }));
@@ -78530,7 +78827,7 @@ var snapshotCommand = make29("snapshot", {
78530
78827
  }
78531
78828
  return yield* new SamvaCliError({ message });
78532
78829
  }
78533
- const outDir = resolve13(out);
78830
+ const outDir = resolve14(out);
78534
78831
  const shots = yield* promise2(() => takeSnapshots({
78535
78832
  html: rendered.html,
78536
78833
  template: rendered.template,
@@ -78558,10 +78855,10 @@ var snapshotCommand = make29("snapshot", {
78558
78855
  return yield* printJson(result);
78559
78856
  for (const file of shots.files)
78560
78857
  yield* log(file.path);
78561
- }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
78858
+ })).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
78562
78859
 
78563
78860
  // src/commands/templates/types-command.ts
78564
- import { dirname as dirname4, resolve as resolve14 } from "path";
78861
+ import { dirname as dirname4, resolve as resolve15 } from "path";
78565
78862
 
78566
78863
  // src/commands/templates/schema-types.ts
78567
78864
  var RESERVED = new Set([
@@ -78609,6 +78906,93 @@ var pascalCase = (value, fallback) => {
78609
78906
  const joined = parts.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("").replace(/^[0-9]+/, "");
78610
78907
  return joined.length === 0 || RESERVED.has(joined.toLowerCase()) ? fallback : joined;
78611
78908
  };
78909
+ var text2 = (value) => ({ kind: "text", text: value });
78910
+ var NEVER = text2("never");
78911
+ var union2 = (members) => members.length === 1 ? members[0] ?? NEVER : { kind: "union", members: members.flatMap((m) => m.kind === "union" ? m.members : [m]) };
78912
+ var PRINT_WIDTH = 100;
78913
+ var pad2 = (indent) => " ".repeat(indent);
78914
+ var isFlat = (doc) => {
78915
+ switch (doc.kind) {
78916
+ case "text":
78917
+ return true;
78918
+ case "object":
78919
+ return false;
78920
+ case "union":
78921
+ case "intersection":
78922
+ return doc.members.every(isFlat);
78923
+ case "array":
78924
+ return isFlat(doc.element);
78925
+ case "tuple":
78926
+ return doc.members.every(isFlat) && (doc.rest === undefined || isFlat(doc.rest));
78927
+ case "record":
78928
+ return isFlat(doc.value);
78929
+ }
78930
+ };
78931
+ var flat = (doc) => {
78932
+ switch (doc.kind) {
78933
+ case "text":
78934
+ return doc.text;
78935
+ case "object":
78936
+ return `{ ${doc.members.map(({ name, optional, type }) => `readonly ${name}${optional ? "?" : ""}: ${flat(type)}`).join("; ")} }`;
78937
+ case "union":
78938
+ return doc.members.map(flat).join(" | ");
78939
+ case "intersection":
78940
+ return doc.members.map(operand).join(" & ");
78941
+ case "array":
78942
+ return `readonly ${wrapped(doc.element)}[]`;
78943
+ case "tuple":
78944
+ return `readonly [${[...doc.members.map(flat), ...doc.rest === undefined ? [] : [`...${flat(doc.rest)}[]`]].join(", ")}]`;
78945
+ case "record":
78946
+ return `Record<string, ${flat(doc.value)}>`;
78947
+ }
78948
+ };
78949
+ var needsParentheses = (doc) => doc.kind === "union" || doc.kind === "intersection" || doc.kind === "array" || doc.kind === "tuple";
78950
+ var operand = (doc) => doc.kind === "union" ? `(${flat(doc)})` : flat(doc);
78951
+ var wrapped = (doc) => needsParentheses(doc) ? `(${flat(doc)})` : flat(doc);
78952
+ var printType = (doc, indent, column, suffix = 1) => {
78953
+ switch (doc.kind) {
78954
+ case "object":
78955
+ return `{
78956
+ ${doc.members.map(({ name, optional, type }) => {
78957
+ const head = `${pad2(indent + 2)}readonly ${name}${optional ? "?" : ""}:`;
78958
+ const body = printType(type, indent + 2, head.length + 1);
78959
+ return `${head}${body.startsWith(`
78960
+ `) ? "" : " "}${body};`;
78961
+ }).join(`
78962
+ `)}
78963
+ ${pad2(indent)}}`;
78964
+ case "union": {
78965
+ if (isFlat(doc) && column + flat(doc).length + suffix <= PRINT_WIDTH)
78966
+ return flat(doc);
78967
+ return doc.members.map((member) => `
78968
+ ${pad2(indent + 2)}| ${printType(member, indent + 4, indent + 4, 0)}`).join("");
78969
+ }
78970
+ case "intersection":
78971
+ return doc.members.map((member) => {
78972
+ const body = printType(member, indent, column, suffix);
78973
+ if (member.kind !== "union")
78974
+ return body;
78975
+ return body.startsWith(`
78976
+ `) ? `(${body}
78977
+ ${pad2(indent)})` : `(${body})`;
78978
+ }).join(" & ");
78979
+ case "array": {
78980
+ const element = doc.element;
78981
+ if (isFlat(doc) && column + flat(doc).length + suffix <= PRINT_WIDTH)
78982
+ return flat(doc);
78983
+ if (element.kind === "object")
78984
+ return `readonly ${printType(element, indent, column + 9, 0)}[]`;
78985
+ if (!needsParentheses(element))
78986
+ return flat(doc);
78987
+ const body = printType(element, indent, column + 10, 0);
78988
+ return body.startsWith(`
78989
+ `) ? `readonly (${body}
78990
+ ${pad2(indent)})[]` : `readonly (${body})[]`;
78991
+ }
78992
+ default:
78993
+ return flat(doc);
78994
+ }
78995
+ };
78612
78996
  var UNSUPPORTED_KEYWORDS = [
78613
78997
  "not",
78614
78998
  "if",
@@ -78625,31 +79009,31 @@ var UNSUPPORTED_KEYWORDS = [
78625
79009
  ];
78626
79010
  var literal = (value, pointer, state) => {
78627
79011
  if (value === null)
78628
- return "null";
79012
+ return text2("null");
78629
79013
  if (typeof value === "string")
78630
- return JSON.stringify(value);
79014
+ return text2(JSON.stringify(value));
78631
79015
  if (typeof value === "number" || typeof value === "boolean")
78632
- return String(value);
79016
+ return text2(String(value));
78633
79017
  state.problems.push({
78634
79018
  pointer,
78635
79019
  message: "Only string, number, boolean and null literals become generated types."
78636
79020
  });
78637
- return "never";
79021
+ return NEVER;
78638
79022
  };
78639
79023
  var scalar = (type, pointer, state) => {
78640
79024
  switch (type) {
78641
79025
  case "string":
78642
- return "string";
79026
+ return text2("string");
78643
79027
  case "number":
78644
79028
  case "integer":
78645
- return "number";
79029
+ return text2("number");
78646
79030
  case "boolean":
78647
- return "boolean";
79031
+ return text2("boolean");
78648
79032
  case "null":
78649
- return "null";
79033
+ return text2("null");
78650
79034
  default:
78651
79035
  state.problems.push({ pointer, message: `Unsupported JSON Schema type ${type}.` });
78652
- return "never";
79036
+ return NEVER;
78653
79037
  }
78654
79038
  };
78655
79039
  var definitionName = (state, key, index, pointer) => {
@@ -78671,13 +79055,13 @@ var emit = (schema, pointer, root, state) => {
78671
79055
  pointer,
78672
79056
  message: "An unconstrained schema cannot produce a type that checks anything."
78673
79057
  });
78674
- return "never";
79058
+ return NEVER;
78675
79059
  }
78676
79060
  if (schema === false)
78677
- return "never";
79061
+ return NEVER;
78678
79062
  if (!isObject2(schema)) {
78679
79063
  state.problems.push({ pointer, message: "Expected a JSON Schema object." });
78680
- return "never";
79064
+ return NEVER;
78681
79065
  }
78682
79066
  for (const keyword of UNSUPPORTED_KEYWORDS) {
78683
79067
  if (schema[keyword] !== undefined) {
@@ -78685,7 +79069,7 @@ var emit = (schema, pointer, root, state) => {
78685
79069
  pointer,
78686
79070
  message: `Generated types do not support the ${keyword} keyword.`
78687
79071
  });
78688
- return "never";
79072
+ return NEVER;
78689
79073
  }
78690
79074
  }
78691
79075
  const reference = schema.$ref;
@@ -78697,48 +79081,51 @@ var emit = (schema, pointer, root, state) => {
78697
79081
  pointer,
78698
79082
  message: `Only local #/$defs references are generated; found ${reference}.`
78699
79083
  });
78700
- return "never";
79084
+ return NEVER;
78701
79085
  }
78702
79086
  const name = definitionName(state, key, Object.keys(defs).indexOf(key), pointer);
78703
79087
  if (name === undefined)
78704
- return "never";
79088
+ return NEVER;
78705
79089
  if (!state.defs.has(name) && !state.emitting.has(name)) {
78706
79090
  state.emitting.add(name);
78707
79091
  state.defs.set(name, emit(defs[key], `#/$defs/${key}`, root, state));
78708
79092
  }
78709
- return name;
79093
+ return text2(name);
78710
79094
  }
78711
79095
  if (schema.const !== undefined)
78712
79096
  return literal(schema.const, pointer, state);
78713
79097
  if (Array.isArray(schema.enum)) {
78714
- return schema.enum.length === 0 ? "never" : schema.enum.map((value) => literal(value, pointer, state)).join(" | ");
79098
+ return schema.enum.length === 0 ? NEVER : union2(schema.enum.map((value) => literal(value, pointer, state)));
78715
79099
  }
78716
79100
  if (Array.isArray(schema.allOf)) {
78717
- return schema.allOf.map((member, index) => emit(member, `${pointer}/allOf/${index}`, root, state)).join(" & ");
79101
+ return {
79102
+ kind: "intersection",
79103
+ members: schema.allOf.map((member, index) => emit(member, `${pointer}/allOf/${index}`, root, state))
79104
+ };
78718
79105
  }
78719
79106
  for (const key of ["anyOf", "oneOf"]) {
78720
79107
  const members = schema[key];
78721
79108
  if (!Array.isArray(members))
78722
79109
  continue;
78723
- return members.length === 0 ? "never" : members.map((member, index) => emit(member, `${pointer}/${key}/${index}`, root, state)).join(" | ");
79110
+ return members.length === 0 ? NEVER : union2(members.map((member, index) => emit(member, `${pointer}/${key}/${index}`, root, state)));
78724
79111
  }
78725
79112
  const type = schema.type;
78726
79113
  if (Array.isArray(type)) {
78727
- return type.map((member) => scalarOrStructured(schema, member, pointer, root, state)).join(" | ");
79114
+ return union2(type.map((member) => scalarOrStructured(schema, member, pointer, root, state)));
78728
79115
  }
78729
79116
  if (typeof type !== "string") {
78730
79117
  state.problems.push({
78731
79118
  pointer,
78732
79119
  message: "A generated type needs an explicit type, enum, const, $ref or composition."
78733
79120
  });
78734
- return "never";
79121
+ return NEVER;
78735
79122
  }
78736
79123
  return scalarOrStructured(schema, type, pointer, root, state);
78737
79124
  };
78738
79125
  var scalarOrStructured = (schema, type, pointer, root, state) => {
78739
79126
  if (typeof type !== "string") {
78740
79127
  state.problems.push({ pointer, message: "Expected a JSON Schema type name." });
78741
- return "never";
79128
+ return NEVER;
78742
79129
  }
78743
79130
  if (type === "object") {
78744
79131
  const properties = isObject2(schema.properties) ? schema.properties : {};
@@ -78751,40 +79138,52 @@ var scalarOrStructured = (schema, type, pointer, root, state) => {
78751
79138
  pointer,
78752
79139
  message: "An object with no declared properties cannot produce a type that checks input."
78753
79140
  });
78754
- return "never";
79141
+ return NEVER;
78755
79142
  }
78756
- return additional === false ? "Record<string, never>" : `Record<string, ${emit(additional, `${pointer}/additionalProperties`, root, state)}>`;
79143
+ return additional === false ? { kind: "record", value: NEVER } : {
79144
+ kind: "record",
79145
+ value: emit(additional, `${pointer}/additionalProperties`, root, state)
79146
+ };
78757
79147
  }
78758
79148
  const declared = keys.map((key) => ({
78759
79149
  name: SAFE_PROPERTY.test(key) ? key : JSON.stringify(key),
78760
- optional: required.has(key) ? "" : "?",
79150
+ optional: !required.has(key),
78761
79151
  type: emit(properties[key], `${pointer}/properties/${key}`, root, state)
78762
79152
  }));
78763
- const shape = `{
78764
- ${declared.map(({ name, optional, type: member }) => ` readonly ${name}${optional}: ${member};`).join(`
78765
- `)}
78766
- }`;
79153
+ const shape = { kind: "object", members: declared };
78767
79154
  if (additional === undefined || additional === false || additional === true)
78768
79155
  return shape;
78769
79156
  const extra = emit(additional, `${pointer}/additionalProperties`, root, state);
78770
- const open = [...new Set([...declared.map(({ type: member }) => member), extra])].filter((member) => member !== "never").join(" | ");
78771
- return `${shape} & Record<string, ${open.length === 0 ? "never" : open}>`;
79157
+ const seen = new Set;
79158
+ const open = [...declared.map((member) => member.type), extra].filter((member) => {
79159
+ const key = flat(member);
79160
+ if (key === "never" || seen.has(key))
79161
+ return false;
79162
+ seen.add(key);
79163
+ return true;
79164
+ });
79165
+ return {
79166
+ kind: "intersection",
79167
+ members: [shape, { kind: "record", value: open.length === 0 ? NEVER : union2(open) }]
79168
+ };
78772
79169
  }
78773
79170
  if (type === "array") {
78774
79171
  if (Array.isArray(schema.prefixItems)) {
78775
79172
  const tuple = schema.prefixItems.map((member, index) => emit(member, `${pointer}/prefixItems/${index}`, root, state));
78776
- const rest = schema.items === undefined || schema.items === false ? "" : `, ...${emit(schema.items, `${pointer}/items`, root, state)}[]`;
78777
- return `readonly [${tuple.join(", ")}${rest}]`;
79173
+ return {
79174
+ kind: "tuple",
79175
+ members: tuple,
79176
+ ...schema.items === undefined || schema.items === false ? {} : { rest: emit(schema.items, `${pointer}/items`, root, state) }
79177
+ };
78778
79178
  }
78779
79179
  if (schema.items === undefined) {
78780
79180
  state.problems.push({
78781
79181
  pointer,
78782
79182
  message: "An array without declared items cannot produce a type that checks input."
78783
79183
  });
78784
- return "never";
79184
+ return NEVER;
78785
79185
  }
78786
- const member = emit(schema.items, `${pointer}/items`, root, state);
78787
- return SAFE_PROPERTY.test(member) ? `readonly ${member}[]` : `readonly (${member})[]`;
79186
+ return { kind: "array", element: emit(schema.items, `${pointer}/items`, root, state) };
78788
79187
  }
78789
79188
  return scalar(type, pointer, state);
78790
79189
  };
@@ -78808,31 +79207,54 @@ var schemaToTypeScript = (schema, prefix) => {
78808
79207
  var TEMPLATE_TYPES_MANIFEST_PREFIX = "// samva:catalog ";
78809
79208
  var ManifestSchema = Struct({
78810
79209
  version: Literal2(1),
78811
- reference: Literals(["exact", "contract-latest"]),
79210
+ by: optionalKey2(Literals(["id", "slug"])),
79211
+ reference: optionalKey2(Literals(["exact", "contract-latest"])),
78812
79212
  templates: ArraySchema(Struct({
78813
79213
  key: String4,
78814
79214
  templateId: String4,
78815
- reference: String4
79215
+ reference: optionalKey2(String4),
79216
+ inputContractId: optionalKey2(String4)
78816
79217
  }))
78817
79218
  });
78818
79219
  var decodeManifest = decodeUnknownOption2(fromJsonString2(ManifestSchema));
79220
+ var withKeying = (manifest) => ({ ...manifest, by: manifest.by ?? "id" });
78819
79221
  var readTemplateTypesManifest = (source) => {
78820
79222
  const line = source.split(`
78821
79223
  `).find((candidate) => candidate.startsWith(TEMPLATE_TYPES_MANIFEST_PREFIX));
78822
- return line === undefined ? undefined : getOrUndefined(decodeManifest(line.slice(TEMPLATE_TYPES_MANIFEST_PREFIX.length)));
79224
+ return line === undefined ? undefined : getOrUndefined(map(decodeManifest(line.slice(TEMPLATE_TYPES_MANIFEST_PREFIX.length)), withKeying));
79225
+ };
79226
+ var MODULE_TYPE_NAMES = [
79227
+ "SamvaTemplateInputs",
79228
+ "TemplateContent",
79229
+ "TemplateSlug",
79230
+ "TemplateValue",
79231
+ "TemplateValues"
79232
+ ];
79233
+ var memberKey = (value) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : JSON.stringify(value);
79234
+ var declaration = (head, type) => {
79235
+ const body = printType(type, 0, head.length + 1);
79236
+ return `${head}${body.startsWith(`
79237
+ `) ? "" : " "}${body};`;
79238
+ };
79239
+ var mapEntry = (key, fields) => {
79240
+ const flat = ` ${key}: { ${fields.map(([name, value]) => `${name}: ${value}`).join(", ")} },`;
79241
+ return flat.length <= PRINT_WIDTH ? flat : [` ${key}: {`, ...fields.map(([name, value]) => ` ${name}: ${value},`), " },"].join(`
79242
+ `);
78823
79243
  };
78824
79244
  var renderTemplateTypes = (input) => {
79245
+ const bySlug = input.by === "slug";
79246
+ const referenceKind = bySlug ? "contract-latest" : input.reference;
78825
79247
  const skipped = [];
78826
79248
  const problems = [];
78827
79249
  const entries = [...input.entries].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
78828
79250
  const generated = [];
78829
- const used = new Set;
79251
+ const declared = new Set(MODULE_TYPE_NAMES);
78830
79252
  for (const [index, entry] of entries.entries()) {
78831
- const reference = input.reference === "exact" ? entry.publicationId : entry.inputContractId;
79253
+ const reference = referenceKind === "exact" ? entry.publicationId : entry.inputContractId;
78832
79254
  if (reference === undefined) {
78833
79255
  skipped.push({
78834
79256
  key: entry.key,
78835
- reason: input.reference === "exact" ? "no publication yet; publish the template before generating an exact reference" : "no input contract yet; publish the template before adopting its contract"
79257
+ reason: referenceKind === "exact" ? "no publication yet; publish the template before generating an exact reference" : bySlug ? "no input contract yet; publish the template before generating types by slug" : "no input contract yet; publish the template before adopting its contract"
78836
79258
  });
78837
79259
  continue;
78838
79260
  }
@@ -78841,10 +79263,17 @@ var renderTemplateTypes = (input) => {
78841
79263
  continue;
78842
79264
  }
78843
79265
  let name = pascalCase(entry.key, `Template${index + 1}`);
78844
- while (used.has(name))
79266
+ let rendered = schemaToTypeScript(entry.inputSchema, name);
79267
+ const typeNames = () => [
79268
+ `${name}Input`,
79269
+ ...rendered.definitions.map((definition) => definition.name)
79270
+ ];
79271
+ while (typeNames().some((typeName) => declared.has(typeName))) {
78845
79272
  name = `${name}_`;
78846
- used.add(name);
78847
- const rendered = schemaToTypeScript(entry.inputSchema, name);
79273
+ rendered = schemaToTypeScript(entry.inputSchema, name);
79274
+ }
79275
+ for (const typeName of typeNames())
79276
+ declared.add(typeName);
78848
79277
  for (const problem of rendered.problems)
78849
79278
  problems.push({ ...problem, key: entry.key });
78850
79279
  generated.push({
@@ -78857,19 +79286,60 @@ var renderTemplateTypes = (input) => {
78857
79286
  }
78858
79287
  const manifest = {
78859
79288
  version: 1,
78860
- reference: input.reference,
79289
+ by: input.by,
79290
+ ...bySlug ? {} : { reference: referenceKind },
78861
79291
  templates: generated.map(({ entry, reference }) => ({
78862
79292
  key: entry.key,
78863
79293
  templateId: entry.templateId,
78864
- reference
79294
+ ...bySlug ? { inputContractId: reference } : { reference }
78865
79295
  }))
78866
79296
  };
78867
- const referenceField = input.reference === "exact" ? "publicationId" : "inputContractId";
78868
- const referenceNote = input.reference === "exact" ? "Each reference pins one exact publication, so a send from this file renders the same message until you regenerate." : "Each reference names an input contract, so a send from this file follows the newest publication that still validates input identically.";
78869
- const definitions = generated.flatMap(({ definitions: members }) => members.map(({ name, type }) => `type ${name} = ${type};`));
78870
- const inputs = generated.map(({ entry, name, type }) => [`/** Input for the \`${entry.key}\` template. */`, `export type ${name}Input = ${type};`].join(`
79297
+ const referenceField = referenceKind === "exact" ? "publicationId" : "inputContractId";
79298
+ const referenceNote = bySlug ? "Each helper names a template by slug, so a send from this file renders the template's current publication; its input is typed from the input contract current when you generated." : referenceKind === "exact" ? "Each reference pins one exact publication, so a send from this file renders the same message until you regenerate." : "Each reference names an input contract, so a send from this file follows the newest publication that still validates input identically.";
79299
+ const definitions = generated.flatMap(({ definitions: members }) => members.map(({ name, type }) => declaration(`type ${name} =`, type)));
79300
+ const inputs = generated.map(({ entry, name, type }) => [
79301
+ `/** Input for the \`${entry.key}\` template. */`,
79302
+ declaration(`export type ${name}Input =`, type)
79303
+ ].join(`
78871
79304
  `));
78872
- const key = (value) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : JSON.stringify(value);
79305
+ const keying = bySlug ? [
79306
+ "/**",
79307
+ " * Email content for one template, named by slug. TypeScript rejects input the template's",
79308
+ " * input contract would reject; spread the result into `email.send`.",
79309
+ " */",
79310
+ "export const templateContent = <Slug extends TemplateSlug>(",
79311
+ " slug: Slug,",
79312
+ " templateData: SamvaTemplateInputs[Slug],",
79313
+ ") => ({ templateSlug: slug, templateData: asTemplateData(templateData) });"
79314
+ ] : [
79315
+ `/** ${referenceNote} */`,
79316
+ generated.length === 0 ? "export const samvaTemplates = {} as const;" : `export const samvaTemplates = {
79317
+ ${generated.map(({ entry, reference }) => mapEntry(memberKey(entry.key), [
79318
+ ["templateId", JSON.stringify(entry.templateId)],
79319
+ [referenceField, JSON.stringify(reference)]
79320
+ ])).join(`
79321
+ `)}
79322
+ } as const;`,
79323
+ "",
79324
+ "/** What a send spreads in: the template's reference and its input as JSON. */",
79325
+ "export type TemplateContent = {",
79326
+ " readonly templateId: string;",
79327
+ ` readonly ${referenceField}: string;`,
79328
+ " readonly templateData: TemplateValues;",
79329
+ "};",
79330
+ "",
79331
+ "/**",
79332
+ " * Email content for one template. TypeScript rejects input the published",
79333
+ " * schema would reject, and the reference travels with it. Spread it into `email.send`.",
79334
+ " */",
79335
+ "export const templateContent = <Slug extends TemplateSlug>(",
79336
+ " slug: Slug,",
79337
+ " templateData: SamvaTemplateInputs[Slug],",
79338
+ "): TemplateContent => ({",
79339
+ " ...samvaTemplates[slug],",
79340
+ " templateData: asTemplateData(templateData),",
79341
+ "});"
79342
+ ];
78873
79343
  const source = `${[
78874
79344
  "// Generated by `samva templates types`. Do not edit.",
78875
79345
  `//`,
@@ -78886,24 +79356,43 @@ var renderTemplateTypes = (input) => {
78886
79356
  `), ""],
78887
79357
  "/** What each published template accepts, keyed by slug. */",
78888
79358
  generated.length === 0 ? "export interface SamvaTemplateInputs {}" : `export interface SamvaTemplateInputs {
78889
- ${generated.map(({ entry, name }) => ` readonly ${key(entry.key)}: ${name}Input;`).join(`
79359
+ ${generated.map(({ entry, name }) => ` readonly ${memberKey(entry.key)}: ${name}Input;`).join(`
78890
79360
  `)}
78891
79361
  }`,
78892
79362
  "",
78893
- `/** ${referenceNote} */`,
78894
- generated.length === 0 ? "export const samvaTemplates = {} as const;" : `export const samvaTemplates = {
78895
- ${generated.map(({ entry, reference }) => ` ${key(entry.key)}: { templateId: ${JSON.stringify(entry.templateId)}, ${referenceField}: ${JSON.stringify(reference)} },`).join(`
78896
- `)}
78897
- } as const;`,
79363
+ "/** The slug of every template in this file. */",
79364
+ "export type TemplateSlug = keyof SamvaTemplateInputs;",
78898
79365
  "",
78899
- "/**",
78900
- " * Email content for one template. TypeScript rejects input the published",
78901
- " * schema would reject, and the reference travels with it.",
78902
- " */",
78903
- "export const templateContent = <Name extends keyof SamvaTemplateInputs>(",
78904
- " name: Name,",
78905
- " templateData: SamvaTemplateInputs[Name],",
78906
- ") => ({ ...samvaTemplates[name], templateData });"
79366
+ "// What the API accepts as template data. A generated input is made of these values, but its",
79367
+ "// readonly lists and optional properties are not always assignable to them, so the helper",
79368
+ "// rebuilds the input as JSON values, leaving out a property that is undefined.",
79369
+ "type TemplateValue =",
79370
+ " | string",
79371
+ " | number",
79372
+ " | boolean",
79373
+ " | null",
79374
+ " | TemplateValue[]",
79375
+ " | { [key: string]: TemplateValue };",
79376
+ "type TemplateValues = { [key: string]: TemplateValue };",
79377
+ "const toTemplateValue = (value: unknown): TemplateValue => {",
79378
+ ' if (value === null || typeof value === "string") return value;',
79379
+ ' if (typeof value === "number" || typeof value === "boolean") return value;',
79380
+ " if (Array.isArray(value)) return value.map(toTemplateValue);",
79381
+ ' if (typeof value !== "object") return null;',
79382
+ " return Object.fromEntries(",
79383
+ " Object.entries(value)",
79384
+ " .filter(([, member]) => member !== undefined)",
79385
+ " .map(([key, member]) => [key, toTemplateValue(member)]),",
79386
+ " );",
79387
+ "};",
79388
+ "const asTemplateData = (input: SamvaTemplateInputs[TemplateSlug]): TemplateValues =>",
79389
+ " Object.fromEntries(",
79390
+ " Object.entries(input)",
79391
+ " .filter(([, member]) => member !== undefined)",
79392
+ " .map(([key, member]) => [key, toTemplateValue(member)]),",
79393
+ " );",
79394
+ "",
79395
+ ...keying
78907
79396
  ].join(`
78908
79397
  `)}
78909
79398
  `;
@@ -78920,6 +79409,13 @@ var templateTypesDrift = (existing, generated) => {
78920
79409
  detail: "The file carries no samva:catalog manifest; it was not written by this command."
78921
79410
  }
78922
79411
  ];
79412
+ if (manifest.by !== generated.manifest.by)
79413
+ return [
79414
+ {
79415
+ kind: "keying",
79416
+ detail: `Generated by ${manifest.by}; asked for ${generated.manifest.by}.`
79417
+ }
79418
+ ];
78923
79419
  if (manifest.reference !== generated.manifest.reference)
78924
79420
  return [
78925
79421
  {
@@ -78936,7 +79432,14 @@ var templateTypesDrift = (existing, generated) => {
78936
79432
  drift.push({ kind: "added", key, detail: `${key} is published and not in the file.` });
78937
79433
  continue;
78938
79434
  }
78939
- if (previous.reference !== entry.reference)
79435
+ if (generated.manifest.by === "slug") {
79436
+ if (previous.inputContractId !== entry.inputContractId)
79437
+ drift.push({
79438
+ kind: "contract",
79439
+ key,
79440
+ detail: `${key} was typed from input contract ${previous.inputContractId}; the catalog now has ${entry.inputContractId}.`
79441
+ });
79442
+ } else if (previous.reference !== entry.reference)
78940
79443
  drift.push({
78941
79444
  kind: "reference",
78942
79445
  key,
@@ -78982,13 +79485,22 @@ var readCatalog = fn2("templates.types.readCatalog")(function* () {
78982
79485
  ...item.inputSchema === null ? {} : { inputSchema: item.inputSchema }
78983
79486
  }))), { concurrency: CATALOG_READ_CONCURRENCY });
78984
79487
  });
79488
+ var keyingRows = (by, reference) => by === "slug" ? [
79489
+ ["By", "slug"],
79490
+ ["Publication", "current"],
79491
+ ["Drift", "input contract"]
79492
+ ] : [
79493
+ ["By", "id"],
79494
+ ["Reference", reference ?? "exact"]
79495
+ ];
78985
79496
  var readExisting = (path) => gen2(function* () {
78986
79497
  const fs = yield* FileSystem;
78987
79498
  return (yield* fs.exists(path)) ? yield* fs.readFileString(path) : undefined;
78988
79499
  }).pipe(catch_2(() => succeed6(undefined)));
78989
79500
  var typesCommand = make29("types", {
78990
79501
  out: String9("out").pipe(withDescription2("TypeScript file to write the generated template inputs into"), withDefault5("samva-templates.ts")),
78991
- reference: String9("reference").pipe(withDescription2("exact pins one publication per template; contract-latest follows the newest publication that validates input identically"), withDefault5("exact")),
79502
+ by: Literals3("by", ["id", "slug"]).pipe(withDescription2("id keys each send by template id with a reference; slug keys it by template slug and follows the template's current publication"), withDefault5("id")),
79503
+ reference: String9("reference").pipe(withDescription2("With --by id (default exact): exact pins one publication per template; contract-latest follows the newest publication that validates input identically"), optional4),
78992
79504
  check: Boolean6("check").pipe(withDescription2("Report drift without writing; exits 1 when the file is out of date"), withDefault5(false)),
78993
79505
  json: jsonFlag
78994
79506
  }).pipe(withDescription3("Generate typed template inputs from the authenticated catalog"), withExamples([
@@ -78996,21 +79508,32 @@ var typesCommand = make29("types", {
78996
79508
  command: "samva templates types --out src/samva-templates.ts",
78997
79509
  description: "Pin each template's current publication and type its input"
78998
79510
  },
79511
+ {
79512
+ command: "samva templates types --by slug --out src/samva-templates.ts",
79513
+ description: "Type a send that names the template by slug: { templateSlug, templateData }"
79514
+ },
78999
79515
  {
79000
79516
  command: "samva templates types --check",
79001
79517
  description: "Fail CI when the generated file no longer matches the catalog"
79002
79518
  }
79003
- ]), withHandler(fnUntraced2(function* ({ out, reference, check, json }) {
79519
+ ]), withHandler(fnUntraced2(function* ({ out, by, reference: requested, check, json }) {
79520
+ const reference = getOrElse(requested, () => "exact");
79004
79521
  if (reference !== "exact" && reference !== "contract-latest") {
79005
79522
  return yield* new SamvaCliError({
79006
79523
  message: `Unknown --reference ${reference}. Use exact or contract-latest.`
79007
79524
  });
79008
79525
  }
79526
+ if (by === "slug" && isSome2(requested)) {
79527
+ return yield* new SamvaCliError({
79528
+ message: "--reference does not apply with --by slug: a send by slug renders the template's current publication, and the types follow its input contract."
79529
+ });
79530
+ }
79009
79531
  const context = yield* resolveExecutionContext;
79010
- const path = resolve14(out);
79532
+ const path = resolve15(out);
79011
79533
  const entries = yield* readCatalog();
79012
79534
  const generated = renderTemplateTypes({
79013
79535
  entries,
79536
+ by,
79014
79537
  reference,
79015
79538
  origin: context.apiUrl
79016
79539
  });
@@ -79023,7 +79546,8 @@ var typesCommand = make29("types", {
79023
79546
  action: "templates.types",
79024
79547
  ok: false,
79025
79548
  out: path,
79026
- reference,
79549
+ by,
79550
+ ...generated.manifest.reference === undefined ? {} : { reference: generated.manifest.reference },
79027
79551
  problems: generated.problems
79028
79552
  },
79029
79553
  human: [
@@ -79039,7 +79563,8 @@ var typesCommand = make29("types", {
79039
79563
  action: "templates.types",
79040
79564
  ok: check ? drift.length === 0 : true,
79041
79565
  out: path,
79042
- reference,
79566
+ by,
79567
+ ...generated.manifest.reference === undefined ? {} : { reference: generated.manifest.reference },
79043
79568
  templates: generated.manifest.templates,
79044
79569
  skipped: generated.skipped,
79045
79570
  drift,
@@ -79051,7 +79576,7 @@ var typesCommand = make29("types", {
79051
79576
  return yield* printJson(result);
79052
79577
  return yield* printFields([
79053
79578
  ["File", path],
79054
- ["Reference", reference],
79579
+ ...keyingRows(by, generated.manifest.reference),
79055
79580
  ["Templates", generated.manifest.templates.length],
79056
79581
  ["Status", "up to date"]
79057
79582
  ]);
@@ -79078,7 +79603,7 @@ var typesCommand = make29("types", {
79078
79603
  return yield* printJson(written);
79079
79604
  yield* printFields([
79080
79605
  ["File", path],
79081
- ["Reference", reference],
79606
+ ...keyingRows(by, generated.manifest.reference),
79082
79607
  ["Templates", generated.manifest.templates.length],
79083
79608
  ["Skipped", generated.skipped.length]
79084
79609
  ]);
@@ -79087,9 +79612,22 @@ var typesCommand = make29("types", {
79087
79612
  })));
79088
79613
 
79089
79614
  // src/commands/templates/workspace.ts
79090
- import { basename as basename2, resolve as resolve15 } from "path";
79615
+ import { basename as basename2, resolve as resolve16 } from "path";
79091
79616
  var templateIdArgument = String10("template-id").pipe(withDescription4("Template whose project workspace to act on"));
79092
79617
  var revisionFlag2 = String9("revision").pipe(withDescription2("Workspace revision this call is fenced to; a workspace that moved refuses the call"));
79618
+ var readRevisionFlag = String9("revision").pipe(withDescription2("Workspace revision this call is fenced to; a workspace that moved refuses the call (default: the workspace's current revision)"), optional4);
79619
+ var currentRevision = (id, revision) => isSome2(revision) ? succeed6(revision.value) : gen2(function* () {
79620
+ const client = yield* getClientProvider;
79621
+ const view = yield* openWorkspace({ id }).pipe(client.provide, mapError2((error) => {
79622
+ const failure = toCliApiError("Could not read the template workspace revision", error);
79623
+ return new SamvaCliError({
79624
+ message: `${failure.message} The current revision is read by opening the workspace, which needs templates:create; pass --revision to skip that.`,
79625
+ api: failure.api,
79626
+ details: failure.details
79627
+ });
79628
+ }));
79629
+ return view.workspace.revision;
79630
+ });
79093
79631
  var idempotencyKeyFlag4 = String9("idempotency-key").pipe(withDescription2("Key so an identical retry returns the original result rather than repeating it"));
79094
79632
  var apiFailure2 = (what) => (error) => toCliApiError(`Could not ${what}`, error);
79095
79633
  var emit2 = (value) => printJsonDocument(value);
@@ -79105,11 +79643,12 @@ var open3 = make29("open", { id: templateIdArgument, json: jsonFlag }).pipe(with
79105
79643
  })));
79106
79644
  var diff = make29("diff", {
79107
79645
  id: templateIdArgument,
79108
- revision: revisionFlag2,
79646
+ revision: readRevisionFlag,
79109
79647
  json: jsonFlag
79110
79648
  }).pipe(withDescription3("Show every unsaved change against the workspace Git base"), withHandler(fnUntraced2(function* ({ id, revision, json }) {
79649
+ const expectedRevision = yield* currentRevision(id, revision);
79111
79650
  const client = yield* getClientProvider;
79112
- const result = yield* diffWorkspace({ id, expectedRevision: revision }).pipe(client.provide, mapError2(apiFailure2("diff the template workspace")));
79651
+ const result = yield* diffWorkspace({ id, expectedRevision }).pipe(client.provide, mapError2(apiFailure2("diff the template workspace")));
79113
79652
  yield* emit2(result);
79114
79653
  })));
79115
79654
  var patch2 = make29("patch", {
@@ -79140,7 +79679,7 @@ var patch2 = make29("patch", {
79140
79679
  message: `--set must be <project-path>=<local-file>; got ${entry}.`
79141
79680
  });
79142
79681
  const path = entry.slice(0, separator);
79143
- const source = resolve15(entry.slice(separator + 1));
79682
+ const source = resolve16(entry.slice(separator + 1));
79144
79683
  const content = yield* fs.readFileString(source).pipe(mapError2((cause) => new SamvaCliError({ message: `Could not read ${source}: ${String(cause)}` })));
79145
79684
  written.push({ path, content });
79146
79685
  }
@@ -79174,7 +79713,7 @@ var addAsset = make29("add-asset", {
79174
79713
  }
79175
79714
  ]), withHandler(fnUntraced2(function* ({ id, file, revision, path, replace, idempotencyKey, json }) {
79176
79715
  const fs = yield* FileSystem;
79177
- const source = resolve15(file);
79716
+ const source = resolve16(file);
79178
79717
  const bytes = yield* fs.readFile(source).pipe(mapError2((cause) => new SamvaCliError({ message: `Could not read ${source}: ${String(cause)}` })));
79179
79718
  const client = yield* getClientProvider;
79180
79719
  const view = yield* putWorkspaceAsset({
@@ -79189,28 +79728,30 @@ var addAsset = make29("add-asset", {
79189
79728
  })));
79190
79729
  var check4 = make29("check", {
79191
79730
  id: templateIdArgument,
79192
- revision: revisionFlag2,
79731
+ revision: readRevisionFlag,
79193
79732
  json: jsonFlag
79194
79733
  }).pipe(withDescription3("Build the effective workspace tree and report what it declares plus every finding"), withExamples([
79195
79734
  {
79196
- command: "samva templates workspace check tmpl_abc123 --revision 5",
79197
- description: "Read the template id, input schema, fixture names, and located diagnostics without rendering or writing"
79735
+ command: "samva templates workspace check tmpl_abc123",
79736
+ description: "Read the template id, input schema, fixture names, and located diagnostics without rendering or writing; --revision 5 fences the call to a revision you inspected"
79198
79737
  }
79199
79738
  ]), withHandler(fnUntraced2(function* ({ id, revision, json }) {
79739
+ const expectedRevision = yield* currentRevision(id, revision);
79200
79740
  const client = yield* getClientProvider;
79201
- const result = yield* checkWorkspace({ id, expectedRevision: revision }).pipe(client.provide, mapError2(apiFailure2("check the template workspace")));
79741
+ const result = yield* checkWorkspace({ id, expectedRevision }).pipe(client.provide, mapError2(apiFailure2("check the template workspace")));
79202
79742
  yield* emit2(result);
79203
79743
  })));
79204
79744
  var fixture = make29("fixture", {
79205
79745
  id: templateIdArgument,
79206
- revision: revisionFlag2,
79746
+ revision: readRevisionFlag,
79207
79747
  name: String9("name").pipe(withDescription2("One of the fixture names `workspace check` reported")),
79208
79748
  json: jsonFlag
79209
79749
  }).pipe(withDescription3("Render one input the template declares, as the recipient receives it"), withHandler(fnUntraced2(function* ({ id, revision, name, json }) {
79750
+ const expectedRevision = yield* currentRevision(id, revision);
79210
79751
  const client = yield* getClientProvider;
79211
79752
  const result = yield* renderWorkspaceFixture({
79212
79753
  id,
79213
- expectedRevision: revision,
79754
+ expectedRevision,
79214
79755
  fixture: name
79215
79756
  }).pipe(client.provide, mapError2(apiFailure2("render the template fixture")));
79216
79757
  yield* emit2(result);
@@ -79287,42 +79828,52 @@ var workspaceCommand = make29("workspace").pipe(withDescription3("Author a templ
79287
79828
 
79288
79829
  // src/commands/templates.ts
79289
79830
  var tsconfigFlag = String9("tsconfig").pipe(withDescription2("TypeScript project used to check authored templates"), withDefault5("tsconfig.json"));
79290
- var dryRunFlag6 = Boolean6("dry-run").pipe(withDescription2("Show the request without calling the API"), withDefault5(false));
79831
+ var dryRunFlag7 = Boolean6("dry-run").pipe(withDescription2("Show the request without calling the API"), withDefault5(false));
79291
79832
  var templateIdArgument2 = String10("id").pipe(withDescription4("Remote template id"));
79292
79833
  var parseObject = (raw, flag) => try_3({
79293
79834
  try: () => JSON.parse(raw),
79294
79835
  catch: () => new SamvaCliError({ message: `${flag} must be a valid JSON object.` })
79295
79836
  }).pipe(filterOrFail2((value) => isObject(value) && !Array.isArray(value), () => new SamvaCliError({ message: `${flag} must be a non-null JSON object.` })));
79296
79837
  var decodePayload = (raw, flag, schema) => parseObject(raw, flag).pipe(flatMap3(decodeUnknownEffect2(schema)), mapError2((error) => error instanceof SamvaCliError ? error : new SamvaCliError({ message: `${flag} does not match the API schema: ${String(error)}` })));
79297
- var createProjectBuildSession = (dir, assetBase) => tryPromise2({
79298
- try: async () => {
79299
- const entry = Bun.resolveSync("@samva/vite/build", process.cwd());
79300
- const projectBuild = await import(pathToFileURL5(entry).href);
79301
- const { createBrandResolver, loadBrandEnv } = await loadSamvaBrandResolver(process.cwd());
79302
- const projectBrand = await import(pathToFileURL5(Bun.resolveSync("@samva/markup/brand", process.cwd())).href);
79303
- return projectBuild.createTemplateBuildSession({
79304
- brandResolver: createBrandResolver({
79838
+ var createProjectBuildSession = (dir, assetBase) => gen2(function* () {
79839
+ yield* requireCompatibleToolchain(`Could not start template watch${dir === undefined ? "" : ` in ${dir}`}`);
79840
+ const selection = yield* invocationEnv;
79841
+ return yield* tryPromise2({
79842
+ try: async () => {
79843
+ const entry = Bun.resolveSync("@samva/vite/build", process.cwd());
79844
+ const projectBuild = await import(pathToFileURL5(entry).href);
79845
+ const { createBrandResolver, loadBrandEnv } = await loadSamvaBrandResolver(process.cwd());
79846
+ const projectBrand = await import(pathToFileURL5(Bun.resolveSync("@samva/markup/brand", process.cwd())).href);
79847
+ return projectBuild.createTemplateBuildSession({
79848
+ brandResolver: createBrandResolver({
79849
+ root: process.cwd(),
79850
+ env: { ...await loadBrandEnv(process.cwd()), ...selection }
79851
+ }),
79852
+ brandPlugin: projectBrand.samvaBrandPlugin,
79305
79853
  root: process.cwd(),
79306
- env: await loadBrandEnv(process.cwd())
79307
- }),
79308
- brandPlugin: projectBrand.samvaBrandPlugin,
79309
- root: process.cwd(),
79310
- ...dir === undefined ? {} : { dir: resolve16(dir) },
79311
- ...assetBase === undefined ? {} : { assetBase }
79312
- });
79313
- },
79314
- catch: (cause) => new SamvaCliError({
79315
- message: toolchainFailure(String(cause), `Could not start template watch${dir === undefined ? "" : ` in ${dir}`}`)
79316
- })
79854
+ ...dir === undefined ? {} : { dir: resolve17(dir) },
79855
+ ...assetBase === undefined ? {} : { assetBase }
79856
+ });
79857
+ },
79858
+ catch: (cause) => new SamvaCliError({
79859
+ message: toolchainFailure(String(cause), `Could not start template watch${dir === undefined ? "" : ` in ${dir}`}`)
79860
+ })
79861
+ });
79317
79862
  });
79318
79863
  var TSC_DIAGNOSTIC_LINE = /^(?:(.*?)(?:\((\d+),(\d+)\))?: )?(?:error )?(TS\d+): (.*)$/;
79864
+ var BRAND_MODULE_NOT_FOUND = /^Cannot find module 'samva:brand(?:\/[^']*)?'/;
79865
+ var ASSET_MODULE_NOT_FOUND = /^Cannot find module '[^']+\.(?:png|jpe?g|gif|webp|woff2)'/;
79866
+ var withTypesHint = (message, tsconfig) => {
79867
+ const types = BRAND_MODULE_NOT_FOUND.test(message) ? "@samva/markup/samva-brand" : ASSET_MODULE_NOT_FOUND.test(message) ? "vite/client" : undefined;
79868
+ return types === undefined ? message : `${message} Add "${types}" to compilerOptions.types in ${basename3(tsconfig)}.`;
79869
+ };
79319
79870
  var parseTypeScriptDiagnostics = (output, tsconfig) => output.split(`
79320
79871
  `).filter((line) => line.length > 0).map((line) => {
79321
79872
  const match = TSC_DIAGNOSTIC_LINE.exec(line);
79322
79873
  return match === null ? { file: tsconfig, code: "TSC", message: line } : {
79323
79874
  file: match[1] ?? tsconfig,
79324
79875
  code: match[4] ?? "TSC",
79325
- message: match[5] ?? line,
79876
+ message: withTypesHint(match[5] ?? line, tsconfig),
79326
79877
  ...match[2] === undefined ? {} : { line: Number(match[2]) },
79327
79878
  ...match[3] === undefined ? {} : { column: Number(match[3]) }
79328
79879
  };
@@ -79330,16 +79881,16 @@ var parseTypeScriptDiagnostics = (output, tsconfig) => output.split(`
79330
79881
  var typecheckProject = (tsconfig) => tryPromise2({
79331
79882
  try: async () => {
79332
79883
  const typescriptPackage = Bun.resolveSync("typescript/package.json", process.cwd());
79333
- const tsc = resolve16(dirname5(typescriptPackage), "bin/tsc");
79884
+ const tsc = resolve17(dirname5(typescriptPackage), "bin/tsc");
79334
79885
  const typescriptManifest = await Bun.file(typescriptPackage).json();
79335
- const child = Bun.spawn(["node", tsc, "--noEmit", "--pretty", "false", "--project", resolve16(tsconfig)], { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" });
79886
+ const child = Bun.spawn(["node", tsc, "--noEmit", "--pretty", "false", "--project", resolve17(tsconfig)], { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" });
79336
79887
  const [stdout, stderr, exitCode] = await Promise.all([
79337
79888
  new Response(child.stdout).text(),
79338
79889
  new Response(child.stderr).text(),
79339
79890
  child.exited
79340
79891
  ]);
79341
79892
  const output = `${stdout}${stderr}`.trim();
79342
- const diagnostics = parseTypeScriptDiagnostics(output, resolve16(tsconfig));
79893
+ const diagnostics = parseTypeScriptDiagnostics(output, resolve17(tsconfig));
79343
79894
  if (exitCode !== 0 && diagnostics.length === 0) {
79344
79895
  return Promise.reject(new SamvaCliError({
79345
79896
  message: output || `Could not run TypeScript from this project. Install typescript and check ${tsconfig}.`
@@ -79421,7 +79972,7 @@ var observeTemplateCheck = (dir, tsconfig, build) => gen2(function* () {
79421
79972
  problems: typeProblems
79422
79973
  }
79423
79974
  });
79424
- const attempted = typeProblems.length > 0 ? yield* option2(build()) : some2(yield* build());
79975
+ const attempted = typeProblems.length > 0 ? yield* build().pipe(asSome2, catchTag2("SamvaCliError", () => succeed6(none2()))) : some2(yield* build());
79425
79976
  if (isNone2(attempted))
79426
79977
  return typeFailure();
79427
79978
  const built = attempted.value;
@@ -79433,7 +79984,15 @@ var observeTemplateCheck = (dir, tsconfig, build) => gen2(function* () {
79433
79984
  const ok = typeProblems.length === 0 && isSuccessfulBuild(built);
79434
79985
  const compilerVersion = built.compilerVersion;
79435
79986
  const warnings = compatibilityFindings(built, "warning");
79436
- const message = ok ? `TypeScript v${typecheck.version} \xB7 compiler ${compilerVersion} \xB7 ${built.files.length} template(s) compiled \xB7 ${warnings.length} compatibility warning(s)` : typeProblems.length > 0 ? `TypeScript check failed (${typeProblems.length} problem(s)); template check found ${problems.length - typeProblems.length} more.` : built.files.length === 0 ? NO_TEMPLATES(dir) : `Template check failed (${problems.length} problem(s)).`;
79987
+ const failureMessage = () => {
79988
+ if (typeProblems.length > 0) {
79989
+ return `TypeScript check failed (${typeProblems.length} problem(s)); template check found ${problems.length - typeProblems.length} more.`;
79990
+ }
79991
+ if (built.files.length === 0)
79992
+ return NO_TEMPLATES(dir);
79993
+ return `Template check failed (${problems.length} problem(s)).`;
79994
+ };
79995
+ const message = ok ? `TypeScript v${typecheck.version} \xB7 compiler ${compilerVersion} \xB7 ${built.files.length} template(s) compiled \xB7 ${warnings.length} compatibility warning(s)` : failureMessage();
79437
79996
  return {
79438
79997
  ok,
79439
79998
  message,
@@ -79634,7 +80193,7 @@ var build3 = make29("build", {
79634
80193
  }
79635
80194
  return yield* new SamvaCliError({ message });
79636
80195
  }
79637
- const target = resolve16(out);
80196
+ const target = resolve17(out);
79638
80197
  const artifact = toTemplateBuildArtifact(built);
79639
80198
  yield* writeJsonFile(target, artifact);
79640
80199
  const result = {
@@ -79718,7 +80277,7 @@ var createRemote = make29("create", {
79718
80277
  data: String9("data").pipe(withDescription2("Complete template JSON payload")),
79719
80278
  namespace: namespaceFlag("create"),
79720
80279
  shared: Boolean6("shared").pipe(withDescription2("Share the new organization-level project with every namespace; namespace-scoped keys read and send it but cannot change it"), withDefault5(false)),
79721
- dryRun: dryRunFlag6,
80280
+ dryRun: dryRunFlag7,
79722
80281
  json: jsonFlag
79723
80282
  }).pipe(withDescription3("Create a remote template from JSON"), withExamples([
79724
80283
  {
@@ -79761,7 +80320,7 @@ var updateRemote = make29("update", {
79761
80320
  id: templateIdArgument2,
79762
80321
  data: String9("data").pipe(withDescription2("JSON object of template fields to update")),
79763
80322
  shared: Literals3("shared", ["true", "false"]).pipe(withDescription2("Share the template's organization-level project with every namespace (true) or stop sharing it (false); applies to every entry in the project"), optional4),
79764
- dryRun: dryRunFlag6,
80323
+ dryRun: dryRunFlag7,
79765
80324
  json: jsonFlag
79766
80325
  }).pipe(withDescription3("Update a remote template from JSON"), withExamples([
79767
80326
  {
@@ -79791,7 +80350,7 @@ var updateRemote = make29("update", {
79791
80350
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79792
80351
  var removeRemote = make29("remove", {
79793
80352
  id: templateIdArgument2,
79794
- dryRun: dryRunFlag6,
80353
+ dryRun: dryRunFlag7,
79795
80354
  yes: Boolean6("yes").pipe(withDescription2("Confirm destructive state change"), withDefault5(false)),
79796
80355
  json: jsonFlag
79797
80356
  }).pipe(withDescription3("Deactivate a remote template; its project file and publications remain"), withExamples([{ command: "samva templates remove tmpl_abc123 --dry-run" }]), withHandler(({ id, dryRun, yes, json }) => gen2(function* () {
@@ -79804,7 +80363,7 @@ var removeRemote = make29("remove", {
79804
80363
  nonInteractiveMessage: "remove requires --yes in non-interactive mode. Preview with --dry-run."
79805
80364
  });
79806
80365
  const client = yield* getClientProvider;
79807
- const result = yield* remove13({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not remove template", error)));
80366
+ const result = yield* remove15({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not remove template", error)));
79808
80367
  if (json)
79809
80368
  return yield* printJson(result);
79810
80369
  yield* printFields([
@@ -79812,18 +80371,6 @@ var removeRemote = make29("remove", {
79812
80371
  ["Removed", result.success]
79813
80372
  ]);
79814
80373
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79815
- var exportProject = (input) => runToolchain({
79816
- action: `Could not export templates${input.dir === undefined ? "" : ` in ${input.dir}`}`,
79817
- options: {
79818
- root: process.cwd(),
79819
- out: resolve16(input.out),
79820
- ...input.dir === undefined ? {} : { dir: resolve16(input.dir) },
79821
- ...input.assetBase === undefined ? {} : { assetBase: input.assetBase }
79822
- },
79823
- script: `import { exportProject } from "@samva/vite/project"; ${OPTIONS_PRELUDE}
79824
- ${brandResolverPrelude()}
79825
- process.stdout.write(JSON.stringify(await exportProject(options)));`
79826
- });
79827
80374
  var exportCommand = make29("export", {
79828
80375
  dir: dirFlag,
79829
80376
  out: Directory2("out").pipe(withDescription2("Directory to write rendered fixtures, assets, and the catalog into")),
@@ -79844,7 +80391,7 @@ var exportCommand = make29("export", {
79844
80391
  const result = {
79845
80392
  action: "templates.export",
79846
80393
  ok: errors.length === 0,
79847
- out: resolve16(out),
80394
+ out: resolve17(out),
79848
80395
  assetBase: receipt.assets.base,
79849
80396
  templates: receipt.templates.map((template) => ({
79850
80397
  id: template.id,
@@ -80486,7 +81033,7 @@ var list27 = /* @__PURE__ */ make55("webhooksList");
80486
81033
  var create16 = /* @__PURE__ */ make55("webhooksCreate");
80487
81034
  var getSecret = /* @__PURE__ */ make55("webhooksGetSecret");
80488
81035
  var get28 = /* @__PURE__ */ make55("webhooksGet");
80489
- var remove14 = /* @__PURE__ */ make55("webhooksRemove");
81036
+ var remove16 = /* @__PURE__ */ make55("webhooksRemove");
80490
81037
  var update14 = /* @__PURE__ */ make55("webhooksUpdate");
80491
81038
  var test = /* @__PURE__ */ make55("webhooksTest");
80492
81039
  var getLogs = /* @__PURE__ */ make55("webhooksGetLogs");
@@ -80749,11 +81296,11 @@ var rotate3 = make29("rotate-secret", {
80749
81296
  const client = yield* getClientProvider;
80750
81297
  const data = yield* regenerateSecret({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not rotate webhook secret", error)));
80751
81298
  const warning = `Read this signing secret again with samva webhooks secret ${id}. The previous secret remains valid for 24 hours.`;
80752
- const secret = value2(data.secret);
81299
+ const signingSecret = value2(data.secret);
80753
81300
  if (json)
80754
- return yield* printJson({ secret, warning });
81301
+ return yield* printJson({ secret: signingSecret, warning });
80755
81302
  yield* error(`WARNING: ${warning}`);
80756
- yield* log(secret);
81303
+ yield* log(signingSecret);
80757
81304
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
80758
81305
  var create17 = make29("create", {
80759
81306
  data: String9("data").pipe(withDescription2("Webhook JSON: name, url, eventTypes, and optional retry settings")),
@@ -80773,16 +81320,17 @@ var create17 = make29("create", {
80773
81320
  const client = yield* getClientProvider;
80774
81321
  const result = yield* create16(payload).pipe(client.provide, mapError2((error) => toCliApiError("Could not create webhook", error)));
80775
81322
  const warning = `Read this signing secret again with samva webhooks secret ${result.endpoint.id}.`;
80776
- const secret = value2(result.secret);
80777
- if (json)
80778
- return yield* printJson({ endpoint: result.endpoint, secret, warning });
81323
+ const signingSecret = value2(result.secret);
81324
+ if (json) {
81325
+ return yield* printJson({ endpoint: result.endpoint, secret: signingSecret, warning });
81326
+ }
80779
81327
  yield* error(`WARNING: ${warning}`);
80780
81328
  yield* printFields([
80781
81329
  ["ID", result.endpoint.id],
80782
81330
  ["Name", result.endpoint.name],
80783
81331
  ["Status", result.endpoint.status],
80784
81332
  ["URL", result.endpoint.url],
80785
- ["Signing secret", secret]
81333
+ ["Signing secret", signingSecret]
80786
81334
  ]);
80787
81335
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
80788
81336
  var update15 = make29("update", {
@@ -80808,7 +81356,7 @@ var update15 = make29("update", {
80808
81356
  return yield* printJson(result);
80809
81357
  yield* printFields(objectRows(result));
80810
81358
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
80811
- var remove15 = make29("remove", {
81359
+ var remove17 = make29("remove", {
80812
81360
  id: idArgument4,
80813
81361
  dryRun: Boolean6("dry-run").pipe(withDefault5(false)),
80814
81362
  yes: Boolean6("yes").pipe(withDefault5(false)),
@@ -80826,7 +81374,7 @@ var remove15 = make29("remove", {
80826
81374
  nonInteractiveMessage: "Permanent webhook deletion requires --yes in non-interactive mode. Preview with --dry-run."
80827
81375
  });
80828
81376
  const client = yield* getClientProvider;
80829
- const result = yield* remove14({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not remove webhook", error)));
81377
+ const result = yield* remove16({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not remove webhook", error)));
80830
81378
  if (json)
80831
81379
  return yield* printJson(result);
80832
81380
  yield* log(`Removed webhook ${id}.`);
@@ -80842,7 +81390,7 @@ var webhooksCommand = make29("webhooks").pipe(withDescription3("Inspect, test, a
80842
81390
  stats3,
80843
81391
  retry4,
80844
81392
  rotate3,
80845
- remove15
81393
+ remove17
80846
81394
  ]));
80847
81395
 
80848
81396
  // src/lib/root-commands.ts
@@ -80913,6 +81461,40 @@ var cli = make29("samva").pipe(withDescription3(`Samva \u2014 email and platform
80913
81461
  }
80914
81462
  ]), withSubcommands([...rootCommands, helpCommand]), withGlobalFlags(globalFlags));
80915
81463
 
81464
+ // src/lib/help-json.ts
81465
+ var description = (value) => getOrNull(value);
81466
+ var flag = (doc) => ({
81467
+ name: doc.name,
81468
+ aliases: doc.aliases,
81469
+ type: doc.type,
81470
+ description: description(doc.description),
81471
+ required: doc.required
81472
+ });
81473
+ var helpDocJson = (doc) => ({
81474
+ schemaVersion: 1,
81475
+ description: doc.description,
81476
+ usage: doc.usage,
81477
+ flags: doc.flags.map(flag),
81478
+ globalFlags: (doc.globalFlags ?? []).map(flag),
81479
+ args: (doc.args ?? []).map((arg) => ({
81480
+ name: arg.name,
81481
+ type: arg.type,
81482
+ description: description(arg.description),
81483
+ required: arg.required,
81484
+ variadic: arg.variadic
81485
+ })),
81486
+ subcommands: (doc.subcommands ?? []).flatMap((group) => group.commands.map((command) => ({
81487
+ name: command.name,
81488
+ alias: command.alias ?? null,
81489
+ description: command.shortDescription ?? command.description,
81490
+ group: group.group ?? null
81491
+ }))),
81492
+ examples: (doc.examples ?? []).map((example) => ({
81493
+ command: example.command,
81494
+ description: example.description ?? null
81495
+ }))
81496
+ });
81497
+
80916
81498
  // src/bin.ts
80917
81499
  var argv = process.argv.slice(2);
80918
81500
  var outputMode = outputModeFromArgv(argv);
@@ -80938,7 +81520,7 @@ var rootMachineHelp = (() => {
80938
81520
  }
80939
81521
  return { requested, command: undefined };
80940
81522
  })();
80941
- var isRootMachineHelp = machineMode && rootMachineHelp.requested && rootMachineHelp.command === undefined;
81523
+ var helpRequested = processArgs.some((token) => token === "--help" || token === "-h");
80942
81524
  var isUnknownRootMachineHelp = machineMode && rootMachineHelp.requested && rootMachineHelp.command !== undefined && !isKnownRootCommand(rootMachineHelp.command);
80943
81525
  var cliLogger = make30(({ logLevel, message }) => {
80944
81526
  const text = (Array.isArray(message) ? message : [message]).map(String).join(" ");
@@ -80964,11 +81546,21 @@ var isBareHelp = (error) => error instanceof ShowHelp && error.errors.length ===
80964
81546
  var formatter = {
80965
81547
  ...defaultFormatter2(),
80966
81548
  formatError: () => "",
80967
- formatErrors: () => ""
81549
+ formatErrors: () => "",
81550
+ ...machineMode && helpRequested ? {
81551
+ formatHelpDoc: (doc) => JSON.stringify(helpDocJson(doc))
81552
+ } : {}
80968
81553
  };
80969
81554
  var flushSuccessfulResults = (state, partial = false) => gen2(function* () {
80970
- if (outputMode === "quiet" || outputMode === "jsonl")
81555
+ if (outputMode === "quiet")
80971
81556
  return;
81557
+ if (outputMode === "jsonl") {
81558
+ if (helpRequested && state.lines.length === 1) {
81559
+ yield* writeOutput(process.stdout, `${JSON.stringify(resultEnvelope(commandNameFromArgv(argv), JSON.parse(state.lines[0])))}
81560
+ `);
81561
+ }
81562
+ return;
81563
+ }
80972
81564
  if (outputMode === "human") {
80973
81565
  if (state.lines.length > 0) {
80974
81566
  yield* writeOutput(process.stdout, `${state.lines.join(`
@@ -81070,12 +81662,7 @@ Try: ${next}`}`);
81070
81662
  }
81071
81663
  yield* printJsonError(error2, message);
81072
81664
  });
81073
- var runMachineHelp = gen2(function* () {
81074
- const payload = resultEnvelope(commandNameFromArgv(argv), helpManifest);
81075
- yield* writeOutput(process.stdout, `${JSON.stringify(payload, null, outputMode === "jsonl" ? undefined : 2)}
81076
- `);
81077
- });
81078
- var program = (isRootMachineHelp ? validateProcessArgs(argv).pipe(flatMap3(() => runMachineHelp)) : isUnknownRootMachineHelp ? validateProcessArgs(argv).pipe(flatMap3(() => new CliUsageError({
81665
+ var program = (isUnknownRootMachineHelp ? validateProcessArgs(argv).pipe(flatMap3(() => new CliUsageError({
81079
81666
  code: "unknown_subcommand",
81080
81667
  message: `Unknown subcommand ${JSON.stringify(rootMachineHelp.command)} for samva.`
81081
81668
  }))) : withStdoutBuffer(validateProcessArgs(argv).pipe(flatMap3(() => run5(cli, { version: package_default.version }))))).pipe(provide4(mergeAll2(layer14([cliLogger]), layer12, layer13(formatter), layer17)), matchEffect3({