@koda-sl/baker-cli 0.383.0-dev.3b7d77b41 → 0.383.0-dev.9b0f8605e

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 defineCommand256, 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),
@@ -5087,6 +5111,20 @@ var FLOW_SIDE_EFFECT_CATALOG = [
5087
5111
  reference: SIDE_EFFECT_REF("httpWebhook")
5088
5112
  },
5089
5113
  crmSideEffect("googleSpreadsheet", "Appends a row to a Google Sheet.", "columnMapping", MAP_FIRST),
5114
+ {
5115
+ kind: "side-effect",
5116
+ type: "shopifyCustomer",
5117
+ summary: "Adds a sign-up to the connected Shopify store with the consent they ticked.",
5118
+ skeleton: {
5119
+ ...SIDE_EFFECT_ENVELOPE,
5120
+ type: "shopifyCustomer",
5121
+ customer: { email: null, phone: null, acceptsEmailMarketing: null, acceptsSmsMarketing: null },
5122
+ offer: null
5123
+ },
5124
+ owners: { customer: "agent", offer: "agent" },
5125
+ gotcha: "Map email to an email field with baker flows map. Email and SMS consent each need their own optional, visible, initially unticked checkbox; SMS also needs phone. No connection secret is needed. An optional offer names a published sign-up offer; read baker shopify signup-offers. Show its code with ShopifySignupCode.",
5126
+ reference: SIDE_EFFECT_REF("shopifyCustomer")
5127
+ },
5090
5128
  crmSideEffect("hubspotForm", "Submits to a HubSpot form endpoint.", "fieldMapping", MAP_FIRST),
5091
5129
  crmSideEffect("pipedrive", "Creates or updates a Pipedrive record.", "fieldMapping", MAP_FIRST),
5092
5130
  crmSideEffect("goHighlevelContact", "Creates or updates a GoHighLevel contact.", "fieldMapping", MAP_FIRST),
@@ -6970,7 +7008,6 @@ var STORE_EVENTS = {
6970
7008
  product_out_of_stock: {
6971
7009
  label: "A product runs out of stock",
6972
7010
  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
7011
  payload: [
6975
7012
  "title",
6976
7013
  "handle",
@@ -6981,13 +7018,11 @@ var STORE_EVENTS = {
6981
7018
  "promoted_by",
6982
7019
  "promotions_checked",
6983
7020
  ...PRODUCT_FIELDS.slice(2)
6984
- ],
6985
- namesProduct: true
7021
+ ]
6986
7022
  },
6987
7023
  product_back_in_stock: {
6988
7024
  label: "A product is back in stock",
6989
7025
  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
7026
  payload: [
6992
7027
  "title",
6993
7028
  "handle",
@@ -6998,20 +7033,16 @@ var STORE_EVENTS = {
6998
7033
  "reason",
6999
7034
  "promoted_by",
7000
7035
  ...PRODUCT_FIELDS.slice(2)
7001
- ],
7002
- namesProduct: true
7036
+ ]
7003
7037
  },
7004
7038
  product_created: {
7005
7039
  label: "A new product is added",
7006
7040
  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
7041
+ payload: [...PRODUCT_FIELDS]
7010
7042
  },
7011
7043
  product_updated: {
7012
7044
  label: "A product is changed",
7013
7045
  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
7046
  payload: [
7016
7047
  "title",
7017
7048
  "handle",
@@ -7022,41 +7053,33 @@ var STORE_EVENTS = {
7022
7053
  "before_published",
7023
7054
  "price_changes",
7024
7055
  ...PRODUCT_FIELDS.slice(2)
7025
- ],
7026
- namesProduct: true
7056
+ ]
7027
7057
  },
7028
7058
  collection_updated: {
7029
7059
  label: "A collection changes",
7030
7060
  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
7061
+ payload: ["title", "collection", "changed", "product_count", "before_product_count", "published", "store"]
7034
7062
  },
7035
7063
  purchase: {
7036
7064
  label: "An order is paid",
7037
7065
  sentence: "When an order is paid",
7038
- description: "The store's sale; value is the order's total in the store's currency.",
7039
7066
  payload: ["order_name", "ordered_at", "shopify_source", "visitor_from", "campaign_from", "store"],
7040
- namesProduct: false
7067
+ alsoTheSite: true
7041
7068
  },
7042
7069
  refund: {
7043
7070
  label: "Money is refunded on an order",
7044
7071
  sentence: "When money is refunded on an order",
7045
- description: "Money given back on an order; value is the amount refunded.",
7046
7072
  payload: ["order_name", "store"],
7047
- namesProduct: false
7073
+ alsoTheSite: true
7048
7074
  },
7049
7075
  customer_created: {
7050
7076
  label: "A new customer signs up",
7051
7077
  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
7078
+ payload: ["customer", "country", "accepts_email", "orders", "tags", "account_state", "store"]
7055
7079
  },
7056
7080
  checkout_abandoned: {
7057
7081
  label: "A checkout is abandoned",
7058
7082
  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
7083
  payload: [
7061
7084
  "total",
7062
7085
  "currency",
@@ -7069,13 +7092,11 @@ var STORE_EVENTS = {
7069
7092
  "started_at",
7070
7093
  "last_activity_at",
7071
7094
  "store"
7072
- ],
7073
- namesProduct: false
7095
+ ]
7074
7096
  },
7075
7097
  sales_drop: {
7076
7098
  label: "Sales drop",
7077
7099
  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
7100
  payload: [
7080
7101
  "window_hours",
7081
7102
  "window_start",
@@ -7088,11 +7109,73 @@ var STORE_EVENTS = {
7088
7109
  "drop_percent",
7089
7110
  "baseline_days",
7090
7111
  "store"
7091
- ],
7092
- namesProduct: false
7112
+ ]
7093
7113
  }
7094
7114
  };
7095
7115
  var STORE_EVENT_NAMES = Object.keys(STORE_EVENTS);
7116
+ var STORE_FIELD_LABELS = {
7117
+ baker_source: "Comes from",
7118
+ store: "Store",
7119
+ title: "Product",
7120
+ handle: "Product address",
7121
+ status: "Status",
7122
+ published: "On the store",
7123
+ price: "Price",
7124
+ price_max: "Highest price",
7125
+ currency: "Currency",
7126
+ collections: "Collections",
7127
+ tags: "Tags",
7128
+ product_type: "Product type",
7129
+ vendor: "Brand",
7130
+ product_url: "Product page",
7131
+ whole_product: "Whole product out of stock",
7132
+ options_out: "Options out of stock",
7133
+ options_out_count: "How many options are out",
7134
+ options_count: "How many options it has",
7135
+ promoted_by: "Ads found",
7136
+ promotions_checked: "Where Baker looked for ads",
7137
+ whole_product_was_out: "Whole product was out of stock",
7138
+ options_back: "Options back in stock",
7139
+ still_out_count: "Options still out of stock",
7140
+ stock_holds: "Ads Baker paused",
7141
+ reason: "Why",
7142
+ changed: "What changed",
7143
+ before_title: "Name before",
7144
+ before_price: "Price before",
7145
+ before_status: "Status before",
7146
+ before_published: "On the store before",
7147
+ price_changes: "Price changes",
7148
+ collection: "Collection",
7149
+ product_count: "Products",
7150
+ before_product_count: "Products before",
7151
+ order_name: "Order",
7152
+ ordered_at: "Ordered at",
7153
+ shopify_source: "Sold through",
7154
+ visitor_from: "Shopper came from",
7155
+ campaign_from: "Campaign",
7156
+ customer: "Customer",
7157
+ country: "Country",
7158
+ accepts_email: "Accepts email",
7159
+ orders: "Orders",
7160
+ account_state: "Account",
7161
+ total: "Total",
7162
+ items_count: "Items",
7163
+ products: "Products",
7164
+ product_handles: "Product addresses",
7165
+ known_customer: "Known customer",
7166
+ discount_codes_count: "Discount codes",
7167
+ started_at: "Started at",
7168
+ last_activity_at: "Last active at",
7169
+ window_hours: "Hours looked at",
7170
+ window_start: "From",
7171
+ window_end: "To",
7172
+ expected_orders: "Usual orders",
7173
+ revenue: "Sales",
7174
+ expected_revenue: "Usual sales",
7175
+ drop_percent: "Drop (%)",
7176
+ baseline_days: "Days compared"
7177
+ };
7178
+ var FIELD_LABELS = new Map(Object.entries(STORE_FIELD_LABELS));
7096
7179
 
7097
7180
  // ../api/src/scheduledAgentRules.ts
7098
7181
  var CONDITION_OPERATORS = {
@@ -8435,8 +8518,8 @@ var videosDeleteResponseSchema = z29.object({ ok: z29.literal(true) });
8435
8518
 
8436
8519
  // src/commands/actions/tagError.ts
8437
8520
  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);
8521
+ function splitSlugs(list3) {
8522
+ return list3.split(",").map((slug) => slug.trim()).filter((slug) => slug.length > 0);
8440
8523
  }
8441
8524
  function actionTagFix(message) {
8442
8525
  const match = UNKNOWN_TAG_RE.exec(message);
@@ -8535,9 +8618,9 @@ function parsePriority(value, { allowClear }) {
8535
8618
  `Unknown --priority "${value}". Expected one of: ${ACTION_PRIORITIES.join(", ")}${allowClear ? ", or 'none' to clear" : ""}.`
8536
8619
  );
8537
8620
  }
8538
- function looksScheduled(name, description) {
8621
+ function looksScheduled(name, description2) {
8539
8622
  const haystack = fold(`${name}
8540
- ${description}`);
8623
+ ${description2}`);
8541
8624
  return SCHEDULE_SIGNALS.some((re2) => re2.test(haystack));
8542
8625
  }
8543
8626
  var CAMPAIGN_ENTITY = String.raw`(?:campaigns?|campanas?|ad\s*sets?|adsets?|ad\s*groups?|adgroups?|ads?|anuncios?|conjuntos?\s+de\s+anuncios|grupos?\s+de\s+anuncios)`;
@@ -8695,10 +8778,10 @@ var SURFACE_RULES = [
8695
8778
  var FAN_OUT_NUDGE_THRESHOLD = 2;
8696
8779
  var FAN_OUT_THRESHOLD = 3;
8697
8780
  var SCHEDULED_HINT = 'This reads as recurring/scheduled work. If it should run on a cadence or a future date, create a Scheduled agent instead \u2014 baker scheduled-agents create --cron "0 9 * * MON" (or --run-at, or --trigger for a reaction rather than a clock; guide: `__tooling__/docs/tools/baker/scheduled-agents.md`). Do not capture "set up a schedule" as a Work Action.';
8698
- function looksExecutable(name, description) {
8781
+ function looksExecutable(name, description2) {
8699
8782
  const foldedName = fold(name);
8700
8783
  const full = `${foldedName}
8701
- ${fold(description)}`;
8784
+ ${fold(description2)}`;
8702
8785
  for (const rule of SURFACE_RULES) {
8703
8786
  const haystack = rule.nameOnly ? foldedName : full;
8704
8787
  if (rule.except?.some((re2) => re2.test(full))) {
@@ -8718,7 +8801,7 @@ ${fold(description)}`;
8718
8801
  }
8719
8802
  function buildCreateHints({
8720
8803
  name,
8721
- description,
8804
+ description: description2,
8722
8805
  tempId,
8723
8806
  tags,
8724
8807
  prioritySet,
@@ -8726,13 +8809,13 @@ function buildCreateHints({
8726
8809
  }) {
8727
8810
  const hints2 = [];
8728
8811
  const expanded = shellExpandedIn(`${name}
8729
- ${description}`);
8812
+ ${description2}`);
8730
8813
  if (expanded) {
8731
8814
  hints2.push(
8732
8815
  `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.`
8733
8816
  );
8734
8817
  }
8735
- const advisory = advisoryHint(name, description);
8818
+ const advisory = advisoryHint(name, description2);
8736
8819
  if (advisory) {
8737
8820
  hints2.push(
8738
8821
  `${advisory.hint} If nothing actually blocks it: baker actions draft remove ${tempId}, then do the work.`
@@ -8748,7 +8831,7 @@ ${description}`);
8748
8831
  );
8749
8832
  }
8750
8833
  hints2.push(`Link dependencies: baker actions link --blocker <id> --blocked ${tempId}`);
8751
- if (!description) {
8834
+ if (!description2) {
8752
8835
  hints2.push("Add description: baker actions update <tempId> --description '...' (what/why/where/done-when)");
8753
8836
  }
8754
8837
  if (!tags || tags.length === 0) {
@@ -8766,11 +8849,11 @@ ${description}`);
8766
8849
  function shellExpandedIn(text2) {
8767
8850
  return /(?:^|[\s(])((?:\/usr)?\/bin\/(?:ba|z|da|k)?sh|-?(?:ba|z|da|k)sh)\b/.exec(text2)?.[1] ?? null;
8768
8851
  }
8769
- function advisoryHint(name, description) {
8770
- if (looksScheduled(name, description)) {
8852
+ function advisoryHint(name, description2) {
8853
+ if (looksScheduled(name, description2)) {
8771
8854
  return { kind: "scheduled", hint: SCHEDULED_HINT };
8772
8855
  }
8773
- const executable = looksExecutable(name, description);
8856
+ const executable = looksExecutable(name, description2);
8774
8857
  return executable ? { kind: "executable", ...executable } : null;
8775
8858
  }
8776
8859
  var ACTIONS_LIST_DEFAULT_LIMIT = 500;
@@ -8856,9 +8939,9 @@ function parseSkillFrontmatter(md) {
8856
8939
  const lines = extractFrontmatterLines(md);
8857
8940
  if (!lines) return null;
8858
8941
  const name = readField(lines, "name");
8859
- const description = readField(lines, "description");
8860
- if (!name || !description) return null;
8861
- const trimmed = description.length > DESCRIPTION_MAX ? `${description.slice(0, DESCRIPTION_MAX)}\u2026` : description;
8942
+ const description2 = readField(lines, "description");
8943
+ if (!name || !description2) return null;
8944
+ const trimmed = description2.length > DESCRIPTION_MAX ? `${description2.slice(0, DESCRIPTION_MAX)}\u2026` : description2;
8862
8945
  return { name, description: trimmed };
8863
8946
  }
8864
8947
  function findSkillsDir(startDir) {
@@ -11601,8 +11684,8 @@ function capabilityFix(surface) {
11601
11684
  // src/commands/ads/boolean-word-positional.ts
11602
11685
  var BOOLEAN_WORD = /^(true|false)$/i;
11603
11686
  function booleanWordPositionalError(args5, entity) {
11604
- const positionals = Array.isArray(args5._) ? args5._ : [];
11605
- const word = positionals.find((value2) => typeof value2 === "string" && BOOLEAN_WORD.test(value2));
11687
+ const positionals2 = Array.isArray(args5._) ? args5._ : [];
11688
+ const word = positionals2.find((value2) => typeof value2 === "string" && BOOLEAN_WORD.test(value2));
11606
11689
  if (word === void 0) {
11607
11690
  return void 0;
11608
11691
  }
@@ -11936,7 +12019,7 @@ function words(headline) {
11936
12019
  return headline.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter(Boolean);
11937
12020
  }
11938
12021
  function canonical(word) {
11939
- for (const [i, group] of CYCLED_VERBS.entries()) if (group.includes(word)) return `group:${i}`;
12022
+ for (const [i, group2] of CYCLED_VERBS.entries()) if (group2.includes(word)) return `group:${i}`;
11940
12023
  return word;
11941
12024
  }
11942
12025
  function overlap(a, b) {
@@ -12211,11 +12294,11 @@ function requireTarget(args5, entity) {
12211
12294
  failWriteValidation(booleanWord);
12212
12295
  }
12213
12296
  const positional = Array.isArray(args5._) ? args5._[0] : void 0;
12214
- const target2 = args5.id ?? args5.target ?? args5.ref ?? positional;
12215
- if (typeof target2 !== "string" || target2.length === 0) {
12297
+ const target4 = args5.id ?? args5.target ?? args5.ref ?? positional;
12298
+ if (typeof target4 !== "string" || target4.length === 0) {
12216
12299
  failWriteValidation(`pass the ${entity} resource name or id as the positional argument`);
12217
12300
  }
12218
- return target2;
12301
+ return target4;
12219
12302
  }
12220
12303
  function requireStringFlag(value, flag3) {
12221
12304
  if (typeof value !== "string" || value.length === 0) {
@@ -12423,11 +12506,11 @@ async function stageGoogleOps(rawOps, hints2) {
12423
12506
  handleGoogleError(err);
12424
12507
  }
12425
12508
  }
12426
- async function stageUpdate(kind, customerId, target2, payload, hints2) {
12427
- await stageGoogleOp({ kind, customerId, target: target2, payload }, hints2);
12509
+ async function stageUpdate(kind, customerId, target4, payload, hints2) {
12510
+ await stageGoogleOp({ kind, customerId, target: target4, payload }, hints2);
12428
12511
  }
12429
- async function stageTarget(kind, customerId, target2, hints2) {
12430
- await stageGoogleOp({ kind, customerId, target: target2 }, hints2);
12512
+ async function stageTarget(kind, customerId, target4, hints2) {
12513
+ await stageGoogleOp({ kind, customerId, target: target4 }, hints2);
12431
12514
  }
12432
12515
  async function draftAction(path49, body, chat) {
12433
12516
  try {
@@ -12606,9 +12689,9 @@ var DATAFORSEO_LANGUAGES = [
12606
12689
  { code: "da", name: "Danish" },
12607
12690
  { code: "fi", name: "Finnish" }
12608
12691
  ];
12609
- function toRecord(list) {
12692
+ function toRecord(list3) {
12610
12693
  const map = {};
12611
- for (const item of list) {
12694
+ for (const item of list3) {
12612
12695
  const key = item.id ?? item.code;
12613
12696
  if (key) map[key] = item.name;
12614
12697
  }
@@ -13984,9 +14067,9 @@ function servingScopeWarnings(args5, finalQuery) {
13984
14067
  }
13985
14068
  function parseSelectedFields(gaql) {
13986
14069
  const match = gaql.match(/\bSELECT\b([\s\S]*?)\bFROM\b/i);
13987
- const list = match?.[1];
13988
- if (!list) return [];
13989
- return list.split(",").map((field) => field.trim()).filter((field) => /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)*$/.test(field));
14070
+ const list3 = match?.[1];
14071
+ if (!list3) return [];
14072
+ return list3.split(",").map((field) => field.trim()).filter((field) => /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)*$/.test(field));
13990
14073
  }
13991
14074
  function foldObservedFields(rows, observed) {
13992
14075
  for (const row of rows) {
@@ -14983,6 +15066,7 @@ var DESTINATION_NAMES = {
14983
15066
  googleSpreadsheet: "Google Sheets",
14984
15067
  hubspotForm: "HubSpot Form",
14985
15068
  goHighlevelContact: "GoHighLevel",
15069
+ shopifyCustomer: "Shopify",
14986
15070
  crmble: "Crmble"
14987
15071
  };
14988
15072
  function destinationKey(type) {
@@ -18441,13 +18525,13 @@ var budgetsCommand = defineCommand31({
18441
18525
  },
18442
18526
  run: async ({ args: args5 }) => {
18443
18527
  const customerId = requireCustomerId(args5);
18444
- const target2 = requireTarget(args5, "budget");
18528
+ const target4 = requireTarget(args5, "budget");
18445
18529
  const payload = mergePayload(loadJsonFileArg(args5.file), {
18446
18530
  name: args5.name,
18447
18531
  amountMicros: microsFlag(args5.amount, "--amount"),
18448
18532
  deliveryMethod: args5.delivery
18449
18533
  });
18450
- await stageUpdate("google.budget.update", customerId, target2, payload, BUDGET_DEMAND_HINTS);
18534
+ await stageUpdate("google.budget.update", customerId, target4, payload, BUDGET_DEMAND_HINTS);
18451
18535
  }
18452
18536
  })
18453
18537
  }
@@ -18763,7 +18847,7 @@ var campaignsCommand = defineCommand31({
18763
18847
  if (level !== "campaign") {
18764
18848
  failWriteValidation("--level must be campaign or account");
18765
18849
  }
18766
- const target2 = requireTarget(args5, "campaign");
18850
+ const target4 = requireTarget(args5, "campaign");
18767
18851
  const payload = mergePayload(loadJsonFileArg(args5.file), {
18768
18852
  name: args5.name,
18769
18853
  budget: args5["budget-ref"],
@@ -18776,7 +18860,7 @@ var campaignsCommand = defineCommand31({
18776
18860
  euPoliticalAds: args5["eu-political-ads"],
18777
18861
  status: args5.status
18778
18862
  });
18779
- await stageUpdate("google.campaign.update", customerId, target2, payload);
18863
+ await stageUpdate("google.campaign.update", customerId, target4, payload);
18780
18864
  }
18781
18865
  }),
18782
18866
  pause: statusCommand2("google.campaign.pause", "campaign"),
@@ -18846,13 +18930,13 @@ var adGroupsCommand = defineCommand31({
18846
18930
  },
18847
18931
  run: async ({ args: args5 }) => {
18848
18932
  const customerId = requireCustomerId(args5);
18849
- const target2 = requireTarget(args5, "ad group");
18933
+ const target4 = requireTarget(args5, "ad group");
18850
18934
  const payload = mergePayload(loadJsonFileArg(args5.file), {
18851
18935
  name: args5.name,
18852
18936
  cpcBidMicros: microsFlag(args5["cpc-bid"], "--cpc-bid"),
18853
18937
  status: args5.status
18854
18938
  });
18855
- await stageUpdate("google.adGroup.update", customerId, target2, payload);
18939
+ await stageUpdate("google.adGroup.update", customerId, target4, payload);
18856
18940
  }
18857
18941
  }),
18858
18942
  pause: statusCommand2("google.adGroup.pause", "ad group"),
@@ -18880,11 +18964,11 @@ function keywordFinalUrlHints(finalUrl, count2) {
18880
18964
  }
18881
18965
  function keywordUpdateFinalUrls(args5) {
18882
18966
  const finalUrl = args5["final-url"];
18883
- const clear = args5["clear-final-url"] === true;
18967
+ const clear2 = args5["clear-final-url"] === true;
18884
18968
  if (typeof finalUrl !== "string" || finalUrl.length === 0) {
18885
- return clear ? [] : void 0;
18969
+ return clear2 ? [] : void 0;
18886
18970
  }
18887
- if (clear) {
18971
+ if (clear2) {
18888
18972
  failWriteValidation(
18889
18973
  "--clear-final-url sends the keyword back to the ad's landing page \u2014 pass it OR --final-url, not both"
18890
18974
  );
@@ -19500,13 +19584,13 @@ var assetGroupsCommand = defineCommand31({
19500
19584
  },
19501
19585
  run: async ({ args: args5 }) => {
19502
19586
  const customerId = requireCustomerId(args5);
19503
- const target2 = requireTarget(args5, "asset group");
19587
+ const target4 = requireTarget(args5, "asset group");
19504
19588
  const payload = mergePayload(loadJsonFileArg(args5.file), {
19505
19589
  name: args5.name,
19506
19590
  finalUrls: listFlag(args5["final-urls"]),
19507
19591
  status: args5.status
19508
19592
  });
19509
- await stageUpdate("google.assetGroup.update", customerId, target2, payload);
19593
+ await stageUpdate("google.assetGroup.update", customerId, target4, payload);
19510
19594
  }
19511
19595
  }),
19512
19596
  attach: defineCommand31({
@@ -19544,9 +19628,9 @@ var assetGroupsCommand = defineCommand31({
19544
19628
  ])
19545
19629
  }
19546
19630
  });
19547
- function fileCreateCommand(name, kind, description, hints2) {
19631
+ function fileCreateCommand(name, kind, description2, hints2) {
19548
19632
  return defineCommand31({
19549
- meta: { name, description },
19633
+ meta: { name, description: description2 },
19550
19634
  args: { ...customerIdArg, ...fileArg },
19551
19635
  run: async ({ args: args5 }) => {
19552
19636
  const customerId = requireCustomerId(args5);
@@ -19588,7 +19672,7 @@ var assetsUpdateCommand = defineCommand31({
19588
19672
  },
19589
19673
  run: async ({ args: args5 }) => {
19590
19674
  const customerId = requireCustomerId(args5);
19591
- const target2 = requireTarget(args5, "asset");
19675
+ const target4 = requireTarget(args5, "asset");
19592
19676
  const payload = mergePayload(loadJsonFileArg(args5.file), {
19593
19677
  name: args5.name,
19594
19678
  linkText: args5["link-text"],
@@ -19601,7 +19685,7 @@ var assetsUpdateCommand = defineCommand31({
19601
19685
  callToAction: args5.cta,
19602
19686
  text: args5.text
19603
19687
  });
19604
- await stageUpdate("google.asset.update", customerId, target2, payload);
19688
+ await stageUpdate("google.asset.update", customerId, target4, payload);
19605
19689
  }
19606
19690
  });
19607
19691
  var assetsCommand = defineCommand31({
@@ -19914,8 +19998,8 @@ var campaignCriteriaCommand = defineCommand31({
19914
19998
  },
19915
19999
  run: async ({ args: args5 }) => {
19916
20000
  const customerId = requireCustomerId(args5);
19917
- const target2 = requireTarget(args5, "campaign criterion");
19918
- await stageUpdate("google.campaignCriterion.update", customerId, target2, {
20001
+ const target4 = requireTarget(args5, "campaign criterion");
20002
+ await stageUpdate("google.campaignCriterion.update", customerId, target4, {
19919
20003
  bidModifier: bidModifierFlag(args5["bid-modifier"])
19920
20004
  });
19921
20005
  }
@@ -20345,7 +20429,7 @@ async function stageLinkedinOps(rawOps, hints2) {
20345
20429
  async function stageStatusChange(input) {
20346
20430
  const payload = input.kind === "creative.update" ? { intendedStatus: input.status } : { status: input.status };
20347
20431
  await stageLinkedinOps(
20348
- input.targets.map((target2) => ({ kind: input.kind, accountId: input.accountId, target: target2, payload }))
20432
+ input.targets.map((target4) => ({ kind: input.kind, accountId: input.accountId, target: target4, payload }))
20349
20433
  );
20350
20434
  }
20351
20435
  function requireTarget2(args5, entity) {
@@ -20353,17 +20437,17 @@ function requireTarget2(args5, entity) {
20353
20437
  if (inverted !== void 0) {
20354
20438
  failWriteValidation2(inverted);
20355
20439
  }
20356
- const positionals = readPositionals(args5);
20357
- const target2 = args5.id ?? args5.target ?? args5.ref ?? positionals[0];
20358
- if (typeof target2 !== "string" || target2.length === 0) {
20440
+ const positionals2 = readPositionals(args5);
20441
+ const target4 = args5.id ?? args5.target ?? args5.ref ?? positionals2[0];
20442
+ if (typeof target4 !== "string" || target4.length === 0) {
20359
20443
  failWriteValidation2(`pass the ${entity} id or URN as the positional argument`);
20360
20444
  }
20361
- if (positionals.length > 1 || splitIdList(target2).length > 1) {
20445
+ if (positionals2.length > 1 || splitIdList(target4).length > 1) {
20362
20446
  failWriteValidation2(
20363
20447
  `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.`
20364
20448
  );
20365
20449
  }
20366
- return target2;
20450
+ return target4;
20367
20451
  }
20368
20452
  function readPositionals(args5) {
20369
20453
  if (!Array.isArray(args5._)) {
@@ -20381,11 +20465,11 @@ function idsFileEntries(path49) {
20381
20465
  return readFileSync5(path49, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
20382
20466
  }
20383
20467
  function requireTargets(args5, entity) {
20384
- const positionals = readPositionals(args5);
20468
+ const positionals2 = readPositionals(args5);
20385
20469
  const named = [args5.id, args5.target, args5.ref].filter(
20386
20470
  (value) => typeof value === "string" && value.length > 0
20387
20471
  );
20388
- const fromArgs = positionals.length > 0 ? positionals : named;
20472
+ const fromArgs = positionals2.length > 0 ? positionals2 : named;
20389
20473
  const targets = [.../* @__PURE__ */ new Set([...fromArgs.flatMap(splitIdList), ...idsFileEntries(args5["ids-file"])])];
20390
20474
  if (targets.length === 0) {
20391
20475
  failWriteValidation2(
@@ -21329,9 +21413,9 @@ Example: baker ads linkedin campaigns create --name "ABM Tier-1" --group li_temp
21329
21413
  await stageOp({ kind: "campaign.create", accountId, payload });
21330
21414
  }
21331
21415
  });
21332
- function campaignUpdatePayloadFromFlags(args5, target2, file) {
21416
+ function campaignUpdatePayloadFromFlags(args5, target4, file) {
21333
21417
  const flags = campaignPayloadFromFlags(args5);
21334
- if (!target2.startsWith(TEMP_REF_PREFIX)) {
21418
+ if (!target4.startsWith(TEMP_REF_PREFIX)) {
21335
21419
  flags.campaignGroup = void 0;
21336
21420
  flags.type = void 0;
21337
21421
  flags.locale = void 0;
@@ -21352,9 +21436,9 @@ Example: baker ads linkedin campaigns update 123456 --daily-budget 100 --currenc
21352
21436
  },
21353
21437
  run: async ({ args: args5 }) => {
21354
21438
  const accountId = bareAccountId(args5);
21355
- const target2 = requireTarget2(args5, "campaign");
21356
- const payload = campaignUpdatePayloadFromFlags(args5, target2, loadJsonFileArg2(args5.file));
21357
- await stageOp({ kind: "campaign.update", accountId, target: target2, payload });
21439
+ const target4 = requireTarget2(args5, "campaign");
21440
+ const payload = campaignUpdatePayloadFromFlags(args5, target4, loadJsonFileArg2(args5.file));
21441
+ await stageOp({ kind: "campaign.update", accountId, target: target4, payload });
21358
21442
  }
21359
21443
  });
21360
21444
  function statusSugarCommand(entity, kind, name) {
@@ -21501,11 +21585,11 @@ Examples:
21501
21585
  file: { type: "string", description: "JSON file with the full payload; flags override file keys" }
21502
21586
  },
21503
21587
  run: async ({ args: args5, rawArgs }) => {
21504
- const { accountId, target: target2 } = accountTrackingTarget(args5);
21588
+ const { accountId, target: target4 } = accountTrackingTarget(args5);
21505
21589
  await stageOp({
21506
21590
  kind: "trackingParams.set",
21507
21591
  accountId,
21508
- target: target2,
21592
+ target: target4,
21509
21593
  payload: trackingParamsPayload(args5, rawArgs)
21510
21594
  });
21511
21595
  }
@@ -21793,7 +21877,7 @@ Examples:
21793
21877
  campaign: args5.campaign,
21794
21878
  content: Object.keys(content).length > 0 ? content : void 0
21795
21879
  });
21796
- await stageLinkedinOps(targets.map((target2) => ({ kind: "creative.update", accountId, target: target2, payload })));
21880
+ await stageLinkedinOps(targets.map((target4) => ({ kind: "creative.update", accountId, target: target4, payload })));
21797
21881
  }
21798
21882
  });
21799
21883
  var AUDIENCE_TYPES2 = ["company-list", "user-list", "retargeting", "engagement"];
@@ -21817,7 +21901,7 @@ Example: baker ads linkedin audiences create --name "ABM Tier-1" --type company-
21817
21901
  },
21818
21902
  run: async ({ args: args5 }) => {
21819
21903
  const accountId = bareAccountId(args5);
21820
- const list = parseListFileArg(args5["list-file"], INLINE_ROWS_MAX);
21904
+ const list3 = parseListFileArg(args5["list-file"], INLINE_ROWS_MAX);
21821
21905
  const raw = args5.file ? loadJsonFileArg2(args5.file) : void 0;
21822
21906
  await stageOp({
21823
21907
  kind: "audience.create",
@@ -21825,7 +21909,7 @@ Example: baker ads linkedin audiences create --name "ABM Tier-1" --type company-
21825
21909
  payload: {
21826
21910
  name: args5.name,
21827
21911
  audienceType: args5.type,
21828
- ...list ? { listColumns: list.columns, listRows: list.rows } : {},
21912
+ ...list3 ? { listColumns: list3.columns, listRows: list3.rows } : {},
21829
21913
  ...raw ? { raw } : {}
21830
21914
  }
21831
21915
  });
@@ -21844,8 +21928,8 @@ Example: baker ads linkedin audiences upload urn:li:dmpSegment:42 --list-file mo
21844
21928
  },
21845
21929
  run: async ({ args: args5 }) => {
21846
21930
  const accountId = bareAccountId(args5);
21847
- const list = parseListFileArg(args5["list-file"], INLINE_ROWS_MAX);
21848
- if (!list) {
21931
+ const list3 = parseListFileArg(args5["list-file"], INLINE_ROWS_MAX);
21932
+ if (!list3) {
21849
21933
  failWriteValidation2("--list-file is required");
21850
21934
  }
21851
21935
  await stageOp({
@@ -21853,8 +21937,8 @@ Example: baker ads linkedin audiences upload urn:li:dmpSegment:42 --list-file mo
21853
21937
  accountId,
21854
21938
  payload: {
21855
21939
  segment: requireTarget2(args5, "audience segment"),
21856
- listColumns: list.columns,
21857
- listRows: list.rows
21940
+ listColumns: list3.columns,
21941
+ listRows: list3.rows
21858
21942
  }
21859
21943
  });
21860
21944
  }
@@ -26050,11 +26134,11 @@ function requireTarget3(args5, entity) {
26050
26134
  failWriteValidation3(inverted);
26051
26135
  }
26052
26136
  const positional = Array.isArray(args5._) ? args5._[0] : void 0;
26053
- const target2 = args5.id ?? args5.target ?? args5.ref ?? positional;
26054
- if (typeof target2 !== "string" || target2.length === 0) {
26137
+ const target4 = args5.id ?? args5.target ?? args5.ref ?? positional;
26138
+ if (typeof target4 !== "string" || target4.length === 0) {
26055
26139
  failWriteValidation3(`pass the ${entity} id as the positional argument`);
26056
26140
  }
26057
- return target2;
26141
+ return target4;
26058
26142
  }
26059
26143
  async function stageOp2(raw) {
26060
26144
  const preflight = metaDraftOpInputSchema.safeParse(raw);
@@ -28243,13 +28327,13 @@ async function resolveXAccountId(args5) {
28243
28327
  });
28244
28328
  process.exit(1);
28245
28329
  }
28246
- const list = accounts.map((a) => ` ${a.id} ${a.name}`).join("\n");
28330
+ const list3 = accounts.map((a) => ` ${a.id} ${a.name}`).join("\n");
28247
28331
  writeAdsJson({
28248
28332
  ok: false,
28249
28333
  error: {
28250
28334
  code: "MULTIPLE_ACCOUNTS",
28251
28335
  message: `Multiple X Ads accounts found. Pass --account-id or set BAKER_X_ADS_ACCOUNT_ID:
28252
- ${list}`
28336
+ ${list3}`
28253
28337
  }
28254
28338
  });
28255
28339
  process.exit(1);
@@ -30336,10 +30420,10 @@ function wallClockAsUtc(instant, timeZone) {
30336
30420
  }
30337
30421
  function accountMidnightIso(daysAgo, timeZone, now = /* @__PURE__ */ new Date()) {
30338
30422
  const localNow = new Date(wallClockAsUtc(now, timeZone));
30339
- const target2 = Date.UTC(localNow.getUTCFullYear(), localNow.getUTCMonth(), localNow.getUTCDate() - daysAgo, 0, 0, 0);
30340
- let instant = target2;
30423
+ const target4 = Date.UTC(localNow.getUTCFullYear(), localNow.getUTCMonth(), localNow.getUTCDate() - daysAgo, 0, 0, 0);
30424
+ let instant = target4;
30341
30425
  for (let i = 0; i < 2; i++) {
30342
- instant = target2 - (wallClockAsUtc(new Date(instant), timeZone) - instant);
30426
+ instant = target4 - (wallClockAsUtc(new Date(instant), timeZone) - instant);
30343
30427
  }
30344
30428
  return isoSeconds(new Date(instant));
30345
30429
  }
@@ -31849,12 +31933,12 @@ function setFromRename(args5, existing) {
31849
31933
  }
31850
31934
  function conversionsWrite(args5, existing) {
31851
31935
  const set = [...setFromEvents(args5, existing), ...setFromRename(args5, existing)];
31852
- const remove = [
31936
+ const remove2 = [
31853
31937
  ...args5.removeKeys,
31854
31938
  ...args5.removeName === void 0 ? [] : under(args5.removeName, existing).map((row) => row.eventKey)
31855
31939
  ];
31856
- if (set.length === 0 && remove.length === 0) return null;
31857
- return { ...set.length > 0 ? { set } : {}, ...remove.length > 0 ? { remove } : {} };
31940
+ if (set.length === 0 && remove2.length === 0) return null;
31941
+ return { ...set.length > 0 ? { set } : {}, ...remove2.length > 0 ? { remove: remove2 } : {} };
31858
31942
  }
31859
31943
 
31860
31944
  // src/commands/analytics/conversionHints.ts
@@ -31917,9 +32001,9 @@ function mergeStoredWithCounts(stored, reported) {
31917
32001
  const counts = new Map(reported.map((row) => [row.name, row]));
31918
32002
  const groups = /* @__PURE__ */ new Map();
31919
32003
  for (const row of stored) {
31920
- const group = groups.get(row.name) ?? [];
31921
- group.push(row);
31922
- groups.set(row.name, group);
32004
+ const group2 = groups.get(row.name) ?? [];
32005
+ group2.push(row);
32006
+ groups.set(row.name, group2);
31923
32007
  }
31924
32008
  return [...groups.entries()].map(([name, rows]) => {
31925
32009
  const modes = new Set(rows.map((row) => row.countMode));
@@ -32719,7 +32803,7 @@ import { defineCommand as defineCommand97 } from "citty";
32719
32803
  var SET_FLAGS = ["--source-event", "--platform", "--tag", "--event"];
32720
32804
  function parseSendsArgs(raw, argv, now = Date.now()) {
32721
32805
  const set = parseSetFlags(raw, argv, now);
32722
- const remove = onlyOnce(raw.remove, "--remove", argv);
32806
+ const remove2 = onlyOnce(raw.remove, "--remove", argv);
32723
32807
  const days2 = raw.days === void 0 ? void 0 : Number(raw.days);
32724
32808
  if (days2 !== void 0 && (!Number.isInteger(days2) || days2 <= 0 || days2 > 400)) {
32725
32809
  throw new ConversionArgsError("--days must be a whole number of days, from 1 to 400.");
@@ -32735,7 +32819,7 @@ function parseSendsArgs(raw, argv, now = Date.now()) {
32735
32819
  }
32736
32820
  return {
32737
32821
  ...set === void 0 ? {} : { set },
32738
- ...remove === void 0 ? {} : { remove },
32822
+ ...remove2 === void 0 ? {} : { remove: remove2 },
32739
32823
  ...days2 === void 0 ? {} : { days: days2 },
32740
32824
  ...uploads === void 0 ? {} : { uploads },
32741
32825
  ...raw.failed === true ? { failedOnly: true } : {},
@@ -35884,14 +35968,14 @@ function undisambiguatedSubsets(faces) {
35884
35968
  }
35885
35969
  function suggestFamilies(wanted, catalogue, limit2 = 5) {
35886
35970
  const key = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
35887
- const target2 = key(wanted);
35888
- if (!target2) return [];
35889
- const words2 = target2.split(" ");
35971
+ const target4 = key(wanted);
35972
+ if (!target4) return [];
35973
+ const words2 = target4.split(" ");
35890
35974
  const scored = catalogue.map((family) => {
35891
35975
  const candidate = key(family);
35892
- if (candidate === target2) return { family, score: 0 };
35893
- if (candidate.startsWith(target2)) return { family, score: 1 };
35894
- if (candidate.includes(target2)) return { family, score: 2 };
35976
+ if (candidate === target4) return { family, score: 0 };
35977
+ if (candidate.startsWith(target4)) return { family, score: 1 };
35978
+ if (candidate.includes(target4)) return { family, score: 2 };
35895
35979
  const shared = words2.filter((w) => w.length > 2 && candidate.includes(w)).length;
35896
35980
  return { family, score: shared > 0 ? 3 + (words2.length - shared) : Number.POSITIVE_INFINITY };
35897
35981
  }).filter((c) => Number.isFinite(c.score)).sort((a, b) => a.score - b.score || a.family.localeCompare(b.family));
@@ -35952,8 +36036,8 @@ async function resolveBrandFaces(family, weights, subsets, deps, styles = ["norm
35952
36036
  function rebaseFontFaceSrc(block2, fromDir, toDir = "src/styles") {
35953
36037
  return block2.replace(/url\(\s*(['"]?)([^'")]+)\1\s*\)/gi, (whole, _quote, href) => {
35954
36038
  if (!isRepoFontHref(href)) return whole;
35955
- const target2 = posix.normalize(posix.join(fromDir, href));
35956
- return `url('${posix.relative(toDir, target2)}')`;
36039
+ const target4 = posix.normalize(posix.join(fromDir, href));
36040
+ return `url('${posix.relative(toDir, target4)}')`;
35957
36041
  });
35958
36042
  }
35959
36043
  function declaredFontFaces(source) {
@@ -36131,12 +36215,12 @@ function declaredWeights(body) {
36131
36215
  return range;
36132
36216
  }
36133
36217
  function selfHostedWeights(css, family) {
36134
- const target2 = normalizeFamily(family);
36218
+ const target4 = normalizeFamily(family);
36135
36219
  const weights = /* @__PURE__ */ new Set();
36136
36220
  for (const rule of stripCssComments(css).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
36137
36221
  const body = rule[1] ?? "";
36138
36222
  const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
36139
- if (!declared || normalizeFamily(declared) !== target2) continue;
36223
+ if (!declared || normalizeFamily(declared) !== target4) continue;
36140
36224
  for (const weight of declaredWeights(body)) weights.add(weight);
36141
36225
  }
36142
36226
  return [...weights].sort((a, b) => a - b);
@@ -36146,12 +36230,12 @@ function fontFileSubset(href) {
36146
36230
  return /^[A-Za-z0-9]+-[1-9]00(?:italic)?-([\w-]+)\.woff2$/.exec(file)?.[1] ?? null;
36147
36231
  }
36148
36232
  function selfHostedSubsets(css, family) {
36149
- const target2 = normalizeFamily(family);
36233
+ const target4 = normalizeFamily(family);
36150
36234
  const subsets = /* @__PURE__ */ new Set();
36151
36235
  for (const rule of stripCssComments(css).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
36152
36236
  const body = rule[1] ?? "";
36153
36237
  const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
36154
- if (!declared || normalizeFamily(declared) !== target2) continue;
36238
+ if (!declared || normalizeFamily(declared) !== target4) continue;
36155
36239
  for (const url of body.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/gi)) {
36156
36240
  const subset = fontFileSubset(url[1] ?? "");
36157
36241
  if (subset) subsets.add(subset);
@@ -36160,12 +36244,12 @@ function selfHostedSubsets(css, family) {
36160
36244
  return [...subsets].sort();
36161
36245
  }
36162
36246
  function selfHostedStyles(css, family) {
36163
- const target2 = normalizeFamily(family);
36247
+ const target4 = normalizeFamily(family);
36164
36248
  const styles = /* @__PURE__ */ new Set();
36165
36249
  for (const rule of stripCssComments(css).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
36166
36250
  const body = rule[1] ?? "";
36167
36251
  const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
36168
- if (declared && normalizeFamily(declared) === target2) styles.add(faceStyle(body));
36252
+ if (declared && normalizeFamily(declared) === target4) styles.add(faceStyle(body));
36169
36253
  }
36170
36254
  return [...styles].sort();
36171
36255
  }
@@ -36189,12 +36273,12 @@ function unresolvedBrandFamilies(css) {
36189
36273
  return declared.filter((family) => !loaded.has(family)).map((family) => asAuthored(css, family));
36190
36274
  }
36191
36275
  function stripFontFaceBlocks(source, family, styles) {
36192
- const target2 = normalizeFamily(family);
36276
+ const target4 = normalizeFamily(family);
36193
36277
  const ranges = [];
36194
36278
  for (const rule of stripCssComments(source).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
36195
36279
  const body = rule[1] ?? "";
36196
36280
  const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
36197
- if (!declared || normalizeFamily(declared) !== target2 || rule.index === void 0) continue;
36281
+ if (!declared || normalizeFamily(declared) !== target4 || rule.index === void 0) continue;
36198
36282
  if (styles && !styles.has(faceStyle(body))) continue;
36199
36283
  ranges.push([rule.index, rule.index + rule[0].length]);
36200
36284
  }
@@ -36788,11 +36872,11 @@ function scoreRetentionRisk(videoNodes) {
36788
36872
  };
36789
36873
  }
36790
36874
  function scoreMechanismClarity(allPromptText) {
36791
- const clear = MECHANISM_CUE.test(allPromptText);
36875
+ const clear2 = MECHANISM_CUE.test(allPromptText);
36792
36876
  return {
36793
36877
  dimension: "mechanism_clarity",
36794
- score: clear ? 1 : 0.4,
36795
- note: clear ? "states a mechanism/proof" : "no clear 'why it works' \u2014 add the mechanism"
36878
+ score: clear2 ? 1 : 0.4,
36879
+ note: clear2 ? "states a mechanism/proof" : "no clear 'why it works' \u2014 add the mechanism"
36796
36880
  };
36797
36881
  }
36798
36882
  function clamp01(n) {
@@ -36955,8 +37039,8 @@ function canvasOutputsRoot(canvasFile, cwd = process.cwd(), exists2 = existsSync
36955
37039
  const root = findWorkspaceRoot(path5.dirname(path5.resolve(cwd, canvasFile)), exists2) ?? cwd;
36956
37040
  return path5.join(root, "canvas");
36957
37041
  }
36958
- function canonicalize(target2) {
36959
- const abs = path5.resolve(target2);
37042
+ function canonicalize(target4) {
37043
+ const abs = path5.resolve(target4);
36960
37044
  let dir = abs;
36961
37045
  for (; ; ) {
36962
37046
  try {
@@ -36969,19 +37053,19 @@ function canonicalize(target2) {
36969
37053
  }
36970
37054
  }
36971
37055
  }
36972
- function isInside(root, target2) {
36973
- const rel = path5.relative(root, target2);
37056
+ function isInside(root, target4) {
37057
+ const rel = path5.relative(root, target4);
36974
37058
  return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
36975
37059
  }
36976
- function toCanvasRelative(canvasDir, target2) {
36977
- return path5.relative(canvasDir, target2).split(path5.sep).join("/");
37060
+ function toCanvasRelative(canvasDir, target4) {
37061
+ return path5.relative(canvasDir, target4).split(path5.sep).join("/");
36978
37062
  }
36979
37063
  var RUNTIME_WORKSPACE_ROOT = "/home/user/repo";
36980
37064
  function rewriteTarget(value, workspaceRoot) {
36981
37065
  if (typeof value !== "string" || value.length === 0) return null;
36982
37066
  if (value.includes("[TODO") || looksLikeHttpUrl(value) || !path5.isAbsolute(value)) return null;
36983
- const target2 = canonicalize(value);
36984
- if (isInside(workspaceRoot, target2)) return target2;
37067
+ const target4 = canonicalize(value);
37068
+ if (isInside(workspaceRoot, target4)) return target4;
36985
37069
  const fromRuntime = path5.relative(RUNTIME_WORKSPACE_ROOT, path5.normalize(value));
36986
37070
  if (fromRuntime !== "" && !fromRuntime.startsWith("..") && !path5.isAbsolute(fromRuntime)) {
36987
37071
  return path5.join(workspaceRoot, fromRuntime);
@@ -37007,9 +37091,9 @@ function normalizeCanvasPaths(canvas, canvasDir, workspaceRoot) {
37007
37091
  }
37008
37092
  const key = localPathParam(n);
37009
37093
  if (!key) return node;
37010
- const target2 = rewriteTarget(params2[key], root);
37011
- if (target2 === null) return node;
37012
- const to = toCanvasRelative(dir, target2);
37094
+ const target4 = rewriteTarget(params2[key], root);
37095
+ if (target4 === null) return node;
37096
+ const to = toCanvasRelative(dir, target4);
37013
37097
  rewrites.push({ nodeId, param: key, from: params2[key], to });
37014
37098
  return { ...node, params: { ...params2, [key]: to } };
37015
37099
  });
@@ -37864,8 +37948,8 @@ function castAvatarsOnElements(elementsInput, avatars) {
37864
37948
  if (!avatar) return el;
37865
37949
  next += 1;
37866
37950
  if (el.label) byLabel.set(el.label, avatar);
37867
- const description = avatar.subjectDescription?.trim();
37868
- return { ...el, ...description ? { description } : {}, source_image: avatar.sheetUrl, source_is_sheet: true };
37951
+ const description2 = avatar.subjectDescription?.trim();
37952
+ return { ...el, ...description2 ? { description: description2 } : {}, source_image: avatar.sheetUrl, source_is_sheet: true };
37869
37953
  });
37870
37954
  return cast.map((el) => {
37871
37955
  const avatar = el.same_as ? byLabel.get(el.same_as) : void 0;
@@ -38160,7 +38244,7 @@ function buildFramePrompt(edge, sceneIndex, framePrompt, present2, hasAnchor, mo
38160
38244
  "- ORIGINAL_FRAME \u2014 use ONLY for composition: framing, camera angle, shot size, subject placement, pose, and proportions. IGNORE its text, logo, brand name, colors, AND the identity of every person/animal/object in it \u2014 those come from the labeled reference images above, never from this frame. It is a DIFFERENT brand's footage with DIFFERENT actors, here ONLY to anchor where things sit and how the shot is framed (e.g. a profile/side angle stays a profile/side angle), never who they are or what palette to use."
38161
38245
  ] : []
38162
38246
  ].join("\n");
38163
- const description = framePrompt?.trim() || `the ${edge} frame of scene ${sceneIndex + 1} \u2014 describe the full composition, subjects, setting, action, lighting, and palette here. (Edit this line to change ONLY this frame.)`;
38247
+ const description2 = framePrompt?.trim() || `the ${edge} frame of scene ${sceneIndex + 1} \u2014 describe the full composition, subjects, setting, action, lighting, and palette here. (Edit this line to change ONLY this frame.)`;
38164
38248
  const isHookFrame = sceneIndex === 0 && edge === "start";
38165
38249
  const cleanPlateBlock = [
38166
38250
  "CRITICAL \u2014 RENDER A CLEAN PLATE WITH ZERO TEXT OR GRAPHIC OVERLAYS:",
@@ -38238,7 +38322,7 @@ function buildFramePrompt(edge, sceneIndex, framePrompt, present2, hasAnchor, mo
38238
38322
  ""
38239
38323
  ] : [],
38240
38324
  "FRAME DESCRIPTION (this frame's editable prompt):",
38241
- description,
38325
+ description2,
38242
38326
  "",
38243
38327
  "Render exactly what the FRAME DESCRIPTION and the SHARED AD SPEC specify \u2014 this is the authoritative ad: its cast identity (via the reference images), palette, brand, and intent are law. Keep every recurring element identical to its reference image across all frames. Again: clean plate only \u2014 no rendered text, and no icons/stickers/emojis/badges (those live on the overlay layer).",
38244
38328
  "",
@@ -39094,8 +39178,8 @@ function localizeNumeralsForNative(text2, langCode) {
39094
39178
  if (!langCode || !INTEGER_SPELLERS[langCode]) return text2;
39095
39179
  return text2.replace(/(?<![\w.,-])\d{1,9}(?![\w.,-])/g, (m) => spellNumber(langCode, Number.parseInt(m, 10)));
39096
39180
  }
39097
- function parseVoiceTraits(description) {
39098
- const d = description.toLowerCase();
39181
+ function parseVoiceTraits(description2) {
39182
+ const d = description2.toLowerCase();
39099
39183
  const out = {};
39100
39184
  if (/\b(female|woman|women|girl|lady)\b/.test(d)) out.gender = "female";
39101
39185
  else if (/\b(male|man|men|guy|boy)\b/.test(d)) out.gender = "male";
@@ -39306,11 +39390,11 @@ function makeVoiceFactory(blueprint, canonical2, nodes, voiceLanguage, pinnedVoi
39306
39390
  const existing = bySpeaker.get(speaker);
39307
39391
  if (existing) return existing;
39308
39392
  const id = sanitizeId(`voice_${speaker}`, `voice_${bySpeaker.size}`);
39309
- const description = describe(speaker);
39310
- const traits = parseVoiceTraits(description);
39393
+ const description2 = describe(speaker);
39394
+ const traits = parseVoiceTraits(description2);
39311
39395
  if (voiceLanguage) traits.language = voiceLanguage;
39312
39396
  nodes.push(
39313
- pinnedVoiceId ? { id, type: "voice_select", params: { description, voice_id: pinnedVoiceId, ...traits } } : { id, type: "voice_select", params: { description, ...traits } }
39397
+ pinnedVoiceId ? { id, type: "voice_select", params: { description: description2, voice_id: pinnedVoiceId, ...traits } } : { id, type: "voice_select", params: { description: description2, ...traits } }
39314
39398
  );
39315
39399
  bySpeaker.set(speaker, id);
39316
39400
  return id;
@@ -41312,8 +41396,8 @@ function mimeForFile(filePath) {
41312
41396
  function toPosix(p) {
41313
41397
  return p.split(path14.sep).join("/");
41314
41398
  }
41315
- function isInside2(dir, target2) {
41316
- const rel = path14.relative(dir, target2);
41399
+ function isInside2(dir, target4) {
41400
+ const rel = path14.relative(dir, target4);
41317
41401
  return !rel.startsWith("..") && !path14.isAbsolute(rel);
41318
41402
  }
41319
41403
  function localPathRefsFromCanvas(parsed) {
@@ -41429,11 +41513,11 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
41429
41513
  if (path14.isAbsolute(entry.path) || entry.path.split("/").includes("..")) {
41430
41514
  throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
41431
41515
  }
41432
- const target2 = path14.resolve(resolvedTarget, entry.path);
41433
- if (target2 !== resolvedTarget && !target2.startsWith(resolvedTarget + path14.sep)) {
41516
+ const target4 = path14.resolve(resolvedTarget, entry.path);
41517
+ if (target4 !== resolvedTarget && !target4.startsWith(resolvedTarget + path14.sep)) {
41434
41518
  throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
41435
41519
  }
41436
- const existing = await readFile10(target2).catch(() => null);
41520
+ const existing = await readFile10(target4).catch(() => null);
41437
41521
  if (existing) {
41438
41522
  if (sha256Hex(existing) === entry.sha256) {
41439
41523
  upToDate.push(entry.path);
@@ -41444,19 +41528,19 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
41444
41528
  continue;
41445
41529
  }
41446
41530
  }
41447
- planned.push({ entry, target: target2 });
41531
+ planned.push({ entry, target: target4 });
41448
41532
  }
41449
41533
  if (conflicts.length > 0) throw new SnapshotConflictError(conflicts);
41450
41534
  const restored = [];
41451
- for (const { entry, target: target2 } of planned) {
41535
+ for (const { entry, target: target4 } of planned) {
41452
41536
  const res = await fetch(entry.url);
41453
41537
  if (!res.ok) throw new Error(`snapshot download failed for ${entry.path}: ${res.status} ${res.statusText}`);
41454
41538
  const bytes = Buffer.from(await res.arrayBuffer());
41455
41539
  if (sha256Hex(bytes) !== entry.sha256) {
41456
41540
  throw new Error(`snapshot download for ${entry.path} does not match its recorded sha256`);
41457
41541
  }
41458
- await mkdir5(path14.dirname(target2), { recursive: true });
41459
- await writeFile7(target2, bytes);
41542
+ await mkdir5(path14.dirname(target4), { recursive: true });
41543
+ await writeFile7(target4, bytes);
41460
41544
  restored.push(entry.path);
41461
41545
  }
41462
41546
  return { canvasPath: path14.resolve(resolvedTarget, manifest.canvas.path), restored, upToDate };
@@ -42602,8 +42686,8 @@ function castAvatarsOnStaticElements(elementsInput, avatars) {
42602
42686
  const avatar = isPerson(el) ? avatars[next] : void 0;
42603
42687
  if (!avatar) return el;
42604
42688
  next += 1;
42605
- const description = avatar.subjectDescription?.trim();
42606
- return { ...el, ...description ? { description } : {}, source_image: avatar.sheetUrl };
42689
+ const description2 = avatar.subjectDescription?.trim();
42690
+ return { ...el, ...description2 ? { description: description2 } : {}, source_image: avatar.sheetUrl };
42607
42691
  });
42608
42692
  }
42609
42693
  function staticAvatarsNotCast(elementsInput, handles) {
@@ -43126,9 +43210,9 @@ async function resolveCastAvatars(flag3) {
43126
43210
  }
43127
43211
 
43128
43212
  // src/commands/canvas/scaffold-static-ad.ts
43129
- async function fileExists(target2) {
43213
+ async function fileExists(target4) {
43130
43214
  try {
43131
- await access(target2);
43215
+ await access(target4);
43132
43216
  return true;
43133
43217
  } catch {
43134
43218
  return false;
@@ -43854,8 +43938,8 @@ function beatSeconds(line, videoModel) {
43854
43938
  }
43855
43939
  var BRAND_PLATE = "A solid flat brand colour plate \u2014 an end card. No people, no objects, no scenery, no text of any kind.";
43856
43940
  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;
43857
- function briefWantsLegibleText(show) {
43858
- return ASKS_FOR_LEGIBLE.test(show);
43941
+ function briefWantsLegibleText(show2) {
43942
+ return ASKS_FOR_LEGIBLE.test(show2);
43859
43943
  }
43860
43944
  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ÁÉÍÓÚÑ]/;
43861
43945
  function lineIntroducesSpeaker(line) {
@@ -43909,8 +43993,8 @@ function adSpecCastElements(spec, avatar) {
43909
43993
  ...spec.set?.image?.trim() ? { source_image: spec.set.image.trim() } : {}
43910
43994
  });
43911
43995
  }
43912
- const description = (avatar?.subjectDescription ?? spec.cast?.description)?.trim();
43913
- if (!description) return elements;
43996
+ const description2 = (avatar?.subjectDescription ?? spec.cast?.description)?.trim();
43997
+ if (!description2) return elements;
43914
43998
  const scenes = spec.beats.map((beat, i) => ({ beat, i })).filter(({ beat, i }) => beat.cast !== false && !(endCardWanted(spec) && i === lastIndex)).map(({ i }) => i);
43915
43999
  if (scenes.length === 0) return elements;
43916
44000
  const sheet = avatar?.sheetUrl?.trim();
@@ -43920,7 +44004,7 @@ function adSpecCastElements(spec, avatar) {
43920
44004
  // Same id the dialogue names as speaker: that pairing is what lets the engine see
43921
44005
  // the presenter as present in a scene and route it to native audio.
43922
44006
  cast_id: AD_CAST_ID,
43923
- description,
44007
+ description: description2,
43924
44008
  scenes,
43925
44009
  // An avatar's identity sheet IS the turnaround the engine would otherwise render,
43926
44010
  // so it is handed over as one. Left unmarked, the run paid to generate a sheet OF
@@ -44530,7 +44614,7 @@ var scaffoldAdCommand = defineCommand115({
44530
44614
  if (flagAvatar) {
44531
44615
  spec.data.cast = { ...spec.data.cast, avatar: flagAvatar };
44532
44616
  }
44533
- const contradicts = spec.data.beats.map((beat, i) => ({ i, show: beat.show })).filter(({ show }) => briefWantsLegibleText(show));
44617
+ const contradicts = spec.data.beats.map((beat, i) => ({ i, show: beat.show })).filter(({ show: show2 }) => briefWantsLegibleText(show2));
44534
44618
  if (contradicts.length > 0) {
44535
44619
  writeJson({
44536
44620
  ok: false,
@@ -45214,8 +45298,8 @@ function referenceVideoExt(url, contentType) {
45214
45298
  const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase();
45215
45299
  return mime && VIDEO_EXT_BY_MIME[mime] || ".mp4";
45216
45300
  }
45217
- async function fileExists2(target2) {
45218
- return access2(target2).then(
45301
+ async function fileExists2(target4) {
45302
+ return access2(target4).then(
45219
45303
  () => true,
45220
45304
  () => false
45221
45305
  );
@@ -46533,8 +46617,8 @@ async function importedFiles(text2, file, root) {
46533
46617
  for (const match of text2.matchAll(re2)) {
46534
46618
  const spec = match[1];
46535
46619
  if (spec === void 0) continue;
46536
- const target2 = await resolveSpecifier(spec, file, root);
46537
- if (target2 !== null) out.push(target2);
46620
+ const target4 = await resolveSpecifier(spec, file, root);
46621
+ if (target4 !== null) out.push(target4);
46538
46622
  }
46539
46623
  }
46540
46624
  return out;
@@ -46552,8 +46636,8 @@ async function landingGraphFiles(root, slug) {
46552
46636
  continue;
46553
46637
  }
46554
46638
  seen.add(file);
46555
- for (const target2 of await importedFiles(text2, file, root)) {
46556
- if (!seen.has(target2)) queue.push(target2);
46639
+ for (const target4 of await importedFiles(text2, file, root)) {
46640
+ if (!seen.has(target4)) queue.push(target4);
46557
46641
  }
46558
46642
  }
46559
46643
  return new Set([...seen].map((abs) => path29.relative(root, abs).split(path29.sep).join("/")));
@@ -46676,8 +46760,8 @@ function repointMoved(text2, opts) {
46676
46760
  return text2.replace(QUOTED_RELATIVE, (_match, quote, spec) => {
46677
46761
  const abs = path31.resolve(opts.fromDir, spec);
46678
46762
  const within = path31.relative(opts.movedFrom, abs);
46679
- const target2 = within.startsWith("..") || path31.isAbsolute(within) ? abs : path31.resolve(opts.movedTo, within);
46680
- return `${quote}${toSpecifier(opts.toDir, target2)}${quote}`;
46763
+ const target4 = within.startsWith("..") || path31.isAbsolute(within) ? abs : path31.resolve(opts.movedTo, within);
46764
+ return `${quote}${toSpecifier(opts.toDir, target4)}${quote}`;
46681
46765
  });
46682
46766
  }
46683
46767
 
@@ -46753,8 +46837,8 @@ function repointIntoArchive(text2, sourceAbs, destAbs, archivedBySource) {
46753
46837
  const toDir = path32.dirname(destAbs);
46754
46838
  return text2.replace(QUOTED_RELATIVE2, (match, quote, spec, offset) => {
46755
46839
  const [specPath, suffix] = splitSuffix(spec);
46756
- const target2 = path32.resolve(fromDir, specPath);
46757
- const resolved = resolveInArchive(target2, archivedBySource) ?? resolveOnDisk(target2, isImportPosition(text2, offset));
46840
+ const target4 = path32.resolve(fromDir, specPath);
46841
+ const resolved = resolveInArchive(target4, archivedBySource) ?? resolveOnDisk(target4, isImportPosition(text2, offset));
46758
46842
  if (resolved === null) return match;
46759
46843
  const rel = posix2(path32.relative(toDir, resolved));
46760
46844
  return `${quote}${rel.startsWith(".") ? rel : `./${rel}`}${suffix}${quote}`;
@@ -46812,63 +46896,63 @@ async function promoteVariantFiles(opts) {
46812
46896
  }
46813
46897
  return written;
46814
46898
  }
46815
- function archiveRecord(target2, variantNumber) {
46899
+ function archiveRecord(target4, variantNumber) {
46816
46900
  return `${JSON.stringify(
46817
46901
  {
46818
- experimentId: target2.experimentId,
46819
- page: target2.landingSlug,
46820
- pageInternalId: target2.landingInternalId,
46821
- variant: target2.variantSlug,
46902
+ experimentId: target4.experimentId,
46903
+ page: target4.landingSlug,
46904
+ pageInternalId: target4.landingInternalId,
46905
+ variant: target4.variantSlug,
46822
46906
  variantNumber,
46823
46907
  // Which arm this snapshot is. On a control win the variant lost; on a
46824
46908
  // variant win what lost is the page's own previous content, which never
46825
46909
  // had a number of its own — so it is filed under the test that rejected
46826
46910
  // it, which is the only number that means anything about it.
46827
- arm: target2.survivor === "variant" ? "page-before-this-test" : "variant"
46911
+ arm: target4.survivor === "variant" ? "page-before-this-test" : "variant"
46828
46912
  },
46829
46913
  null,
46830
46914
  2
46831
46915
  )}
46832
46916
  `;
46833
46917
  }
46834
- async function foldExperiment(root, target2) {
46835
- const pageDir = path32.resolve(root, "src/pages", target2.landingSlug);
46836
- const variantDir = path32.resolve(root, "src/pages", target2.variantSlug);
46837
- const variantNumber = variantNumberOf(target2.variantSlug);
46918
+ async function foldExperiment(root, target4) {
46919
+ const pageDir = path32.resolve(root, "src/pages", target4.landingSlug);
46920
+ const variantDir = path32.resolve(root, "src/pages", target4.variantSlug);
46921
+ const variantNumber = variantNumberOf(target4.variantSlug);
46838
46922
  if (variantNumber === null) {
46839
46923
  return {
46840
- error: `\u201C${target2.variantSlug}\u201D is not a variant of \u201C${target2.landingSlug}\u201D, so there is nothing to fold`
46924
+ error: `\u201C${target4.variantSlug}\u201D is not a variant of \u201C${target4.landingSlug}\u201D, so there is nothing to fold`
46841
46925
  };
46842
46926
  }
46843
46927
  if (!await isDir(pageDir)) {
46844
- return { error: `There is no page at src/pages/${target2.landingSlug}/` };
46928
+ return { error: `There is no page at src/pages/${target4.landingSlug}/` };
46845
46929
  }
46846
46930
  if (!await isDir(variantDir)) {
46847
- return { error: `src/pages/${target2.variantSlug}/ is already gone \u2014 this test looks folded in already` };
46931
+ return { error: `src/pages/${target4.variantSlug}/ is already gone \u2014 this test looks folded in already` };
46848
46932
  }
46849
- const archiveKey = target2.landingInternalId ?? target2.landingSlug;
46933
+ const archiveKey = target4.landingInternalId ?? target4.landingSlug;
46850
46934
  const archiveDir = path32.resolve(root, ARCHIVE_ROOT, archiveKey, String(variantNumber));
46851
46935
  await rm8(archiveDir, { recursive: true, force: true });
46852
46936
  await mkdir9(archiveDir, { recursive: true });
46853
46937
  const written = await archiveArm({
46854
46938
  root,
46855
- armDir: target2.survivor === "variant" ? pageDir : variantDir,
46939
+ armDir: target4.survivor === "variant" ? pageDir : variantDir,
46856
46940
  pageDir,
46857
46941
  archiveDir,
46858
- armSlug: target2.survivor === "variant" ? target2.landingSlug : target2.variantSlug
46942
+ armSlug: target4.survivor === "variant" ? target4.landingSlug : target4.variantSlug
46859
46943
  });
46860
- await writeFile12(path32.join(archiveDir, "_variant.json"), archiveRecord(target2, variantNumber), "utf8");
46944
+ await writeFile12(path32.join(archiveDir, "_variant.json"), archiveRecord(target4, variantNumber), "utf8");
46861
46945
  written.push(posix2(path32.join(ARCHIVE_ROOT, archiveKey, String(variantNumber), "_variant.json")));
46862
- if (target2.survivor === "variant") {
46946
+ if (target4.survivor === "variant") {
46863
46947
  written.push(...await promoteVariantFiles({ root, pageDir, variantDir }));
46864
46948
  }
46865
46949
  await rm8(variantDir, { recursive: true, force: true });
46866
46950
  return {
46867
- experimentId: target2.experimentId,
46868
- page: target2.landingSlug,
46951
+ experimentId: target4.experimentId,
46952
+ page: target4.landingSlug,
46869
46953
  archivedTo: posix2(path32.join(ARCHIVE_ROOT, archiveKey, String(variantNumber))),
46870
46954
  written,
46871
- removed: [`src/pages/${target2.variantSlug}`]
46955
+ removed: [`src/pages/${target4.variantSlug}`]
46872
46956
  };
46873
46957
  }
46874
46958
 
@@ -48372,9 +48456,9 @@ var foldCommand = defineCommand124({
48372
48456
  }
48373
48457
  const folded = [];
48374
48458
  const failures = [];
48375
- for (const target2 of owed) {
48376
- const result = await foldExperiment(process.cwd(), target2);
48377
- if ("error" in result) failures.push(`${target2.landingSlug}: ${result.error}`);
48459
+ for (const target4 of owed) {
48460
+ const result = await foldExperiment(process.cwd(), target4);
48461
+ if ("error" in result) failures.push(`${target4.landingSlug}: ${result.error}`);
48378
48462
  else folded.push(result);
48379
48463
  }
48380
48464
  if (folded.length > 0) {
@@ -48916,6 +49000,9 @@ var ADS_MATCHING = [
48916
49000
  "country"
48917
49001
  ];
48918
49002
  var MAPPING_SLOTS = {
49003
+ shopifyCustomer: [
49004
+ { kind: "record", key: "customer", allowed: ["email", "phone", "acceptsEmailMarketing", "acceptsSmsMarketing"] }
49005
+ ],
48919
49006
  googleSpreadsheet: [{ kind: "list", key: "columnMapping", addressedBy: "column" }],
48920
49007
  hubspotForm: [
48921
49008
  {
@@ -49157,9 +49244,9 @@ function listPatch(shape, sideEffect, entries) {
49157
49244
  function existingEntries(shape, sideEffect) {
49158
49245
  const owner = shape.under ? sideEffect[shape.under] : sideEffect;
49159
49246
  if (!owner || typeof owner !== "object") return [];
49160
- const list = owner[shape.key];
49161
- if (!Array.isArray(list)) return [];
49162
- return list.filter((entry) => Boolean(entry) && typeof entry === "object");
49247
+ const list3 = owner[shape.key];
49248
+ if (!Array.isArray(list3)) return [];
49249
+ return list3.filter((entry) => Boolean(entry) && typeof entry === "object");
49163
49250
  }
49164
49251
  function mergeEntries(shape, existing, incoming) {
49165
49252
  const address = (entry) => {
@@ -49660,10 +49747,10 @@ function dropUnsetOptionals(sideEffect, path49) {
49660
49747
  return [{ path: path49, change: `dropped \`${key}\` (an optional string is absent, never null)` }];
49661
49748
  });
49662
49749
  }
49663
- function fillNulledArrays(target2, prefix, path49) {
49750
+ function fillNulledArrays(target4, prefix, path49) {
49664
49751
  return ARRAY_FIELDS.flatMap((key) => {
49665
- if (!(key in target2) || target2[key] !== null) return [];
49666
- target2[key] = [];
49752
+ if (!(key in target4) || target4[key] !== null) return [];
49753
+ target4[key] = [];
49667
49754
  return [{ path: path49, change: `\`${prefix}${key}: null\` \u2192 \`[]\`` }];
49668
49755
  });
49669
49756
  }
@@ -49689,9 +49776,9 @@ function sideEffectsOf(node) {
49689
49776
  function normalizeSideEffect(sideEffect, where) {
49690
49777
  const path49 = `${where} \u2192 ${String(sideEffect.id ?? "side effect")}`;
49691
49778
  const arrays = ARRAY_OWNERS.flatMap((owner) => {
49692
- const target2 = owner ? sideEffect[owner] : sideEffect;
49693
- if (!target2 || typeof target2 !== "object") return [];
49694
- return fillNulledArrays(target2, owner ? `${owner}.` : "", path49);
49779
+ const target4 = owner ? sideEffect[owner] : sideEffect;
49780
+ if (!target4 || typeof target4 !== "object") return [];
49781
+ return fillNulledArrays(target4, owner ? `${owner}.` : "", path49);
49695
49782
  });
49696
49783
  return [...dropUnsetOptionals(sideEffect, path49), ...arrays, ...tidyEmailRecipients(sideEffect, path49)];
49697
49784
  }
@@ -50881,13 +50968,13 @@ async function resolvePropertyId(args5) {
50881
50968
  if (properties.length === 0) {
50882
50969
  handleConnectionError("ga4");
50883
50970
  }
50884
- const list = properties.map((p) => ` ${p.externalId} ${p.name}`).join("\n");
50971
+ const list3 = properties.map((p) => ` ${p.externalId} ${p.name}`).join("\n");
50885
50972
  writeJsonEnvelope({
50886
50973
  ok: false,
50887
50974
  error: {
50888
50975
  code: "MULTIPLE_PROPERTIES",
50889
50976
  message: `Multiple properties found. Pass --property-id or set BAKER_GA4_PROPERTY_ID:
50890
- ${list}`
50977
+ ${list3}`
50891
50978
  }
50892
50979
  });
50893
50980
  process.exit(1);
@@ -50980,11 +51067,11 @@ function failValidation3(message) {
50980
51067
  }
50981
51068
  function requireTarget5(args5, entity) {
50982
51069
  const positional = Array.isArray(args5._) ? args5._[0] : void 0;
50983
- const target2 = args5.id ?? args5.target ?? positional;
50984
- if (typeof target2 !== "string" || target2.length === 0) {
51070
+ const target4 = args5.id ?? args5.target ?? positional;
51071
+ if (typeof target4 !== "string" || target4.length === 0) {
50985
51072
  failValidation3(`pass the ${entity} id as the positional argument`);
50986
51073
  }
50987
- return target2;
51074
+ return target4;
50988
51075
  }
50989
51076
  function readJsonSource(args5, flag3) {
50990
51077
  const inline = args5[flag3];
@@ -51978,11 +52065,11 @@ Examples:
51978
52065
  "property-id": propertyArg
51979
52066
  },
51980
52067
  run: async ({ args: args5 }) => {
51981
- const target2 = requireTarget5(args5, "audience id");
52068
+ const target4 = requireTarget5(args5, "audience id");
51982
52069
  const payload = loadJsonArg(args5);
51983
52070
  await stageOp3({
51984
52071
  kind: "ga4.audience.update",
51985
- target: target2,
52072
+ target: target4,
51986
52073
  payload,
51987
52074
  ...withProperty(args5)
51988
52075
  });
@@ -52111,13 +52198,13 @@ Examples:
52111
52198
  "property-id": propertyArg
52112
52199
  },
52113
52200
  run: async ({ args: args5 }) => {
52114
- const target2 = requireTarget5(args5, "Google Ads link id");
52201
+ const target4 = requireTarget5(args5, "Google Ads link id");
52115
52202
  if (typeof args5["ads-personalization"] !== "string") {
52116
52203
  failValidation3("pass --ads-personalization true|false \u2014 it is the only thing a link update can change");
52117
52204
  }
52118
52205
  await stageOp3({
52119
52206
  kind: "ga4.googleAdsLink.update",
52120
- target: target2,
52207
+ target: target4,
52121
52208
  payload: { adsPersonalizationEnabled: booleanArg(args5["ads-personalization"], "ads-personalization") },
52122
52209
  ...withProperty(args5)
52123
52210
  });
@@ -52352,13 +52439,13 @@ async function resolveSiteUrl(args5) {
52352
52439
  if (sites.length === 0) {
52353
52440
  handleConnectionError("gsc");
52354
52441
  }
52355
- const list = sites.map((s) => ` ${s.siteUrl} (${s.permissionLevel})`).join("\n");
52442
+ const list3 = sites.map((s) => ` ${s.siteUrl} (${s.permissionLevel})`).join("\n");
52356
52443
  writeJsonEnvelope({
52357
52444
  ok: false,
52358
52445
  error: {
52359
52446
  code: "MULTIPLE_SITES",
52360
52447
  message: `Multiple sites found. Pass --site-url or set BAKER_GSC_SITE_URL:
52361
- ${list}`
52448
+ ${list3}`
52362
52449
  }
52363
52450
  });
52364
52451
  process.exit(1);
@@ -53348,7 +53435,7 @@ function propertiesCommand2(object) {
53348
53435
  object
53349
53436
  });
53350
53437
  const { properties } = response.data;
53351
- const operators = Object.entries(HUBSPOT_OPERATORS).map(([kind, list]) => `${kind}: ${list.map((operator) => operator.value).join(", ")}`).join(" \xB7 ");
53438
+ const operators = Object.entries(HUBSPOT_OPERATORS).map(([kind, list3]) => `${kind}: ${list3.map((operator) => operator.value).join(", ")}`).join(" \xB7 ");
53352
53439
  const objectFlag = object === "contact" ? "" : ` --object ${object}`;
53353
53440
  const hints2 = properties.length === 0 ? [`This account has no visible ${object} property, so there is nothing an event could watch.`] : [
53354
53441
  `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.`
@@ -53932,17 +54019,17 @@ var dimensionsCommand = defineCommand147({
53932
54019
  },
53933
54020
  run: async ({ args: args5 }) => {
53934
54021
  try {
53935
- const target2 = args5.target;
53936
- if (!target2) {
54022
+ const target4 = args5.target;
54023
+ if (!target4) {
53937
54024
  writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "target argument is required" } });
53938
54025
  process.exit(1);
53939
54026
  }
53940
- const buffer = await readImageBuffer(target2);
54027
+ const buffer = await readImageBuffer(target4);
53941
54028
  const dims = getDimensionsFromBuffer(buffer);
53942
54029
  if (!dims) {
53943
54030
  writeJson({
53944
54031
  ok: false,
53945
- error: { code: "IMAGE_PROCESSING_ERROR", message: `Could not extract dimensions from ${target2}` }
54032
+ error: { code: "IMAGE_PROCESSING_ERROR", message: `Could not extract dimensions from ${target4}` }
53946
54033
  });
53947
54034
  process.exit(1);
53948
54035
  }
@@ -53950,8 +54037,8 @@ var dimensionsCommand = defineCommand147({
53950
54037
  {
53951
54038
  ok: true,
53952
54039
  data: {
53953
- target: target2,
53954
- source: isRemoteUrl(target2) ? "url" : "file",
54040
+ target: target4,
54041
+ source: isRemoteUrl(target4) ? "url" : "file",
53955
54042
  width: dims.width,
53956
54043
  height: dims.height,
53957
54044
  aspectRatio: Math.round(dims.aspectRatio * 1e4) / 1e4,
@@ -54080,15 +54167,15 @@ registerSchema({
54080
54167
  var ValidationError3 = class extends Error {
54081
54168
  };
54082
54169
  var UnusableTargetError = class extends Error {
54083
- constructor(target2) {
54084
- super(`"${target2}" is neither an image URL nor a library image id.`);
54085
- this.target = target2;
54170
+ constructor(target4) {
54171
+ super(`"${target4}" is neither an image URL nor a library image id.`);
54172
+ this.target = target4;
54086
54173
  }
54087
54174
  };
54088
54175
  function readTargets(args5) {
54089
- const positionals = Array.isArray(args5._) ? args5._ : [];
54176
+ const positionals2 = Array.isArray(args5._) ? args5._ : [];
54090
54177
  const named = typeof args5.targets === "string" ? [args5.targets] : [];
54091
- const all = positionals.length > 0 ? positionals : named;
54178
+ const all = positionals2.length > 0 ? positionals2 : named;
54092
54179
  const seen = /* @__PURE__ */ new Set();
54093
54180
  const out = [];
54094
54181
  for (const value of all) {
@@ -54113,13 +54200,13 @@ function classify(raw) {
54113
54200
  }
54114
54201
  return { raw, kind: "imageId" };
54115
54202
  }
54116
- async function resolveTarget(target2) {
54117
- if (target2.kind === "url") {
54118
- return { input: target2.raw, url: target2.raw, baseName: baseNameFromUrl(target2.raw) };
54203
+ async function resolveTarget(target4) {
54204
+ if (target4.kind === "url") {
54205
+ return { input: target4.raw, url: target4.raw, baseName: baseNameFromUrl(target4.raw) };
54119
54206
  }
54120
- const doc = await apiGet("/api/images/get", { id: target2.raw });
54121
- const stem = sanitizeBaseName(doc.name.length > 0 ? doc.name : target2.raw);
54122
- return { input: target2.raw, url: doc.imageUrl, baseName: stem };
54207
+ const doc = await apiGet("/api/images/get", { id: target4.raw });
54208
+ const stem = sanitizeBaseName(doc.name.length > 0 ? doc.name : target4.raw);
54209
+ return { input: target4.raw, url: doc.imageUrl, baseName: stem };
54123
54210
  }
54124
54211
  async function fetchBytes(url) {
54125
54212
  return await fetchExternalBytes(url, { maxBytes: MAX_REMOTE_IMAGE_BYTES });
@@ -54155,9 +54242,9 @@ function failureMessage(err, fallback) {
54155
54242
  async function runDownloads(plan) {
54156
54243
  const failed = [];
54157
54244
  const fetched = [];
54158
- for (const target2 of plan.targets) {
54245
+ for (const target4 of plan.targets) {
54159
54246
  try {
54160
- const resolved = await resolveTarget(target2);
54247
+ const resolved = await resolveTarget(target4);
54161
54248
  const { buffer, contentType } = await fetchBytes(resolved.url);
54162
54249
  fetched.push({
54163
54250
  input: resolved.input,
@@ -54171,7 +54258,7 @@ async function runDownloads(plan) {
54171
54258
  })
54172
54259
  });
54173
54260
  } catch (err) {
54174
- failed.push({ input: target2.raw, error: failureMessage(err, "Download failed") });
54261
+ failed.push({ input: target4.raw, error: failureMessage(err, "Download failed") });
54175
54262
  }
54176
54263
  }
54177
54264
  const paths = disambiguate(fetched.map((item) => item.path));
@@ -54392,7 +54479,7 @@ function providerHitNormalizer(record, full) {
54392
54479
  if (!full) {
54393
54480
  return compact3;
54394
54481
  }
54395
- const group = asRecord2(record.group);
54482
+ const group2 = asRecord2(record.group);
54396
54483
  return {
54397
54484
  ...compact3,
54398
54485
  externalUrl: str(record.externalUrl),
@@ -54404,7 +54491,7 @@ function providerHitNormalizer(record, full) {
54404
54491
  score: num(record.score),
54405
54492
  deduped: typeof record.deduped === "boolean" ? record.deduped : void 0,
54406
54493
  alsoInLibrary: str(record.alsoInLibrary),
54407
- groupKey: str(group.key)
54494
+ groupKey: str(group2.key)
54408
54495
  };
54409
54496
  }
54410
54497
  function readOutputFlags(args5) {
@@ -55742,18 +55829,18 @@ function findColorRangeInternal(data, ignoredColor = null, customThreshold) {
55742
55829
  const color = { r: data[i], g: data[i + 1], b: data[i + 2] };
55743
55830
  if (ignoredColor && colorDistance(color, ignoredColor) < threshold) continue;
55744
55831
  let found = false;
55745
- for (const group of groups) {
55746
- const distance = colorDistance(group.color, color);
55747
- const contrastRatio = getContrastRatio(group.color, color);
55832
+ for (const group2 of groups) {
55833
+ const distance = colorDistance(group2.color, color);
55834
+ const contrastRatio = getContrastRatio(group2.color, color);
55748
55835
  let groupingThreshold = threshold;
55749
55836
  if (contrastRatio > 3) groupingThreshold = Math.min(groupingThreshold, 8);
55750
- const hueDistance = getHueDistance(group.color, color);
55837
+ const hueDistance = getHueDistance(group2.color, color);
55751
55838
  if (hueDistance > 20) groupingThreshold = Math.min(groupingThreshold, 5);
55752
- if (isHighlySaturated(color) && isHighlySaturated(group.color)) {
55839
+ if (isHighlySaturated(color) && isHighlySaturated(group2.color)) {
55753
55840
  groupingThreshold = Math.min(groupingThreshold, 10);
55754
55841
  }
55755
55842
  if (distance < groupingThreshold) {
55756
- group.count++;
55843
+ group2.count++;
55757
55844
  found = true;
55758
55845
  break;
55759
55846
  }
@@ -55858,13 +55945,13 @@ function hasGradient(data) {
55858
55945
  function perceivedLuminance(color) {
55859
55946
  return (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) / 255;
55860
55947
  }
55861
- function createSvgRecolorer(target2, palette) {
55862
- if (palette.length < 2) return () => target2;
55948
+ function createSvgRecolorer(target4, palette) {
55949
+ if (palette.length < 2) return () => target4;
55863
55950
  const luminances = palette.map(perceivedLuminance);
55864
55951
  const min = Math.min(...luminances);
55865
55952
  const max = Math.max(...luminances);
55866
- if (max - min < 0.05) return () => target2;
55867
- const [h, s, targetLightness] = rgbToHsl(target2.r, target2.g, target2.b);
55953
+ if (max - min < 0.05) return () => target4;
55954
+ const [h, s, targetLightness] = rgbToHsl(target4.r, target4.g, target4.b);
55868
55955
  const lightnessRange = 0.75;
55869
55956
  const minLightness = targetLightness >= 0.5 ? Math.max(0, targetLightness - lightnessRange) : targetLightness;
55870
55957
  const maxLightness = targetLightness >= 0.5 ? targetLightness : Math.min(1, targetLightness + lightnessRange);
@@ -56858,18 +56945,18 @@ var uploadCommand = defineCommand167({
56858
56945
  },
56859
56946
  run: async ({ args: args5 }) => {
56860
56947
  try {
56861
- const target2 = args5.file;
56862
- if (!target2) {
56948
+ const target4 = args5.file;
56949
+ if (!target4) {
56863
56950
  writeJson({
56864
56951
  ok: false,
56865
56952
  error: { code: "VALIDATION_ERROR", message: "File path or URL is required" }
56866
56953
  });
56867
56954
  process.exit(1);
56868
56955
  }
56869
- if (isRemoteUrl2(target2)) {
56870
- await uploadRemote(target2, args5);
56956
+ if (isRemoteUrl2(target4)) {
56957
+ await uploadRemote(target4, args5);
56871
56958
  } else {
56872
- await uploadLocal(target2, args5);
56959
+ await uploadLocal(target4, args5);
56873
56960
  }
56874
56961
  } catch (err) {
56875
56962
  if (err instanceof ApiError) {
@@ -56881,7 +56968,7 @@ var uploadCommand = defineCommand167({
56881
56968
  }
56882
56969
  }
56883
56970
  });
56884
- async function uploadRemote(target2, args5) {
56971
+ async function uploadRemote(target4, args5) {
56885
56972
  const source = args5.source ?? "uploaded";
56886
56973
  if (args5["dry-run"]) {
56887
56974
  writeJson({
@@ -56889,7 +56976,7 @@ async function uploadRemote(target2, args5) {
56889
56976
  dryRun: true,
56890
56977
  operation: "images.upload (remote)",
56891
56978
  params: {
56892
- url: target2,
56979
+ url: target4,
56893
56980
  source,
56894
56981
  externalId: args5["external-id"] ?? null,
56895
56982
  externalUrl: args5["external-url"] ?? null,
@@ -56898,22 +56985,22 @@ async function uploadRemote(target2, args5) {
56898
56985
  });
56899
56986
  return;
56900
56987
  }
56901
- const body = { url: target2, source };
56988
+ const body = { url: target4, source };
56902
56989
  if (args5["external-id"]) body.externalId = args5["external-id"];
56903
56990
  if (args5["external-url"]) body.externalUrl = args5["external-url"];
56904
56991
  if (args5.context) body.descriptionContext = args5.context;
56905
56992
  const data = await apiPost("/api/images/ingest", body);
56906
56993
  writeJson({ ok: true, data });
56907
56994
  }
56908
- async function uploadLocal(target2, args5) {
56909
- const contentType = args5["content-type"] || detectImageContentType(target2);
56995
+ async function uploadLocal(target4, args5) {
56996
+ const contentType = args5["content-type"] || detectImageContentType(target4);
56910
56997
  if (args5["dry-run"]) {
56911
56998
  writeJson({
56912
56999
  ok: true,
56913
57000
  dryRun: true,
56914
57001
  operation: "images.upload (local)",
56915
57002
  params: {
56916
- file: target2,
57003
+ file: target4,
56917
57004
  contentType,
56918
57005
  source: args5.source ?? null,
56919
57006
  descriptionContext: args5.context ?? null
@@ -56922,7 +57009,7 @@ async function uploadLocal(target2, args5) {
56922
57009
  return;
56923
57010
  }
56924
57011
  const data = await uploadLocalImage({
56925
- file: target2,
57012
+ file: target4,
56926
57013
  contentType,
56927
57014
  source: args5.source,
56928
57015
  descriptionContext: args5.context
@@ -59211,7 +59298,7 @@ function classifyPreflightStatus(status) {
59211
59298
  async function preflightProxyRoute(route, options = {}) {
59212
59299
  if (route.kind !== "proxy") return { usable: true };
59213
59300
  const timeoutMs = options.timeoutMs ?? 8e3;
59214
- const target2 = options.target ?? "example.com:443";
59301
+ const target4 = options.target ?? "example.com:443";
59215
59302
  const endpoint = new URL(route.server);
59216
59303
  const auth = Buffer.from(`${route.username}:${route.password}`).toString("base64");
59217
59304
  return await new Promise((resolve5) => {
@@ -59223,8 +59310,8 @@ async function preflightProxyRoute(route, options = {}) {
59223
59310
  host: endpoint.hostname,
59224
59311
  port: Number(endpoint.port),
59225
59312
  method: "CONNECT",
59226
- path: target2,
59227
- headers: { "Proxy-Authorization": `Basic ${auth}`, Host: target2 },
59313
+ path: target4,
59314
+ headers: { "Proxy-Authorization": `Basic ${auth}`, Host: target4 },
59228
59315
  timeout: timeoutMs
59229
59316
  });
59230
59317
  request.on("connect", (response, socket) => {
@@ -60150,9 +60237,9 @@ function bandsCollide(a, b, margin, maxGap) {
60150
60237
  const gap = Math.min(Math.abs(shrunkA.bottom - shrunkB.top), Math.abs(shrunkB.bottom - shrunkA.top));
60151
60238
  return gap <= maxGap;
60152
60239
  }
60153
- function shouldPromoteToParent(group) {
60154
- const isRunt = group.childCount < 3 || group.height < 80;
60155
- return isRunt && group.parentHeight < 1600;
60240
+ function shouldPromoteToParent(group2) {
60241
+ const isRunt = group2.childCount < 3 || group2.height < 80;
60242
+ return isRunt && group2.parentHeight < 1600;
60156
60243
  }
60157
60244
 
60158
60245
  // src/engine/landing-library/segment.ts
@@ -60371,35 +60458,35 @@ var inPageSegment = (options) => {
60371
60458
  };
60372
60459
  const clusterOnce = (groups2) => {
60373
60460
  const buckets = [];
60374
- for (const group of groups2) {
60375
- if (group.sealed) {
60376
- buckets.push([group]);
60461
+ for (const group2 of groups2) {
60462
+ if (group2.sealed) {
60463
+ buckets.push([group2]);
60377
60464
  continue;
60378
60465
  }
60379
- const target2 = buckets.find(
60466
+ const target4 = buckets.find(
60380
60467
  (bucket) => bucket.some(
60381
60468
  (member) => !member.sealed && geom.bandsCollide(
60382
60469
  { top: member.top, bottom: member.bottom },
60383
- { top: group.top, bottom: group.bottom },
60470
+ { top: group2.top, bottom: group2.bottom },
60384
60471
  options.collisionMargin,
60385
60472
  gapThreshold
60386
60473
  )
60387
60474
  )
60388
60475
  );
60389
- if (target2) target2.push(group);
60390
- else buckets.push([group]);
60476
+ if (target4) target4.push(group2);
60477
+ else buckets.push([group2]);
60391
60478
  }
60392
60479
  if (buckets.length === groups2.length) return groups2;
60393
60480
  return buckets.flatMap((bucket) => bucket.length === 1 ? [bucket[0]] : mergeBucket(bucket));
60394
60481
  };
60395
- let groups = leaves.map((leaf) => ({
60396
- root: leaf.element,
60397
- top: leaf.top,
60398
- bottom: leaf.bottom,
60399
- left: leaf.left,
60400
- right: leaf.right,
60401
- height: leaf.height,
60402
- leaves: [leaf],
60482
+ let groups = leaves.map((leaf2) => ({
60483
+ root: leaf2.element,
60484
+ top: leaf2.top,
60485
+ bottom: leaf2.bottom,
60486
+ left: leaf2.left,
60487
+ right: leaf2.right,
60488
+ height: leaf2.height,
60489
+ leaves: [leaf2],
60403
60490
  sealed: false
60404
60491
  }));
60405
60492
  for (let pass = 0; pass < 40; pass++) {
@@ -60409,21 +60496,21 @@ var inPageSegment = (options) => {
60409
60496
  }
60410
60497
  for (let pass = 0; pass < 10; pass++) {
60411
60498
  let promoted = false;
60412
- groups = groups.map((group) => {
60413
- const parent = group.root.parentElement;
60414
- if (!parent || parent === document.documentElement || parent === document.body) return group;
60499
+ groups = groups.map((group2) => {
60500
+ const parent = group2.root.parentElement;
60501
+ if (!parent || parent === document.documentElement || parent === document.body) return group2;
60415
60502
  const parentBox = boxOf(parent);
60416
- if (!parentBox) return group;
60503
+ if (!parentBox) return group2;
60417
60504
  if (!geom.shouldPromoteToParent({
60418
- childCount: group.leaves.length,
60419
- height: group.height,
60505
+ childCount: group2.leaves.length,
60506
+ height: group2.height,
60420
60507
  parentHeight: parentBox.height
60421
60508
  })) {
60422
- return group;
60509
+ return group2;
60423
60510
  }
60424
60511
  promoted = true;
60425
60512
  return {
60426
- ...group,
60513
+ ...group2,
60427
60514
  root: parent,
60428
60515
  top: parentBox.top,
60429
60516
  bottom: parentBox.bottom,
@@ -60439,23 +60526,23 @@ var inPageSegment = (options) => {
60439
60526
  const containsBox = (outer, inner) => outer.top - 4 <= inner.top && outer.bottom + 4 >= inner.bottom && outer.left - 4 <= inner.left && outer.right + 4 >= inner.right;
60440
60527
  const deduped = [];
60441
60528
  const area = (g) => (g.right - g.left) * g.height;
60442
- for (const group of groups.slice().sort((a, b) => area(b) - area(a) || b.leaves.length - a.leaves.length)) {
60529
+ for (const group2 of groups.slice().sort((a, b) => area(b) - area(a) || b.leaves.length - a.leaves.length)) {
60443
60530
  const duplicate = deduped.some(
60444
- (kept) => kept.root === group.root || kept.root.contains(group.root) || sameBox(kept, group) || containsBox(kept, group)
60531
+ (kept) => kept.root === group2.root || kept.root.contains(group2.root) || sameBox(kept, group2) || containsBox(kept, group2)
60445
60532
  );
60446
- if (!duplicate) deduped.push(group);
60533
+ if (!duplicate) deduped.push(group2);
60447
60534
  }
60448
- return deduped.filter((group) => group.height > 0 && group.right - group.left > 0).sort((a, b) => a.top - b.top).map((group, index) => ({
60535
+ return deduped.filter((group2) => group2.height > 0 && group2.right - group2.left > 0).sort((a, b) => a.top - b.top).map((group2, index) => ({
60449
60536
  index,
60450
- selector: selectorFor(group.root),
60537
+ selector: selectorFor(group2.root),
60451
60538
  rect: {
60452
- top: group.top,
60453
- left: group.left,
60454
- width: group.right - group.left,
60455
- height: group.height
60539
+ top: group2.top,
60540
+ left: group2.left,
60541
+ width: group2.right - group2.left,
60542
+ height: group2.height
60456
60543
  },
60457
- leafCount: group.leaves.length,
60458
- textPreview: visibleText(group.root).slice(0, 200)
60544
+ leafCount: group2.leaves.length,
60545
+ textPreview: visibleText(group2.root).slice(0, 200)
60459
60546
  }));
60460
60547
  };
60461
60548
 
@@ -60482,8 +60569,8 @@ async function captureMotionTake(browser, url, selector, timeoutMs = 45e3) {
60482
60569
  await settleNavigation(page);
60483
60570
  await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => void 0);
60484
60571
  await settleConsent(page);
60485
- const target2 = page.locator(selector).first();
60486
- if (!await target2.count().catch(() => 0)) return null;
60572
+ const target4 = page.locator(selector).first();
60573
+ if (!await target4.count().catch(() => 0)) return null;
60487
60574
  await page.evaluate((sectionSelector) => {
60488
60575
  const element = document.querySelector(sectionSelector);
60489
60576
  if (!element) return;
@@ -60537,12 +60624,12 @@ async function aimAtSection(page, selector) {
60537
60624
  }
60538
60625
  async function hideFloatingFurniture(page, selector) {
60539
60626
  await page.evaluate((sectionSelector) => {
60540
- const target2 = document.querySelector(sectionSelector);
60541
- if (!target2) return;
60627
+ const target4 = document.querySelector(sectionSelector);
60628
+ if (!target4) return;
60542
60629
  for (const element of Array.from(document.querySelectorAll("*"))) {
60543
60630
  const position = getComputedStyle(element).position;
60544
60631
  if (position !== "fixed" && position !== "sticky") continue;
60545
- if (element.contains(target2) || target2.contains(element) || element === target2) continue;
60632
+ if (element.contains(target4) || target4.contains(element) || element === target4) continue;
60546
60633
  element.style.setProperty("visibility", "hidden", "important");
60547
60634
  }
60548
60635
  }, selector);
@@ -60599,9 +60686,9 @@ async function renderBundleToPng(browser, html, viewportWidth, options = {}) {
60599
60686
  await page.waitForTimeout(300);
60600
60687
  if (!wholePage) {
60601
60688
  for (const selector of [`[${SECTION_ROOT_ATTRIBUTE}]`, "body > *"]) {
60602
- const target2 = page.locator(selector).first();
60603
- if (!await target2.count()) continue;
60604
- const shot = await target2.screenshot({ type: "png", timeout: timeoutMs }).catch(() => null);
60689
+ const target4 = page.locator(selector).first();
60690
+ if (!await target4.count()) continue;
60691
+ const shot = await target4.screenshot({ type: "png", timeout: timeoutMs }).catch(() => null);
60605
60692
  if (shot) return shot;
60606
60693
  }
60607
60694
  }
@@ -61077,8 +61164,8 @@ async function scrapeLanding(options) {
61077
61164
  import { existsSync as existsSync9 } from "fs";
61078
61165
  import path41 from "path";
61079
61166
  var SCRATCH_DIR = ".baker";
61080
- function isWithin(parent, target2) {
61081
- const relative = path41.relative(parent, target2);
61167
+ function isWithin(parent, target4) {
61168
+ const relative = path41.relative(parent, target4);
61082
61169
  return relative === "" || !relative.startsWith("..") && !path41.isAbsolute(relative);
61083
61170
  }
61084
61171
  function findRepoRoot(from) {
@@ -61369,8 +61456,8 @@ registerSchema({
61369
61456
  function buildSearchBody(args5) {
61370
61457
  const body = {};
61371
61458
  const setList2 = (key, value) => {
61372
- const list = splitList2(value);
61373
- if (list) Object.assign(body, { [key]: list });
61459
+ const list3 = splitList2(value);
61460
+ if (list3) Object.assign(body, { [key]: list3 });
61374
61461
  };
61375
61462
  if (args5.query) body.query = String(args5.query);
61376
61463
  if (args5["similar-to"]) body.similarToSectionId = String(args5["similar-to"]);
@@ -62145,9 +62232,9 @@ function customEntry(server) {
62145
62232
  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.";
62146
62233
  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.";
62147
62234
  function parseHeaders(raw, rawArgs) {
62148
- const list = repeatedValues(rawArgs, "header", raw);
62235
+ const list3 = repeatedValues(rawArgs, "header", raw);
62149
62236
  const headers = {};
62150
- for (const entry of list) {
62237
+ for (const entry of list3) {
62151
62238
  const text2 = String(entry);
62152
62239
  const sep = text2.indexOf(":") >= 0 && (text2.indexOf(":") < text2.indexOf("=") || text2.indexOf("=") < 0) ? ":" : "=";
62153
62240
  const at = text2.indexOf(sep);
@@ -63316,7 +63403,7 @@ Examples:
63316
63403
  "no-cache": { type: "boolean", description: "Skip server cache, hit API directly", required: false }
63317
63404
  },
63318
63405
  run: async ({ args: args5 }) => {
63319
- const target2 = args5.target;
63406
+ const target4 = args5.target;
63320
63407
  const location2 = args5.location || void 0;
63321
63408
  const language = args5.language || void 0;
63322
63409
  const sort = args5.sort || void 0;
@@ -63326,7 +63413,7 @@ Examples:
63326
63413
  const queryContext = buildResearchQueryContext(location2, language);
63327
63414
  try {
63328
63415
  const data = await apiPost("/api/research/keywords-for-site", {
63329
- target: target2,
63416
+ target: target4,
63330
63417
  location: location2,
63331
63418
  language,
63332
63419
  sort,
@@ -63499,7 +63586,7 @@ Examples:
63499
63586
  "no-cache": { type: "boolean", description: "Skip server cache, hit API directly", required: false }
63500
63587
  },
63501
63588
  run: async ({ args: args5 }) => {
63502
- const target2 = args5.target;
63589
+ const target4 = args5.target;
63503
63590
  const location2 = args5.location || void 0;
63504
63591
  const language = args5.language || void 0;
63505
63592
  const limit2 = args5.limit ? Number(args5.limit) : void 0;
@@ -63507,7 +63594,7 @@ Examples:
63507
63594
  const queryContext = buildResearchQueryContext(location2, language);
63508
63595
  try {
63509
63596
  const data = await apiPost("/api/research/relevant-pages", {
63510
- target: target2,
63597
+ target: target4,
63511
63598
  location: location2,
63512
63599
  language,
63513
63600
  limit: limit2,
@@ -63854,7 +63941,7 @@ function refuseMisusedKinds(kind, args5) {
63854
63941
  }
63855
63942
  }
63856
63943
  function refuseStrayFlags(kind, args5) {
63857
- const allowed = new Set(TRIGGER_TARGETS[kind].map((target2) => TRIGGER_TARGET_FIELDS[target2.field].flag));
63944
+ const allowed = new Set(TRIGGER_TARGETS[kind].map((target4) => TRIGGER_TARGET_FIELDS[target4.field].flag));
63858
63945
  const stray = TRIGGER_TARGET_FIELD_IDS.map((field) => TRIGGER_TARGET_FIELDS[field].flag).filter(
63859
63946
  (flag3) => !allowed.has(flag3) && flagText(args5, flag3) !== void 0 && !(kind === "experiment.started" && flag3 === "experiment")
63860
63947
  );
@@ -64090,13 +64177,13 @@ var createCommand3 = defineCommand197({
64090
64177
  try {
64091
64178
  refuseAutoPublish(args5);
64092
64179
  const name = args5.name;
64093
- const description = args5.description;
64180
+ const description2 = args5.description;
64094
64181
  const prompt = args5.prompt;
64095
64182
  const template = typeof args5.template === "string" ? args5.template.trim() : "";
64096
64183
  if (template.length === 0 && (!name || name.trim().length === 0)) {
64097
64184
  failValidation4("--name is required. Or pass --template to use a recipe's name.");
64098
64185
  }
64099
- if (template.length === 0 && (!description || description.trim().length === 0)) {
64186
+ if (template.length === 0 && (!description2 || description2.trim().length === 0)) {
64100
64187
  failValidation4("--description is required. Or pass --template to use a recipe's line.");
64101
64188
  }
64102
64189
  if (template.length === 0 && (!prompt || prompt.trim().length === 0)) {
@@ -64107,7 +64194,7 @@ var createCommand3 = defineCommand197({
64107
64194
  const body = {
64108
64195
  chatId: requireChatId(),
64109
64196
  ...name?.trim() ? { name } : {},
64110
- ...description?.trim() ? { description } : {},
64197
+ ...description2?.trim() ? { description: description2 } : {},
64111
64198
  enabled: !args5.disabled,
64112
64199
  ...trigger,
64113
64200
  ...prompt ? { prompt } : {},
@@ -64456,8 +64543,8 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
64456
64543
  try {
64457
64544
  const save = flag3("save");
64458
64545
  const publish = flag3("publish");
64459
- const remove = flag3("delete");
64460
- if ([save, publish, remove].filter((id) => id.length > 0).length > 1) {
64546
+ const remove2 = flag3("delete");
64547
+ if ([save, publish, remove2].filter((id) => id.length > 0).length > 1) {
64461
64548
  failValidation4("Pass one of --save, --publish or --delete at a time.");
64462
64549
  }
64463
64550
  if (save.length > 0) {
@@ -64486,10 +64573,10 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
64486
64573
  writeJson(response2);
64487
64574
  return;
64488
64575
  }
64489
- if (remove.length > 0) {
64576
+ if (remove2.length > 0) {
64490
64577
  const response2 = await apiPost("/api/marketplace/remove", {
64491
64578
  chatId: requireChatId(),
64492
- slug: remove
64579
+ slug: remove2
64493
64580
  });
64494
64581
  writeJson(response2);
64495
64582
  return;
@@ -64787,7 +64874,7 @@ var schemaCommand2 = defineCommand208({
64787
64874
  });
64788
64875
 
64789
64876
  // src/commands/shopify/index.ts
64790
- import { defineCommand as defineCommand211 } from "citty";
64877
+ import { defineCommand as defineCommand216 } from "citty";
64791
64878
 
64792
64879
  // src/commands/shopify/buyers.ts
64793
64880
  import { defineCommand as defineCommand209 } from "citty";
@@ -65074,7 +65161,18 @@ var shopifyStoreSummarySchema = z50.object({
65074
65161
  });
65075
65162
  var shopifyStoreResponseSchema = z50.object({
65076
65163
  store: shopifyStoreStateSchema,
65077
- summary: shopifyStoreSummarySchema
65164
+ summary: shopifyStoreSummarySchema,
65165
+ /**
65166
+ * Which store changes Baker may make (S9), by what the store allowed: false
65167
+ * means `baker shopify change …` will ask the user to allow it first.
65168
+ */
65169
+ canChange: z50.object({
65170
+ products: z50.boolean(),
65171
+ publishing: z50.boolean(),
65172
+ discounts: z50.boolean(),
65173
+ redirects: z50.boolean(),
65174
+ marketing: z50.boolean()
65175
+ }).optional()
65078
65176
  });
65079
65177
  var shopifyProductRowSchema = z50.object({
65080
65178
  productId: z50.string(),
@@ -65112,7 +65210,7 @@ var shopifyVariantSchema = z50.object({
65112
65210
  compareAtPrice: shopifyPriceSchema.optional(),
65113
65211
  stock: shopifyOptionStockSchema,
65114
65212
  quantity: z50.number().optional(),
65115
- /** The link for the page's buy button: never build a Shopify link by hand. Absent when the option can't be bought. */
65213
+ /** 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. */
65116
65214
  buyUrl: z50.string().optional(),
65117
65215
  buyUrlKind: shopifyBuyDestinationSchema.optional(),
65118
65216
  /** Why there is no buy link. */
@@ -65227,7 +65325,7 @@ var shopifyProductDetailSchema = z50.object({
65227
65325
  handle: z50.string(),
65228
65326
  /** The product's page in Baker — the link to share with the user. */
65229
65327
  url: z50.string(),
65230
- /** The product's page on the online store: to look at, never a buy button's link (that is each option's `buyUrl`). */
65328
+ /** 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). */
65231
65329
  storeUrl: z50.string().optional(),
65232
65330
  availability: shopifyAvailabilitySchema.optional(),
65233
65331
  status: shopifyProductStatusSchema.optional(),
@@ -65791,6 +65889,359 @@ var shopifyCohortsResponseSchema = z51.object({
65791
65889
  cohorts: z51.array(shopifyCohortSchema)
65792
65890
  });
65793
65891
 
65892
+ // ../api/src/shopify/writes.ts
65893
+ import { z as z52 } from "zod";
65894
+ var SHOPIFY_TEMP_REF_PREFIX = "shopify_temp_";
65895
+ var SHOPIFY_PERMISSION_NEEDED_PREFIX = "Baker isn't allowed to change this Shopify store yet";
65896
+ var shopifyWriteEntitySchema = z52.enum([
65897
+ "product",
65898
+ "variant",
65899
+ "metafield",
65900
+ "collection",
65901
+ "discountCode",
65902
+ "urlRedirect",
65903
+ "marketingActivity",
65904
+ "webPixel",
65905
+ "signupOffer"
65906
+ ]);
65907
+ var shopifyDraftOpKindSchema = z52.enum([
65908
+ "shopify.product.update",
65909
+ "shopify.variant.update",
65910
+ "shopify.metafield.set",
65911
+ "shopify.metafield.delete",
65912
+ "shopify.collection.create",
65913
+ "shopify.collection.publish",
65914
+ "shopify.collection.unpublish",
65915
+ "shopify.collection.update",
65916
+ "shopify.collection.delete",
65917
+ "shopify.collection.addProducts",
65918
+ "shopify.collection.removeProducts",
65919
+ "shopify.discountCode.create",
65920
+ "shopify.discountCode.deactivate",
65921
+ "shopify.discountCode.schedule",
65922
+ "shopify.urlRedirect.create",
65923
+ "shopify.urlRedirect.delete",
65924
+ "shopify.marketingActivity.upsert",
65925
+ "shopify.marketingActivity.delete",
65926
+ "shopify.webPixel.update",
65927
+ "shopify.signupOffer.set",
65928
+ "shopify.signupOffer.end"
65929
+ ]);
65930
+ var shopifyDecimalSchema = z52.string().regex(/^\d{1,12}(\.\d{1,4})?$/, 'write amounts as a decimal string, e.g. "19.95"');
65931
+ var tagSchema = z52.string().trim().min(1).max(255);
65932
+ var seoSchema = z52.object({ title: z52.string().max(255).optional(), description: z52.string().max(320).optional() }).strict();
65933
+ var SHOPIFY_DESCRIPTION_MAX_CHARS = 2e5;
65934
+ var descriptionHtmlSchema = z52.string().max(SHOPIFY_DESCRIPTION_MAX_CHARS);
65935
+ var productUpdateSchema = z52.object({
65936
+ title: z52.string().trim().min(1).max(255).optional(),
65937
+ descriptionHtml: descriptionHtmlSchema.optional(),
65938
+ seo: seoSchema.optional(),
65939
+ /** The WHOLE tag list after the change (Shopify replaces it). `addTags`/`removeTags` are resolved against the live tags. */
65940
+ tags: z52.array(tagSchema).max(250).optional(),
65941
+ addTags: z52.array(tagSchema).max(250).optional(),
65942
+ removeTags: z52.array(tagSchema).max(250).optional(),
65943
+ productType: z52.string().max(255).optional()
65944
+ }).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change").refine(
65945
+ (payload) => payload.tags === void 0 || payload.addTags === void 0 && payload.removeTags === void 0,
65946
+ "give either the whole `tags` list or `addTags`/`removeTags`, not both"
65947
+ );
65948
+ var shopifyWeightUnitSchema = z52.enum(["GRAMS", "KILOGRAMS", "OUNCES", "POUNDS"]);
65949
+ var variantUpdateSchema = z52.object({
65950
+ barcode: z52.string().trim().max(255).optional(),
65951
+ sku: z52.string().trim().max(255).optional(),
65952
+ weight: z52.object({ value: z52.number().nonnegative(), unit: shopifyWeightUnitSchema }).strict().optional(),
65953
+ price: shopifyDecimalSchema.optional(),
65954
+ /** `null` clears the sale ("compare at") price. */
65955
+ compareAtPrice: shopifyDecimalSchema.nullable().optional()
65956
+ }).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change");
65957
+ var metafieldKeySchema = z52.string().min(2).max(64).regex(/^[A-Za-z0-9_-]+$/, "letters, digits, `_` and `-` only");
65958
+ var metafieldNamespaceSchema = z52.string().min(2).max(255).regex(/^[A-Za-z0-9_-]+$/, "letters, digits, `_` and `-` only");
65959
+ var metafieldSetSchema = z52.object({
65960
+ namespace: metafieldNamespaceSchema,
65961
+ key: metafieldKeySchema,
65962
+ /** Shopify's metafield type, e.g. `single_line_text_field`, `multi_line_text_field`, `rich_text_field`, `number_integer`, `boolean`, `json`. */
65963
+ type: z52.string().min(1).max(100),
65964
+ value: z52.string().max(1e5)
65965
+ }).strict();
65966
+ var metafieldDeleteSchema = z52.object({ namespace: metafieldNamespaceSchema, key: metafieldKeySchema }).strict();
65967
+ var shopifyCollectionSortOrderSchema = z52.enum([
65968
+ "MANUAL",
65969
+ "BEST_SELLING",
65970
+ "ALPHA_ASC",
65971
+ "ALPHA_DESC",
65972
+ "PRICE_DESC",
65973
+ "PRICE_ASC",
65974
+ "CREATED",
65975
+ "CREATED_DESC"
65976
+ ]);
65977
+ 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");
65978
+ var SHOPIFY_COLLECTION_PRODUCTS_MAX = 250;
65979
+ var collectionCreateSchema = z52.object({
65980
+ title: z52.string().trim().min(1).max(255),
65981
+ handle: handleSchema.optional(),
65982
+ descriptionHtml: descriptionHtmlSchema.optional(),
65983
+ seo: seoSchema.optional(),
65984
+ sortOrder: shopifyCollectionSortOrderSchema.optional(),
65985
+ /** The theme template the collection page uses (the part after `collection.`). Kept when a deleted collection is put back. */
65986
+ templateSuffix: z52.string().max(255).optional(),
65987
+ /** Product ids, handles or links, in the order shown with sort order MANUAL. */
65988
+ products: z52.array(z52.string().min(1)).max(SHOPIFY_COLLECTION_PRODUCTS_MAX).optional(),
65989
+ /** Show it on the online store (a second change, staged with it). Default true. */
65990
+ onlineStore: z52.boolean().optional()
65991
+ }).strict();
65992
+ var collectionUpdateSchema = z52.object({
65993
+ title: z52.string().trim().min(1).max(255).optional(),
65994
+ descriptionHtml: descriptionHtmlSchema.optional(),
65995
+ seo: seoSchema.optional(),
65996
+ sortOrder: shopifyCollectionSortOrderSchema.optional()
65997
+ }).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change");
65998
+ var collectionProductsSchema = z52.object({ products: z52.array(z52.string().min(1)).min(1).max(SHOPIFY_COLLECTION_PRODUCTS_MAX) }).strict();
65999
+ var isoDateTimeSchema = z52.iso.datetime({ offset: true });
66000
+ var discountCodeSchema = z52.string().regex(/^[A-Za-z0-9_-]{3,64}$/, "3\u201364 letters, digits, `_` or `-`, e.g. SPRING10");
66001
+ var discountValueSchema = z52.union([
66002
+ z52.object({ percentage: z52.number().min(1).max(100) }).strict(),
66003
+ z52.object({ amount: shopifyDecimalSchema }).strict()
66004
+ ]);
66005
+ var discountAppliesToSchema = z52.union([
66006
+ z52.object({ all: z52.literal(true) }).strict(),
66007
+ z52.object({ products: z52.array(z52.string().min(1)).min(1).max(100) }).strict(),
66008
+ z52.object({ collections: z52.array(z52.string().min(1)).min(1).max(100) }).strict()
66009
+ ]);
66010
+ var discountCreateSchema = z52.object({
66011
+ code: discountCodeSchema,
66012
+ /** What the store's staff see; defaults to the code. */
66013
+ title: z52.string().trim().min(1).max(255).optional(),
66014
+ value: discountValueSchema,
66015
+ appliesTo: discountAppliesToSchema,
66016
+ minimumSubtotal: shopifyDecimalSchema.optional(),
66017
+ minimumQuantity: z52.number().int().min(1).max(1e4).optional(),
66018
+ /** Default: the moment it is published. */
66019
+ startsAt: isoDateTimeSchema.optional(),
66020
+ endsAt: isoDateTimeSchema.optional(),
66021
+ usageLimit: z52.number().int().min(1).max(1e6).optional(),
66022
+ /** Default true. */
66023
+ oncePerCustomer: z52.boolean().optional(),
66024
+ combinesWith: z52.object({
66025
+ productDiscounts: z52.boolean().optional(),
66026
+ orderDiscounts: z52.boolean().optional(),
66027
+ shippingDiscounts: z52.boolean().optional()
66028
+ }).strict().optional()
66029
+ }).strict().refine(
66030
+ (payload) => payload.minimumSubtotal === void 0 || payload.minimumQuantity === void 0,
66031
+ "a discount takes a minimum amount or a minimum quantity, not both"
66032
+ );
66033
+ var discountScheduleSchema = z52.object({ startsAt: isoDateTimeSchema, endsAt: isoDateTimeSchema.nullable() }).strict();
66034
+ var storePathSchema = z52.string().min(2).max(1024).regex(/^\/[^\s]*$/, 'a path on the store starting with "/", e.g. /collections/old');
66035
+ var urlRedirectCreateSchema = z52.object({
66036
+ from: storePathSchema,
66037
+ to: z52.union([storePathSchema, z52.url({ protocol: /^https$/ })])
66038
+ }).strict();
66039
+ var shopifyMarketingPlatformSchema = z52.enum(["google-ads", "meta"]);
66040
+ var marketingUpsertSchema = z52.object({
66041
+ platform: shopifyMarketingPlatformSchema,
66042
+ accountId: z52.string().min(1).max(64),
66043
+ campaignId: z52.string().regex(/^\d+$/, "the campaign's numeric id"),
66044
+ utm: z52.object({
66045
+ campaign: z52.string().trim().min(1).max(255),
66046
+ source: z52.string().trim().min(1).max(255),
66047
+ medium: z52.string().trim().min(1).max(255)
66048
+ }).strict()
66049
+ }).strict();
66050
+ var signupOfferHandleSchema = z52.string().regex(/^[a-z0-9][a-z0-9-]{1,39}$/, "2\u201340 lowercase letters, digits and dashes, e.g. welcome-10");
66051
+ var signupOfferSetSchema = z52.object({
66052
+ /** What the store's staff see in front of each code, and the Store page's row. */
66053
+ name: z52.string().trim().min(1).max(80),
66054
+ value: discountValueSchema,
66055
+ appliesTo: discountAppliesToSchema,
66056
+ minimumSubtotal: shopifyDecimalSchema.optional(),
66057
+ /** Each code's life, from the moment it is given. */
66058
+ validForDays: z52.number().int().min(1).max(365),
66059
+ /** The offer stops giving codes after this; no code outlives it. */
66060
+ endsAt: isoDateTimeSchema.optional(),
66061
+ /** How many codes the offer gives in all. */
66062
+ maxCodes: z52.number().int().min(1).max(1e5).optional(),
66063
+ /** The start of every code (WELCOME-7Q2M9XKD); default: the handle in capitals. */
66064
+ codePrefix: z52.string().regex(/^[A-Z0-9]{2,12}$/, "2\u201312 capital letters or digits, e.g. WELCOME").optional(),
66065
+ combinesWith: z52.object({
66066
+ productDiscounts: z52.boolean().optional(),
66067
+ orderDiscounts: z52.boolean().optional(),
66068
+ shippingDiscounts: z52.boolean().optional()
66069
+ }).strict().optional()
66070
+ }).strict();
66071
+ var emptySchema = z52.object({}).strict();
66072
+ var opFields = {
66073
+ /** Optional caller-chosen ref for a create, so later ops in the batch can name it (`shopify_temp_…`). */
66074
+ ref: z52.string().startsWith(SHOPIFY_TEMP_REF_PREFIX).max(80).optional()
66075
+ };
66076
+ var target2 = z52.string().trim().min(1).max(2048);
66077
+ var shopifyDraftOpInputSchema = z52.discriminatedUnion("kind", [
66078
+ z52.object({ kind: z52.literal("shopify.product.update"), target: target2, payload: productUpdateSchema, ...opFields }),
66079
+ z52.object({ kind: z52.literal("shopify.variant.update"), target: target2, payload: variantUpdateSchema, ...opFields }),
66080
+ z52.object({ kind: z52.literal("shopify.metafield.set"), target: target2, payload: metafieldSetSchema, ...opFields }),
66081
+ z52.object({ kind: z52.literal("shopify.metafield.delete"), target: target2, payload: metafieldDeleteSchema, ...opFields }),
66082
+ z52.object({ kind: z52.literal("shopify.collection.create"), payload: collectionCreateSchema, ...opFields }),
66083
+ z52.object({ kind: z52.literal("shopify.collection.publish"), target: target2, payload: emptySchema.optional(), ...opFields }),
66084
+ z52.object({ kind: z52.literal("shopify.collection.unpublish"), target: target2, payload: emptySchema.optional(), ...opFields }),
66085
+ z52.object({ kind: z52.literal("shopify.collection.update"), target: target2, payload: collectionUpdateSchema, ...opFields }),
66086
+ z52.object({ kind: z52.literal("shopify.collection.delete"), target: target2, payload: emptySchema.optional(), ...opFields }),
66087
+ z52.object({
66088
+ kind: z52.literal("shopify.collection.addProducts"),
66089
+ target: target2,
66090
+ payload: collectionProductsSchema,
66091
+ ...opFields
66092
+ }),
66093
+ z52.object({
66094
+ kind: z52.literal("shopify.collection.removeProducts"),
66095
+ target: target2,
66096
+ payload: collectionProductsSchema,
66097
+ ...opFields
66098
+ }),
66099
+ z52.object({ kind: z52.literal("shopify.discountCode.create"), payload: discountCreateSchema, ...opFields }),
66100
+ z52.object({
66101
+ kind: z52.literal("shopify.discountCode.deactivate"),
66102
+ target: target2,
66103
+ payload: emptySchema.optional(),
66104
+ ...opFields
66105
+ }),
66106
+ z52.object({ kind: z52.literal("shopify.discountCode.schedule"), target: target2, payload: discountScheduleSchema, ...opFields }),
66107
+ z52.object({ kind: z52.literal("shopify.urlRedirect.create"), payload: urlRedirectCreateSchema, ...opFields }),
66108
+ z52.object({ kind: z52.literal("shopify.urlRedirect.delete"), target: target2, payload: emptySchema.optional(), ...opFields }),
66109
+ z52.object({ kind: z52.literal("shopify.marketingActivity.upsert"), payload: marketingUpsertSchema, ...opFields }),
66110
+ z52.object({
66111
+ kind: z52.literal("shopify.marketingActivity.delete"),
66112
+ target: target2,
66113
+ payload: emptySchema.optional(),
66114
+ ...opFields
66115
+ }),
66116
+ z52.object({ kind: z52.literal("shopify.webPixel.update"), payload: emptySchema.optional(), ...opFields }),
66117
+ z52.object({
66118
+ kind: z52.literal("shopify.signupOffer.set"),
66119
+ target: signupOfferHandleSchema,
66120
+ payload: signupOfferSetSchema,
66121
+ ...opFields
66122
+ }),
66123
+ z52.object({
66124
+ kind: z52.literal("shopify.signupOffer.end"),
66125
+ target: signupOfferHandleSchema,
66126
+ payload: emptySchema.optional(),
66127
+ ...opFields
66128
+ })
66129
+ ]);
66130
+ var PAYLOAD_KEYS = new Map(
66131
+ shopifyDraftOpInputSchema.options.map((option) => {
66132
+ const payload = option.shape.payload;
66133
+ const object = payload instanceof z52.ZodOptional ? payload.unwrap() : payload;
66134
+ return [option.shape.kind.value, object instanceof z52.ZodObject ? Object.keys(object.shape) : []];
66135
+ })
66136
+ );
66137
+ var askUserQuestionInputSchema = z52.object({
66138
+ questions: z52.array(
66139
+ z52.object({
66140
+ question: z52.string(),
66141
+ header: z52.string(),
66142
+ multiSelect: z52.boolean(),
66143
+ options: z52.array(z52.object({ label: z52.string(), description: z52.string() })).min(2)
66144
+ })
66145
+ ).min(1)
66146
+ });
66147
+ var shopifyNeedsApprovalSchema = z52.object({
66148
+ /** Pass to AskUserQuestion exactly as given. */
66149
+ askUserQuestion: askUserQuestionInputSchema,
66150
+ /** The user already said no to (at least one of) these changes: don't ask again in this turn. */
66151
+ declined: z52.boolean()
66152
+ });
66153
+ var shopifyVerificationSchema = z52.object({
66154
+ status: z52.enum(["confirmed", "drifted", "unconfirmed"]),
66155
+ checkedAt: z52.number(),
66156
+ note: z52.string().optional()
66157
+ });
66158
+ var shopifyDraftOpResultSchema = z52.object({
66159
+ status: z52.enum(["applied", "failed", "skipped"]),
66160
+ createdId: z52.string().optional(),
66161
+ error: z52.string().optional(),
66162
+ note: z52.string().optional(),
66163
+ skippedBecause: z52.string().optional(),
66164
+ executedAt: z52.number().optional(),
66165
+ pendingJob: z52.string().optional(),
66166
+ verification: shopifyVerificationSchema.optional()
66167
+ });
66168
+ var stagedOpSchema = z52.object({
66169
+ ref: z52.string(),
66170
+ kind: shopifyDraftOpKindSchema,
66171
+ summary: z52.string(),
66172
+ warnings: z52.array(z52.string())
66173
+ });
66174
+ var shopifyDraftStageResponseSchema = z52.union([
66175
+ z52.object({
66176
+ staged: z52.literal(true),
66177
+ count: z52.number(),
66178
+ ops: z52.array(stagedOpSchema),
66179
+ amended: z52.boolean().optional()
66180
+ }),
66181
+ z52.object({ staged: z52.literal(false), needsApproval: shopifyNeedsApprovalSchema })
66182
+ ]);
66183
+ var SHOPIFY_DRAFT_BATCH_MAX = 100;
66184
+ var shopifyDraftStageRequestSchema = z52.object({ chatId: z52.string(), op: shopifyDraftOpInputSchema });
66185
+ var shopifyDraftStageBatchRequestSchema = z52.object({
66186
+ chatId: z52.string(),
66187
+ ops: z52.array(shopifyDraftOpInputSchema).min(1).max(SHOPIFY_DRAFT_BATCH_MAX)
66188
+ });
66189
+ var shopifyDraftAmendRequestSchema = z52.object({
66190
+ chatId: z52.string(),
66191
+ ref: z52.string(),
66192
+ patch: z52.record(z52.string(), z52.unknown())
66193
+ });
66194
+ var shopifyDraftOpViewSchema = z52.object({
66195
+ ref: z52.string(),
66196
+ kind: shopifyDraftOpKindSchema,
66197
+ entity: shopifyWriteEntitySchema,
66198
+ target: z52.string().optional(),
66199
+ targetLabel: z52.string().optional(),
66200
+ summary: z52.string(),
66201
+ money: z52.boolean(),
66202
+ warnings: z52.array(z52.string()).optional(),
66203
+ dependsOn: z52.array(z52.string()),
66204
+ undoOf: z52.string().optional(),
66205
+ stagedAt: z52.number(),
66206
+ result: shopifyDraftOpResultSchema.optional()
66207
+ });
66208
+ var shopifyDraftListRequestSchema = z52.object({ chatId: z52.string() });
66209
+ var shopifyDraftListResponseSchema = z52.object({
66210
+ status: z52.enum(["active", "publishing", "applied", "discarded", "none"]),
66211
+ store: z52.string().optional(),
66212
+ count: z52.number(),
66213
+ ops: z52.array(shopifyDraftOpViewSchema),
66214
+ publishError: z52.string().optional()
66215
+ });
66216
+ var shopifyDraftShowRequestSchema = z52.object({ chatId: z52.string(), ref: z52.string() });
66217
+ var shopifyDraftShowResponseSchema = z52.object({
66218
+ op: shopifyDraftOpViewSchema.extend({ payload: z52.unknown().optional(), before: z52.unknown().optional() })
66219
+ });
66220
+ var shopifyDraftRemoveRequestSchema = z52.object({ chatId: z52.string(), ref: z52.string() });
66221
+ var shopifyDraftRemoveResponseSchema = z52.object({ removed: z52.array(z52.string()) });
66222
+ var shopifyDraftClearRequestSchema = z52.object({ chatId: z52.string() });
66223
+ var shopifyDraftClearResponseSchema = z52.object({ cleared: z52.number() });
66224
+ var shopifyChangeViewSchema = z52.object({
66225
+ changeId: z52.string(),
66226
+ when: z52.string(),
66227
+ kind: shopifyDraftOpKindSchema,
66228
+ summary: z52.string(),
66229
+ status: z52.enum(["applied", "failed", "undone"]),
66230
+ chatId: z52.string(),
66231
+ undoable: z52.boolean(),
66232
+ /** Why it can't be undone, when it can't. Whether the store still holds Baker's change is checked when undo is staged. */
66233
+ notUndoableBecause: z52.string().optional()
66234
+ });
66235
+ var shopifyChangesRequestSchema = z52.object({
66236
+ limit: z52.coerce.number().int().min(1).max(100).optional(),
66237
+ chatId: z52.string().optional()
66238
+ });
66239
+ var shopifyChangesResponseSchema = z52.object({ changes: z52.array(shopifyChangeViewSchema) });
66240
+ var shopifyUndoRequestSchema = z52.object({
66241
+ chatId: z52.string(),
66242
+ changeIds: z52.array(z52.string().min(1)).min(1).max(SHOPIFY_DRAFT_BATCH_MAX)
66243
+ });
66244
+
65794
66245
  // src/commands/shopify/refusal.ts
65795
66246
  var CONTINUE2 = "Keep going with the wider task: say which part you could not read, skip it, and finish everything else.";
65796
66247
  function shopifyRefusal(code, message) {
@@ -65815,6 +66266,36 @@ function shopifyRefusal(code, message) {
65815
66266
  }
65816
66267
  return null;
65817
66268
  }
66269
+ function storeChangeRefusal(code, message) {
66270
+ if (code !== "FORBIDDEN" || !message.startsWith(SHOPIFY_PERMISSION_NEEDED_PREFIX)) return null;
66271
+ return {
66272
+ code: "SHOPIFY_PERMISSION_NEEDED",
66273
+ fix: {
66274
+ action: "request_connection",
66275
+ 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.'
66276
+ }
66277
+ };
66278
+ }
66279
+ function approvalRefusal(needsApproval) {
66280
+ if (needsApproval.declined) {
66281
+ return {
66282
+ code: "NEEDS_USER_APPROVAL",
66283
+ fix: {
66284
+ action: "continue_without",
66285
+ askUserQuestion: needsApproval.askUserQuestion,
66286
+ 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."
66287
+ }
66288
+ };
66289
+ }
66290
+ return {
66291
+ code: "NEEDS_USER_APPROVAL",
66292
+ fix: {
66293
+ action: "ask_user",
66294
+ askUserQuestion: needsApproval.askUserQuestion,
66295
+ 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.'
66296
+ }
66297
+ };
66298
+ }
65818
66299
 
65819
66300
  // src/commands/shopify/common.ts
65820
66301
  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.";
@@ -65862,7 +66343,7 @@ var fullArg = {
65862
66343
 
65863
66344
  // src/commands/shopify/hints.ts
65864
66345
  var RECONNECT = 'Baker has lost access to this Shopify store, so these products may be out of date. Say so, and ask the user to reconnect with `request_connection` { platform: "shopify" } \u2014 the card offers Reconnect.';
65865
- var OWN_STOREFRONT = "This store sells through its own storefront, not a Shopify online store: Baker has no store page links for its products \u2014 link to the product pages on the client's own site, and ask the user for them if you don't know them.";
66346
+ var OWN_STOREFRONT = "This store sells through its own storefront, not a Shopify online store: Baker links to its product pages once the website's address is set in Brain \u2192 Store (`buyUrlKind` says where each link goes); until then, link to the product pages on the client's own site, and ask the user for them if you don't know them.";
65866
66347
  function day(iso) {
65867
66348
  return iso ? iso.slice(0, 10) : null;
65868
66349
  }
@@ -65970,17 +66451,17 @@ function stockHintsFor(product) {
65970
66451
  }
65971
66452
  var BUY_NOTES = {
65972
66453
  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.",
65973
- 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.",
65974
- own_storefront: "The store sells through its own storefront: buy links go straight to Shopify's checkout.",
66454
+ subscription_optional: "It can also be bought by subscription: on a landing `<ShopifyProduct>` offers the plans; a `buyUrl` elsewhere adds it once.",
66455
+ own_storefront: "The store sells through its own website: `buyUrlKind` says where each link goes \u2014 its product page, or its cart page once its developers confirm it in Brain \u2192 Store.",
65975
66456
  details_pending: "Baker hasn't read this product's details yet (subscriptions among them): its buy links may change.",
65976
- 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."
66457
+ 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."
65977
66458
  };
65978
66459
  function buyHints(product) {
65979
66460
  const hints2 = [];
65980
66461
  if (product.variants.some((variant) => variant.buyUrl)) {
65981
66462
  hints2.push(
65982
- `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).`,
65983
- "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`)."
66463
+ `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\`.`,
66464
+ `\`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).`
65984
66465
  );
65985
66466
  }
65986
66467
  const notes = new Set(product.variants.flatMap((variant) => variant.notes ?? []));
@@ -66435,18 +66916,899 @@ var segmentsCommand = defineCommand209({
66435
66916
  }
66436
66917
  });
66437
66918
 
66438
- // src/commands/shopify/promotions.ts
66919
+ // src/commands/shopify/change.ts
66920
+ import { defineCommand as defineCommand211 } from "citty";
66921
+
66922
+ // src/commands/shopify/writeCommon.ts
66439
66923
  import { defineCommand as defineCommand210 } from "citty";
66440
66924
 
66925
+ // src/commands/shopify/writeHints.ts
66926
+ 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.";
66927
+ var WARNINGS = "Read the warnings above before telling the user this is ready.";
66928
+ var DISCOUNT = "The code works only after the chat is published; don't announce it as live or share it as working yet.";
66929
+ var EMPTY_COLLECTION = "The new collection has no products yet: add them with `baker shopify change collection add <collection> <product\u2026>`.";
66930
+ 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).";
66931
+ function repointedRedirects(ops) {
66932
+ const removed = ops.flatMap(
66933
+ (op) => op.kind === "shopify.urlRedirect.delete" && op.ref.startsWith("urlRedirect:") ? [op.ref.slice("urlRedirect:".length)] : []
66934
+ );
66935
+ return removed.flatMap((path49) => {
66936
+ const made = ops.find(
66937
+ (op) => op.kind === "shopify.urlRedirect.create" && op.summary.startsWith(`Redirect ${path49} to `)
66938
+ );
66939
+ if (!made) return [];
66940
+ const to = made.summary.slice(`Redirect ${path49} to `.length);
66941
+ return [
66942
+ `${path49} still redirects after publish, now to ${to}: tell the user the old link goes there, not that its redirect is removed.`
66943
+ ];
66944
+ });
66945
+ }
66946
+ function stageHints(data) {
66947
+ if (!data.staged) return [];
66948
+ const hints2 = [STAGED, ...repointedRedirects(data.ops)];
66949
+ if (data.ops.some((op) => op.warnings.length > 0)) hints2.push(WARNINGS);
66950
+ if (data.ops.some((op) => op.kind === "shopify.discountCode.create")) hints2.push(DISCOUNT);
66951
+ if (data.ops.some((op) => op.kind === "shopify.collection.delete")) hints2.push(COLLECTION_DELETE);
66952
+ if (data.ops.some(
66953
+ (op) => op.kind === "shopify.collection.create" && op.warnings.some((warning) => /no products/.test(warning))
66954
+ )) {
66955
+ hints2.push(EMPTY_COLLECTION);
66956
+ }
66957
+ return hints2;
66958
+ }
66959
+ function changesHints(data) {
66960
+ if (data.changes.length === 0) return ["Baker hasn't changed this store yet."];
66961
+ return data.changes.some((change) => change.undoable) ? ["Undo one with `baker shopify undo <changeId>` (staged like any change, applied when the user publishes)."] : [];
66962
+ }
66963
+ function auditHints(data) {
66964
+ const hints2 = [];
66965
+ for (const check of data.checks) {
66966
+ if (check.fix?.kind === "staged") hints2.push(`Stage the fix for "${check.title}": \`${check.fix.command}\`.`);
66967
+ }
66968
+ for (const check of data.checks) {
66969
+ if (check.fix?.kind === "merchant") {
66970
+ hints2.push(
66971
+ `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" : ""}.`
66972
+ );
66973
+ }
66974
+ }
66975
+ for (const check of data.checks) {
66976
+ if (check.fix?.kind === "decision") hints2.push(`A person decides "${check.title}": ${check.fix.where}.`);
66977
+ }
66978
+ return hints2;
66979
+ }
66980
+
66981
+ // src/commands/shopify/writeCommon.ts
66982
+ function failChange(err) {
66983
+ if (err instanceof ApiError) {
66984
+ const refusal = storeChangeRefusal(err.code, err.message);
66985
+ if (refusal) {
66986
+ writeJson({ ok: false, error: { code: refusal.code, message: err.message, fix: refusal.fix } });
66987
+ process.exit(1);
66988
+ }
66989
+ }
66990
+ fail9(err);
66991
+ }
66992
+ function emitStage(data) {
66993
+ if (!data.staged) {
66994
+ const refusal = approvalRefusal(data.needsApproval);
66995
+ writeJson({
66996
+ ok: false,
66997
+ error: {
66998
+ code: refusal.code,
66999
+ 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.",
67000
+ fix: refusal.fix
67001
+ }
67002
+ });
67003
+ process.exit(1);
67004
+ }
67005
+ writeJson({ ok: true, hints: stageHints(data), data });
67006
+ }
67007
+ async function stage(ops) {
67008
+ const chatId = requireChatId();
67009
+ try {
67010
+ const [only] = ops;
67011
+ const data = ops.length === 1 && only ? await apiPost("/api/shopify/draft/stage", { chatId, op: only }) : await apiPost("/api/shopify/draft/stage-batch", { chatId, ops });
67012
+ emitStage(data);
67013
+ } catch (err) {
67014
+ failChange(err);
67015
+ }
67016
+ }
67017
+ function list(value) {
67018
+ if (typeof value !== "string" || value.trim() === "") return void 0;
67019
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
67020
+ }
67021
+ function defined(entries) {
67022
+ return Object.fromEntries(Object.entries(entries).filter(([, value]) => value !== void 0 && value !== ""));
67023
+ }
67024
+ function payloadsFrom(args5) {
67025
+ if (typeof args5.json !== "string" && typeof args5.file !== "string") return null;
67026
+ return loadJsonPayloads(args5);
67027
+ }
67028
+ var JSON_ARGS = {
67029
+ json: {
67030
+ type: "string",
67031
+ 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',
67032
+ required: false
67033
+ },
67034
+ file: { type: "string", description: "A JSON file holding the same", required: false }
67035
+ };
67036
+ function leaf(schemaName, meta, args5, run) {
67037
+ registerSchema({
67038
+ command: schemaName,
67039
+ description: meta.description,
67040
+ args: Object.fromEntries(
67041
+ Object.entries(args5).map(([name, arg]) => [
67042
+ name,
67043
+ {
67044
+ type: String(arg.type ?? "string"),
67045
+ description: String(arg.description ?? ""),
67046
+ required: arg.required === true
67047
+ }
67048
+ ])
67049
+ )
67050
+ });
67051
+ return defineCommand210({ meta, args: args5, run: async ({ args: parsed }) => await run(parsed) });
67052
+ }
67053
+
67054
+ // src/commands/shopify/change.ts
67055
+ var TARGET = (what) => ({ type: "positional", description: what, required: false });
67056
+ function positionals(args5) {
67057
+ return Array.isArray(args5._) ? args5._.filter((value) => typeof value === "string") : [];
67058
+ }
67059
+ function target3(args5, what) {
67060
+ const [first] = positionals(args5);
67061
+ if (!first) failValidation3(`pass the ${what} as the first argument`);
67062
+ return first;
67063
+ }
67064
+ async function stageFor(kind, args5, flags, targetOf) {
67065
+ const payloads = payloadsFrom(args5);
67066
+ if (payloads) {
67067
+ await stage(
67068
+ payloads.map(
67069
+ (payload2) => "kind" in payload2 ? payload2 : { kind, ...targetOf ? { target: targetOf() } : {}, payload: payload2 }
67070
+ )
67071
+ );
67072
+ return;
67073
+ }
67074
+ const payload = flags();
67075
+ if (Object.keys(payload).length === 0)
67076
+ failValidation3("name at least one thing to change (see `baker schema` for the flags)");
67077
+ await stage([{ kind, ...targetOf ? { target: targetOf() } : {}, payload }]);
67078
+ }
67079
+ var productUpdate = leaf(
67080
+ "shopify.change.product.update",
67081
+ {
67082
+ name: "update",
67083
+ 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.`
67084
+ },
67085
+ {
67086
+ product: TARGET("Product id, handle or link"),
67087
+ title: { type: "string", description: "New title", required: false },
67088
+ description: { type: "string", description: "New description, as HTML (no scripts or frames)", required: false },
67089
+ "seo-title": { type: "string", description: "Title shown in search results", required: false },
67090
+ "seo-description": { type: "string", description: "Description shown in search results", required: false },
67091
+ tags: { type: "string", description: "The whole tag list, comma separated (replaces it)", required: false },
67092
+ "add-tags": { type: "string", description: "Tags to add, comma separated", required: false },
67093
+ "remove-tags": { type: "string", description: "Tags to remove, comma separated", required: false },
67094
+ type: { type: "string", description: "Product type", required: false },
67095
+ ...JSON_ARGS
67096
+ },
67097
+ async (args5) => await stageFor(
67098
+ "shopify.product.update",
67099
+ args5,
67100
+ () => defined({
67101
+ title: args5.title,
67102
+ descriptionHtml: args5.description,
67103
+ seo: args5["seo-title"] !== void 0 || args5["seo-description"] !== void 0 ? defined({ title: args5["seo-title"], description: args5["seo-description"] }) : void 0,
67104
+ tags: list(args5.tags),
67105
+ addTags: list(args5["add-tags"]),
67106
+ removeTags: list(args5["remove-tags"]),
67107
+ productType: args5.type
67108
+ }),
67109
+ positionals(args5).length > 0 ? () => target3(args5, "product") : void 0
67110
+ )
67111
+ );
67112
+ var WEIGHT_UNITS = {
67113
+ g: "GRAMS",
67114
+ gram: "GRAMS",
67115
+ grams: "GRAMS",
67116
+ kg: "KILOGRAMS",
67117
+ kgs: "KILOGRAMS",
67118
+ kilogram: "KILOGRAMS",
67119
+ kilograms: "KILOGRAMS",
67120
+ oz: "OUNCES",
67121
+ ounce: "OUNCES",
67122
+ ounces: "OUNCES",
67123
+ lb: "POUNDS",
67124
+ lbs: "POUNDS",
67125
+ pound: "POUNDS",
67126
+ pounds: "POUNDS"
67127
+ };
67128
+ function weightOf(value, unit) {
67129
+ if (value === void 0) return void 0;
67130
+ const named = typeof unit === "string" ? unit.trim() : "";
67131
+ return {
67132
+ value: Number(value),
67133
+ unit: named ? WEIGHT_UNITS[named.toLowerCase()] ?? named.toUpperCase() : "KILOGRAMS"
67134
+ };
67135
+ }
67136
+ var variantUpdate = leaf(
67137
+ "shopify.change.variant.update",
67138
+ {
67139
+ name: "update",
67140
+ 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."
67141
+ },
67142
+ {
67143
+ variant: TARGET("The option's id (variantId from baker shopify product)"),
67144
+ sku: { type: "string", description: "SKU", required: false },
67145
+ barcode: { type: "string", description: "Barcode / GTIN (8, 12, 13 or 14 digits)", required: false },
67146
+ weight: { type: "string", description: "Weight value", required: false },
67147
+ "weight-unit": {
67148
+ type: "string",
67149
+ description: "kg | g | lb | oz (default kg)",
67150
+ required: false
67151
+ },
67152
+ price: {
67153
+ type: "string",
67154
+ description: `Price as a decimal, e.g. "649.95" (needs the user's yes)`,
67155
+ required: false
67156
+ },
67157
+ "sale-price": {
67158
+ type: "string",
67159
+ description: `Compare-at price, or "none" to clear it (needs the user's yes)`,
67160
+ required: false
67161
+ },
67162
+ ...JSON_ARGS
67163
+ },
67164
+ async (args5) => await stageFor(
67165
+ "shopify.variant.update",
67166
+ args5,
67167
+ () => defined({
67168
+ sku: args5.sku,
67169
+ barcode: args5.barcode,
67170
+ weight: weightOf(args5.weight, args5["weight-unit"]),
67171
+ price: args5.price,
67172
+ compareAtPrice: args5["sale-price"] === "none" ? null : args5["sale-price"]
67173
+ }),
67174
+ positionals(args5).length > 0 ? () => target3(args5, "option id") : void 0
67175
+ )
67176
+ );
67177
+ var fieldSet = leaf(
67178
+ "shopify.change.field.set",
67179
+ {
67180
+ name: "set",
67181
+ 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".'
67182
+ },
67183
+ {
67184
+ owner: TARGET("product:<ref>, variant:<id> or collection:<ref>"),
67185
+ namespace: { type: "string", description: "Namespace, e.g. custom", required: false },
67186
+ key: { type: "string", description: "Key, e.g. care", required: false },
67187
+ type: {
67188
+ type: "string",
67189
+ description: "Shopify's type, e.g. single_line_text_field, rich_text_field, json",
67190
+ required: false
67191
+ },
67192
+ value: { type: "string", description: "The value, as Shopify takes it for that type", required: false },
67193
+ ...JSON_ARGS
67194
+ },
67195
+ async (args5) => await stageFor(
67196
+ "shopify.metafield.set",
67197
+ args5,
67198
+ () => defined({ namespace: args5.namespace, key: args5.key, type: args5.type, value: args5.value }),
67199
+ () => target3(args5, "owner")
67200
+ )
67201
+ );
67202
+ var fieldRemove = leaf(
67203
+ "shopify.change.field.remove",
67204
+ {
67205
+ name: "remove",
67206
+ description: "Remove a custom field from a product, an option or a collection. Example: baker shopify change field remove product:the-snowboard custom.care"
67207
+ },
67208
+ {
67209
+ owner: TARGET("product:<ref>, variant:<id> or collection:<ref>"),
67210
+ field: TARGET("namespace.key, e.g. custom.care")
67211
+ },
67212
+ async (args5) => {
67213
+ const [owner, field] = positionals(args5);
67214
+ const [namespace, ...rest] = (field ?? "").split(".");
67215
+ if (!owner || !namespace || rest.length === 0) failValidation3("pass the owner and the field as namespace.key");
67216
+ await stage([{ kind: "shopify.metafield.delete", target: owner, payload: { namespace, key: rest.join(".") } }]);
67217
+ }
67218
+ );
67219
+ var collectionCreate = leaf(
67220
+ "shopify.change.collection.create",
67221
+ {
67222
+ name: "create",
67223
+ 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.'
67224
+ },
67225
+ {
67226
+ title: { type: "string", description: "Title", required: false },
67227
+ handle: {
67228
+ type: "string",
67229
+ description: "Its address (/collections/<handle>); default from the title",
67230
+ required: false
67231
+ },
67232
+ description: { type: "string", description: "Description, as HTML (no scripts or frames)", required: false },
67233
+ "seo-title": { type: "string", description: "Title shown in search results", required: false },
67234
+ "seo-description": { type: "string", description: "Description shown in search results", required: false },
67235
+ products: {
67236
+ type: "string",
67237
+ description: "Products, comma separated (ids, handles or links), in order",
67238
+ required: false
67239
+ },
67240
+ sort: {
67241
+ type: "string",
67242
+ description: "MANUAL | BEST_SELLING | ALPHA_ASC | ALPHA_DESC | PRICE_ASC | PRICE_DESC | CREATED | CREATED_DESC",
67243
+ required: false
67244
+ },
67245
+ hidden: { type: "boolean", description: "Don't show it on the online store yet", required: false },
67246
+ ...JSON_ARGS
67247
+ },
67248
+ async (args5) => await stageFor(
67249
+ "shopify.collection.create",
67250
+ args5,
67251
+ () => defined({
67252
+ title: args5.title,
67253
+ handle: args5.handle,
67254
+ descriptionHtml: args5.description,
67255
+ seo: args5["seo-title"] !== void 0 || args5["seo-description"] !== void 0 ? defined({ title: args5["seo-title"], description: args5["seo-description"] }) : void 0,
67256
+ products: list(args5.products),
67257
+ sortOrder: typeof args5.sort === "string" ? args5.sort.toUpperCase() : void 0,
67258
+ onlineStore: args5.hidden === true ? false : void 0
67259
+ })
67260
+ )
67261
+ );
67262
+ var collectionUpdate = leaf(
67263
+ "shopify.change.collection.update",
67264
+ {
67265
+ name: "update",
67266
+ 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"`
67267
+ },
67268
+ {
67269
+ collection: TARGET("Collection id, handle or link"),
67270
+ title: { type: "string", description: "New title", required: false },
67271
+ description: { type: "string", description: "New description, as HTML", required: false },
67272
+ "seo-title": { type: "string", description: "Title shown in search results", required: false },
67273
+ "seo-description": { type: "string", description: "Description shown in search results", required: false },
67274
+ sort: { type: "string", description: "Sort order (see collection create)", required: false },
67275
+ ...JSON_ARGS
67276
+ },
67277
+ async (args5) => await stageFor(
67278
+ "shopify.collection.update",
67279
+ args5,
67280
+ () => defined({
67281
+ title: args5.title,
67282
+ descriptionHtml: args5.description,
67283
+ seo: args5["seo-title"] !== void 0 || args5["seo-description"] !== void 0 ? defined({ title: args5["seo-title"], description: args5["seo-description"] }) : void 0,
67284
+ sortOrder: typeof args5.sort === "string" ? args5.sort.toUpperCase() : void 0
67285
+ }),
67286
+ () => target3(args5, "collection")
67287
+ )
67288
+ );
67289
+ function collectionVerb(verb, kind, description2) {
67290
+ return leaf(
67291
+ `shopify.change.collection.${verb}`,
67292
+ { name: verb, description: description2 },
67293
+ { collection: TARGET("Collection id, handle, link or a shopify_temp_ ref staged in this chat") },
67294
+ async (args5) => await stage([{ kind, target: target3(args5, "collection") }])
67295
+ );
67296
+ }
67297
+ function membership(verb, kind, description2) {
67298
+ return leaf(
67299
+ `shopify.change.collection.${verb}`,
67300
+ { name: verb, description: description2 },
67301
+ {
67302
+ collection: TARGET("Collection id, handle, link or a shopify_temp_ ref"),
67303
+ products: TARGET("Products (ids, handles or links), space separated")
67304
+ },
67305
+ async (args5) => {
67306
+ const [collection, ...products] = positionals(args5);
67307
+ if (!collection || products.length === 0) failValidation3("pass the collection, then the products");
67308
+ await stage([{ kind, target: collection, payload: { products } }]);
67309
+ }
67310
+ );
67311
+ }
67312
+ function discountValue(args5) {
67313
+ if (args5.percent !== void 0) return { percentage: Number(args5.percent) };
67314
+ if (args5.amount !== void 0) return { amount: args5.amount };
67315
+ return void 0;
67316
+ }
67317
+ function appliesToOf(args5) {
67318
+ const products = list(args5.products);
67319
+ if (products) return { products };
67320
+ const collections = list(args5.collections);
67321
+ return collections ? { collections } : { all: true };
67322
+ }
67323
+ function combinesOf(value) {
67324
+ const combines = list(value) ?? [];
67325
+ if (combines.length === 0) return void 0;
67326
+ return {
67327
+ productDiscounts: combines.includes("product"),
67328
+ orderDiscounts: combines.includes("order"),
67329
+ shippingDiscounts: combines.includes("shipping")
67330
+ };
67331
+ }
67332
+ function numberOf3(value) {
67333
+ return value === void 0 ? void 0 : Number(value);
67334
+ }
67335
+ function discountPayload(args5) {
67336
+ return defined({
67337
+ code: args5.code,
67338
+ title: args5.title,
67339
+ value: discountValue(args5),
67340
+ appliesTo: appliesToOf(args5),
67341
+ minimumSubtotal: args5["min-subtotal"],
67342
+ minimumQuantity: numberOf3(args5["min-quantity"]),
67343
+ startsAt: args5.starts,
67344
+ endsAt: args5.ends,
67345
+ usageLimit: numberOf3(args5.limit),
67346
+ oncePerCustomer: args5["any-times"] === true ? false : void 0,
67347
+ combinesWith: combinesOf(args5.combines)
67348
+ });
67349
+ }
67350
+ function signupOfferFlagsProblem(args5) {
67351
+ if (typeof args5.name !== "string" || args5.name.trim() === "")
67352
+ return 'Add --name "<what shoppers and store staff see, e.g. 10% off>": every sign-up offer needs a name.';
67353
+ if (args5["valid-days"] === void 0)
67354
+ return "Add --valid-days <1-365>: how many days each code lasts after it is given.";
67355
+ return void 0;
67356
+ }
67357
+ function signupOfferPayload(args5) {
67358
+ return defined({
67359
+ name: args5.name,
67360
+ value: discountValue(args5),
67361
+ appliesTo: appliesToOf(args5),
67362
+ minimumSubtotal: args5.minimum,
67363
+ validForDays: numberOf3(args5["valid-days"]),
67364
+ endsAt: args5.ends,
67365
+ maxCodes: numberOf3(args5["max-codes"]),
67366
+ codePrefix: args5.prefix,
67367
+ combinesWith: combinesOf(args5.combines)
67368
+ });
67369
+ }
67370
+ var signupOfferSet = leaf(
67371
+ "shopify.change.signupOffer.set",
67372
+ {
67373
+ name: "set",
67374
+ description: 'Stage a sign-up offer. Changes money: ask the exact approval question returned by this command. Example: baker shopify change signup-offer set welcome-10 --name "Welcome" --percent 10 --valid-days 14 --max-codes 1000'
67375
+ },
67376
+ {
67377
+ handle: TARGET("Offer handle, e.g. welcome-10"),
67378
+ name: {
67379
+ type: "string",
67380
+ description: 'Offer name shoppers and store staff see (required), e.g. "10% off"',
67381
+ required: false
67382
+ },
67383
+ percent: { type: "string", description: "Percentage off (choose percent or amount)", required: false },
67384
+ amount: { type: "string", description: "Amount off in the store currency", required: false },
67385
+ products: { type: "string", description: "Product handles or ids, comma separated", required: false },
67386
+ collections: { type: "string", description: "Collection handles or ids, comma separated", required: false },
67387
+ minimum: { type: "string", description: "Minimum subtotal", required: false },
67388
+ "valid-days": { type: "string", description: "Each code lasts 1\u2013365 days (required)", required: false },
67389
+ ends: { type: "string", description: "Offer end as ISO date and time; no code outlives it", required: false },
67390
+ "max-codes": { type: "string", description: "Total codes the offer may give, 1\u2013100000", required: false },
67391
+ prefix: { type: "string", description: "Code prefix: 2\u201312 uppercase letters or digits", required: false },
67392
+ combines: {
67393
+ type: "string",
67394
+ description: "Comma-separated product,order,shipping discounts to combine",
67395
+ required: false
67396
+ },
67397
+ ...JSON_ARGS
67398
+ },
67399
+ async (args5) => await stageFor(
67400
+ "shopify.signupOffer.set",
67401
+ args5,
67402
+ () => {
67403
+ const problem = signupOfferFlagsProblem(args5);
67404
+ if (problem) failValidation3(problem);
67405
+ return signupOfferPayload(args5);
67406
+ },
67407
+ () => target3(args5, "offer handle")
67408
+ )
67409
+ );
67410
+ var signupOfferEnd = leaf(
67411
+ "shopify.change.signupOffer.end",
67412
+ {
67413
+ name: "end",
67414
+ description: "End a sign-up offer at Publish. Existing codes keep their terms. Example: baker shopify change signup-offer end welcome-10"
67415
+ },
67416
+ { handle: TARGET("Offer handle") },
67417
+ async (args5) => await stage([{ kind: "shopify.signupOffer.end", target: target3(args5, "offer handle") }])
67418
+ );
67419
+ var discountCreate = leaf(
67420
+ "shopify.change.discount.create",
67421
+ {
67422
+ name: "create",
67423
+ 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."
67424
+ },
67425
+ {
67426
+ code: { type: "string", description: "The code shoppers type, e.g. SPRING10", required: false },
67427
+ title: { type: "string", description: "What the store's staff see (default: the code)", required: false },
67428
+ percent: { type: "string", description: "Percentage off, 1-100", required: false },
67429
+ amount: { type: "string", description: "Amount off, as a decimal in the store's currency", required: false },
67430
+ products: { type: "string", description: "Only these products, comma separated", required: false },
67431
+ collections: { type: "string", description: "Only these collections, comma separated", required: false },
67432
+ "min-subtotal": { type: "string", description: "Minimum order amount", required: false },
67433
+ "min-quantity": { type: "string", description: "Minimum number of items", required: false },
67434
+ starts: { type: "string", description: "Start, ISO date-time (default: when published)", required: false },
67435
+ ends: { type: "string", description: "End, ISO date-time (default: no end)", required: false },
67436
+ limit: { type: "string", description: "Total number of uses", required: false },
67437
+ "any-times": { type: "boolean", description: "A customer may use it more than once", required: false },
67438
+ combines: {
67439
+ type: "string",
67440
+ description: "What it combines with, comma separated: product, order, shipping",
67441
+ required: false
67442
+ },
67443
+ ...JSON_ARGS
67444
+ },
67445
+ async (args5) => await stageFor("shopify.discountCode.create", args5, () => discountPayload(args5))
67446
+ );
67447
+ var discountStop = leaf(
67448
+ "shopify.change.discount.stop",
67449
+ {
67450
+ name: "stop",
67451
+ 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"
67452
+ },
67453
+ { code: TARGET("The discount code") },
67454
+ async (args5) => await stage([{ kind: "shopify.discountCode.deactivate", target: target3(args5, "code") }])
67455
+ );
67456
+ var discountDates = leaf(
67457
+ "shopify.change.discount.dates",
67458
+ {
67459
+ name: "dates",
67460
+ 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"
67461
+ },
67462
+ {
67463
+ code: TARGET("The discount code"),
67464
+ starts: { type: "string", description: "Start, ISO date-time", required: false },
67465
+ ends: { type: "string", description: 'End, ISO date-time, or "never"', required: false }
67466
+ },
67467
+ async (args5) => {
67468
+ if (typeof args5.starts !== "string" || typeof args5.ends !== "string") failValidation3("pass --starts and --ends");
67469
+ await stage([
67470
+ {
67471
+ kind: "shopify.discountCode.schedule",
67472
+ target: target3(args5, "code"),
67473
+ payload: { startsAt: args5.starts, endsAt: args5.ends === "never" ? null : args5.ends }
67474
+ }
67475
+ ]);
67476
+ }
67477
+ );
67478
+ var redirectCreate = leaf(
67479
+ "shopify.change.redirect.create",
67480
+ {
67481
+ name: "create",
67482
+ 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"
67483
+ },
67484
+ {
67485
+ from: { type: "string", description: "The old path, starting with /", required: false },
67486
+ to: { type: "string", description: "Where it sends shoppers: a path or an https link", required: false },
67487
+ ...JSON_ARGS
67488
+ },
67489
+ async (args5) => await stageFor("shopify.urlRedirect.create", args5, () => defined({ from: args5.from, to: args5.to }))
67490
+ );
67491
+ var redirectRemove = leaf(
67492
+ "shopify.change.redirect.remove",
67493
+ {
67494
+ name: "remove",
67495
+ description: "Remove a redirect, by the path it redirects. Example: baker shopify change redirect remove /old"
67496
+ },
67497
+ { from: TARGET("The redirected path") },
67498
+ async (args5) => await stage([{ kind: "shopify.urlRedirect.delete", target: target3(args5, "path") }])
67499
+ );
67500
+ var campaignAdd = leaf(
67501
+ "shopify.change.campaign.add",
67502
+ {
67503
+ name: "add",
67504
+ 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"
67505
+ },
67506
+ {
67507
+ platform: { type: "string", description: "google-ads | meta", required: false },
67508
+ account: { type: "string", description: "The ad account id", required: false },
67509
+ campaign: { type: "string", description: "The campaign id", required: false },
67510
+ "utm-campaign": { type: "string", description: "utm_campaign its links carry", required: false },
67511
+ "utm-source": { type: "string", description: "utm_source", required: false },
67512
+ "utm-medium": { type: "string", description: "utm_medium", required: false }
67513
+ },
67514
+ async (args5) => await stage([
67515
+ {
67516
+ kind: "shopify.marketingActivity.upsert",
67517
+ payload: {
67518
+ platform: args5.platform,
67519
+ accountId: args5.account,
67520
+ campaignId: args5.campaign,
67521
+ utm: { campaign: args5["utm-campaign"], source: args5["utm-source"], medium: args5["utm-medium"] }
67522
+ }
67523
+ }
67524
+ ])
67525
+ );
67526
+ var campaignRemove = leaf(
67527
+ "shopify.change.campaign.remove",
67528
+ {
67529
+ name: "remove",
67530
+ description: "Take a Baker campaign out of Shopify Marketing. Example: baker shopify change campaign remove google-ads:3865083115:2101"
67531
+ },
67532
+ { campaign: TARGET("<platform>:<account>:<campaign>") },
67533
+ async (args5) => await stage([{ kind: "shopify.marketingActivity.delete", target: target3(args5, "campaign") }])
67534
+ );
67535
+ var measurementReconnect = leaf(
67536
+ "shopify.change.measurement.reconnect",
67537
+ {
67538
+ name: "reconnect",
67539
+ 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."
67540
+ },
67541
+ {},
67542
+ async () => await stage([{ kind: "shopify.webPixel.update" }])
67543
+ );
67544
+ var group = (name, description2, subCommands) => defineCommand211({ meta: { name, description: description2 }, subCommands });
67545
+ var changeCommand = defineCommand211({
67546
+ meta: {
67547
+ name: "change",
67548
+ description: `Stage a change to the connected store. Nothing changes until the user publishes the chat; every
67549
+ change can be put back with \`baker shopify undo\`. Prices and discounts need the user's yes first.
67550
+
67551
+ baker shopify change product update <product> --title \u2026 --seo-title \u2026 --add-tags a,b
67552
+ baker shopify change variant update <variantId> --sku \u2026 --barcode \u2026 --price \u2026
67553
+ baker shopify change field set product:<product> --namespace custom --key \u2026 --type \u2026 --value \u2026
67554
+ baker shopify change collection create --title \u2026 --products a,b
67555
+ baker shopify change discount create --code SPRING10 --percent 10 --ends <iso>
67556
+ baker shopify change redirect create --from /old --to /new
67557
+ baker shopify change campaign add --platform google-ads --account \u2026 --campaign \u2026
67558
+ baker shopify change measurement reconnect`
67559
+ },
67560
+ subCommands: {
67561
+ "signup-offer": group("signup-offer", "Stage or end a sign-up offer", { set: signupOfferSet, end: signupOfferEnd }),
67562
+ product: group("product", "Change a product's title, description, SEO, tags or type", { update: productUpdate }),
67563
+ variant: group("variant", "Change an option's SKU, barcode, weight or price", { update: variantUpdate }),
67564
+ field: group("field", "Set or remove a custom field", { set: fieldSet, remove: fieldRemove }),
67565
+ collection: group("collection", "Create, change, show, hide, fill or delete a collection", {
67566
+ create: collectionCreate,
67567
+ update: collectionUpdate,
67568
+ show: collectionVerb(
67569
+ "show",
67570
+ "shopify.collection.publish",
67571
+ "Show a collection on the online store. Example: baker shopify change collection show summer-boards"
67572
+ ),
67573
+ hide: collectionVerb(
67574
+ "hide",
67575
+ "shopify.collection.unpublish",
67576
+ "Hide a collection from the online store. Example: baker shopify change collection hide summer-boards"
67577
+ ),
67578
+ delete: collectionVerb(
67579
+ "delete",
67580
+ "shopify.collection.delete",
67581
+ "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"
67582
+ ),
67583
+ add: membership(
67584
+ "add",
67585
+ "shopify.collection.addProducts",
67586
+ "Add products to a collection. Example: baker shopify change collection add summer-boards snowboard-a snowboard-b"
67587
+ ),
67588
+ remove: membership(
67589
+ "remove",
67590
+ "shopify.collection.removeProducts",
67591
+ "Remove products from a collection. Example: baker shopify change collection remove summer-boards snowboard-a"
67592
+ )
67593
+ }),
67594
+ discount: group("discount", "Create, stop or reschedule a discount code (needs the user's yes)", {
67595
+ create: discountCreate,
67596
+ stop: discountStop,
67597
+ dates: discountDates
67598
+ }),
67599
+ redirect: group("redirect", "Create or remove a redirect", { create: redirectCreate, remove: redirectRemove }),
67600
+ campaign: group("campaign", "Show or remove a Baker campaign in Shopify Marketing", {
67601
+ add: campaignAdd,
67602
+ remove: campaignRemove
67603
+ }),
67604
+ measurement: group("measurement", "Reconnect Baker's measurement on the store", {
67605
+ reconnect: measurementReconnect
67606
+ })
67607
+ }
67608
+ });
67609
+
67610
+ // src/commands/shopify/changes.ts
67611
+ var changesCommand2 = leaf(
67612
+ "shopify.changes",
67613
+ {
67614
+ name: "changes",
67615
+ 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)."
67616
+ },
67617
+ {
67618
+ limit: { type: "string", description: "How many, 1-100 (default 20)", required: false },
67619
+ chat: { type: "string", description: "Only one chat's changes", required: false }
67620
+ },
67621
+ async (args5) => {
67622
+ try {
67623
+ const data = await apiGet(
67624
+ "/api/shopify/changes",
67625
+ params({ limit: args5.limit, chatId: args5.chat })
67626
+ );
67627
+ writeJson({ ok: true, hints: changesHints(data), data });
67628
+ } catch (err) {
67629
+ failChange(err);
67630
+ }
67631
+ }
67632
+ );
67633
+ var undoCommand = leaf(
67634
+ "shopify.undo",
67635
+ {
67636
+ name: "undo",
67637
+ 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]"
67638
+ },
67639
+ { changeIds: { type: "positional", description: "changeIds from `baker shopify changes`", required: false } },
67640
+ async (args5) => {
67641
+ const changeIds = Array.isArray(args5._) ? args5._.filter((value) => typeof value === "string") : [];
67642
+ if (changeIds.length === 0) failValidation3("pass the changeIds to undo (see `baker shopify changes`)");
67643
+ try {
67644
+ const data = await apiPost("/api/shopify/undo", {
67645
+ chatId: requireChatId(),
67646
+ changeIds
67647
+ });
67648
+ emitStage(data);
67649
+ } catch (err) {
67650
+ failChange(err);
67651
+ }
67652
+ }
67653
+ );
67654
+
67655
+ // src/commands/shopify/draft.ts
67656
+ import { defineCommand as defineCommand212 } from "citty";
67657
+ function refOf(args5) {
67658
+ const [ref] = Array.isArray(args5._) ? args5._ : [];
67659
+ if (typeof ref !== "string" || ref === "")
67660
+ failValidation3("pass the staged change's ref (see `baker shopify draft list`)");
67661
+ return ref;
67662
+ }
67663
+ function listHints(data) {
67664
+ if (data.status === "none" || data.count === 0) return ["No store changes are staged on this chat."];
67665
+ if (data.status === "active") {
67666
+ return [
67667
+ "Staged only: these apply when the user publishes the chat. Correct one with `baker shopify draft amend <ref>`, drop one with `remove`."
67668
+ ];
67669
+ }
67670
+ const failed = data.ops.filter((op) => op.result?.status === "failed" || op.result?.status === "skipped");
67671
+ return failed.length > 0 ? [`${failed.length} of ${data.count} changes did not go through \u2014 say which and why; never report them as made.`] : [];
67672
+ }
67673
+ var list2 = leaf(
67674
+ "shopify.draft.list",
67675
+ {
67676
+ name: "list",
67677
+ 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."
67678
+ },
67679
+ { chat: CHAT_READ_ARG },
67680
+ async (args5) => {
67681
+ try {
67682
+ const data = await apiPost("/api/shopify/draft/list", {
67683
+ chatId: resolveChatId(args5.chat)
67684
+ });
67685
+ writeJson({ ok: true, hints: listHints(data), data });
67686
+ } catch (err) {
67687
+ failChange(err);
67688
+ }
67689
+ }
67690
+ );
67691
+ var show = leaf(
67692
+ "shopify.draft.show",
67693
+ {
67694
+ name: "show",
67695
+ description: "One staged store change in full: what it sends and the store as it was read when it was staged (the before)."
67696
+ },
67697
+ { ref: { type: "positional", description: "The staged change's ref", required: false }, chat: CHAT_READ_ARG },
67698
+ async (args5) => {
67699
+ try {
67700
+ const data = await apiPost("/api/shopify/draft/show", { chatId: resolveChatId(args5.chat), ref: refOf(args5) });
67701
+ writeJson({ ok: true, data });
67702
+ } catch (err) {
67703
+ failChange(err);
67704
+ }
67705
+ }
67706
+ );
67707
+ var amend = leaf(
67708
+ "shopify.draft.amend",
67709
+ {
67710
+ name: "amend",
67711
+ 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.`
67712
+ },
67713
+ {
67714
+ ref: { type: "positional", description: "The staged change's ref", required: false },
67715
+ patch: { type: "string", description: "Inline JSON patch", required: false },
67716
+ file: { type: "string", description: "A JSON file with the patch", required: false }
67717
+ },
67718
+ async (args5) => {
67719
+ try {
67720
+ const data = await apiPost("/api/shopify/draft/amend", {
67721
+ chatId: requireChatId(),
67722
+ ref: refOf(args5),
67723
+ patch: loadJsonArg(args5, "patch")
67724
+ });
67725
+ emitStage(data);
67726
+ } catch (err) {
67727
+ failChange(err);
67728
+ }
67729
+ }
67730
+ );
67731
+ var remove = leaf(
67732
+ "shopify.draft.remove",
67733
+ {
67734
+ name: "remove",
67735
+ description: "Drop one staged store change, and any change that needs it (a new collection's show)."
67736
+ },
67737
+ { ref: { type: "positional", description: "The staged change's ref", required: false } },
67738
+ async (args5) => {
67739
+ try {
67740
+ writeJson({
67741
+ ok: true,
67742
+ data: await apiPost("/api/shopify/draft/remove", { chatId: requireChatId(), ref: refOf(args5) })
67743
+ });
67744
+ } catch (err) {
67745
+ failChange(err);
67746
+ }
67747
+ }
67748
+ );
67749
+ var clear = leaf(
67750
+ "shopify.draft.clear",
67751
+ { name: "clear", description: "Drop every store change staged on this chat." },
67752
+ {},
67753
+ async () => {
67754
+ try {
67755
+ writeJson({ ok: true, data: await apiPost("/api/shopify/draft/clear", { chatId: requireChatId() }) });
67756
+ } catch (err) {
67757
+ failChange(err);
67758
+ }
67759
+ }
67760
+ );
67761
+ var draftCommand5 = defineCommand212({
67762
+ meta: { name: "draft", description: "List, show, amend, remove or clear the store changes staged on this chat" },
67763
+ subCommands: { list: list2, show, amend, remove, clear }
67764
+ });
67765
+
67766
+ // src/commands/shopify/promotions.ts
67767
+ import { defineCommand as defineCommand213 } from "citty";
67768
+
66441
67769
  // src/commands/shopify/landingLinks.ts
66442
67770
  import { readdir as readdir16, readFile as readFile33, stat as stat11 } from "fs/promises";
66443
67771
  import path47 from "path";
67772
+ import { parse } from "@astrojs/compiler/sync";
66444
67773
  var PAGES_MAX = 300;
66445
67774
  var FILE_BYTES_MAX = 256 * 1024;
66446
67775
  function escaped(text2) {
66447
67776
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
66448
67777
  }
66449
- function evidenceIn(text2, keys) {
67778
+ var BUY_COMPONENTS = /* @__PURE__ */ new Set(["ShopifyProduct", "ShopifyBuyButton"]);
67779
+ var STRING_LITERAL = /^\s*(["'`])([^"'`]*)\1\s*$/;
67780
+ var ITEM_PRODUCT = /\bproduct\s*:\s*(["'`])([^"'`]*)\1/g;
67781
+ function namedBy(attribute) {
67782
+ if (attribute.name === "product") {
67783
+ if (attribute.kind === "quoted" || attribute.kind === "template-literal") return [attribute.value];
67784
+ if (attribute.kind === "expression") return [STRING_LITERAL.exec(attribute.value)?.[2] ?? ""];
67785
+ }
67786
+ if (attribute.name === "items" && attribute.kind === "expression") {
67787
+ return [...attribute.value.matchAll(ITEM_PRODUCT)].map((match) => match[2] ?? "");
67788
+ }
67789
+ return [];
67790
+ }
67791
+ function componentProducts(node, into) {
67792
+ if (node.type === "component" && BUY_COMPONENTS.has(node.name)) {
67793
+ for (const attribute of node.attributes) into.push(...namedBy(attribute));
67794
+ }
67795
+ if ("children" in node) for (const child of node.children) componentProducts(child, into);
67796
+ return into;
67797
+ }
67798
+ function buyComponentProducts(file) {
67799
+ if (!file.path.endsWith(".astro")) return [];
67800
+ try {
67801
+ return componentProducts(parse(file.text, void 0).ast, []);
67802
+ } catch {
67803
+ return [];
67804
+ }
67805
+ }
67806
+ function namesProduct(file, keys) {
67807
+ const wanted = new Set([keys.handle.toLowerCase(), ...keys.productId ? [keys.productId] : []].filter(Boolean));
67808
+ return buyComponentProducts(file).some((named) => wanted.has(named.trim().toLowerCase()));
67809
+ }
67810
+ function evidenceIn(file, keys) {
67811
+ const { text: text2 } = file;
66450
67812
  const evidence = [];
66451
67813
  if (keys.buyUrls.some((url) => url && text2.includes(url))) evidence.push("buy_link");
66452
67814
  const page = new RegExp(`/products/${escaped(keys.handle)}(?![A-Za-z0-9_-])`, "i");
@@ -66455,24 +67817,25 @@ function evidenceIn(text2, keys) {
66455
67817
  (id) => new RegExp(`(?:/cart/(?:\\d+:\\d+,)*${id}:|variant=${id}(?!\\d))`).test(text2)
66456
67818
  );
66457
67819
  if (option) evidence.push("variant_id");
67820
+ if (namesProduct(file, keys)) evidence.push("buy_button");
66458
67821
  return evidence;
66459
67822
  }
66460
67823
  function landingLinksOf(pages, keys) {
66461
67824
  const bySlug = /* @__PURE__ */ new Map();
66462
67825
  for (const page of pages) {
66463
- const found = new Set(page.files.flatMap((file) => evidenceIn(file.text, keys)));
67826
+ const found = new Set(page.files.flatMap((file) => evidenceIn(file, keys)));
66464
67827
  if (found.size === 0) continue;
66465
67828
  const slug = page.slug.replace(/--\d+$/, "");
66466
67829
  const held = bySlug.get(slug) ?? /* @__PURE__ */ new Set();
66467
67830
  for (const evidence of found) held.add(evidence);
66468
67831
  bySlug.set(slug, held);
66469
67832
  }
66470
- const order = ["buy_link", "product_page", "variant_id"];
67833
+ const order = ["buy_link", "product_page", "variant_id", "buy_button"];
66471
67834
  return [...bySlug].map(([slug, evidence]) => ({ slug, evidence: order.filter((entry) => evidence.has(entry)) }));
66472
67835
  }
66473
- async function isDir4(target2) {
67836
+ async function isDir4(target4) {
66474
67837
  try {
66475
- return (await stat11(target2)).isDirectory();
67838
+ return (await stat11(target4)).isDirectory();
66476
67839
  } catch {
66477
67840
  return false;
66478
67841
  }
@@ -66510,8 +67873,8 @@ function plural(count2, one, many = `${one}s`) {
66510
67873
  }
66511
67874
  function pauseCommand2(ad) {
66512
67875
  if (ad.platform === "meta") return `\`baker ads meta ads pause ${ad.id ?? "<id>"}\``;
66513
- const target2 = `${ad.resource ?? "<resource>"} --customer-id ${ad.account_id ?? "<account_id>"}`;
66514
- return ad.level === "asset_group" ? `\`baker ads google asset-groups update ${target2} --status PAUSED\`` : `\`baker ads google ads pause ${target2}\``;
67876
+ const target4 = `${ad.resource ?? "<resource>"} --customer-id ${ad.account_id ?? "<account_id>"}`;
67877
+ return ad.level === "asset_group" ? `\`baker ads google asset-groups update ${target4} --status PAUSED\`` : `\`baker ads google ads pause ${target4}\``;
66515
67878
  }
66516
67879
  function pauseHint(answer) {
66517
67880
  if (answer.product.stock === "in_stock" || answer.ads.length === 0) return null;
@@ -66520,8 +67883,7 @@ function pauseHint(answer) {
66520
67883
  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.`;
66521
67884
  }
66522
67885
  const one = active.length === 1;
66523
- const remembered = answer.remembered ? " A pause is remembered for this product only because you read its promotions here." : "";
66524
- 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.${remembered}`;
67886
+ 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.`;
66525
67887
  }
66526
67888
  function promotionHints(answer, scan) {
66527
67889
  const hints2 = [];
@@ -66555,7 +67917,7 @@ function promotionHints(answer, scan) {
66555
67917
  }
66556
67918
  if (!answer.remembered && answer.ads.length > 0) {
66557
67919
  hints2.push(
66558
- "This read was not remembered for a Session (no chat): a pause staged from it will never be offered back for restarting when the product returns."
67920
+ "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."
66559
67921
  );
66560
67922
  }
66561
67923
  return hints2;
@@ -66589,8 +67951,8 @@ var META_GROUP = {
66589
67951
  };
66590
67952
  function resumeCommand2(hold) {
66591
67953
  if (hold.platform === "meta") return `baker ads meta ${META_GROUP[hold.level]} resume ${hold.entity_id}`;
66592
- const target2 = `${hold.entity_id} --customer-id ${hold.account_id}`;
66593
- return hold.level === "asset_group" ? `baker ads google asset-groups update ${target2} --status ENABLED` : `baker ads google ${GOOGLE_GROUP[hold.level]} resume ${target2}`;
67954
+ const target4 = `${hold.entity_id} --customer-id ${hold.account_id}`;
67955
+ return hold.level === "asset_group" ? `baker ads google asset-groups update ${target4} --status ENABLED` : `baker ads google ${GOOGLE_GROUP[hold.level]} resume ${target4}`;
66594
67956
  }
66595
67957
  function quoted(title, fallback) {
66596
67958
  return title ? `\u201C${title}\u201D` : fallback;
@@ -66691,7 +68053,12 @@ registerSchema({
66691
68053
  full: fullArg
66692
68054
  }
66693
68055
  });
66694
- var promotionsCommand = defineCommand210({
68056
+ function promotionsShown(answer, full) {
68057
+ if (full) return answer;
68058
+ const { remembered: _remembered, ...shown } = answer;
68059
+ return shown;
68060
+ }
68061
+ var promotionsCommand = defineCommand213({
66695
68062
  meta: {
66696
68063
  name: "promotions",
66697
68064
  description: "What advertises one product: ads that lead to it, landings that link to it, Shopping that shows it. Example: baker shopify promotions 10310413942969"
@@ -66707,8 +68074,8 @@ var promotionsCommand = defineCommand210({
66707
68074
  },
66708
68075
  run: async ({ args: args5 }) => {
66709
68076
  try {
66710
- const positionals = Array.isArray(args5._) ? args5._ : [];
66711
- if (positionals.length > 1) {
68077
+ const positionals2 = Array.isArray(args5._) ? args5._ : [];
68078
+ if (positionals2.length > 1) {
66712
68079
  throw new ApiError("VALIDATION_ERROR", "product: Pass one product id, handle or product link.");
66713
68080
  }
66714
68081
  const { product, store } = await apiGet(
@@ -66718,6 +68085,7 @@ var promotionsCommand = defineCommand210({
66718
68085
  const scan = await readWorkspacePages(process.cwd());
66719
68086
  const linked = scan ? landingLinksOf(scan.pages, {
66720
68087
  handle: product.handle,
68088
+ productId: product.productId,
66721
68089
  variantIds: product.variants.map((variant) => variant.variantId),
66722
68090
  buyUrls: product.variants.flatMap((variant) => variant.buyUrl ? [variant.buyUrl] : [])
66723
68091
  }) : void 0;
@@ -66727,7 +68095,7 @@ var promotionsCommand = defineCommand210({
66727
68095
  scanned: scan !== null,
66728
68096
  ...args5.full ? { full: true } : {}
66729
68097
  });
66730
- emit2(answer, { store }, [
68098
+ emit2(promotionsShown(answer, args5.full === true), { store }, [
66731
68099
  ...storeStateHints(store),
66732
68100
  ...promotionHints(answer, { scanned: scan !== null, skippedPages: scan?.skipped ?? 0 })
66733
68101
  ]);
@@ -66736,7 +68104,7 @@ var promotionsCommand = defineCommand210({
66736
68104
  }
66737
68105
  }
66738
68106
  });
66739
- var stockHoldsCommand = defineCommand210({
68107
+ var stockHoldsCommand = defineCommand213({
66740
68108
  meta: {
66741
68109
  name: "stock-holds",
66742
68110
  description: "The ads Baker paused because a product ran out, and whether each may be resumed now. Example: baker shopify stock-holds --product 10310413942969"
@@ -66763,10 +68131,121 @@ var stockHoldsCommand = defineCommand210({
66763
68131
  }
66764
68132
  });
66765
68133
 
68134
+ // ../api/src/shopify/signups.ts
68135
+ import { z as z53 } from "zod";
68136
+ var SIGNUP_CODE_PATTERN = /^[A-Z0-9]{2,12}-[0-9A-HJKMNP-TV-Z]{8}$/;
68137
+ var SIGNUP_STATES = [
68138
+ "waiting",
68139
+ "ready",
68140
+ "used",
68141
+ "expired",
68142
+ "already",
68143
+ "ended",
68144
+ "none",
68145
+ "unavailable"
68146
+ ];
68147
+ var CODE_STATES = /* @__PURE__ */ new Set(["ready", "used", "expired"]);
68148
+ var shopifySignupReplySchema = z53.object({
68149
+ state: z53.enum(SIGNUP_STATES),
68150
+ code: z53.string().regex(SIGNUP_CODE_PATTERN).optional(),
68151
+ endsAt: z53.string().min(1).max(40).optional()
68152
+ }).strict().refine(
68153
+ (reply) => CODE_STATES.has(reply.state) ? reply.code !== void 0 && reply.endsAt !== void 0 : true,
68154
+ "a code state carries the code and when it ends"
68155
+ ).refine(
68156
+ (reply) => CODE_STATES.has(reply.state) ? true : reply.code === void 0 && reply.endsAt === void 0,
68157
+ "only the browser the code was given to is told it"
68158
+ );
68159
+ var shopifySignupResponseSchema = z53.union([
68160
+ z53.object({ ok: z53.literal(true), signup: shopifySignupReplySchema }).passthrough(),
68161
+ z53.object({ error: z53.string(), signup: shopifySignupReplySchema }).passthrough()
68162
+ ]);
68163
+ var signupOfferStatusSchema = z53.enum(["active", "ended", "staged"]);
68164
+ var shopifySignupOfferViewSchema = z53.object({
68165
+ handle: z53.string(),
68166
+ name: z53.string(),
68167
+ /** "10% off everything, each code valid 14 days, at most 1,000 codes". */
68168
+ summary: z53.string(),
68169
+ status: signupOfferStatusSchema,
68170
+ validForDays: z53.number(),
68171
+ endsAt: z53.string().optional(),
68172
+ maxCodes: z53.number().optional(),
68173
+ codesGiven: z53.number()
68174
+ });
68175
+ var shopifySignupOffersResponseSchema = z53.object({
68176
+ /** The company has a store connection Baker can use (connected, not waiting on a reconnect). */
68177
+ connected: z53.boolean(),
68178
+ /** The store allows Baker to add customers (sign-ups work). */
68179
+ canAddCustomers: z53.boolean(),
68180
+ /** The store allows Baker to make discount codes (offers give codes). */
68181
+ canGiveCodes: z53.boolean(),
68182
+ offers: z53.array(shopifySignupOfferViewSchema),
68183
+ /** Sign-ups the store didn't take in the last 7 days: for a missing permission, and for anything else. */
68184
+ refusedLast7Days: z53.object({ permission: z53.number(), other: z53.number() })
68185
+ });
68186
+
68187
+ // src/commands/shopify/signupOffers.ts
68188
+ import { defineCommand as defineCommand214 } from "citty";
68189
+ var description = "Start here for landing sign-ups: published and this chat's staged offers, codes given and store permissions. Example: baker shopify signup-offers. Forms name an offer handle; terms are staged with baker shopify change signup-offer set.";
68190
+ registerSchema({ command: "shopify.signupOffers", description, args: {} });
68191
+ var signupOffersCommand = defineCommand214({
68192
+ meta: { name: "signup-offers", description },
68193
+ async run() {
68194
+ try {
68195
+ const data = shopifySignupOffersResponseSchema.parse(
68196
+ await apiGet("/api/shopify/signup-offers", params({ chatId: optionalChatId() }))
68197
+ );
68198
+ const hints2 = [];
68199
+ if (!data.canAddCustomers)
68200
+ hints2.push(
68201
+ 'Sign-ups cannot be added to the store yet: call request_connection with { platform: "shopify", reason: "Add sign-ups from your landing" }. Keep building the page; the Form works once allowed.'
68202
+ );
68203
+ if (!data.canGiveCodes)
68204
+ hints2.push(
68205
+ "Codes need the store to allow discounts: use request_connection for Shopify and keep building the page."
68206
+ );
68207
+ if (data.offers.some((offer) => offer.status === "ended"))
68208
+ hints2.push(
68209
+ "Forms naming ended offers still add customers but give no code: stage a new offer or remove offer from the Form."
68210
+ );
68211
+ if (data.offers.length === 0)
68212
+ hints2.push(
68213
+ "No sign-up offer exists yet. Omit offer for sign-ups without a code, or stage one after the user approves its terms."
68214
+ );
68215
+ writeJson({ ok: true, hints: hints2, data });
68216
+ } catch (error) {
68217
+ fail9(error);
68218
+ }
68219
+ }
68220
+ });
68221
+
68222
+ // src/commands/shopify/tracking.ts
68223
+ import { defineCommand as defineCommand215 } from "citty";
68224
+ var audit = leaf(
68225
+ "shopify.tracking.audit",
68226
+ {
68227
+ name: "audit",
68228
+ 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."
68229
+ },
68230
+ { full: { type: "boolean", description: "Add the facts behind each check", required: false } },
68231
+ async (args5) => {
68232
+ try {
68233
+ const data = await apiPost("/api/shopify/tracking/audit", { full: args5.full === true });
68234
+ writeJson({ ok: true, hints: auditHints(data), data });
68235
+ } catch (err) {
68236
+ fail9(err);
68237
+ }
68238
+ }
68239
+ );
68240
+ var trackingCommand2 = defineCommand215({
68241
+ meta: { name: "tracking", description: "Check how the store is measured: baker shopify tracking audit" },
68242
+ subCommands: { audit }
68243
+ });
68244
+
66766
68245
  // src/commands/shopify/index.ts
66767
68246
  registerSchema({
66768
68247
  command: "shopify.store",
66769
- 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.',
68248
+ 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`).',
66770
68249
  args: {
66771
68250
  full: {
66772
68251
  ...fullArg,
@@ -66801,7 +68280,7 @@ registerSchema({
66801
68280
  });
66802
68281
  registerSchema({
66803
68282
  command: "shopify.product",
66804
- 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.",
68283
+ 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.",
66805
68284
  args: {
66806
68285
  ref: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
66807
68286
  "variants-cursor": {
@@ -66891,7 +68370,7 @@ registerSchema({
66891
68370
  }
66892
68371
  }
66893
68372
  });
66894
- var salesCommand = defineCommand211({
68373
+ var salesCommand = defineCommand216({
66895
68374
  meta: {
66896
68375
  name: "sales",
66897
68376
  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"
@@ -66929,7 +68408,7 @@ var salesCommand = defineCommand211({
66929
68408
  }
66930
68409
  }
66931
68410
  });
66932
- var storeCommand = defineCommand211({
68411
+ var storeCommand = defineCommand216({
66933
68412
  meta: {
66934
68413
  name: "store",
66935
68414
  description: "The connected Shopify store: currency, countries, how many products, what is out of stock. Example: baker shopify store"
@@ -66940,13 +68419,14 @@ var storeCommand = defineCommand211({
66940
68419
  run: async ({ args: args5 }) => {
66941
68420
  try {
66942
68421
  const answer = await apiGet("/api/shopify/store", params({ full: args5.full }));
66943
- emit2(answer.summary, { store: answer.store }, [...storeStateHints(answer.store), ...storeHints(answer.summary)]);
68422
+ const data = answer.canChange ? { ...answer.summary, canChange: answer.canChange } : answer.summary;
68423
+ emit2(data, { store: answer.store }, [...storeStateHints(answer.store), ...storeHints(answer.summary)]);
66944
68424
  } catch (err) {
66945
68425
  fail9(err);
66946
68426
  }
66947
68427
  }
66948
68428
  });
66949
- var productsCommand = defineCommand211({
68429
+ var productsCommand = defineCommand216({
66950
68430
  meta: {
66951
68431
  name: "products",
66952
68432
  description: 'What the store sells, with price and stock. Example: baker shopify products --search "snowboard" --stock out_of_stock'
@@ -66992,7 +68472,7 @@ var productsCommand = defineCommand211({
66992
68472
  }
66993
68473
  }
66994
68474
  });
66995
- var productCommand = defineCommand211({
68475
+ var productCommand = defineCommand216({
66996
68476
  meta: {
66997
68477
  name: "product",
66998
68478
  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"
@@ -67026,8 +68506,8 @@ var productCommand = defineCommand211({
67026
68506
  },
67027
68507
  run: async ({ args: args5 }) => {
67028
68508
  try {
67029
- const positionals = Array.isArray(args5._) ? args5._ : [];
67030
- if (positionals.length > 1) {
68509
+ const positionals2 = Array.isArray(args5._) ? args5._ : [];
68510
+ if (positionals2.length > 1) {
67031
68511
  throw new ApiError(
67032
68512
  "VALIDATION_ERROR",
67033
68513
  "ref: Pass one product id, handle or product link \u2014 quote a link, and search by words with `baker shopify products --search`."
@@ -67055,7 +68535,7 @@ var productCommand = defineCommand211({
67055
68535
  }
67056
68536
  }
67057
68537
  });
67058
- var collectionsCommand = defineCommand211({
68538
+ var collectionsCommand = defineCommand216({
67059
68539
  meta: {
67060
68540
  name: "collections",
67061
68541
  description: "The store's collections, with the handles --collection takes. Example: baker shopify collections --full"
@@ -67080,7 +68560,7 @@ var collectionsCommand = defineCommand211({
67080
68560
  }
67081
68561
  }
67082
68562
  });
67083
- var stockCommand2 = defineCommand211({
68563
+ var stockCommand2 = defineCommand216({
67084
68564
  meta: {
67085
68565
  name: "stock",
67086
68566
  description: "Every product with options out of stock, naming them. Example: baker shopify stock"
@@ -67104,12 +68584,12 @@ var stockCommand2 = defineCommand211({
67104
68584
  }
67105
68585
  }
67106
68586
  });
67107
- var shopifyCommand = defineCommand211({
68587
+ var shopifyCommand = defineCommand216({
67108
68588
  meta: {
67109
68589
  name: "shopify",
67110
68590
  description: `Read the connected Shopify store as Baker keeps it \u2014 products, prices by country, stock, collections,
67111
- product photos, what it sold, who buys, where and what, and what advertises each product. Read-only:
67112
- nothing here changes the store.
68591
+ product photos, what it sold, who buys, where and what, and what advertises each product \u2014 and stage
68592
+ changes to it (\`change\`), applied when the user publishes the chat and undone with \`undo\`.
67113
68593
 
67114
68594
  Examples:
67115
68595
  baker shopify store # currency, countries, how many products, what is out of stock
@@ -67133,17 +68613,22 @@ Examples:
67133
68613
  baker shopify cohorts --months 12 # buyers by first-order month: who came back
67134
68614
  baker shopify promotions <product> # what advertises it: ads, landings, Shopping (read before pausing)
67135
68615
  baker shopify stock-holds --product <product> # what Baker paused because it ran out: what to resume, what to tell the user
68616
+ baker shopify change product update <product> --title "\u2026" # stage a store change (product|variant|field|collection|discount|redirect|campaign|measurement)
68617
+ baker shopify draft list # the store changes staged on this chat
68618
+ baker shopify changes # what Baker changed in the store; baker shopify undo <changeId> puts one back
68619
+ baker shopify tracking audit # is the store measured right? (reads only)
67136
68620
 
67137
68621
  Product photos are already in the image library: place them by imageId
67138
68622
  (baker images download <imageIds> --out src/pages/<page>/_images/), never by a cdn.shopify.com link.
67139
68623
  Product videos are in the video library: place them with <Video playbackId="\u2026" />.
67140
- A page's buy button uses the option's buyUrl, one button per option \u2014 never build a Shopify link
67141
- yourself, and never use storeUrl (a page to look at) for it.
68624
+ On a landing, the buy button is <ShopifyProduct product="\u2026"/> \u2014 never a buyUrl or a Shopify link
68625
+ written by hand. buyUrl is for ads, emails and messages; never use storeUrl (a page to look at) for it.
67142
68626
  NO_SHOPIFY_CONNECTION means no store is connected: follow error.fix. SHOPIFY_NOT_AVAILABLE means it cannot be
67143
68627
  connected here yet: carry on without it.
67144
68628
  Full guide: __tooling__/docs/tools/baker/shopify.md`
67145
68629
  },
67146
68630
  subCommands: {
68631
+ "signup-offers": signupOffersCommand,
67147
68632
  store: storeCommand,
67148
68633
  products: productsCommand,
67149
68634
  product: productCommand,
@@ -67157,15 +68642,20 @@ Full guide: __tooling__/docs/tools/baker/shopify.md`
67157
68642
  cohorts: cohortsCommand,
67158
68643
  segments: segmentsCommand,
67159
68644
  promotions: promotionsCommand,
67160
- "stock-holds": stockHoldsCommand
68645
+ "stock-holds": stockHoldsCommand,
68646
+ change: changeCommand,
68647
+ draft: draftCommand5,
68648
+ changes: changesCommand2,
68649
+ undo: undoCommand,
68650
+ tracking: trackingCommand2
67161
68651
  }
67162
68652
  });
67163
68653
 
67164
68654
  // src/commands/studio/index.ts
67165
- import { defineCommand as defineCommand220 } from "citty";
68655
+ import { defineCommand as defineCommand225 } from "citty";
67166
68656
 
67167
68657
  // src/commands/studio/animate.ts
67168
- import { defineCommand as defineCommand212 } from "citty";
68658
+ import { defineCommand as defineCommand217 } from "citty";
67169
68659
 
67170
68660
  // src/commands/studio/spokenPace.ts
67171
68661
  var WORDS_PER_SECOND3 = SPOKEN_WORDS_PER_SECOND;
@@ -67678,7 +69168,7 @@ async function adByHandHintFor(body) {
67678
69168
  return null;
67679
69169
  }
67680
69170
  }
67681
- var animateCommand = defineCommand212({
69171
+ var animateCommand = defineCommand217({
67682
69172
  meta: {
67683
69173
  name: "animate",
67684
69174
  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"
@@ -67803,7 +69293,7 @@ var animateCommand = defineCommand212({
67803
69293
  });
67804
69294
 
67805
69295
  // src/commands/studio/generate.ts
67806
- import { defineCommand as defineCommand213 } from "citty";
69296
+ import { defineCommand as defineCommand218 } from "citty";
67807
69297
  var MODEL_LIST2 = IMAGE_MODEL_IDS;
67808
69298
  var DEFAULT_MAX_WAIT_MS2 = 24e4;
67809
69299
  registerSchema({
@@ -67939,7 +69429,7 @@ function buildGenerateBody(args5, prompt) {
67939
69429
  }
67940
69430
  return body;
67941
69431
  }
67942
- var generateCommand = defineCommand213({
69432
+ var generateCommand = defineCommand218({
67943
69433
  meta: {
67944
69434
  name: "generate",
67945
69435
  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]]'"
@@ -68022,7 +69512,7 @@ var generateCommand = defineCommand213({
68022
69512
  });
68023
69513
 
68024
69514
  // src/commands/studio/get.ts
68025
- import { defineCommand as defineCommand214 } from "citty";
69515
+ import { defineCommand as defineCommand219 } from "citty";
68026
69516
  registerSchema({
68027
69517
  command: "studio.get",
68028
69518
  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.",
@@ -68031,7 +69521,7 @@ registerSchema({
68031
69521
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
68032
69522
  }
68033
69523
  });
68034
- var getCommand5 = defineCommand214({
69524
+ var getCommand5 = defineCommand219({
68035
69525
  meta: {
68036
69526
  name: "get",
68037
69527
  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"
@@ -68060,7 +69550,7 @@ var getCommand5 = defineCommand214({
68060
69550
  });
68061
69551
 
68062
69552
  // src/commands/studio/improve.ts
68063
- import { defineCommand as defineCommand215 } from "citty";
69553
+ import { defineCommand as defineCommand220 } from "citty";
68064
69554
  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.";
68065
69555
  registerSchema({
68066
69556
  command: "studio.improve",
@@ -68085,7 +69575,7 @@ registerSchema({
68085
69575
  }
68086
69576
  }
68087
69577
  });
68088
- var improveCommand = defineCommand215({
69578
+ var improveCommand = defineCommand220({
68089
69579
  meta: {
68090
69580
  name: "improve",
68091
69581
  description: `${DESCRIPTION2}
@@ -68129,7 +69619,7 @@ Examples:
68129
69619
  });
68130
69620
 
68131
69621
  // src/commands/studio/keep.ts
68132
- import { defineCommand as defineCommand216 } from "citty";
69622
+ import { defineCommand as defineCommand221 } from "citty";
68133
69623
  registerSchema({
68134
69624
  command: "studio.keep",
68135
69625
  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.",
@@ -68139,7 +69629,7 @@ registerSchema({
68139
69629
  undo: { type: "boolean", description: "Un-star an image, or take a kept clip back out", required: false }
68140
69630
  }
68141
69631
  });
68142
- var keepCommand = defineCommand216({
69632
+ var keepCommand = defineCommand221({
68143
69633
  meta: {
68144
69634
  name: "keep",
68145
69635
  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"
@@ -68190,7 +69680,7 @@ var keepCommand = defineCommand216({
68190
69680
  });
68191
69681
 
68192
69682
  // src/commands/studio/list.ts
68193
- import { defineCommand as defineCommand217 } from "citty";
69683
+ import { defineCommand as defineCommand222 } from "citty";
68194
69684
  registerSchema({
68195
69685
  command: "studio.list",
68196
69686
  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.",
@@ -68201,7 +69691,7 @@ registerSchema({
68201
69691
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
68202
69692
  }
68203
69693
  });
68204
- var listCommand18 = defineCommand217({
69694
+ var listCommand18 = defineCommand222({
68205
69695
  meta: {
68206
69696
  name: "list",
68207
69697
  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"
@@ -68235,7 +69725,7 @@ var listCommand18 = defineCommand217({
68235
69725
  });
68236
69726
 
68237
69727
  // src/commands/studio/models.ts
68238
- import { defineCommand as defineCommand218 } from "citty";
69728
+ import { defineCommand as defineCommand223 } from "citty";
68239
69729
  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.";
68240
69730
  registerSchema({
68241
69731
  command: "studio.models",
@@ -68322,7 +69812,7 @@ function buildModelCards(kind, model) {
68322
69812
  const selected = model ? ids.filter((id) => id === model) : ids;
68323
69813
  return selected.map((id) => build(id));
68324
69814
  }
68325
- var modelsCommand = defineCommand218({
69815
+ var modelsCommand = defineCommand223({
68326
69816
  meta: {
68327
69817
  name: "models",
68328
69818
  description: `${DESCRIPTION3}
@@ -68369,13 +69859,13 @@ Examples:
68369
69859
  });
68370
69860
 
68371
69861
  // src/commands/studio/skills.ts
68372
- import { defineCommand as defineCommand219 } from "citty";
69862
+ import { defineCommand as defineCommand224 } from "citty";
68373
69863
  registerSchema({
68374
69864
  command: "studio.skills",
68375
69865
  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.",
68376
69866
  args: {}
68377
69867
  });
68378
- var skillsCommand = defineCommand219({
69868
+ var skillsCommand = defineCommand224({
68379
69869
  meta: {
68380
69870
  name: "skills",
68381
69871
  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"
@@ -68399,7 +69889,7 @@ var skillsCommand = defineCommand219({
68399
69889
  });
68400
69890
 
68401
69891
  // src/commands/studio/index.ts
68402
- var studioCommand = defineCommand220({
69892
+ var studioCommand = defineCommand225({
68403
69893
  meta: {
68404
69894
  name: "studio",
68405
69895
  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.
@@ -68442,10 +69932,10 @@ Full guide: __tooling__/docs/tools/baker/studio.md`
68442
69932
  });
68443
69933
 
68444
69934
  // src/commands/tag-manager/index.ts
68445
- import { defineCommand as defineCommand224 } from "citty";
69935
+ import { defineCommand as defineCommand229 } from "citty";
68446
69936
 
68447
69937
  // src/commands/tag-manager/draft.ts
68448
- import { defineCommand as defineCommand221 } from "citty";
69938
+ import { defineCommand as defineCommand226 } from "citty";
68449
69939
 
68450
69940
  // src/commands/tag-manager/shared.ts
68451
69941
  import { readFileSync as readFileSync20 } from "fs";
@@ -68455,11 +69945,11 @@ function failValidation5(message) {
68455
69945
  }
68456
69946
  function requireTarget6(args5, entity) {
68457
69947
  const positional = Array.isArray(args5._) ? args5._[0] : void 0;
68458
- const target2 = args5.id ?? args5.target ?? positional;
68459
- if (typeof target2 !== "string" || target2.length === 0) {
69948
+ const target4 = args5.id ?? args5.target ?? positional;
69949
+ if (typeof target4 !== "string" || target4.length === 0) {
68460
69950
  failValidation5(`pass the ${entity} id or path as the positional argument`);
68461
69951
  }
68462
- return target2;
69952
+ return target4;
68463
69953
  }
68464
69954
  function loadJsonArg2(args5, flag3 = "json") {
68465
69955
  const inline = args5[flag3];
@@ -68568,13 +70058,13 @@ registerSchema({
68568
70058
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
68569
70059
  }
68570
70060
  });
68571
- var draftCommand5 = defineCommand221({
70061
+ var draftCommand6 = defineCommand226({
68572
70062
  meta: {
68573
70063
  name: "draft",
68574
70064
  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."
68575
70065
  },
68576
70066
  subCommands: {
68577
- list: defineCommand221({
70067
+ list: defineCommand226({
68578
70068
  meta: {
68579
70069
  name: "list",
68580
70070
  description: "Review everything staged on this chat (--json for the raw envelope)"
@@ -68587,7 +70077,7 @@ var draftCommand5 = defineCommand221({
68587
70077
  await draftList2(args5.json === true, args5.chat);
68588
70078
  }
68589
70079
  }),
68590
- show: defineCommand221({
70080
+ show: defineCommand226({
68591
70081
  meta: {
68592
70082
  name: "show",
68593
70083
  description: "Print the full staged payload for one change \u2014 the receipt to verify it looks right before publish (never truncated)."
@@ -68604,7 +70094,7 @@ var draftCommand5 = defineCommand221({
68604
70094
  );
68605
70095
  }
68606
70096
  }),
68607
- amend: defineCommand221({
70097
+ amend: defineCommand226({
68608
70098
  meta: {
68609
70099
  name: "amend",
68610
70100
  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."
@@ -68621,7 +70111,7 @@ var draftCommand5 = defineCommand221({
68621
70111
  });
68622
70112
  }
68623
70113
  }),
68624
- remove: defineCommand221({
70114
+ remove: defineCommand226({
68625
70115
  meta: { name: "remove", description: "Remove one staged change (cascades to anything depending on it)" },
68626
70116
  args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
68627
70117
  run: async ({ args: args5 }) => {
@@ -68630,7 +70120,7 @@ var draftCommand5 = defineCommand221({
68630
70120
  });
68631
70121
  }
68632
70122
  }),
68633
- clear: defineCommand221({
70123
+ clear: defineCommand226({
68634
70124
  meta: { name: "clear", description: "Discard all Tag Manager changes staged on this chat" },
68635
70125
  run: async () => {
68636
70126
  await draftAction3("/api/tag-manager/draft/clear", {});
@@ -68640,7 +70130,7 @@ var draftCommand5 = defineCommand221({
68640
70130
  });
68641
70131
 
68642
70132
  // src/commands/tag-manager/read.ts
68643
- import { defineCommand as defineCommand222 } from "citty";
70133
+ import { defineCommand as defineCommand227 } from "citty";
68644
70134
  registerSchema({
68645
70135
  command: "tagManager.containers",
68646
70136
  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.",
@@ -68681,7 +70171,7 @@ function containersHints(containers) {
68681
70171
  }))
68682
70172
  });
68683
70173
  }
68684
- var containersCommand = defineCommand222({
70174
+ var containersCommand = defineCommand227({
68685
70175
  meta: {
68686
70176
  name: "containers",
68687
70177
  description: `List Tag Manager containers reachable by this company's connection.
@@ -68698,7 +70188,7 @@ Start here:
68698
70188
  }
68699
70189
  }
68700
70190
  });
68701
- var readCommand = defineCommand222({
70191
+ var readCommand = defineCommand227({
68702
70192
  meta: {
68703
70193
  name: "read",
68704
70194
  description: `Read the current contents of the Tag Manager container \u2014 always do this before staging changes.
@@ -68740,7 +70230,7 @@ Examples:
68740
70230
  });
68741
70231
 
68742
70232
  // src/commands/tag-manager/write-commands.ts
68743
- import { defineCommand as defineCommand223 } from "citty";
70233
+ import { defineCommand as defineCommand228 } from "citty";
68744
70234
  var CONTAINER_ARG_DESCRIPTION = "Numeric container id (optional only when one container is connected \u2014 run `baker tag-manager containers`)";
68745
70235
  var ENTITIES = [
68746
70236
  {
@@ -68796,10 +70286,10 @@ for (const { entity, noun, createHint } of ENTITIES) {
68796
70286
  });
68797
70287
  }
68798
70288
  function entityCommand(entity, noun, example) {
68799
- return defineCommand223({
70289
+ return defineCommand228({
68800
70290
  meta: { name: entity, description: `Stage ${noun} changes on this chat's Tag Manager draft` },
68801
70291
  subCommands: {
68802
- create: defineCommand223({
70292
+ create: defineCommand228({
68803
70293
  meta: {
68804
70294
  name: "create",
68805
70295
  description: `Stage a new ${noun}
@@ -68821,7 +70311,7 @@ Examples:
68821
70311
  });
68822
70312
  }
68823
70313
  }),
68824
- update: defineCommand223({
70314
+ update: defineCommand228({
68825
70315
  meta: {
68826
70316
  name: "update",
68827
70317
  description: `Stage an update to an existing ${noun} (pass its id or path)`
@@ -68841,7 +70331,7 @@ Examples:
68841
70331
  });
68842
70332
  }
68843
70333
  }),
68844
- delete: defineCommand223({
70334
+ delete: defineCommand228({
68845
70335
  meta: { name: "delete", description: `Stage the deletion of a ${noun} (pass its id or path)` },
68846
70336
  args: {
68847
70337
  id: { type: "positional", description: `${noun} id or path`, required: false },
@@ -68882,7 +70372,7 @@ function builtinTypes(args5) {
68882
70372
  }
68883
70373
  return raw.split(",").map((entry) => entry.trim());
68884
70374
  }
68885
- var builtinCommand = defineCommand223({
70375
+ var builtinCommand = defineCommand228({
68886
70376
  meta: {
68887
70377
  name: "builtin",
68888
70378
  description: `Enable or disable built-in variables
@@ -68892,7 +70382,7 @@ Examples:
68892
70382
  baker tag-manager builtin disable --types formId`
68893
70383
  },
68894
70384
  subCommands: {
68895
- enable: defineCommand223({
70385
+ enable: defineCommand228({
68896
70386
  meta: { name: "enable", description: "Stage enabling built-in variables" },
68897
70387
  args: {
68898
70388
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -68906,7 +70396,7 @@ Examples:
68906
70396
  });
68907
70397
  }
68908
70398
  }),
68909
- disable: defineCommand223({
70399
+ disable: defineCommand228({
68910
70400
  meta: { name: "disable", description: "Stage disabling built-in variables" },
68911
70401
  args: {
68912
70402
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -68924,7 +70414,7 @@ Examples:
68924
70414
  });
68925
70415
 
68926
70416
  // src/commands/tag-manager/index.ts
68927
- var tagManagerCommand = defineCommand224({
70417
+ var tagManagerCommand = defineCommand229({
68928
70418
  meta: {
68929
70419
  name: "tag-manager",
68930
70420
  description: `Read and change what lives inside the client's Google Tag Manager container \u2014 tags, triggers, variables, folders and built-in variables.
@@ -68956,12 +70446,12 @@ Full guide: __tooling__/docs/tools/baker/tag-manager.md`
68956
70446
  variable: variableCommand,
68957
70447
  folder: folderCommand,
68958
70448
  builtin: builtinCommand,
68959
- draft: draftCommand5
70449
+ draft: draftCommand6
68960
70450
  }
68961
70451
  });
68962
70452
 
68963
70453
  // src/commands/tags/index.ts
68964
- import { defineCommand as defineCommand225 } from "citty";
70454
+ import { defineCommand as defineCommand230 } from "citty";
68965
70455
 
68966
70456
  // src/commands/tags/shared.ts
68967
70457
  function failApi3(err) {
@@ -69041,7 +70531,7 @@ async function listTags(json) {
69041
70531
  var listArgs11 = {
69042
70532
  json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list" }
69043
70533
  };
69044
- var listCommand19 = defineCommand225({
70534
+ var listCommand19 = defineCommand230({
69045
70535
  meta: {
69046
70536
  name: "list",
69047
70537
  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"
@@ -69060,7 +70550,7 @@ async function listDraft4(chat) {
69060
70550
  failApi3(err);
69061
70551
  }
69062
70552
  }
69063
- var draftCommand6 = defineCommand225({
70553
+ var draftCommand7 = defineCommand230({
69064
70554
  meta: {
69065
70555
  name: "draft",
69066
70556
  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."
@@ -69070,7 +70560,7 @@ var draftCommand6 = defineCommand225({
69070
70560
  await listDraft4(args5.chat);
69071
70561
  }
69072
70562
  });
69073
- var tagsCommand4 = defineCommand225({
70563
+ var tagsCommand4 = defineCommand230({
69074
70564
  meta: {
69075
70565
  name: "tags",
69076
70566
  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.
@@ -69088,7 +70578,7 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
69088
70578
  },
69089
70579
  subCommands: {
69090
70580
  list: listCommand19,
69091
- draft: draftCommand6
70581
+ draft: draftCommand7
69092
70582
  },
69093
70583
  // Bare `baker tags` lists the effective tags. `args` stays so citty knows this
69094
70584
  // command's flags when it scans rawArgs for a subcommand name, and `default` (not
@@ -69099,10 +70589,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
69099
70589
  });
69100
70590
 
69101
70591
  // src/commands/testimonials/index.ts
69102
- import { defineCommand as defineCommand229 } from "citty";
70592
+ import { defineCommand as defineCommand234 } from "citty";
69103
70593
 
69104
70594
  // src/commands/testimonials/get.ts
69105
- import { defineCommand as defineCommand226 } from "citty";
70595
+ import { defineCommand as defineCommand231 } from "citty";
69106
70596
  registerSchema({
69107
70597
  command: "testimonials.get",
69108
70598
  description: "Get a single testimonial by ID",
@@ -69110,7 +70600,7 @@ registerSchema({
69110
70600
  id: { type: "string", description: "Testimonial ID", required: true }
69111
70601
  }
69112
70602
  });
69113
- var getCommand6 = defineCommand226({
70603
+ var getCommand6 = defineCommand231({
69114
70604
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
69115
70605
  args: {
69116
70606
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -69147,7 +70637,7 @@ var getCommand6 = defineCommand226({
69147
70637
  });
69148
70638
 
69149
70639
  // src/commands/testimonials/list.ts
69150
- import { defineCommand as defineCommand227 } from "citty";
70640
+ import { defineCommand as defineCommand232 } from "citty";
69151
70641
 
69152
70642
  // src/commands/testimonials/emptyCorpusHints.ts
69153
70643
  function resolveEmptyReason({
@@ -69304,7 +70794,7 @@ function buildListParams(args5) {
69304
70794
  }
69305
70795
  return params2;
69306
70796
  }
69307
- var listCommand20 = defineCommand227({
70797
+ var listCommand20 = defineCommand232({
69308
70798
  meta: {
69309
70799
  name: "list",
69310
70800
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -69364,7 +70854,7 @@ var listCommand20 = defineCommand227({
69364
70854
  });
69365
70855
 
69366
70856
  // src/commands/testimonials/search.ts
69367
- import { defineCommand as defineCommand228 } from "citty";
70857
+ import { defineCommand as defineCommand233 } from "citty";
69368
70858
  var FILTER_FLAGS2 = ["source", "rating-min", "rating-max", "status", "sentiment", "language", "tags"];
69369
70859
  function languageBiasHint(results, requestedLanguage) {
69370
70860
  if (requestedLanguage) {
@@ -69443,7 +70933,7 @@ function buildSearchRequest(query, args5) {
69443
70933
  }
69444
70934
  return body;
69445
70935
  }
69446
- var searchCommand3 = defineCommand228({
70936
+ var searchCommand3 = defineCommand233({
69447
70937
  meta: {
69448
70938
  name: "search",
69449
70939
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -69507,7 +70997,7 @@ var searchCommand3 = defineCommand228({
69507
70997
  var tagsCommand5 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
69508
70998
 
69509
70999
  // src/commands/testimonials/index.ts
69510
- var testimonialsCommand = defineCommand229({
71000
+ var testimonialsCommand = defineCommand234({
69511
71001
  meta: {
69512
71002
  name: "testimonials",
69513
71003
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -69529,10 +71019,10 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
69529
71019
  });
69530
71020
 
69531
71021
  // src/commands/videos/index.ts
69532
- import { defineCommand as defineCommand237 } from "citty";
71022
+ import { defineCommand as defineCommand242 } from "citty";
69533
71023
 
69534
71024
  // src/commands/videos/delete.ts
69535
- import { defineCommand as defineCommand230 } from "citty";
71025
+ import { defineCommand as defineCommand235 } from "citty";
69536
71026
  registerSchema({
69537
71027
  command: "videos.delete",
69538
71028
  description: "Delete a video by ID",
@@ -69546,7 +71036,7 @@ registerSchema({
69546
71036
  }
69547
71037
  }
69548
71038
  });
69549
- var deleteCommand4 = defineCommand230({
71039
+ var deleteCommand4 = defineCommand235({
69550
71040
  meta: {
69551
71041
  name: "delete",
69552
71042
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -69587,7 +71077,7 @@ var deleteCommand4 = defineCommand230({
69587
71077
  });
69588
71078
 
69589
71079
  // src/commands/videos/describe.ts
69590
- import { defineCommand as defineCommand231 } from "citty";
71080
+ import { defineCommand as defineCommand236 } from "citty";
69591
71081
  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.";
69592
71082
  registerSchema({
69593
71083
  command: "videos.describe",
@@ -69604,7 +71094,7 @@ registerSchema({
69604
71094
  full: { type: "boolean", description: "Return the whole library row, not just what changed", required: false }
69605
71095
  }
69606
71096
  });
69607
- var describeCommand2 = defineCommand231({
71097
+ var describeCommand2 = defineCommand236({
69608
71098
  meta: {
69609
71099
  name: "describe",
69610
71100
  description: `Correct what the library says a video is. Only the fields you pass change; the rest keep their current values.
@@ -69662,7 +71152,7 @@ Example: baker videos describe j571abc123 --description "Customer explaining how
69662
71152
  });
69663
71153
 
69664
71154
  // src/commands/videos/get.ts
69665
- import { defineCommand as defineCommand232 } from "citty";
71155
+ import { defineCommand as defineCommand237 } from "citty";
69666
71156
  registerSchema({
69667
71157
  command: "videos.get",
69668
71158
  description: "Get a single video by ID",
@@ -69670,7 +71160,7 @@ registerSchema({
69670
71160
  id: { type: "string", description: "Video ID", required: true }
69671
71161
  }
69672
71162
  });
69673
- var getCommand7 = defineCommand232({
71163
+ var getCommand7 = defineCommand237({
69674
71164
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
69675
71165
  args: {
69676
71166
  id: { type: "positional", description: "Video ID", required: false },
@@ -69707,7 +71197,7 @@ var getCommand7 = defineCommand232({
69707
71197
  });
69708
71198
 
69709
71199
  // src/commands/videos/group.ts
69710
- import { defineCommand as defineCommand233 } from "citty";
71200
+ import { defineCommand as defineCommand238 } from "citty";
69711
71201
  registerSchema({
69712
71202
  command: "videos.group",
69713
71203
  description: "List every clip and image that arrived in the same set as this video (carousel slides, one page)",
@@ -69716,7 +71206,7 @@ registerSchema({
69716
71206
  "group-key": { type: "string", description: "The set key directly, when you already have it", required: false }
69717
71207
  }
69718
71208
  });
69719
- var groupCommand2 = defineCommand233({
71209
+ var groupCommand2 = defineCommand238({
69720
71210
  meta: {
69721
71211
  name: "group",
69722
71212
  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>"
@@ -69739,7 +71229,7 @@ var groupCommand2 = defineCommand233({
69739
71229
  import { mkdtemp as mkdtemp3, rm as rm9, stat as stat12 } from "fs/promises";
69740
71230
  import { tmpdir as tmpdir4 } from "os";
69741
71231
  import path48 from "path";
69742
- import { defineCommand as defineCommand234 } from "citty";
71232
+ import { defineCommand as defineCommand239 } from "citty";
69743
71233
 
69744
71234
  // src/lib/streamUpload.ts
69745
71235
  import { createHash as createHash2 } from "crypto";
@@ -69903,7 +71393,7 @@ registerSchema({
69903
71393
  "dry-run": { type: "boolean", description: "Preview the operation without executing", required: false }
69904
71394
  }
69905
71395
  });
69906
- var ingestCommand2 = defineCommand234({
71396
+ var ingestCommand2 = defineCommand239({
69907
71397
  meta: {
69908
71398
  name: "ingest",
69909
71399
  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"
@@ -70165,7 +71655,7 @@ async function uploadToAssetStore(filePath, sizeBytes) {
70165
71655
  }
70166
71656
 
70167
71657
  // src/commands/videos/search.ts
70168
- import { defineCommand as defineCommand235 } from "citty";
71658
+ import { defineCommand as defineCommand240 } from "citty";
70169
71659
  registerSchema({
70170
71660
  command: "videos.search",
70171
71661
  description: "Search videos by text query. Only returns ready videos.",
@@ -70175,7 +71665,7 @@ registerSchema({
70175
71665
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
70176
71666
  }
70177
71667
  });
70178
- var searchCommand4 = defineCommand235({
71668
+ var searchCommand4 = defineCommand240({
70179
71669
  meta: {
70180
71670
  name: "search",
70181
71671
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -70227,7 +71717,7 @@ var tagsCommand6 = makeTagsCommand("videos", "video", "/api/videos/tags");
70227
71717
  // src/commands/videos/upload.ts
70228
71718
  import { readFile as readFile34, stat as stat13 } from "fs/promises";
70229
71719
  import { basename as basename3, extname as extname4 } from "path";
70230
- import { defineCommand as defineCommand236 } from "citty";
71720
+ import { defineCommand as defineCommand241 } from "citty";
70231
71721
  var MIME_MAP = {
70232
71722
  ".mp4": "video/mp4",
70233
71723
  ".mov": "video/quicktime",
@@ -70269,7 +71759,7 @@ function detectContentType(filePath) {
70269
71759
  function isRemoteUrl3(value) {
70270
71760
  return /^https?:\/\//i.test(value);
70271
71761
  }
70272
- var uploadCommand2 = defineCommand236({
71762
+ var uploadCommand2 = defineCommand241({
70273
71763
  meta: {
70274
71764
  name: "upload",
70275
71765
  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"
@@ -70353,7 +71843,7 @@ var uploadCommand2 = defineCommand236({
70353
71843
  });
70354
71844
 
70355
71845
  // src/commands/videos/index.ts
70356
- var videosCommand = defineCommand237({
71846
+ var videosCommand = defineCommand242({
70357
71847
  meta: {
70358
71848
  name: "videos",
70359
71849
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, ingest, describe, delete, tags.
@@ -70382,10 +71872,10 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
70382
71872
  });
70383
71873
 
70384
71874
  // src/commands/winning-ads/index.ts
70385
- import { defineCommand as defineCommand250 } from "citty";
71875
+ import { defineCommand as defineCommand255 } from "citty";
70386
71876
 
70387
71877
  // src/commands/winning-ads/advertisers.ts
70388
- import { defineCommand as defineCommand238 } from "citty";
71878
+ import { defineCommand as defineCommand243 } from "citty";
70389
71879
 
70390
71880
  // src/commands/winning-ads/shared.ts
70391
71881
  function splitList3(value) {
@@ -70438,7 +71928,7 @@ function advertiserNormalizer(record, full) {
70438
71928
  last_synced_at: record.last_synced_at ?? null
70439
71929
  };
70440
71930
  }
70441
- var advertisersCommand2 = defineCommand238({
71931
+ var advertisersCommand2 = defineCommand243({
70442
71932
  meta: {
70443
71933
  name: "advertisers",
70444
71934
  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'
@@ -70496,7 +71986,7 @@ var advertisersCommand2 = defineCommand238({
70496
71986
  });
70497
71987
 
70498
71988
  // src/commands/winning-ads/brief.ts
70499
- import { defineCommand as defineCommand239 } from "citty";
71989
+ import { defineCommand as defineCommand244 } from "citty";
70500
71990
  registerSchema({
70501
71991
  command: "winning-ads.brief",
70502
71992
  description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
@@ -70542,7 +72032,7 @@ function parseDna(raw) {
70542
72032
  }
70543
72033
  return parsed;
70544
72034
  }
70545
- var briefCommand = defineCommand239({
72035
+ var briefCommand = defineCommand244({
70546
72036
  meta: {
70547
72037
  name: "brief",
70548
72038
  description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
@@ -70578,7 +72068,7 @@ var briefCommand = defineCommand239({
70578
72068
  });
70579
72069
 
70580
72070
  // src/commands/winning-ads/content.ts
70581
- import { defineCommand as defineCommand240 } from "citty";
72071
+ import { defineCommand as defineCommand245 } from "citty";
70582
72072
  registerSchema({
70583
72073
  command: "winning-ads.content",
70584
72074
  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.",
@@ -70591,7 +72081,7 @@ registerSchema({
70591
72081
  }
70592
72082
  }
70593
72083
  });
70594
- var contentCommand = defineCommand240({
72084
+ var contentCommand = defineCommand245({
70595
72085
  meta: {
70596
72086
  name: "content",
70597
72087
  description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
@@ -70640,7 +72130,7 @@ var contentCommand = defineCommand240({
70640
72130
  });
70641
72131
 
70642
72132
  // src/commands/winning-ads/feed.ts
70643
- import { defineCommand as defineCommand241 } from "citty";
72133
+ import { defineCommand as defineCommand246 } from "citty";
70644
72134
  function buildFeedParams(input) {
70645
72135
  const params2 = {};
70646
72136
  const advertiser = splitList3(input.advertiser);
@@ -70692,7 +72182,7 @@ registerSchema({
70692
72182
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
70693
72183
  }
70694
72184
  });
70695
- var feedCommand2 = defineCommand241({
72185
+ var feedCommand2 = defineCommand246({
70696
72186
  meta: {
70697
72187
  name: "feed",
70698
72188
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -70777,7 +72267,7 @@ var feedCommand2 = defineCommand241({
70777
72267
  });
70778
72268
 
70779
72269
  // src/commands/winning-ads/follow.ts
70780
- import { defineCommand as defineCommand242 } from "citty";
72270
+ import { defineCommand as defineCommand247 } from "citty";
70781
72271
  var PLATFORMS = adLibraryPlatformSchema.options;
70782
72272
  registerSchema({
70783
72273
  command: "winning-ads.follow",
@@ -70792,7 +72282,7 @@ registerSchema({
70792
72282
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
70793
72283
  }
70794
72284
  });
70795
- var followCommand = defineCommand242({
72285
+ var followCommand = defineCommand247({
70796
72286
  meta: {
70797
72287
  name: "follow",
70798
72288
  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'
@@ -70839,7 +72329,7 @@ var followCommand = defineCommand242({
70839
72329
  });
70840
72330
 
70841
72331
  // src/commands/winning-ads/follow-competitors.ts
70842
- import { defineCommand as defineCommand243 } from "citty";
72332
+ import { defineCommand as defineCommand248 } from "citty";
70843
72333
  var PLATFORMS2 = adLibraryPlatformSchema.options;
70844
72334
  var BATCH_TIMEOUT_MS = 3e5;
70845
72335
  function buildFollowBatchBody(input) {
@@ -70872,7 +72362,7 @@ registerSchema({
70872
72362
  }
70873
72363
  }
70874
72364
  });
70875
- var followCompetitorsCommand = defineCommand243({
72365
+ var followCompetitorsCommand = defineCommand248({
70876
72366
  meta: {
70877
72367
  name: "follow-competitors",
70878
72368
  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"'
@@ -70947,7 +72437,7 @@ var followCompetitorsCommand = defineCommand243({
70947
72437
  });
70948
72438
 
70949
72439
  // src/commands/winning-ads/following.ts
70950
- import { defineCommand as defineCommand244 } from "citty";
72440
+ import { defineCommand as defineCommand249 } from "citty";
70951
72441
  registerSchema({
70952
72442
  command: "winning-ads.following",
70953
72443
  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.",
@@ -71001,7 +72491,7 @@ function followingNormalizer(record, full) {
71001
72491
  platforms: Array.isArray(record.platforms) ? record.platforms : []
71002
72492
  };
71003
72493
  }
71004
- var followingCommand = defineCommand244({
72494
+ var followingCommand = defineCommand249({
71005
72495
  meta: {
71006
72496
  name: "following",
71007
72497
  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"
@@ -71037,7 +72527,7 @@ var followingCommand = defineCommand244({
71037
72527
  });
71038
72528
 
71039
72529
  // src/commands/winning-ads/patterns.ts
71040
- import { defineCommand as defineCommand245 } from "citty";
72530
+ import { defineCommand as defineCommand250 } from "citty";
71041
72531
  registerSchema({
71042
72532
  command: "winning-ads.patterns",
71043
72533
  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.",
@@ -71076,7 +72566,7 @@ function discriminatorRow(record) {
71076
72566
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
71077
72567
  };
71078
72568
  }
71079
- var patternsCommand = defineCommand245({
72569
+ var patternsCommand = defineCommand250({
71080
72570
  meta: {
71081
72571
  name: "patterns",
71082
72572
  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"
@@ -71132,7 +72622,7 @@ var patternsCommand = defineCommand245({
71132
72622
  });
71133
72623
 
71134
72624
  // src/commands/winning-ads/search.ts
71135
- import { defineCommand as defineCommand246 } from "citty";
72625
+ import { defineCommand as defineCommand251 } from "citty";
71136
72626
  registerSchema({
71137
72627
  command: "winning-ads.search",
71138
72628
  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.",
@@ -71205,15 +72695,15 @@ function setNumber(body, key, value) {
71205
72695
  body[key] = Number(value);
71206
72696
  }
71207
72697
  }
71208
- function setList(target2, key, value) {
71209
- const list = splitList3(value);
71210
- if (list.length) {
71211
- target2[key] = list;
72698
+ function setList(target4, key, value) {
72699
+ const list3 = splitList3(value);
72700
+ if (list3.length) {
72701
+ target4[key] = list3;
71212
72702
  }
71213
72703
  }
71214
- function setString(target2, key, value) {
72704
+ function setString(target4, key, value) {
71215
72705
  if (value) {
71216
- target2[key] = value;
72706
+ target4[key] = value;
71217
72707
  }
71218
72708
  }
71219
72709
  function buildSearchBody2(args5) {
@@ -71270,7 +72760,7 @@ function reportSearchError(err) {
71270
72760
  }
71271
72761
  reportError2(err);
71272
72762
  }
71273
- var searchCommand5 = defineCommand246({
72763
+ var searchCommand5 = defineCommand251({
71274
72764
  meta: {
71275
72765
  name: "search",
71276
72766
  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"
@@ -71390,7 +72880,7 @@ var searchCommand5 = defineCommand246({
71390
72880
  });
71391
72881
 
71392
72882
  // src/commands/winning-ads/seeds.ts
71393
- import { defineCommand as defineCommand247 } from "citty";
72883
+ import { defineCommand as defineCommand252 } from "citty";
71394
72884
  function leanRow(r) {
71395
72885
  return {
71396
72886
  key: r.key,
@@ -71418,7 +72908,7 @@ function makeSeedCommand(opts) {
71418
72908
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
71419
72909
  }
71420
72910
  });
71421
- return defineCommand247({
72911
+ return defineCommand252({
71422
72912
  meta: { name: opts.name, description: opts.description },
71423
72913
  args: {
71424
72914
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -71467,7 +72957,7 @@ var formatsCommand = makeSeedCommand({
71467
72957
  });
71468
72958
 
71469
72959
  // src/commands/winning-ads/unfollow.ts
71470
- import { defineCommand as defineCommand248 } from "citty";
72960
+ import { defineCommand as defineCommand253 } from "citty";
71471
72961
  registerSchema({
71472
72962
  command: "winning-ads.unfollow",
71473
72963
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -71475,7 +72965,7 @@ registerSchema({
71475
72965
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
71476
72966
  }
71477
72967
  });
71478
- var unfollowCommand = defineCommand248({
72968
+ var unfollowCommand = defineCommand253({
71479
72969
  meta: {
71480
72970
  name: "unfollow",
71481
72971
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -71496,7 +72986,7 @@ var unfollowCommand = defineCommand248({
71496
72986
  });
71497
72987
 
71498
72988
  // src/commands/winning-ads/winners.ts
71499
- import { defineCommand as defineCommand249 } from "citty";
72989
+ import { defineCommand as defineCommand254 } from "citty";
71500
72990
  registerSchema({
71501
72991
  command: "winning-ads.winners",
71502
72992
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -71506,7 +72996,7 @@ registerSchema({
71506
72996
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin|tiktok", required: false }
71507
72997
  }
71508
72998
  });
71509
- var winnersCommand = defineCommand249({
72999
+ var winnersCommand = defineCommand254({
71510
73000
  meta: {
71511
73001
  name: "winners",
71512
73002
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -71556,7 +73046,7 @@ var winnersCommand = defineCommand249({
71556
73046
  });
71557
73047
 
71558
73048
  // src/commands/winning-ads/index.ts
71559
- var winningAdsCommand = defineCommand250({
73049
+ var winningAdsCommand = defineCommand255({
71560
73050
  meta: {
71561
73051
  name: "winning-ads",
71562
73052
  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.
@@ -71756,8 +73246,8 @@ function suggestFlag(unknown, command) {
71756
73246
  const name = flag3.slice(2);
71757
73247
  const def = args5[name] ?? args5[camel(name)];
71758
73248
  const described = def && typeof def === "object" ? def.description : void 0;
71759
- const description = typeof described === "string" ? described.split(/(?<!\b(?:e\.g|i\.e|vs))\.\s/)[0]?.trim().replace(/\.$/, "") : "";
71760
- return description ? { flag: flag3, description } : { flag: flag3 };
73249
+ const description2 = typeof described === "string" ? described.split(/(?<!\b(?:e\.g|i\.e|vs))\.\s/)[0]?.trim().replace(/\.$/, "") : "";
73250
+ return description2 ? { flag: flag3, description: description2 } : { flag: flag3 };
71761
73251
  }
71762
73252
  function suggestionText(suggestion) {
71763
73253
  if (!suggestion) {
@@ -71767,12 +73257,12 @@ function suggestionText(suggestion) {
71767
73257
  }
71768
73258
  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. ";
71769
73259
  function unknownFlagEnvelope(unknown, commandPath, suggestion, hasSchema) {
71770
- const list = unknown.join(", ");
73260
+ const list3 = unknown.join(", ");
71771
73261
  return {
71772
73262
  ok: false,
71773
73263
  error: {
71774
73264
  code: "UNKNOWN_ARGUMENT",
71775
- message: `${list} ${unknown.length > 1 ? "are not options" : "is not an option"} of \`baker ${commandPath}\`.`,
73265
+ message: `${list3} ${unknown.length > 1 ? "are not options" : "is not an option"} of \`baker ${commandPath}\`.`,
71776
73266
  fix: {
71777
73267
  action: hasSchema ? "check_schema" : "check_help",
71778
73268
  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.`
@@ -71883,7 +73373,7 @@ function getCliVersion() {
71883
73373
  }
71884
73374
 
71885
73375
  // src/cli.ts
71886
- var main = defineCommand251({
73376
+ var main = defineCommand256({
71887
73377
  meta: {
71888
73378
  name: "baker",
71889
73379
  version: getCliVersion(),