@koda-sl/baker-cli 0.376.0-dev.2f68e83cd → 0.376.0-dev.494d5fded

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.
package/dist/cli.js CHANGED
@@ -133,7 +133,7 @@ import {
133
133
  } from "./chunk-7YNOGIWQ.js";
134
134
 
135
135
  // src/cli.ts
136
- import { defineCommand as defineCommand251, runMain } from "citty";
136
+ import { defineCommand as defineCommand255, runMain } from "citty";
137
137
 
138
138
  // src/cache-flag.ts
139
139
  var NO_CACHE_ARG = {
@@ -1209,8 +1209,8 @@ var campaignUpdateSchema = z3.object({
1209
1209
  }
1210
1210
  validateCampaignBudgets(p, ctx, { requireBudget: false });
1211
1211
  });
1212
- function trackingParamsLevel(target2) {
1213
- return SPONSORED_ACCOUNT_URN_REGEX.test(target2) ? "account" : "campaign";
1212
+ function trackingParamsLevel(target4) {
1213
+ return SPONSORED_ACCOUNT_URN_REGEX.test(target4) ? "account" : "campaign";
1214
1214
  }
1215
1215
  function bareLinkedinUrnId(urn) {
1216
1216
  return urn.split(":").pop() ?? urn;
@@ -4329,7 +4329,8 @@ var CAPABILITY_SURFACES = [
4329
4329
  "x-ads",
4330
4330
  "tag-manager",
4331
4331
  "ga4",
4332
- "gsc"
4332
+ "gsc",
4333
+ "shopify"
4333
4334
  ];
4334
4335
  var CAPABILITY_SURFACE_SPECS = {
4335
4336
  "google-ads": {
@@ -4412,6 +4413,26 @@ var CAPABILITY_SURFACE_SPECS = {
4412
4413
  writePath: "read-only",
4413
4414
  writeScopes: ["https://www.googleapis.com/auth/webmasters"],
4414
4415
  commands: "baker gsc"
4416
+ },
4417
+ shopify: {
4418
+ id: "shopify",
4419
+ label: "Shopify store",
4420
+ provider: "shopify",
4421
+ resourceNoun: "store",
4422
+ // Staged during the chat and applied to the real store when it completes,
4423
+ // every time; each applied change can be put back by another publish
4424
+ // (`baker shopify undo`). The write permissions are optional scopes the
4425
+ // store grants when a change first needs one, so `scopeBlockers` is the
4426
+ // per-company signal.
4427
+ writePath: "staged-immediate",
4428
+ writeScopes: [
4429
+ "write_products",
4430
+ "write_publications",
4431
+ "write_discounts",
4432
+ "write_online_store_navigation",
4433
+ "write_marketing_events"
4434
+ ],
4435
+ commands: "baker shopify"
4415
4436
  }
4416
4437
  };
4417
4438
 
@@ -4530,6 +4551,8 @@ var chatChangeTypeSchema = z13.enum([
4530
4551
  "x-ads",
4531
4552
  "tag-manager",
4532
4553
  "ga4",
4554
+ // Changes to the company's Shopify store (S9). Staged, applied at Publish.
4555
+ "shopify",
4533
4556
  // A recipe the company added to their own marketplace, staged like any other
4534
4557
  // Session effect — see convex/companyTemplates/mutations.ts.
4535
4558
  "recipe",
@@ -4750,6 +4773,7 @@ var chatEffectsSchema = z13.object({
4750
4773
  creatives: z13.array(creativeEffectSchema),
4751
4774
  tagManager: z13.array(configEffectSchema),
4752
4775
  ga4: z13.array(configEffectSchema),
4776
+ shopify: z13.array(configEffectSchema),
4753
4777
  funnel: z13.array(funnelEffectSchema),
4754
4778
  conversions: z13.array(settingEffectSchema),
4755
4779
  conversionSends: z13.array(settingEffectSchema),
@@ -6970,7 +6994,6 @@ var STORE_EVENTS = {
6970
6994
  product_out_of_stock: {
6971
6995
  label: "A product runs out of stock",
6972
6996
  sentence: "When a product in your Shopify store runs out of stock",
6973
- description: "One or more of its options can no longer be bought; whole_product says whether nothing at all can be.",
6974
6997
  payload: [
6975
6998
  "title",
6976
6999
  "handle",
@@ -6981,13 +7004,11 @@ var STORE_EVENTS = {
6981
7004
  "promoted_by",
6982
7005
  "promotions_checked",
6983
7006
  ...PRODUCT_FIELDS.slice(2)
6984
- ],
6985
- namesProduct: true
7007
+ ]
6986
7008
  },
6987
7009
  product_back_in_stock: {
6988
7010
  label: "A product is back in stock",
6989
7011
  sentence: "When a product in your Shopify store is back in stock",
6990
- description: "Options that ran out can be bought again, or ads were paused for it after it came back.",
6991
7012
  payload: [
6992
7013
  "title",
6993
7014
  "handle",
@@ -6998,20 +7019,16 @@ var STORE_EVENTS = {
6998
7019
  "reason",
6999
7020
  "promoted_by",
7000
7021
  ...PRODUCT_FIELDS.slice(2)
7001
- ],
7002
- namesProduct: true
7022
+ ]
7003
7023
  },
7004
7024
  product_created: {
7005
7025
  label: "A new product is added",
7006
7026
  sentence: "When a new product is added to your Shopify store",
7007
- description: "A product appears in the store (never during Baker's first import).",
7008
- payload: [...PRODUCT_FIELDS],
7009
- namesProduct: true
7027
+ payload: [...PRODUCT_FIELDS]
7010
7028
  },
7011
7029
  product_updated: {
7012
7030
  label: "A product is changed",
7013
7031
  sentence: "When a product in your Shopify store is changed",
7014
- description: "Its price, title, status, publication, handle, tags, type, vendor or description changed; stock changes and changes Baker made do not count.",
7015
7032
  payload: [
7016
7033
  "title",
7017
7034
  "handle",
@@ -7022,41 +7039,33 @@ var STORE_EVENTS = {
7022
7039
  "before_published",
7023
7040
  "price_changes",
7024
7041
  ...PRODUCT_FIELDS.slice(2)
7025
- ],
7026
- namesProduct: true
7042
+ ]
7027
7043
  },
7028
7044
  collection_updated: {
7029
7045
  label: "A collection changes",
7030
7046
  sentence: "When a collection in your Shopify store changes",
7031
- description: "A collection is created, renamed, published or unpublished, or gains or loses products.",
7032
- payload: ["title", "collection", "changed", "product_count", "before_product_count", "published", "store"],
7033
- namesProduct: false
7047
+ payload: ["title", "collection", "changed", "product_count", "before_product_count", "published", "store"]
7034
7048
  },
7035
7049
  purchase: {
7036
7050
  label: "An order is paid",
7037
7051
  sentence: "When an order is paid",
7038
- description: "The store's sale; value is the order's total in the store's currency.",
7039
7052
  payload: ["order_name", "ordered_at", "shopify_source", "visitor_from", "campaign_from", "store"],
7040
- namesProduct: false
7053
+ alsoTheSite: true
7041
7054
  },
7042
7055
  refund: {
7043
7056
  label: "Money is refunded on an order",
7044
7057
  sentence: "When money is refunded on an order",
7045
- description: "Money given back on an order; value is the amount refunded.",
7046
7058
  payload: ["order_name", "store"],
7047
- namesProduct: false
7059
+ alsoTheSite: true
7048
7060
  },
7049
7061
  customer_created: {
7050
7062
  label: "A new customer signs up",
7051
7063
  sentence: "When a new customer account is created in your Shopify store",
7052
- description: "A new customer account, with their country and whether they accept email \u2014 never a name or address.",
7053
- payload: ["customer", "country", "accepts_email", "orders", "tags", "account_state", "store"],
7054
- namesProduct: false
7064
+ payload: ["customer", "country", "accepts_email", "orders", "tags", "account_state", "store"]
7055
7065
  },
7056
7066
  checkout_abandoned: {
7057
7067
  label: "A checkout is abandoned",
7058
7068
  sentence: "When a shopper leaves a checkout in your Shopify store without buying",
7059
- description: "A checkout with contact details was not completed after an hour.",
7060
7069
  payload: [
7061
7070
  "total",
7062
7071
  "currency",
@@ -7069,13 +7078,11 @@ var STORE_EVENTS = {
7069
7078
  "started_at",
7070
7079
  "last_activity_at",
7071
7080
  "store"
7072
- ],
7073
- namesProduct: false
7081
+ ]
7074
7082
  },
7075
7083
  sales_drop: {
7076
7084
  label: "Sales drop",
7077
7085
  sentence: "When your Shopify store's sales drop well below usual",
7078
- description: "The last 6 hours brought far fewer orders than the same hours usually do.",
7079
7086
  payload: [
7080
7087
  "window_hours",
7081
7088
  "window_start",
@@ -7088,8 +7095,7 @@ var STORE_EVENTS = {
7088
7095
  "drop_percent",
7089
7096
  "baseline_days",
7090
7097
  "store"
7091
- ],
7092
- namesProduct: false
7098
+ ]
7093
7099
  }
7094
7100
  };
7095
7101
  var STORE_EVENT_NAMES = Object.keys(STORE_EVENTS);
@@ -8435,8 +8441,8 @@ var videosDeleteResponseSchema = z29.object({ ok: z29.literal(true) });
8435
8441
 
8436
8442
  // src/commands/actions/tagError.ts
8437
8443
  var UNKNOWN_TAG_RE = /Unknown action tag\(s\): ([^.]+)\. Valid tags: ([^.]+)\./;
8438
- function splitSlugs(list) {
8439
- return list.split(",").map((slug) => slug.trim()).filter((slug) => slug.length > 0);
8444
+ function splitSlugs(list3) {
8445
+ return list3.split(",").map((slug) => slug.trim()).filter((slug) => slug.length > 0);
8440
8446
  }
8441
8447
  function actionTagFix(message) {
8442
8448
  const match = UNKNOWN_TAG_RE.exec(message);
@@ -8725,6 +8731,13 @@ function buildCreateHints({
8725
8731
  draftCreateCount
8726
8732
  }) {
8727
8733
  const hints2 = [];
8734
+ const expanded = shellExpandedIn(`${name}
8735
+ ${description}`);
8736
+ if (expanded) {
8737
+ hints2.push(
8738
+ `The text says \u201C${expanded}\u201D: the shell replaced a $ amount (like $0) inside double quotes before Baker saw it. Fix it now: baker actions update ${tempId} --description '<the text, with the amounts as you read them>' (and --name '\u2026' if the name has it) \u2014 single quotes keep $ as written.`
8739
+ );
8740
+ }
8728
8741
  const advisory = advisoryHint(name, description);
8729
8742
  if (advisory) {
8730
8743
  hints2.push(
@@ -8756,6 +8769,9 @@ function buildCreateHints({
8756
8769
  }
8757
8770
  return hints2;
8758
8771
  }
8772
+ function shellExpandedIn(text2) {
8773
+ return /(?:^|[\s(])((?:\/usr)?\/bin\/(?:ba|z|da|k)?sh|-?(?:ba|z|da|k)sh)\b/.exec(text2)?.[1] ?? null;
8774
+ }
8759
8775
  function advisoryHint(name, description) {
8760
8776
  if (looksScheduled(name, description)) {
8761
8777
  return { kind: "scheduled", hint: SCHEDULED_HINT };
@@ -9948,7 +9964,7 @@ import { defineCommand as defineCommand19 } from "citty";
9948
9964
  function listed(resources) {
9949
9965
  return resources.map((resource) => resource.label ? `${resource.id} (${resource.label})` : resource.id).join("; ");
9950
9966
  }
9951
- function connectedResourceHints({ plural: plural2, singular, flag: flag3, resources }) {
9967
+ function connectedResourceHints({ plural: plural3, singular, flag: flag3, resources }) {
9952
9968
  if (resources.length === 0) {
9953
9969
  return [];
9954
9970
  }
@@ -9956,7 +9972,7 @@ function connectedResourceHints({ plural: plural2, singular, flag: flag3, resour
9956
9972
  return [`This company connected one ${singular}: ${listed(resources)}. Commands use it unless you pass ${flag3}.`];
9957
9973
  }
9958
9974
  return [
9959
- `This company connected ${resources.length} ${plural2}: ${listed(resources)}. Each command works on one, so pass ${flag3} \u2014 nothing is picked for you. A question about the whole business covers all ${resources.length}: answering from one and reporting it as the total is wrong.`
9975
+ `This company connected ${resources.length} ${plural3}: ${listed(resources)}. Each command works on one, so pass ${flag3} \u2014 nothing is picked for you. A question about the whole business covers all ${resources.length}: answering from one and reporting it as the total is wrong.`
9960
9976
  ];
9961
9977
  }
9962
9978
 
@@ -11591,8 +11607,8 @@ function capabilityFix(surface) {
11591
11607
  // src/commands/ads/boolean-word-positional.ts
11592
11608
  var BOOLEAN_WORD = /^(true|false)$/i;
11593
11609
  function booleanWordPositionalError(args5, entity) {
11594
- const positionals = Array.isArray(args5._) ? args5._ : [];
11595
- const word = positionals.find((value2) => typeof value2 === "string" && BOOLEAN_WORD.test(value2));
11610
+ const positionals2 = Array.isArray(args5._) ? args5._ : [];
11611
+ const word = positionals2.find((value2) => typeof value2 === "string" && BOOLEAN_WORD.test(value2));
11596
11612
  if (word === void 0) {
11597
11613
  return void 0;
11598
11614
  }
@@ -11926,7 +11942,7 @@ function words(headline) {
11926
11942
  return headline.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter(Boolean);
11927
11943
  }
11928
11944
  function canonical(word) {
11929
- for (const [i, group] of CYCLED_VERBS.entries()) if (group.includes(word)) return `group:${i}`;
11945
+ for (const [i, group2] of CYCLED_VERBS.entries()) if (group2.includes(word)) return `group:${i}`;
11930
11946
  return word;
11931
11947
  }
11932
11948
  function overlap(a, b) {
@@ -12201,11 +12217,11 @@ function requireTarget(args5, entity) {
12201
12217
  failWriteValidation(booleanWord);
12202
12218
  }
12203
12219
  const positional = Array.isArray(args5._) ? args5._[0] : void 0;
12204
- const target2 = args5.id ?? args5.target ?? args5.ref ?? positional;
12205
- if (typeof target2 !== "string" || target2.length === 0) {
12220
+ const target4 = args5.id ?? args5.target ?? args5.ref ?? positional;
12221
+ if (typeof target4 !== "string" || target4.length === 0) {
12206
12222
  failWriteValidation(`pass the ${entity} resource name or id as the positional argument`);
12207
12223
  }
12208
- return target2;
12224
+ return target4;
12209
12225
  }
12210
12226
  function requireStringFlag(value, flag3) {
12211
12227
  if (typeof value !== "string" || value.length === 0) {
@@ -12413,11 +12429,11 @@ async function stageGoogleOps(rawOps, hints2) {
12413
12429
  handleGoogleError(err);
12414
12430
  }
12415
12431
  }
12416
- async function stageUpdate(kind, customerId, target2, payload, hints2) {
12417
- await stageGoogleOp({ kind, customerId, target: target2, payload }, hints2);
12432
+ async function stageUpdate(kind, customerId, target4, payload, hints2) {
12433
+ await stageGoogleOp({ kind, customerId, target: target4, payload }, hints2);
12418
12434
  }
12419
- async function stageTarget(kind, customerId, target2, hints2) {
12420
- await stageGoogleOp({ kind, customerId, target: target2 }, hints2);
12435
+ async function stageTarget(kind, customerId, target4, hints2) {
12436
+ await stageGoogleOp({ kind, customerId, target: target4 }, hints2);
12421
12437
  }
12422
12438
  async function draftAction(path49, body, chat) {
12423
12439
  try {
@@ -12596,9 +12612,9 @@ var DATAFORSEO_LANGUAGES = [
12596
12612
  { code: "da", name: "Danish" },
12597
12613
  { code: "fi", name: "Finnish" }
12598
12614
  ];
12599
- function toRecord(list) {
12615
+ function toRecord(list3) {
12600
12616
  const map = {};
12601
- for (const item of list) {
12617
+ for (const item of list3) {
12602
12618
  const key = item.id ?? item.code;
12603
12619
  if (key) map[key] = item.name;
12604
12620
  }
@@ -13974,9 +13990,9 @@ function servingScopeWarnings(args5, finalQuery) {
13974
13990
  }
13975
13991
  function parseSelectedFields(gaql) {
13976
13992
  const match = gaql.match(/\bSELECT\b([\s\S]*?)\bFROM\b/i);
13977
- const list = match?.[1];
13978
- if (!list) return [];
13979
- return list.split(",").map((field) => field.trim()).filter((field) => /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)*$/.test(field));
13993
+ const list3 = match?.[1];
13994
+ if (!list3) return [];
13995
+ return list3.split(",").map((field) => field.trim()).filter((field) => /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)*$/.test(field));
13980
13996
  }
13981
13997
  function foldObservedFields(rows, observed) {
13982
13998
  for (const row of rows) {
@@ -18075,19 +18091,19 @@ var CSV_SEPARATORS = /* @__PURE__ */ new Set([",", ";", " "]);
18075
18091
  function splitCsvLine(line) {
18076
18092
  const fields = [];
18077
18093
  let current = "";
18078
- let quoted = false;
18094
+ let quoted2 = false;
18079
18095
  for (let i = 0; i < line.length; i++) {
18080
18096
  const char = line[i];
18081
- if (quoted && char !== '"') {
18097
+ if (quoted2 && char !== '"') {
18082
18098
  current += char;
18083
- } else if (quoted) {
18084
- quoted = line[i + 1] === '"';
18085
- if (quoted) {
18099
+ } else if (quoted2) {
18100
+ quoted2 = line[i + 1] === '"';
18101
+ if (quoted2) {
18086
18102
  current += '"';
18087
18103
  i += 1;
18088
18104
  }
18089
18105
  } else if (char === '"') {
18090
- quoted = true;
18106
+ quoted2 = true;
18091
18107
  } else if (CSV_SEPARATORS.has(char ?? "")) {
18092
18108
  fields.push(current);
18093
18109
  current = "";
@@ -18431,13 +18447,13 @@ var budgetsCommand = defineCommand31({
18431
18447
  },
18432
18448
  run: async ({ args: args5 }) => {
18433
18449
  const customerId = requireCustomerId(args5);
18434
- const target2 = requireTarget(args5, "budget");
18450
+ const target4 = requireTarget(args5, "budget");
18435
18451
  const payload = mergePayload(loadJsonFileArg(args5.file), {
18436
18452
  name: args5.name,
18437
18453
  amountMicros: microsFlag(args5.amount, "--amount"),
18438
18454
  deliveryMethod: args5.delivery
18439
18455
  });
18440
- await stageUpdate("google.budget.update", customerId, target2, payload, BUDGET_DEMAND_HINTS);
18456
+ await stageUpdate("google.budget.update", customerId, target4, payload, BUDGET_DEMAND_HINTS);
18441
18457
  }
18442
18458
  })
18443
18459
  }
@@ -18753,7 +18769,7 @@ var campaignsCommand = defineCommand31({
18753
18769
  if (level !== "campaign") {
18754
18770
  failWriteValidation("--level must be campaign or account");
18755
18771
  }
18756
- const target2 = requireTarget(args5, "campaign");
18772
+ const target4 = requireTarget(args5, "campaign");
18757
18773
  const payload = mergePayload(loadJsonFileArg(args5.file), {
18758
18774
  name: args5.name,
18759
18775
  budget: args5["budget-ref"],
@@ -18766,7 +18782,7 @@ var campaignsCommand = defineCommand31({
18766
18782
  euPoliticalAds: args5["eu-political-ads"],
18767
18783
  status: args5.status
18768
18784
  });
18769
- await stageUpdate("google.campaign.update", customerId, target2, payload);
18785
+ await stageUpdate("google.campaign.update", customerId, target4, payload);
18770
18786
  }
18771
18787
  }),
18772
18788
  pause: statusCommand2("google.campaign.pause", "campaign"),
@@ -18836,13 +18852,13 @@ var adGroupsCommand = defineCommand31({
18836
18852
  },
18837
18853
  run: async ({ args: args5 }) => {
18838
18854
  const customerId = requireCustomerId(args5);
18839
- const target2 = requireTarget(args5, "ad group");
18855
+ const target4 = requireTarget(args5, "ad group");
18840
18856
  const payload = mergePayload(loadJsonFileArg(args5.file), {
18841
18857
  name: args5.name,
18842
18858
  cpcBidMicros: microsFlag(args5["cpc-bid"], "--cpc-bid"),
18843
18859
  status: args5.status
18844
18860
  });
18845
- await stageUpdate("google.adGroup.update", customerId, target2, payload);
18861
+ await stageUpdate("google.adGroup.update", customerId, target4, payload);
18846
18862
  }
18847
18863
  }),
18848
18864
  pause: statusCommand2("google.adGroup.pause", "ad group"),
@@ -18870,11 +18886,11 @@ function keywordFinalUrlHints(finalUrl, count2) {
18870
18886
  }
18871
18887
  function keywordUpdateFinalUrls(args5) {
18872
18888
  const finalUrl = args5["final-url"];
18873
- const clear = args5["clear-final-url"] === true;
18889
+ const clear2 = args5["clear-final-url"] === true;
18874
18890
  if (typeof finalUrl !== "string" || finalUrl.length === 0) {
18875
- return clear ? [] : void 0;
18891
+ return clear2 ? [] : void 0;
18876
18892
  }
18877
- if (clear) {
18893
+ if (clear2) {
18878
18894
  failWriteValidation(
18879
18895
  "--clear-final-url sends the keyword back to the ad's landing page \u2014 pass it OR --final-url, not both"
18880
18896
  );
@@ -19490,13 +19506,13 @@ var assetGroupsCommand = defineCommand31({
19490
19506
  },
19491
19507
  run: async ({ args: args5 }) => {
19492
19508
  const customerId = requireCustomerId(args5);
19493
- const target2 = requireTarget(args5, "asset group");
19509
+ const target4 = requireTarget(args5, "asset group");
19494
19510
  const payload = mergePayload(loadJsonFileArg(args5.file), {
19495
19511
  name: args5.name,
19496
19512
  finalUrls: listFlag(args5["final-urls"]),
19497
19513
  status: args5.status
19498
19514
  });
19499
- await stageUpdate("google.assetGroup.update", customerId, target2, payload);
19515
+ await stageUpdate("google.assetGroup.update", customerId, target4, payload);
19500
19516
  }
19501
19517
  }),
19502
19518
  attach: defineCommand31({
@@ -19578,7 +19594,7 @@ var assetsUpdateCommand = defineCommand31({
19578
19594
  },
19579
19595
  run: async ({ args: args5 }) => {
19580
19596
  const customerId = requireCustomerId(args5);
19581
- const target2 = requireTarget(args5, "asset");
19597
+ const target4 = requireTarget(args5, "asset");
19582
19598
  const payload = mergePayload(loadJsonFileArg(args5.file), {
19583
19599
  name: args5.name,
19584
19600
  linkText: args5["link-text"],
@@ -19591,7 +19607,7 @@ var assetsUpdateCommand = defineCommand31({
19591
19607
  callToAction: args5.cta,
19592
19608
  text: args5.text
19593
19609
  });
19594
- await stageUpdate("google.asset.update", customerId, target2, payload);
19610
+ await stageUpdate("google.asset.update", customerId, target4, payload);
19595
19611
  }
19596
19612
  });
19597
19613
  var assetsCommand = defineCommand31({
@@ -19904,8 +19920,8 @@ var campaignCriteriaCommand = defineCommand31({
19904
19920
  },
19905
19921
  run: async ({ args: args5 }) => {
19906
19922
  const customerId = requireCustomerId(args5);
19907
- const target2 = requireTarget(args5, "campaign criterion");
19908
- await stageUpdate("google.campaignCriterion.update", customerId, target2, {
19923
+ const target4 = requireTarget(args5, "campaign criterion");
19924
+ await stageUpdate("google.campaignCriterion.update", customerId, target4, {
19909
19925
  bidModifier: bidModifierFlag(args5["bid-modifier"])
19910
19926
  });
19911
19927
  }
@@ -20335,7 +20351,7 @@ async function stageLinkedinOps(rawOps, hints2) {
20335
20351
  async function stageStatusChange(input) {
20336
20352
  const payload = input.kind === "creative.update" ? { intendedStatus: input.status } : { status: input.status };
20337
20353
  await stageLinkedinOps(
20338
- input.targets.map((target2) => ({ kind: input.kind, accountId: input.accountId, target: target2, payload }))
20354
+ input.targets.map((target4) => ({ kind: input.kind, accountId: input.accountId, target: target4, payload }))
20339
20355
  );
20340
20356
  }
20341
20357
  function requireTarget2(args5, entity) {
@@ -20343,17 +20359,17 @@ function requireTarget2(args5, entity) {
20343
20359
  if (inverted !== void 0) {
20344
20360
  failWriteValidation2(inverted);
20345
20361
  }
20346
- const positionals = readPositionals(args5);
20347
- const target2 = args5.id ?? args5.target ?? args5.ref ?? positionals[0];
20348
- if (typeof target2 !== "string" || target2.length === 0) {
20362
+ const positionals2 = readPositionals(args5);
20363
+ const target4 = args5.id ?? args5.target ?? args5.ref ?? positionals2[0];
20364
+ if (typeof target4 !== "string" || target4.length === 0) {
20349
20365
  failWriteValidation2(`pass the ${entity} id or URN as the positional argument`);
20350
20366
  }
20351
- if (positionals.length > 1 || splitIdList(target2).length > 1) {
20367
+ if (positionals2.length > 1 || splitIdList(target4).length > 1) {
20352
20368
  failWriteValidation2(
20353
20369
  `this command takes one ${entity} id \u2014 run it once per id. Only pause/resume/archive and 'creatives update' accept several ids in one call.`
20354
20370
  );
20355
20371
  }
20356
- return target2;
20372
+ return target4;
20357
20373
  }
20358
20374
  function readPositionals(args5) {
20359
20375
  if (!Array.isArray(args5._)) {
@@ -20371,11 +20387,11 @@ function idsFileEntries(path49) {
20371
20387
  return readFileSync5(path49, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
20372
20388
  }
20373
20389
  function requireTargets(args5, entity) {
20374
- const positionals = readPositionals(args5);
20390
+ const positionals2 = readPositionals(args5);
20375
20391
  const named = [args5.id, args5.target, args5.ref].filter(
20376
20392
  (value) => typeof value === "string" && value.length > 0
20377
20393
  );
20378
- const fromArgs = positionals.length > 0 ? positionals : named;
20394
+ const fromArgs = positionals2.length > 0 ? positionals2 : named;
20379
20395
  const targets = [.../* @__PURE__ */ new Set([...fromArgs.flatMap(splitIdList), ...idsFileEntries(args5["ids-file"])])];
20380
20396
  if (targets.length === 0) {
20381
20397
  failWriteValidation2(
@@ -21319,9 +21335,9 @@ Example: baker ads linkedin campaigns create --name "ABM Tier-1" --group li_temp
21319
21335
  await stageOp({ kind: "campaign.create", accountId, payload });
21320
21336
  }
21321
21337
  });
21322
- function campaignUpdatePayloadFromFlags(args5, target2, file) {
21338
+ function campaignUpdatePayloadFromFlags(args5, target4, file) {
21323
21339
  const flags = campaignPayloadFromFlags(args5);
21324
- if (!target2.startsWith(TEMP_REF_PREFIX)) {
21340
+ if (!target4.startsWith(TEMP_REF_PREFIX)) {
21325
21341
  flags.campaignGroup = void 0;
21326
21342
  flags.type = void 0;
21327
21343
  flags.locale = void 0;
@@ -21342,9 +21358,9 @@ Example: baker ads linkedin campaigns update 123456 --daily-budget 100 --currenc
21342
21358
  },
21343
21359
  run: async ({ args: args5 }) => {
21344
21360
  const accountId = bareAccountId(args5);
21345
- const target2 = requireTarget2(args5, "campaign");
21346
- const payload = campaignUpdatePayloadFromFlags(args5, target2, loadJsonFileArg2(args5.file));
21347
- await stageOp({ kind: "campaign.update", accountId, target: target2, payload });
21361
+ const target4 = requireTarget2(args5, "campaign");
21362
+ const payload = campaignUpdatePayloadFromFlags(args5, target4, loadJsonFileArg2(args5.file));
21363
+ await stageOp({ kind: "campaign.update", accountId, target: target4, payload });
21348
21364
  }
21349
21365
  });
21350
21366
  function statusSugarCommand(entity, kind, name) {
@@ -21491,11 +21507,11 @@ Examples:
21491
21507
  file: { type: "string", description: "JSON file with the full payload; flags override file keys" }
21492
21508
  },
21493
21509
  run: async ({ args: args5, rawArgs }) => {
21494
- const { accountId, target: target2 } = accountTrackingTarget(args5);
21510
+ const { accountId, target: target4 } = accountTrackingTarget(args5);
21495
21511
  await stageOp({
21496
21512
  kind: "trackingParams.set",
21497
21513
  accountId,
21498
- target: target2,
21514
+ target: target4,
21499
21515
  payload: trackingParamsPayload(args5, rawArgs)
21500
21516
  });
21501
21517
  }
@@ -21783,7 +21799,7 @@ Examples:
21783
21799
  campaign: args5.campaign,
21784
21800
  content: Object.keys(content).length > 0 ? content : void 0
21785
21801
  });
21786
- await stageLinkedinOps(targets.map((target2) => ({ kind: "creative.update", accountId, target: target2, payload })));
21802
+ await stageLinkedinOps(targets.map((target4) => ({ kind: "creative.update", accountId, target: target4, payload })));
21787
21803
  }
21788
21804
  });
21789
21805
  var AUDIENCE_TYPES2 = ["company-list", "user-list", "retargeting", "engagement"];
@@ -21807,7 +21823,7 @@ Example: baker ads linkedin audiences create --name "ABM Tier-1" --type company-
21807
21823
  },
21808
21824
  run: async ({ args: args5 }) => {
21809
21825
  const accountId = bareAccountId(args5);
21810
- const list = parseListFileArg(args5["list-file"], INLINE_ROWS_MAX);
21826
+ const list3 = parseListFileArg(args5["list-file"], INLINE_ROWS_MAX);
21811
21827
  const raw = args5.file ? loadJsonFileArg2(args5.file) : void 0;
21812
21828
  await stageOp({
21813
21829
  kind: "audience.create",
@@ -21815,7 +21831,7 @@ Example: baker ads linkedin audiences create --name "ABM Tier-1" --type company-
21815
21831
  payload: {
21816
21832
  name: args5.name,
21817
21833
  audienceType: args5.type,
21818
- ...list ? { listColumns: list.columns, listRows: list.rows } : {},
21834
+ ...list3 ? { listColumns: list3.columns, listRows: list3.rows } : {},
21819
21835
  ...raw ? { raw } : {}
21820
21836
  }
21821
21837
  });
@@ -21834,8 +21850,8 @@ Example: baker ads linkedin audiences upload urn:li:dmpSegment:42 --list-file mo
21834
21850
  },
21835
21851
  run: async ({ args: args5 }) => {
21836
21852
  const accountId = bareAccountId(args5);
21837
- const list = parseListFileArg(args5["list-file"], INLINE_ROWS_MAX);
21838
- if (!list) {
21853
+ const list3 = parseListFileArg(args5["list-file"], INLINE_ROWS_MAX);
21854
+ if (!list3) {
21839
21855
  failWriteValidation2("--list-file is required");
21840
21856
  }
21841
21857
  await stageOp({
@@ -21843,8 +21859,8 @@ Example: baker ads linkedin audiences upload urn:li:dmpSegment:42 --list-file mo
21843
21859
  accountId,
21844
21860
  payload: {
21845
21861
  segment: requireTarget2(args5, "audience segment"),
21846
- listColumns: list.columns,
21847
- listRows: list.rows
21862
+ listColumns: list3.columns,
21863
+ listRows: list3.rows
21848
21864
  }
21849
21865
  });
21850
21866
  }
@@ -26040,11 +26056,11 @@ function requireTarget3(args5, entity) {
26040
26056
  failWriteValidation3(inverted);
26041
26057
  }
26042
26058
  const positional = Array.isArray(args5._) ? args5._[0] : void 0;
26043
- const target2 = args5.id ?? args5.target ?? args5.ref ?? positional;
26044
- if (typeof target2 !== "string" || target2.length === 0) {
26059
+ const target4 = args5.id ?? args5.target ?? args5.ref ?? positional;
26060
+ if (typeof target4 !== "string" || target4.length === 0) {
26045
26061
  failWriteValidation3(`pass the ${entity} id as the positional argument`);
26046
26062
  }
26047
- return target2;
26063
+ return target4;
26048
26064
  }
26049
26065
  async function stageOp2(raw) {
26050
26066
  const preflight = metaDraftOpInputSchema.safeParse(raw);
@@ -28233,13 +28249,13 @@ async function resolveXAccountId(args5) {
28233
28249
  });
28234
28250
  process.exit(1);
28235
28251
  }
28236
- const list = accounts.map((a) => ` ${a.id} ${a.name}`).join("\n");
28252
+ const list3 = accounts.map((a) => ` ${a.id} ${a.name}`).join("\n");
28237
28253
  writeAdsJson({
28238
28254
  ok: false,
28239
28255
  error: {
28240
28256
  code: "MULTIPLE_ACCOUNTS",
28241
28257
  message: `Multiple X Ads accounts found. Pass --account-id or set BAKER_X_ADS_ACCOUNT_ID:
28242
- ${list}`
28258
+ ${list3}`
28243
28259
  }
28244
28260
  });
28245
28261
  process.exit(1);
@@ -30326,10 +30342,10 @@ function wallClockAsUtc(instant, timeZone) {
30326
30342
  }
30327
30343
  function accountMidnightIso(daysAgo, timeZone, now = /* @__PURE__ */ new Date()) {
30328
30344
  const localNow = new Date(wallClockAsUtc(now, timeZone));
30329
- const target2 = Date.UTC(localNow.getUTCFullYear(), localNow.getUTCMonth(), localNow.getUTCDate() - daysAgo, 0, 0, 0);
30330
- let instant = target2;
30345
+ const target4 = Date.UTC(localNow.getUTCFullYear(), localNow.getUTCMonth(), localNow.getUTCDate() - daysAgo, 0, 0, 0);
30346
+ let instant = target4;
30331
30347
  for (let i = 0; i < 2; i++) {
30332
- instant = target2 - (wallClockAsUtc(new Date(instant), timeZone) - instant);
30348
+ instant = target4 - (wallClockAsUtc(new Date(instant), timeZone) - instant);
30333
30349
  }
30334
30350
  return isoSeconds(new Date(instant));
30335
30351
  }
@@ -31839,12 +31855,12 @@ function setFromRename(args5, existing) {
31839
31855
  }
31840
31856
  function conversionsWrite(args5, existing) {
31841
31857
  const set = [...setFromEvents(args5, existing), ...setFromRename(args5, existing)];
31842
- const remove = [
31858
+ const remove2 = [
31843
31859
  ...args5.removeKeys,
31844
31860
  ...args5.removeName === void 0 ? [] : under(args5.removeName, existing).map((row) => row.eventKey)
31845
31861
  ];
31846
- if (set.length === 0 && remove.length === 0) return null;
31847
- return { ...set.length > 0 ? { set } : {}, ...remove.length > 0 ? { remove } : {} };
31862
+ if (set.length === 0 && remove2.length === 0) return null;
31863
+ return { ...set.length > 0 ? { set } : {}, ...remove2.length > 0 ? { remove: remove2 } : {} };
31848
31864
  }
31849
31865
 
31850
31866
  // src/commands/analytics/conversionHints.ts
@@ -31907,9 +31923,9 @@ function mergeStoredWithCounts(stored, reported) {
31907
31923
  const counts = new Map(reported.map((row) => [row.name, row]));
31908
31924
  const groups = /* @__PURE__ */ new Map();
31909
31925
  for (const row of stored) {
31910
- const group = groups.get(row.name) ?? [];
31911
- group.push(row);
31912
- groups.set(row.name, group);
31926
+ const group2 = groups.get(row.name) ?? [];
31927
+ group2.push(row);
31928
+ groups.set(row.name, group2);
31913
31929
  }
31914
31930
  return [...groups.entries()].map(([name, rows]) => {
31915
31931
  const modes = new Set(rows.map((row) => row.countMode));
@@ -32709,7 +32725,7 @@ import { defineCommand as defineCommand97 } from "citty";
32709
32725
  var SET_FLAGS = ["--source-event", "--platform", "--tag", "--event"];
32710
32726
  function parseSendsArgs(raw, argv, now = Date.now()) {
32711
32727
  const set = parseSetFlags(raw, argv, now);
32712
- const remove = onlyOnce(raw.remove, "--remove", argv);
32728
+ const remove2 = onlyOnce(raw.remove, "--remove", argv);
32713
32729
  const days2 = raw.days === void 0 ? void 0 : Number(raw.days);
32714
32730
  if (days2 !== void 0 && (!Number.isInteger(days2) || days2 <= 0 || days2 > 400)) {
32715
32731
  throw new ConversionArgsError("--days must be a whole number of days, from 1 to 400.");
@@ -32725,7 +32741,7 @@ function parseSendsArgs(raw, argv, now = Date.now()) {
32725
32741
  }
32726
32742
  return {
32727
32743
  ...set === void 0 ? {} : { set },
32728
- ...remove === void 0 ? {} : { remove },
32744
+ ...remove2 === void 0 ? {} : { remove: remove2 },
32729
32745
  ...days2 === void 0 ? {} : { days: days2 },
32730
32746
  ...uploads === void 0 ? {} : { uploads },
32731
32747
  ...raw.failed === true ? { failedOnly: true } : {},
@@ -34077,7 +34093,7 @@ var eventsCommand = presetCommand({
34077
34093
  }
34078
34094
  },
34079
34095
  resolve: (args5) => {
34080
- const steps = args5.steps ? String(args5.steps).split(",").map((step) => step.trim()).filter((step) => step !== "") : void 0;
34096
+ const steps = args5.steps ? String(args5.steps).split(",").map((step2) => step2.trim()).filter((step2) => step2 !== "") : void 0;
34081
34097
  if (args5.property && !args5.event) {
34082
34098
  return {
34083
34099
  error: "Breaking an event down by a property needs --event too, e.g. --event video --property milestone"
@@ -35874,14 +35890,14 @@ function undisambiguatedSubsets(faces) {
35874
35890
  }
35875
35891
  function suggestFamilies(wanted, catalogue, limit2 = 5) {
35876
35892
  const key = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
35877
- const target2 = key(wanted);
35878
- if (!target2) return [];
35879
- const words2 = target2.split(" ");
35893
+ const target4 = key(wanted);
35894
+ if (!target4) return [];
35895
+ const words2 = target4.split(" ");
35880
35896
  const scored = catalogue.map((family) => {
35881
35897
  const candidate = key(family);
35882
- if (candidate === target2) return { family, score: 0 };
35883
- if (candidate.startsWith(target2)) return { family, score: 1 };
35884
- if (candidate.includes(target2)) return { family, score: 2 };
35898
+ if (candidate === target4) return { family, score: 0 };
35899
+ if (candidate.startsWith(target4)) return { family, score: 1 };
35900
+ if (candidate.includes(target4)) return { family, score: 2 };
35885
35901
  const shared = words2.filter((w) => w.length > 2 && candidate.includes(w)).length;
35886
35902
  return { family, score: shared > 0 ? 3 + (words2.length - shared) : Number.POSITIVE_INFINITY };
35887
35903
  }).filter((c) => Number.isFinite(c.score)).sort((a, b) => a.score - b.score || a.family.localeCompare(b.family));
@@ -35942,8 +35958,8 @@ async function resolveBrandFaces(family, weights, subsets, deps, styles = ["norm
35942
35958
  function rebaseFontFaceSrc(block2, fromDir, toDir = "src/styles") {
35943
35959
  return block2.replace(/url\(\s*(['"]?)([^'")]+)\1\s*\)/gi, (whole, _quote, href) => {
35944
35960
  if (!isRepoFontHref(href)) return whole;
35945
- const target2 = posix.normalize(posix.join(fromDir, href));
35946
- return `url('${posix.relative(toDir, target2)}')`;
35961
+ const target4 = posix.normalize(posix.join(fromDir, href));
35962
+ return `url('${posix.relative(toDir, target4)}')`;
35947
35963
  });
35948
35964
  }
35949
35965
  function declaredFontFaces(source) {
@@ -36121,12 +36137,12 @@ function declaredWeights(body) {
36121
36137
  return range;
36122
36138
  }
36123
36139
  function selfHostedWeights(css, family) {
36124
- const target2 = normalizeFamily(family);
36140
+ const target4 = normalizeFamily(family);
36125
36141
  const weights = /* @__PURE__ */ new Set();
36126
36142
  for (const rule of stripCssComments(css).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
36127
36143
  const body = rule[1] ?? "";
36128
36144
  const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
36129
- if (!declared || normalizeFamily(declared) !== target2) continue;
36145
+ if (!declared || normalizeFamily(declared) !== target4) continue;
36130
36146
  for (const weight of declaredWeights(body)) weights.add(weight);
36131
36147
  }
36132
36148
  return [...weights].sort((a, b) => a - b);
@@ -36136,12 +36152,12 @@ function fontFileSubset(href) {
36136
36152
  return /^[A-Za-z0-9]+-[1-9]00(?:italic)?-([\w-]+)\.woff2$/.exec(file)?.[1] ?? null;
36137
36153
  }
36138
36154
  function selfHostedSubsets(css, family) {
36139
- const target2 = normalizeFamily(family);
36155
+ const target4 = normalizeFamily(family);
36140
36156
  const subsets = /* @__PURE__ */ new Set();
36141
36157
  for (const rule of stripCssComments(css).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
36142
36158
  const body = rule[1] ?? "";
36143
36159
  const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
36144
- if (!declared || normalizeFamily(declared) !== target2) continue;
36160
+ if (!declared || normalizeFamily(declared) !== target4) continue;
36145
36161
  for (const url of body.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/gi)) {
36146
36162
  const subset = fontFileSubset(url[1] ?? "");
36147
36163
  if (subset) subsets.add(subset);
@@ -36150,12 +36166,12 @@ function selfHostedSubsets(css, family) {
36150
36166
  return [...subsets].sort();
36151
36167
  }
36152
36168
  function selfHostedStyles(css, family) {
36153
- const target2 = normalizeFamily(family);
36169
+ const target4 = normalizeFamily(family);
36154
36170
  const styles = /* @__PURE__ */ new Set();
36155
36171
  for (const rule of stripCssComments(css).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
36156
36172
  const body = rule[1] ?? "";
36157
36173
  const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
36158
- if (declared && normalizeFamily(declared) === target2) styles.add(faceStyle(body));
36174
+ if (declared && normalizeFamily(declared) === target4) styles.add(faceStyle(body));
36159
36175
  }
36160
36176
  return [...styles].sort();
36161
36177
  }
@@ -36179,12 +36195,12 @@ function unresolvedBrandFamilies(css) {
36179
36195
  return declared.filter((family) => !loaded.has(family)).map((family) => asAuthored(css, family));
36180
36196
  }
36181
36197
  function stripFontFaceBlocks(source, family, styles) {
36182
- const target2 = normalizeFamily(family);
36198
+ const target4 = normalizeFamily(family);
36183
36199
  const ranges = [];
36184
36200
  for (const rule of stripCssComments(source).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
36185
36201
  const body = rule[1] ?? "";
36186
36202
  const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
36187
- if (!declared || normalizeFamily(declared) !== target2 || rule.index === void 0) continue;
36203
+ if (!declared || normalizeFamily(declared) !== target4 || rule.index === void 0) continue;
36188
36204
  if (styles && !styles.has(faceStyle(body))) continue;
36189
36205
  ranges.push([rule.index, rule.index + rule[0].length]);
36190
36206
  }
@@ -36778,11 +36794,11 @@ function scoreRetentionRisk(videoNodes) {
36778
36794
  };
36779
36795
  }
36780
36796
  function scoreMechanismClarity(allPromptText) {
36781
- const clear = MECHANISM_CUE.test(allPromptText);
36797
+ const clear2 = MECHANISM_CUE.test(allPromptText);
36782
36798
  return {
36783
36799
  dimension: "mechanism_clarity",
36784
- score: clear ? 1 : 0.4,
36785
- note: clear ? "states a mechanism/proof" : "no clear 'why it works' \u2014 add the mechanism"
36800
+ score: clear2 ? 1 : 0.4,
36801
+ note: clear2 ? "states a mechanism/proof" : "no clear 'why it works' \u2014 add the mechanism"
36786
36802
  };
36787
36803
  }
36788
36804
  function clamp01(n) {
@@ -36795,8 +36811,8 @@ function staticCopyLines(nodes) {
36795
36811
  return specimenText2.split("\n").map((s) => s.trim()).filter(Boolean);
36796
36812
  }
36797
36813
  const gen = nodes.find((n) => n.type === "image_generate");
36798
- const quoted = promptOf(gen).match(/"[^"]+"/g);
36799
- return quoted ? quoted.map((s) => s.slice(1, -1)) : [];
36814
+ const quoted2 = promptOf(gen).match(/"[^"]+"/g);
36815
+ return quoted2 ? quoted2.map((s) => s.slice(1, -1)) : [];
36800
36816
  }
36801
36817
  function scoreTextLegibilityRisk(nodes) {
36802
36818
  const lines = staticCopyLines(nodes).length;
@@ -36945,8 +36961,8 @@ function canvasOutputsRoot(canvasFile, cwd = process.cwd(), exists2 = existsSync
36945
36961
  const root = findWorkspaceRoot(path5.dirname(path5.resolve(cwd, canvasFile)), exists2) ?? cwd;
36946
36962
  return path5.join(root, "canvas");
36947
36963
  }
36948
- function canonicalize(target2) {
36949
- const abs = path5.resolve(target2);
36964
+ function canonicalize(target4) {
36965
+ const abs = path5.resolve(target4);
36950
36966
  let dir = abs;
36951
36967
  for (; ; ) {
36952
36968
  try {
@@ -36959,19 +36975,19 @@ function canonicalize(target2) {
36959
36975
  }
36960
36976
  }
36961
36977
  }
36962
- function isInside(root, target2) {
36963
- const rel = path5.relative(root, target2);
36978
+ function isInside(root, target4) {
36979
+ const rel = path5.relative(root, target4);
36964
36980
  return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
36965
36981
  }
36966
- function toCanvasRelative(canvasDir, target2) {
36967
- return path5.relative(canvasDir, target2).split(path5.sep).join("/");
36982
+ function toCanvasRelative(canvasDir, target4) {
36983
+ return path5.relative(canvasDir, target4).split(path5.sep).join("/");
36968
36984
  }
36969
36985
  var RUNTIME_WORKSPACE_ROOT = "/home/user/repo";
36970
36986
  function rewriteTarget(value, workspaceRoot) {
36971
36987
  if (typeof value !== "string" || value.length === 0) return null;
36972
36988
  if (value.includes("[TODO") || looksLikeHttpUrl(value) || !path5.isAbsolute(value)) return null;
36973
- const target2 = canonicalize(value);
36974
- if (isInside(workspaceRoot, target2)) return target2;
36989
+ const target4 = canonicalize(value);
36990
+ if (isInside(workspaceRoot, target4)) return target4;
36975
36991
  const fromRuntime = path5.relative(RUNTIME_WORKSPACE_ROOT, path5.normalize(value));
36976
36992
  if (fromRuntime !== "" && !fromRuntime.startsWith("..") && !path5.isAbsolute(fromRuntime)) {
36977
36993
  return path5.join(workspaceRoot, fromRuntime);
@@ -36997,9 +37013,9 @@ function normalizeCanvasPaths(canvas, canvasDir, workspaceRoot) {
36997
37013
  }
36998
37014
  const key = localPathParam(n);
36999
37015
  if (!key) return node;
37000
- const target2 = rewriteTarget(params2[key], root);
37001
- if (target2 === null) return node;
37002
- const to = toCanvasRelative(dir, target2);
37016
+ const target4 = rewriteTarget(params2[key], root);
37017
+ if (target4 === null) return node;
37018
+ const to = toCanvasRelative(dir, target4);
37003
37019
  rewrites.push({ nodeId, param: key, from: params2[key], to });
37004
37020
  return { ...node, params: { ...params2, [key]: to } };
37005
37021
  });
@@ -41302,8 +41318,8 @@ function mimeForFile(filePath) {
41302
41318
  function toPosix(p) {
41303
41319
  return p.split(path14.sep).join("/");
41304
41320
  }
41305
- function isInside2(dir, target2) {
41306
- const rel = path14.relative(dir, target2);
41321
+ function isInside2(dir, target4) {
41322
+ const rel = path14.relative(dir, target4);
41307
41323
  return !rel.startsWith("..") && !path14.isAbsolute(rel);
41308
41324
  }
41309
41325
  function localPathRefsFromCanvas(parsed) {
@@ -41419,11 +41435,11 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
41419
41435
  if (path14.isAbsolute(entry.path) || entry.path.split("/").includes("..")) {
41420
41436
  throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
41421
41437
  }
41422
- const target2 = path14.resolve(resolvedTarget, entry.path);
41423
- if (target2 !== resolvedTarget && !target2.startsWith(resolvedTarget + path14.sep)) {
41438
+ const target4 = path14.resolve(resolvedTarget, entry.path);
41439
+ if (target4 !== resolvedTarget && !target4.startsWith(resolvedTarget + path14.sep)) {
41424
41440
  throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
41425
41441
  }
41426
- const existing = await readFile10(target2).catch(() => null);
41442
+ const existing = await readFile10(target4).catch(() => null);
41427
41443
  if (existing) {
41428
41444
  if (sha256Hex(existing) === entry.sha256) {
41429
41445
  upToDate.push(entry.path);
@@ -41434,19 +41450,19 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
41434
41450
  continue;
41435
41451
  }
41436
41452
  }
41437
- planned.push({ entry, target: target2 });
41453
+ planned.push({ entry, target: target4 });
41438
41454
  }
41439
41455
  if (conflicts.length > 0) throw new SnapshotConflictError(conflicts);
41440
41456
  const restored = [];
41441
- for (const { entry, target: target2 } of planned) {
41457
+ for (const { entry, target: target4 } of planned) {
41442
41458
  const res = await fetch(entry.url);
41443
41459
  if (!res.ok) throw new Error(`snapshot download failed for ${entry.path}: ${res.status} ${res.statusText}`);
41444
41460
  const bytes = Buffer.from(await res.arrayBuffer());
41445
41461
  if (sha256Hex(bytes) !== entry.sha256) {
41446
41462
  throw new Error(`snapshot download for ${entry.path} does not match its recorded sha256`);
41447
41463
  }
41448
- await mkdir5(path14.dirname(target2), { recursive: true });
41449
- await writeFile7(target2, bytes);
41464
+ await mkdir5(path14.dirname(target4), { recursive: true });
41465
+ await writeFile7(target4, bytes);
41450
41466
  restored.push(entry.path);
41451
41467
  }
41452
41468
  return { canvasPath: path14.resolve(resolvedTarget, manifest.canvas.path), restored, upToDate };
@@ -41481,8 +41497,8 @@ function sceneAt(windows, atS) {
41481
41497
  function reviewFrameTimes(durationS, frames) {
41482
41498
  const n = Math.max(2, frames);
41483
41499
  const last = Math.max(0, durationS - 0.15);
41484
- const step = last / (n - 1);
41485
- return Array.from({ length: n }, (_, i) => Math.round(i * step * 100) / 100);
41500
+ const step2 = last / (n - 1);
41501
+ return Array.from({ length: n }, (_, i) => Math.round(i * step2 * 100) / 100);
41486
41502
  }
41487
41503
  function buildFrameGrabArgs(video, atS, out) {
41488
41504
  return [
@@ -43116,9 +43132,9 @@ async function resolveCastAvatars(flag3) {
43116
43132
  }
43117
43133
 
43118
43134
  // src/commands/canvas/scaffold-static-ad.ts
43119
- async function fileExists(target2) {
43135
+ async function fileExists(target4) {
43120
43136
  try {
43121
- await access(target2);
43137
+ await access(target4);
43122
43138
  return true;
43123
43139
  } catch {
43124
43140
  return false;
@@ -43844,8 +43860,8 @@ function beatSeconds(line, videoModel) {
43844
43860
  }
43845
43861
  var BRAND_PLATE = "A solid flat brand colour plate \u2014 an end card. No people, no objects, no scenery, no text of any kind.";
43846
43862
  var ASKS_FOR_LEGIBLE = /\b(focus on the (number|amount|total|price|figure|text)|readable|legible|clearly (shows?|visible)|highlighted (number|amount|total)|showing the (number|amount|total|price))\b/i;
43847
- function briefWantsLegibleText(show) {
43848
- return ASKS_FOR_LEGIBLE.test(show);
43863
+ function briefWantsLegibleText(show2) {
43864
+ return ASKS_FOR_LEGIBLE.test(show2);
43849
43865
  }
43850
43866
  var SELF_INTRODUCTION = /\b([Ss]oy|[Mm]e llamo|[Mm]i nombre es|[Ss]ou|[Jj]e m'appelle|[Jj]e suis|[Mm]i chiamo|[Ss]ono|[Ii]ch bin|[Ii]'m|[Ii] am|[Mm]y name is)\s+[A-ZÁÉÍÓÚÑ]/;
43851
43867
  function lineIntroducesSpeaker(line) {
@@ -44520,7 +44536,7 @@ var scaffoldAdCommand = defineCommand115({
44520
44536
  if (flagAvatar) {
44521
44537
  spec.data.cast = { ...spec.data.cast, avatar: flagAvatar };
44522
44538
  }
44523
- const contradicts = spec.data.beats.map((beat, i) => ({ i, show: beat.show })).filter(({ show }) => briefWantsLegibleText(show));
44539
+ const contradicts = spec.data.beats.map((beat, i) => ({ i, show: beat.show })).filter(({ show: show2 }) => briefWantsLegibleText(show2));
44524
44540
  if (contradicts.length > 0) {
44525
44541
  writeJson({
44526
44542
  ok: false,
@@ -45204,8 +45220,8 @@ function referenceVideoExt(url, contentType) {
45204
45220
  const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase();
45205
45221
  return mime && VIDEO_EXT_BY_MIME[mime] || ".mp4";
45206
45222
  }
45207
- async function fileExists2(target2) {
45208
- return access2(target2).then(
45223
+ async function fileExists2(target4) {
45224
+ return access2(target4).then(
45209
45225
  () => true,
45210
45226
  () => false
45211
45227
  );
@@ -46523,8 +46539,8 @@ async function importedFiles(text2, file, root) {
46523
46539
  for (const match of text2.matchAll(re2)) {
46524
46540
  const spec = match[1];
46525
46541
  if (spec === void 0) continue;
46526
- const target2 = await resolveSpecifier(spec, file, root);
46527
- if (target2 !== null) out.push(target2);
46542
+ const target4 = await resolveSpecifier(spec, file, root);
46543
+ if (target4 !== null) out.push(target4);
46528
46544
  }
46529
46545
  }
46530
46546
  return out;
@@ -46542,8 +46558,8 @@ async function landingGraphFiles(root, slug) {
46542
46558
  continue;
46543
46559
  }
46544
46560
  seen.add(file);
46545
- for (const target2 of await importedFiles(text2, file, root)) {
46546
- if (!seen.has(target2)) queue.push(target2);
46561
+ for (const target4 of await importedFiles(text2, file, root)) {
46562
+ if (!seen.has(target4)) queue.push(target4);
46547
46563
  }
46548
46564
  }
46549
46565
  return new Set([...seen].map((abs) => path29.relative(root, abs).split(path29.sep).join("/")));
@@ -46666,8 +46682,8 @@ function repointMoved(text2, opts) {
46666
46682
  return text2.replace(QUOTED_RELATIVE, (_match, quote, spec) => {
46667
46683
  const abs = path31.resolve(opts.fromDir, spec);
46668
46684
  const within = path31.relative(opts.movedFrom, abs);
46669
- const target2 = within.startsWith("..") || path31.isAbsolute(within) ? abs : path31.resolve(opts.movedTo, within);
46670
- return `${quote}${toSpecifier(opts.toDir, target2)}${quote}`;
46685
+ const target4 = within.startsWith("..") || path31.isAbsolute(within) ? abs : path31.resolve(opts.movedTo, within);
46686
+ return `${quote}${toSpecifier(opts.toDir, target4)}${quote}`;
46671
46687
  });
46672
46688
  }
46673
46689
 
@@ -46743,8 +46759,8 @@ function repointIntoArchive(text2, sourceAbs, destAbs, archivedBySource) {
46743
46759
  const toDir = path32.dirname(destAbs);
46744
46760
  return text2.replace(QUOTED_RELATIVE2, (match, quote, spec, offset) => {
46745
46761
  const [specPath, suffix] = splitSuffix(spec);
46746
- const target2 = path32.resolve(fromDir, specPath);
46747
- const resolved = resolveInArchive(target2, archivedBySource) ?? resolveOnDisk(target2, isImportPosition(text2, offset));
46762
+ const target4 = path32.resolve(fromDir, specPath);
46763
+ const resolved = resolveInArchive(target4, archivedBySource) ?? resolveOnDisk(target4, isImportPosition(text2, offset));
46748
46764
  if (resolved === null) return match;
46749
46765
  const rel = posix2(path32.relative(toDir, resolved));
46750
46766
  return `${quote}${rel.startsWith(".") ? rel : `./${rel}`}${suffix}${quote}`;
@@ -46802,63 +46818,63 @@ async function promoteVariantFiles(opts) {
46802
46818
  }
46803
46819
  return written;
46804
46820
  }
46805
- function archiveRecord(target2, variantNumber) {
46821
+ function archiveRecord(target4, variantNumber) {
46806
46822
  return `${JSON.stringify(
46807
46823
  {
46808
- experimentId: target2.experimentId,
46809
- page: target2.landingSlug,
46810
- pageInternalId: target2.landingInternalId,
46811
- variant: target2.variantSlug,
46824
+ experimentId: target4.experimentId,
46825
+ page: target4.landingSlug,
46826
+ pageInternalId: target4.landingInternalId,
46827
+ variant: target4.variantSlug,
46812
46828
  variantNumber,
46813
46829
  // Which arm this snapshot is. On a control win the variant lost; on a
46814
46830
  // variant win what lost is the page's own previous content, which never
46815
46831
  // had a number of its own — so it is filed under the test that rejected
46816
46832
  // it, which is the only number that means anything about it.
46817
- arm: target2.survivor === "variant" ? "page-before-this-test" : "variant"
46833
+ arm: target4.survivor === "variant" ? "page-before-this-test" : "variant"
46818
46834
  },
46819
46835
  null,
46820
46836
  2
46821
46837
  )}
46822
46838
  `;
46823
46839
  }
46824
- async function foldExperiment(root, target2) {
46825
- const pageDir = path32.resolve(root, "src/pages", target2.landingSlug);
46826
- const variantDir = path32.resolve(root, "src/pages", target2.variantSlug);
46827
- const variantNumber = variantNumberOf(target2.variantSlug);
46840
+ async function foldExperiment(root, target4) {
46841
+ const pageDir = path32.resolve(root, "src/pages", target4.landingSlug);
46842
+ const variantDir = path32.resolve(root, "src/pages", target4.variantSlug);
46843
+ const variantNumber = variantNumberOf(target4.variantSlug);
46828
46844
  if (variantNumber === null) {
46829
46845
  return {
46830
- error: `\u201C${target2.variantSlug}\u201D is not a variant of \u201C${target2.landingSlug}\u201D, so there is nothing to fold`
46846
+ error: `\u201C${target4.variantSlug}\u201D is not a variant of \u201C${target4.landingSlug}\u201D, so there is nothing to fold`
46831
46847
  };
46832
46848
  }
46833
46849
  if (!await isDir(pageDir)) {
46834
- return { error: `There is no page at src/pages/${target2.landingSlug}/` };
46850
+ return { error: `There is no page at src/pages/${target4.landingSlug}/` };
46835
46851
  }
46836
46852
  if (!await isDir(variantDir)) {
46837
- return { error: `src/pages/${target2.variantSlug}/ is already gone \u2014 this test looks folded in already` };
46853
+ return { error: `src/pages/${target4.variantSlug}/ is already gone \u2014 this test looks folded in already` };
46838
46854
  }
46839
- const archiveKey = target2.landingInternalId ?? target2.landingSlug;
46855
+ const archiveKey = target4.landingInternalId ?? target4.landingSlug;
46840
46856
  const archiveDir = path32.resolve(root, ARCHIVE_ROOT, archiveKey, String(variantNumber));
46841
46857
  await rm8(archiveDir, { recursive: true, force: true });
46842
46858
  await mkdir9(archiveDir, { recursive: true });
46843
46859
  const written = await archiveArm({
46844
46860
  root,
46845
- armDir: target2.survivor === "variant" ? pageDir : variantDir,
46861
+ armDir: target4.survivor === "variant" ? pageDir : variantDir,
46846
46862
  pageDir,
46847
46863
  archiveDir,
46848
- armSlug: target2.survivor === "variant" ? target2.landingSlug : target2.variantSlug
46864
+ armSlug: target4.survivor === "variant" ? target4.landingSlug : target4.variantSlug
46849
46865
  });
46850
- await writeFile12(path32.join(archiveDir, "_variant.json"), archiveRecord(target2, variantNumber), "utf8");
46866
+ await writeFile12(path32.join(archiveDir, "_variant.json"), archiveRecord(target4, variantNumber), "utf8");
46851
46867
  written.push(posix2(path32.join(ARCHIVE_ROOT, archiveKey, String(variantNumber), "_variant.json")));
46852
- if (target2.survivor === "variant") {
46868
+ if (target4.survivor === "variant") {
46853
46869
  written.push(...await promoteVariantFiles({ root, pageDir, variantDir }));
46854
46870
  }
46855
46871
  await rm8(variantDir, { recursive: true, force: true });
46856
46872
  return {
46857
- experimentId: target2.experimentId,
46858
- page: target2.landingSlug,
46873
+ experimentId: target4.experimentId,
46874
+ page: target4.landingSlug,
46859
46875
  archivedTo: posix2(path32.join(ARCHIVE_ROOT, archiveKey, String(variantNumber))),
46860
46876
  written,
46861
- removed: [`src/pages/${target2.variantSlug}`]
46877
+ removed: [`src/pages/${target4.variantSlug}`]
46862
46878
  };
46863
46879
  }
46864
46880
 
@@ -48362,9 +48378,9 @@ var foldCommand = defineCommand124({
48362
48378
  }
48363
48379
  const folded = [];
48364
48380
  const failures = [];
48365
- for (const target2 of owed) {
48366
- const result = await foldExperiment(process.cwd(), target2);
48367
- if ("error" in result) failures.push(`${target2.landingSlug}: ${result.error}`);
48381
+ for (const target4 of owed) {
48382
+ const result = await foldExperiment(process.cwd(), target4);
48383
+ if ("error" in result) failures.push(`${target4.landingSlug}: ${result.error}`);
48368
48384
  else folded.push(result);
48369
48385
  }
48370
48386
  if (folded.length > 0) {
@@ -48799,11 +48815,11 @@ function titleize(value) {
48799
48815
  return value.split(/[_-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
48800
48816
  }
48801
48817
  function resolveField(tree, ref) {
48802
- const { step, name } = splitRef(ref, "customForm");
48818
+ const { step: step2, name } = splitRef(ref, "customForm");
48803
48819
  const owners = fieldOwners(tree);
48804
48820
  const inventory = owners.map((owner2) => ` ${owner2.name} (${owner2.id}): ${owner2.fields.join(", ") || "(no fields)"}`).join("\n");
48805
48821
  const candidates = owners.filter(
48806
- (owner2) => owner2.fields.includes(name) && (!step || owner2.name === step || owner2.id === step)
48822
+ (owner2) => owner2.fields.includes(name) && (!step2 || owner2.name === step2 || owner2.id === step2)
48807
48823
  );
48808
48824
  if (candidates.length === 0) {
48809
48825
  throw new MappingError(`Form field "${ref}" does not exist in this form.`, inventory);
@@ -48842,14 +48858,14 @@ function resolveGlobal(tree, name) {
48842
48858
  return mention(id, id);
48843
48859
  }
48844
48860
  function resolveCode(tree, ref) {
48845
- const { step, name } = splitRef(ref, "codeExecution");
48861
+ const { step: step2, name } = splitRef(ref, "codeExecution");
48846
48862
  const owners = walkNodes(tree).flatMap((node) => {
48847
48863
  const exposed = externalOf(node).codeExecution?.exposedValues ?? [];
48848
48864
  const names = exposed.map((value) => value?.name).filter((value) => typeof value === "string");
48849
48865
  return names.length > 0 ? [{ id: String(node.id ?? ""), name: String(node.name ?? "step"), names }] : [];
48850
48866
  });
48851
48867
  const candidates = owners.filter(
48852
- (owner2) => owner2.names.includes(name) && (!step || owner2.name === step || owner2.id === step)
48868
+ (owner2) => owner2.names.includes(name) && (!step2 || owner2.name === step2 || owner2.id === step2)
48853
48869
  );
48854
48870
  if (candidates.length !== 1) {
48855
48871
  throw new MappingError(
@@ -49147,9 +49163,9 @@ function listPatch(shape, sideEffect, entries) {
49147
49163
  function existingEntries(shape, sideEffect) {
49148
49164
  const owner = shape.under ? sideEffect[shape.under] : sideEffect;
49149
49165
  if (!owner || typeof owner !== "object") return [];
49150
- const list = owner[shape.key];
49151
- if (!Array.isArray(list)) return [];
49152
- return list.filter((entry) => Boolean(entry) && typeof entry === "object");
49166
+ const list3 = owner[shape.key];
49167
+ if (!Array.isArray(list3)) return [];
49168
+ return list3.filter((entry) => Boolean(entry) && typeof entry === "object");
49153
49169
  }
49154
49170
  function mergeEntries(shape, existing, incoming) {
49155
49171
  const address = (entry) => {
@@ -49650,10 +49666,10 @@ function dropUnsetOptionals(sideEffect, path49) {
49650
49666
  return [{ path: path49, change: `dropped \`${key}\` (an optional string is absent, never null)` }];
49651
49667
  });
49652
49668
  }
49653
- function fillNulledArrays(target2, prefix, path49) {
49669
+ function fillNulledArrays(target4, prefix, path49) {
49654
49670
  return ARRAY_FIELDS.flatMap((key) => {
49655
- if (!(key in target2) || target2[key] !== null) return [];
49656
- target2[key] = [];
49671
+ if (!(key in target4) || target4[key] !== null) return [];
49672
+ target4[key] = [];
49657
49673
  return [{ path: path49, change: `\`${prefix}${key}: null\` \u2192 \`[]\`` }];
49658
49674
  });
49659
49675
  }
@@ -49679,9 +49695,9 @@ function sideEffectsOf(node) {
49679
49695
  function normalizeSideEffect(sideEffect, where) {
49680
49696
  const path49 = `${where} \u2192 ${String(sideEffect.id ?? "side effect")}`;
49681
49697
  const arrays = ARRAY_OWNERS.flatMap((owner) => {
49682
- const target2 = owner ? sideEffect[owner] : sideEffect;
49683
- if (!target2 || typeof target2 !== "object") return [];
49684
- return fillNulledArrays(target2, owner ? `${owner}.` : "", path49);
49698
+ const target4 = owner ? sideEffect[owner] : sideEffect;
49699
+ if (!target4 || typeof target4 !== "object") return [];
49700
+ return fillNulledArrays(target4, owner ? `${owner}.` : "", path49);
49685
49701
  });
49686
49702
  return [...dropUnsetOptionals(sideEffect, path49), ...arrays, ...tidyEmailRecipients(sideEffect, path49)];
49687
49703
  }
@@ -50074,7 +50090,7 @@ function formsNamedHere(forms) {
50074
50090
  }
50075
50091
  function unplacedFormHints(steps, forms, counted) {
50076
50092
  const onAStep = new Set(
50077
- steps.flatMap((step) => (step.triggers ?? []).flatMap((trigger) => trigger.eventKey ? [trigger.eventKey] : []))
50093
+ steps.flatMap((step2) => (step2.triggers ?? []).flatMap((trigger) => trigger.eventKey ? [trigger.eventKey] : []))
50078
50094
  );
50079
50095
  const unplaced = [];
50080
50096
  for (const form of forms) {
@@ -50552,12 +50568,12 @@ var FunnelArgsError = class extends Error {
50552
50568
  };
50553
50569
  function parseFunnelArgs(raw) {
50554
50570
  const steps = parseSteps(raw.steps);
50555
- const step = parseStep(raw.step);
50571
+ const step2 = parseStep(raw.step);
50556
50572
  const removeStep = parseRemoveStep(raw["remove-step"] ?? raw.removeStep);
50557
50573
  const preset = parsePreset(raw.preset);
50558
50574
  const addTrigger = parseWayIn(raw["add-trigger"] ?? raw.addTrigger, raw.event, "--add-trigger");
50559
50575
  const removeTrigger = parseWayIn(raw["remove-trigger"] ?? raw.removeTrigger, raw.event, "--remove-trigger");
50560
- const doors = [steps, step, removeStep, preset, addTrigger, removeTrigger].filter(
50576
+ const doors = [steps, step2, removeStep, preset, addTrigger, removeTrigger].filter(
50561
50577
  (door) => door !== void 0
50562
50578
  ).length;
50563
50579
  if (doors > 1) {
@@ -50571,7 +50587,7 @@ function parseFunnelArgs(raw) {
50571
50587
  }
50572
50588
  return {
50573
50589
  ...steps === void 0 ? {} : { steps },
50574
- ...step === void 0 ? {} : { step },
50590
+ ...step2 === void 0 ? {} : { step: step2 },
50575
50591
  ...removeStep === void 0 ? {} : { removeStep },
50576
50592
  ...preset === void 0 ? {} : { preset },
50577
50593
  ...addTrigger === void 0 ? {} : { addTrigger },
@@ -50660,16 +50676,16 @@ function parseRemoveStep(raw) {
50660
50676
  return raw.trim();
50661
50677
  }
50662
50678
  function refuseRetiredShapes(steps) {
50663
- for (const step of steps) {
50664
- if (typeof step !== "object" || step === null) continue;
50665
- const key = String(step.key ?? "?");
50666
- if ("offlineConversions" in step) {
50679
+ for (const step2 of steps) {
50680
+ if (typeof step2 !== "object" || step2 === null) continue;
50681
+ const key = String(step2.key ?? "?");
50682
+ if ("offlineConversions" in step2) {
50667
50683
  throw new FunnelArgsError(`Step "${key}" puts sends on the step, and a send belongs to the conversion.`, {
50668
50684
  action: "set_sends_on_the_conversion",
50669
50685
  explanation: "Drop `offlineConversions`. Name the outcome with `baker analytics conversions`, then `baker analytics sends --set --conversion <name> --platform <p> --tag <tagId> --event <e>`; the step reads its conversions' sends back as `sends[]`."
50670
50686
  });
50671
50687
  }
50672
- const triggers = step.triggers;
50688
+ const triggers = step2.triggers;
50673
50689
  if (!Array.isArray(triggers)) continue;
50674
50690
  const kinds = triggers.map(
50675
50691
  (trigger) => typeof trigger === "object" && trigger !== null ? trigger.kind : void 0
@@ -50871,13 +50887,13 @@ async function resolvePropertyId(args5) {
50871
50887
  if (properties.length === 0) {
50872
50888
  handleConnectionError("ga4");
50873
50889
  }
50874
- const list = properties.map((p) => ` ${p.externalId} ${p.name}`).join("\n");
50890
+ const list3 = properties.map((p) => ` ${p.externalId} ${p.name}`).join("\n");
50875
50891
  writeJsonEnvelope({
50876
50892
  ok: false,
50877
50893
  error: {
50878
50894
  code: "MULTIPLE_PROPERTIES",
50879
50895
  message: `Multiple properties found. Pass --property-id or set BAKER_GA4_PROPERTY_ID:
50880
- ${list}`
50896
+ ${list3}`
50881
50897
  }
50882
50898
  });
50883
50899
  process.exit(1);
@@ -50970,11 +50986,11 @@ function failValidation3(message) {
50970
50986
  }
50971
50987
  function requireTarget5(args5, entity) {
50972
50988
  const positional = Array.isArray(args5._) ? args5._[0] : void 0;
50973
- const target2 = args5.id ?? args5.target ?? positional;
50974
- if (typeof target2 !== "string" || target2.length === 0) {
50989
+ const target4 = args5.id ?? args5.target ?? positional;
50990
+ if (typeof target4 !== "string" || target4.length === 0) {
50975
50991
  failValidation3(`pass the ${entity} id as the positional argument`);
50976
50992
  }
50977
- return target2;
50993
+ return target4;
50978
50994
  }
50979
50995
  function readJsonSource(args5, flag3) {
50980
50996
  const inline = args5[flag3];
@@ -51968,11 +51984,11 @@ Examples:
51968
51984
  "property-id": propertyArg
51969
51985
  },
51970
51986
  run: async ({ args: args5 }) => {
51971
- const target2 = requireTarget5(args5, "audience id");
51987
+ const target4 = requireTarget5(args5, "audience id");
51972
51988
  const payload = loadJsonArg(args5);
51973
51989
  await stageOp3({
51974
51990
  kind: "ga4.audience.update",
51975
- target: target2,
51991
+ target: target4,
51976
51992
  payload,
51977
51993
  ...withProperty(args5)
51978
51994
  });
@@ -52101,13 +52117,13 @@ Examples:
52101
52117
  "property-id": propertyArg
52102
52118
  },
52103
52119
  run: async ({ args: args5 }) => {
52104
- const target2 = requireTarget5(args5, "Google Ads link id");
52120
+ const target4 = requireTarget5(args5, "Google Ads link id");
52105
52121
  if (typeof args5["ads-personalization"] !== "string") {
52106
52122
  failValidation3("pass --ads-personalization true|false \u2014 it is the only thing a link update can change");
52107
52123
  }
52108
52124
  await stageOp3({
52109
52125
  kind: "ga4.googleAdsLink.update",
52110
- target: target2,
52126
+ target: target4,
52111
52127
  payload: { adsPersonalizationEnabled: booleanArg(args5["ads-personalization"], "ads-personalization") },
52112
52128
  ...withProperty(args5)
52113
52129
  });
@@ -52342,13 +52358,13 @@ async function resolveSiteUrl(args5) {
52342
52358
  if (sites.length === 0) {
52343
52359
  handleConnectionError("gsc");
52344
52360
  }
52345
- const list = sites.map((s) => ` ${s.siteUrl} (${s.permissionLevel})`).join("\n");
52361
+ const list3 = sites.map((s) => ` ${s.siteUrl} (${s.permissionLevel})`).join("\n");
52346
52362
  writeJsonEnvelope({
52347
52363
  ok: false,
52348
52364
  error: {
52349
52365
  code: "MULTIPLE_SITES",
52350
52366
  message: `Multiple sites found. Pass --site-url or set BAKER_GSC_SITE_URL:
52351
- ${list}`
52367
+ ${list3}`
52352
52368
  }
52353
52369
  });
52354
52370
  process.exit(1);
@@ -53241,7 +53257,7 @@ function workflowViewHints(workflow) {
53241
53257
  "This workflow is switched off, so nothing below it is running now \u2014 it may still have run while it was on. Say so before reporting on what it does."
53242
53258
  );
53243
53259
  }
53244
- const ordered = workflow.steps.filter((step) => step.type === "LIST_BRANCH" && (step.branches?.length ?? 0) > 1);
53260
+ const ordered = workflow.steps.filter((step2) => step2.type === "LIST_BRANCH" && (step2.branches?.length ?? 0) > 1);
53245
53261
  if (ordered.length > 0) {
53246
53262
  hints2.push(
53247
53263
  "Branches are listed in evaluation order and the first match wins. Check that no broad branch sits above a narrower one \u2014 if it does, the narrow branch never runs and nothing in HubSpot reports it."
@@ -53338,7 +53354,7 @@ function propertiesCommand2(object) {
53338
53354
  object
53339
53355
  });
53340
53356
  const { properties } = response.data;
53341
- const operators = Object.entries(HUBSPOT_OPERATORS).map(([kind, list]) => `${kind}: ${list.map((operator) => operator.value).join(", ")}`).join(" \xB7 ");
53357
+ const operators = Object.entries(HUBSPOT_OPERATORS).map(([kind, list3]) => `${kind}: ${list3.map((operator) => operator.value).join(", ")}`).join(" \xB7 ");
53342
53358
  const objectFlag = object === "contact" ? "" : ` --object ${object}`;
53343
53359
  const hints2 = properties.length === 0 ? [`This account has no visible ${object} property, so there is nothing an event could watch.`] : [
53344
53360
  `Pick the property and the condition that mean the outcome, then \`baker analytics sources --create-hubspot${objectFlag} --property <name> [--operator <op>] --value <value>\u2026\` (\`--value\` repeats for a list). Operators by kind \u2014 ${operators}. Counting starts from creation: a ${object} already satisfying it is not counted until it leaves and comes back.`
@@ -53922,17 +53938,17 @@ var dimensionsCommand = defineCommand147({
53922
53938
  },
53923
53939
  run: async ({ args: args5 }) => {
53924
53940
  try {
53925
- const target2 = args5.target;
53926
- if (!target2) {
53941
+ const target4 = args5.target;
53942
+ if (!target4) {
53927
53943
  writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "target argument is required" } });
53928
53944
  process.exit(1);
53929
53945
  }
53930
- const buffer = await readImageBuffer(target2);
53946
+ const buffer = await readImageBuffer(target4);
53931
53947
  const dims = getDimensionsFromBuffer(buffer);
53932
53948
  if (!dims) {
53933
53949
  writeJson({
53934
53950
  ok: false,
53935
- error: { code: "IMAGE_PROCESSING_ERROR", message: `Could not extract dimensions from ${target2}` }
53951
+ error: { code: "IMAGE_PROCESSING_ERROR", message: `Could not extract dimensions from ${target4}` }
53936
53952
  });
53937
53953
  process.exit(1);
53938
53954
  }
@@ -53940,8 +53956,8 @@ var dimensionsCommand = defineCommand147({
53940
53956
  {
53941
53957
  ok: true,
53942
53958
  data: {
53943
- target: target2,
53944
- source: isRemoteUrl(target2) ? "url" : "file",
53959
+ target: target4,
53960
+ source: isRemoteUrl(target4) ? "url" : "file",
53945
53961
  width: dims.width,
53946
53962
  height: dims.height,
53947
53963
  aspectRatio: Math.round(dims.aspectRatio * 1e4) / 1e4,
@@ -54070,15 +54086,15 @@ registerSchema({
54070
54086
  var ValidationError3 = class extends Error {
54071
54087
  };
54072
54088
  var UnusableTargetError = class extends Error {
54073
- constructor(target2) {
54074
- super(`"${target2}" is neither an image URL nor a library image id.`);
54075
- this.target = target2;
54089
+ constructor(target4) {
54090
+ super(`"${target4}" is neither an image URL nor a library image id.`);
54091
+ this.target = target4;
54076
54092
  }
54077
54093
  };
54078
54094
  function readTargets(args5) {
54079
- const positionals = Array.isArray(args5._) ? args5._ : [];
54095
+ const positionals2 = Array.isArray(args5._) ? args5._ : [];
54080
54096
  const named = typeof args5.targets === "string" ? [args5.targets] : [];
54081
- const all = positionals.length > 0 ? positionals : named;
54097
+ const all = positionals2.length > 0 ? positionals2 : named;
54082
54098
  const seen = /* @__PURE__ */ new Set();
54083
54099
  const out = [];
54084
54100
  for (const value of all) {
@@ -54103,13 +54119,13 @@ function classify(raw) {
54103
54119
  }
54104
54120
  return { raw, kind: "imageId" };
54105
54121
  }
54106
- async function resolveTarget(target2) {
54107
- if (target2.kind === "url") {
54108
- return { input: target2.raw, url: target2.raw, baseName: baseNameFromUrl(target2.raw) };
54122
+ async function resolveTarget(target4) {
54123
+ if (target4.kind === "url") {
54124
+ return { input: target4.raw, url: target4.raw, baseName: baseNameFromUrl(target4.raw) };
54109
54125
  }
54110
- const doc = await apiGet("/api/images/get", { id: target2.raw });
54111
- const stem = sanitizeBaseName(doc.name.length > 0 ? doc.name : target2.raw);
54112
- return { input: target2.raw, url: doc.imageUrl, baseName: stem };
54126
+ const doc = await apiGet("/api/images/get", { id: target4.raw });
54127
+ const stem = sanitizeBaseName(doc.name.length > 0 ? doc.name : target4.raw);
54128
+ return { input: target4.raw, url: doc.imageUrl, baseName: stem };
54113
54129
  }
54114
54130
  async function fetchBytes(url) {
54115
54131
  return await fetchExternalBytes(url, { maxBytes: MAX_REMOTE_IMAGE_BYTES });
@@ -54145,9 +54161,9 @@ function failureMessage(err, fallback) {
54145
54161
  async function runDownloads(plan) {
54146
54162
  const failed = [];
54147
54163
  const fetched = [];
54148
- for (const target2 of plan.targets) {
54164
+ for (const target4 of plan.targets) {
54149
54165
  try {
54150
- const resolved = await resolveTarget(target2);
54166
+ const resolved = await resolveTarget(target4);
54151
54167
  const { buffer, contentType } = await fetchBytes(resolved.url);
54152
54168
  fetched.push({
54153
54169
  input: resolved.input,
@@ -54161,7 +54177,7 @@ async function runDownloads(plan) {
54161
54177
  })
54162
54178
  });
54163
54179
  } catch (err) {
54164
- failed.push({ input: target2.raw, error: failureMessage(err, "Download failed") });
54180
+ failed.push({ input: target4.raw, error: failureMessage(err, "Download failed") });
54165
54181
  }
54166
54182
  }
54167
54183
  const paths = disambiguate(fetched.map((item) => item.path));
@@ -54382,7 +54398,7 @@ function providerHitNormalizer(record, full) {
54382
54398
  if (!full) {
54383
54399
  return compact3;
54384
54400
  }
54385
- const group = asRecord2(record.group);
54401
+ const group2 = asRecord2(record.group);
54386
54402
  return {
54387
54403
  ...compact3,
54388
54404
  externalUrl: str(record.externalUrl),
@@ -54394,7 +54410,7 @@ function providerHitNormalizer(record, full) {
54394
54410
  score: num(record.score),
54395
54411
  deduped: typeof record.deduped === "boolean" ? record.deduped : void 0,
54396
54412
  alsoInLibrary: str(record.alsoInLibrary),
54397
- groupKey: str(group.key)
54413
+ groupKey: str(group2.key)
54398
54414
  };
54399
54415
  }
54400
54416
  function readOutputFlags(args5) {
@@ -55732,18 +55748,18 @@ function findColorRangeInternal(data, ignoredColor = null, customThreshold) {
55732
55748
  const color = { r: data[i], g: data[i + 1], b: data[i + 2] };
55733
55749
  if (ignoredColor && colorDistance(color, ignoredColor) < threshold) continue;
55734
55750
  let found = false;
55735
- for (const group of groups) {
55736
- const distance = colorDistance(group.color, color);
55737
- const contrastRatio = getContrastRatio(group.color, color);
55751
+ for (const group2 of groups) {
55752
+ const distance = colorDistance(group2.color, color);
55753
+ const contrastRatio = getContrastRatio(group2.color, color);
55738
55754
  let groupingThreshold = threshold;
55739
55755
  if (contrastRatio > 3) groupingThreshold = Math.min(groupingThreshold, 8);
55740
- const hueDistance = getHueDistance(group.color, color);
55756
+ const hueDistance = getHueDistance(group2.color, color);
55741
55757
  if (hueDistance > 20) groupingThreshold = Math.min(groupingThreshold, 5);
55742
- if (isHighlySaturated(color) && isHighlySaturated(group.color)) {
55758
+ if (isHighlySaturated(color) && isHighlySaturated(group2.color)) {
55743
55759
  groupingThreshold = Math.min(groupingThreshold, 10);
55744
55760
  }
55745
55761
  if (distance < groupingThreshold) {
55746
- group.count++;
55762
+ group2.count++;
55747
55763
  found = true;
55748
55764
  break;
55749
55765
  }
@@ -55848,13 +55864,13 @@ function hasGradient(data) {
55848
55864
  function perceivedLuminance(color) {
55849
55865
  return (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) / 255;
55850
55866
  }
55851
- function createSvgRecolorer(target2, palette) {
55852
- if (palette.length < 2) return () => target2;
55867
+ function createSvgRecolorer(target4, palette) {
55868
+ if (palette.length < 2) return () => target4;
55853
55869
  const luminances = palette.map(perceivedLuminance);
55854
55870
  const min = Math.min(...luminances);
55855
55871
  const max = Math.max(...luminances);
55856
- if (max - min < 0.05) return () => target2;
55857
- const [h, s, targetLightness] = rgbToHsl(target2.r, target2.g, target2.b);
55872
+ if (max - min < 0.05) return () => target4;
55873
+ const [h, s, targetLightness] = rgbToHsl(target4.r, target4.g, target4.b);
55858
55874
  const lightnessRange = 0.75;
55859
55875
  const minLightness = targetLightness >= 0.5 ? Math.max(0, targetLightness - lightnessRange) : targetLightness;
55860
55876
  const maxLightness = targetLightness >= 0.5 ? targetLightness : Math.min(1, targetLightness + lightnessRange);
@@ -56848,18 +56864,18 @@ var uploadCommand = defineCommand167({
56848
56864
  },
56849
56865
  run: async ({ args: args5 }) => {
56850
56866
  try {
56851
- const target2 = args5.file;
56852
- if (!target2) {
56867
+ const target4 = args5.file;
56868
+ if (!target4) {
56853
56869
  writeJson({
56854
56870
  ok: false,
56855
56871
  error: { code: "VALIDATION_ERROR", message: "File path or URL is required" }
56856
56872
  });
56857
56873
  process.exit(1);
56858
56874
  }
56859
- if (isRemoteUrl2(target2)) {
56860
- await uploadRemote(target2, args5);
56875
+ if (isRemoteUrl2(target4)) {
56876
+ await uploadRemote(target4, args5);
56861
56877
  } else {
56862
- await uploadLocal(target2, args5);
56878
+ await uploadLocal(target4, args5);
56863
56879
  }
56864
56880
  } catch (err) {
56865
56881
  if (err instanceof ApiError) {
@@ -56871,7 +56887,7 @@ var uploadCommand = defineCommand167({
56871
56887
  }
56872
56888
  }
56873
56889
  });
56874
- async function uploadRemote(target2, args5) {
56890
+ async function uploadRemote(target4, args5) {
56875
56891
  const source = args5.source ?? "uploaded";
56876
56892
  if (args5["dry-run"]) {
56877
56893
  writeJson({
@@ -56879,7 +56895,7 @@ async function uploadRemote(target2, args5) {
56879
56895
  dryRun: true,
56880
56896
  operation: "images.upload (remote)",
56881
56897
  params: {
56882
- url: target2,
56898
+ url: target4,
56883
56899
  source,
56884
56900
  externalId: args5["external-id"] ?? null,
56885
56901
  externalUrl: args5["external-url"] ?? null,
@@ -56888,22 +56904,22 @@ async function uploadRemote(target2, args5) {
56888
56904
  });
56889
56905
  return;
56890
56906
  }
56891
- const body = { url: target2, source };
56907
+ const body = { url: target4, source };
56892
56908
  if (args5["external-id"]) body.externalId = args5["external-id"];
56893
56909
  if (args5["external-url"]) body.externalUrl = args5["external-url"];
56894
56910
  if (args5.context) body.descriptionContext = args5.context;
56895
56911
  const data = await apiPost("/api/images/ingest", body);
56896
56912
  writeJson({ ok: true, data });
56897
56913
  }
56898
- async function uploadLocal(target2, args5) {
56899
- const contentType = args5["content-type"] || detectImageContentType(target2);
56914
+ async function uploadLocal(target4, args5) {
56915
+ const contentType = args5["content-type"] || detectImageContentType(target4);
56900
56916
  if (args5["dry-run"]) {
56901
56917
  writeJson({
56902
56918
  ok: true,
56903
56919
  dryRun: true,
56904
56920
  operation: "images.upload (local)",
56905
56921
  params: {
56906
- file: target2,
56922
+ file: target4,
56907
56923
  contentType,
56908
56924
  source: args5.source ?? null,
56909
56925
  descriptionContext: args5.context ?? null
@@ -56912,7 +56928,7 @@ async function uploadLocal(target2, args5) {
56912
56928
  return;
56913
56929
  }
56914
56930
  const data = await uploadLocalImage({
56915
- file: target2,
56931
+ file: target4,
56916
56932
  contentType,
56917
56933
  source: args5.source,
56918
56934
  descriptionContext: args5.context
@@ -59201,7 +59217,7 @@ function classifyPreflightStatus(status) {
59201
59217
  async function preflightProxyRoute(route, options = {}) {
59202
59218
  if (route.kind !== "proxy") return { usable: true };
59203
59219
  const timeoutMs = options.timeoutMs ?? 8e3;
59204
- const target2 = options.target ?? "example.com:443";
59220
+ const target4 = options.target ?? "example.com:443";
59205
59221
  const endpoint = new URL(route.server);
59206
59222
  const auth = Buffer.from(`${route.username}:${route.password}`).toString("base64");
59207
59223
  return await new Promise((resolve5) => {
@@ -59213,8 +59229,8 @@ async function preflightProxyRoute(route, options = {}) {
59213
59229
  host: endpoint.hostname,
59214
59230
  port: Number(endpoint.port),
59215
59231
  method: "CONNECT",
59216
- path: target2,
59217
- headers: { "Proxy-Authorization": `Basic ${auth}`, Host: target2 },
59232
+ path: target4,
59233
+ headers: { "Proxy-Authorization": `Basic ${auth}`, Host: target4 },
59218
59234
  timeout: timeoutMs
59219
59235
  });
59220
59236
  request.on("connect", (response, socket) => {
@@ -60054,10 +60070,10 @@ async function scrollThroughPage(page) {
60054
60070
  await acrossNavigation(
60055
60071
  page,
60056
60072
  () => page.evaluate(async () => {
60057
- const step = 600;
60073
+ const step2 = 600;
60058
60074
  const pause = () => new Promise((resolve5) => setTimeout(resolve5, 250));
60059
60075
  for (let i = 0; i < 40; i++) {
60060
- window.scrollBy(0, step);
60076
+ window.scrollBy(0, step2);
60061
60077
  await pause();
60062
60078
  const reachedBottom = window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 2;
60063
60079
  if (reachedBottom) break;
@@ -60140,9 +60156,9 @@ function bandsCollide(a, b, margin, maxGap) {
60140
60156
  const gap = Math.min(Math.abs(shrunkA.bottom - shrunkB.top), Math.abs(shrunkB.bottom - shrunkA.top));
60141
60157
  return gap <= maxGap;
60142
60158
  }
60143
- function shouldPromoteToParent(group) {
60144
- const isRunt = group.childCount < 3 || group.height < 80;
60145
- return isRunt && group.parentHeight < 1600;
60159
+ function shouldPromoteToParent(group2) {
60160
+ const isRunt = group2.childCount < 3 || group2.height < 80;
60161
+ return isRunt && group2.parentHeight < 1600;
60146
60162
  }
60147
60163
 
60148
60164
  // src/engine/landing-library/segment.ts
@@ -60361,35 +60377,35 @@ var inPageSegment = (options) => {
60361
60377
  };
60362
60378
  const clusterOnce = (groups2) => {
60363
60379
  const buckets = [];
60364
- for (const group of groups2) {
60365
- if (group.sealed) {
60366
- buckets.push([group]);
60380
+ for (const group2 of groups2) {
60381
+ if (group2.sealed) {
60382
+ buckets.push([group2]);
60367
60383
  continue;
60368
60384
  }
60369
- const target2 = buckets.find(
60385
+ const target4 = buckets.find(
60370
60386
  (bucket) => bucket.some(
60371
60387
  (member) => !member.sealed && geom.bandsCollide(
60372
60388
  { top: member.top, bottom: member.bottom },
60373
- { top: group.top, bottom: group.bottom },
60389
+ { top: group2.top, bottom: group2.bottom },
60374
60390
  options.collisionMargin,
60375
60391
  gapThreshold
60376
60392
  )
60377
60393
  )
60378
60394
  );
60379
- if (target2) target2.push(group);
60380
- else buckets.push([group]);
60395
+ if (target4) target4.push(group2);
60396
+ else buckets.push([group2]);
60381
60397
  }
60382
60398
  if (buckets.length === groups2.length) return groups2;
60383
60399
  return buckets.flatMap((bucket) => bucket.length === 1 ? [bucket[0]] : mergeBucket(bucket));
60384
60400
  };
60385
- let groups = leaves.map((leaf) => ({
60386
- root: leaf.element,
60387
- top: leaf.top,
60388
- bottom: leaf.bottom,
60389
- left: leaf.left,
60390
- right: leaf.right,
60391
- height: leaf.height,
60392
- leaves: [leaf],
60401
+ let groups = leaves.map((leaf2) => ({
60402
+ root: leaf2.element,
60403
+ top: leaf2.top,
60404
+ bottom: leaf2.bottom,
60405
+ left: leaf2.left,
60406
+ right: leaf2.right,
60407
+ height: leaf2.height,
60408
+ leaves: [leaf2],
60393
60409
  sealed: false
60394
60410
  }));
60395
60411
  for (let pass = 0; pass < 40; pass++) {
@@ -60399,21 +60415,21 @@ var inPageSegment = (options) => {
60399
60415
  }
60400
60416
  for (let pass = 0; pass < 10; pass++) {
60401
60417
  let promoted = false;
60402
- groups = groups.map((group) => {
60403
- const parent = group.root.parentElement;
60404
- if (!parent || parent === document.documentElement || parent === document.body) return group;
60418
+ groups = groups.map((group2) => {
60419
+ const parent = group2.root.parentElement;
60420
+ if (!parent || parent === document.documentElement || parent === document.body) return group2;
60405
60421
  const parentBox = boxOf(parent);
60406
- if (!parentBox) return group;
60422
+ if (!parentBox) return group2;
60407
60423
  if (!geom.shouldPromoteToParent({
60408
- childCount: group.leaves.length,
60409
- height: group.height,
60424
+ childCount: group2.leaves.length,
60425
+ height: group2.height,
60410
60426
  parentHeight: parentBox.height
60411
60427
  })) {
60412
- return group;
60428
+ return group2;
60413
60429
  }
60414
60430
  promoted = true;
60415
60431
  return {
60416
- ...group,
60432
+ ...group2,
60417
60433
  root: parent,
60418
60434
  top: parentBox.top,
60419
60435
  bottom: parentBox.bottom,
@@ -60429,23 +60445,23 @@ var inPageSegment = (options) => {
60429
60445
  const containsBox = (outer, inner) => outer.top - 4 <= inner.top && outer.bottom + 4 >= inner.bottom && outer.left - 4 <= inner.left && outer.right + 4 >= inner.right;
60430
60446
  const deduped = [];
60431
60447
  const area = (g) => (g.right - g.left) * g.height;
60432
- for (const group of groups.slice().sort((a, b) => area(b) - area(a) || b.leaves.length - a.leaves.length)) {
60448
+ for (const group2 of groups.slice().sort((a, b) => area(b) - area(a) || b.leaves.length - a.leaves.length)) {
60433
60449
  const duplicate = deduped.some(
60434
- (kept) => kept.root === group.root || kept.root.contains(group.root) || sameBox(kept, group) || containsBox(kept, group)
60450
+ (kept) => kept.root === group2.root || kept.root.contains(group2.root) || sameBox(kept, group2) || containsBox(kept, group2)
60435
60451
  );
60436
- if (!duplicate) deduped.push(group);
60452
+ if (!duplicate) deduped.push(group2);
60437
60453
  }
60438
- return deduped.filter((group) => group.height > 0 && group.right - group.left > 0).sort((a, b) => a.top - b.top).map((group, index) => ({
60454
+ return deduped.filter((group2) => group2.height > 0 && group2.right - group2.left > 0).sort((a, b) => a.top - b.top).map((group2, index) => ({
60439
60455
  index,
60440
- selector: selectorFor(group.root),
60456
+ selector: selectorFor(group2.root),
60441
60457
  rect: {
60442
- top: group.top,
60443
- left: group.left,
60444
- width: group.right - group.left,
60445
- height: group.height
60458
+ top: group2.top,
60459
+ left: group2.left,
60460
+ width: group2.right - group2.left,
60461
+ height: group2.height
60446
60462
  },
60447
- leafCount: group.leaves.length,
60448
- textPreview: visibleText(group.root).slice(0, 200)
60463
+ leafCount: group2.leaves.length,
60464
+ textPreview: visibleText(group2.root).slice(0, 200)
60449
60465
  }));
60450
60466
  };
60451
60467
 
@@ -60472,8 +60488,8 @@ async function captureMotionTake(browser, url, selector, timeoutMs = 45e3) {
60472
60488
  await settleNavigation(page);
60473
60489
  await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => void 0);
60474
60490
  await settleConsent(page);
60475
- const target2 = page.locator(selector).first();
60476
- if (!await target2.count().catch(() => 0)) return null;
60491
+ const target4 = page.locator(selector).first();
60492
+ if (!await target4.count().catch(() => 0)) return null;
60477
60493
  await page.evaluate((sectionSelector) => {
60478
60494
  const element = document.querySelector(sectionSelector);
60479
60495
  if (!element) return;
@@ -60527,12 +60543,12 @@ async function aimAtSection(page, selector) {
60527
60543
  }
60528
60544
  async function hideFloatingFurniture(page, selector) {
60529
60545
  await page.evaluate((sectionSelector) => {
60530
- const target2 = document.querySelector(sectionSelector);
60531
- if (!target2) return;
60546
+ const target4 = document.querySelector(sectionSelector);
60547
+ if (!target4) return;
60532
60548
  for (const element of Array.from(document.querySelectorAll("*"))) {
60533
60549
  const position = getComputedStyle(element).position;
60534
60550
  if (position !== "fixed" && position !== "sticky") continue;
60535
- if (element.contains(target2) || target2.contains(element) || element === target2) continue;
60551
+ if (element.contains(target4) || target4.contains(element) || element === target4) continue;
60536
60552
  element.style.setProperty("visibility", "hidden", "important");
60537
60553
  }
60538
60554
  }, selector);
@@ -60589,9 +60605,9 @@ async function renderBundleToPng(browser, html, viewportWidth, options = {}) {
60589
60605
  await page.waitForTimeout(300);
60590
60606
  if (!wholePage) {
60591
60607
  for (const selector of [`[${SECTION_ROOT_ATTRIBUTE}]`, "body > *"]) {
60592
- const target2 = page.locator(selector).first();
60593
- if (!await target2.count()) continue;
60594
- const shot = await target2.screenshot({ type: "png", timeout: timeoutMs }).catch(() => null);
60608
+ const target4 = page.locator(selector).first();
60609
+ if (!await target4.count()) continue;
60610
+ const shot = await target4.screenshot({ type: "png", timeout: timeoutMs }).catch(() => null);
60595
60611
  if (shot) return shot;
60596
60612
  }
60597
60613
  }
@@ -61067,8 +61083,8 @@ async function scrapeLanding(options) {
61067
61083
  import { existsSync as existsSync9 } from "fs";
61068
61084
  import path41 from "path";
61069
61085
  var SCRATCH_DIR = ".baker";
61070
- function isWithin(parent, target2) {
61071
- const relative = path41.relative(parent, target2);
61086
+ function isWithin(parent, target4) {
61087
+ const relative = path41.relative(parent, target4);
61072
61088
  return relative === "" || !relative.startsWith("..") && !path41.isAbsolute(relative);
61073
61089
  }
61074
61090
  function findRepoRoot(from) {
@@ -61359,8 +61375,8 @@ registerSchema({
61359
61375
  function buildSearchBody(args5) {
61360
61376
  const body = {};
61361
61377
  const setList2 = (key, value) => {
61362
- const list = splitList2(value);
61363
- if (list) Object.assign(body, { [key]: list });
61378
+ const list3 = splitList2(value);
61379
+ if (list3) Object.assign(body, { [key]: list3 });
61364
61380
  };
61365
61381
  if (args5.query) body.query = String(args5.query);
61366
61382
  if (args5["similar-to"]) body.similarToSectionId = String(args5["similar-to"]);
@@ -62135,9 +62151,9 @@ function customEntry(server) {
62135
62151
  var BROKEN_SERVER_HINT = "A server marked usable=false will NOT answer: its tools are absent from this turn. Say plainly that the tool is not working and relay `problem`; never claim its tools are loading, coming next message, or temporarily unavailable. `problem` names what a person has to fix \u2014 the address, the sign-in, or the key the server was added with \u2014 so pass on that fix and no other: never tell the user to reconnect a server whose `problem` is its address or its key. The user makes the fix in the dashboard (Brain \u2192 Integrations \u2192 MCPs). If `suggestedAddress` is present, that is the address to use.";
62136
62152
  var LAST_CHECK_FAILED_HINT = "A server with lastCheck=failed was unreachable or erroring when it was last checked \u2014 that may already have passed, and its tools ARE loaded this turn. Try the tool; if it works, say nothing about the check. If it fails, report `problem` as what happened rather than guessing.";
62137
62153
  function parseHeaders(raw, rawArgs) {
62138
- const list = repeatedValues(rawArgs, "header", raw);
62154
+ const list3 = repeatedValues(rawArgs, "header", raw);
62139
62155
  const headers = {};
62140
- for (const entry of list) {
62156
+ for (const entry of list3) {
62141
62157
  const text2 = String(entry);
62142
62158
  const sep = text2.indexOf(":") >= 0 && (text2.indexOf(":") < text2.indexOf("=") || text2.indexOf("=") < 0) ? ":" : "=";
62143
62159
  const at = text2.indexOf(sep);
@@ -63306,7 +63322,7 @@ Examples:
63306
63322
  "no-cache": { type: "boolean", description: "Skip server cache, hit API directly", required: false }
63307
63323
  },
63308
63324
  run: async ({ args: args5 }) => {
63309
- const target2 = args5.target;
63325
+ const target4 = args5.target;
63310
63326
  const location2 = args5.location || void 0;
63311
63327
  const language = args5.language || void 0;
63312
63328
  const sort = args5.sort || void 0;
@@ -63316,7 +63332,7 @@ Examples:
63316
63332
  const queryContext = buildResearchQueryContext(location2, language);
63317
63333
  try {
63318
63334
  const data = await apiPost("/api/research/keywords-for-site", {
63319
- target: target2,
63335
+ target: target4,
63320
63336
  location: location2,
63321
63337
  language,
63322
63338
  sort,
@@ -63489,7 +63505,7 @@ Examples:
63489
63505
  "no-cache": { type: "boolean", description: "Skip server cache, hit API directly", required: false }
63490
63506
  },
63491
63507
  run: async ({ args: args5 }) => {
63492
- const target2 = args5.target;
63508
+ const target4 = args5.target;
63493
63509
  const location2 = args5.location || void 0;
63494
63510
  const language = args5.language || void 0;
63495
63511
  const limit2 = args5.limit ? Number(args5.limit) : void 0;
@@ -63497,7 +63513,7 @@ Examples:
63497
63513
  const queryContext = buildResearchQueryContext(location2, language);
63498
63514
  try {
63499
63515
  const data = await apiPost("/api/research/relevant-pages", {
63500
- target: target2,
63516
+ target: target4,
63501
63517
  location: location2,
63502
63518
  language,
63503
63519
  limit: limit2,
@@ -63844,7 +63860,7 @@ function refuseMisusedKinds(kind, args5) {
63844
63860
  }
63845
63861
  }
63846
63862
  function refuseStrayFlags(kind, args5) {
63847
- const allowed = new Set(TRIGGER_TARGETS[kind].map((target2) => TRIGGER_TARGET_FIELDS[target2.field].flag));
63863
+ const allowed = new Set(TRIGGER_TARGETS[kind].map((target4) => TRIGGER_TARGET_FIELDS[target4.field].flag));
63848
63864
  const stray = TRIGGER_TARGET_FIELD_IDS.map((field) => TRIGGER_TARGET_FIELDS[field].flag).filter(
63849
63865
  (flag3) => !allowed.has(flag3) && flagText(args5, flag3) !== void 0 && !(kind === "experiment.started" && flag3 === "experiment")
63850
63866
  );
@@ -64018,8 +64034,16 @@ registerSchema({
64018
64034
  command: "scheduled-agents.create",
64019
64035
  description: "Stage a scheduled agent: a trigger and a prompt. Applies when the chat is published and returns a temp_agent_* ID. Start here: `baker scheduled-agents triggers` lists everything that can start one.",
64020
64036
  args: {
64021
- name: { type: "string", description: "What a person will call it in the list", required: true },
64022
- description: { type: "string", description: "One line under the name. Not read by the run.", required: true },
64037
+ name: {
64038
+ type: "string",
64039
+ description: "What a person will call it in the list. With --template, leave it out to use the recipe's name.",
64040
+ required: false
64041
+ },
64042
+ description: {
64043
+ type: "string",
64044
+ description: "One line under the name. Not read by the run. With --template, leave it out: the recipe's own line is used, and it says what waits for the user's review.",
64045
+ required: false
64046
+ },
64023
64047
  prompt: {
64024
64048
  type: "string",
64025
64049
  description: "What the run does, in full. This IS the job \u2014 nothing else is resolved when it fires. Write it as you would write a message to an agent.",
@@ -64075,11 +64099,11 @@ var createCommand3 = defineCommand197({
64075
64099
  const description = args5.description;
64076
64100
  const prompt = args5.prompt;
64077
64101
  const template = typeof args5.template === "string" ? args5.template.trim() : "";
64078
- if (!name || name.trim().length === 0) {
64079
- failValidation4("--name is required.");
64102
+ if (template.length === 0 && (!name || name.trim().length === 0)) {
64103
+ failValidation4("--name is required. Or pass --template to use a recipe's name.");
64080
64104
  }
64081
- if (!description || description.trim().length === 0) {
64082
- failValidation4("--description is required.");
64105
+ if (template.length === 0 && (!description || description.trim().length === 0)) {
64106
+ failValidation4("--description is required. Or pass --template to use a recipe's line.");
64083
64107
  }
64084
64108
  if (template.length === 0 && (!prompt || prompt.trim().length === 0)) {
64085
64109
  failValidation4("--prompt is required: it is the whole of what a run does. Or pass --template to copy a brief.");
@@ -64088,8 +64112,8 @@ var createCommand3 = defineCommand197({
64088
64112
  const reaction = reactionBody(args5);
64089
64113
  const body = {
64090
64114
  chatId: requireChatId(),
64091
- name,
64092
- description,
64115
+ ...name?.trim() ? { name } : {},
64116
+ ...description?.trim() ? { description } : {},
64093
64117
  enabled: !args5.disabled,
64094
64118
  ...trigger,
64095
64119
  ...prompt ? { prompt } : {},
@@ -64438,8 +64462,8 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
64438
64462
  try {
64439
64463
  const save = flag3("save");
64440
64464
  const publish = flag3("publish");
64441
- const remove = flag3("delete");
64442
- if ([save, publish, remove].filter((id) => id.length > 0).length > 1) {
64465
+ const remove2 = flag3("delete");
64466
+ if ([save, publish, remove2].filter((id) => id.length > 0).length > 1) {
64443
64467
  failValidation4("Pass one of --save, --publish or --delete at a time.");
64444
64468
  }
64445
64469
  if (save.length > 0) {
@@ -64468,10 +64492,10 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
64468
64492
  writeJson(response2);
64469
64493
  return;
64470
64494
  }
64471
- if (remove.length > 0) {
64495
+ if (remove2.length > 0) {
64472
64496
  const response2 = await apiPost("/api/marketplace/remove", {
64473
64497
  chatId: requireChatId(),
64474
- slug: remove
64498
+ slug: remove2
64475
64499
  });
64476
64500
  writeJson(response2);
64477
64501
  return;
@@ -64769,7 +64793,7 @@ var schemaCommand2 = defineCommand208({
64769
64793
  });
64770
64794
 
64771
64795
  // src/commands/shopify/index.ts
64772
- import { defineCommand as defineCommand211 } from "citty";
64796
+ import { defineCommand as defineCommand215 } from "citty";
64773
64797
 
64774
64798
  // src/commands/shopify/buyers.ts
64775
64799
  import { defineCommand as defineCommand209 } from "citty";
@@ -65056,7 +65080,18 @@ var shopifyStoreSummarySchema = z50.object({
65056
65080
  });
65057
65081
  var shopifyStoreResponseSchema = z50.object({
65058
65082
  store: shopifyStoreStateSchema,
65059
- summary: shopifyStoreSummarySchema
65083
+ summary: shopifyStoreSummarySchema,
65084
+ /**
65085
+ * Which store changes Baker may make (S9), by what the store allowed: false
65086
+ * means `baker shopify change …` will ask the user to allow it first.
65087
+ */
65088
+ canChange: z50.object({
65089
+ products: z50.boolean(),
65090
+ publishing: z50.boolean(),
65091
+ discounts: z50.boolean(),
65092
+ redirects: z50.boolean(),
65093
+ marketing: z50.boolean()
65094
+ }).optional()
65060
65095
  });
65061
65096
  var shopifyProductRowSchema = z50.object({
65062
65097
  productId: z50.string(),
@@ -65094,7 +65129,7 @@ var shopifyVariantSchema = z50.object({
65094
65129
  compareAtPrice: shopifyPriceSchema.optional(),
65095
65130
  stock: shopifyOptionStockSchema,
65096
65131
  quantity: z50.number().optional(),
65097
- /** The link for the page's buy button: never build a Shopify link by hand. Absent when the option can't be bought. */
65132
+ /** The option's buy link for ads, emails and messages (a landing places `<ShopifyProduct>` instead): never build a Shopify link by hand. Absent when the option can't be bought. */
65098
65133
  buyUrl: z50.string().optional(),
65099
65134
  buyUrlKind: shopifyBuyDestinationSchema.optional(),
65100
65135
  /** Why there is no buy link. */
@@ -65209,7 +65244,7 @@ var shopifyProductDetailSchema = z50.object({
65209
65244
  handle: z50.string(),
65210
65245
  /** The product's page in Baker — the link to share with the user. */
65211
65246
  url: z50.string(),
65212
- /** The product's page on the online store: to look at, never a buy button's link (that is each option's `buyUrl`). */
65247
+ /** The product's page on the online store: to look at, never a buy button's link (that is each option's `buyUrl`, or `<ShopifyProduct>` on a landing). */
65213
65248
  storeUrl: z50.string().optional(),
65214
65249
  availability: shopifyAvailabilitySchema.optional(),
65215
65250
  status: shopifyProductStatusSchema.optional(),
@@ -65773,6 +65808,323 @@ var shopifyCohortsResponseSchema = z51.object({
65773
65808
  cohorts: z51.array(shopifyCohortSchema)
65774
65809
  });
65775
65810
 
65811
+ // ../api/src/shopify/writes.ts
65812
+ import { z as z52 } from "zod";
65813
+ var SHOPIFY_TEMP_REF_PREFIX = "shopify_temp_";
65814
+ var SHOPIFY_PERMISSION_NEEDED_PREFIX = "Baker isn't allowed to change this Shopify store yet";
65815
+ var shopifyWriteEntitySchema = z52.enum([
65816
+ "product",
65817
+ "variant",
65818
+ "metafield",
65819
+ "collection",
65820
+ "discountCode",
65821
+ "urlRedirect",
65822
+ "marketingActivity",
65823
+ "webPixel"
65824
+ ]);
65825
+ var shopifyDraftOpKindSchema = z52.enum([
65826
+ "shopify.product.update",
65827
+ "shopify.variant.update",
65828
+ "shopify.metafield.set",
65829
+ "shopify.metafield.delete",
65830
+ "shopify.collection.create",
65831
+ "shopify.collection.publish",
65832
+ "shopify.collection.unpublish",
65833
+ "shopify.collection.update",
65834
+ "shopify.collection.delete",
65835
+ "shopify.collection.addProducts",
65836
+ "shopify.collection.removeProducts",
65837
+ "shopify.discountCode.create",
65838
+ "shopify.discountCode.deactivate",
65839
+ "shopify.discountCode.schedule",
65840
+ "shopify.urlRedirect.create",
65841
+ "shopify.urlRedirect.delete",
65842
+ "shopify.marketingActivity.upsert",
65843
+ "shopify.marketingActivity.delete",
65844
+ "shopify.webPixel.update"
65845
+ ]);
65846
+ var shopifyDecimalSchema = z52.string().regex(/^\d{1,12}(\.\d{1,4})?$/, 'write amounts as a decimal string, e.g. "19.95"');
65847
+ var tagSchema = z52.string().trim().min(1).max(255);
65848
+ var seoSchema = z52.object({ title: z52.string().max(255).optional(), description: z52.string().max(320).optional() }).strict();
65849
+ var SHOPIFY_DESCRIPTION_MAX_CHARS = 2e5;
65850
+ var descriptionHtmlSchema = z52.string().max(SHOPIFY_DESCRIPTION_MAX_CHARS);
65851
+ var productUpdateSchema = z52.object({
65852
+ title: z52.string().trim().min(1).max(255).optional(),
65853
+ descriptionHtml: descriptionHtmlSchema.optional(),
65854
+ seo: seoSchema.optional(),
65855
+ /** The WHOLE tag list after the change (Shopify replaces it). `addTags`/`removeTags` are resolved against the live tags. */
65856
+ tags: z52.array(tagSchema).max(250).optional(),
65857
+ addTags: z52.array(tagSchema).max(250).optional(),
65858
+ removeTags: z52.array(tagSchema).max(250).optional(),
65859
+ productType: z52.string().max(255).optional()
65860
+ }).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change").refine(
65861
+ (payload) => payload.tags === void 0 || payload.addTags === void 0 && payload.removeTags === void 0,
65862
+ "give either the whole `tags` list or `addTags`/`removeTags`, not both"
65863
+ );
65864
+ var shopifyWeightUnitSchema = z52.enum(["GRAMS", "KILOGRAMS", "OUNCES", "POUNDS"]);
65865
+ var variantUpdateSchema = z52.object({
65866
+ barcode: z52.string().trim().max(255).optional(),
65867
+ sku: z52.string().trim().max(255).optional(),
65868
+ weight: z52.object({ value: z52.number().nonnegative(), unit: shopifyWeightUnitSchema }).strict().optional(),
65869
+ price: shopifyDecimalSchema.optional(),
65870
+ /** `null` clears the sale ("compare at") price. */
65871
+ compareAtPrice: shopifyDecimalSchema.nullable().optional()
65872
+ }).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change");
65873
+ var metafieldKeySchema = z52.string().min(2).max(64).regex(/^[A-Za-z0-9_-]+$/, "letters, digits, `_` and `-` only");
65874
+ var metafieldNamespaceSchema = z52.string().min(2).max(255).regex(/^[A-Za-z0-9_-]+$/, "letters, digits, `_` and `-` only");
65875
+ var metafieldSetSchema = z52.object({
65876
+ namespace: metafieldNamespaceSchema,
65877
+ key: metafieldKeySchema,
65878
+ /** Shopify's metafield type, e.g. `single_line_text_field`, `multi_line_text_field`, `rich_text_field`, `number_integer`, `boolean`, `json`. */
65879
+ type: z52.string().min(1).max(100),
65880
+ value: z52.string().max(1e5)
65881
+ }).strict();
65882
+ var metafieldDeleteSchema = z52.object({ namespace: metafieldNamespaceSchema, key: metafieldKeySchema }).strict();
65883
+ var shopifyCollectionSortOrderSchema = z52.enum([
65884
+ "MANUAL",
65885
+ "BEST_SELLING",
65886
+ "ALPHA_ASC",
65887
+ "ALPHA_DESC",
65888
+ "PRICE_DESC",
65889
+ "PRICE_ASC",
65890
+ "CREATED",
65891
+ "CREATED_DESC"
65892
+ ]);
65893
+ var handleSchema = z52.string().min(1).max(255).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "lowercase letters, digits and single dashes, e.g. summer-snowboards");
65894
+ var SHOPIFY_COLLECTION_PRODUCTS_MAX = 250;
65895
+ var collectionCreateSchema = z52.object({
65896
+ title: z52.string().trim().min(1).max(255),
65897
+ handle: handleSchema.optional(),
65898
+ descriptionHtml: descriptionHtmlSchema.optional(),
65899
+ seo: seoSchema.optional(),
65900
+ sortOrder: shopifyCollectionSortOrderSchema.optional(),
65901
+ /** The theme template the collection page uses (the part after `collection.`). Kept when a deleted collection is put back. */
65902
+ templateSuffix: z52.string().max(255).optional(),
65903
+ /** Product ids, handles or links, in the order shown with sort order MANUAL. */
65904
+ products: z52.array(z52.string().min(1)).max(SHOPIFY_COLLECTION_PRODUCTS_MAX).optional(),
65905
+ /** Show it on the online store (a second change, staged with it). Default true. */
65906
+ onlineStore: z52.boolean().optional()
65907
+ }).strict();
65908
+ var collectionUpdateSchema = z52.object({
65909
+ title: z52.string().trim().min(1).max(255).optional(),
65910
+ descriptionHtml: descriptionHtmlSchema.optional(),
65911
+ seo: seoSchema.optional(),
65912
+ sortOrder: shopifyCollectionSortOrderSchema.optional()
65913
+ }).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change");
65914
+ var collectionProductsSchema = z52.object({ products: z52.array(z52.string().min(1)).min(1).max(SHOPIFY_COLLECTION_PRODUCTS_MAX) }).strict();
65915
+ var isoDateTimeSchema = z52.iso.datetime({ offset: true });
65916
+ var discountCodeSchema = z52.string().regex(/^[A-Za-z0-9_-]{3,64}$/, "3\u201364 letters, digits, `_` or `-`, e.g. SPRING10");
65917
+ var discountValueSchema = z52.union([
65918
+ z52.object({ percentage: z52.number().min(1).max(100) }).strict(),
65919
+ z52.object({ amount: shopifyDecimalSchema }).strict()
65920
+ ]);
65921
+ var discountAppliesToSchema = z52.union([
65922
+ z52.object({ all: z52.literal(true) }).strict(),
65923
+ z52.object({ products: z52.array(z52.string().min(1)).min(1).max(100) }).strict(),
65924
+ z52.object({ collections: z52.array(z52.string().min(1)).min(1).max(100) }).strict()
65925
+ ]);
65926
+ var discountCreateSchema = z52.object({
65927
+ code: discountCodeSchema,
65928
+ /** What the store's staff see; defaults to the code. */
65929
+ title: z52.string().trim().min(1).max(255).optional(),
65930
+ value: discountValueSchema,
65931
+ appliesTo: discountAppliesToSchema,
65932
+ minimumSubtotal: shopifyDecimalSchema.optional(),
65933
+ minimumQuantity: z52.number().int().min(1).max(1e4).optional(),
65934
+ /** Default: the moment it is published. */
65935
+ startsAt: isoDateTimeSchema.optional(),
65936
+ endsAt: isoDateTimeSchema.optional(),
65937
+ usageLimit: z52.number().int().min(1).max(1e6).optional(),
65938
+ /** Default true. */
65939
+ oncePerCustomer: z52.boolean().optional(),
65940
+ combinesWith: z52.object({
65941
+ productDiscounts: z52.boolean().optional(),
65942
+ orderDiscounts: z52.boolean().optional(),
65943
+ shippingDiscounts: z52.boolean().optional()
65944
+ }).strict().optional()
65945
+ }).strict().refine(
65946
+ (payload) => payload.minimumSubtotal === void 0 || payload.minimumQuantity === void 0,
65947
+ "a discount takes a minimum amount or a minimum quantity, not both"
65948
+ );
65949
+ var discountScheduleSchema = z52.object({ startsAt: isoDateTimeSchema, endsAt: isoDateTimeSchema.nullable() }).strict();
65950
+ var storePathSchema = z52.string().min(2).max(1024).regex(/^\/[^\s]*$/, 'a path on the store starting with "/", e.g. /collections/old');
65951
+ var urlRedirectCreateSchema = z52.object({
65952
+ from: storePathSchema,
65953
+ to: z52.union([storePathSchema, z52.url({ protocol: /^https$/ })])
65954
+ }).strict();
65955
+ var shopifyMarketingPlatformSchema = z52.enum(["google-ads", "meta"]);
65956
+ var marketingUpsertSchema = z52.object({
65957
+ platform: shopifyMarketingPlatformSchema,
65958
+ accountId: z52.string().min(1).max(64),
65959
+ campaignId: z52.string().regex(/^\d+$/, "the campaign's numeric id"),
65960
+ utm: z52.object({
65961
+ campaign: z52.string().trim().min(1).max(255),
65962
+ source: z52.string().trim().min(1).max(255),
65963
+ medium: z52.string().trim().min(1).max(255)
65964
+ }).strict()
65965
+ }).strict();
65966
+ var emptySchema = z52.object({}).strict();
65967
+ var opFields = {
65968
+ /** Optional caller-chosen ref for a create, so later ops in the batch can name it (`shopify_temp_…`). */
65969
+ ref: z52.string().startsWith(SHOPIFY_TEMP_REF_PREFIX).max(80).optional()
65970
+ };
65971
+ var target2 = z52.string().trim().min(1).max(2048);
65972
+ var shopifyDraftOpInputSchema = z52.discriminatedUnion("kind", [
65973
+ z52.object({ kind: z52.literal("shopify.product.update"), target: target2, payload: productUpdateSchema, ...opFields }),
65974
+ z52.object({ kind: z52.literal("shopify.variant.update"), target: target2, payload: variantUpdateSchema, ...opFields }),
65975
+ z52.object({ kind: z52.literal("shopify.metafield.set"), target: target2, payload: metafieldSetSchema, ...opFields }),
65976
+ z52.object({ kind: z52.literal("shopify.metafield.delete"), target: target2, payload: metafieldDeleteSchema, ...opFields }),
65977
+ z52.object({ kind: z52.literal("shopify.collection.create"), payload: collectionCreateSchema, ...opFields }),
65978
+ z52.object({ kind: z52.literal("shopify.collection.publish"), target: target2, payload: emptySchema.optional(), ...opFields }),
65979
+ z52.object({ kind: z52.literal("shopify.collection.unpublish"), target: target2, payload: emptySchema.optional(), ...opFields }),
65980
+ z52.object({ kind: z52.literal("shopify.collection.update"), target: target2, payload: collectionUpdateSchema, ...opFields }),
65981
+ z52.object({ kind: z52.literal("shopify.collection.delete"), target: target2, payload: emptySchema.optional(), ...opFields }),
65982
+ z52.object({
65983
+ kind: z52.literal("shopify.collection.addProducts"),
65984
+ target: target2,
65985
+ payload: collectionProductsSchema,
65986
+ ...opFields
65987
+ }),
65988
+ z52.object({
65989
+ kind: z52.literal("shopify.collection.removeProducts"),
65990
+ target: target2,
65991
+ payload: collectionProductsSchema,
65992
+ ...opFields
65993
+ }),
65994
+ z52.object({ kind: z52.literal("shopify.discountCode.create"), payload: discountCreateSchema, ...opFields }),
65995
+ z52.object({
65996
+ kind: z52.literal("shopify.discountCode.deactivate"),
65997
+ target: target2,
65998
+ payload: emptySchema.optional(),
65999
+ ...opFields
66000
+ }),
66001
+ z52.object({ kind: z52.literal("shopify.discountCode.schedule"), target: target2, payload: discountScheduleSchema, ...opFields }),
66002
+ z52.object({ kind: z52.literal("shopify.urlRedirect.create"), payload: urlRedirectCreateSchema, ...opFields }),
66003
+ z52.object({ kind: z52.literal("shopify.urlRedirect.delete"), target: target2, payload: emptySchema.optional(), ...opFields }),
66004
+ z52.object({ kind: z52.literal("shopify.marketingActivity.upsert"), payload: marketingUpsertSchema, ...opFields }),
66005
+ z52.object({
66006
+ kind: z52.literal("shopify.marketingActivity.delete"),
66007
+ target: target2,
66008
+ payload: emptySchema.optional(),
66009
+ ...opFields
66010
+ }),
66011
+ z52.object({ kind: z52.literal("shopify.webPixel.update"), payload: emptySchema.optional(), ...opFields })
66012
+ ]);
66013
+ var PAYLOAD_KEYS = new Map(
66014
+ shopifyDraftOpInputSchema.options.map((option) => {
66015
+ const payload = option.shape.payload;
66016
+ const object = payload instanceof z52.ZodOptional ? payload.unwrap() : payload;
66017
+ return [option.shape.kind.value, object instanceof z52.ZodObject ? Object.keys(object.shape) : []];
66018
+ })
66019
+ );
66020
+ var askUserQuestionInputSchema = z52.object({
66021
+ questions: z52.array(
66022
+ z52.object({
66023
+ question: z52.string(),
66024
+ header: z52.string(),
66025
+ multiSelect: z52.boolean(),
66026
+ options: z52.array(z52.object({ label: z52.string(), description: z52.string() })).min(2)
66027
+ })
66028
+ ).min(1)
66029
+ });
66030
+ var shopifyNeedsApprovalSchema = z52.object({
66031
+ /** Pass to AskUserQuestion exactly as given. */
66032
+ askUserQuestion: askUserQuestionInputSchema,
66033
+ /** The user already said no to (at least one of) these changes: don't ask again in this turn. */
66034
+ declined: z52.boolean()
66035
+ });
66036
+ var shopifyVerificationSchema = z52.object({
66037
+ status: z52.enum(["confirmed", "drifted", "unconfirmed"]),
66038
+ checkedAt: z52.number(),
66039
+ note: z52.string().optional()
66040
+ });
66041
+ var shopifyDraftOpResultSchema = z52.object({
66042
+ status: z52.enum(["applied", "failed", "skipped"]),
66043
+ createdId: z52.string().optional(),
66044
+ error: z52.string().optional(),
66045
+ note: z52.string().optional(),
66046
+ skippedBecause: z52.string().optional(),
66047
+ executedAt: z52.number().optional(),
66048
+ pendingJob: z52.string().optional(),
66049
+ verification: shopifyVerificationSchema.optional()
66050
+ });
66051
+ var stagedOpSchema = z52.object({
66052
+ ref: z52.string(),
66053
+ kind: shopifyDraftOpKindSchema,
66054
+ summary: z52.string(),
66055
+ warnings: z52.array(z52.string())
66056
+ });
66057
+ var shopifyDraftStageResponseSchema = z52.union([
66058
+ z52.object({
66059
+ staged: z52.literal(true),
66060
+ count: z52.number(),
66061
+ ops: z52.array(stagedOpSchema),
66062
+ amended: z52.boolean().optional()
66063
+ }),
66064
+ z52.object({ staged: z52.literal(false), needsApproval: shopifyNeedsApprovalSchema })
66065
+ ]);
66066
+ var SHOPIFY_DRAFT_BATCH_MAX = 100;
66067
+ var shopifyDraftStageRequestSchema = z52.object({ chatId: z52.string(), op: shopifyDraftOpInputSchema });
66068
+ var shopifyDraftStageBatchRequestSchema = z52.object({
66069
+ chatId: z52.string(),
66070
+ ops: z52.array(shopifyDraftOpInputSchema).min(1).max(SHOPIFY_DRAFT_BATCH_MAX)
66071
+ });
66072
+ var shopifyDraftAmendRequestSchema = z52.object({
66073
+ chatId: z52.string(),
66074
+ ref: z52.string(),
66075
+ patch: z52.record(z52.string(), z52.unknown())
66076
+ });
66077
+ var shopifyDraftOpViewSchema = z52.object({
66078
+ ref: z52.string(),
66079
+ kind: shopifyDraftOpKindSchema,
66080
+ entity: shopifyWriteEntitySchema,
66081
+ target: z52.string().optional(),
66082
+ targetLabel: z52.string().optional(),
66083
+ summary: z52.string(),
66084
+ money: z52.boolean(),
66085
+ warnings: z52.array(z52.string()).optional(),
66086
+ dependsOn: z52.array(z52.string()),
66087
+ undoOf: z52.string().optional(),
66088
+ stagedAt: z52.number(),
66089
+ result: shopifyDraftOpResultSchema.optional()
66090
+ });
66091
+ var shopifyDraftListRequestSchema = z52.object({ chatId: z52.string() });
66092
+ var shopifyDraftListResponseSchema = z52.object({
66093
+ status: z52.enum(["active", "publishing", "applied", "discarded", "none"]),
66094
+ store: z52.string().optional(),
66095
+ count: z52.number(),
66096
+ ops: z52.array(shopifyDraftOpViewSchema),
66097
+ publishError: z52.string().optional()
66098
+ });
66099
+ var shopifyDraftShowRequestSchema = z52.object({ chatId: z52.string(), ref: z52.string() });
66100
+ var shopifyDraftShowResponseSchema = z52.object({
66101
+ op: shopifyDraftOpViewSchema.extend({ payload: z52.unknown().optional(), before: z52.unknown().optional() })
66102
+ });
66103
+ var shopifyDraftRemoveRequestSchema = z52.object({ chatId: z52.string(), ref: z52.string() });
66104
+ var shopifyDraftRemoveResponseSchema = z52.object({ removed: z52.array(z52.string()) });
66105
+ var shopifyDraftClearRequestSchema = z52.object({ chatId: z52.string() });
66106
+ var shopifyDraftClearResponseSchema = z52.object({ cleared: z52.number() });
66107
+ var shopifyChangeViewSchema = z52.object({
66108
+ changeId: z52.string(),
66109
+ when: z52.string(),
66110
+ kind: shopifyDraftOpKindSchema,
66111
+ summary: z52.string(),
66112
+ status: z52.enum(["applied", "failed", "undone"]),
66113
+ chatId: z52.string(),
66114
+ undoable: z52.boolean(),
66115
+ /** Why it can't be undone, when it can't. Whether the store still holds Baker's change is checked when undo is staged. */
66116
+ notUndoableBecause: z52.string().optional()
66117
+ });
66118
+ var shopifyChangesRequestSchema = z52.object({
66119
+ limit: z52.coerce.number().int().min(1).max(100).optional(),
66120
+ chatId: z52.string().optional()
66121
+ });
66122
+ var shopifyChangesResponseSchema = z52.object({ changes: z52.array(shopifyChangeViewSchema) });
66123
+ var shopifyUndoRequestSchema = z52.object({
66124
+ chatId: z52.string(),
66125
+ changeIds: z52.array(z52.string().min(1)).min(1).max(SHOPIFY_DRAFT_BATCH_MAX)
66126
+ });
66127
+
65776
66128
  // src/commands/shopify/refusal.ts
65777
66129
  var CONTINUE2 = "Keep going with the wider task: say which part you could not read, skip it, and finish everything else.";
65778
66130
  function shopifyRefusal(code, message) {
@@ -65797,6 +66149,36 @@ function shopifyRefusal(code, message) {
65797
66149
  }
65798
66150
  return null;
65799
66151
  }
66152
+ function storeChangeRefusal(code, message) {
66153
+ if (code !== "FORBIDDEN" || !message.startsWith(SHOPIFY_PERMISSION_NEEDED_PREFIX)) return null;
66154
+ return {
66155
+ code: "SHOPIFY_PERMISSION_NEEDED",
66156
+ fix: {
66157
+ action: "request_connection",
66158
+ explanation: 'AGENT INSTRUCTION: the store hasn\'t allowed Baker to make this kind of change yet, and only the user can. Call `request_connection` with { platform: "shopify", reason } \u2014 reason in their words, e.g. "so I can update your product titles". They allow it in Shopify; the card finishes when Shopify confirms. If its result lists something under cannotRead, that change is still not allowed: don\'t promise it. Then run this command again. Keep going with the rest of the task.'
66159
+ }
66160
+ };
66161
+ }
66162
+ function approvalRefusal(needsApproval) {
66163
+ if (needsApproval.declined) {
66164
+ return {
66165
+ code: "NEEDS_USER_APPROVAL",
66166
+ fix: {
66167
+ action: "continue_without",
66168
+ askUserQuestion: needsApproval.askUserQuestion,
66169
+ explanation: "The user said no to this change. Don't stage it, don't ask again on your own, and don't find another way to make it; carry on with the rest of the task. Ask again only if the user asks for it again (they changed their mind): then call AskUserQuestion with `askUserQuestion` exactly as given, and if they choose \"Yes, make these changes\", run this same command again."
66170
+ }
66171
+ };
66172
+ }
66173
+ return {
66174
+ code: "NEEDS_USER_APPROVAL",
66175
+ fix: {
66176
+ action: "ask_user",
66177
+ askUserQuestion: needsApproval.askUserQuestion,
66178
+ explanation: 'This changes money in the store, so the user says yes first. Call AskUserQuestion with `askUserQuestion` exactly as given (you may say a sentence before it, never change it). If the user chooses "Yes, make these changes", run this same command again. Do not stage it any other way.'
66179
+ }
66180
+ };
66181
+ }
65800
66182
 
65801
66183
  // src/commands/shopify/common.ts
65802
66184
  var PRODUCT_NOT_FOUND_HINT = "Find it with `baker shopify products --search <words>`. If `baker shopify store` shows products Baker couldn't read or products still coming in, it may be one of those \u2014 say so rather than that it doesn't exist.";
@@ -65952,17 +66334,17 @@ function stockHintsFor(product) {
65952
66334
  }
65953
66335
  var BUY_NOTES = {
65954
66336
  password_protected: "The store asks for a password before anyone can see it: a shopper following a buy link meets it first \u2014 tell the user before sending traffic.",
65955
- subscription_optional: "It can also be bought by subscription, but a buy link can only add it once \u2014 say so if the page promotes the subscription.",
66337
+ subscription_optional: "It can also be bought by subscription: on a landing `<ShopifyProduct>` offers the plans; a `buyUrl` elsewhere adds it once.",
65956
66338
  own_storefront: "The store sells through its own storefront: buy links go straight to Shopify's checkout.",
65957
66339
  details_pending: "Baker hasn't read this product's details yet (subscriptions among them): its buy links may change.",
65958
- subscription_only: "It's sold only by subscription, which a buy link can't carry: its buy link is the product page, where the shopper picks the plan."
66340
+ subscription_only: "It's sold only by subscription: on a landing `<ShopifyProduct>` offers the plans; a `buyUrl` elsewhere is its product page, where the shopper picks one."
65959
66341
  };
65960
66342
  function buyHints(product) {
65961
66343
  const hints2 = [];
65962
66344
  if (product.variants.some((variant) => variant.buyUrl)) {
65963
66345
  hints2.push(
65964
- `Use each option's \`buyUrl\` for the page's buy button \u2014 never build a Shopify link yourself (it follows the store's setting: ${product.buyDestination.replace("_", " ")}; \`--quantity <n>\` sets how many).`,
65965
- "Keep that `buyUrl` on the button: never `storeUrl` (a page to look at, not a buy link), and never drop or replace the buy button to get a page past its checks. The options to sell are `variants[]` alone \u2014 one buy button per option, each with its own `buyUrl`, never a size or colour picker made up from a size chart or other details. When the shopper must choose on the store, link the option's `productPage` (from `--full`)."
66346
+ `On a landing, place \`<ShopifyProduct product="${product.handle}" />\` (and \`<ShopifyBuyButton product="${product.handle}">\` for a second button): it shows the options, price and stock, and its button hands the shopper to the store. Never put a \`buyUrl\` on a landing yourself, and never build an option picker. See /landing \`.claude/skills/landing/references/verticals/ecommerce.md\`.`,
66347
+ `\`buyUrl\` is for links outside a landing \u2014 an ad, an email, a message: one per option; never \`storeUrl\` (a page to look at), never a Shopify link built yourself (it follows the store's setting: ${product.buyDestination.replace("_", " ")}; \`--quantity <n>\` sets how many).`
65966
66348
  );
65967
66349
  }
65968
66350
  const notes = new Set(product.variants.flatMap((variant) => variant.notes ?? []));
@@ -66417,9 +66799,786 @@ var segmentsCommand = defineCommand209({
66417
66799
  }
66418
66800
  });
66419
66801
 
66420
- // src/commands/shopify/promotions.ts
66802
+ // src/commands/shopify/change.ts
66803
+ import { defineCommand as defineCommand211 } from "citty";
66804
+
66805
+ // src/commands/shopify/writeCommon.ts
66421
66806
  import { defineCommand as defineCommand210 } from "citty";
66422
66807
 
66808
+ // src/commands/shopify/writeHints.ts
66809
+ var STAGED = "Staged \u2014 nothing changes in the store until the user publishes the chat. Review: `baker shopify draft list`. Say the change is ready and happens when they publish, never that it is done.";
66810
+ var WARNINGS = "Read the warnings above before telling the user this is ready.";
66811
+ var DISCOUNT = "The code works only after the chat is published; don't announce it as live or share it as working yet.";
66812
+ var EMPTY_COLLECTION = "The new collection has no products yet: add them with `baker shopify change collection add <collection> <product\u2026>`.";
66813
+ var COLLECTION_DELETE = "Its address stops working when it is deleted: keep old links alive with `baker shopify change redirect create --from /collections/<handle> --to <page>` (ask the user where they should land).";
66814
+ function repointedRedirects(ops) {
66815
+ const removed = ops.flatMap(
66816
+ (op) => op.kind === "shopify.urlRedirect.delete" && op.ref.startsWith("urlRedirect:") ? [op.ref.slice("urlRedirect:".length)] : []
66817
+ );
66818
+ return removed.flatMap((path49) => {
66819
+ const made = ops.find(
66820
+ (op) => op.kind === "shopify.urlRedirect.create" && op.summary.startsWith(`Redirect ${path49} to `)
66821
+ );
66822
+ if (!made) return [];
66823
+ const to = made.summary.slice(`Redirect ${path49} to `.length);
66824
+ return [
66825
+ `${path49} still redirects after publish, now to ${to}: tell the user the old link goes there, not that its redirect is removed.`
66826
+ ];
66827
+ });
66828
+ }
66829
+ function stageHints(data) {
66830
+ if (!data.staged) return [];
66831
+ const hints2 = [STAGED, ...repointedRedirects(data.ops)];
66832
+ if (data.ops.some((op) => op.warnings.length > 0)) hints2.push(WARNINGS);
66833
+ if (data.ops.some((op) => op.kind === "shopify.discountCode.create")) hints2.push(DISCOUNT);
66834
+ if (data.ops.some((op) => op.kind === "shopify.collection.delete")) hints2.push(COLLECTION_DELETE);
66835
+ if (data.ops.some(
66836
+ (op) => op.kind === "shopify.collection.create" && op.warnings.some((warning) => /no products/.test(warning))
66837
+ )) {
66838
+ hints2.push(EMPTY_COLLECTION);
66839
+ }
66840
+ return hints2;
66841
+ }
66842
+ function changesHints(data) {
66843
+ if (data.changes.length === 0) return ["Baker hasn't changed this store yet."];
66844
+ return data.changes.some((change) => change.undoable) ? ["Undo one with `baker shopify undo <changeId>` (staged like any change, applied when the user publishes)."] : [];
66845
+ }
66846
+ function auditHints(data) {
66847
+ const hints2 = [];
66848
+ for (const check of data.checks) {
66849
+ if (check.fix?.kind === "staged") hints2.push(`Stage the fix for "${check.title}": \`${check.fix.command}\`.`);
66850
+ }
66851
+ for (const check of data.checks) {
66852
+ if (check.fix?.kind === "merchant") {
66853
+ hints2.push(
66854
+ `Only the merchant can fix "${check.title}": ${check.fix.task}. Make it a Task with \`baker actions create\`${check.fix.link ? " and include the link" : ""}.`
66855
+ );
66856
+ }
66857
+ }
66858
+ for (const check of data.checks) {
66859
+ if (check.fix?.kind === "decision") hints2.push(`A person decides "${check.title}": ${check.fix.where}.`);
66860
+ }
66861
+ return hints2;
66862
+ }
66863
+
66864
+ // src/commands/shopify/writeCommon.ts
66865
+ function failChange(err) {
66866
+ if (err instanceof ApiError) {
66867
+ const refusal = storeChangeRefusal(err.code, err.message);
66868
+ if (refusal) {
66869
+ writeJson({ ok: false, error: { code: refusal.code, message: err.message, fix: refusal.fix } });
66870
+ process.exit(1);
66871
+ }
66872
+ }
66873
+ fail9(err);
66874
+ }
66875
+ function emitStage(data) {
66876
+ if (!data.staged) {
66877
+ const refusal = approvalRefusal(data.needsApproval);
66878
+ writeJson({
66879
+ ok: false,
66880
+ error: {
66881
+ code: refusal.code,
66882
+ message: data.needsApproval.declined ? "The user said no to this change in the chat." : "This changes money in the store: the user has to say yes to it in the chat first.",
66883
+ fix: refusal.fix
66884
+ }
66885
+ });
66886
+ process.exit(1);
66887
+ }
66888
+ writeJson({ ok: true, hints: stageHints(data), data });
66889
+ }
66890
+ async function stage(ops) {
66891
+ const chatId = requireChatId();
66892
+ try {
66893
+ const [only] = ops;
66894
+ const data = ops.length === 1 && only ? await apiPost("/api/shopify/draft/stage", { chatId, op: only }) : await apiPost("/api/shopify/draft/stage-batch", { chatId, ops });
66895
+ emitStage(data);
66896
+ } catch (err) {
66897
+ failChange(err);
66898
+ }
66899
+ }
66900
+ function list(value) {
66901
+ if (typeof value !== "string" || value.trim() === "") return void 0;
66902
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
66903
+ }
66904
+ function defined(entries) {
66905
+ return Object.fromEntries(Object.entries(entries).filter(([, value]) => value !== void 0 && value !== ""));
66906
+ }
66907
+ function payloadsFrom(args5) {
66908
+ if (typeof args5.json !== "string" && typeof args5.file !== "string") return null;
66909
+ return loadJsonPayloads(args5);
66910
+ }
66911
+ var JSON_ARGS = {
66912
+ json: {
66913
+ type: "string",
66914
+ description: 'The change as inline JSON: a payload for the target given (e.g. {"addTags":["summer"]}), or an array of {"kind","target","payload"} items to stage together in one store read',
66915
+ required: false
66916
+ },
66917
+ file: { type: "string", description: "A JSON file holding the same", required: false }
66918
+ };
66919
+ function leaf(schemaName, meta, args5, run) {
66920
+ registerSchema({
66921
+ command: schemaName,
66922
+ description: meta.description,
66923
+ args: Object.fromEntries(
66924
+ Object.entries(args5).map(([name, arg]) => [
66925
+ name,
66926
+ {
66927
+ type: String(arg.type ?? "string"),
66928
+ description: String(arg.description ?? ""),
66929
+ required: arg.required === true
66930
+ }
66931
+ ])
66932
+ )
66933
+ });
66934
+ return defineCommand210({ meta, args: args5, run: async ({ args: parsed }) => await run(parsed) });
66935
+ }
66936
+
66937
+ // src/commands/shopify/change.ts
66938
+ var TARGET = (what) => ({ type: "positional", description: what, required: false });
66939
+ function positionals(args5) {
66940
+ return Array.isArray(args5._) ? args5._.filter((value) => typeof value === "string") : [];
66941
+ }
66942
+ function target3(args5, what) {
66943
+ const [first] = positionals(args5);
66944
+ if (!first) failValidation3(`pass the ${what} as the first argument`);
66945
+ return first;
66946
+ }
66947
+ async function stageFor(kind, args5, flags, targetOf) {
66948
+ const payloads = payloadsFrom(args5);
66949
+ if (payloads) {
66950
+ await stage(
66951
+ payloads.map(
66952
+ (payload2) => "kind" in payload2 ? payload2 : { kind, ...targetOf ? { target: targetOf() } : {}, payload: payload2 }
66953
+ )
66954
+ );
66955
+ return;
66956
+ }
66957
+ const payload = flags();
66958
+ if (Object.keys(payload).length === 0)
66959
+ failValidation3("name at least one thing to change (see `baker schema` for the flags)");
66960
+ await stage([{ kind, ...targetOf ? { target: targetOf() } : {}, payload }]);
66961
+ }
66962
+ var productUpdate = leaf(
66963
+ "shopify.change.product.update",
66964
+ {
66965
+ name: "update",
66966
+ description: `Start here to change a product's title, description, SEO, tags or type. Example: baker shopify change product update the-snowboard --title "The Snowboard" --seo-title "\u2026" --add-tags summer. Tags: --tags replaces the whole list, --add-tags/--remove-tags edit it. Descriptions are HTML without scripts or frames. Many products in one call: --json '[{"kind":"shopify.product.update","target":"<handle>","payload":{"addTags":["summer"]}}, \u2026]' \u2014 payload keys: title, descriptionHtml, seo {title, description}, tags | addTags | removeTags, productType.`
66967
+ },
66968
+ {
66969
+ product: TARGET("Product id, handle or link"),
66970
+ title: { type: "string", description: "New title", required: false },
66971
+ description: { type: "string", description: "New description, as HTML (no scripts or frames)", required: false },
66972
+ "seo-title": { type: "string", description: "Title shown in search results", required: false },
66973
+ "seo-description": { type: "string", description: "Description shown in search results", required: false },
66974
+ tags: { type: "string", description: "The whole tag list, comma separated (replaces it)", required: false },
66975
+ "add-tags": { type: "string", description: "Tags to add, comma separated", required: false },
66976
+ "remove-tags": { type: "string", description: "Tags to remove, comma separated", required: false },
66977
+ type: { type: "string", description: "Product type", required: false },
66978
+ ...JSON_ARGS
66979
+ },
66980
+ async (args5) => await stageFor(
66981
+ "shopify.product.update",
66982
+ args5,
66983
+ () => defined({
66984
+ title: args5.title,
66985
+ descriptionHtml: args5.description,
66986
+ seo: args5["seo-title"] !== void 0 || args5["seo-description"] !== void 0 ? defined({ title: args5["seo-title"], description: args5["seo-description"] }) : void 0,
66987
+ tags: list(args5.tags),
66988
+ addTags: list(args5["add-tags"]),
66989
+ removeTags: list(args5["remove-tags"]),
66990
+ productType: args5.type
66991
+ }),
66992
+ positionals(args5).length > 0 ? () => target3(args5, "product") : void 0
66993
+ )
66994
+ );
66995
+ var WEIGHT_UNITS = {
66996
+ g: "GRAMS",
66997
+ gram: "GRAMS",
66998
+ grams: "GRAMS",
66999
+ kg: "KILOGRAMS",
67000
+ kgs: "KILOGRAMS",
67001
+ kilogram: "KILOGRAMS",
67002
+ kilograms: "KILOGRAMS",
67003
+ oz: "OUNCES",
67004
+ ounce: "OUNCES",
67005
+ ounces: "OUNCES",
67006
+ lb: "POUNDS",
67007
+ lbs: "POUNDS",
67008
+ pound: "POUNDS",
67009
+ pounds: "POUNDS"
67010
+ };
67011
+ function weightOf(value, unit) {
67012
+ if (value === void 0) return void 0;
67013
+ const named = typeof unit === "string" ? unit.trim() : "";
67014
+ return {
67015
+ value: Number(value),
67016
+ unit: named ? WEIGHT_UNITS[named.toLowerCase()] ?? named.toUpperCase() : "KILOGRAMS"
67017
+ };
67018
+ }
67019
+ var variantUpdate = leaf(
67020
+ "shopify.change.variant.update",
67021
+ {
67022
+ name: "update",
67023
+ description: "Change one option (variant) of a product: SKU, barcode (GTIN), weight \u2014 and its price or sale price, which the user must approve in the chat first (the command hands you the question). Take the option's id from `baker shopify product <product>` (variantId). Example: baker shopify change variant update 456 --sku SNOW-L --barcode 4006381333931 --weight 1.5 --weight-unit kg. --sale-price none clears it."
67024
+ },
67025
+ {
67026
+ variant: TARGET("The option's id (variantId from baker shopify product)"),
67027
+ sku: { type: "string", description: "SKU", required: false },
67028
+ barcode: { type: "string", description: "Barcode / GTIN (8, 12, 13 or 14 digits)", required: false },
67029
+ weight: { type: "string", description: "Weight value", required: false },
67030
+ "weight-unit": {
67031
+ type: "string",
67032
+ description: "kg | g | lb | oz (default kg)",
67033
+ required: false
67034
+ },
67035
+ price: {
67036
+ type: "string",
67037
+ description: `Price as a decimal, e.g. "649.95" (needs the user's yes)`,
67038
+ required: false
67039
+ },
67040
+ "sale-price": {
67041
+ type: "string",
67042
+ description: `Compare-at price, or "none" to clear it (needs the user's yes)`,
67043
+ required: false
67044
+ },
67045
+ ...JSON_ARGS
67046
+ },
67047
+ async (args5) => await stageFor(
67048
+ "shopify.variant.update",
67049
+ args5,
67050
+ () => defined({
67051
+ sku: args5.sku,
67052
+ barcode: args5.barcode,
67053
+ weight: weightOf(args5.weight, args5["weight-unit"]),
67054
+ price: args5.price,
67055
+ compareAtPrice: args5["sale-price"] === "none" ? null : args5["sale-price"]
67056
+ }),
67057
+ positionals(args5).length > 0 ? () => target3(args5, "option id") : void 0
67058
+ )
67059
+ );
67060
+ var fieldSet = leaf(
67061
+ "shopify.change.field.set",
67062
+ {
67063
+ name: "set",
67064
+ description: 'Set a custom field (metafield) on a product, an option or a collection. The owner is product:<ref>, variant:<id>, collection:<ref> or a collection staged in this chat (its shopify_temp_ ref); a bare reference is a product. If the store defines the field, send its type. Example: baker shopify change field set product:the-snowboard --namespace custom --key care --type single_line_text_field --value "Wash cold".'
67065
+ },
67066
+ {
67067
+ owner: TARGET("product:<ref>, variant:<id> or collection:<ref>"),
67068
+ namespace: { type: "string", description: "Namespace, e.g. custom", required: false },
67069
+ key: { type: "string", description: "Key, e.g. care", required: false },
67070
+ type: {
67071
+ type: "string",
67072
+ description: "Shopify's type, e.g. single_line_text_field, rich_text_field, json",
67073
+ required: false
67074
+ },
67075
+ value: { type: "string", description: "The value, as Shopify takes it for that type", required: false },
67076
+ ...JSON_ARGS
67077
+ },
67078
+ async (args5) => await stageFor(
67079
+ "shopify.metafield.set",
67080
+ args5,
67081
+ () => defined({ namespace: args5.namespace, key: args5.key, type: args5.type, value: args5.value }),
67082
+ () => target3(args5, "owner")
67083
+ )
67084
+ );
67085
+ var fieldRemove = leaf(
67086
+ "shopify.change.field.remove",
67087
+ {
67088
+ name: "remove",
67089
+ description: "Remove a custom field from a product, an option or a collection. Example: baker shopify change field remove product:the-snowboard custom.care"
67090
+ },
67091
+ {
67092
+ owner: TARGET("product:<ref>, variant:<id> or collection:<ref>"),
67093
+ field: TARGET("namespace.key, e.g. custom.care")
67094
+ },
67095
+ async (args5) => {
67096
+ const [owner, field] = positionals(args5);
67097
+ const [namespace, ...rest] = (field ?? "").split(".");
67098
+ if (!owner || !namespace || rest.length === 0) failValidation3("pass the owner and the field as namespace.key");
67099
+ await stage([{ kind: "shopify.metafield.delete", target: owner, payload: { namespace, key: rest.join(".") } }]);
67100
+ }
67101
+ );
67102
+ var collectionCreate = leaf(
67103
+ "shopify.change.collection.create",
67104
+ {
67105
+ name: "create",
67106
+ description: 'Create a collection of hand-picked products, shown on the online store (a second change, staged with it) unless --hidden. Example: baker shopify change collection create --title "Summer boards" --products snowboard-a,snowboard-b. Collections whose products join by rules are made in Shopify.'
67107
+ },
67108
+ {
67109
+ title: { type: "string", description: "Title", required: false },
67110
+ handle: {
67111
+ type: "string",
67112
+ description: "Its address (/collections/<handle>); default from the title",
67113
+ required: false
67114
+ },
67115
+ description: { type: "string", description: "Description, as HTML (no scripts or frames)", required: false },
67116
+ "seo-title": { type: "string", description: "Title shown in search results", required: false },
67117
+ "seo-description": { type: "string", description: "Description shown in search results", required: false },
67118
+ products: {
67119
+ type: "string",
67120
+ description: "Products, comma separated (ids, handles or links), in order",
67121
+ required: false
67122
+ },
67123
+ sort: {
67124
+ type: "string",
67125
+ description: "MANUAL | BEST_SELLING | ALPHA_ASC | ALPHA_DESC | PRICE_ASC | PRICE_DESC | CREATED | CREATED_DESC",
67126
+ required: false
67127
+ },
67128
+ hidden: { type: "boolean", description: "Don't show it on the online store yet", required: false },
67129
+ ...JSON_ARGS
67130
+ },
67131
+ async (args5) => await stageFor(
67132
+ "shopify.collection.create",
67133
+ args5,
67134
+ () => defined({
67135
+ title: args5.title,
67136
+ handle: args5.handle,
67137
+ descriptionHtml: args5.description,
67138
+ seo: args5["seo-title"] !== void 0 || args5["seo-description"] !== void 0 ? defined({ title: args5["seo-title"], description: args5["seo-description"] }) : void 0,
67139
+ products: list(args5.products),
67140
+ sortOrder: typeof args5.sort === "string" ? args5.sort.toUpperCase() : void 0,
67141
+ onlineStore: args5.hidden === true ? false : void 0
67142
+ })
67143
+ )
67144
+ );
67145
+ var collectionUpdate = leaf(
67146
+ "shopify.change.collection.update",
67147
+ {
67148
+ name: "update",
67149
+ description: `Change a collection's title, description, SEO or sort order (its address stays: stage a redirect to move it). Example: baker shopify change collection update summer-boards --title "Summer"`
67150
+ },
67151
+ {
67152
+ collection: TARGET("Collection id, handle or link"),
67153
+ title: { type: "string", description: "New title", required: false },
67154
+ description: { type: "string", description: "New description, as HTML", required: false },
67155
+ "seo-title": { type: "string", description: "Title shown in search results", required: false },
67156
+ "seo-description": { type: "string", description: "Description shown in search results", required: false },
67157
+ sort: { type: "string", description: "Sort order (see collection create)", required: false },
67158
+ ...JSON_ARGS
67159
+ },
67160
+ async (args5) => await stageFor(
67161
+ "shopify.collection.update",
67162
+ args5,
67163
+ () => defined({
67164
+ title: args5.title,
67165
+ descriptionHtml: args5.description,
67166
+ seo: args5["seo-title"] !== void 0 || args5["seo-description"] !== void 0 ? defined({ title: args5["seo-title"], description: args5["seo-description"] }) : void 0,
67167
+ sortOrder: typeof args5.sort === "string" ? args5.sort.toUpperCase() : void 0
67168
+ }),
67169
+ () => target3(args5, "collection")
67170
+ )
67171
+ );
67172
+ function collectionVerb(verb, kind, description) {
67173
+ return leaf(
67174
+ `shopify.change.collection.${verb}`,
67175
+ { name: verb, description },
67176
+ { collection: TARGET("Collection id, handle, link or a shopify_temp_ ref staged in this chat") },
67177
+ async (args5) => await stage([{ kind, target: target3(args5, "collection") }])
67178
+ );
67179
+ }
67180
+ function membership(verb, kind, description) {
67181
+ return leaf(
67182
+ `shopify.change.collection.${verb}`,
67183
+ { name: verb, description },
67184
+ {
67185
+ collection: TARGET("Collection id, handle, link or a shopify_temp_ ref"),
67186
+ products: TARGET("Products (ids, handles or links), space separated")
67187
+ },
67188
+ async (args5) => {
67189
+ const [collection, ...products] = positionals(args5);
67190
+ if (!collection || products.length === 0) failValidation3("pass the collection, then the products");
67191
+ await stage([{ kind, target: collection, payload: { products } }]);
67192
+ }
67193
+ );
67194
+ }
67195
+ function discountValue(args5) {
67196
+ if (args5.percent !== void 0) return { percentage: Number(args5.percent) };
67197
+ if (args5.amount !== void 0) return { amount: args5.amount };
67198
+ return void 0;
67199
+ }
67200
+ function appliesToOf(args5) {
67201
+ const products = list(args5.products);
67202
+ if (products) return { products };
67203
+ const collections = list(args5.collections);
67204
+ return collections ? { collections } : { all: true };
67205
+ }
67206
+ function combinesOf(value) {
67207
+ const combines = list(value) ?? [];
67208
+ if (combines.length === 0) return void 0;
67209
+ return {
67210
+ productDiscounts: combines.includes("product"),
67211
+ orderDiscounts: combines.includes("order"),
67212
+ shippingDiscounts: combines.includes("shipping")
67213
+ };
67214
+ }
67215
+ function numberOf3(value) {
67216
+ return value === void 0 ? void 0 : Number(value);
67217
+ }
67218
+ function discountPayload(args5) {
67219
+ return defined({
67220
+ code: args5.code,
67221
+ title: args5.title,
67222
+ value: discountValue(args5),
67223
+ appliesTo: appliesToOf(args5),
67224
+ minimumSubtotal: args5["min-subtotal"],
67225
+ minimumQuantity: numberOf3(args5["min-quantity"]),
67226
+ startsAt: args5.starts,
67227
+ endsAt: args5.ends,
67228
+ usageLimit: numberOf3(args5.limit),
67229
+ oncePerCustomer: args5["any-times"] === true ? false : void 0,
67230
+ combinesWith: combinesOf(args5.combines)
67231
+ });
67232
+ }
67233
+ var discountCreate = leaf(
67234
+ "shopify.change.discount.create",
67235
+ {
67236
+ name: "create",
67237
+ description: "Create a discount code for everyone who has it. Changes money: the user must say yes in the chat first \u2014 the command hands you the exact question. Example: baker shopify change discount create --code SPRING10 --percent 10 --ends 2026-10-12T23:59:00Z --limit 500. Applies to everything unless --products or --collections; starts when published unless --starts."
67238
+ },
67239
+ {
67240
+ code: { type: "string", description: "The code shoppers type, e.g. SPRING10", required: false },
67241
+ title: { type: "string", description: "What the store's staff see (default: the code)", required: false },
67242
+ percent: { type: "string", description: "Percentage off, 1-100", required: false },
67243
+ amount: { type: "string", description: "Amount off, as a decimal in the store's currency", required: false },
67244
+ products: { type: "string", description: "Only these products, comma separated", required: false },
67245
+ collections: { type: "string", description: "Only these collections, comma separated", required: false },
67246
+ "min-subtotal": { type: "string", description: "Minimum order amount", required: false },
67247
+ "min-quantity": { type: "string", description: "Minimum number of items", required: false },
67248
+ starts: { type: "string", description: "Start, ISO date-time (default: when published)", required: false },
67249
+ ends: { type: "string", description: "End, ISO date-time (default: no end)", required: false },
67250
+ limit: { type: "string", description: "Total number of uses", required: false },
67251
+ "any-times": { type: "boolean", description: "A customer may use it more than once", required: false },
67252
+ combines: {
67253
+ type: "string",
67254
+ description: "What it combines with, comma separated: product, order, shipping",
67255
+ required: false
67256
+ },
67257
+ ...JSON_ARGS
67258
+ },
67259
+ async (args5) => await stageFor("shopify.discountCode.create", args5, () => discountPayload(args5))
67260
+ );
67261
+ var discountStop = leaf(
67262
+ "shopify.change.discount.stop",
67263
+ {
67264
+ name: "stop",
67265
+ description: "Stop a discount code (it ends when published). Changes money: needs the user's yes. Undo puts its own dates back. Example: baker shopify change discount stop SPRING10"
67266
+ },
67267
+ { code: TARGET("The discount code") },
67268
+ async (args5) => await stage([{ kind: "shopify.discountCode.deactivate", target: target3(args5, "code") }])
67269
+ );
67270
+ var discountDates = leaf(
67271
+ "shopify.change.discount.dates",
67272
+ {
67273
+ name: "dates",
67274
+ description: "Change when a discount code runs. Changes money: needs the user's yes. Example: baker shopify change discount dates SPRING10 --starts 2026-10-01T00:00:00Z --ends never"
67275
+ },
67276
+ {
67277
+ code: TARGET("The discount code"),
67278
+ starts: { type: "string", description: "Start, ISO date-time", required: false },
67279
+ ends: { type: "string", description: 'End, ISO date-time, or "never"', required: false }
67280
+ },
67281
+ async (args5) => {
67282
+ if (typeof args5.starts !== "string" || typeof args5.ends !== "string") failValidation3("pass --starts and --ends");
67283
+ await stage([
67284
+ {
67285
+ kind: "shopify.discountCode.schedule",
67286
+ target: target3(args5, "code"),
67287
+ payload: { startsAt: args5.starts, endsAt: args5.ends === "never" ? null : args5.ends }
67288
+ }
67289
+ ]);
67290
+ }
67291
+ );
67292
+ var redirectCreate = leaf(
67293
+ "shopify.change.redirect.create",
67294
+ {
67295
+ name: "create",
67296
+ description: "Redirect an old address on the store to a new one (store paths, or a full https link). Example: baker shopify change redirect create --from /collections/old-boards --to /collections/summer-boards"
67297
+ },
67298
+ {
67299
+ from: { type: "string", description: "The old path, starting with /", required: false },
67300
+ to: { type: "string", description: "Where it sends shoppers: a path or an https link", required: false },
67301
+ ...JSON_ARGS
67302
+ },
67303
+ async (args5) => await stageFor("shopify.urlRedirect.create", args5, () => defined({ from: args5.from, to: args5.to }))
67304
+ );
67305
+ var redirectRemove = leaf(
67306
+ "shopify.change.redirect.remove",
67307
+ {
67308
+ name: "remove",
67309
+ description: "Remove a redirect, by the path it redirects. Example: baker shopify change redirect remove /old"
67310
+ },
67311
+ { from: TARGET("The redirected path") },
67312
+ async (args5) => await stage([{ kind: "shopify.urlRedirect.delete", target: target3(args5, "path") }])
67313
+ );
67314
+ var campaignAdd = leaf(
67315
+ "shopify.change.campaign.add",
67316
+ {
67317
+ name: "add",
67318
+ description: "Show a Baker ad campaign in the store's Shopify Marketing, so Shopify's reports credit its sales; Baker then sends its daily figures. Title, status, budget and spend come from the ad platform. Example: baker shopify change campaign add --platform google-ads --account 3865083115 --campaign 2101 --utm-campaign spring --utm-source google --utm-medium cpc"
67319
+ },
67320
+ {
67321
+ platform: { type: "string", description: "google-ads | meta", required: false },
67322
+ account: { type: "string", description: "The ad account id", required: false },
67323
+ campaign: { type: "string", description: "The campaign id", required: false },
67324
+ "utm-campaign": { type: "string", description: "utm_campaign its links carry", required: false },
67325
+ "utm-source": { type: "string", description: "utm_source", required: false },
67326
+ "utm-medium": { type: "string", description: "utm_medium", required: false }
67327
+ },
67328
+ async (args5) => await stage([
67329
+ {
67330
+ kind: "shopify.marketingActivity.upsert",
67331
+ payload: {
67332
+ platform: args5.platform,
67333
+ accountId: args5.account,
67334
+ campaignId: args5.campaign,
67335
+ utm: { campaign: args5["utm-campaign"], source: args5["utm-source"], medium: args5["utm-medium"] }
67336
+ }
67337
+ }
67338
+ ])
67339
+ );
67340
+ var campaignRemove = leaf(
67341
+ "shopify.change.campaign.remove",
67342
+ {
67343
+ name: "remove",
67344
+ description: "Take a Baker campaign out of Shopify Marketing. Example: baker shopify change campaign remove google-ads:3865083115:2101"
67345
+ },
67346
+ { campaign: TARGET("<platform>:<account>:<campaign>") },
67347
+ async (args5) => await stage([{ kind: "shopify.marketingActivity.delete", target: target3(args5, "campaign") }])
67348
+ );
67349
+ var measurementReconnect = leaf(
67350
+ "shopify.change.measurement.reconnect",
67351
+ {
67352
+ name: "reconnect",
67353
+ description: "Reconnect Baker's measurement on the store (the fix `baker shopify tracking audit` names when it is gone). Applies when published; Baker also repairs it on its own once a day."
67354
+ },
67355
+ {},
67356
+ async () => await stage([{ kind: "shopify.webPixel.update" }])
67357
+ );
67358
+ var group = (name, description, subCommands) => defineCommand211({ meta: { name, description }, subCommands });
67359
+ var changeCommand = defineCommand211({
67360
+ meta: {
67361
+ name: "change",
67362
+ description: `Stage a change to the connected store. Nothing changes until the user publishes the chat; every
67363
+ change can be put back with \`baker shopify undo\`. Prices and discounts need the user's yes first.
67364
+
67365
+ baker shopify change product update <product> --title \u2026 --seo-title \u2026 --add-tags a,b
67366
+ baker shopify change variant update <variantId> --sku \u2026 --barcode \u2026 --price \u2026
67367
+ baker shopify change field set product:<product> --namespace custom --key \u2026 --type \u2026 --value \u2026
67368
+ baker shopify change collection create --title \u2026 --products a,b
67369
+ baker shopify change discount create --code SPRING10 --percent 10 --ends <iso>
67370
+ baker shopify change redirect create --from /old --to /new
67371
+ baker shopify change campaign add --platform google-ads --account \u2026 --campaign \u2026
67372
+ baker shopify change measurement reconnect`
67373
+ },
67374
+ subCommands: {
67375
+ product: group("product", "Change a product's title, description, SEO, tags or type", { update: productUpdate }),
67376
+ variant: group("variant", "Change an option's SKU, barcode, weight or price", { update: variantUpdate }),
67377
+ field: group("field", "Set or remove a custom field", { set: fieldSet, remove: fieldRemove }),
67378
+ collection: group("collection", "Create, change, show, hide, fill or delete a collection", {
67379
+ create: collectionCreate,
67380
+ update: collectionUpdate,
67381
+ show: collectionVerb(
67382
+ "show",
67383
+ "shopify.collection.publish",
67384
+ "Show a collection on the online store. Example: baker shopify change collection show summer-boards"
67385
+ ),
67386
+ hide: collectionVerb(
67387
+ "hide",
67388
+ "shopify.collection.unpublish",
67389
+ "Hide a collection from the online store. Example: baker shopify change collection hide summer-boards"
67390
+ ),
67391
+ delete: collectionVerb(
67392
+ "delete",
67393
+ "shopify.collection.delete",
67394
+ "Delete a collection \u2014 only one undo could rebuild exactly (hand-picked products, no image, no custom fields, no discount or menu pointing at it); refused otherwise with the reason. Stage a redirect for its address. Example: baker shopify change collection delete old-boards"
67395
+ ),
67396
+ add: membership(
67397
+ "add",
67398
+ "shopify.collection.addProducts",
67399
+ "Add products to a collection. Example: baker shopify change collection add summer-boards snowboard-a snowboard-b"
67400
+ ),
67401
+ remove: membership(
67402
+ "remove",
67403
+ "shopify.collection.removeProducts",
67404
+ "Remove products from a collection. Example: baker shopify change collection remove summer-boards snowboard-a"
67405
+ )
67406
+ }),
67407
+ discount: group("discount", "Create, stop or reschedule a discount code (needs the user's yes)", {
67408
+ create: discountCreate,
67409
+ stop: discountStop,
67410
+ dates: discountDates
67411
+ }),
67412
+ redirect: group("redirect", "Create or remove a redirect", { create: redirectCreate, remove: redirectRemove }),
67413
+ campaign: group("campaign", "Show or remove a Baker campaign in Shopify Marketing", {
67414
+ add: campaignAdd,
67415
+ remove: campaignRemove
67416
+ }),
67417
+ measurement: group("measurement", "Reconnect Baker's measurement on the store", {
67418
+ reconnect: measurementReconnect
67419
+ })
67420
+ }
67421
+ });
67422
+
67423
+ // src/commands/shopify/changes.ts
67424
+ var changesCommand2 = leaf(
67425
+ "shopify.changes",
67426
+ {
67427
+ name: "changes",
67428
+ description: "The changes Baker made to the store, newest first, each with its changeId and whether it can be undone. Example: baker shopify changes --limit 20 (or --chat <id> for one chat's)."
67429
+ },
67430
+ {
67431
+ limit: { type: "string", description: "How many, 1-100 (default 20)", required: false },
67432
+ chat: { type: "string", description: "Only one chat's changes", required: false }
67433
+ },
67434
+ async (args5) => {
67435
+ try {
67436
+ const data = await apiGet(
67437
+ "/api/shopify/changes",
67438
+ params({ limit: args5.limit, chatId: args5.chat })
67439
+ );
67440
+ writeJson({ ok: true, hints: changesHints(data), data });
67441
+ } catch (err) {
67442
+ failChange(err);
67443
+ }
67444
+ }
67445
+ );
67446
+ var undoCommand = leaf(
67447
+ "shopify.undo",
67448
+ {
67449
+ name: "undo",
67450
+ description: "Stage the change that puts an applied store change back, read from the store as it was before it \u2014 applied when the user publishes, like any change. Prices and discounts ask the user again. Refused when the store changed since. Example: baker shopify undo <changeId> [<changeId>\u2026]"
67451
+ },
67452
+ { changeIds: { type: "positional", description: "changeIds from `baker shopify changes`", required: false } },
67453
+ async (args5) => {
67454
+ const changeIds = Array.isArray(args5._) ? args5._.filter((value) => typeof value === "string") : [];
67455
+ if (changeIds.length === 0) failValidation3("pass the changeIds to undo (see `baker shopify changes`)");
67456
+ try {
67457
+ const data = await apiPost("/api/shopify/undo", {
67458
+ chatId: requireChatId(),
67459
+ changeIds
67460
+ });
67461
+ emitStage(data);
67462
+ } catch (err) {
67463
+ failChange(err);
67464
+ }
67465
+ }
67466
+ );
67467
+
67468
+ // src/commands/shopify/draft.ts
67469
+ import { defineCommand as defineCommand212 } from "citty";
67470
+ function refOf(args5) {
67471
+ const [ref] = Array.isArray(args5._) ? args5._ : [];
67472
+ if (typeof ref !== "string" || ref === "")
67473
+ failValidation3("pass the staged change's ref (see `baker shopify draft list`)");
67474
+ return ref;
67475
+ }
67476
+ function listHints(data) {
67477
+ if (data.status === "none" || data.count === 0) return ["No store changes are staged on this chat."];
67478
+ if (data.status === "active") {
67479
+ return [
67480
+ "Staged only: these apply when the user publishes the chat. Correct one with `baker shopify draft amend <ref>`, drop one with `remove`."
67481
+ ];
67482
+ }
67483
+ const failed = data.ops.filter((op) => op.result?.status === "failed" || op.result?.status === "skipped");
67484
+ return failed.length > 0 ? [`${failed.length} of ${data.count} changes did not go through \u2014 say which and why; never report them as made.`] : [];
67485
+ }
67486
+ var list2 = leaf(
67487
+ "shopify.draft.list",
67488
+ {
67489
+ name: "list",
67490
+ description: "Start here to review the store changes staged on this chat (or another chat's with --chat): what each is, whether it changes money, and after publish what happened to it."
67491
+ },
67492
+ { chat: CHAT_READ_ARG },
67493
+ async (args5) => {
67494
+ try {
67495
+ const data = await apiPost("/api/shopify/draft/list", {
67496
+ chatId: resolveChatId(args5.chat)
67497
+ });
67498
+ writeJson({ ok: true, hints: listHints(data), data });
67499
+ } catch (err) {
67500
+ failChange(err);
67501
+ }
67502
+ }
67503
+ );
67504
+ var show = leaf(
67505
+ "shopify.draft.show",
67506
+ {
67507
+ name: "show",
67508
+ description: "One staged store change in full: what it sends and the store as it was read when it was staged (the before)."
67509
+ },
67510
+ { ref: { type: "positional", description: "The staged change's ref", required: false }, chat: CHAT_READ_ARG },
67511
+ async (args5) => {
67512
+ try {
67513
+ const data = await apiPost("/api/shopify/draft/show", { chatId: resolveChatId(args5.chat), ref: refOf(args5) });
67514
+ writeJson({ ok: true, data });
67515
+ } catch (err) {
67516
+ failChange(err);
67517
+ }
67518
+ }
67519
+ );
67520
+ var amend = leaf(
67521
+ "shopify.draft.amend",
67522
+ {
67523
+ name: "amend",
67524
+ description: `Correct a staged change in place: a JSON patch merged into its payload (objects merge, null removes a key) and every check run again \u2014 a changed price asks the user again. Example: baker shopify draft amend product:123 --patch '{"title":"Better"}'. Keys are the payload's, not the flags': productType (not type), seo {title, description}, addTags, compareAtPrice; a key it doesn't take is refused with the ones it does.`
67525
+ },
67526
+ {
67527
+ ref: { type: "positional", description: "The staged change's ref", required: false },
67528
+ patch: { type: "string", description: "Inline JSON patch", required: false },
67529
+ file: { type: "string", description: "A JSON file with the patch", required: false }
67530
+ },
67531
+ async (args5) => {
67532
+ try {
67533
+ const data = await apiPost("/api/shopify/draft/amend", {
67534
+ chatId: requireChatId(),
67535
+ ref: refOf(args5),
67536
+ patch: loadJsonArg(args5, "patch")
67537
+ });
67538
+ emitStage(data);
67539
+ } catch (err) {
67540
+ failChange(err);
67541
+ }
67542
+ }
67543
+ );
67544
+ var remove = leaf(
67545
+ "shopify.draft.remove",
67546
+ {
67547
+ name: "remove",
67548
+ description: "Drop one staged store change, and any change that needs it (a new collection's show)."
67549
+ },
67550
+ { ref: { type: "positional", description: "The staged change's ref", required: false } },
67551
+ async (args5) => {
67552
+ try {
67553
+ writeJson({
67554
+ ok: true,
67555
+ data: await apiPost("/api/shopify/draft/remove", { chatId: requireChatId(), ref: refOf(args5) })
67556
+ });
67557
+ } catch (err) {
67558
+ failChange(err);
67559
+ }
67560
+ }
67561
+ );
67562
+ var clear = leaf(
67563
+ "shopify.draft.clear",
67564
+ { name: "clear", description: "Drop every store change staged on this chat." },
67565
+ {},
67566
+ async () => {
67567
+ try {
67568
+ writeJson({ ok: true, data: await apiPost("/api/shopify/draft/clear", { chatId: requireChatId() }) });
67569
+ } catch (err) {
67570
+ failChange(err);
67571
+ }
67572
+ }
67573
+ );
67574
+ var draftCommand5 = defineCommand212({
67575
+ meta: { name: "draft", description: "List, show, amend, remove or clear the store changes staged on this chat" },
67576
+ subCommands: { list: list2, show, amend, remove, clear }
67577
+ });
67578
+
67579
+ // src/commands/shopify/promotions.ts
67580
+ import { defineCommand as defineCommand213 } from "citty";
67581
+
66423
67582
  // src/commands/shopify/landingLinks.ts
66424
67583
  import { readdir as readdir16, readFile as readFile33, stat as stat11 } from "fs/promises";
66425
67584
  import path47 from "path";
@@ -66428,6 +67587,33 @@ var FILE_BYTES_MAX = 256 * 1024;
66428
67587
  function escaped(text2) {
66429
67588
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
66430
67589
  }
67590
+ var BUY_COMPONENT = /<(?:ShopifyProduct|ShopifyBuyButton)\b/g;
67591
+ var NAMED_PRODUCT = /\bproduct\s*(?:=\s*\{?\s*|:\s*)(["'`])([^"'`]*)\1/g;
67592
+ function step(scan, char) {
67593
+ if (scan.quote) return char === scan.quote ? { ...scan, quote: null } : scan;
67594
+ if (char === '"' || char === "'" || char === "`") return { ...scan, quote: char };
67595
+ if (char === "{") return { ...scan, depth: scan.depth + 1 };
67596
+ if (char === "}") return { ...scan, depth: Math.max(0, scan.depth - 1) };
67597
+ return scan;
67598
+ }
67599
+ function tagText(text2, start) {
67600
+ let scan = { depth: 0, quote: null };
67601
+ for (let index = start + 1; index < text2.length; index += 1) {
67602
+ const char = text2[index];
67603
+ if (char === ">" && scan.depth === 0 && !scan.quote) return text2.slice(start, index + 1);
67604
+ scan = step(scan, char);
67605
+ }
67606
+ return text2.slice(start);
67607
+ }
67608
+ function namesProduct(text2, keys) {
67609
+ const wanted = new Set([keys.handle.toLowerCase(), ...keys.productId ? [keys.productId] : []].filter(Boolean));
67610
+ for (const tag of text2.matchAll(BUY_COMPONENT)) {
67611
+ for (const named of tagText(text2, tag.index).matchAll(NAMED_PRODUCT)) {
67612
+ if (wanted.has((named[2] ?? "").trim().toLowerCase())) return true;
67613
+ }
67614
+ }
67615
+ return false;
67616
+ }
66431
67617
  function evidenceIn(text2, keys) {
66432
67618
  const evidence = [];
66433
67619
  if (keys.buyUrls.some((url) => url && text2.includes(url))) evidence.push("buy_link");
@@ -66437,6 +67623,7 @@ function evidenceIn(text2, keys) {
66437
67623
  (id) => new RegExp(`(?:/cart/(?:\\d+:\\d+,)*${id}:|variant=${id}(?!\\d))`).test(text2)
66438
67624
  );
66439
67625
  if (option) evidence.push("variant_id");
67626
+ if (namesProduct(text2, keys)) evidence.push("buy_button");
66440
67627
  return evidence;
66441
67628
  }
66442
67629
  function landingLinksOf(pages, keys) {
@@ -66449,12 +67636,12 @@ function landingLinksOf(pages, keys) {
66449
67636
  for (const evidence of found) held.add(evidence);
66450
67637
  bySlug.set(slug, held);
66451
67638
  }
66452
- const order = ["buy_link", "product_page", "variant_id"];
67639
+ const order = ["buy_link", "product_page", "variant_id", "buy_button"];
66453
67640
  return [...bySlug].map(([slug, evidence]) => ({ slug, evidence: order.filter((entry) => evidence.has(entry)) }));
66454
67641
  }
66455
- async function isDir4(target2) {
67642
+ async function isDir4(target4) {
66456
67643
  try {
66457
- return (await stat11(target2)).isDirectory();
67644
+ return (await stat11(target4)).isDirectory();
66458
67645
  } catch {
66459
67646
  return false;
66460
67647
  }
@@ -66492,15 +67679,17 @@ function plural(count2, one, many = `${one}s`) {
66492
67679
  }
66493
67680
  function pauseCommand2(ad) {
66494
67681
  if (ad.platform === "meta") return `\`baker ads meta ads pause ${ad.id ?? "<id>"}\``;
66495
- const target2 = `${ad.resource ?? "<resource>"} --customer-id ${ad.account_id ?? "<account_id>"}`;
66496
- return ad.level === "asset_group" ? `\`baker ads google asset-groups update ${target2} --status PAUSED\`` : `\`baker ads google ads pause ${target2}\``;
67682
+ const target4 = `${ad.resource ?? "<resource>"} --customer-id ${ad.account_id ?? "<account_id>"}`;
67683
+ return ad.level === "asset_group" ? `\`baker ads google asset-groups update ${target4} --status PAUSED\`` : `\`baker ads google ads pause ${target4}\``;
66497
67684
  }
66498
67685
  function pauseHint(answer) {
67686
+ if (answer.product.stock === "in_stock" || answer.ads.length === 0) return null;
66499
67687
  const active = answer.ads.filter((ad) => ad.status === "active");
66500
- if (answer.product.stock === "in_stock" || active.length === 0) return null;
67688
+ if (active.length === 0) {
67689
+ return `Every ad that sends people to \u201C${answer.product.title}\u201D is already paused: stage nothing for it. Tell the user this run paused nothing, and promise nothing about these ads coming back when it is back in stock.`;
67690
+ }
66501
67691
  const one = active.length === 1;
66502
- const remembered = answer.remembered ? " A pause is remembered for this product only because you read its promotions here." : "";
66503
- return `${plural(active.length, "active ad")} still ${one ? "sends" : "send"} people to \u201C${answer.product.title}\u201D, which is out of stock. Stage pausing ${one ? "it" : "them"}: ${active.map(pauseCommand2).join("; ")}.${remembered}`;
67692
+ return `${plural(active.length, "active ad")} still ${one ? "sends" : "send"} people to \u201C${answer.product.title}\u201D, which is out of stock. Stage pausing ${one ? "it" : "them"}: ${active.map(pauseCommand2).join("; ")}. Each pause waits for the user to review and publish, and nothing turns it back on by itself.`;
66504
67693
  }
66505
67694
  function promotionHints(answer, scan) {
66506
67695
  const hints2 = [];
@@ -66520,7 +67709,10 @@ function promotionHints(answer, scan) {
66520
67709
  hints2.push(`/${landing.slug} links to this product but is not live; no ad can send people to it yet.`);
66521
67710
  }
66522
67711
  if (answer.ads.length === 0 && answer.landings.length === 0 && answer.shopping.length === 0) {
66523
- hints2.push(`Nothing advertises \u201C${answer.product.title}\u201D that Baker can see: nothing to pause.`);
67712
+ const nothing = `Nothing advertises \u201C${answer.product.title}\u201D that Baker can see`;
67713
+ hints2.push(
67714
+ answer.product.stock === "in_stock" ? `${nothing}.` : `${nothing}: stage nothing for it. Tell the user this run paused nothing for it, and promise nothing about ads coming back when it is back in stock.`
67715
+ );
66524
67716
  }
66525
67717
  if (!scan.scanned) {
66526
67718
  hints2.push(
@@ -66529,15 +67721,25 @@ function promotionHints(answer, scan) {
66529
67721
  } else if (scan.skippedPages > 0) {
66530
67722
  hints2.push(`${plural(scan.skippedPages, "page")} not checked for links to this product (past the 300-page bound).`);
66531
67723
  }
66532
- if (!answer.remembered) {
67724
+ if (!answer.remembered && answer.ads.length > 0) {
66533
67725
  hints2.push(
66534
- "This read was not remembered for a Session (no chat): a pause staged from it will not be undone when the product is back."
67726
+ "This read was made outside a Session (no chat): a pause staged from it will never be offered back for restarting when the product returns."
66535
67727
  );
66536
67728
  }
66537
67729
  return hints2;
66538
67730
  }
66539
- function holdName(hold) {
66540
- return `${PLATFORM[hold.platform]} ${hold.level.replace("_", " ")} ${hold.name ? `\u201C${hold.name}\u201D` : hold.entity_id}`;
67731
+
67732
+ // src/commands/shopify/stockHoldReport.ts
67733
+ var PLATFORM2 = { "google-ads": "Google Ads", meta: "Meta" };
67734
+ var LEVEL = {
67735
+ campaign: "campaign",
67736
+ ad_group: "ad group",
67737
+ ad: "ad",
67738
+ asset_group: "asset group",
67739
+ adset: "ad set"
67740
+ };
67741
+ function adName(ad) {
67742
+ return `${PLATFORM2[ad.platform]} ${LEVEL[ad.level]} ${ad.name ? `\u201C${ad.name}\u201D` : ad.entity_id}`;
66541
67743
  }
66542
67744
  var GOOGLE_GROUP = {
66543
67745
  campaign: "campaigns",
@@ -66546,10 +67748,6 @@ var GOOGLE_GROUP = {
66546
67748
  asset_group: "asset-groups",
66547
67749
  adset: "ads"
66548
67750
  };
66549
- function googleResume(hold) {
66550
- const target2 = `${hold.entity_id} --customer-id ${hold.account_id}`;
66551
- return hold.level === "asset_group" ? `baker ads google asset-groups update ${target2} --status ENABLED` : `baker ads google ${GOOGLE_GROUP[hold.level]} resume ${target2}`;
66552
- }
66553
67751
  var META_GROUP = {
66554
67752
  campaign: "campaigns",
66555
67753
  ad_group: "adsets",
@@ -66558,46 +67756,92 @@ var META_GROUP = {
66558
67756
  adset: "adsets"
66559
67757
  };
66560
67758
  function resumeCommand2(hold) {
66561
- return hold.platform === "meta" ? `\`baker ads meta ${META_GROUP[hold.level]} resume ${hold.entity_id}\`` : `\`${googleResume(hold)}\``;
67759
+ if (hold.platform === "meta") return `baker ads meta ${META_GROUP[hold.level]} resume ${hold.entity_id}`;
67760
+ const target4 = `${hold.entity_id} --customer-id ${hold.account_id}`;
67761
+ return hold.level === "asset_group" ? `baker ads google asset-groups update ${target4} --status ENABLED` : `baker ads google ${GOOGLE_GROUP[hold.level]} resume ${target4}`;
67762
+ }
67763
+ function quoted(title, fallback) {
67764
+ return title ? `\u201C${title}\u201D` : fallback;
67765
+ }
67766
+ function heldLine(hold) {
67767
+ const name = adName(hold);
67768
+ const product = quoted(hold.product.title, "the product");
67769
+ switch (hold.verdict) {
67770
+ case "resumable":
67771
+ return `Ready to turn back on when you publish: ${name} \u2014 Baker paused it because ${product} ran out.`;
67772
+ case "still_out_of_stock":
67773
+ return `Stays paused: ${name} \u2014 ${product} still cannot be bought.`;
67774
+ case "changed_since":
67775
+ return `Left as it is: ${name} \u2014 someone changed it after Baker paused it when ${product} ran out, so check it yourself.`;
67776
+ case "still_promotes_out_of_stock":
67777
+ return `Stays paused: ${name} \u2014 it also sends people to ${quoted(hold.verdict_detail, "another product")}, which is still out of stock.`;
67778
+ case "cannot_confirm":
67779
+ return `Left as it is: ${name} \u2014 Baker cannot tell whether someone changed it after pausing it when ${product} ran out, so check it before turning it back on.`;
67780
+ case "already_active":
67781
+ return `Nothing to do: ${name} is already running again.`;
67782
+ default:
67783
+ return null;
67784
+ }
66562
67785
  }
66563
- function stockHoldHints(answer) {
66564
- const hints2 = [];
66565
- const held = answer.holds.filter((hold) => hold.state === "held");
66566
- if (answer.holds.length === 0)
66567
- hints2.push("Nothing was paused because this product ran out: there is nothing to resume.");
66568
- const resumable = held.filter((hold) => hold.verdict === "resumable");
66569
- if (resumable.length > 0) {
66570
- hints2.push(
66571
- `${resumable.length} can be resumed: stage ${resumable.map(resumeCommand2).join("; ")}. Resume nothing else.`
66572
- );
67786
+ var CLOSED_HOW = {
67787
+ resumed_by_back_in_stock_run: "turned back on by an earlier back-in-stock run",
67788
+ changed_by_baker: "turned back on by other work published in Baker",
67789
+ already_active: "found running again, turned back on by someone",
67790
+ removed: "removed",
67791
+ product_removed: "let go when the product left the store"
67792
+ };
67793
+ function plural2(count2, one) {
67794
+ return `${count2} ${count2 === 1 ? one : `${one}s`}`;
67795
+ }
67796
+ function nothingHeldLines(answer, asked, product) {
67797
+ if (!asked.product) {
67798
+ return answer.holds.length > 0 ? ["None of these is still paused because its product ran out, so there is nothing to turn back on."] : ["Baker has no ad paused because a product ran out, so there is nothing to turn back on."];
66573
67799
  }
66574
- for (const hold of held) {
66575
- if (hold.verdict === "changed_since") {
66576
- hints2.push(`${holdName(hold)} was changed by someone after Baker paused it: leave it, and tell the user.`);
66577
- } else if (hold.verdict === "still_promotes_out_of_stock") {
66578
- hints2.push(
66579
- `${holdName(hold)} still sends people to \u201C${hold.verdict_detail ?? "another product"}\u201D, which is out: leave it until that is back.`
66580
- );
66581
- } else if (hold.verdict === "cannot_confirm") {
66582
- hints2.push(
66583
- `${holdName(hold)} could not be confirmed as untouched since Baker paused it: leave it, and tell the user to check it.`
66584
- );
66585
- } else if (hold.verdict === "already_active") {
66586
- hints2.push(`${holdName(hold)} is already running again: nothing to do.`);
66587
- }
67800
+ if (answer.pending_sessions.length > 0) {
67801
+ return [
67802
+ `Baker's pause for ${product} is still waiting for your review and was never published, so there is nothing to turn back on.`
67803
+ ];
66588
67804
  }
66589
- for (const session of answer.pending_sessions) {
66590
- hints2.push(
66591
- `The out-of-stock work \u201C${session.title}\u201D is still waiting for review and would pause ${plural(session.staged_pauses, "ad")} for this product: tell the user to discard it.`
67805
+ if (answer.recently_closed.length > 0) {
67806
+ return answer.recently_closed.map(
67807
+ (hold) => `Already handled: Baker paused ${adName(hold)} when ${product} ran out, and it was later ${CLOSED_HOW[hold.resolved_reason]}.`
66592
67808
  );
66593
67809
  }
66594
- return hints2;
67810
+ return [`Baker has nothing paused because ${product} ran out, so there is nothing to turn back on.`];
67811
+ }
67812
+ function stockHoldReport(answer, asked) {
67813
+ const product = quoted(answer.product?.title, "the product that came back");
67814
+ const held = answer.holds.filter((hold) => hold.state === "held");
67815
+ const tell_user = [
67816
+ ...held.length === 0 ? nothingHeldLines(answer, asked, product) : [],
67817
+ ...held.flatMap((hold) => heldLine(hold) ?? []),
67818
+ ...answer.not_paused.map((ad) => `Left as it is: ${adName(ad)} \u2014 Baker did not pause it when ${product} ran out.`),
67819
+ ...answer.pending_sessions.map(
67820
+ (pending) => `Discard the work waiting for your review, \u201C${pending.title}\u201D: it would pause ${plural2(pending.staged_pauses, "ad")} for ${product}, which can be bought again.`
67821
+ )
67822
+ ];
67823
+ const resume = held.filter((hold) => hold.verdict === "resumable").map((hold) => ({ ad: adName(hold), command: resumeCommand2(hold) }));
67824
+ const pages = answer.pages_changed.map((run) => ({
67825
+ pages: run.pages.map((slug) => `/${slug}`),
67826
+ read: `baker chats diff ${run.chat_id} --surface landings --full`
67827
+ }));
67828
+ const hints2 = [
67829
+ resume.length > 0 ? "Stage each `resume` command, and nothing else." : `Stage nothing${asked.product ? ` for ${product}` : ""}.`,
67830
+ "Tell the user the `tell_user` lines as they are written, and nothing about any other ad: an ad this read does not name is not this run's to explain.",
67831
+ ...pages.map(
67832
+ (run) => `The out-of-stock run also changed ${run.pages.join(", ")}: read those changes with its \`read\` command, and put back only what it changed for ${product}.`
67833
+ )
67834
+ ];
67835
+ const compact3 = { tell_user, resume, ...pages.length > 0 ? { pages } : {} };
67836
+ if (!asked.full) return { data: compact3, hints: hints2 };
67837
+ const { store: _store, ...everything } = answer;
67838
+ return { data: { ...compact3, ...everything }, hints: hints2 };
66595
67839
  }
66596
67840
 
66597
67841
  // src/commands/shopify/promotions.ts
66598
67842
  registerSchema({
66599
67843
  command: "shopify.promotions",
66600
- description: "Start here when a product runs out of stock or comes back: what is advertising it \u2014 ads on Google Ads and Meta that lead to its store page, its buy link or a landing that links to it (`via`), the landings themselves (from this workspace's pages, and from measured visits), and the Shopping campaigns and catalog ads that show it (they stop by themselves when it runs out \u2014 never pause them). Read it in the Session that pauses: a pause is remembered \u2014 and undone when the product is back in stock \u2014 only for a product whose promotions that Session read. `checked.not_checked` names a platform Baker could not read.",
67844
+ description: "Start here when a product runs out of stock or comes back: what is advertising it \u2014 ads on Google Ads and Meta that lead to its store page, its buy link or a landing that links to it (`via`), the landings themselves (from this workspace's pages, and from measured visits), and the Shopping campaigns and catalog ads that show it (they stop by themselves when it runs out \u2014 never pause them). Read it in the Session that pauses: a pause is remembered only for a product whose promotions that Session read, so that \u201CRestart ads when a product is back\u201D, if the company has it, can later stage turning it back on for the user to review \u2014 nothing restarts by itself, and every pause waits for the user to review and publish. `checked.not_checked` names a platform Baker could not read.",
66601
67845
  args: {
66602
67846
  product: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
66603
67847
  full: {
@@ -66608,14 +67852,19 @@ registerSchema({
66608
67852
  });
66609
67853
  registerSchema({
66610
67854
  command: "shopify.stock-holds",
66611
- description: "Start here when a product is back in stock: the ads Baker paused because it ran out, each with a `verdict` \u2014 `resumable` (resume it), `still_out_of_stock`, `changed_since` (someone changed it after Baker paused it: leave it), `already_active`, `still_promotes_out_of_stock` (it also sends people to another product that is still out: `verdict_detail` names it), `cannot_confirm`. Resume only what is resumable. `pending_sessions` lists out-of-stock work still waiting for review that would pause this product's ads: tell the user to discard it.",
67855
+ description: "Start here when a product is back in stock: what Baker paused because it ran out, answered for the marketer. `resume` holds the one command to stage per ad Baker may turn back on now \u2014 stage those and nothing else. `tell_user` holds one sentence per ad (and per piece of out-of-stock work still waiting for review), written for the marketer: tell them those, as written. `pages` names landings the out-of-stock run changed and the read that shows those page changes. Use `--product` for the product that came back: the answer then speaks only of its ads. `--full` adds the codes behind each sentence (verdicts, ids) for debugging, never for the user.",
66612
67856
  args: {
66613
67857
  product: { type: "string", description: "Only one product's holds: its id, handle or link", required: false },
66614
67858
  all: { type: "boolean", description: "Resolved holds too (resumed or released)", required: false, default: false },
66615
67859
  full: fullArg
66616
67860
  }
66617
67861
  });
66618
- var promotionsCommand = defineCommand210({
67862
+ function promotionsShown(answer, full) {
67863
+ if (full) return answer;
67864
+ const { remembered: _remembered, ...shown } = answer;
67865
+ return shown;
67866
+ }
67867
+ var promotionsCommand = defineCommand213({
66619
67868
  meta: {
66620
67869
  name: "promotions",
66621
67870
  description: "What advertises one product: ads that lead to it, landings that link to it, Shopping that shows it. Example: baker shopify promotions 10310413942969"
@@ -66631,8 +67880,8 @@ var promotionsCommand = defineCommand210({
66631
67880
  },
66632
67881
  run: async ({ args: args5 }) => {
66633
67882
  try {
66634
- const positionals = Array.isArray(args5._) ? args5._ : [];
66635
- if (positionals.length > 1) {
67883
+ const positionals2 = Array.isArray(args5._) ? args5._ : [];
67884
+ if (positionals2.length > 1) {
66636
67885
  throw new ApiError("VALIDATION_ERROR", "product: Pass one product id, handle or product link.");
66637
67886
  }
66638
67887
  const { product, store } = await apiGet(
@@ -66642,6 +67891,7 @@ var promotionsCommand = defineCommand210({
66642
67891
  const scan = await readWorkspacePages(process.cwd());
66643
67892
  const linked = scan ? landingLinksOf(scan.pages, {
66644
67893
  handle: product.handle,
67894
+ productId: product.productId,
66645
67895
  variantIds: product.variants.map((variant) => variant.variantId),
66646
67896
  buyUrls: product.variants.flatMap((variant) => variant.buyUrl ? [variant.buyUrl] : [])
66647
67897
  }) : void 0;
@@ -66651,7 +67901,7 @@ var promotionsCommand = defineCommand210({
66651
67901
  scanned: scan !== null,
66652
67902
  ...args5.full ? { full: true } : {}
66653
67903
  });
66654
- emit2(answer, { store }, [
67904
+ emit2(promotionsShown(answer, args5.full === true), { store }, [
66655
67905
  ...storeStateHints(store),
66656
67906
  ...promotionHints(answer, { scanned: scan !== null, skippedPages: scan?.skipped ?? 0 })
66657
67907
  ]);
@@ -66660,7 +67910,7 @@ var promotionsCommand = defineCommand210({
66660
67910
  }
66661
67911
  }
66662
67912
  });
66663
- var stockHoldsCommand = defineCommand210({
67913
+ var stockHoldsCommand = defineCommand213({
66664
67914
  meta: {
66665
67915
  name: "stock-holds",
66666
67916
  description: "The ads Baker paused because a product ran out, and whether each may be resumed now. Example: baker shopify stock-holds --product 10310413942969"
@@ -66676,18 +67926,44 @@ var stockHoldsCommand = defineCommand210({
66676
67926
  "/api/shopify/stock-holds",
66677
67927
  params({ product: args5.product, all: args5.all, full: args5.full })
66678
67928
  );
66679
- const { store, ...data } = answer;
66680
- emit2(data, { store, count: answer.holds.length }, [...storeStateHints(store), ...stockHoldHints(answer)]);
67929
+ const report = stockHoldReport(answer, { product: args5.product !== void 0, full: args5.full === true });
67930
+ emit2(report.data, { store: answer.store, count: answer.holds.length }, [
67931
+ ...storeStateHints(answer.store),
67932
+ ...report.hints
67933
+ ]);
67934
+ } catch (err) {
67935
+ fail9(err);
67936
+ }
67937
+ }
67938
+ });
67939
+
67940
+ // src/commands/shopify/tracking.ts
67941
+ import { defineCommand as defineCommand214 } from "citty";
67942
+ var audit = leaf(
67943
+ "shopify.tracking.audit",
67944
+ {
67945
+ name: "audit",
67946
+ description: "Start here for 'is our store's tracking set up right?': Baker's measurement on the store, Baker switched on in the theme, whether orders carry Baker's visitor, who reports sales to Meta and Google, shoppers' consent, and whether the store sells through its own website. Each check is ok, problem (with its fix: a change to stage, a task for the merchant, or a decision) or unknown (with why). Reads only \u2014 it changes nothing."
67947
+ },
67948
+ { full: { type: "boolean", description: "Add the facts behind each check", required: false } },
67949
+ async (args5) => {
67950
+ try {
67951
+ const data = await apiPost("/api/shopify/tracking/audit", { full: args5.full === true });
67952
+ writeJson({ ok: true, hints: auditHints(data), data });
66681
67953
  } catch (err) {
66682
67954
  fail9(err);
66683
67955
  }
66684
67956
  }
67957
+ );
67958
+ var trackingCommand2 = defineCommand214({
67959
+ meta: { name: "tracking", description: "Check how the store is measured: baker shopify tracking audit" },
67960
+ subCommands: { audit }
66685
67961
  });
66686
67962
 
66687
67963
  // src/commands/shopify/index.ts
66688
67964
  registerSchema({
66689
67965
  command: "shopify.store",
66690
- description: 'Start here for the connected Shopify store: its name, currency, home country, how many products and collections it has, how many products are out of stock or partly out, the prices by country it sells at (only when it has more than one), where its buy links send a shopper (`buyDestination`: checkout, store_cart or product_page), its languages and policies, whether it asks for a password, and the link to the Products page in Baker. `capped: true` means a count stopped at its ceiling (100 out of stock or partly out, 500 collections): say "100 or more", never the number as exact. `meta.store` says whether Baker\'s copy is complete and whether Baker can still reach the store.',
67966
+ description: 'Start here for the connected Shopify store: its name, currency, home country, how many products and collections it has, how many products are out of stock or partly out, the prices by country it sells at (only when it has more than one), where its buy links send a shopper (`buyDestination`: checkout, store_cart or product_page), its languages and policies, whether it asks for a password, and the link to the Products page in Baker. `capped: true` means a count stopped at its ceiling (100 out of stock or partly out, 500 collections): say "100 or more", never the number as exact. `meta.store` says whether Baker\'s copy is complete and whether Baker can still reach the store. `canChange` says which kinds of store change the store allows Baker to stage (products, publishing, discounts, redirects, marketing); a `false` one is allowed by the user in Shopify (`request_connection`).',
66691
67967
  args: {
66692
67968
  full: {
66693
67969
  ...fullArg,
@@ -66722,7 +67998,7 @@ registerSchema({
66722
67998
  });
66723
67999
  registerSchema({
66724
68000
  command: "shopify.product",
66725
- description: "One product of the connected store \u2014 accepts its id, its handle, a link to it on the store, in Baker or in Shopify admin. Returns its options with price, stock and `buyUrl` (the link for a page's buy button, one per option \u2014 never build a Shopify link yourself, never use `storeUrl` for it), prices by country, the options out of stock, its collections, its photos' library imageIds to place on a landing (`baker images download <imageIds> --out src/pages/<page>/_images/`) or pass to a creative, its videos' playbackIds (`<Video playbackId=\u2026/>`), and `contentSummary`: what else Baker keeps about it. Start here for a landing from a product: add `--content` for the formatted description, every custom field by its name (a size chart, care instructions), the rating, subscriptions and translations; `--field <name>` returns one field whole. `--out-of-stock` lists only the options out of stock.",
68001
+ description: "One product of the connected store \u2014 accepts its id, its handle, a link to it on the store, in Baker or in Shopify admin. Returns its options with price, stock and `buyUrl` (each option's buy link for ads, emails and messages; on a landing use `<ShopifyProduct>` instead \u2014 never build a Shopify link yourself, never use `storeUrl` for it), prices by country, the options out of stock, its collections, its photos' library imageIds to place on a landing (`baker images download <imageIds> --out src/pages/<page>/_images/`) or pass to a creative, its videos' playbackIds (`<Video playbackId=\u2026/>`), and `contentSummary`: what else Baker keeps about it. Start here for a landing from a product: add `--content` for the formatted description, every custom field by its name (a size chart, care instructions), the rating, subscriptions and translations; `--field <name>` returns one field whole. `--out-of-stock` lists only the options out of stock.",
66726
68002
  args: {
66727
68003
  ref: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
66728
68004
  "variants-cursor": {
@@ -66812,7 +68088,7 @@ registerSchema({
66812
68088
  }
66813
68089
  }
66814
68090
  });
66815
- var salesCommand = defineCommand211({
68091
+ var salesCommand = defineCommand215({
66816
68092
  meta: {
66817
68093
  name: "sales",
66818
68094
  description: "What the store sold: orders, sales, refunds and net, by day, product, campaign or landing \u2014 or when buyers order (weekday, hour, month). Example: baker shopify sales --days 7 --by campaign"
@@ -66850,7 +68126,7 @@ var salesCommand = defineCommand211({
66850
68126
  }
66851
68127
  }
66852
68128
  });
66853
- var storeCommand = defineCommand211({
68129
+ var storeCommand = defineCommand215({
66854
68130
  meta: {
66855
68131
  name: "store",
66856
68132
  description: "The connected Shopify store: currency, countries, how many products, what is out of stock. Example: baker shopify store"
@@ -66861,13 +68137,14 @@ var storeCommand = defineCommand211({
66861
68137
  run: async ({ args: args5 }) => {
66862
68138
  try {
66863
68139
  const answer = await apiGet("/api/shopify/store", params({ full: args5.full }));
66864
- emit2(answer.summary, { store: answer.store }, [...storeStateHints(answer.store), ...storeHints(answer.summary)]);
68140
+ const data = answer.canChange ? { ...answer.summary, canChange: answer.canChange } : answer.summary;
68141
+ emit2(data, { store: answer.store }, [...storeStateHints(answer.store), ...storeHints(answer.summary)]);
66865
68142
  } catch (err) {
66866
68143
  fail9(err);
66867
68144
  }
66868
68145
  }
66869
68146
  });
66870
- var productsCommand = defineCommand211({
68147
+ var productsCommand = defineCommand215({
66871
68148
  meta: {
66872
68149
  name: "products",
66873
68150
  description: 'What the store sells, with price and stock. Example: baker shopify products --search "snowboard" --stock out_of_stock'
@@ -66913,7 +68190,7 @@ var productsCommand = defineCommand211({
66913
68190
  }
66914
68191
  }
66915
68192
  });
66916
- var productCommand = defineCommand211({
68193
+ var productCommand = defineCommand215({
66917
68194
  meta: {
66918
68195
  name: "product",
66919
68196
  description: "One product: options with buy links, prices by country, stock, photo imageIds, video playbackIds. Accepts an id, handle or link. Example: baker shopify product the-complete-snowboard --content"
@@ -66947,8 +68224,8 @@ var productCommand = defineCommand211({
66947
68224
  },
66948
68225
  run: async ({ args: args5 }) => {
66949
68226
  try {
66950
- const positionals = Array.isArray(args5._) ? args5._ : [];
66951
- if (positionals.length > 1) {
68227
+ const positionals2 = Array.isArray(args5._) ? args5._ : [];
68228
+ if (positionals2.length > 1) {
66952
68229
  throw new ApiError(
66953
68230
  "VALIDATION_ERROR",
66954
68231
  "ref: Pass one product id, handle or product link \u2014 quote a link, and search by words with `baker shopify products --search`."
@@ -66976,7 +68253,7 @@ var productCommand = defineCommand211({
66976
68253
  }
66977
68254
  }
66978
68255
  });
66979
- var collectionsCommand = defineCommand211({
68256
+ var collectionsCommand = defineCommand215({
66980
68257
  meta: {
66981
68258
  name: "collections",
66982
68259
  description: "The store's collections, with the handles --collection takes. Example: baker shopify collections --full"
@@ -67001,7 +68278,7 @@ var collectionsCommand = defineCommand211({
67001
68278
  }
67002
68279
  }
67003
68280
  });
67004
- var stockCommand2 = defineCommand211({
68281
+ var stockCommand2 = defineCommand215({
67005
68282
  meta: {
67006
68283
  name: "stock",
67007
68284
  description: "Every product with options out of stock, naming them. Example: baker shopify stock"
@@ -67025,12 +68302,12 @@ var stockCommand2 = defineCommand211({
67025
68302
  }
67026
68303
  }
67027
68304
  });
67028
- var shopifyCommand = defineCommand211({
68305
+ var shopifyCommand = defineCommand215({
67029
68306
  meta: {
67030
68307
  name: "shopify",
67031
68308
  description: `Read the connected Shopify store as Baker keeps it \u2014 products, prices by country, stock, collections,
67032
- product photos, what it sold, who buys, where and what, and what advertises each product. Read-only:
67033
- nothing here changes the store.
68309
+ product photos, what it sold, who buys, where and what, and what advertises each product \u2014 and stage
68310
+ changes to it (\`change\`), applied when the user publishes the chat and undone with \`undo\`.
67034
68311
 
67035
68312
  Examples:
67036
68313
  baker shopify store # currency, countries, how many products, what is out of stock
@@ -67053,13 +68330,17 @@ Examples:
67053
68330
  baker shopify segments # best, loyal, at risk, new, one-time \u2014 and who to find more of
67054
68331
  baker shopify cohorts --months 12 # buyers by first-order month: who came back
67055
68332
  baker shopify promotions <product> # what advertises it: ads, landings, Shopping (read before pausing)
67056
- baker shopify stock-holds --product <product> # what Baker paused because it ran out, and what may be resumed
68333
+ baker shopify stock-holds --product <product> # what Baker paused because it ran out: what to resume, what to tell the user
68334
+ baker shopify change product update <product> --title "\u2026" # stage a store change (product|variant|field|collection|discount|redirect|campaign|measurement)
68335
+ baker shopify draft list # the store changes staged on this chat
68336
+ baker shopify changes # what Baker changed in the store; baker shopify undo <changeId> puts one back
68337
+ baker shopify tracking audit # is the store measured right? (reads only)
67057
68338
 
67058
68339
  Product photos are already in the image library: place them by imageId
67059
68340
  (baker images download <imageIds> --out src/pages/<page>/_images/), never by a cdn.shopify.com link.
67060
68341
  Product videos are in the video library: place them with <Video playbackId="\u2026" />.
67061
- A page's buy button uses the option's buyUrl, one button per option \u2014 never build a Shopify link
67062
- yourself, and never use storeUrl (a page to look at) for it.
68342
+ On a landing, the buy button is <ShopifyProduct product="\u2026"/> \u2014 never a buyUrl or a Shopify link
68343
+ written by hand. buyUrl is for ads, emails and messages; never use storeUrl (a page to look at) for it.
67063
68344
  NO_SHOPIFY_CONNECTION means no store is connected: follow error.fix. SHOPIFY_NOT_AVAILABLE means it cannot be
67064
68345
  connected here yet: carry on without it.
67065
68346
  Full guide: __tooling__/docs/tools/baker/shopify.md`
@@ -67078,15 +68359,20 @@ Full guide: __tooling__/docs/tools/baker/shopify.md`
67078
68359
  cohorts: cohortsCommand,
67079
68360
  segments: segmentsCommand,
67080
68361
  promotions: promotionsCommand,
67081
- "stock-holds": stockHoldsCommand
68362
+ "stock-holds": stockHoldsCommand,
68363
+ change: changeCommand,
68364
+ draft: draftCommand5,
68365
+ changes: changesCommand2,
68366
+ undo: undoCommand,
68367
+ tracking: trackingCommand2
67082
68368
  }
67083
68369
  });
67084
68370
 
67085
68371
  // src/commands/studio/index.ts
67086
- import { defineCommand as defineCommand220 } from "citty";
68372
+ import { defineCommand as defineCommand224 } from "citty";
67087
68373
 
67088
68374
  // src/commands/studio/animate.ts
67089
- import { defineCommand as defineCommand212 } from "citty";
68375
+ import { defineCommand as defineCommand216 } from "citty";
67090
68376
 
67091
68377
  // src/commands/studio/spokenPace.ts
67092
68378
  var WORDS_PER_SECOND3 = SPOKEN_WORDS_PER_SECOND;
@@ -67599,7 +68885,7 @@ async function adByHandHintFor(body) {
67599
68885
  return null;
67600
68886
  }
67601
68887
  }
67602
- var animateCommand = defineCommand212({
68888
+ var animateCommand = defineCommand216({
67603
68889
  meta: {
67604
68890
  name: "animate",
67605
68891
  description: "Render a clip. With an image the look is already fixed, so the prompt describes MOVEMENT \u2014 what the camera does, what the subject does, in what order. With --from text there is no image and the prompt is the whole shot.\n\nA rendered clip is NOT usable anywhere until you keep it: `baker studio keep <id> --slot N` is what puts it in the video library. Takes nobody keeps are never ingested, which is what makes a rejected batch cheap.\n\nFor anything longer than 15 seconds, or to build on footage that already exists, use --model bytedance/seedance-2.5: it renders 4-30s and is the only model that reads an existing clip or an existing soundtrack.\n\nExamples:\n baker studio animate 'slow push in, model turns to camera and smiles' --image j57abc123def456ghi789\n baker studio animate 'she looks to camera and says: \"Hola, soy Elena\"' --avatar elena --quality 720p --aspect-ratio 9:16\n baker studio animate 'handheld drift right, steam rising from the cup' --image './out/hero.png' --duration 6 --quality 1080p\n baker studio animate 'product rotates once on a turntable' --image j57abc\u2026,j57def\u2026 --from references\n baker studio animate 'she keeps walking, camera stays with her, then she stops and looks up' --image j57abc\u2026 --from references --from-clip j57batch\u2026:0 --model bytedance/seedance-2.5 --duration 20\n baker studio animate 'hold on the product, then a slow push-in' --from references --from-video j57vid\u2026 --model bytedance/seedance-2.5\n baker studio animate 'slow drone pull-back over a solar farm at golden hour, no people' --from text --model bytedance/seedance-2.5 --duration 12"
@@ -67724,7 +69010,7 @@ var animateCommand = defineCommand212({
67724
69010
  });
67725
69011
 
67726
69012
  // src/commands/studio/generate.ts
67727
- import { defineCommand as defineCommand213 } from "citty";
69013
+ import { defineCommand as defineCommand217 } from "citty";
67728
69014
  var MODEL_LIST2 = IMAGE_MODEL_IDS;
67729
69015
  var DEFAULT_MAX_WAIT_MS2 = 24e4;
67730
69016
  registerSchema({
@@ -67860,7 +69146,7 @@ function buildGenerateBody(args5, prompt) {
67860
69146
  }
67861
69147
  return body;
67862
69148
  }
67863
- var generateCommand = defineCommand213({
69149
+ var generateCommand = defineCommand217({
67864
69150
  meta: {
67865
69151
  name: "generate",
67866
69152
  description: "Start here to make an image. Renders 1-8 takes of one brief, ingests each into the media library as it lands, and shows the batch in the dashboard Studio next to the ones the client ran.\n\nModel choice: google/gemini-3.1-flash-image-preview (default \u2014 fast, best at editing a reference and at extreme ratios), google/gemini-3-pro-image-preview (highest fidelity, slower), openai/gpt-image-2 (photoreal and the cleanest in-image text \u2014 no --image-size, no 4:5 / 5:4), recraft/recraft-v4.1-pro-vector (vector/flat marks with palette control).\n\n--reference is the biggest quality lever there is: a real logo, product shot, Pinterest pin or sandbox screenshot beats any amount of adjectives.\n\nExamples:\n baker studio generate 'matte black bottle on wet marble, hard studio light, 35mm' --aspect-ratio 3:2 --count 3\n baker studio generate 'this bottle on a sunlit kitchen counter' --reference './src/brand/product.png,https://\u2026/kitchen.jpg'\n baker studio generate 'founder-style selfie, kitchen background, natural light' --skill ugc-selfie-hook\n baker studio generate 'flat geometric mascot, brand palette' --model recraft/recraft-v4.1-pro-vector --rgb-colors '[[10,10,10],[255,80,0]]'"
@@ -67943,7 +69229,7 @@ var generateCommand = defineCommand213({
67943
69229
  });
67944
69230
 
67945
69231
  // src/commands/studio/get.ts
67946
- import { defineCommand as defineCommand214 } from "citty";
69232
+ import { defineCommand as defineCommand218 } from "citty";
67947
69233
  registerSchema({
67948
69234
  command: "studio.get",
67949
69235
  description: "Read one Studio batch: every take, where it lives, and why a take is missing. This is how you pick up a batch that was still rendering when the start command returned.",
@@ -67952,7 +69238,7 @@ registerSchema({
67952
69238
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
67953
69239
  }
67954
69240
  });
67955
- var getCommand5 = defineCommand214({
69241
+ var getCommand5 = defineCommand218({
67956
69242
  meta: {
67957
69243
  name: "get",
67958
69244
  description: "Read one Studio batch \u2014 the takes, their urls, whether each is in the library, and the reason for any that failed.\n\nExample: baker studio get j57abc123def456ghi789\nExample: baker studio get j57abc123def456ghi789 --full"
@@ -67981,7 +69267,7 @@ var getCommand5 = defineCommand214({
67981
69267
  });
67982
69268
 
67983
69269
  // src/commands/studio/improve.ts
67984
- import { defineCommand as defineCommand215 } from "citty";
69270
+ import { defineCommand as defineCommand219 } from "citty";
67985
69271
  var DESCRIPTION2 = "Sharpen a rough brief into directed art direction \u2014 the same rewrite the client gets from the wand in the Studio prompt bar. Reach for it when you are relaying the CLIENT's own words and want them shaped without substituting your voice; when you are writing the art direction yourself, just write it, because you will do a better job than this does.";
67986
69272
  registerSchema({
67987
69273
  command: "studio.improve",
@@ -68006,7 +69292,7 @@ registerSchema({
68006
69292
  }
68007
69293
  }
68008
69294
  });
68009
- var improveCommand = defineCommand215({
69295
+ var improveCommand = defineCommand219({
68010
69296
  meta: {
68011
69297
  name: "improve",
68012
69298
  description: `${DESCRIPTION2}
@@ -68050,7 +69336,7 @@ Examples:
68050
69336
  });
68051
69337
 
68052
69338
  // src/commands/studio/keep.ts
68053
- import { defineCommand as defineCommand216 } from "citty";
69339
+ import { defineCommand as defineCommand220 } from "citty";
68054
69340
  registerSchema({
68055
69341
  command: "studio.keep",
68056
69342
  description: "Mark one take as the keeper. For an image this stars it, so the client reviewing the batch sees which one you used. For a CLIP it is the step that puts it in the video library \u2014 until then the clip cannot be used in a canvas, a landing, or an ad.",
@@ -68060,7 +69346,7 @@ registerSchema({
68060
69346
  undo: { type: "boolean", description: "Un-star an image, or take a kept clip back out", required: false }
68061
69347
  }
68062
69348
  });
68063
- var keepCommand = defineCommand216({
69349
+ var keepCommand = defineCommand220({
68064
69350
  meta: {
68065
69351
  name: "keep",
68066
69352
  description: "Mark one take as the keeper. An image gets starred (it was already in the library); a clip gets INGESTED into the video library, which is what makes it usable anywhere else.\n\nExample: baker studio keep j57abc123def456ghi789 --slot 2\nExample: baker studio keep j57abc123def456ghi789 --slot 2 --undo"
@@ -68111,7 +69397,7 @@ var keepCommand = defineCommand216({
68111
69397
  });
68112
69398
 
68113
69399
  // src/commands/studio/list.ts
68114
- import { defineCommand as defineCommand217 } from "citty";
69400
+ import { defineCommand as defineCommand221 } from "citty";
68115
69401
  registerSchema({
68116
69402
  command: "studio.list",
68117
69403
  description: "Recent Studio batches for THIS conversation, newest first \u2014 what you have already generated, so you re-use a take instead of paying for it twice. `--all` widens it to everything the company generated, including what people ran themselves in the dashboard.",
@@ -68122,7 +69408,7 @@ registerSchema({
68122
69408
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
68123
69409
  }
68124
69410
  });
68125
- var listCommand18 = defineCommand217({
69411
+ var listCommand18 = defineCommand221({
68126
69412
  meta: {
68127
69413
  name: "list",
68128
69414
  description: "Recent Studio batches, newest first. Scoped to this conversation unless you pass --all.\n\nExample: baker studio list\nExample: baker studio list --kind video --limit 5\nExample: baker studio list --all # includes batches the client ran in the dashboard"
@@ -68156,7 +69442,7 @@ var listCommand18 = defineCommand217({
68156
69442
  });
68157
69443
 
68158
69444
  // src/commands/studio/models.ts
68159
- import { defineCommand as defineCommand218 } from "citty";
69445
+ import { defineCommand as defineCommand222 } from "citty";
68160
69446
  var DESCRIPTION3 = "What each Studio model actually accepts: its shapes, resolutions, clip lengths, prompt character cap, how many reference images it takes, and which knobs it has. Read this before a batch you care about \u2014 the models disagree far more than they look like they do, and a setting the chosen model does not have is REFUSED, not ignored.";
68161
69447
  registerSchema({
68162
69448
  command: "studio.models",
@@ -68243,7 +69529,7 @@ function buildModelCards(kind, model) {
68243
69529
  const selected = model ? ids.filter((id) => id === model) : ids;
68244
69530
  return selected.map((id) => build(id));
68245
69531
  }
68246
- var modelsCommand = defineCommand218({
69532
+ var modelsCommand = defineCommand222({
68247
69533
  meta: {
68248
69534
  name: "models",
68249
69535
  description: `${DESCRIPTION3}
@@ -68290,13 +69576,13 @@ Examples:
68290
69576
  });
68291
69577
 
68292
69578
  // src/commands/studio/skills.ts
68293
- import { defineCommand as defineCommand219 } from "citty";
69579
+ import { defineCommand as defineCommand223 } from "citty";
68294
69580
  registerSchema({
68295
69581
  command: "studio.skills",
68296
69582
  description: "The craft directions `studio generate --skill <id>` accepts. Each one carries directed art direction plus the model, shape and take count it wants, so you pick a look by name instead of writing the boilerplate yourself.",
68297
69583
  args: {}
68298
69584
  });
68299
- var skillsCommand = defineCommand219({
69585
+ var skillsCommand = defineCommand223({
68300
69586
  meta: {
68301
69587
  name: "skills",
68302
69588
  description: "List the craft directions available to `baker studio generate --skill <id>` \u2014 what each one is for, whether it wants a reference image, and the model/shape/count it defaults to.\n\nExample: baker studio skills"
@@ -68320,7 +69606,7 @@ var skillsCommand = defineCommand219({
68320
69606
  });
68321
69607
 
68322
69608
  // src/commands/studio/index.ts
68323
- var studioCommand = defineCommand220({
69609
+ var studioCommand = defineCommand224({
68324
69610
  meta: {
68325
69611
  name: "studio",
68326
69612
  description: `Make new imagery and clips. Every batch is recorded and shows up in the dashboard Studio for the client to review, labelled with this conversation.
@@ -68363,10 +69649,10 @@ Full guide: __tooling__/docs/tools/baker/studio.md`
68363
69649
  });
68364
69650
 
68365
69651
  // src/commands/tag-manager/index.ts
68366
- import { defineCommand as defineCommand224 } from "citty";
69652
+ import { defineCommand as defineCommand228 } from "citty";
68367
69653
 
68368
69654
  // src/commands/tag-manager/draft.ts
68369
- import { defineCommand as defineCommand221 } from "citty";
69655
+ import { defineCommand as defineCommand225 } from "citty";
68370
69656
 
68371
69657
  // src/commands/tag-manager/shared.ts
68372
69658
  import { readFileSync as readFileSync20 } from "fs";
@@ -68376,11 +69662,11 @@ function failValidation5(message) {
68376
69662
  }
68377
69663
  function requireTarget6(args5, entity) {
68378
69664
  const positional = Array.isArray(args5._) ? args5._[0] : void 0;
68379
- const target2 = args5.id ?? args5.target ?? positional;
68380
- if (typeof target2 !== "string" || target2.length === 0) {
69665
+ const target4 = args5.id ?? args5.target ?? positional;
69666
+ if (typeof target4 !== "string" || target4.length === 0) {
68381
69667
  failValidation5(`pass the ${entity} id or path as the positional argument`);
68382
69668
  }
68383
- return target2;
69669
+ return target4;
68384
69670
  }
68385
69671
  function loadJsonArg2(args5, flag3 = "json") {
68386
69672
  const inline = args5[flag3];
@@ -68489,13 +69775,13 @@ registerSchema({
68489
69775
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
68490
69776
  }
68491
69777
  });
68492
- var draftCommand5 = defineCommand221({
69778
+ var draftCommand6 = defineCommand225({
68493
69779
  meta: {
68494
69780
  name: "draft",
68495
69781
  description: "List, show, amend, remove, or clear staged Tag Manager changes for this chat. `list` and `show` take --chat <id> to read an earlier chat's changes instead."
68496
69782
  },
68497
69783
  subCommands: {
68498
- list: defineCommand221({
69784
+ list: defineCommand225({
68499
69785
  meta: {
68500
69786
  name: "list",
68501
69787
  description: "Review everything staged on this chat (--json for the raw envelope)"
@@ -68508,7 +69794,7 @@ var draftCommand5 = defineCommand221({
68508
69794
  await draftList2(args5.json === true, args5.chat);
68509
69795
  }
68510
69796
  }),
68511
- show: defineCommand221({
69797
+ show: defineCommand225({
68512
69798
  meta: {
68513
69799
  name: "show",
68514
69800
  description: "Print the full staged payload for one change \u2014 the receipt to verify it looks right before publish (never truncated)."
@@ -68525,7 +69811,7 @@ var draftCommand5 = defineCommand221({
68525
69811
  );
68526
69812
  }
68527
69813
  }),
68528
- amend: defineCommand221({
69814
+ amend: defineCommand225({
68529
69815
  meta: {
68530
69816
  name: "amend",
68531
69817
  description: "Update a staged change in place \u2014 merges a JSON patch into its payload (objects deep-merge, null deletes a key, arrays/scalars replace) and re-validates. Use this instead of remove + re-create."
@@ -68542,7 +69828,7 @@ var draftCommand5 = defineCommand221({
68542
69828
  });
68543
69829
  }
68544
69830
  }),
68545
- remove: defineCommand221({
69831
+ remove: defineCommand225({
68546
69832
  meta: { name: "remove", description: "Remove one staged change (cascades to anything depending on it)" },
68547
69833
  args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
68548
69834
  run: async ({ args: args5 }) => {
@@ -68551,7 +69837,7 @@ var draftCommand5 = defineCommand221({
68551
69837
  });
68552
69838
  }
68553
69839
  }),
68554
- clear: defineCommand221({
69840
+ clear: defineCommand225({
68555
69841
  meta: { name: "clear", description: "Discard all Tag Manager changes staged on this chat" },
68556
69842
  run: async () => {
68557
69843
  await draftAction3("/api/tag-manager/draft/clear", {});
@@ -68561,7 +69847,7 @@ var draftCommand5 = defineCommand221({
68561
69847
  });
68562
69848
 
68563
69849
  // src/commands/tag-manager/read.ts
68564
- import { defineCommand as defineCommand222 } from "citty";
69850
+ import { defineCommand as defineCommand226 } from "citty";
68565
69851
  registerSchema({
68566
69852
  command: "tagManager.containers",
68567
69853
  description: "List the Google Tag Manager containers this company's connection can reach. Every container the company connected is flagged `connected: true` \u2014 there can be several, and Baker may read and change all of them. Start here to confirm which containers you are managing.",
@@ -68602,7 +69888,7 @@ function containersHints(containers) {
68602
69888
  }))
68603
69889
  });
68604
69890
  }
68605
- var containersCommand = defineCommand222({
69891
+ var containersCommand = defineCommand226({
68606
69892
  meta: {
68607
69893
  name: "containers",
68608
69894
  description: `List Tag Manager containers reachable by this company's connection.
@@ -68619,7 +69905,7 @@ Start here:
68619
69905
  }
68620
69906
  }
68621
69907
  });
68622
- var readCommand = defineCommand222({
69908
+ var readCommand = defineCommand226({
68623
69909
  meta: {
68624
69910
  name: "read",
68625
69911
  description: `Read the current contents of the Tag Manager container \u2014 always do this before staging changes.
@@ -68661,7 +69947,7 @@ Examples:
68661
69947
  });
68662
69948
 
68663
69949
  // src/commands/tag-manager/write-commands.ts
68664
- import { defineCommand as defineCommand223 } from "citty";
69950
+ import { defineCommand as defineCommand227 } from "citty";
68665
69951
  var CONTAINER_ARG_DESCRIPTION = "Numeric container id (optional only when one container is connected \u2014 run `baker tag-manager containers`)";
68666
69952
  var ENTITIES = [
68667
69953
  {
@@ -68717,10 +70003,10 @@ for (const { entity, noun, createHint } of ENTITIES) {
68717
70003
  });
68718
70004
  }
68719
70005
  function entityCommand(entity, noun, example) {
68720
- return defineCommand223({
70006
+ return defineCommand227({
68721
70007
  meta: { name: entity, description: `Stage ${noun} changes on this chat's Tag Manager draft` },
68722
70008
  subCommands: {
68723
- create: defineCommand223({
70009
+ create: defineCommand227({
68724
70010
  meta: {
68725
70011
  name: "create",
68726
70012
  description: `Stage a new ${noun}
@@ -68742,7 +70028,7 @@ Examples:
68742
70028
  });
68743
70029
  }
68744
70030
  }),
68745
- update: defineCommand223({
70031
+ update: defineCommand227({
68746
70032
  meta: {
68747
70033
  name: "update",
68748
70034
  description: `Stage an update to an existing ${noun} (pass its id or path)`
@@ -68762,7 +70048,7 @@ Examples:
68762
70048
  });
68763
70049
  }
68764
70050
  }),
68765
- delete: defineCommand223({
70051
+ delete: defineCommand227({
68766
70052
  meta: { name: "delete", description: `Stage the deletion of a ${noun} (pass its id or path)` },
68767
70053
  args: {
68768
70054
  id: { type: "positional", description: `${noun} id or path`, required: false },
@@ -68803,7 +70089,7 @@ function builtinTypes(args5) {
68803
70089
  }
68804
70090
  return raw.split(",").map((entry) => entry.trim());
68805
70091
  }
68806
- var builtinCommand = defineCommand223({
70092
+ var builtinCommand = defineCommand227({
68807
70093
  meta: {
68808
70094
  name: "builtin",
68809
70095
  description: `Enable or disable built-in variables
@@ -68813,7 +70099,7 @@ Examples:
68813
70099
  baker tag-manager builtin disable --types formId`
68814
70100
  },
68815
70101
  subCommands: {
68816
- enable: defineCommand223({
70102
+ enable: defineCommand227({
68817
70103
  meta: { name: "enable", description: "Stage enabling built-in variables" },
68818
70104
  args: {
68819
70105
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -68827,7 +70113,7 @@ Examples:
68827
70113
  });
68828
70114
  }
68829
70115
  }),
68830
- disable: defineCommand223({
70116
+ disable: defineCommand227({
68831
70117
  meta: { name: "disable", description: "Stage disabling built-in variables" },
68832
70118
  args: {
68833
70119
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -68845,7 +70131,7 @@ Examples:
68845
70131
  });
68846
70132
 
68847
70133
  // src/commands/tag-manager/index.ts
68848
- var tagManagerCommand = defineCommand224({
70134
+ var tagManagerCommand = defineCommand228({
68849
70135
  meta: {
68850
70136
  name: "tag-manager",
68851
70137
  description: `Read and change what lives inside the client's Google Tag Manager container \u2014 tags, triggers, variables, folders and built-in variables.
@@ -68877,12 +70163,12 @@ Full guide: __tooling__/docs/tools/baker/tag-manager.md`
68877
70163
  variable: variableCommand,
68878
70164
  folder: folderCommand,
68879
70165
  builtin: builtinCommand,
68880
- draft: draftCommand5
70166
+ draft: draftCommand6
68881
70167
  }
68882
70168
  });
68883
70169
 
68884
70170
  // src/commands/tags/index.ts
68885
- import { defineCommand as defineCommand225 } from "citty";
70171
+ import { defineCommand as defineCommand229 } from "citty";
68886
70172
 
68887
70173
  // src/commands/tags/shared.ts
68888
70174
  function failApi3(err) {
@@ -68962,7 +70248,7 @@ async function listTags(json) {
68962
70248
  var listArgs11 = {
68963
70249
  json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list" }
68964
70250
  };
68965
- var listCommand19 = defineCommand225({
70251
+ var listCommand19 = defineCommand229({
68966
70252
  meta: {
68967
70253
  name: "list",
68968
70254
  description: "Effective tags for this chat (production + staged), with each tag's full readable config (secrets excluded) \u2014 reuse a stored value to pre-fill a change rather than asking the user. Refs printed here are what flow side-effect tagIds should use. Example: baker tags list"
@@ -68981,7 +70267,7 @@ async function listDraft4(chat) {
68981
70267
  failApi3(err);
68982
70268
  }
68983
70269
  }
68984
- var draftCommand6 = defineCommand225({
70270
+ var draftCommand7 = defineCommand229({
68985
70271
  meta: {
68986
70272
  name: "draft",
68987
70273
  description: "Review the tag changes staged in this chat (read-only). Staged changes were approved via request_tag_input and apply when the chat is published; to amend or drop one, propose a follow-up change through the same tool (a delete on a tag_temp_* ref drops the staged create). Takes --chat <id> to read an earlier chat's staged changes instead."
@@ -68991,7 +70277,7 @@ var draftCommand6 = defineCommand225({
68991
70277
  await listDraft4(args5.chat);
68992
70278
  }
68993
70279
  });
68994
- var tagsCommand4 = defineCommand225({
70280
+ var tagsCommand4 = defineCommand229({
68995
70281
  meta: {
68996
70282
  name: "tags",
68997
70283
  description: `Read the client's marketing/analytics tags (Meta pixel, GA4, Google Ads, GTM, Clarity, Hotjar, \u2026) \u2014 production tags plus the changes staged in this chat.
@@ -69009,7 +70295,7 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
69009
70295
  },
69010
70296
  subCommands: {
69011
70297
  list: listCommand19,
69012
- draft: draftCommand6
70298
+ draft: draftCommand7
69013
70299
  },
69014
70300
  // Bare `baker tags` lists the effective tags. `args` stays so citty knows this
69015
70301
  // command's flags when it scans rawArgs for a subcommand name, and `default` (not
@@ -69020,10 +70306,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
69020
70306
  });
69021
70307
 
69022
70308
  // src/commands/testimonials/index.ts
69023
- import { defineCommand as defineCommand229 } from "citty";
70309
+ import { defineCommand as defineCommand233 } from "citty";
69024
70310
 
69025
70311
  // src/commands/testimonials/get.ts
69026
- import { defineCommand as defineCommand226 } from "citty";
70312
+ import { defineCommand as defineCommand230 } from "citty";
69027
70313
  registerSchema({
69028
70314
  command: "testimonials.get",
69029
70315
  description: "Get a single testimonial by ID",
@@ -69031,7 +70317,7 @@ registerSchema({
69031
70317
  id: { type: "string", description: "Testimonial ID", required: true }
69032
70318
  }
69033
70319
  });
69034
- var getCommand6 = defineCommand226({
70320
+ var getCommand6 = defineCommand230({
69035
70321
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
69036
70322
  args: {
69037
70323
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -69068,7 +70354,7 @@ var getCommand6 = defineCommand226({
69068
70354
  });
69069
70355
 
69070
70356
  // src/commands/testimonials/list.ts
69071
- import { defineCommand as defineCommand227 } from "citty";
70357
+ import { defineCommand as defineCommand231 } from "citty";
69072
70358
 
69073
70359
  // src/commands/testimonials/emptyCorpusHints.ts
69074
70360
  function resolveEmptyReason({
@@ -69225,7 +70511,7 @@ function buildListParams(args5) {
69225
70511
  }
69226
70512
  return params2;
69227
70513
  }
69228
- var listCommand20 = defineCommand227({
70514
+ var listCommand20 = defineCommand231({
69229
70515
  meta: {
69230
70516
  name: "list",
69231
70517
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -69285,7 +70571,7 @@ var listCommand20 = defineCommand227({
69285
70571
  });
69286
70572
 
69287
70573
  // src/commands/testimonials/search.ts
69288
- import { defineCommand as defineCommand228 } from "citty";
70574
+ import { defineCommand as defineCommand232 } from "citty";
69289
70575
  var FILTER_FLAGS2 = ["source", "rating-min", "rating-max", "status", "sentiment", "language", "tags"];
69290
70576
  function languageBiasHint(results, requestedLanguage) {
69291
70577
  if (requestedLanguage) {
@@ -69364,7 +70650,7 @@ function buildSearchRequest(query, args5) {
69364
70650
  }
69365
70651
  return body;
69366
70652
  }
69367
- var searchCommand3 = defineCommand228({
70653
+ var searchCommand3 = defineCommand232({
69368
70654
  meta: {
69369
70655
  name: "search",
69370
70656
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -69428,7 +70714,7 @@ var searchCommand3 = defineCommand228({
69428
70714
  var tagsCommand5 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
69429
70715
 
69430
70716
  // src/commands/testimonials/index.ts
69431
- var testimonialsCommand = defineCommand229({
70717
+ var testimonialsCommand = defineCommand233({
69432
70718
  meta: {
69433
70719
  name: "testimonials",
69434
70720
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -69450,10 +70736,10 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
69450
70736
  });
69451
70737
 
69452
70738
  // src/commands/videos/index.ts
69453
- import { defineCommand as defineCommand237 } from "citty";
70739
+ import { defineCommand as defineCommand241 } from "citty";
69454
70740
 
69455
70741
  // src/commands/videos/delete.ts
69456
- import { defineCommand as defineCommand230 } from "citty";
70742
+ import { defineCommand as defineCommand234 } from "citty";
69457
70743
  registerSchema({
69458
70744
  command: "videos.delete",
69459
70745
  description: "Delete a video by ID",
@@ -69467,7 +70753,7 @@ registerSchema({
69467
70753
  }
69468
70754
  }
69469
70755
  });
69470
- var deleteCommand4 = defineCommand230({
70756
+ var deleteCommand4 = defineCommand234({
69471
70757
  meta: {
69472
70758
  name: "delete",
69473
70759
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -69508,7 +70794,7 @@ var deleteCommand4 = defineCommand230({
69508
70794
  });
69509
70795
 
69510
70796
  // src/commands/videos/describe.ts
69511
- import { defineCommand as defineCommand231 } from "citty";
70797
+ import { defineCommand as defineCommand235 } from "citty";
69512
70798
  var DESCRIPTION_HELP2 = "What the clip actually shows, in the words someone would search for it by. This is what `baker videos search` retrieves on.";
69513
70799
  registerSchema({
69514
70800
  command: "videos.describe",
@@ -69525,7 +70811,7 @@ registerSchema({
69525
70811
  full: { type: "boolean", description: "Return the whole library row, not just what changed", required: false }
69526
70812
  }
69527
70813
  });
69528
- var describeCommand2 = defineCommand231({
70814
+ var describeCommand2 = defineCommand235({
69529
70815
  meta: {
69530
70816
  name: "describe",
69531
70817
  description: `Correct what the library says a video is. Only the fields you pass change; the rest keep their current values.
@@ -69583,7 +70869,7 @@ Example: baker videos describe j571abc123 --description "Customer explaining how
69583
70869
  });
69584
70870
 
69585
70871
  // src/commands/videos/get.ts
69586
- import { defineCommand as defineCommand232 } from "citty";
70872
+ import { defineCommand as defineCommand236 } from "citty";
69587
70873
  registerSchema({
69588
70874
  command: "videos.get",
69589
70875
  description: "Get a single video by ID",
@@ -69591,7 +70877,7 @@ registerSchema({
69591
70877
  id: { type: "string", description: "Video ID", required: true }
69592
70878
  }
69593
70879
  });
69594
- var getCommand7 = defineCommand232({
70880
+ var getCommand7 = defineCommand236({
69595
70881
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
69596
70882
  args: {
69597
70883
  id: { type: "positional", description: "Video ID", required: false },
@@ -69628,7 +70914,7 @@ var getCommand7 = defineCommand232({
69628
70914
  });
69629
70915
 
69630
70916
  // src/commands/videos/group.ts
69631
- import { defineCommand as defineCommand233 } from "citty";
70917
+ import { defineCommand as defineCommand237 } from "citty";
69632
70918
  registerSchema({
69633
70919
  command: "videos.group",
69634
70920
  description: "List every clip and image that arrived in the same set as this video (carousel slides, one page)",
@@ -69637,7 +70923,7 @@ registerSchema({
69637
70923
  "group-key": { type: "string", description: "The set key directly, when you already have it", required: false }
69638
70924
  }
69639
70925
  });
69640
- var groupCommand2 = defineCommand233({
70926
+ var groupCommand2 = defineCommand237({
69641
70927
  meta: {
69642
70928
  name: "group",
69643
70929
  description: "List every asset that arrived in the same set as this clip \u2014 the other slides of the Instagram post it came from, stills included. A carousel is authored to be read in order, so a clip pulled out of one is usually missing half its meaning. Example: baker videos group <videoId>"
@@ -69660,7 +70946,7 @@ var groupCommand2 = defineCommand233({
69660
70946
  import { mkdtemp as mkdtemp3, rm as rm9, stat as stat12 } from "fs/promises";
69661
70947
  import { tmpdir as tmpdir4 } from "os";
69662
70948
  import path48 from "path";
69663
- import { defineCommand as defineCommand234 } from "citty";
70949
+ import { defineCommand as defineCommand238 } from "citty";
69664
70950
 
69665
70951
  // src/lib/streamUpload.ts
69666
70952
  import { createHash as createHash2 } from "crypto";
@@ -69824,7 +71110,7 @@ registerSchema({
69824
71110
  "dry-run": { type: "boolean", description: "Preview the operation without executing", required: false }
69825
71111
  }
69826
71112
  });
69827
- var ingestCommand2 = defineCommand234({
71113
+ var ingestCommand2 = defineCommand238({
69828
71114
  meta: {
69829
71115
  name: "ingest",
69830
71116
  description: "Add a video to the library from a URL. A direct file URL is handed straight to Baker, which fetches it. A page URL (YouTube, TikTok, Vimeo, Instagram) is downloaded here first, then uploaded \u2014 and a direct URL that Baker cannot fetch falls back to that same path automatically.\n\nExample: baker videos ingest https://www.youtube.com/watch?v=abc123"
@@ -70086,7 +71372,7 @@ async function uploadToAssetStore(filePath, sizeBytes) {
70086
71372
  }
70087
71373
 
70088
71374
  // src/commands/videos/search.ts
70089
- import { defineCommand as defineCommand235 } from "citty";
71375
+ import { defineCommand as defineCommand239 } from "citty";
70090
71376
  registerSchema({
70091
71377
  command: "videos.search",
70092
71378
  description: "Search videos by text query. Only returns ready videos.",
@@ -70096,7 +71382,7 @@ registerSchema({
70096
71382
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
70097
71383
  }
70098
71384
  });
70099
- var searchCommand4 = defineCommand235({
71385
+ var searchCommand4 = defineCommand239({
70100
71386
  meta: {
70101
71387
  name: "search",
70102
71388
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -70148,7 +71434,7 @@ var tagsCommand6 = makeTagsCommand("videos", "video", "/api/videos/tags");
70148
71434
  // src/commands/videos/upload.ts
70149
71435
  import { readFile as readFile34, stat as stat13 } from "fs/promises";
70150
71436
  import { basename as basename3, extname as extname4 } from "path";
70151
- import { defineCommand as defineCommand236 } from "citty";
71437
+ import { defineCommand as defineCommand240 } from "citty";
70152
71438
  var MIME_MAP = {
70153
71439
  ".mp4": "video/mp4",
70154
71440
  ".mov": "video/quicktime",
@@ -70190,7 +71476,7 @@ function detectContentType(filePath) {
70190
71476
  function isRemoteUrl3(value) {
70191
71477
  return /^https?:\/\//i.test(value);
70192
71478
  }
70193
- var uploadCommand2 = defineCommand236({
71479
+ var uploadCommand2 = defineCommand240({
70194
71480
  meta: {
70195
71481
  name: "upload",
70196
71482
  description: "Upload a video to Baker \u2014 accepts a local file path OR a remote http(s) URL.\n\nLocal: auto-detects content type and uploads via Mux direct upload.\nRemote: hands off to `videos ingest` (direct fetch, or download-then-upload for a YouTube/TikTok/Vimeo page).\n\nExamples:\n baker videos upload ./demo.mp4\n baker videos upload https://www.youtube.com/watch?v=abc123"
@@ -70274,7 +71560,7 @@ var uploadCommand2 = defineCommand236({
70274
71560
  });
70275
71561
 
70276
71562
  // src/commands/videos/index.ts
70277
- var videosCommand = defineCommand237({
71563
+ var videosCommand = defineCommand241({
70278
71564
  meta: {
70279
71565
  name: "videos",
70280
71566
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, ingest, describe, delete, tags.
@@ -70303,10 +71589,10 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
70303
71589
  });
70304
71590
 
70305
71591
  // src/commands/winning-ads/index.ts
70306
- import { defineCommand as defineCommand250 } from "citty";
71592
+ import { defineCommand as defineCommand254 } from "citty";
70307
71593
 
70308
71594
  // src/commands/winning-ads/advertisers.ts
70309
- import { defineCommand as defineCommand238 } from "citty";
71595
+ import { defineCommand as defineCommand242 } from "citty";
70310
71596
 
70311
71597
  // src/commands/winning-ads/shared.ts
70312
71598
  function splitList3(value) {
@@ -70359,7 +71645,7 @@ function advertiserNormalizer(record, full) {
70359
71645
  last_synced_at: record.last_synced_at ?? null
70360
71646
  };
70361
71647
  }
70362
- var advertisersCommand2 = defineCommand238({
71648
+ var advertisersCommand2 = defineCommand242({
70363
71649
  meta: {
70364
71650
  name: "advertisers",
70365
71651
  description: 'List corpus advertisers by name or domain. Find your own advertiser for --exclude-advertiser, or a competitor for --advertiser-id / winners. Example: baker winning-ads advertisers "Deel" --output md'
@@ -70417,7 +71703,7 @@ var advertisersCommand2 = defineCommand238({
70417
71703
  });
70418
71704
 
70419
71705
  // src/commands/winning-ads/brief.ts
70420
- import { defineCommand as defineCommand239 } from "citty";
71706
+ import { defineCommand as defineCommand243 } from "citty";
70421
71707
  registerSchema({
70422
71708
  command: "winning-ads.brief",
70423
71709
  description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
@@ -70463,7 +71749,7 @@ function parseDna(raw) {
70463
71749
  }
70464
71750
  return parsed;
70465
71751
  }
70466
- var briefCommand = defineCommand239({
71752
+ var briefCommand = defineCommand243({
70467
71753
  meta: {
70468
71754
  name: "brief",
70469
71755
  description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
@@ -70499,7 +71785,7 @@ var briefCommand = defineCommand239({
70499
71785
  });
70500
71786
 
70501
71787
  // src/commands/winning-ads/content.ts
70502
- import { defineCommand as defineCommand240 } from "citty";
71788
+ import { defineCommand as defineCommand244 } from "citty";
70503
71789
  registerSchema({
70504
71790
  command: "winning-ads.content",
70505
71791
  description: "Read what's INSIDE one winning ad: the spoken transcript, the on-screen text, and the ad copy. Use this after `search`/`winners`/`feed` return a shortlist \u2014 pass an ad_id to understand a reference before reproducing it. Add --full for speech, pacing, and soundtrack detail. Video ads carry the transcript/on-screen text; static ads carry only the copy.",
@@ -70512,7 +71798,7 @@ registerSchema({
70512
71798
  }
70513
71799
  }
70514
71800
  });
70515
- var contentCommand = defineCommand240({
71801
+ var contentCommand = defineCommand244({
70516
71802
  meta: {
70517
71803
  name: "content",
70518
71804
  description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
@@ -70561,7 +71847,7 @@ var contentCommand = defineCommand240({
70561
71847
  });
70562
71848
 
70563
71849
  // src/commands/winning-ads/feed.ts
70564
- import { defineCommand as defineCommand241 } from "citty";
71850
+ import { defineCommand as defineCommand245 } from "citty";
70565
71851
  function buildFeedParams(input) {
70566
71852
  const params2 = {};
70567
71853
  const advertiser = splitList3(input.advertiser);
@@ -70613,7 +71899,7 @@ registerSchema({
70613
71899
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
70614
71900
  }
70615
71901
  });
70616
- var feedCommand2 = defineCommand241({
71902
+ var feedCommand2 = defineCommand245({
70617
71903
  meta: {
70618
71904
  name: "feed",
70619
71905
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -70698,7 +71984,7 @@ var feedCommand2 = defineCommand241({
70698
71984
  });
70699
71985
 
70700
71986
  // src/commands/winning-ads/follow.ts
70701
- import { defineCommand as defineCommand242 } from "citty";
71987
+ import { defineCommand as defineCommand246 } from "citty";
70702
71988
  var PLATFORMS = adLibraryPlatformSchema.options;
70703
71989
  registerSchema({
70704
71990
  command: "winning-ads.follow",
@@ -70713,7 +71999,7 @@ registerSchema({
70713
71999
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
70714
72000
  }
70715
72001
  });
70716
- var followCommand = defineCommand242({
72002
+ var followCommand = defineCommand246({
70717
72003
  meta: {
70718
72004
  name: "follow",
70719
72005
  description: 'Follow a brand to track ALL its ads \u2014 every platform and country. --platform is how we read your input, not a limit. A domain tracks every platform we can resolve. Example: baker winning-ads follow "deel.com" --platform meta'
@@ -70760,7 +72046,7 @@ var followCommand = defineCommand242({
70760
72046
  });
70761
72047
 
70762
72048
  // src/commands/winning-ads/follow-competitors.ts
70763
- import { defineCommand as defineCommand243 } from "citty";
72049
+ import { defineCommand as defineCommand247 } from "citty";
70764
72050
  var PLATFORMS2 = adLibraryPlatformSchema.options;
70765
72051
  var BATCH_TIMEOUT_MS = 3e5;
70766
72052
  function buildFollowBatchBody(input) {
@@ -70793,7 +72079,7 @@ registerSchema({
70793
72079
  }
70794
72080
  }
70795
72081
  });
70796
- var followCompetitorsCommand = defineCommand243({
72082
+ var followCompetitorsCommand = defineCommand247({
70797
72083
  meta: {
70798
72084
  name: "follow-competitors",
70799
72085
  description: 'Follow many brands at once by domain \u2014 add every competitor in one call. Example: baker winning-ads follow-competitors "deel.com,notion.so,hubspot.com"'
@@ -70868,7 +72154,7 @@ var followCompetitorsCommand = defineCommand243({
70868
72154
  });
70869
72155
 
70870
72156
  // src/commands/winning-ads/following.ts
70871
- import { defineCommand as defineCommand244 } from "citty";
72157
+ import { defineCommand as defineCommand248 } from "citty";
70872
72158
  registerSchema({
70873
72159
  command: "winning-ads.following",
70874
72160
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts. A brand still adding has counts that are a lie in progress; one with `discovery_failed` has counts that are short because we couldn't finish looking, which is not the same as it running no ads.",
@@ -70922,7 +72208,7 @@ function followingNormalizer(record, full) {
70922
72208
  platforms: Array.isArray(record.platforms) ? record.platforms : []
70923
72209
  };
70924
72210
  }
70925
- var followingCommand = defineCommand244({
72211
+ var followingCommand = defineCommand248({
70926
72212
  meta: {
70927
72213
  name: "following",
70928
72214
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. A brand's counts are only final once it is ready. Example: baker winning-ads following --output md"
@@ -70958,7 +72244,7 @@ var followingCommand = defineCommand244({
70958
72244
  });
70959
72245
 
70960
72246
  // src/commands/winning-ads/patterns.ts
70961
- import { defineCommand as defineCommand245 } from "citty";
72247
+ import { defineCommand as defineCommand249 } from "citty";
70962
72248
  registerSchema({
70963
72249
  command: "winning-ads.patterns",
70964
72250
  description: "Mine what separates two cohorts of ads: pass a comma-list of winning ad ids (--winners) and a comma-list of weaker ad ids (--duds). Returns the discriminating DNA fields.",
@@ -70997,7 +72283,7 @@ function discriminatorRow(record) {
70997
72283
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
70998
72284
  };
70999
72285
  }
71000
- var patternsCommand = defineCommand245({
72286
+ var patternsCommand = defineCommand249({
71001
72287
  meta: {
71002
72288
  name: "patterns",
71003
72289
  description: "Discover what separates winning ads from weak ones. Example: baker winning-ads patterns --winners a_1,a_2,a_3 --duds a_9,a_8 --output md"
@@ -71053,7 +72339,7 @@ var patternsCommand = defineCommand245({
71053
72339
  });
71054
72340
 
71055
72341
  // src/commands/winning-ads/search.ts
71056
- import { defineCommand as defineCommand246 } from "citty";
72342
+ import { defineCommand as defineCommand250 } from "citty";
71057
72343
  registerSchema({
71058
72344
  command: "winning-ads.search",
71059
72345
  description: "Search the ad-dna corpus of scored winning ads. Returns a lean shortlist (advertiser, summary, scores, media_url) to pick a reference to reproduce.",
@@ -71126,15 +72412,15 @@ function setNumber(body, key, value) {
71126
72412
  body[key] = Number(value);
71127
72413
  }
71128
72414
  }
71129
- function setList(target2, key, value) {
71130
- const list = splitList3(value);
71131
- if (list.length) {
71132
- target2[key] = list;
72415
+ function setList(target4, key, value) {
72416
+ const list3 = splitList3(value);
72417
+ if (list3.length) {
72418
+ target4[key] = list3;
71133
72419
  }
71134
72420
  }
71135
- function setString(target2, key, value) {
72421
+ function setString(target4, key, value) {
71136
72422
  if (value) {
71137
- target2[key] = value;
72423
+ target4[key] = value;
71138
72424
  }
71139
72425
  }
71140
72426
  function buildSearchBody2(args5) {
@@ -71191,7 +72477,7 @@ function reportSearchError(err) {
71191
72477
  }
71192
72478
  reportError2(err);
71193
72479
  }
71194
- var searchCommand5 = defineCommand246({
72480
+ var searchCommand5 = defineCommand250({
71195
72481
  meta: {
71196
72482
  name: "search",
71197
72483
  description: "Search winning reference ads. Example: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --winner-category winner --exclude-advertiser adv_123 --output md"
@@ -71311,7 +72597,7 @@ var searchCommand5 = defineCommand246({
71311
72597
  });
71312
72598
 
71313
72599
  // src/commands/winning-ads/seeds.ts
71314
- import { defineCommand as defineCommand247 } from "citty";
72600
+ import { defineCommand as defineCommand251 } from "citty";
71315
72601
  function leanRow(r) {
71316
72602
  return {
71317
72603
  key: r.key,
@@ -71339,7 +72625,7 @@ function makeSeedCommand(opts) {
71339
72625
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
71340
72626
  }
71341
72627
  });
71342
- return defineCommand247({
72628
+ return defineCommand251({
71343
72629
  meta: { name: opts.name, description: opts.description },
71344
72630
  args: {
71345
72631
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -71388,7 +72674,7 @@ var formatsCommand = makeSeedCommand({
71388
72674
  });
71389
72675
 
71390
72676
  // src/commands/winning-ads/unfollow.ts
71391
- import { defineCommand as defineCommand248 } from "citty";
72677
+ import { defineCommand as defineCommand252 } from "citty";
71392
72678
  registerSchema({
71393
72679
  command: "winning-ads.unfollow",
71394
72680
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -71396,7 +72682,7 @@ registerSchema({
71396
72682
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
71397
72683
  }
71398
72684
  });
71399
- var unfollowCommand = defineCommand248({
72685
+ var unfollowCommand = defineCommand252({
71400
72686
  meta: {
71401
72687
  name: "unfollow",
71402
72688
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -71417,7 +72703,7 @@ var unfollowCommand = defineCommand248({
71417
72703
  });
71418
72704
 
71419
72705
  // src/commands/winning-ads/winners.ts
71420
- import { defineCommand as defineCommand249 } from "citty";
72706
+ import { defineCommand as defineCommand253 } from "citty";
71421
72707
  registerSchema({
71422
72708
  command: "winning-ads.winners",
71423
72709
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -71427,7 +72713,7 @@ registerSchema({
71427
72713
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin|tiktok", required: false }
71428
72714
  }
71429
72715
  });
71430
- var winnersCommand = defineCommand249({
72716
+ var winnersCommand = defineCommand253({
71431
72717
  meta: {
71432
72718
  name: "winners",
71433
72719
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -71477,7 +72763,7 @@ var winnersCommand = defineCommand249({
71477
72763
  });
71478
72764
 
71479
72765
  // src/commands/winning-ads/index.ts
71480
- var winningAdsCommand = defineCommand250({
72766
+ var winningAdsCommand = defineCommand254({
71481
72767
  meta: {
71482
72768
  name: "winning-ads",
71483
72769
  description: `Search the ad-dna corpus of scored "winning" ads for reference creatives to reproduce, and manage the brands your library tracks. Proxied through the Baker backend (BAKER_API_KEY) \u2014 no separate token needed.
@@ -71688,12 +72974,12 @@ function suggestionText(suggestion) {
71688
72974
  }
71689
72975
  var OUTPUT_EXPLANATION = "This command has no --output option: `--output json` is accepted by every command that prints the JSON envelope, and md or files only where `baker schema` lists `output`. Drop the option and read the default output. ";
71690
72976
  function unknownFlagEnvelope(unknown, commandPath, suggestion, hasSchema) {
71691
- const list = unknown.join(", ");
72977
+ const list3 = unknown.join(", ");
71692
72978
  return {
71693
72979
  ok: false,
71694
72980
  error: {
71695
72981
  code: "UNKNOWN_ARGUMENT",
71696
- message: `${list} ${unknown.length > 1 ? "are not options" : "is not an option"} of \`baker ${commandPath}\`.`,
72982
+ message: `${list3} ${unknown.length > 1 ? "are not options" : "is not an option"} of \`baker ${commandPath}\`.`,
71697
72983
  fix: {
71698
72984
  action: hasSchema ? "check_schema" : "check_help",
71699
72985
  explanation: (unknown.some((flag3) => flag3 === "--output" || flag3.startsWith("--output=")) ? OUTPUT_EXPLANATION : "") + suggestionText(suggestion) + `Run \`${hasSchema ? `baker schema ${commandPath}` : `baker ${commandPath} --help`}\` to see every option this command takes. Do not re-run the same call: until this version an unrecognised option was accepted and ignored, so a command that looked like it had filtered or narrowed the result was in fact answering the unfiltered question.`
@@ -71804,7 +73090,7 @@ function getCliVersion() {
71804
73090
  }
71805
73091
 
71806
73092
  // src/cli.ts
71807
- var main = defineCommand251({
73093
+ var main = defineCommand255({
71808
73094
  meta: {
71809
73095
  name: "baker",
71810
73096
  version: getCliVersion(),