@koda-sl/baker-cli 0.372.0-dev.0d1659d6a → 0.373.0-dev.56c2ad392

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 defineCommand249, runMain } from "citty";
136
+ import { defineCommand as defineCommand250, runMain } from "citty";
137
137
 
138
138
  // src/cache-flag.ts
139
139
  var NO_CACHE_ARG = {
@@ -161,8 +161,8 @@ function cacheFlagForParser(argv) {
161
161
  }
162
162
  function rewriteBeforeDoubleDash(argv, rewrite) {
163
163
  const end = argv.indexOf("--");
164
- const limit = end === -1 ? argv.length : end;
165
- return argv.flatMap((token, index) => index >= limit ? [token] : rewrite(token));
164
+ const limit2 = end === -1 ? argv.length : end;
165
+ return argv.flatMap((token, index) => index >= limit2 ? [token] : rewrite(token));
166
166
  }
167
167
 
168
168
  // src/commands/actions/index.ts
@@ -1492,7 +1492,7 @@ var audienceCreateSchema = z3.object({
1492
1492
  }
1493
1493
  });
1494
1494
  var ENGAGEMENT_LOOKBACK_DAYS = [7, 14, 28, 30, 60, 90, 180, 365];
1495
- var WEBSITE_LOOKBACK_DAYS = ENGAGEMENT_LOOKBACK_DAYS.filter((days) => days >= 14 && days <= 180);
1495
+ var WEBSITE_LOOKBACK_DAYS = ENGAGEMENT_LOOKBACK_DAYS.filter((days2) => days2 >= 14 && days2 <= 180);
1496
1496
  var EVENT_LOOKBACK_DAYS = [730];
1497
1497
  var WEBSITE_SOURCE_PLATFORM = "LINKEDIN_WEBSITE_RETARGETING";
1498
1498
  var TRIGGER_SOURCE_TYPE_BY_PLATFORM = { [WEBSITE_SOURCE_PLATFORM]: "WEBSITE" };
@@ -1578,9 +1578,9 @@ function retargetingConfigProblems(raw) {
1578
1578
  const expectedTriggerSource = TRIGGER_SOURCE_TYPE_BY_PLATFORM[sourcePlatform] ?? sourcePlatform;
1579
1579
  for (const rule of rules.data) {
1580
1580
  problems.push(...ruleProblems(rule, sourcePlatform ? expectedTriggerSource : void 0));
1581
- const days = rule.timeFrame.timeSpan.duration;
1582
- if (!allowed.includes(days)) {
1583
- problems.push(`lookback ${days} days is not offered for ${sourcePlatform} \u2014 use ${allowed.join(", ")}`);
1581
+ const days2 = rule.timeFrame.timeSpan.duration;
1582
+ if (!allowed.includes(days2)) {
1583
+ problems.push(`lookback ${days2} days is not offered for ${sourcePlatform} \u2014 use ${allowed.join(", ")}`);
1584
1584
  }
1585
1585
  }
1586
1586
  const shapes = new Set(rules.data.map((rule) => `${rule.engagementTrigger}|${rule.timeFrame.timeSpan.duration}`));
@@ -5897,7 +5897,11 @@ var REJECTION_CODES = [
5897
5897
  "missing_identity",
5898
5898
  "invalid_timestamp",
5899
5899
  "invalid_value",
5900
- "batch_too_large"
5900
+ "batch_too_large",
5901
+ // Appended, never inserted: the set a client branches on grows, and no code
5902
+ // it already handles changes meaning.
5903
+ "invalid_items",
5904
+ "invalid_consent"
5901
5905
  ];
5902
5906
  var EVENT_INGEST_BATCH_MAX = 500;
5903
5907
  var EVENT_INGEST_BODY_MAX_BYTES = 8 * 1024 * 1024;
@@ -5910,12 +5914,15 @@ function validateEventName(name) {
5910
5914
  if (value === "" || value.length > MAX_EVENT_NAME || !EVENT_NAME.test(value)) return "invalid_name";
5911
5915
  return null;
5912
5916
  }
5917
+ var MAX_ITEM_QUANTITY = 1e6;
5913
5918
  var REJECTION_MESSAGES = {
5914
5919
  invalid_event_id: "event_id, when sent, must be 1-128 characters of A-Z a-z 0-9 . _ : -",
5915
5920
  invalid_name: "event_name is required: up to 80 characters of letters (any language), digits, spaces, . _ and -, starting with a letter or a digit",
5916
5921
  missing_identity: "user_data named nobody, and no url named a page",
5917
5922
  invalid_timestamp: "event_time must be an ISO-8601 instant with an offset or Unix seconds, and not in the future",
5918
5923
  invalid_value: "value and currency go together: a non-negative decimal and an ISO-4217 code",
5924
+ invalid_items: "each item names a product_id, variant_id or feed_id; quantity is a whole number from 1 to 1000000; price is a non-negative decimal sent with a currency",
5925
+ invalid_consent: "consent.analytics and consent.marketing, when sent, are granted or denied",
5919
5926
  batch_too_large: `a request carries at most ${EVENT_INGEST_BATCH_MAX} events and ${EVENT_INGEST_BODY_MAX_BYTES / (1024 * 1024)} MB`
5920
5927
  };
5921
5928
 
@@ -7136,12 +7143,12 @@ function parseClause(clause) {
7136
7143
  return { error: `"${clause}" has no operator this understands. ${EXPRESSION_HELP}` };
7137
7144
  }
7138
7145
  function parseConditionsExpression(expression) {
7139
- const split = splitClauses(expression);
7140
- if ("error" in split) {
7141
- return { ok: false, error: split.error };
7146
+ const split2 = splitClauses(expression);
7147
+ if ("error" in split2) {
7148
+ return { ok: false, error: split2.error };
7142
7149
  }
7143
7150
  const rules = [];
7144
- for (const clause of split.clauses) {
7151
+ for (const clause of split2.clauses) {
7145
7152
  if (clause.length === 0) {
7146
7153
  return { ok: false, error: `An empty rule. ${EXPRESSION_HELP}` };
7147
7154
  }
@@ -7154,7 +7161,7 @@ function parseConditionsExpression(expression) {
7154
7161
  if (rules.length > MAX_CONDITION_RULES) {
7155
7162
  return { ok: false, error: `At most ${MAX_CONDITION_RULES} rules.` };
7156
7163
  }
7157
- return { ok: true, conditions: { match: split.match, rules } };
7164
+ return { ok: true, conditions: { match: split2.match, rules } };
7158
7165
  }
7159
7166
 
7160
7167
  // ../api/src/scheduledAgents.ts
@@ -7294,7 +7301,8 @@ var TRIGGER_KINDS = {
7294
7301
  "utm_source",
7295
7302
  "utm_medium",
7296
7303
  "utm_campaign",
7297
- "properties.<name>"
7304
+ "properties.<name>",
7305
+ "items"
7298
7306
  ],
7299
7307
  keywords: ["purchase", "signup", "conversion", "analytics", "tracking", "hubspot", "crm", "deal", "funnel", "lead"]
7300
7308
  },
@@ -8610,12 +8618,12 @@ function advisoryHint(name, description) {
8610
8618
  return executable ? { kind: "executable", ...executable } : null;
8611
8619
  }
8612
8620
  var ACTIONS_LIST_DEFAULT_LIMIT = 500;
8613
- function buildListHints({ returned, limit }) {
8614
- if (returned < limit) {
8621
+ function buildListHints({ returned, limit: limit2 }) {
8622
+ if (returned < limit2) {
8615
8623
  return [];
8616
8624
  }
8617
8625
  return [
8618
- `This list is capped at ${limit} Tasks and came back full, so older Tasks exist that are NOT shown. Do not treat it as the whole backlog \u2014 re-run with --limit ${limit * 2} (or narrow with --status / --search).`
8626
+ `This list is capped at ${limit2} Tasks and came back full, so older Tasks exist that are NOT shown. Do not treat it as the whole backlog \u2014 re-run with --limit ${limit2 * 2} (or narrow with --status / --search).`
8619
8627
  ];
8620
8628
  }
8621
8629
 
@@ -9394,9 +9402,9 @@ function resolveWindow(args5) {
9394
9402
  const todayStart = startOfLocalDay(new Date(now));
9395
9403
  return { fromMs: todayStart - DAY_MS, toMs: todayStart };
9396
9404
  }
9397
- const days = /^(\d+)d$/.exec(since);
9398
- if (days) {
9399
- return { fromMs: now - Number(days[1]) * DAY_MS, toMs: now };
9405
+ const days2 = /^(\d+)d$/.exec(since);
9406
+ if (days2) {
9407
+ return { fromMs: now - Number(days2[1]) * DAY_MS, toMs: now };
9400
9408
  }
9401
9409
  return failValidation(`--since must be one of: today | yesterday | <N>d (e.g. 7d). Got: ${since}`);
9402
9410
  }
@@ -9794,15 +9802,15 @@ import { defineCommand as defineCommand19 } from "citty";
9794
9802
  function listed(resources) {
9795
9803
  return resources.map((resource) => resource.label ? `${resource.id} (${resource.label})` : resource.id).join("; ");
9796
9804
  }
9797
- function connectedResourceHints({ plural, singular, flag: flag2, resources }) {
9805
+ function connectedResourceHints({ plural, singular, flag: flag3, resources }) {
9798
9806
  if (resources.length === 0) {
9799
9807
  return [];
9800
9808
  }
9801
9809
  if (resources.length === 1) {
9802
- return [`This company connected one ${singular}: ${listed(resources)}. Commands use it unless you pass ${flag2}.`];
9810
+ return [`This company connected one ${singular}: ${listed(resources)}. Commands use it unless you pass ${flag3}.`];
9803
9811
  }
9804
9812
  return [
9805
- `This company connected ${resources.length} ${plural}: ${listed(resources)}. Each command works on one, so pass ${flag2} \u2014 nothing is picked for you. A question about the whole business covers all ${resources.length}: answering from one and reporting it as the total is wrong.`
9813
+ `This company connected ${resources.length} ${plural}: ${listed(resources)}. Each command works on one, so pass ${flag3} \u2014 nothing is picked for you. A question about the whole business covers all ${resources.length}: answering from one and reporting it as the total is wrong.`
9806
9814
  ];
9807
9815
  }
9808
9816
 
@@ -10025,8 +10033,8 @@ var CHANGE_SCOPE_MAX_DAYS = {
10025
10033
  };
10026
10034
  var DEFAULT_CHANGE_DAYS = 7;
10027
10035
  var MAX_CHANGE_DAYS = CHANGE_SCOPE_MAX_DAYS.summary;
10028
- function isChangeLookbackWithinReach(days, scope) {
10029
- return days <= CHANGE_SCOPE_MAX_DAYS[scope];
10036
+ function isChangeLookbackWithinReach(days2, scope) {
10037
+ return days2 <= CHANGE_SCOPE_MAX_DAYS[scope];
10030
10038
  }
10031
10039
 
10032
10040
  // ../api/src/ads-google/geo-constants.ts
@@ -10459,8 +10467,8 @@ function equivalentBetween(literal, now) {
10459
10467
  const unit = rolling[2];
10460
10468
  if (n < 1) return null;
10461
10469
  if (unit === "DAYS" || unit === "WEEKS") {
10462
- const days = unit === "DAYS" ? n : n * 7;
10463
- return range(utcDate(year, today.getUTCMonth(), today.getUTCDate() - days), yesterday);
10470
+ const days2 = unit === "DAYS" ? n : n * 7;
10471
+ return range(utcDate(year, today.getUTCMonth(), today.getUTCDate() - days2), yesterday);
10464
10472
  }
10465
10473
  const months = unit === "MONTHS" ? n : n * 12;
10466
10474
  return range(monthsBefore(today, months), yesterday);
@@ -10506,13 +10514,13 @@ function resolveChangesWindow(input) {
10506
10514
  }
10507
10515
  };
10508
10516
  }
10509
- const days = input.days ?? DEFAULT_CHANGE_DAYS;
10510
- if (!Number.isInteger(days) || days < 1) {
10517
+ const days2 = input.days ?? DEFAULT_CHANGE_DAYS;
10518
+ if (!Number.isInteger(days2) || days2 < 1) {
10511
10519
  return {
10512
10520
  ok: false,
10513
10521
  error: {
10514
10522
  code: "INVALID_LOOKBACK",
10515
- message: `--days must be a whole number of days, got '${days}'.`,
10523
+ message: `--days must be a whole number of days, got '${days2}'.`,
10516
10524
  fix: {
10517
10525
  action: "retry_with_flag",
10518
10526
  explanation: `Pass --days as a positive integer, e.g. --days ${DEFAULT_CHANGE_DAYS}.`
@@ -10520,32 +10528,32 @@ function resolveChangesWindow(input) {
10520
10528
  }
10521
10529
  };
10522
10530
  }
10523
- if (!isChangeLookbackWithinReach(days, scope)) {
10524
- return { ok: false, error: lookbackTooLong(scope, days, CHANGE_SCOPE_MAX_DAYS[scope]) };
10531
+ if (!isChangeLookbackWithinReach(days2, scope)) {
10532
+ return { ok: false, error: lookbackTooLong(scope, days2, CHANGE_SCOPE_MAX_DAYS[scope]) };
10525
10533
  }
10526
- return { ok: true, scope, days, hints: hintsFor(scope, days) };
10534
+ return { ok: true, scope, days: days2, hints: hintsFor(scope, days2) };
10527
10535
  }
10528
- function lookbackTooLong(scope, days, max) {
10529
- if (scope === "detail" && days <= CHANGE_SCOPE_MAX_DAYS.summary) {
10536
+ function lookbackTooLong(scope, days2, max) {
10537
+ if (scope === "detail" && days2 <= CHANGE_SCOPE_MAX_DAYS.summary) {
10530
10538
  return {
10531
10539
  code: "LOOKBACK_TOO_LONG",
10532
- message: `Google keeps field-level change detail for ${max} days only; ${days} days is outside that window.`,
10540
+ message: `Google keeps field-level change detail for ${max} days only; ${days2} days is outside that window.`,
10533
10541
  fix: {
10534
10542
  action: "narrow_date_range",
10535
- explanation: `Re-run with --scope summary --days ${days} to reach back ${CHANGE_SCOPE_MAX_DAYS.summary} days. Summary tells you which campaigns, ad groups, ads and criteria changed and when, but not the old and new values \u2014 read the resource itself for current values.`
10543
+ explanation: `Re-run with --scope summary --days ${days2} to reach back ${CHANGE_SCOPE_MAX_DAYS.summary} days. Summary tells you which campaigns, ad groups, ads and criteria changed and when, but not the old and new values \u2014 read the resource itself for current values.`
10536
10544
  }
10537
10545
  };
10538
10546
  }
10539
10547
  return {
10540
10548
  code: "LOOKBACK_TOO_LONG",
10541
- message: `Google's account history API reaches back ${CHANGE_SCOPE_MAX_DAYS.summary} days at most; ${days} days is outside that window.`,
10549
+ message: `Google's account history API reaches back ${CHANGE_SCOPE_MAX_DAYS.summary} days at most; ${days2} days is outside that window.`,
10542
10550
  fix: {
10543
10551
  action: "use_different_resource",
10544
10552
  explanation: `Do not abort \u2014 infer the change from performance instead. Query monthly metrics per campaign (${monthlySpendTrendQuery()}) to see which campaigns stopped or started spending. That shows what changed and when, though not who changed it. Older history exists only in the Google Ads web UI, which has no API \u2014 ask the user to check it if the 'who' matters.`
10545
10553
  }
10546
10554
  };
10547
10555
  }
10548
- function hintsFor(scope, days) {
10556
+ function hintsFor(scope, days2) {
10549
10557
  const hints2 = [];
10550
10558
  if (scope === "detail") {
10551
10559
  hints2.push(
@@ -10555,7 +10563,7 @@ function hintsFor(scope, days) {
10555
10563
  hints2.push(
10556
10564
  "Summary history reports which resources changed and when, not old and new values. To see what a changed resource looks like now, query it by resource_name."
10557
10565
  );
10558
- if (days > CHANGE_SCOPE_MAX_DAYS.detail) {
10566
+ if (days2 > CHANGE_SCOPE_MAX_DAYS.detail) {
10559
10567
  hints2.push(
10560
10568
  `Changes inside the last ${CHANGE_SCOPE_MAX_DAYS.detail} days also have field-level detail \u2014 re-run with --scope detail for those.`
10561
10569
  );
@@ -11001,11 +11009,11 @@ var CORRECTION_RULES = [
11001
11009
  {
11002
11010
  matchApiError: /changeEventError\W{0,4}START_DATE_TOO_OLD/i,
11003
11011
  fix: (ctx) => {
11004
- const limit = ctx.originalQuery.match(/LIMIT\s+(\d+)/i)?.[1] ?? "50";
11012
+ const limit2 = ctx.originalQuery.match(/LIMIT\s+(\d+)/i)?.[1] ?? "50";
11005
11013
  const requested = ctx.originalQuery.match(/change_date_time\s*>=\s*'([^']+)'/i)?.[1]?.slice(0, 10);
11006
11014
  const oldestStart = new Date(Date.now() - 89 * 24 * 60 * 60 * 1e3).toISOString().slice(0, 10);
11007
11015
  const start = requested && requested > oldestStart ? requested : oldestStart;
11008
- const corrected = `SELECT change_status.last_change_date_time, change_status.resource_type, change_status.resource_status, change_status.resource_name, change_status.campaign, change_status.ad_group FROM change_status WHERE change_status.last_change_date_time >= '${start}' ORDER BY change_status.last_change_date_time DESC LIMIT ${limit}`;
11016
+ const corrected = `SELECT change_status.last_change_date_time, change_status.resource_type, change_status.resource_status, change_status.resource_name, change_status.campaign, change_status.ad_group FROM change_status WHERE change_status.last_change_date_time >= '${start}' ORDER BY change_status.last_change_date_time DESC LIMIT ${limit2}`;
11009
11017
  return {
11010
11018
  action: "use_different_resource",
11011
11019
  correctedCommand: buildCommand(corrected, ctx),
@@ -11397,8 +11405,8 @@ import { defineCommand as defineCommand22 } from "citty";
11397
11405
  import { readFileSync as readFileSync2 } from "fs";
11398
11406
 
11399
11407
  // src/lib/repeatedFlags.ts
11400
- function repeatedFlagValues(rawArgs, flag2) {
11401
- const long = `--${flag2}`;
11408
+ function repeatedFlagValues(rawArgs, flag3) {
11409
+ const long = `--${flag3}`;
11402
11410
  const inline = `${long}=`;
11403
11411
  const values = [];
11404
11412
  for (let i = 0; i < rawArgs.length; i++) {
@@ -11416,9 +11424,9 @@ function repeatedFlagValues(rawArgs, flag2) {
11416
11424
  }
11417
11425
  return values;
11418
11426
  }
11419
- function repeatedValues(rawArgs, flag2, parsed) {
11427
+ function repeatedValues(rawArgs, flag3, parsed) {
11420
11428
  if (rawArgs !== void 0) {
11421
- const fromLine = repeatedFlagValues(rawArgs, flag2);
11429
+ const fromLine = repeatedFlagValues(rawArgs, flag3);
11422
11430
  if (fromLine.length > 0) return fromLine;
11423
11431
  }
11424
11432
  if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
@@ -11444,8 +11452,8 @@ function booleanWordPositionalError(args5, entity) {
11444
11452
  }
11445
11453
  const value = word.toLowerCase();
11446
11454
  const raised = Object.keys(args5).filter((key) => key !== "_" && args5[key] === true);
11447
- const named = raised.length > 0 ? raised.map((flag2) => `--${flag2}`).join(" or ") : "a boolean flag";
11448
- const fix = raised.length > 0 ? raised.map((flag2) => `--${flag2}=${value}`).join(" or ") : `--<flag>=${value}`;
11455
+ const named = raised.length > 0 ? raised.map((flag3) => `--${flag3}`).join(" or ") : "a boolean flag";
11456
+ const fix = raised.length > 0 ? raised.map((flag3) => `--${flag3}=${value}`).join(" or ") : `--<flag>=${value}`;
11449
11457
  const negated = raised.length === 1 && value === "false" ? ` (or --no-${raised[0]})` : "";
11450
11458
  return `"${word}" was read as the ${entity} target, not as a value for ${named}. A boolean flag written with a space is set to TRUE and the word is dropped \u2014 attach it instead: ${fix}${negated}.`;
11451
11459
  }
@@ -12053,19 +12061,19 @@ function requireTarget(args5, entity) {
12053
12061
  }
12054
12062
  return target2;
12055
12063
  }
12056
- function requireStringFlag(value, flag2) {
12064
+ function requireStringFlag(value, flag3) {
12057
12065
  if (typeof value !== "string" || value.length === 0) {
12058
- failWriteValidation(`${flag2} is required`);
12066
+ failWriteValidation(`${flag3} is required`);
12059
12067
  }
12060
12068
  return value;
12061
12069
  }
12062
- function microsFlag(value, flag2) {
12070
+ function microsFlag(value, flag3) {
12063
12071
  if (value === void 0 || value === null || value === "") {
12064
12072
  return void 0;
12065
12073
  }
12066
12074
  const num4 = Number(value);
12067
12075
  if (Number.isNaN(num4) || num4 <= 0) {
12068
- failWriteValidation(`${flag2} must be a positive amount (major currency units, e.g. 50 or 50.00)`);
12076
+ failWriteValidation(`${flag3} must be a positive amount (major currency units, e.g. 50 or 50.00)`);
12069
12077
  }
12070
12078
  return toMicros(num4);
12071
12079
  }
@@ -12992,13 +13000,13 @@ function buildStatusScope(statusFields, includePaused) {
12992
13000
  }
12993
13001
  function expandPreset(preset, params2, now = /* @__PURE__ */ new Date()) {
12994
13002
  const dateRange = params2.dateRange ?? preset.defaultDateRange;
12995
- const limit = params2.limit ?? preset.defaultLimit;
13003
+ const limit2 = params2.limit ?? preset.defaultLimit;
12996
13004
  if (preset.gaqlTemplate.includes("{dateRange}") && !isValidDateRange(dateRange)) {
12997
13005
  return { query: "", dateRangeError: dateRangeError(dateRange, now) };
12998
13006
  }
12999
13007
  const statusScope = buildStatusScope(preset.statusFields, params2.includePaused ?? false);
13000
13008
  const range = dateRange.trim();
13001
- const query = preset.gaqlTemplate.replace(/DURING\s+\{dateRange\}/g, BETWEEN_RE.test(range) ? range : `DURING ${range}`).replace(/\{dateRange\}/g, range).replace(/\{limit\}/g, String(limit)).replace(/\{statusScope\}/g, statusScope);
13009
+ const query = preset.gaqlTemplate.replace(/DURING\s+\{dateRange\}/g, BETWEEN_RE.test(range) ? range : `DURING ${range}`).replace(/\{dateRange\}/g, range).replace(/\{limit\}/g, String(limit2)).replace(/\{statusScope\}/g, statusScope);
13002
13010
  return { query };
13003
13011
  }
13004
13012
 
@@ -13118,7 +13126,7 @@ function addRequiredAncestorFields(query) {
13118
13126
  ]
13119
13127
  };
13120
13128
  }
13121
- function applyAutoFixes(query, limit) {
13129
+ function applyAutoFixes(query, limit2) {
13122
13130
  let corrected = query;
13123
13131
  const warnings = [];
13124
13132
  if (/(?<![\w.])keyword\.text\b/.test(corrected)) {
@@ -13149,7 +13157,7 @@ function applyAutoFixes(query, limit) {
13149
13157
  warnings.push({ code: "ENUM_CORRECTED", message: "ad_group.status ACTIVE \u2192 ENABLED" });
13150
13158
  }
13151
13159
  if (!/\bLIMIT\b/i.test(corrected)) {
13152
- const effectiveLimit = limit ?? 200;
13160
+ const effectiveLimit = limit2 ?? 200;
13153
13161
  corrected = `${corrected.trimEnd()} LIMIT ${effectiveLimit}`;
13154
13162
  if (!isSingleRowRead(corrected)) {
13155
13163
  warnings.push({ code: "AUTO_LIMIT", message: `Added LIMIT ${effectiveLimit}. Use --limit to override.` });
@@ -13267,7 +13275,7 @@ function rejectContainsCall(query, customerId) {
13267
13275
  }
13268
13276
  };
13269
13277
  }
13270
- function validatePreflight(query, customerId, limit) {
13278
+ function validatePreflight(query, customerId, limit2) {
13271
13279
  const warnings = [];
13272
13280
  const corrected = query;
13273
13281
  const firstWord = corrected.trim().split(/\s+/)[0]?.toUpperCase();
@@ -13343,7 +13351,7 @@ function validatePreflight(query, customerId, limit) {
13343
13351
  message: "ad_group_criterion returns BOTH positive and negative keywords. Add ad_group_criterion.negative to SELECT or filter with WHERE ad_group_criterion.negative = FALSE (positives) / TRUE (negatives). Use --preset positive-keywords or negative-keywords instead."
13344
13352
  });
13345
13353
  }
13346
- const { query: autoFixed, warnings: autoFixWarnings } = applyAutoFixes(corrected, limit);
13354
+ const { query: autoFixed, warnings: autoFixWarnings } = applyAutoFixes(corrected, limit2);
13347
13355
  warnings.push(...autoFixWarnings);
13348
13356
  const analysis = analyzeServingChain(autoFixed);
13349
13357
  if (!analysis) return { valid: true, correctedQuery: autoFixed, warnings };
@@ -13612,12 +13620,12 @@ function writeRowsToFile(filePath, rows, fields, append) {
13612
13620
  writeFileSync(filePath, JSON.stringify(rows, null, 2), "utf-8");
13613
13621
  }
13614
13622
  }
13615
- function effectiveRowLimit(finalQuery, limit, all = false) {
13623
+ function effectiveRowLimit(finalQuery, limit2, all = false) {
13616
13624
  if (isSingleRowRead(finalQuery)) return void 0;
13617
13625
  const match = finalQuery.match(/\bLIMIT\s+(\d+)/i);
13618
13626
  const statementLimit = match?.[1] ? Number(match[1]) : void 0;
13619
13627
  if (all) return statementLimit;
13620
- return statementLimit === void 0 ? limit : Math.min(limit, statementLimit);
13628
+ return statementLimit === void 0 ? limit2 : Math.min(limit2, statementLimit);
13621
13629
  }
13622
13630
  function warningLines(warnings, format) {
13623
13631
  return warnings.map((w) => {
@@ -13735,7 +13743,7 @@ function outputResults(rows, fieldDescs, warnings, args5, cached, read) {
13735
13743
  }
13736
13744
  writeRows(format, rows, resultColumns(read.gaql, rows), warningLines(allWarnings, format));
13737
13745
  }
13738
- function resolveGaql(args5, limit) {
13746
+ function resolveGaql(args5, limit2) {
13739
13747
  if (args5.preset) {
13740
13748
  const preset = getPreset(args5.preset);
13741
13749
  if (!preset) {
@@ -13748,7 +13756,7 @@ function resolveGaql(args5, limit) {
13748
13756
  }
13749
13757
  const expanded = expandPreset(preset, {
13750
13758
  dateRange: args5["date-range"],
13751
- limit,
13759
+ limit: limit2,
13752
13760
  includePaused: Boolean(args5["include-paused"])
13753
13761
  });
13754
13762
  if (expanded.dateRangeError) {
@@ -13900,8 +13908,8 @@ async function fetchAllPages(gaql, body, args5, servingOptions) {
13900
13908
  fileColumns: fileColumns ?? []
13901
13909
  };
13902
13910
  }
13903
- async function executeQuery(finalQuery, args5, customerId, limit, warnings, useCache, cacheKey) {
13904
- const body = { customerId, query: finalQuery, pageSize: limit };
13911
+ async function executeQuery(finalQuery, args5, customerId, limit2, warnings, useCache, cacheKey) {
13912
+ const body = { customerId, query: finalQuery, pageSize: limit2 };
13905
13913
  const managerId = getManagerIdForCustomer(customerId);
13906
13914
  if (managerId) body.managerId = managerId;
13907
13915
  if (args5.cursor) body.pageToken = args5.cursor;
@@ -13916,7 +13924,7 @@ async function executeQuery(finalQuery, args5, customerId, limit, warnings, useC
13916
13924
  if (args5.out) {
13917
13925
  const truncation = completenessWarnings({
13918
13926
  rowCount: totalRowsWritten,
13919
- effectiveLimit: effectiveRowLimit(finalQuery, limit, Boolean(args5.all)),
13927
+ effectiveLimit: effectiveRowLimit(finalQuery, limit2, Boolean(args5.all)),
13920
13928
  all: Boolean(args5.all),
13921
13929
  hasMore: Boolean(lastPageToken)
13922
13930
  });
@@ -13958,7 +13966,7 @@ async function executeQuery(finalQuery, args5, customerId, limit, warnings, useC
13958
13966
  false,
13959
13967
  {
13960
13968
  gaql: finalQuery,
13961
- effectiveLimit: effectiveRowLimit(finalQuery, limit, Boolean(args5.all)),
13969
+ effectiveLimit: effectiveRowLimit(finalQuery, limit2, Boolean(args5.all)),
13962
13970
  all: Boolean(args5.all),
13963
13971
  nextCursor: lastPageToken
13964
13972
  }
@@ -13970,8 +13978,8 @@ function shouldReadCache(args5) {
13970
13978
  var DEFAULT_QUERY_LIMIT = 200;
13971
13979
  function parseQueryLimit(raw) {
13972
13980
  if (raw === void 0 || raw === null || raw === "") return { ok: true, limit: DEFAULT_QUERY_LIMIT };
13973
- const limit = Number(raw);
13974
- if (Number.isInteger(limit) && limit >= 1 && limit <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return { ok: true, limit };
13981
+ const limit2 = Number(raw);
13982
+ if (Number.isInteger(limit2) && limit2 >= 1 && limit2 <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return { ok: true, limit: limit2 };
13975
13983
  return {
13976
13984
  ok: false,
13977
13985
  error: {
@@ -13994,13 +14002,13 @@ async function validateArgs(args5) {
13994
14002
  writeAdsJson(parsedLimit.error);
13995
14003
  process.exit(1);
13996
14004
  }
13997
- const { limit } = parsedLimit;
14005
+ const { limit: limit2 } = parsedLimit;
13998
14006
  const customerId = await resolveCustomerId(args5);
13999
- const gaql = resolveGaql(args5, limit);
14007
+ const gaql = resolveGaql(args5, limit2);
14000
14008
  if (!gaql) {
14001
14009
  process.exit(1);
14002
14010
  }
14003
- const preflight = validatePreflight(gaql, customerId, limit);
14011
+ const preflight = validatePreflight(gaql, customerId, limit2);
14004
14012
  if (!preflight.valid && preflight.error) {
14005
14013
  writeAdsJson(preflight.error);
14006
14014
  process.exit(1);
@@ -14008,7 +14016,7 @@ async function validateArgs(args5) {
14008
14016
  const finalQuery = preflight.correctedQuery ?? gaql;
14009
14017
  return {
14010
14018
  customerId,
14011
- limit,
14019
+ limit: limit2,
14012
14020
  finalQuery,
14013
14021
  warnings: [...preflight.warnings, ...servingScopeWarnings(args5, finalQuery)],
14014
14022
  useCache: !args5["no-cache"],
@@ -14085,7 +14093,7 @@ Examples:
14085
14093
  }
14086
14094
  const validated = await validateArgs(args5);
14087
14095
  if (!validated) return;
14088
- const { customerId, limit, finalQuery, warnings, useCache, cacheKey } = validated;
14096
+ const { customerId, limit: limit2, finalQuery, warnings, useCache, cacheKey } = validated;
14089
14097
  if (shouldReadCache(args5)) {
14090
14098
  const cached = cacheGet("queries", cacheKey);
14091
14099
  if (cached) {
@@ -14103,7 +14111,7 @@ Examples:
14103
14111
  true,
14104
14112
  {
14105
14113
  gaql: finalQuery,
14106
- effectiveLimit: effectiveRowLimit(finalQuery, limit, Boolean(args5.all)),
14114
+ effectiveLimit: effectiveRowLimit(finalQuery, limit2, Boolean(args5.all)),
14107
14115
  all: Boolean(args5.all)
14108
14116
  }
14109
14117
  );
@@ -14111,7 +14119,7 @@ Examples:
14111
14119
  }
14112
14120
  }
14113
14121
  try {
14114
- await executeQuery(finalQuery, args5, customerId, limit, warnings, useCache, cacheKey);
14122
+ await executeQuery(finalQuery, args5, customerId, limit2, warnings, useCache, cacheKey);
14115
14123
  } catch (err) {
14116
14124
  handleQueryError(err, finalQuery, customerId);
14117
14125
  }
@@ -14906,8 +14914,22 @@ var ANALYTICS_EVENT_TYPES = [
14906
14914
  * page, `ingest` for a third party's server — and every report that needs to
14907
14915
  * tell them apart filters on that.
14908
14916
  */
14909
- "custom"
14917
+ "custom",
14918
+ /**
14919
+ * A fact about the business that names no person — a product changed, a
14920
+ * variant went out of stock.
14921
+ *
14922
+ * Only a commerce door writes it (`ingestShopifyFacts`), and it is a type of
14923
+ * its own rather than a `custom` event with no identity for one reason:
14924
+ * every report that lists named events whitelists `custom`, so an
14925
+ * operational fact would otherwise appear in the conversion picker, the
14926
+ * funnel and the sends. Here it reaches the raw feed and the agents that
14927
+ * listen for its name, and no person-level report. The row builder blanks
14928
+ * every identity column on it, whatever it carried.
14929
+ */
14930
+ "operational"
14910
14931
  ];
14932
+ var EVENT_ITEMS_MAX = 250;
14911
14933
  var ANALYTICS_DEVICE_TYPES = ["desktop", "mobile", "tablet", "bot", "unknown"];
14912
14934
  var ANALYTICS_BEACON_BATCH_MAX = 50;
14913
14935
 
@@ -17700,12 +17722,12 @@ function skipBlanks(value, at) {
17700
17722
  while (value[i] === " " || value[i] === " ") i++;
17701
17723
  return i;
17702
17724
  }
17703
- function readQuotedField(value, open, flag2) {
17725
+ function readQuotedField(value, open, flag3) {
17704
17726
  let text2 = "";
17705
17727
  let j = open + 1;
17706
17728
  while (!(value[j] === '"' && value[j + 1] !== '"')) {
17707
17729
  if (j >= value.length) {
17708
- failWriteValidation(`--${flag2} has a quote with no closing quote: ${value} \u2014 ${AD_COPY_LIST_USAGE}`);
17730
+ failWriteValidation(`--${flag3} has a quote with no closing quote: ${value} \u2014 ${AD_COPY_LIST_USAGE}`);
17709
17731
  }
17710
17732
  text2 += value[j];
17711
17733
  j += value[j] === '"' ? 2 : 1;
@@ -17713,18 +17735,18 @@ function readQuotedField(value, open, flag2) {
17713
17735
  const after = skipBlanks(value, j + 1);
17714
17736
  if (after < value.length && value[after] !== ",") {
17715
17737
  failWriteValidation(
17716
- `--${flag2}: text follows the closing quote of "${text2}" \u2014 a quoted item must end at a comma. To put a quote inside an item, double it ("") or ${AD_COPY_LIST_USAGE}`
17738
+ `--${flag3}: text follows the closing quote of "${text2}" \u2014 a quoted item must end at a comma. To put a quote inside an item, double it ("") or ${AD_COPY_LIST_USAGE}`
17717
17739
  );
17718
17740
  }
17719
17741
  return { text: text2, next: after + 1 };
17720
17742
  }
17721
- function splitQuotedList(value, flag2) {
17743
+ function splitQuotedList(value, flag3) {
17722
17744
  const fields = [];
17723
17745
  let i = 0;
17724
17746
  while (i <= value.length) {
17725
17747
  const start = skipBlanks(value, i);
17726
17748
  if (value[start] === '"') {
17727
- const { text: text2, next } = readQuotedField(value, start, flag2);
17749
+ const { text: text2, next } = readQuotedField(value, start, flag3);
17728
17750
  fields.push({ text: text2, quoted: true });
17729
17751
  i = next;
17730
17752
  } else {
@@ -17746,53 +17768,53 @@ function jsonStringArray(value) {
17746
17768
  return void 0;
17747
17769
  }
17748
17770
  }
17749
- function parseCopyList(value, flag2) {
17771
+ function parseCopyList(value, flag3) {
17750
17772
  let items;
17751
17773
  if (Array.isArray(value)) {
17752
17774
  items = value.filter((v) => typeof v === "string").map((v) => ({ text: v.trim(), splitAtComma: false }));
17753
17775
  } else if (typeof value === "string") {
17754
17776
  const trimmed = value.trim();
17755
17777
  const json = jsonStringArray(trimmed);
17756
- items = json ? json.map((v) => ({ text: v.trim(), splitAtComma: false })) : splitQuotedList(trimmed, flag2);
17778
+ items = json ? json.map((v) => ({ text: v.trim(), splitAtComma: false })) : splitQuotedList(trimmed, flag3);
17757
17779
  } else {
17758
17780
  return void 0;
17759
17781
  }
17760
17782
  const kept = items.filter((item) => item.text.length > 0);
17761
17783
  return kept.length > 0 ? kept : void 0;
17762
17784
  }
17763
- function copyListFlag(value, flag2) {
17764
- return parseCopyList(value, flag2)?.map((item) => item.text);
17785
+ function copyListFlag(value, flag3) {
17786
+ return parseCopyList(value, flag3)?.map((item) => item.text);
17765
17787
  }
17766
- function refuseDashLedValue(rawArgs, flag2) {
17788
+ function refuseDashLedValue(rawArgs, flag3) {
17767
17789
  for (let i = 0; i < rawArgs.length - 1; i++) {
17768
17790
  if (rawArgs[i] === "--") return;
17769
17791
  const value = rawArgs[i + 1];
17770
- if (rawArgs[i] === `--${flag2}` && value?.startsWith("-")) {
17792
+ if (rawArgs[i] === `--${flag3}` && value?.startsWith("-")) {
17771
17793
  failWriteValidation(
17772
- `--${flag2} is followed by "${value}", which starts with a dash and is read as another flag, not as copy. Join copy that starts with a dash to its flag with =: --${flag2}="${value}"`
17794
+ `--${flag3} is followed by "${value}", which starts with a dash and is read as another flag, not as copy. Join copy that starts with a dash to its flag with =: --${flag3}="${value}"`
17773
17795
  );
17774
17796
  }
17775
17797
  }
17776
17798
  }
17777
17799
  function withRepeatedCopyFlags(args5, rawArgs) {
17778
17800
  const out = { ...args5 };
17779
- for (const flag2 of AD_COPY_LIST_FLAGS) {
17780
- refuseDashLedValue(rawArgs ?? [], flag2);
17781
- const values = repeatedFlagValues(rawArgs ?? [], flag2);
17801
+ for (const flag3 of AD_COPY_LIST_FLAGS) {
17802
+ refuseDashLedValue(rawArgs ?? [], flag3);
17803
+ const values = repeatedFlagValues(rawArgs ?? [], flag3);
17782
17804
  if (values.length > 1) {
17783
- out[flag2] = values;
17805
+ out[flag3] = values;
17784
17806
  }
17785
17807
  }
17786
17808
  return out;
17787
17809
  }
17788
17810
  function copyListSplitHints(args5) {
17789
17811
  const hints2 = [];
17790
- for (const flag2 of AD_COPY_LIST_FLAGS) {
17791
- const items = parseCopyList(args5[flag2], flag2) ?? [];
17812
+ for (const flag3 of AD_COPY_LIST_FLAGS) {
17813
+ const items = parseCopyList(args5[flag3], flag3) ?? [];
17792
17814
  const fragments = items.filter((item, index) => index > 0 && item.splitAtComma && new RegExp("^\\p{Ll}", "u").test(item.text));
17793
17815
  if (fragments.length > 0) {
17794
17816
  hints2.push(
17795
- `--${flag2}: ${fragments.map((f) => `"${f.text}"`).join(", ")} starts in lowercase \u2014 it looks like the end of one item cut at a comma. Check the staged copy; if it was one item, fix this op in place with \`draft amend <ref>\` (the ref this response returned) rather than staging it again, which would leave the cut copy staged too \u2014 ${AD_COPY_LIST_USAGE}.`
17817
+ `--${flag3}: ${fragments.map((f) => `"${f.text}"`).join(", ")} starts in lowercase \u2014 it looks like the end of one item cut at a comma. Check the staged copy; if it was one item, fix this op in place with \`draft amend <ref>\` (the ref this response returned) rather than staging it again, which would leave the cut copy staged too \u2014 ${AD_COPY_LIST_USAGE}.`
17796
17818
  );
17797
17819
  }
17798
17820
  }
@@ -18343,11 +18365,11 @@ function urlCustomParametersFromFlags(args5, rawArgs) {
18343
18365
  failWriteValidation("--clear-custom-params removes every parameter \u2014 pass it OR --custom-param, not both");
18344
18366
  }
18345
18367
  return entries.map((entry) => {
18346
- const split = entry.indexOf("=");
18347
- if (split <= 0) {
18368
+ const split2 = entry.indexOf("=");
18369
+ if (split2 <= 0) {
18348
18370
  failWriteValidation(`--custom-param "${entry}" must be key=value (e.g. --custom-param season=summer)`);
18349
18371
  }
18350
- return { key: entry.slice(0, split), value: entry.slice(split + 1) };
18372
+ return { key: entry.slice(0, split2), value: entry.slice(split2 + 1) };
18351
18373
  });
18352
18374
  }
18353
18375
  var geoTargetTypeArgs = {
@@ -18360,13 +18382,13 @@ var geoTargetTypeArgs = {
18360
18382
  description: "How EXCLUDED locations are matched \u2014 PRESENCE (default: excludes people in the location) or PRESENCE_OR_INTEREST (also excludes people interested in it)."
18361
18383
  }
18362
18384
  };
18363
- function enumFlag(value, flag2, allowed) {
18385
+ function enumFlag(value, flag3, allowed) {
18364
18386
  if (typeof value !== "string" || value.length === 0) {
18365
18387
  return void 0;
18366
18388
  }
18367
18389
  const upper2 = value.toUpperCase();
18368
18390
  if (!allowed.includes(upper2)) {
18369
- failWriteValidation(`${flag2} must be one of ${allowed.join(" | ")}`);
18391
+ failWriteValidation(`${flag3} must be one of ${allowed.join(" | ")}`);
18370
18392
  }
18371
18393
  return upper2;
18372
18394
  }
@@ -19086,8 +19108,8 @@ var FORMAT_CONTENT_FLAGS = {
19086
19108
  // same silent drop this gate exists for, on the one format that reads no flags at all.
19087
19109
  call: []
19088
19110
  };
19089
- function formatsReading(flag2) {
19090
- return Object.entries(FORMAT_CONTENT_FLAGS).filter(([, flags]) => flags.includes(flag2)).map(([format]) => format);
19111
+ function formatsReading(flag3) {
19112
+ return Object.entries(FORMAT_CONTENT_FLAGS).filter(([, flags]) => flags.includes(flag3)).map(([format]) => format);
19091
19113
  }
19092
19114
  function assertFlagsFitFormat(args5, format) {
19093
19115
  const allowed = FORMAT_CONTENT_FLAGS[format];
@@ -19095,14 +19117,14 @@ function assertFlagsFitFormat(args5, format) {
19095
19117
  return;
19096
19118
  }
19097
19119
  const stray = Object.keys(adContentArgs).filter(
19098
- (flag2) => flag2 !== "format" && args5[flag2] !== void 0 && !allowed.includes(flag2)
19120
+ (flag3) => flag3 !== "format" && args5[flag3] !== void 0 && !allowed.includes(flag3)
19099
19121
  );
19100
19122
  if (stray.length === 0) {
19101
19123
  return;
19102
19124
  }
19103
- const detail = stray.map((flag2) => {
19104
- const elsewhere = formatsReading(flag2);
19105
- return elsewhere.length > 0 ? `--${flag2} (${elsewhere.join(" / ")})` : `--${flag2}`;
19125
+ const detail = stray.map((flag3) => {
19126
+ const elsewhere = formatsReading(flag3);
19127
+ return elsewhere.length > 0 ? `--${flag3} (${elsewhere.join(" / ")})` : `--${flag3}`;
19106
19128
  }).join(", ");
19107
19129
  failWriteValidation(
19108
19130
  `a ${format} ad has no ${stray.map((f) => `--${f}`).join(", ")} \u2014 passing it here would stage the change without it. Formats that take it: ${detail}. Check the format with \`baker ads google query "SELECT ad_group_ad.ad.type FROM ad_group_ad WHERE ad_group_ad.ad.id = <id>"\`.`
@@ -19181,10 +19203,10 @@ var adContentArgs = {
19181
19203
  "final-url": { type: "string", description: "Comma-separated final URLs" }
19182
19204
  };
19183
19205
  function assertNoContentFlags(args5) {
19184
- const passed = Object.keys(adContentArgs).filter((flag2) => flag2 !== "format" && args5[flag2] !== void 0);
19206
+ const passed = Object.keys(adContentArgs).filter((flag3) => flag3 !== "format" && args5[flag3] !== void 0);
19185
19207
  if (passed.length > 0) {
19186
19208
  failWriteValidation(
19187
- `${passed.map((flag2) => `--${flag2}`).join(", ")} only build an ad's content alongside --format (responsiveSearch | responsiveDisplay | call | app | video | demandGen | demandGenVideo) \u2014 add it, or pass --file with the complete content`
19209
+ `${passed.map((flag3) => `--${flag3}`).join(", ")} only build an ad's content alongside --format (responsiveSearch | responsiveDisplay | call | app | video | demandGen | demandGenVideo) \u2014 add it, or pass --file with the complete content`
19188
19210
  );
19189
19211
  }
19190
19212
  }
@@ -19907,8 +19929,8 @@ function resolveAccountIdArg2(args5) {
19907
19929
  function todayIso2() {
19908
19930
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19909
19931
  }
19910
- function daysAgoIso2(days) {
19911
- return new Date(Date.now() - days * DAY_MS2).toISOString().slice(0, 10);
19932
+ function daysAgoIso2(days2) {
19933
+ return new Date(Date.now() - days2 * DAY_MS2).toISOString().slice(0, 10);
19912
19934
  }
19913
19935
  function csvOrJson2(args5) {
19914
19936
  return args5.output ?? "json";
@@ -19982,7 +20004,7 @@ function loadPatchArg2(args5) {
19982
20004
  throw err;
19983
20005
  }
19984
20006
  }
19985
- function parseMoneyFlag(amount, currency, flag2) {
20007
+ function parseMoneyFlag(amount, currency, flag3) {
19986
20008
  if (amount === void 0 || amount === null || amount === "") {
19987
20009
  return void 0;
19988
20010
  }
@@ -19990,11 +20012,11 @@ function parseMoneyFlag(amount, currency, flag2) {
19990
20012
  return { amount: String(amount) };
19991
20013
  }
19992
20014
  if (typeof currency !== "string" || currency.length !== 3) {
19993
- failWriteValidation2(`${flag2} got an invalid --currency (expected a 3-letter code like EUR)`);
20015
+ failWriteValidation2(`${flag3} got an invalid --currency (expected a 3-letter code like EUR)`);
19994
20016
  }
19995
20017
  return { amount: String(amount), currencyCode: currency.toUpperCase() };
19996
20018
  }
19997
- function parseDateFlag(value, flag2) {
20019
+ function parseDateFlag(value, flag3) {
19998
20020
  if (value === void 0 || value === null || value === "") {
19999
20021
  return void 0;
20000
20022
  }
@@ -20004,11 +20026,11 @@ function parseDateFlag(value, flag2) {
20004
20026
  }
20005
20027
  const parsed = Date.parse(raw);
20006
20028
  if (Number.isNaN(parsed)) {
20007
- failWriteValidation2(`${flag2} must be an ISO date (2026-07-10) or epoch milliseconds`);
20029
+ failWriteValidation2(`${flag3} must be an ISO date (2026-07-10) or epoch milliseconds`);
20008
20030
  }
20009
20031
  return parsed;
20010
20032
  }
20011
- function parseOnOffFlag(value, flag2) {
20033
+ function parseOnOffFlag(value, flag3) {
20012
20034
  if (value === void 0 || value === null || value === "") {
20013
20035
  return void 0;
20014
20036
  }
@@ -20019,7 +20041,7 @@ function parseOnOffFlag(value, flag2) {
20019
20041
  if (raw === "off" || raw === "false") {
20020
20042
  return false;
20021
20043
  }
20022
- failWriteValidation2(`${flag2} must be on|off`);
20044
+ failWriteValidation2(`${flag3} must be on|off`);
20023
20045
  }
20024
20046
  function parseLocaleFlag(value) {
20025
20047
  if (value === void 0 || value === null || value === "") {
@@ -21205,13 +21227,13 @@ Examples:
21205
21227
  }
21206
21228
  });
21207
21229
  }
21208
- function trackingPairs(value, flag2) {
21230
+ function trackingPairs(value, flag3) {
21209
21231
  const raw = value === void 0 ? [] : Array.isArray(value) ? value : [value];
21210
21232
  return raw.flatMap(
21211
21233
  (entry) => String(entry).split("&").filter((pair) => pair.length > 0).map((pair) => {
21212
21234
  const at = pair.indexOf("=");
21213
21235
  if (at < 1 || at === pair.length - 1) {
21214
- failWriteValidation2(`${flag2} expects key=value pairs \u2014 got "${pair}"`);
21236
+ failWriteValidation2(`${flag3} expects key=value pairs \u2014 got "${pair}"`);
21215
21237
  }
21216
21238
  return [pair.slice(0, at).trim(), pair.slice(at + 1).trim()];
21217
21239
  })
@@ -21547,9 +21569,9 @@ var CREATIVE_AMEND_CONTENT_FLAGS = [
21547
21569
  ];
21548
21570
  function creativeContentPatch(args5) {
21549
21571
  const patch = {};
21550
- for (const [flag2, key] of CREATIVE_AMEND_CONTENT_FLAGS) {
21551
- if (args5[flag2] !== void 0) {
21552
- patch[key] = args5[flag2];
21572
+ for (const [flag3, key] of CREATIVE_AMEND_CONTENT_FLAGS) {
21573
+ if (args5[flag3] !== void 0) {
21574
+ patch[key] = args5[flag3];
21553
21575
  }
21554
21576
  }
21555
21577
  if (args5.cta !== void 0) {
@@ -22204,11 +22226,11 @@ function resolveDateRange(args5) {
22204
22226
  return { start, end };
22205
22227
  }
22206
22228
  const lastDaysRaw = args5["last-days"];
22207
- const days = lastDaysRaw ? Number(lastDaysRaw) : 7;
22208
- if (!Number.isFinite(days) || days < 1 || days > 730) {
22229
+ const days2 = lastDaysRaw ? Number(lastDaysRaw) : 7;
22230
+ if (!Number.isFinite(days2) || days2 < 1 || days2 > 730) {
22209
22231
  handleLinkedinError(new Error(`Invalid --last-days "${lastDaysRaw}". Use a positive number \u2264 730.`));
22210
22232
  }
22211
- return { start: daysAgoIso2(days), end: todayIso2() };
22233
+ return { start: daysAgoIso2(days2), end: todayIso2() };
22212
22234
  }
22213
22235
  function resolveScopeIds(args5, level) {
22214
22236
  if (level === "account") {
@@ -22336,7 +22358,7 @@ Examples \u2014 common AI questions:
22336
22358
  }
22337
22359
  const range = resolveDateRange(args5);
22338
22360
  const metrics = commaSplit(args5.metrics);
22339
- const limit = args5.limit ? Number(args5.limit) : 1e3;
22361
+ const limit2 = args5.limit ? Number(args5.limit) : 1e3;
22340
22362
  const request = {
22341
22363
  level,
22342
22364
  ids,
@@ -22346,7 +22368,7 @@ Examples \u2014 common AI questions:
22346
22368
  start: range.start,
22347
22369
  end: range.end,
22348
22370
  granularity,
22349
- limit
22371
+ limit: limit2
22350
22372
  };
22351
22373
  try {
22352
22374
  const data = await apiPost("/api/ads/linkedin/analytics", {
@@ -23138,8 +23160,8 @@ function numberOf2(v) {
23138
23160
  }
23139
23161
  return 0;
23140
23162
  }
23141
- function topByImpressions(rows, limit) {
23142
- return [...rows].sort((a, b) => numberOf2(b.impressions) - numberOf2(a.impressions)).slice(0, limit);
23163
+ function topByImpressions(rows, limit2) {
23164
+ return [...rows].sort((a, b) => numberOf2(b.impressions) - numberOf2(a.impressions)).slice(0, limit2);
23143
23165
  }
23144
23166
  var demographicsCommand = defineCommand45({
23145
23167
  meta: {
@@ -23230,11 +23252,11 @@ function resolveRange(args5) {
23230
23252
  if (start) {
23231
23253
  return { start, end };
23232
23254
  }
23233
- const days = args5["last-days"] ? Number(args5["last-days"]) : 30;
23234
- if (!Number.isFinite(days) || days < 1) {
23255
+ const days2 = args5["last-days"] ? Number(args5["last-days"]) : 30;
23256
+ if (!Number.isFinite(days2) || days2 < 1) {
23235
23257
  handleLinkedinError(new Error("Invalid --last-days; must be a positive integer."));
23236
23258
  }
23237
- return { start: daysAgoIso2(days), end: todayIso2() };
23259
+ return { start: daysAgoIso2(days2), end: todayIso2() };
23238
23260
  }
23239
23261
 
23240
23262
  // src/commands/ads/linkedin/draft.ts
@@ -25820,7 +25842,7 @@ function mergePayload3(file, flags) {
25820
25842
  }
25821
25843
  return merged;
25822
25844
  }
25823
- function parseMoneyFlag2(amount, currency, flag2) {
25845
+ function parseMoneyFlag2(amount, currency, flag3) {
25824
25846
  if (amount === void 0 || amount === null || amount === "") {
25825
25847
  return void 0;
25826
25848
  }
@@ -25828,11 +25850,11 @@ function parseMoneyFlag2(amount, currency, flag2) {
25828
25850
  return { amount: String(amount) };
25829
25851
  }
25830
25852
  if (typeof currency !== "string" || currency.length !== 3) {
25831
- failWriteValidation3(`${flag2} got an invalid --currency (expected a 3-letter code like USD)`);
25853
+ failWriteValidation3(`${flag3} got an invalid --currency (expected a 3-letter code like USD)`);
25832
25854
  }
25833
25855
  return { amount: String(amount), currencyCode: currency.toUpperCase() };
25834
25856
  }
25835
- function parseDateFlag2(value, flag2) {
25857
+ function parseDateFlag2(value, flag3) {
25836
25858
  if (value === void 0 || value === null || value === "") {
25837
25859
  return void 0;
25838
25860
  }
@@ -25842,7 +25864,7 @@ function parseDateFlag2(value, flag2) {
25842
25864
  }
25843
25865
  const parsed = Date.parse(raw);
25844
25866
  if (Number.isNaN(parsed)) {
25845
- failWriteValidation3(`${flag2} must be an ISO date (2026-07-10) or epoch milliseconds`);
25867
+ failWriteValidation3(`${flag3} must be an ISO date (2026-07-10) or epoch milliseconds`);
25846
25868
  }
25847
25869
  return parsed;
25848
25870
  }
@@ -25856,13 +25878,13 @@ function listFlag2(value) {
25856
25878
  function upper(value) {
25857
25879
  return typeof value === "string" && value.length > 0 ? value.toUpperCase() : void 0;
25858
25880
  }
25859
- function numberFlag(value, flag2) {
25881
+ function numberFlag(value, flag3) {
25860
25882
  if (value === void 0 || value === null || value === "") {
25861
25883
  return void 0;
25862
25884
  }
25863
25885
  const n = Number(value);
25864
25886
  if (!Number.isFinite(n)) {
25865
- failWriteValidation3(`${flag2} must be a number`);
25887
+ failWriteValidation3(`${flag3} must be a number`);
25866
25888
  }
25867
25889
  return n;
25868
25890
  }
@@ -27291,8 +27313,8 @@ function shouldAutoAsync(body) {
27291
27313
  return true;
27292
27314
  }
27293
27315
  if (body.since && body.until) {
27294
- const days = (Date.parse(body.until) - Date.parse(body.since)) / 864e5;
27295
- if (days > 90) {
27316
+ const days2 = (Date.parse(body.until) - Date.parse(body.since)) / 864e5;
27317
+ if (days2 > 90) {
27296
27318
  return true;
27297
27319
  }
27298
27320
  }
@@ -27761,14 +27783,14 @@ async function runStats(args5) {
27761
27783
  if (!pixelId) {
27762
27784
  handleMetaError(new Error("--pixel-id is required when --stats is set"));
27763
27785
  }
27764
- const days = args5.days ? Number(args5.days) : 7;
27765
- if (!Number.isInteger(days) || days <= 0 || days > 90) {
27786
+ const days2 = args5.days ? Number(args5.days) : 7;
27787
+ if (!Number.isInteger(days2) || days2 <= 0 || days2 > 90) {
27766
27788
  handleMetaError(new Error("--days must be a positive integer \u2264 90"));
27767
27789
  }
27768
27790
  const params2 = {
27769
27791
  "pixel-id": pixelId,
27770
27792
  aggregation: args5.aggregation ?? "event",
27771
- "start-time": daysAgoIso(days),
27793
+ "start-time": daysAgoIso(days2),
27772
27794
  "end-time": todayIso()
27773
27795
  };
27774
27796
  if (args5["no-cache"]) params2["skip-cache"] = "true";
@@ -28428,10 +28450,10 @@ function parseXMoney(value) {
28428
28450
  }
28429
28451
  return { ok: true, micros: Math.round(amount * 1e6) };
28430
28452
  }
28431
- function microsFromArg(value, flag2) {
28453
+ function microsFromArg(value, flag3) {
28432
28454
  const parsed = parseXMoney(value);
28433
28455
  if (!parsed.ok) {
28434
- failWriteValidation4(`${flag2} ${parsed.reason}`);
28456
+ failWriteValidation4(`${flag3} ${parsed.reason}`);
28435
28457
  }
28436
28458
  return parsed.micros;
28437
28459
  }
@@ -30215,14 +30237,14 @@ function resolveQueryFromArgs(args5, timeZone) {
30215
30237
  granularity = args5.granularity ?? preset.granularity;
30216
30238
  placement = args5.placement ?? preset.placement;
30217
30239
  const requested = args5.days ? Number(args5.days) : preset.defaultDays;
30218
- const days = Math.min(SYNC_MAX_DAYS, Math.max(1, requested));
30240
+ const days2 = Math.min(SYNC_MAX_DAYS, Math.max(1, requested));
30219
30241
  if (requested > SYNC_MAX_DAYS) {
30220
30242
  hints2.push(
30221
- `Sync stats cover at most ${SYNC_MAX_DAYS} days, so '${preset.name}' was shortened to the last ${days}. For the full window run: baker ads x stats job --entity ${entity} --entity-ids <ids> --metric-groups ${metricGroups?.join(",")} --start-time <start> --end-time <end>`
30243
+ `Sync stats cover at most ${SYNC_MAX_DAYS} days, so '${preset.name}' was shortened to the last ${days2}. For the full window run: baker ads x stats job --entity ${entity} --entity-ids <ids> --metric-groups ${metricGroups?.join(",")} --start-time <start> --end-time <end>`
30222
30244
  );
30223
30245
  }
30224
30246
  const floor = granularity === "DAY" ? (d) => accountMidnightIso(d, timeZone) : isoHourFloor;
30225
- startTime ??= floor(days);
30247
+ startTime ??= floor(days2);
30226
30248
  endTime ??= floor(0);
30227
30249
  }
30228
30250
  const entityIds = parseCsv5(args5["entity-ids"]);
@@ -30920,6 +30942,48 @@ var EVENT_FIELDS = [
30920
30942
  name: "properties",
30921
30943
  need: "optional",
30922
30944
  takes: "A flat object of strings \u2014 anything you want to break the event down by later."
30945
+ },
30946
+ {
30947
+ name: "items",
30948
+ need: "optional",
30949
+ takes: "What was bought, one entry per product line \u2014 for a purchase, a cart or a refund.",
30950
+ note: `Each line names its product by at least one of the ids below. value still carries the whole event's money. An order with more than ${EVENT_ITEMS_MAX} lines keeps the first ${EVENT_ITEMS_MAX}.`
30951
+ },
30952
+ {
30953
+ name: "product_id, variant_id, feed_id",
30954
+ need: "conditional",
30955
+ under: true,
30956
+ takes: "Your store's product id, its variant id, or the id the product has in your ads catalog. At least one per line, up to 255 characters each."
30957
+ },
30958
+ {
30959
+ name: "quantity",
30960
+ need: "optional",
30961
+ under: true,
30962
+ takes: `How many: a whole number from 1 to ${MAX_ITEM_QUANTITY.toLocaleString("en-US")}.`
30963
+ },
30964
+ {
30965
+ name: "price",
30966
+ need: "optional",
30967
+ under: true,
30968
+ takes: "The price of one unit, in the event's currency \u2014 so it needs currency. A decimal like value."
30969
+ },
30970
+ {
30971
+ name: "consent",
30972
+ need: "optional",
30973
+ takes: "What the person allowed, if your site asked them.",
30974
+ note: "Send it whenever you know it. A person who said no to marketing is still counted in your reports, and is never matched or sent to an ad platform."
30975
+ },
30976
+ {
30977
+ name: "analytics",
30978
+ need: "optional",
30979
+ under: true,
30980
+ takes: "granted or denied \u2014 whether they allowed being measured. Denied keeps the event and forgets who it was."
30981
+ },
30982
+ {
30983
+ name: "marketing",
30984
+ need: "optional",
30985
+ under: true,
30986
+ takes: "granted or denied \u2014 whether they allowed their visit to be used for advertising."
30923
30987
  }
30924
30988
  ];
30925
30989
  var RESPONSE_FIELDS = [
@@ -31249,7 +31313,37 @@ var ingestEventSchema = z39.strictObject({
31249
31313
  * left alone: masking one would rename the field and could collide with a
31250
31314
  * sibling.
31251
31315
  */
31252
- properties: z39.record(z39.string(), z39.string()).nullish()
31316
+ properties: z39.record(z39.string(), z39.string()).nullish(),
31317
+ /**
31318
+ * What was bought, one entry per line — for a purchase, a cart, a refund.
31319
+ *
31320
+ * Flat and Meta-like for the same reason as the rest of this body: it is
31321
+ * Meta's `contents` (`id`, `quantity`, `item_price`) and GA4's `items`
31322
+ * (`item_id`, `quantity`, `price`) under one spelling a sender can map from
31323
+ * either. A line names what it is by any of three ids — the store's product,
31324
+ * its variant, or the id the product has in an ads catalog. `price` is the
31325
+ * *unit* price in the event's `currency`; `value` stays the whole event's
31326
+ * money. Structure only here; `validateItems` judges each line.
31327
+ */
31328
+ items: z39.array(
31329
+ z39.strictObject({
31330
+ product_id: z39.string().nullish(),
31331
+ variant_id: z39.string().nullish(),
31332
+ feed_id: z39.string().nullish(),
31333
+ quantity: z39.union([z39.string(), z39.number()]).nullish(),
31334
+ price: z39.union([z39.string(), z39.number()]).nullish()
31335
+ })
31336
+ ).nullish(),
31337
+ /**
31338
+ * What the person allowed, each `granted` or `denied`: `analytics` (being
31339
+ * measured at all) and `marketing` (being matched and sent to an ad
31340
+ * platform). Absent means the sender did not say. Enforced before anything
31341
+ * is stored — see `./consent`. `validateConsent` judges the values.
31342
+ */
31343
+ consent: z39.strictObject({
31344
+ analytics: z39.string().nullish(),
31345
+ marketing: z39.string().nullish()
31346
+ }).nullish()
31253
31347
  });
31254
31348
  var ingestBatchSchema = z39.strictObject({
31255
31349
  events: z39.array(ingestEventSchema).min(1).max(EVENT_INGEST_BATCH_MAX)
@@ -31455,32 +31549,32 @@ function isUsableStepNameKey(key) {
31455
31549
  // src/commands/analytics/conversionArgs.ts
31456
31550
  var ConversionArgsError = class extends Error {
31457
31551
  };
31458
- function allValues(flag2, argv) {
31552
+ function allValues(flag3, argv) {
31459
31553
  const values = [];
31460
31554
  for (let i = 0; i < argv.length; i += 1) {
31461
31555
  const arg = argv[i];
31462
- if (arg === flag2) {
31556
+ if (arg === flag3) {
31463
31557
  const next = argv[i + 1];
31464
31558
  if (next !== void 0 && !next.startsWith("--")) values.push(next);
31465
31559
  continue;
31466
31560
  }
31467
- if (arg.startsWith(`${flag2}=`)) values.push(arg.slice(flag2.length + 1));
31561
+ if (arg.startsWith(`${flag3}=`)) values.push(arg.slice(flag3.length + 1));
31468
31562
  }
31469
31563
  return values.map((value) => value.trim()).filter((value) => value !== "");
31470
31564
  }
31471
- function onlyOnce(value, flag2, argv) {
31565
+ function onlyOnce(value, flag3, argv) {
31472
31566
  if (value === void 0) return void 0;
31473
- const given = argv.filter((arg) => arg === flag2 || arg.startsWith(`${flag2}=`)).length;
31567
+ const given = argv.filter((arg) => arg === flag3 || arg.startsWith(`${flag3}=`)).length;
31474
31568
  if (given > 1) {
31475
31569
  throw new ConversionArgsError(
31476
- `${flag2} was given ${given} times and only the last would have been used. It takes one value.`
31570
+ `${flag3} was given ${given} times and only the last would have been used. It takes one value.`
31477
31571
  );
31478
31572
  }
31479
31573
  const trimmed = String(value).trim();
31480
31574
  return trimmed === "" ? void 0 : trimmed;
31481
31575
  }
31482
- function repeatable(flag2, cittyValue, argv) {
31483
- const fromArgv = allValues(flag2, argv);
31576
+ function repeatable(flag3, cittyValue, argv) {
31577
+ const fromArgv = allValues(flag3, argv);
31484
31578
  if (fromArgv.length > 0) return fromArgv;
31485
31579
  if (cittyValue === void 0) return [];
31486
31580
  const trimmed = String(cittyValue).trim();
@@ -31520,11 +31614,11 @@ function checkEventKeys(eventKeys) {
31520
31614
  }
31521
31615
  function readDays(raw) {
31522
31616
  if (raw === void 0) return void 0;
31523
- const days = Number(raw);
31524
- if (!Number.isFinite(days) || days <= 0) {
31617
+ const days2 = Number(raw);
31618
+ if (!Number.isFinite(days2) || days2 <= 0) {
31525
31619
  throw new ConversionArgsError("--days must be a positive number of days.");
31526
31620
  }
31527
- return days;
31621
+ return days2;
31528
31622
  }
31529
31623
  function parseConversionsArgs(raw, argv) {
31530
31624
  const countMode = onlyOnce(raw["count-mode"], "--count-mode", argv);
@@ -32470,8 +32564,8 @@ var SET_FLAGS = ["--source-event", "--platform", "--tag", "--event"];
32470
32564
  function parseSendsArgs(raw, argv, now = Date.now()) {
32471
32565
  const set = parseSetFlags(raw, argv, now);
32472
32566
  const remove = onlyOnce(raw.remove, "--remove", argv);
32473
- const days = raw.days === void 0 ? void 0 : Number(raw.days);
32474
- if (days !== void 0 && (!Number.isInteger(days) || days <= 0 || days > 400)) {
32567
+ const days2 = raw.days === void 0 ? void 0 : Number(raw.days);
32568
+ if (days2 !== void 0 && (!Number.isInteger(days2) || days2 <= 0 || days2 > 400)) {
32475
32569
  throw new ConversionArgsError("--days must be a whole number of days, from 1 to 400.");
32476
32570
  }
32477
32571
  const uploads = onlyOnce(raw.uploads, "--uploads", argv);
@@ -32486,17 +32580,17 @@ function parseSendsArgs(raw, argv, now = Date.now()) {
32486
32580
  return {
32487
32581
  ...set === void 0 ? {} : { set },
32488
32582
  ...remove === void 0 ? {} : { remove },
32489
- ...days === void 0 ? {} : { days },
32583
+ ...days2 === void 0 ? {} : { days: days2 },
32490
32584
  ...uploads === void 0 ? {} : { uploads },
32491
32585
  ...raw.failed === true ? { failedOnly: true } : {},
32492
32586
  ...raw.skipped === true ? { skippedOnly: true } : {},
32493
32587
  full: raw.full === true
32494
32588
  };
32495
32589
  }
32496
- function flagValueAt(argv, flag2, index) {
32590
+ function flagValueAt(argv, flag3, index) {
32497
32591
  const arg = argv[index] ?? "";
32498
- if (arg === flag2) return argv[index + 1];
32499
- return arg.startsWith(`${flag2}=`) ? arg.slice(flag2.length + 1) : void 0;
32592
+ if (arg === flag3) return argv[index + 1];
32593
+ return arg.startsWith(`${flag3}=`) ? arg.slice(flag3.length + 1) : void 0;
32500
32594
  }
32501
32595
  function parseParameterArgs(argv) {
32502
32596
  const parameters = {};
@@ -32527,7 +32621,7 @@ function parseSetFlags(raw, argv, now) {
32527
32621
  "--tag": onlyOnce(raw.tag, "--tag", argv),
32528
32622
  "--event": onlyOnce(raw.event, "--event", argv)
32529
32623
  };
32530
- const given = SET_FLAGS.filter((flag2) => values[flag2] !== void 0);
32624
+ const given = SET_FLAGS.filter((flag3) => values[flag3] !== void 0);
32531
32625
  const set = raw.set === true;
32532
32626
  if (!set) {
32533
32627
  if (given.length > 0) {
@@ -32537,7 +32631,7 @@ function parseSetFlags(raw, argv, now) {
32537
32631
  }
32538
32632
  return void 0;
32539
32633
  }
32540
- const missing = SET_FLAGS.filter((flag2) => values[flag2] === void 0);
32634
+ const missing = SET_FLAGS.filter((flag3) => values[flag3] === void 0);
32541
32635
  if (missing.length > 0) {
32542
32636
  throw new ConversionArgsError(
32543
32637
  `--set needs ${SET_FLAGS.join(", ")}; missing ${missing.join(", ")}. Copy --tag from platforms[].tags of a plain \`baker analytics sends\`, never from memory.`
@@ -33192,15 +33286,15 @@ registerSchema({
33192
33286
  description: "Where events reach Baker from a server: the client's own server door, and HubSpot events Baker reads \u2014 what each one watches and reads, and how it is doing",
33193
33287
  args: args3
33194
33288
  });
33195
- function flagValueAt2(argv, flag2, index) {
33289
+ function flagValueAt2(argv, flag3, index) {
33196
33290
  const arg = argv[index] ?? "";
33197
- if (arg === flag2) return argv[index + 1];
33198
- return arg.startsWith(`${flag2}=`) ? arg.slice(flag2.length + 1) : void 0;
33291
+ if (arg === flag3) return argv[index + 1];
33292
+ return arg.startsWith(`${flag3}=`) ? arg.slice(flag3.length + 1) : void 0;
33199
33293
  }
33200
- function parseRepeated(argv, flag2) {
33294
+ function parseRepeated(argv, flag3) {
33201
33295
  const values = [];
33202
33296
  for (let index = 0; index < argv.length; index += 1) {
33203
- const value = flagValueAt2(argv, flag2, index);
33297
+ const value = flagValueAt2(argv, flag3, index);
33204
33298
  if (value !== void 0) values.push(value);
33205
33299
  }
33206
33300
  return values;
@@ -34084,10 +34178,10 @@ Examples:
34084
34178
  });
34085
34179
  process.exit(1);
34086
34180
  }
34087
- const days = raw.days === void 0 ? void 0 : Number(raw.days);
34181
+ const days2 = raw.days === void 0 ? void 0 : Number(raw.days);
34088
34182
  const response = await apiPost("/api/analytics/tracking", {
34089
34183
  ...platform ? { platform } : {},
34090
- ...days !== void 0 && Number.isFinite(days) ? { days } : {}
34184
+ ...days2 !== void 0 && Number.isFinite(days2) ? { days: days2 } : {}
34091
34185
  });
34092
34186
  writeJsonEnvelope(response);
34093
34187
  } catch (err) {
@@ -34318,12 +34412,12 @@ Examples:
34318
34412
  args: args5,
34319
34413
  run: async ({ args: raw }) => {
34320
34414
  try {
34321
- const limit = raw.limit === void 0 ? void 0 : Number(raw.limit);
34415
+ const limit2 = raw.limit === void 0 ? void 0 : Number(raw.limit);
34322
34416
  const response = await apiPost("/api/analytics/arrivals", {
34323
34417
  ...raw.mode ? { mode: String(raw.mode) } : {},
34324
34418
  ...raw.outcome ? { outcome: String(raw.outcome) } : {},
34325
34419
  ...raw.event ? { eventName: String(raw.event) } : {},
34326
- ...limit !== void 0 && Number.isFinite(limit) ? { limit } : {},
34420
+ ...limit2 !== void 0 && Number.isFinite(limit2) ? { limit: limit2 } : {},
34327
34421
  ...raw.full ? { full: true } : {}
34328
34422
  });
34329
34423
  writeJsonEnvelope(response);
@@ -34775,8 +34869,8 @@ function parseGuardrails(value) {
34775
34869
  }
34776
34870
  function buildProfilePatch(args5) {
34777
34871
  const patch = {};
34778
- for (const [flag2, field] of TEXT_FIELDS) {
34779
- const value = args5[flag2];
34872
+ for (const [flag3, field] of TEXT_FIELDS) {
34873
+ const value = args5[flag3];
34780
34874
  if (typeof value === "string") {
34781
34875
  Object.assign(patch, { [field]: value.trim() });
34782
34876
  }
@@ -35632,7 +35726,7 @@ function undisambiguatedSubsets(faces) {
35632
35726
  if (new Set(faces.map((face) => face.subset)).size < 2) return [];
35633
35727
  return [...new Set(faces.filter((face) => !face.unicodeRange).map((face) => face.subset))];
35634
35728
  }
35635
- function suggestFamilies(wanted, catalogue, limit = 5) {
35729
+ function suggestFamilies(wanted, catalogue, limit2 = 5) {
35636
35730
  const key = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
35637
35731
  const target2 = key(wanted);
35638
35732
  if (!target2) return [];
@@ -35645,7 +35739,7 @@ function suggestFamilies(wanted, catalogue, limit = 5) {
35645
35739
  const shared = words2.filter((w) => w.length > 2 && candidate.includes(w)).length;
35646
35740
  return { family, score: shared > 0 ? 3 + (words2.length - shared) : Number.POSITIVE_INFINITY };
35647
35741
  }).filter((c) => Number.isFinite(c.score)).sort((a, b) => a.score - b.score || a.family.localeCompare(b.family));
35648
- return scored.slice(0, limit).map((c) => c.family);
35742
+ return scored.slice(0, limit2).map((c) => c.family);
35649
35743
  }
35650
35744
  function asFontsourceMeta(body) {
35651
35745
  if (typeof body !== "object" || body === null) return null;
@@ -35782,16 +35876,16 @@ function googleFontRequests(css) {
35782
35876
  }
35783
35877
  return out;
35784
35878
  }
35785
- function resolveFetchWeights(flag2, declared) {
35786
- if (flag2) {
35787
- return String(flag2).split(",").map((w) => Number(w.trim())).filter((w) => Number.isInteger(w) && w >= 100 && w <= 900);
35879
+ function resolveFetchWeights(flag3, declared) {
35880
+ if (flag3) {
35881
+ return String(flag3).split(",").map((w) => Number(w.trim())).filter((w) => Number.isInteger(w) && w >= 100 && w <= 900);
35788
35882
  }
35789
35883
  return declared?.length ? declared : [400];
35790
35884
  }
35791
35885
  var DEFAULT_SUBSETS = ["latin"];
35792
- function resolveFetchSubsets(flag2, selfHosted) {
35793
- const asked = String(flag2 ?? "").split(",").map((subset) => subset.trim()).filter(Boolean);
35794
- const named = flag2 ? [...new Set(asked)] : selfHosted;
35886
+ function resolveFetchSubsets(flag3, selfHosted) {
35887
+ const asked = String(flag3 ?? "").split(",").map((subset) => subset.trim()).filter(Boolean);
35888
+ const named = flag3 ? [...new Set(asked)] : selfHosted;
35795
35889
  return named.length > 0 ? named : [...DEFAULT_SUBSETS];
35796
35890
  }
35797
35891
  function resolveFetchStyles(italic, selfHosted) {
@@ -41537,11 +41631,11 @@ function classifyAudio(volumedetect, hasAudioStream) {
41537
41631
  }
41538
41632
  var RENDER_REVIEW_UNAVAILABLE = "The render was NOT reviewed \u2014 no model gateway key in this environment. Watch it yourself before sending it on.";
41539
41633
  var REVIEW_CONCURRENCY = 3;
41540
- async function mapWithLimit(items, limit, fn) {
41634
+ async function mapWithLimit(items, limit2, fn) {
41541
41635
  const out = new Array(items.length);
41542
41636
  let next = 0;
41543
41637
  await Promise.all(
41544
- Array.from({ length: Math.min(limit, items.length) }, async () => {
41638
+ Array.from({ length: Math.min(limit2, items.length) }, async () => {
41545
41639
  for (let i = next++; i < items.length; i = next++) {
41546
41640
  out[i] = await fn(items[i]);
41547
41641
  }
@@ -42864,8 +42958,8 @@ async function resolveCastAvatar(handle) {
42864
42958
  };
42865
42959
  }
42866
42960
  }
42867
- async function resolveCastAvatars(flag2) {
42868
- const handles = String(flag2 ?? "").split(",").map((h) => h.trim()).filter(Boolean);
42961
+ async function resolveCastAvatars(flag3) {
42962
+ const handles = String(flag3 ?? "").split(",").map((h) => h.trim()).filter(Boolean);
42869
42963
  const avatars = [];
42870
42964
  for (const handle of handles) {
42871
42965
  const resolved = await resolveCastAvatar(handle);
@@ -42978,7 +43072,7 @@ function fail3(code, message) {
42978
43072
  process.exit(2);
42979
43073
  }
42980
43074
  function resolveModels(args5) {
42981
- const pick = (flag2, kind, fallback) => args5[flag2] ? String(args5[flag2]) : resolveModel(kind, fallback);
43075
+ const pick = (flag3, kind, fallback) => args5[flag3] ? String(args5[flag3]) : resolveModel(kind, fallback);
42982
43076
  return {
42983
43077
  describeModel: pick("describe-model", "image_describe", "~google/gemini-pro-latest"),
42984
43078
  selectModel: pick("select-model", "text_generate", "~google/gemini-flash-latest"),
@@ -42996,13 +43090,13 @@ var PLATFORM_PLACEMENTS = {
42996
43090
  other: void 0
42997
43091
  };
42998
43092
  function resolvePlacements(placementsFlag, definitionPlatform) {
42999
- const flag2 = placementsFlag?.trim();
43000
- if (flag2 === void 0 || flag2 === "") return { ok: true, placements: PLATFORM_PLACEMENTS[definitionPlatform] };
43001
- if (flag2 === "none") return { ok: true, placements: void 0 };
43002
- if (AD_FORMAT_PLATFORMS.includes(flag2)) return { ok: true, placements: flag2 };
43093
+ const flag3 = placementsFlag?.trim();
43094
+ if (flag3 === void 0 || flag3 === "") return { ok: true, placements: PLATFORM_PLACEMENTS[definitionPlatform] };
43095
+ if (flag3 === "none") return { ok: true, placements: void 0 };
43096
+ if (AD_FORMAT_PLATFORMS.includes(flag3)) return { ok: true, placements: flag3 };
43003
43097
  return {
43004
43098
  ok: false,
43005
- message: `--placements "${flag2}" is not a valid placement set. Use one of: ${AD_FORMAT_PLATFORMS.join(", ")}, or "none" for a single base ad.`
43099
+ message: `--placements "${flag3}" is not a valid placement set. Use one of: ${AD_FORMAT_PLATFORMS.join(", ")}, or "none" for a single base ad.`
43006
43100
  };
43007
43101
  }
43008
43102
  function regionCount(layout) {
@@ -45017,7 +45111,7 @@ function parseBudget(raw) {
45017
45111
  throw new Error(`--budget must be one of ${VIDEO_BUDGETS.join(", ")} (got "${v}")`);
45018
45112
  }
45019
45113
  function resolveModels2(args5) {
45020
- const pick = (flag2, kind, fallback) => args5[flag2] ? String(args5[flag2]) : resolveModel2(kind, fallback);
45114
+ const pick = (flag3, kind, fallback) => args5[flag3] ? String(args5[flag3]) : resolveModel2(kind, fallback);
45021
45115
  return {
45022
45116
  // Flash by default: ~2-3× faster per call than Pro, which keeps each
45023
45117
  // per-scene deconstruct step well inside the action time budget and turns a
@@ -45792,9 +45886,9 @@ var listCommand13 = defineCommand121({
45792
45886
  }
45793
45887
  body.status = args5.status;
45794
45888
  }
45795
- const limit = parseBoundedInt(args5.limit, "limit", 1, 100);
45796
- if (limit !== void 0) {
45797
- body.limit = limit;
45889
+ const limit2 = parseBoundedInt(args5.limit, "limit", 1, 100);
45890
+ if (limit2 !== void 0) {
45891
+ body.limit = limit2;
45798
45892
  }
45799
45893
  if (args5.full) {
45800
45894
  body.full = true;
@@ -45891,9 +45985,9 @@ var transcriptCommand = defineCommand121({
45891
45985
  run: async ({ args: args5 }) => {
45892
45986
  try {
45893
45987
  const body = { chatId: args5.id };
45894
- const limit = parseBoundedInt(args5.limit, "limit", 1, 1e3);
45895
- if (limit !== void 0) {
45896
- body.limit = limit;
45988
+ const limit2 = parseBoundedInt(args5.limit, "limit", 1, 1e3);
45989
+ if (limit2 !== void 0) {
45990
+ body.limit = limit2;
45897
45991
  }
45898
45992
  if (args5.full) {
45899
45993
  body.full = true;
@@ -46692,12 +46786,12 @@ function count(value, unit) {
46692
46786
  return `about ${value} ${unit}${value === 1 ? "" : "s"}`;
46693
46787
  }
46694
46788
  function describeWait(earliestDecisionAt, now) {
46695
- const days = (earliestDecisionAt - now) / DAY_MS5;
46696
- if (days <= 0) return "any moment now";
46697
- if (days < 1) return "within a day";
46698
- if (days < 14) return count(Math.round(days), "day");
46699
- if (days < 60) return count(Math.round(days / 7), "week");
46700
- return count(Math.round(days / 30), "month");
46789
+ const days2 = (earliestDecisionAt - now) / DAY_MS5;
46790
+ if (days2 <= 0) return "any moment now";
46791
+ if (days2 < 1) return "within a day";
46792
+ if (days2 < 14) return count(Math.round(days2), "day");
46793
+ if (days2 < 60) return count(Math.round(days2 / 7), "week");
46794
+ return count(Math.round(days2 / 30), "month");
46701
46795
  }
46702
46796
  function roughCount(count2) {
46703
46797
  if (count2 < 10) return `about ${Math.max(1, Math.round(count2)).toLocaleString("en-US")}`;
@@ -47839,12 +47933,12 @@ var historyCommand = defineCommand124({
47839
47933
  limit: { type: "string", description: "At most this many, newest first", required: false }
47840
47934
  },
47841
47935
  run: async ({ args: args5 }) => {
47842
- const limit = args5.limit === void 0 ? void 0 : Number(args5.limit);
47843
- if (limit !== void 0 && !(Number.isInteger(limit) && limit > 0)) fail5("`--limit` is a whole number above zero");
47936
+ const limit2 = args5.limit === void 0 ? void 0 : Number(args5.limit);
47937
+ if (limit2 !== void 0 && !(Number.isInteger(limit2) && limit2 > 0)) fail5("`--limit` is a whole number above zero");
47844
47938
  try {
47845
47939
  const response = await apiPost("/api/experiments/history", {
47846
47940
  ...args5.landing ? { landingSlug: String(args5.landing) } : {},
47847
- ...limit === void 0 ? {} : { limit }
47941
+ ...limit2 === void 0 ? {} : { limit: limit2 }
47848
47942
  });
47849
47943
  writeJsonEnvelope({
47850
47944
  ok: true,
@@ -50325,8 +50419,8 @@ function parseFunnelArgs(raw) {
50325
50419
  "Give one of --steps, --step, --remove-step, --preset, --add-trigger or --remove-trigger, not two."
50326
50420
  );
50327
50421
  }
50328
- const days = raw.days === void 0 ? void 0 : Number(raw.days);
50329
- if (days !== void 0 && (!Number.isInteger(days) || days <= 0 || days > 400)) {
50422
+ const days2 = raw.days === void 0 ? void 0 : Number(raw.days);
50423
+ if (days2 !== void 0 && (!Number.isInteger(days2) || days2 <= 0 || days2 > 400)) {
50330
50424
  throw new FunnelArgsError("--days must be a whole number of days, from 1 to 400.");
50331
50425
  }
50332
50426
  return {
@@ -50338,7 +50432,7 @@ function parseFunnelArgs(raw) {
50338
50432
  ...removeTrigger === void 0 ? {} : { removeTrigger },
50339
50433
  templates: raw.templates === true,
50340
50434
  moments: raw.moments === true,
50341
- ...days === void 0 ? {} : { days }
50435
+ ...days2 === void 0 ? {} : { days: days2 }
50342
50436
  };
50343
50437
  }
50344
50438
  function funnelWrite(args5) {
@@ -50458,19 +50552,19 @@ function parsePreset(raw) {
50458
50552
  }
50459
50553
  return raw;
50460
50554
  }
50461
- function parseWayIn(stepKey, eventKey, flag2) {
50555
+ function parseWayIn(stepKey, eventKey, flag3) {
50462
50556
  if (stepKey === void 0) return void 0;
50463
50557
  if (typeof stepKey !== "string" || stepKey.trim() === "") {
50464
- throw new FunnelArgsError(`${flag2} takes the key of the step to change: \`${flag2} lead\`.`);
50558
+ throw new FunnelArgsError(`${flag3} takes the key of the step to change: \`${flag3} lead\`.`);
50465
50559
  }
50466
50560
  if (typeof eventKey !== "string" || eventKey.trim() === "") {
50467
- throw new FunnelArgsError(`${flag2} needs --event, naming what gets somebody there.`, {
50561
+ throw new FunnelArgsError(`${flag3} needs --event, naming what gets somebody there.`, {
50468
50562
  action: "read_the_moments",
50469
- explanation: `Run \`baker funnel --moments\` and copy an \`events[].eventKey\` \u2014 a Form's own outcome is listed there at zero before anybody has filled it in. Then: \`${flag2} ${stepKey.trim()} --event "<key>"\`.`
50563
+ explanation: `Run \`baker funnel --moments\` and copy an \`events[].eventKey\` \u2014 a Form's own outcome is listed there at zero before anybody has filled it in. Then: \`${flag3} ${stepKey.trim()} --event "<key>"\`.`
50470
50564
  });
50471
50565
  }
50472
50566
  const key = eventKey.trim();
50473
- if (flag2 === "--add-trigger" && parseEventKey(key) === null) {
50567
+ if (flag3 === "--add-trigger" && parseEventKey(key) === null) {
50474
50568
  throw new FunnelArgsError(`"${key}" is not an event key, so nothing would ever reach that step through it.`, {
50475
50569
  action: "read_the_moments",
50476
50570
  explanation: "Run `baker funnel --moments` and copy an `events[].eventKey`. They look like form:<form>:<step>:<trigger>, submit:<form>, page:<name> or view:<landing>."
@@ -50513,7 +50607,7 @@ var args4 = {
50513
50607
  },
50514
50608
  preset: {
50515
50609
  type: "string",
50516
- description: "Draw one of the ready-made graphs instead of stating steps: b2b_sales, b2b_inbound, saas_self_serve, saas_sales_assisted, ecommerce_purchase. It draws over the whole funnel \u2014 every company already has one, so prefer --step to change a step. Run --templates first to see what each one contains",
50610
+ description: `Draw one of the ready-made graphs instead of stating steps: b2b_sales, b2b_inbound, saas_self_serve, saas_sales_assisted, ecommerce_purchase. It draws over the whole funnel \u2014 every company already has one, so prefer --step to change a step. Run --templates first to see what each one contains. With a Shopify store connected, ecommerce_purchase comes wired to the store's own events (page:view_item, page:add_to_cart, page:begin_checkout, and page:purchase for a paid order); "Ordered again" stays unwired`,
50517
50611
  required: false
50518
50612
  },
50519
50613
  moments: {
@@ -50736,29 +50830,29 @@ function requireTarget5(args5, entity) {
50736
50830
  }
50737
50831
  return target2;
50738
50832
  }
50739
- function readJsonSource(args5, flag2) {
50740
- const inline = args5[flag2];
50833
+ function readJsonSource(args5, flag3) {
50834
+ const inline = args5[flag3];
50741
50835
  const file = args5.file;
50742
50836
  const raw = typeof file === "string" && file.length > 0 ? readFileSync17(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
50743
50837
  if (raw === void 0) {
50744
- failValidation3(`pass --${flag2} with inline JSON or --file with a path to a JSON file`);
50838
+ failValidation3(`pass --${flag3} with inline JSON or --file with a path to a JSON file`);
50745
50839
  }
50746
50840
  try {
50747
50841
  return JSON.parse(raw);
50748
50842
  } catch {
50749
- return failValidation3(`--${flag2} is not valid JSON`);
50843
+ return failValidation3(`--${flag3} is not valid JSON`);
50750
50844
  }
50751
50845
  }
50752
- function asObject(value, flag2, index) {
50846
+ function asObject(value, flag3, index) {
50753
50847
  if (!value || typeof value !== "object" || Array.isArray(value)) {
50754
50848
  failValidation3(
50755
- index === void 0 ? `--${flag2} must be a JSON object` : `--${flag2}[${index}] must be a JSON object`
50849
+ index === void 0 ? `--${flag3} must be a JSON object` : `--${flag3}[${index}] must be a JSON object`
50756
50850
  );
50757
50851
  }
50758
50852
  return value;
50759
50853
  }
50760
- function loadJsonArg(args5, flag2 = "json") {
50761
- return asObject(readJsonSource(args5, flag2), flag2);
50854
+ function loadJsonArg(args5, flag3 = "json") {
50855
+ return asObject(readJsonSource(args5, flag3), flag3);
50762
50856
  }
50763
50857
  function loadJsonPayloads(args5) {
50764
50858
  const parsed = readJsonSource(args5, "json");
@@ -51608,18 +51702,18 @@ var RETENTION_BY_MONTHS = {
51608
51702
  "38": "THIRTY_EIGHT_MONTHS",
51609
51703
  "50": "FIFTY_MONTHS"
51610
51704
  };
51611
- function retentionValue(input, flag2) {
51705
+ function retentionValue(input, flag3) {
51612
51706
  const normalized = input.trim();
51613
51707
  const mapped = RETENTION_BY_MONTHS[normalized] ?? (Object.values(RETENTION_BY_MONTHS).includes(normalized.toUpperCase()) ? normalized.toUpperCase() : void 0);
51614
51708
  if (!mapped) {
51615
- failValidation3(`--${flag2} must be one of 2, 14, 26, 38 or 50 (months)`);
51709
+ failValidation3(`--${flag3} must be one of 2, 14, 26, 38 or 50 (months)`);
51616
51710
  }
51617
51711
  return mapped;
51618
51712
  }
51619
- function booleanArg(input, flag2) {
51713
+ function booleanArg(input, flag3) {
51620
51714
  if (input === "true") return true;
51621
51715
  if (input === "false") return false;
51622
- return failValidation3(`--${flag2} must be true or false`);
51716
+ return failValidation3(`--${flag3} must be true or false`);
51623
51717
  }
51624
51718
  var dataRetentionCommand = defineCommand136({
51625
51719
  meta: {
@@ -51955,10 +52049,10 @@ Examples:
51955
52049
  },
51956
52050
  run: async ({ args: args5 }) => {
51957
52051
  const payload = {};
51958
- for (const [flag2, field] of Object.entries(MEASUREMENT_FLAGS)) {
51959
- const value = args5[flag2];
52052
+ for (const [flag3, field] of Object.entries(MEASUREMENT_FLAGS)) {
52053
+ const value = args5[flag3];
51960
52054
  if (typeof value === "string") {
51961
- payload[field] = booleanArg(value, flag2);
52055
+ payload[field] = booleanArg(value, flag3);
51962
52056
  }
51963
52057
  }
51964
52058
  if (typeof args5["search-parameters"] === "string" && args5["search-parameters"].length > 0) {
@@ -52530,13 +52624,13 @@ var listCommand15 = defineCommand142({
52530
52624
  run: async ({ args: args5 }) => {
52531
52625
  try {
52532
52626
  const body = {};
52533
- const limit = parseBoundedInt2(args5.limit, "limit", 1, 200);
52534
- if (limit !== void 0) {
52535
- body.limit = limit;
52627
+ const limit2 = parseBoundedInt2(args5.limit, "limit", 1, 200);
52628
+ if (limit2 !== void 0) {
52629
+ body.limit = limit2;
52536
52630
  }
52537
- const days = parseBoundedInt2(args5.days, "days", 1, 365);
52538
- if (days !== void 0) {
52539
- body.days = days;
52631
+ const days2 = parseBoundedInt2(args5.days, "days", 1, 365);
52632
+ if (days2 !== void 0) {
52633
+ body.days = days2;
52540
52634
  }
52541
52635
  if (args5.category) {
52542
52636
  if (!CATEGORIES.includes(args5.category)) {
@@ -52921,20 +53015,20 @@ var formsSubmissionsCommand = defineCommand143({
52921
53015
  },
52922
53016
  run: async ({ args: args5 }) => {
52923
53017
  try {
52924
- const days = args5.days === void 0 ? 30 : Number(args5.days);
52925
- if (!Number.isInteger(days) || days < 1 || days > 365) {
53018
+ const days2 = args5.days === void 0 ? 30 : Number(args5.days);
53019
+ if (!Number.isInteger(days2) || days2 < 1 || days2 > 365) {
52926
53020
  throw new ApiError("VALIDATION_ERROR", "--days must be a whole number between 1 and 365.");
52927
53021
  }
52928
53022
  const response = await apiPost("/api/hubspot/forms/submissions", {
52929
53023
  formId: args5.formId,
52930
- days,
53024
+ days: days2,
52931
53025
  ...args5.full ? { full: true } : {}
52932
53026
  });
52933
53027
  const { inWindow, total, truncated, lastSubmittedAt } = response.data;
52934
53028
  const hints2 = [];
52935
53029
  if (inWindow === 0) {
52936
53030
  hints2.push(
52937
- total === 0 ? "This form has received nothing at all \u2014 check it is the form actually embedded on the page before concluding the campaign is at fault." : `No leads in the last ${days} days, though the form has ${total} older submission(s). It worked once and has gone quiet.`
53031
+ total === 0 ? "This form has received nothing at all \u2014 check it is the form actually embedded on the page before concluding the campaign is at fault." : `No leads in the last ${days2} days, though the form has ${total} older submission(s). It worked once and has gone quiet.`
52938
53032
  );
52939
53033
  }
52940
53034
  if (truncated) {
@@ -53121,15 +53215,15 @@ var contactsSummaryCommand = defineCommand143({
53121
53215
  },
53122
53216
  run: async ({ args: args5 }) => {
53123
53217
  try {
53124
- const days = args5.days === void 0 ? 30 : Number(args5.days);
53125
- if (!Number.isInteger(days) || days < 1 || days > 365) {
53218
+ const days2 = args5.days === void 0 ? 30 : Number(args5.days);
53219
+ if (!Number.isInteger(days2) || days2 < 1 || days2 > 365) {
53126
53220
  throw new ApiError("VALIDATION_ERROR", "--days must be a whole number between 1 and 365.");
53127
53221
  }
53128
- const response = await apiPost("/api/hubspot/contacts/summary", { days });
53222
+ const response = await apiPost("/api/hubspot/contacts/summary", { days: days2 });
53129
53223
  const { total, withOwner, withoutOwner, byStage, truncated } = response.data;
53130
53224
  const hints2 = [];
53131
53225
  if (total === 0) {
53132
- hints2.push(`No contacts were created in the last ${days} days \u2014 nothing reached the CRM in that window.`);
53226
+ hints2.push(`No contacts were created in the last ${days2} days \u2014 nothing reached the CRM in that window.`);
53133
53227
  } else if (withOwner === 0) {
53134
53228
  hints2.push(
53135
53229
  `None of these ${total} contacts has anyone assigned. That is the strongest available signal that the leads are arriving and nobody is picking them up \u2014 worth raising with the user.`
@@ -54214,7 +54308,7 @@ function emptyResultHints({ provider, hitCount, activeFilters, subject }) {
54214
54308
  return hints2;
54215
54309
  }
54216
54310
  function activeFilterFlags(args5, candidates) {
54217
- return candidates.filter((flag2) => args5[flag2] !== void 0 && args5[flag2] !== "").map((flag2) => `--${flag2}`);
54311
+ return candidates.filter((flag3) => args5[flag3] !== void 0 && args5[flag3] !== "").map((flag3) => `--${flag3}`);
54218
54312
  }
54219
54313
 
54220
54314
  // src/commands/images/find.ts
@@ -58567,8 +58661,8 @@ var favoritesCommand = defineCommand174({
58567
58661
  const params2 = {};
58568
58662
  const types = splitList2(args5.type);
58569
58663
  if (types) params2.type = types.join(",");
58570
- const limit = parseNumber(args5.limit);
58571
- if (limit !== void 0) params2.limit = String(limit);
58664
+ const limit2 = parseNumber(args5.limit);
58665
+ if (limit2 !== void 0) params2.limit = String(limit2);
58572
58666
  const data = await apiGet("/api/landing-inspiration/favorites", params2);
58573
58667
  const sections = Array.isArray(data?.sections) ? data.sections : [];
58574
58668
  const full = args5.full;
@@ -60053,8 +60147,8 @@ var inPageSegment = (options) => {
60053
60147
  };
60054
60148
  const leaves = [];
60055
60149
  const all = document.querySelectorAll("*");
60056
- const limit = Math.min(all.length, options.maxElements);
60057
- for (let i = 0; i < limit; i++) {
60150
+ const limit2 = Math.min(all.length, options.maxElements);
60151
+ for (let i = 0; i < limit2; i++) {
60058
60152
  const element = all[i];
60059
60153
  if (!element) continue;
60060
60154
  const tag = element.tagName.toLowerCase();
@@ -61139,8 +61233,8 @@ function buildSearchBody(args5) {
61139
61233
  if (minCraft !== void 0) body.minCraftScore = minCraft;
61140
61234
  const minFidelity = parseNumber(args5["min-fidelity"]);
61141
61235
  if (minFidelity !== void 0) body.minFidelity = minFidelity;
61142
- const limit = parseNumber(args5.limit);
61143
- body.limit = limit ?? 8;
61236
+ const limit2 = parseNumber(args5.limit);
61237
+ body.limit = limit2 ?? 8;
61144
61238
  body.scope = args5.scope === "all" ? "all" : "favorites";
61145
61239
  return body;
61146
61240
  }
@@ -61287,8 +61381,8 @@ function buildSequencesBody(args5) {
61287
61381
  const sectionType = splitList2(args5["section-type"]);
61288
61382
  if (sectionType) body.sectionType = sectionType;
61289
61383
  body.scope = args5.scope === "all" ? "all" : "favorites";
61290
- const limit = parseNumber(args5.limit);
61291
- if (limit !== void 0) body.limit = limit;
61384
+ const limit2 = parseNumber(args5.limit);
61385
+ if (limit2 !== void 0) body.limit = limit2;
61292
61386
  else if (args5.full) body.limit = MAX_PAGES;
61293
61387
  return body;
61294
61388
  }
@@ -62345,7 +62439,7 @@ Examples:
62345
62439
  const keyword = args5.keyword;
62346
62440
  const location2 = args5.location || void 0;
62347
62441
  const language = args5.language || void 0;
62348
- const limit = args5.limit ? Number(args5.limit) : void 0;
62442
+ const limit2 = args5.limit ? Number(args5.limit) : void 0;
62349
62443
  const skipCache = args5["no-cache"] ? true : void 0;
62350
62444
  const queryContext = buildResearchQueryContext(location2, language);
62351
62445
  try {
@@ -62353,7 +62447,7 @@ Examples:
62353
62447
  keyword,
62354
62448
  location: location2,
62355
62449
  language,
62356
- limit,
62450
+ limit: limit2,
62357
62451
  skipCache
62358
62452
  });
62359
62453
  warnDefaults(queryContext);
@@ -62423,7 +62517,7 @@ Examples:
62423
62517
  const keyword = args5.keyword;
62424
62518
  const location2 = args5.location || void 0;
62425
62519
  const language = args5.language || void 0;
62426
- const limit = args5.limit ? Number(args5.limit) : void 0;
62520
+ const limit2 = args5.limit ? Number(args5.limit) : void 0;
62427
62521
  const skipCache = args5["no-cache"] ? true : void 0;
62428
62522
  const queryContext = buildResearchQueryContext(location2, language);
62429
62523
  try {
@@ -62431,7 +62525,7 @@ Examples:
62431
62525
  keyword,
62432
62526
  location: location2,
62433
62527
  language,
62434
- limit,
62528
+ limit: limit2,
62435
62529
  skipCache
62436
62530
  });
62437
62531
  warnDefaults(queryContext);
@@ -62802,7 +62896,7 @@ Examples:
62802
62896
  const location2 = args5.location || void 0;
62803
62897
  const language = args5.language || void 0;
62804
62898
  const type = args5.type || void 0;
62805
- const limit = args5.limit ? Number(args5.limit) : void 0;
62899
+ const limit2 = args5.limit ? Number(args5.limit) : void 0;
62806
62900
  const offset = args5.offset ? Number(args5.offset) : void 0;
62807
62901
  const skipCache = args5["no-cache"] ? true : void 0;
62808
62902
  const queryContext = buildResearchQueryContext(location2, language);
@@ -62813,12 +62907,12 @@ Examples:
62813
62907
  location: location2,
62814
62908
  language,
62815
62909
  type,
62816
- limit,
62910
+ limit: limit2,
62817
62911
  offset,
62818
62912
  skipCache
62819
62913
  });
62820
62914
  const currentOffset = offset ?? 0;
62821
- const currentLimit = limit ?? 50;
62915
+ const currentLimit = limit2 ?? 50;
62822
62916
  const hasMore = currentOffset + result.data.length < result.total_count;
62823
62917
  warnDefaults(queryContext);
62824
62918
  const format = args5.output || "json";
@@ -63071,7 +63165,7 @@ Examples:
63071
63165
  const language = args5.language || void 0;
63072
63166
  const sort = args5.sort || void 0;
63073
63167
  const type = args5.type || void 0;
63074
- const limit = args5.limit ? Number(args5.limit) : void 0;
63168
+ const limit2 = args5.limit ? Number(args5.limit) : void 0;
63075
63169
  const skipCache = args5["no-cache"] ? true : void 0;
63076
63170
  const queryContext = buildResearchQueryContext(location2, language);
63077
63171
  try {
@@ -63081,7 +63175,7 @@ Examples:
63081
63175
  language,
63082
63176
  sort,
63083
63177
  type,
63084
- limit,
63178
+ limit: limit2,
63085
63179
  skipCache
63086
63180
  });
63087
63181
  warnDefaults(queryContext);
@@ -63252,7 +63346,7 @@ Examples:
63252
63346
  const target2 = args5.target;
63253
63347
  const location2 = args5.location || void 0;
63254
63348
  const language = args5.language || void 0;
63255
- const limit = args5.limit ? Number(args5.limit) : void 0;
63349
+ const limit2 = args5.limit ? Number(args5.limit) : void 0;
63256
63350
  const skipCache = args5["no-cache"] ? true : void 0;
63257
63351
  const queryContext = buildResearchQueryContext(location2, language);
63258
63352
  try {
@@ -63260,7 +63354,7 @@ Examples:
63260
63354
  target: target2,
63261
63355
  location: location2,
63262
63356
  language,
63263
- limit,
63357
+ limit: limit2,
63264
63358
  skipCache
63265
63359
  });
63266
63360
  warnDefaults(queryContext);
@@ -63606,13 +63700,13 @@ function refuseMisusedKinds(kind, args5) {
63606
63700
  function refuseStrayFlags(kind, args5) {
63607
63701
  const allowed = new Set(TRIGGER_TARGETS[kind].map((target2) => TRIGGER_TARGET_FIELDS[target2.field].flag));
63608
63702
  const stray = TRIGGER_TARGET_FIELD_IDS.map((field) => TRIGGER_TARGET_FIELDS[field].flag).filter(
63609
- (flag2) => !allowed.has(flag2) && flagText(args5, flag2) !== void 0 && !(kind === "experiment.started" && flag2 === "experiment")
63703
+ (flag3) => !allowed.has(flag3) && flagText(args5, flag3) !== void 0 && !(kind === "experiment.started" && flag3 === "experiment")
63610
63704
  );
63611
63705
  if (stray.length === 0) {
63612
63706
  return;
63613
63707
  }
63614
63708
  failValidation4(
63615
- `${stray.map((flag2) => `--${flag2}`).join(", ")} does not narrow ${kind}. ${allowed.size > 0 ? `It can be pointed with ${[...allowed].map((flag2) => `--${flag2}`).join(", ")}.` : "It runs every time it happens \u2014 narrow it with --when rules over its payload instead."}`
63709
+ `${stray.map((flag3) => `--${flag3}`).join(", ")} does not narrow ${kind}. ${allowed.size > 0 ? `It can be pointed with ${[...allowed].map((flag3) => `--${flag3}`).join(", ")}.` : "It runs every time it happens \u2014 narrow it with --when rules over its payload instead."}`
63616
63710
  );
63617
63711
  }
63618
63712
  var NARROWING_FLAGS = [...TRIGGER_TARGET_FIELD_IDS.map((field) => TRIGGER_TARGET_FIELDS[field].flag), "config"];
@@ -63620,11 +63714,11 @@ function refuseNarrowingWithoutKind(args5, kind, given) {
63620
63714
  if (kind) {
63621
63715
  return;
63622
63716
  }
63623
- const stray = NARROWING_FLAGS.filter((flag2) => flagText(args5, flag2) !== void 0);
63717
+ const stray = NARROWING_FLAGS.filter((flag3) => flagText(args5, flag3) !== void 0);
63624
63718
  if (stray.length === 0) {
63625
63719
  return;
63626
63720
  }
63627
- const flags = stray.map((flag2) => `--${flag2}`).join(", ");
63721
+ const flags = stray.map((flag3) => `--${flag3}`).join(", ");
63628
63722
  failValidation4(
63629
63723
  given > 0 ? `${flags} does not narrow --cron or --run-at: a time trigger runs every time it comes round. Use --trigger <kind> with ${flags} instead, or leave ${flags} off.` : `${flags} narrows a trigger, and no trigger was given. Pass --trigger <kind> too \u2014 the item's whole trigger, with ${flags} \u2014 or leave ${flags} off.`
63630
63724
  );
@@ -63715,13 +63809,13 @@ var REACTION_ARGS = {
63715
63809
  required: false
63716
63810
  }
63717
63811
  };
63718
- function wholeNumber(raw, flag2, { min }) {
63812
+ function wholeNumber(raw, flag3, { min }) {
63719
63813
  if (raw === void 0) {
63720
63814
  return void 0;
63721
63815
  }
63722
63816
  const value = Number(raw);
63723
63817
  if (!Number.isInteger(value) || value < min) {
63724
- failValidation4(`${flag2} must be a whole number of at least ${min}.`);
63818
+ failValidation4(`${flag3} must be a whole number of at least ${min}.`);
63725
63819
  }
63726
63820
  return value;
63727
63821
  }
@@ -64191,31 +64285,31 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
64191
64285
  delete: { type: "string", description: "Remove one of this company's own, by id", required: false }
64192
64286
  },
64193
64287
  run: async ({ args: args5 }) => {
64194
- const flag2 = (key) => {
64288
+ const flag3 = (key) => {
64195
64289
  const value = args5[key];
64196
64290
  return typeof value === "string" ? value.trim() : "";
64197
64291
  };
64198
64292
  try {
64199
- const save = flag2("save");
64200
- const publish = flag2("publish");
64201
- const remove = flag2("delete");
64293
+ const save = flag3("save");
64294
+ const publish = flag3("publish");
64295
+ const remove = flag3("delete");
64202
64296
  if ([save, publish, remove].filter((id) => id.length > 0).length > 1) {
64203
64297
  failValidation4("Pass one of --save, --publish or --delete at a time.");
64204
64298
  }
64205
64299
  if (save.length > 0) {
64206
- const briefFile = flag2("brief-file");
64207
- const brief2 = briefFile.length > 0 ? await readFile32(path46.resolve(briefFile), "utf8") : flag2("brief");
64300
+ const briefFile = flag3("brief-file");
64301
+ const brief2 = briefFile.length > 0 ? await readFile32(path46.resolve(briefFile), "utf8") : flag3("brief");
64208
64302
  if (brief2.trim().length === 0) {
64209
64303
  failValidation4("--brief-file (preferred) or --brief is required: the brief is the recipe.");
64210
64304
  }
64211
64305
  const response2 = await apiPost("/api/marketplace/save", {
64212
64306
  chatId: requireChatId(),
64213
64307
  slug: save,
64214
- name: flag2("name"),
64215
- category: flag2("recipe-category"),
64216
- tagline: flag2("tagline"),
64308
+ name: flag3("name"),
64309
+ category: flag3("recipe-category"),
64310
+ tagline: flag3("tagline"),
64217
64311
  prompt: brief2,
64218
- frequency: flag2("frequency")
64312
+ frequency: flag3("frequency")
64219
64313
  });
64220
64314
  writeJson(response2);
64221
64315
  return;
@@ -64236,7 +64330,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
64236
64330
  writeJson(response2);
64237
64331
  return;
64238
64332
  }
64239
- const category = flag2("category");
64333
+ const category = flag3("category");
64240
64334
  const response = await apiPost("/api/marketplace/list", {
64241
64335
  ...category.length > 0 ? { category } : {},
64242
64336
  ...args5.drafts === true ? { drafts: true } : {}
@@ -64528,6 +64622,150 @@ var schemaCommand2 = defineCommand208({
64528
64622
  }
64529
64623
  });
64530
64624
 
64625
+ // src/commands/shopify/index.ts
64626
+ import { defineCommand as defineCommand210 } from "citty";
64627
+
64628
+ // src/commands/shopify/buyers.ts
64629
+ import { defineCommand as defineCommand209 } from "citty";
64630
+
64631
+ // src/commands/shopify/buyerHints.ts
64632
+ var AUDIENCE_PATH = "To reach more people like your buyers on Meta, stage an audience of the store's purchasers (`baker ads meta audiences create --subtype WEBSITE`) and a lookalike of it (`--subtype LOOKALIKE`). Baker cannot upload a list of buyers to an ad platform.";
64633
+ var MIN_ORDERS_TO_SCALE = 5;
64634
+ var percent2 = (value) => `${Math.round(value * 100)}%`;
64635
+ function buyerHistoryHints(history, basis) {
64636
+ const hints2 = [];
64637
+ if (history.state === "importing") {
64638
+ hints2.push(
64639
+ `Baker is still bringing in this store's past orders (${history.orders} so far): older periods, repeat buyers and cohorts are incomplete \u2014 say so rather than reporting a drop.`
64640
+ );
64641
+ }
64642
+ if (history.state === "failed") {
64643
+ hints2.push("Baker couldn't finish bringing in this store's past orders; older periods may be incomplete.");
64644
+ }
64645
+ if (basis === "last_60_days") {
64646
+ hints2.push(
64647
+ "Shopify gives Baker this store's last 60 days of orders only: older behaviour is not here, and a buyer whose earlier orders are older counts as new."
64648
+ );
64649
+ }
64650
+ return hints2;
64651
+ }
64652
+ function countriesHints(answer) {
64653
+ const hints2 = buyerHistoryHints(answer.history, answer.basis);
64654
+ if (answer.total.orders === 0) {
64655
+ hints2.push("No orders were placed in this period.");
64656
+ return hints2;
64657
+ }
64658
+ const money = (amount) => `${answer.currency ?? ""} ${amount}`.trim();
64659
+ const growing = answer.rows.filter((row) => row.orders >= MIN_ORDERS_TO_SCALE && (row.ordersGrowth ?? 0) > 0).sort((a, b) => (b.ordersGrowth ?? 0) - (a.ordersGrowth ?? 0))[0];
64660
+ if (growing && answer.level === "country") {
64661
+ hints2.push(
64662
+ `Scale ${growing.name}: its orders grew ${percent2(growing.ordersGrowth ?? 0)} against the period before (${growing.orders} orders, ${money(growing.net)}). Check which campaigns already target it (\`baker ads meta campaigns\`, \`baker ads google query --preset campaign-performance\`) before staging a change.`
64663
+ );
64664
+ }
64665
+ const average = answer.total.averageOrder;
64666
+ const promising = answer.rows.filter((row) => row.orders < MIN_ORDERS_TO_SCALE && average > 0 && row.averageOrder >= 1.5 * average).sort((a, b) => b.averageOrder - a.averageOrder)[0];
64667
+ if (promising && answer.level === "country") {
64668
+ hints2.push(
64669
+ `Test ${promising.name}: its average order is ${money(promising.averageOrder)}, ${(promising.averageOrder / average).toFixed(1)}\xD7 the store's, from only ${promising.orders} orders \u2014 a small budget there would show whether it scales.`
64670
+ );
64671
+ }
64672
+ if (answer.unknown && answer.total.orders > 0 && answer.unknown.orders / answer.total.orders > 0.25) {
64673
+ hints2.push(
64674
+ `${answer.unknown.orders} of these orders have no address Baker could read (Unknown): say so rather than leaving them out of a country's share.`
64675
+ );
64676
+ }
64677
+ return hints2;
64678
+ }
64679
+ function bestSellersHints(answer) {
64680
+ const hints2 = buyerHistoryHints(answer.history, answer.basis);
64681
+ const leader = answer.repeat?.[0];
64682
+ if (leader && leader.buyersAgain > 0) {
64683
+ hints2.push(
64684
+ `Lead a landing with "${leader.title}" (find it with \`baker shopify products --search\`): ${percent2(leader.share)} of the buyers whose first order held it came back.`
64685
+ );
64686
+ }
64687
+ const top = answer.products?.[0];
64688
+ if (top && top.units > 0) {
64689
+ hints2.push(
64690
+ `"${top.title}" is the best seller (${top.units} units). For which first buys bring buyers back, run \`baker shopify best-sellers --view repeat\`; for what is losing steam, \`--view momentum\`.`
64691
+ );
64692
+ }
64693
+ const losing = answer.momentum?.[0];
64694
+ if (losing) {
64695
+ hints2.push(
64696
+ `"${losing.title}" sold ${losing.units} against ${losing.previousUnits} the period before: check its stock (\`baker shopify stock\`) and the ads that promote it before cutting it.`
64697
+ );
64698
+ }
64699
+ const refunded = answer.products?.find((row) => row.units >= 5 && row.refundRate >= 0.2);
64700
+ if (refunded) {
64701
+ hints2.push(
64702
+ `"${refunded.title}" comes back often (${percent2(refunded.refundRate)} of units refunded): don't lead a campaign with it before finding out why.`
64703
+ );
64704
+ }
64705
+ return hints2;
64706
+ }
64707
+ function bestBuyersHint(best) {
64708
+ if (!best || best.buyers === 0) return [];
64709
+ const countries = best.topCountries.map((country) => country.name).join(", ");
64710
+ const products = best.topFirstProducts.map((product) => `"${product.title}"`).join(", ");
64711
+ if (!countries && !products) return [];
64712
+ return [
64713
+ `Find more buyers like your best (${best.buyers}): ${countries ? `target ${countries}` : ""}${countries && products ? "; " : ""}${products ? `lead with ${products}, what their first orders held` : ""}.`
64714
+ ];
64715
+ }
64716
+ function segmentsHints(answer) {
64717
+ const hints2 = buyerHistoryHints(answer.history, answer.basis);
64718
+ hints2.push(...bestBuyersHint(answer.segments.find((segment) => segment.segment === "best")));
64719
+ const atRisk = answer.segments.find((segment) => segment.segment === "at_risk");
64720
+ if (atRisk && atRisk.buyers > 0) {
64721
+ hints2.push(
64722
+ `${atRisk.buyers} repeat buyers have not ordered in ${Math.round(2 * answer.thresholds.gapDays)} days (At risk): a win-back offer to them is worth more than a new buyer of the same value.`
64723
+ );
64724
+ }
64725
+ if (answer.buyers > 0) hints2.push(AUDIENCE_PATH);
64726
+ return hints2;
64727
+ }
64728
+ function buyersHints(answer) {
64729
+ const hints2 = buyerHistoryHints(answer.history, answer.basis);
64730
+ if (answer.summary.orders === 0) {
64731
+ hints2.push("No orders were placed in this period.");
64732
+ return hints2;
64733
+ }
64734
+ if (answer.acquisition?.by === "campaign" && answer.acquisition.rows.length > 0) {
64735
+ hints2.push(
64736
+ "Compare each campaign's lifetime value with what it cost for the same dates (`baker ads meta insights`, `baker ads google query --preset campaign-performance`): Baker keeps no ad spend."
64737
+ );
64738
+ }
64739
+ if (!answer.acquisition) {
64740
+ hints2.push(
64741
+ "For lifetime value by the campaign or channel that brought each buyer, add `--by campaign` or `--by channel`."
64742
+ );
64743
+ }
64744
+ if (answer.summary.guestOrders > 0 && answer.summary.guestOrders >= answer.summary.orders / 2) {
64745
+ hints2.push(
64746
+ `${answer.summary.guestOrders} of ${answer.summary.orders} orders were placed as guests: they count in sales, never as buyers, so repeat rates read low.`
64747
+ );
64748
+ }
64749
+ if (answer.summary.buyers > 0) hints2.push(AUDIENCE_PATH);
64750
+ return hints2;
64751
+ }
64752
+ function cohortsHints(answer) {
64753
+ const hints2 = buyerHistoryHints(answer.history, answer.basis);
64754
+ const reached = answer.cohorts.filter((cohort) => cohort.retention.m3 !== null && cohort.size >= 5);
64755
+ if (reached.length >= 2) {
64756
+ const best = [...reached].sort((a, b) => (b.retention.m3 ?? 0) - (a.retention.m3 ?? 0))[0];
64757
+ if (best) {
64758
+ hints2.push(
64759
+ `Buyers who first ordered in ${best.month} came back best (${percent2(best.retention.m3 ?? 0)} ordered again in their third month): look at what was running then (\`baker shopify buyers --by campaign\`).`
64760
+ );
64761
+ }
64762
+ }
64763
+ return hints2;
64764
+ }
64765
+
64766
+ // ../api/src/shopify/buyers.ts
64767
+ import { z as z51 } from "zod";
64768
+
64531
64769
  // ../api/src/shopify/wire.ts
64532
64770
  import { z as z50 } from "zod";
64533
64771
  var SHOPIFY_NOT_CONNECTED_MESSAGE = "No Shopify store is connected for this company.";
@@ -64905,9 +65143,543 @@ var shopifyStockResponseSchema = z50.object({
64905
65143
  /** More products in that group than `limit`. */
64906
65144
  more: z50.object({ outOfStock: z50.boolean(), partlyOut: z50.boolean() })
64907
65145
  });
65146
+ var shopifySalesBySchema = z50.enum([
65147
+ "day",
65148
+ "product",
65149
+ "campaign",
65150
+ "landing",
65151
+ "weekday",
65152
+ "hour",
65153
+ "month",
65154
+ "month_of_year"
65155
+ ]);
65156
+ var SHOPIFY_SALES_TIMING_BY = ["weekday", "hour", "month", "month_of_year"];
65157
+ var SHOPIFY_SALES_MAX_DAYS = 395;
65158
+ var SHOPIFY_SALES_TIMING_MAX_DAYS = 3650;
65159
+ var shopifySalesRequestSchema = z50.object({
65160
+ days: z50.coerce.number().int().min(1).max(SHOPIFY_SALES_TIMING_MAX_DAYS).optional(),
65161
+ /** Every order the store has (timing breakdowns only). */
65162
+ all: flag.optional(),
65163
+ by: shopifySalesBySchema.optional(),
65164
+ limit: z50.coerce.number().int().min(1).max(100).optional(),
65165
+ full: flag.optional()
65166
+ }).superRefine((request, ctx) => {
65167
+ if (SHOPIFY_SALES_TIMING_BY.includes(request.by ?? "day")) return;
65168
+ if (request.all) {
65169
+ ctx.addIssue({
65170
+ code: "custom",
65171
+ path: ["all"],
65172
+ message: "all is only for --by weekday, hour, month or month_of_year"
65173
+ });
65174
+ }
65175
+ if ((request.days ?? 0) > SHOPIFY_SALES_MAX_DAYS) {
65176
+ ctx.addIssue({
65177
+ code: "custom",
65178
+ path: ["days"],
65179
+ message: `by day, product, campaign or landing reads up to ${SHOPIFY_SALES_MAX_DAYS} days`
65180
+ });
65181
+ }
65182
+ });
65183
+ var shopifySalesCreditSchema = z50.enum([
65184
+ "campaign",
65185
+ "order",
65186
+ "landing",
65187
+ "before_baker",
65188
+ "unattributed",
65189
+ "other"
65190
+ ]);
65191
+ var shopifySalesRowSchema = z50.object({
65192
+ /** A campaign, a landing's name, a day (YYYY-MM-DD) or a product's title. */
65193
+ label: z50.string(),
65194
+ credit: shopifySalesCreditSchema.optional(),
65195
+ orders: z50.number(),
65196
+ sales: z50.number(),
65197
+ refunded: z50.number(),
65198
+ net: z50.number(),
65199
+ /** By day: money received after the sale (an edit paid for). */
65200
+ added: z50.number().optional(),
65201
+ /** By product: units sold and given back. */
65202
+ units: z50.number().optional(),
65203
+ refundedUnits: z50.number().optional(),
65204
+ /** By weekday, hour or month: the buyers who ordered. */
65205
+ buyers: z50.number().optional(),
65206
+ // --full
65207
+ source: z50.string().optional(),
65208
+ medium: z50.string().optional(),
65209
+ campaign: z50.string().optional(),
65210
+ /** The ad platform of the click that brought the buyer: google, meta, linkedin, … */
65211
+ clickPlatform: z50.string().optional(),
65212
+ landingId: z50.string().optional(),
65213
+ productId: z50.string().optional()
65214
+ });
65215
+ var shopifySalesTotalsSchema = z50.object({
65216
+ orders: z50.number(),
65217
+ /** What the orders came to, with money added after the sale. */
65218
+ sales: z50.number(),
65219
+ added: z50.number(),
65220
+ refunded: z50.number(),
65221
+ net: z50.number()
65222
+ });
65223
+ var shopifySalesReportingSchema = z50.enum(["shopify_channel", "baker", "undecided"]);
65224
+ var shopifySalesResponseSchema = z50.object({
65225
+ store: shopifyStoreStateSchema,
65226
+ /** `days` is "all" with `all`; `timezone` names the store's time a timing breakdown was read in. */
65227
+ window: z50.object({
65228
+ days: z50.union([z50.number(), z50.literal("all")]),
65229
+ from: z50.string(),
65230
+ to: z50.string(),
65231
+ timezone: z50.string().optional()
65232
+ }),
65233
+ by: shopifySalesBySchema,
65234
+ /** The store's currency; null when nothing sold in the window. */
65235
+ currency: z50.string().nullable(),
65236
+ totals: shopifySalesTotalsSchema,
65237
+ rows: z50.array(shopifySalesRowSchema),
65238
+ /** Sales in another currency, which are never added to these. */
65239
+ otherCurrencies: z50.array(z50.object({ currency: z50.string(), net: z50.number() })).optional(),
65240
+ salesReporting: z50.object({ meta: shopifySalesReportingSchema, google: shopifySalesReportingSchema }),
65241
+ /** The order history Baker imports when a store connects: still coming in, done, or failed. */
65242
+ history: z50.enum(["importing", "done", "failed", "none"]),
65243
+ /** Baker could not read this store's orders (the store must be reconnected). */
65244
+ ordersUnreadable: z50.literal(true).optional()
65245
+ });
65246
+
65247
+ // ../api/src/shopify/buyers.ts
65248
+ var flag2 = z51.enum(["true", "false"]).transform((value) => value === "true");
65249
+ var SHOPIFY_STORE_WINDOW_MAX_DAYS = 3650;
65250
+ var days = z51.coerce.number().int().min(1).max(SHOPIFY_STORE_WINDOW_MAX_DAYS);
65251
+ var countryCode2 = z51.string().regex(/^[A-Za-z]{2}$/, "a two-letter country code, like DE").transform((value) => value.toUpperCase());
65252
+ var limit = z51.coerce.number().int().min(1).max(100);
65253
+ var windowRequest = {
65254
+ days: days.optional(),
65255
+ /** Every order the store has. */
65256
+ all: flag2.optional(),
65257
+ country: countryCode2.optional(),
65258
+ full: flag2.optional()
65259
+ };
65260
+ var shopifyStoreWindowSchema = z51.object({
65261
+ from: z51.string().nullable(),
65262
+ to: z51.string(),
65263
+ days: z51.union([z51.number(), z51.literal("all")]),
65264
+ timezone: z51.string()
65265
+ });
65266
+ var shopifyBuyerHistorySchema = z51.object({
65267
+ state: z51.enum(["importing", "done", "failed", "none"]),
65268
+ reach: z51.enum(["all", "60_days"]).nullable(),
65269
+ /** The day of the oldest order Baker holds (YYYY-MM-DD). */
65270
+ from: z51.string().nullable(),
65271
+ /** Orders brought in so far. */
65272
+ orders: z51.number()
65273
+ });
65274
+ var shopifyBuyerBasisSchema = z51.enum(["all_orders", "last_60_days"]);
65275
+ var answerCommon = {
65276
+ store: shopifyStoreStateSchema,
65277
+ window: shopifyStoreWindowSchema,
65278
+ /** The store's currency; null when it has no orders yet. */
65279
+ currency: z51.string().nullable(),
65280
+ /** Orders in another currency (the store changed currency), never added to these. */
65281
+ otherCurrencies: z51.array(z51.object({ currency: z51.string(), orders: z51.number(), net: z51.number() })).optional(),
65282
+ history: shopifyBuyerHistorySchema,
65283
+ basis: shopifyBuyerBasisSchema
65284
+ };
65285
+ var shopifyPlaceSchema = z51.object({
65286
+ country: z51.string(),
65287
+ countryName: z51.string(),
65288
+ region: z51.string(),
65289
+ city: z51.string()
65290
+ });
65291
+ var shopifyBuyerSegmentSchema = z51.enum(["best", "loyal", "at_risk", "new", "one_time"]);
65292
+ var shopifySegmentThresholdsSchema = z51.object({
65293
+ /** The store's typical days between a repeat buyer's orders (90 with too few to go on). */
65294
+ gapDays: z51.number(),
65295
+ /** The lifetime net that puts a buyer in the top fifth. */
65296
+ netP80: z51.number()
65297
+ });
65298
+ var shopifySegmentRowSchema = z51.object({
65299
+ segment: shopifyBuyerSegmentSchema,
65300
+ label: z51.string(),
65301
+ /** The rule in words, with this store's numbers. */
65302
+ rule: z51.string(),
65303
+ buyers: z51.number(),
65304
+ /** Of every buyer, 0–1. */
65305
+ share: z51.number(),
65306
+ net: z51.number(),
65307
+ averageOrders: z51.number(),
65308
+ averageDaysSinceLastOrder: z51.number(),
65309
+ topCountries: z51.array(z51.object({ code: z51.string(), name: z51.string(), buyers: z51.number() })),
65310
+ /** The products their first orders held most often. */
65311
+ topFirstProducts: z51.array(z51.object({ title: z51.string(), buyers: z51.number(), productId: z51.string().optional() }))
65312
+ });
65313
+ var shopifySegmentsRequestSchema = z51.object({ ...windowRequest });
65314
+ var shopifySegmentsResponseSchema = z51.object({
65315
+ ...answerCommon,
65316
+ thresholds: shopifySegmentThresholdsSchema,
65317
+ buyers: z51.number(),
65318
+ segments: z51.array(shopifySegmentRowSchema)
65319
+ });
65320
+ var shopifyBuyersSortSchema = z51.enum(["net", "orders", "recent", "first"]);
65321
+ var shopifyAcquisitionBySchema = z51.enum(["campaign", "channel"]);
65322
+ var shopifyBuyersRequestSchema = z51.object({
65323
+ ...windowRequest,
65324
+ segment: shopifyBuyerSegmentSchema.optional(),
65325
+ /** Lifetime value by the campaign or channel of each buyer's first order. */
65326
+ by: shopifyAcquisitionBySchema.optional(),
65327
+ /** Only buyers whose first order came through this channel (a key from `by=channel`). */
65328
+ channel: z51.string().max(200).optional(),
65329
+ sort: shopifyBuyersSortSchema.optional(),
65330
+ limit: limit.optional(),
65331
+ offset: z51.coerce.number().int().min(0).max(1e5).optional()
65332
+ });
65333
+ var shopifyBuyersSummarySchema = z51.object({
65334
+ /** Buyers who ordered in the period. */
65335
+ buyers: z51.number(),
65336
+ /** Of them, whose first order was in the period. */
65337
+ newBuyers: z51.number(),
65338
+ returningBuyers: z51.number(),
65339
+ /** Orders with no customer: counted in money, never a buyer. */
65340
+ guestOrders: z51.number(),
65341
+ orders: z51.number(),
65342
+ net: z51.number(),
65343
+ averageOrder: z51.number(),
65344
+ /** Orders that were their buyer's first, and the rest (Shopify's new vs returning). */
65345
+ newOrders: z51.number(),
65346
+ returningOrders: z51.number(),
65347
+ newNet: z51.number(),
65348
+ returningNet: z51.number(),
65349
+ /** Of every buyer up to the period's end, the share who ordered again (0–1). */
65350
+ repeatRate: z51.number(),
65351
+ /** Median days from a buyer's first order to their second; null when nobody ordered twice. */
65352
+ medianDaysToSecond: z51.number().nullable()
65353
+ });
65354
+ var shopifyCameFromSchema = z51.object({
65355
+ channel: z51.string(),
65356
+ label: z51.string(),
65357
+ campaign: z51.string().optional(),
65358
+ // --full
65359
+ source: z51.string().optional(),
65360
+ medium: z51.string().optional(),
65361
+ clickPlatform: z51.string().optional(),
65362
+ landingId: z51.string().optional()
65363
+ });
65364
+ var shopifyBuyerRowSchema = z51.object({
65365
+ number: z51.number(),
65366
+ /** "Customer #1042". */
65367
+ label: z51.string(),
65368
+ place: shopifyPlaceSchema.nullable(),
65369
+ orders: z51.number(),
65370
+ net: z51.number(),
65371
+ firstOrderAt: z51.string(),
65372
+ lastOrderAt: z51.string(),
65373
+ segment: shopifyBuyerSegmentSchema,
65374
+ cameFrom: shopifyCameFromSchema,
65375
+ // --full
65376
+ gross: z51.number().optional(),
65377
+ refunded: z51.number().optional(),
65378
+ ordersWithDiscount: z51.number().optional(),
65379
+ averageDaysBetweenOrders: z51.number().nullable().optional()
65380
+ });
65381
+ var shopifyAcquisitionRowSchema = z51.object({
65382
+ key: z51.string(),
65383
+ label: z51.string(),
65384
+ buyers: z51.number(),
65385
+ /** What their first orders came to. */
65386
+ firstNet: z51.number(),
65387
+ /** What they have spent since, to date. */
65388
+ lifetimeNet: z51.number(),
65389
+ lifetimePerBuyer: z51.number(),
65390
+ averageOrders: z51.number(),
65391
+ repeatRate: z51.number(),
65392
+ // --full
65393
+ source: z51.string().optional(),
65394
+ medium: z51.string().optional(),
65395
+ campaign: z51.string().optional(),
65396
+ clickPlatform: z51.string().optional()
65397
+ });
65398
+ var shopifyBuyersResponseSchema = z51.object({
65399
+ ...answerCommon,
65400
+ summary: shopifyBuyersSummarySchema,
65401
+ thresholds: shopifySegmentThresholdsSchema,
65402
+ segments: z51.array(z51.object({ segment: shopifyBuyerSegmentSchema, label: z51.string(), buyers: z51.number() })),
65403
+ /** Buyers matching the filters, before the limit. */
65404
+ total: z51.number(),
65405
+ buyers: z51.array(shopifyBuyerRowSchema),
65406
+ acquisition: z51.object({ by: shopifyAcquisitionBySchema, rows: z51.array(shopifyAcquisitionRowSchema) }).optional()
65407
+ });
65408
+ var shopifyBuyerNumberSchema = z51.string().trim().transform((value) => value.replace(/^customer\s*/i, "").replace(/^#/, "")).pipe(z51.string().regex(/^\d+$/, "a buyer's number, like 1042 or #1042")).transform((value) => Number(value)).pipe(z51.number().int().min(1).max(4e9));
65409
+ var shopifyBuyerRequestSchema = z51.object({ number: shopifyBuyerNumberSchema, full: flag2.optional() });
65410
+ var SHOPIFY_BUYER_GONE_MESSAGE = "This buyer is no longer in Baker.";
65411
+ var shopifyBuyerOrderSchema = z51.object({
65412
+ name: z51.string(),
65413
+ /** When it was placed, in the store's time (YYYY-MM-DD HH:MM). */
65414
+ placedAt: z51.string(),
65415
+ net: z51.number(),
65416
+ gross: z51.number(),
65417
+ refunded: z51.number(),
65418
+ products: z51.array(z51.object({ title: z51.string(), quantity: z51.number() })),
65419
+ discountCodes: z51.array(z51.string()),
65420
+ place: shopifyPlaceSchema.nullable(),
65421
+ /** Placed before Baker measured the store: counted, credited to no campaign. */
65422
+ beforeBaker: z51.boolean(),
65423
+ // --full
65424
+ orderId: z51.string().optional(),
65425
+ items: z51.array(z51.object({ productId: z51.string(), variantId: z51.string(), quantity: z51.number(), amount: z51.number() })).optional(),
65426
+ refundedItems: z51.array(z51.object({ productId: z51.string(), variantId: z51.string(), quantity: z51.number(), amount: z51.number() })).optional(),
65427
+ cameFrom: shopifyCameFromSchema.optional()
65428
+ });
65429
+ var shopifyBuyerProfileSchema = z51.object({
65430
+ number: z51.number(),
65431
+ label: z51.string(),
65432
+ place: shopifyPlaceSchema.nullable(),
65433
+ orders: z51.number(),
65434
+ gross: z51.number(),
65435
+ refunded: z51.number(),
65436
+ net: z51.number(),
65437
+ averageOrder: z51.number(),
65438
+ /** Average days between their orders; null with one order. */
65439
+ averageDaysBetweenOrders: z51.number().nullable(),
65440
+ firstOrderAt: z51.string(),
65441
+ lastOrderAt: z51.string(),
65442
+ ordersWithDiscount: z51.number(),
65443
+ segment: shopifyBuyerSegmentSchema,
65444
+ segmentLabel: z51.string(),
65445
+ cameFrom: shopifyCameFromSchema
65446
+ });
65447
+ var shopifyBuyerResponseSchema = z51.object({
65448
+ ...answerCommon,
65449
+ buyer: shopifyBuyerProfileSchema,
65450
+ /** Newest first. */
65451
+ orders: z51.array(shopifyBuyerOrderSchema),
65452
+ products: z51.array(
65453
+ z51.object({ title: z51.string(), quantity: z51.number(), orders: z51.number(), productId: z51.string().optional() })
65454
+ )
65455
+ });
65456
+ var shopifyCountriesRequestSchema = z51.object({
65457
+ ...windowRequest,
65458
+ /** Inside one region of `country` (its code or name): its cities. */
65459
+ region: z51.string().max(100).optional(),
65460
+ /** Only orders holding this product (its id or handle); money is that product's lines. */
65461
+ product: z51.string().max(255).optional(),
65462
+ limit: limit.optional()
65463
+ });
65464
+ var shopifyPlaceRowSchema = z51.object({
65465
+ /** A country code, a region code (or name) or a city. */
65466
+ key: z51.string(),
65467
+ name: z51.string(),
65468
+ orders: z51.number(),
65469
+ buyers: z51.number(),
65470
+ newBuyers: z51.number(),
65471
+ net: z51.number(),
65472
+ averageOrder: z51.number(),
65473
+ /** Of the total's net, 0–1. */
65474
+ share: z51.number(),
65475
+ /** Net against the period before; null when there was none before. */
65476
+ growth: z51.number().nullable(),
65477
+ ordersGrowth: z51.number().nullable(),
65478
+ /** The currency its buyers paid in most. */
65479
+ paidIn: z51.string(),
65480
+ // --full
65481
+ previousNet: z51.number().optional(),
65482
+ previousOrders: z51.number().optional()
65483
+ });
65484
+ var shopifyCountriesResponseSchema = z51.object({
65485
+ ...answerCommon,
65486
+ level: z51.enum(["country", "region", "city"]),
65487
+ country: z51.string().optional(),
65488
+ region: z51.string().optional(),
65489
+ product: z51.object({ id: z51.string(), title: z51.string() }).optional(),
65490
+ total: shopifyPlaceRowSchema,
65491
+ /** Named places, best first; with `unknown` and `other` they add up to `total`. */
65492
+ rows: z51.array(shopifyPlaceRowSchema),
65493
+ /** Orders whose address Baker could not read. Always shown, never folded into `other`. */
65494
+ unknown: shopifyPlaceRowSchema.nullable(),
65495
+ /** Places past the limit, together. */
65496
+ other: shopifyPlaceRowSchema.nullable()
65497
+ });
65498
+ var shopifyBestSellersViewSchema = z51.enum(["products", "variants", "trend", "together", "repeat", "momentum"]);
65499
+ var shopifyBestSellersSortSchema = z51.enum(["units", "revenue", "orders"]);
65500
+ var shopifyBestSellersRequestSchema = z51.object({
65501
+ ...windowRequest,
65502
+ view: shopifyBestSellersViewSchema.optional(),
65503
+ sort: shopifyBestSellersSortSchema.optional(),
65504
+ /** Only this product (its id or handle): its variants, its trend. */
65505
+ product: z51.string().max(255).optional(),
65506
+ limit: limit.optional()
65507
+ });
65508
+ var shopifyBestSellerRowSchema = z51.object({
65509
+ title: z51.string(),
65510
+ /** The option's own title, by variant. */
65511
+ variantTitle: z51.string().optional(),
65512
+ units: z51.number(),
65513
+ refundedUnits: z51.number(),
65514
+ orders: z51.number(),
65515
+ buyers: z51.number(),
65516
+ revenue: z51.number(),
65517
+ refunded: z51.number(),
65518
+ /** Refunded units of units ordered, 0–1. */
65519
+ refundRate: z51.number(),
65520
+ /** Units against the period before; null when none sold before. */
65521
+ growth: z51.number().nullable(),
65522
+ // --full
65523
+ productId: z51.string().optional(),
65524
+ variantId: z51.string().optional(),
65525
+ previousUnits: z51.number().optional(),
65526
+ previousRevenue: z51.number().optional()
65527
+ });
65528
+ var shopifyTrendRowSchema = z51.object({
65529
+ /** YYYY-MM, in the store's time. */
65530
+ month: z51.string(),
65531
+ title: z51.string(),
65532
+ units: z51.number(),
65533
+ revenue: z51.number(),
65534
+ productId: z51.string().optional()
65535
+ });
65536
+ var shopifyTogetherRowSchema = z51.object({
65537
+ first: z51.string(),
65538
+ second: z51.string(),
65539
+ /** Orders holding both. */
65540
+ orders: z51.number(),
65541
+ /** Of the orders holding the first, the share that also held the second (0–1). */
65542
+ confidence: z51.number(),
65543
+ firstId: z51.string().optional(),
65544
+ secondId: z51.string().optional()
65545
+ });
65546
+ var shopifyRepeatRowSchema = z51.object({
65547
+ title: z51.string(),
65548
+ /** Buyers whose first order held it. */
65549
+ buyers: z51.number(),
65550
+ /** Of them, who ordered again. */
65551
+ buyersAgain: z51.number(),
65552
+ share: z51.number(),
65553
+ medianDaysToSecond: z51.number().nullable(),
65554
+ productId: z51.string().optional()
65555
+ });
65556
+ var shopifyMomentumRowSchema = z51.object({
65557
+ title: z51.string(),
65558
+ units: z51.number(),
65559
+ previousUnits: z51.number(),
65560
+ /** Units against the period before (−0.4 = 40% fewer). */
65561
+ change: z51.number(),
65562
+ revenue: z51.number(),
65563
+ productId: z51.string().optional()
65564
+ });
65565
+ var shopifyBestSellersResponseSchema = z51.object({
65566
+ ...answerCommon,
65567
+ view: shopifyBestSellersViewSchema,
65568
+ sort: shopifyBestSellersSortSchema,
65569
+ product: z51.object({ id: z51.string(), title: z51.string() }).optional(),
65570
+ products: z51.array(shopifyBestSellerRowSchema).optional(),
65571
+ variants: z51.array(shopifyBestSellerRowSchema).optional(),
65572
+ trend: z51.array(shopifyTrendRowSchema).optional(),
65573
+ together: z51.array(shopifyTogetherRowSchema).optional(),
65574
+ repeat: z51.array(shopifyRepeatRowSchema).optional(),
65575
+ momentum: z51.array(shopifyMomentumRowSchema).optional()
65576
+ });
65577
+ var shopifyCohortsRequestSchema = z51.object({
65578
+ months: z51.coerce.number().int().min(3).max(36).optional(),
65579
+ country: countryCode2.optional(),
65580
+ full: flag2.optional()
65581
+ });
65582
+ var shopifyCohortMonthSchema = z51.object({
65583
+ /** Months since the first order: 0 is the month they first bought. */
65584
+ offset: z51.number(),
65585
+ activeBuyers: z51.number(),
65586
+ orders: z51.number(),
65587
+ net: z51.number(),
65588
+ /** Of the cohort, who ordered in that month (0–1). */
65589
+ retention: z51.number(),
65590
+ /** What the cohort spent up to that month, per buyer. */
65591
+ cumulativeNetPerBuyer: z51.number()
65592
+ });
65593
+ var shopifyCohortSchema = z51.object({
65594
+ /** The month of their first order, YYYY-MM in the store's time. */
65595
+ month: z51.string(),
65596
+ size: z51.number(),
65597
+ /** Who ordered again 1, 2, 3, 6 and 12 months after; null for a month not reached yet. */
65598
+ retention: z51.object({
65599
+ m1: z51.number().nullable(),
65600
+ m2: z51.number().nullable(),
65601
+ m3: z51.number().nullable(),
65602
+ m6: z51.number().nullable(),
65603
+ m12: z51.number().nullable()
65604
+ }),
65605
+ netPerBuyerAt12: z51.number().nullable(),
65606
+ // --full
65607
+ matrix: z51.array(shopifyCohortMonthSchema).optional()
65608
+ });
65609
+ var shopifyCohortsResponseSchema = z51.object({
65610
+ ...answerCommon,
65611
+ months: z51.number(),
65612
+ cohorts: z51.array(shopifyCohortSchema)
65613
+ });
64908
65614
 
64909
- // src/commands/shopify/index.ts
64910
- import { defineCommand as defineCommand209 } from "citty";
65615
+ // src/commands/shopify/refusal.ts
65616
+ var CONTINUE2 = "Keep going with the wider task: say which part you could not read, skip it, and finish everything else.";
65617
+ function shopifyRefusal(code, message) {
65618
+ if (code !== "NOT_FOUND") return null;
65619
+ if (message.includes(SHOPIFY_NOT_CONNECTED_MESSAGE)) {
65620
+ return {
65621
+ code: "NO_SHOPIFY_CONNECTION",
65622
+ fix: {
65623
+ action: "request_connection",
65624
+ explanation: 'AGENT INSTRUCTION: no Shopify store is connected, and only the user can connect it. Call the `request_connection` tool with { platform: "shopify", reason } \u2014 reason in their words, e.g. "so I can use your real products and prices" \u2014 and they connect it inside the chat; then run this command again. ' + CONTINUE2
65625
+ }
65626
+ };
65627
+ }
65628
+ if (message.includes(SHOPIFY_NOT_OFFERED_MESSAGE)) {
65629
+ return {
65630
+ code: "SHOPIFY_NOT_AVAILABLE",
65631
+ fix: {
65632
+ action: "continue_without",
65633
+ explanation: "Do not call `request_connection` for Shopify and do not tell the user to connect it: a Shopify store cannot be connected on this Baker yet. Answer from the knowledge vault and the client's own website instead (`baker images extract <store url>` for product photos, `baker research fetch <url>` for a product page), say the product facts are unverified, and finish the rest of the task."
65634
+ }
65635
+ };
65636
+ }
65637
+ return null;
65638
+ }
65639
+
65640
+ // src/commands/shopify/common.ts
65641
+ 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.";
65642
+ function fail9(err) {
65643
+ if (err instanceof ApiError) {
65644
+ const refusal = shopifyRefusal(err.code, err.message);
65645
+ if (refusal) {
65646
+ writeJson({ ok: false, error: { code: refusal.code, message: err.message, fix: refusal.fix } });
65647
+ process.exit(1);
65648
+ }
65649
+ if (err.code === "NOT_FOUND" && err.message === SHOPIFY_BUYER_GONE_MESSAGE) {
65650
+ writeJson({
65651
+ ok: false,
65652
+ error: { code: err.code, message: err.message },
65653
+ hints: [
65654
+ "Their store asked Baker to erase this buyer, or they were merged into another one: say the buyer is no longer in Baker, and never guess who they were."
65655
+ ]
65656
+ });
65657
+ process.exit(1);
65658
+ }
65659
+ if (err.code === "NOT_FOUND" && err.message.startsWith(SHOPIFY_PRODUCT_NOT_FOUND_PREFIX)) {
65660
+ writeJson({ ok: false, error: { code: err.code, message: err.message }, hints: [PRODUCT_NOT_FOUND_HINT] });
65661
+ process.exit(1);
65662
+ }
65663
+ }
65664
+ failApi(err);
65665
+ }
65666
+ function emit2(data, meta, hints2) {
65667
+ writeJson({ ok: true, data, meta, ...hints2.length > 0 ? { hints: hints2 } : {} });
65668
+ }
65669
+ function params(entries) {
65670
+ const out = {};
65671
+ for (const [name, value] of Object.entries(entries)) {
65672
+ if (value === void 0 || value === "" || value === false) continue;
65673
+ out[name] = String(value);
65674
+ }
65675
+ return out;
65676
+ }
65677
+ var fullArg = {
65678
+ type: "boolean",
65679
+ description: "Everything Baker keeps, not just what answers the question",
65680
+ required: false,
65681
+ default: false
65682
+ };
64911
65683
 
64912
65684
  // src/commands/shopify/hints.ts
64913
65685
  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.';
@@ -65092,7 +65864,7 @@ function productHints(product, store) {
65092
65864
  }
65093
65865
  return hints2;
65094
65866
  }
65095
- function stockHints(answer, limit) {
65867
+ function stockHints(answer, limit2) {
65096
65868
  const hints2 = [];
65097
65869
  const any = answer.outOfStock.length > 0 || answer.partlyOut.length > 0;
65098
65870
  const unfinished = answer.more.outOfStock || answer.more.partlyOut;
@@ -65103,9 +65875,9 @@ function stockHints(answer, limit) {
65103
65875
  );
65104
65876
  } else if (!unfinished) hints2.push("Nothing is out of stock.");
65105
65877
  if (unfinished) {
65106
- const full = answer.more.outOfStock && answer.outOfStock.length >= limit || answer.more.partlyOut && answer.partlyOut.length >= limit;
65878
+ const full = answer.more.outOfStock && answer.outOfStock.length >= limit2 || answer.more.partlyOut && answer.partlyOut.length >= limit2;
65107
65879
  hints2.push(
65108
- full ? `More than ${limit} products in a list; raise \`--limit\` (up to 200) or ${pageThrough}.` : `Baker didn't check every product in one go, so these lists may be missing some \u2014 don't say nothing else is out of stock; ${pageThrough}.`
65880
+ full ? `More than ${limit2} products in a list; raise \`--limit\` (up to 200) or ${pageThrough}.` : `Baker didn't check every product in one go, so these lists may be missing some \u2014 don't say nothing else is out of stock; ${pageThrough}.`
65109
65881
  );
65110
65882
  }
65111
65883
  const big = answer.partlyOut.find((row) => row.outOptionsMore);
@@ -65140,65 +65912,350 @@ function storeHints(summary) {
65140
65912
  }
65141
65913
  return hints2;
65142
65914
  }
65143
-
65144
- // src/commands/shopify/refusal.ts
65145
- var CONTINUE2 = "Keep going with the wider task: say which part you could not read, skip it, and finish everything else.";
65146
- function shopifyRefusal(code, message) {
65147
- if (code !== "NOT_FOUND") return null;
65148
- if (message.includes(SHOPIFY_NOT_CONNECTED_MESSAGE)) {
65149
- return {
65150
- code: "NO_SHOPIFY_CONNECTION",
65151
- fix: {
65152
- action: "request_connection",
65153
- explanation: 'AGENT INSTRUCTION: no Shopify store is connected, and only the user can connect it. Call the `request_connection` tool with { platform: "shopify", reason } \u2014 reason in their words, e.g. "so I can use your real products and prices" \u2014 and they connect it inside the chat; then run this command again. ' + CONTINUE2
65154
- }
65155
- };
65915
+ function timingHints(answer) {
65916
+ const peak = [...answer.rows].sort((a, b) => b.orders - a.orders)[0];
65917
+ if (!peak || peak.orders === 0) return [];
65918
+ const zone = answer.window.timezone ? `, ${answer.window.timezone}` : "";
65919
+ return [`Most orders are placed on ${peak.label} (store time${zone}): weight budgets and sends towards it.`];
65920
+ }
65921
+ function salesHints(answer) {
65922
+ const hints2 = [];
65923
+ if (answer.ordersUnreadable) {
65924
+ hints2.push(
65925
+ "Baker can't read this store's orders right now, so these sales may be incomplete. Ask the user to reconnect the store with `request_connection` { platform: \"shopify\" }."
65926
+ );
65156
65927
  }
65157
- if (message.includes(SHOPIFY_NOT_OFFERED_MESSAGE)) {
65158
- return {
65159
- code: "SHOPIFY_NOT_AVAILABLE",
65160
- fix: {
65161
- action: "continue_without",
65162
- explanation: "Do not call `request_connection` for Shopify and do not tell the user to connect it: a Shopify store cannot be connected on this Baker yet. Answer from the knowledge vault and the client's own website instead (`baker images extract <store url>` for product photos, `baker research fetch <url>` for a product page), say the product facts are unverified, and finish the rest of the task."
65163
- }
65164
- };
65928
+ if (answer.history === "importing") {
65929
+ hints2.push(
65930
+ "Baker is still bringing in this store's past orders; earlier periods are incomplete \u2014 say so rather than reporting a drop."
65931
+ );
65165
65932
  }
65166
- return null;
65933
+ if (answer.totals.orders === 0) {
65934
+ hints2.push("No orders were paid in this period.");
65935
+ return hints2;
65936
+ }
65937
+ if (answer.by === "weekday" || answer.by === "hour" || answer.by === "month_of_year") {
65938
+ return [...hints2, ...timingHints(answer)];
65939
+ }
65940
+ const notBaker = ["meta", "google"].filter((family) => answer.salesReporting[family] !== "baker");
65941
+ if (notBaker.length > 0) {
65942
+ const names = notBaker.map((family) => family === "meta" ? "Meta" : "Google").join(" and ");
65943
+ hints2.push(
65944
+ `Baker only reports these sales; it does not send them to ${names}. A manager chooses in Brain \u2192 Store whether Baker sends them (where Shopify's own channel already reports sales, its purchase tracking has to be turned off first). Never set up a purchase send yourself.`
65945
+ );
65946
+ }
65947
+ const unattributed = answer.rows.filter((row) => row.credit === "unattributed").reduce((total, row) => total + row.net, 0);
65948
+ if ((answer.by === "campaign" || answer.by === "landing") && answer.totals.net > 0 && unattributed / answer.totals.net > 0.5) {
65949
+ hints2.push(
65950
+ "Most of these sales came from no campaign Baker found: buyers who came straight to the store, or from links without campaign tags. Say so rather than crediting them to a campaign; links with UTM tags and Baker landings make the next ones traceable."
65951
+ );
65952
+ }
65953
+ const leading = answer.by === "landing" ? answer.rows.find((row) => row.credit === "landing") : void 0;
65954
+ if (leading && leading.net > 0) {
65955
+ hints2.push(
65956
+ `"${leading.label}" brought the most sales of the Baker pages (${answer.currency ?? ""} ${leading.net}). It is the page to learn from before building the next one.`
65957
+ );
65958
+ }
65959
+ return hints2;
65167
65960
  }
65168
65961
 
65169
- // src/commands/shopify/index.ts
65170
- 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.";
65171
- function fail9(err) {
65172
- if (err instanceof ApiError) {
65173
- const refusal = shopifyRefusal(err.code, err.message);
65174
- if (refusal) {
65175
- writeJson({ ok: false, error: { code: refusal.code, message: err.message, fix: refusal.fix } });
65176
- process.exit(1);
65962
+ // src/commands/shopify/buyers.ts
65963
+ var PERIOD_NOTE = "Orders placed in the period, in the store's own time, with what was refunded on them since (Shopify's own reports date a refund when it happens instead, so a short period can differ by the refunds that cross its edges).";
65964
+ var periodArgs = {
65965
+ days: { type: "number", description: "The last N days, 1-3650 (default 365)", required: false },
65966
+ all: { type: "boolean", description: "Every order the store has", required: false, default: false },
65967
+ country: {
65968
+ type: "string",
65969
+ description: "Only orders to this country: its two-letter code, like DE",
65970
+ required: false
65971
+ }
65972
+ };
65973
+ var periodFlags = {
65974
+ days: { type: "string", description: "The last N days, 1-3650 (default 365)", required: false },
65975
+ all: { type: "boolean", description: "Every order the store has", required: false, default: false },
65976
+ country: { type: "string", description: "Only orders to this country (two-letter code, like DE)", required: false }
65977
+ };
65978
+ function split(answer) {
65979
+ const { store, window: window2, history, basis, ...data } = answer;
65980
+ return { data, meta: { store, window: window2, history, basis } };
65981
+ }
65982
+ registerSchema({
65983
+ command: "shopify.buyers",
65984
+ description: `Start here for 'who buys from us', 'how many come back' and 'which campaign brings the best customers': the store's buyers in the period \u2014 how many, how many were new, repeat rate, days to a second order, guest orders \u2014 their segments (best, loyal, at risk, new, one-time, by this store's own rhythm) and the top buyers as numbered customers with their country, lifetime spend and the channel of their first order. --by campaign|channel adds lifetime value per acquisition campaign or channel (Baker keeps no ad spend: read it with the ads commands for the same dates). ${PERIOD_NOTE} A buyer is a Shopify customer: guest orders count in money, never as buyers.`,
65985
+ args: {
65986
+ ...periodArgs,
65987
+ segment: {
65988
+ type: "string",
65989
+ description: "Only one segment's buyers: best | loyal | at_risk | new | one_time",
65990
+ required: false,
65991
+ enum: ["best", "loyal", "at_risk", "new", "one_time"]
65992
+ },
65993
+ by: {
65994
+ type: "string",
65995
+ description: "Lifetime value by the campaign or channel of each buyer's first order",
65996
+ required: false,
65997
+ enum: ["campaign", "channel"]
65998
+ },
65999
+ channel: {
66000
+ type: "string",
66001
+ description: "Only buyers who first came through this channel (a key from --by channel)",
66002
+ required: false
66003
+ },
66004
+ sort: {
66005
+ type: "string",
66006
+ description: "net (lifetime spend, default) | orders | recent (last order) | first (first order)",
66007
+ required: false,
66008
+ enum: ["net", "orders", "recent", "first"]
66009
+ },
66010
+ limit: { type: "number", description: "Buyers listed, 1-100 (default 10)", required: false },
66011
+ offset: { type: "number", description: "Skip this many buyers (paging)", required: false },
66012
+ full: {
66013
+ ...fullArg,
66014
+ description: "Add each buyer's gross, refunds, orders with a discount, days between orders and first-order campaign"
65177
66015
  }
65178
- if (err.code === "NOT_FOUND" && err.message.startsWith(SHOPIFY_PRODUCT_NOT_FOUND_PREFIX)) {
65179
- writeJson({ ok: false, error: { code: err.code, message: err.message }, hints: [PRODUCT_NOT_FOUND_HINT] });
65180
- process.exit(1);
66016
+ }
66017
+ });
66018
+ var buyersCommand = defineCommand209({
66019
+ meta: {
66020
+ name: "buyers",
66021
+ description: "Who buys: buyers, new and returning, repeat rate, segments and the top buyers. Start here: baker shopify buyers --days 90 --by channel"
66022
+ },
66023
+ args: {
66024
+ ...periodFlags,
66025
+ segment: { type: "string", description: "best | loyal | at_risk | new | one_time", required: false },
66026
+ by: { type: "string", description: "campaign | channel", required: false },
66027
+ channel: { type: "string", description: "A channel key from --by channel", required: false },
66028
+ sort: { type: "string", description: "net | orders | recent | first", required: false },
66029
+ limit: { type: "string", description: "Buyers listed, 1-100", required: false },
66030
+ offset: { type: "string", description: "Skip this many buyers", required: false },
66031
+ full: { type: "boolean", description: "Add each buyer's detail", required: false, default: false }
66032
+ },
66033
+ run: async ({ args: args5 }) => {
66034
+ try {
66035
+ const answer = await apiGet(
66036
+ "/api/shopify/buyers",
66037
+ params({
66038
+ days: args5.days,
66039
+ all: args5.all,
66040
+ country: args5.country,
66041
+ segment: args5.segment,
66042
+ by: args5.by,
66043
+ channel: args5.channel,
66044
+ sort: args5.sort,
66045
+ limit: args5.limit,
66046
+ offset: args5.offset,
66047
+ full: args5.full
66048
+ })
66049
+ );
66050
+ const { data, meta } = split(answer);
66051
+ emit2(data, meta, [...storeStateHints(answer.store), ...buyersHints(answer)]);
66052
+ } catch (err) {
66053
+ fail9(err);
65181
66054
  }
65182
66055
  }
65183
- failApi(err);
65184
- }
65185
- function emit2(data, meta, hints2) {
65186
- writeJson({ ok: true, data, meta, ...hints2.length > 0 ? { hints: hints2 } : {} });
65187
- }
65188
- function params(entries) {
65189
- const out = {};
65190
- for (const [name, value] of Object.entries(entries)) {
65191
- if (value === void 0 || value === "" || value === false) continue;
65192
- out[name] = String(value);
66056
+ });
66057
+ registerSchema({
66058
+ command: "shopify.buyer",
66059
+ description: "One buyer by their number (1042, #1042 or 'Customer #1042'): where they are, how many orders and how much they spent net of refunds, average order, days between orders, first and last order, discount use, segment and how they first came \u2014 then every order (date in the store's time, products and quantities, net, refunded, discount codes, city), newest first, and what they bought. Never a name or an email. A buyer their store asked Baker to erase, or merged into another, answers 'This buyer is no longer in Baker'.",
66060
+ args: {
66061
+ number: { type: "positional", description: "The buyer's number: 1042, #1042 or 'Customer #1042'", required: true },
66062
+ full: { ...fullArg, description: "Add each order's id, lines with their amounts, refunded lines and campaign" }
65193
66063
  }
65194
- return out;
65195
- }
65196
- var fullArg = {
65197
- type: "boolean",
65198
- description: "Everything Baker keeps, not just what answers the question",
65199
- required: false,
65200
- default: false
65201
- };
66064
+ });
66065
+ var buyerCommand = defineCommand209({
66066
+ meta: {
66067
+ name: "buyer",
66068
+ description: "One buyer's history by number. Example: baker shopify buyer 1042"
66069
+ },
66070
+ args: {
66071
+ number: { type: "positional", description: "1042, #1042 or 'Customer #1042'", required: true },
66072
+ full: { type: "boolean", description: "Add ids, lines and campaigns", required: false, default: false }
66073
+ },
66074
+ run: async ({ args: args5 }) => {
66075
+ try {
66076
+ const answer = await apiGet(
66077
+ "/api/shopify/buyer",
66078
+ params({ number: args5.number, full: args5.full })
66079
+ );
66080
+ const { data, meta } = split(answer);
66081
+ emit2(data, meta, [...storeStateHints(answer.store), ...buyerHistoryHints(answer.history, answer.basis)]);
66082
+ } catch (err) {
66083
+ fail9(err);
66084
+ }
66085
+ }
66086
+ });
66087
+ registerSchema({
66088
+ command: "shopify.countries",
66089
+ description: `Start here for 'where do our buyers come from' and 'which countries should we push': orders, buyers, new buyers, net, average order, share and growth against the period before, by country \u2014 or by region inside --country, or by city inside --country and --region \u2014 with the currency buyers paid in. Orders whose address Baker could not read are 'Unknown', always shown; the rows, Unknown and 'Other' add up to the total. --product narrows to the orders holding one product, with that product's money. ${PERIOD_NOTE}`,
66090
+ args: {
66091
+ ...periodArgs,
66092
+ region: { type: "string", description: "Inside --country, one region (code or name): its cities", required: false },
66093
+ product: { type: "string", description: "Only orders holding this product: its id or handle", required: false },
66094
+ limit: { type: "number", description: "Places listed, 1-100 (default 25; the rest are 'Other')", required: false },
66095
+ full: { ...fullArg, description: "Add the period before's net and orders per place" }
66096
+ }
66097
+ });
66098
+ var countriesCommand2 = defineCommand209({
66099
+ meta: {
66100
+ name: "countries",
66101
+ description: "Where buyers are: orders, buyers and net by country, region or city, with growth. Start here: baker shopify countries --days 90"
66102
+ },
66103
+ args: {
66104
+ ...periodFlags,
66105
+ region: { type: "string", description: "A region inside --country", required: false },
66106
+ product: { type: "string", description: "A product's id or handle", required: false },
66107
+ limit: { type: "string", description: "Places listed, 1-100", required: false },
66108
+ full: { type: "boolean", description: "Add the period before", required: false, default: false }
66109
+ },
66110
+ run: async ({ args: args5 }) => {
66111
+ try {
66112
+ const answer = await apiGet(
66113
+ "/api/shopify/countries",
66114
+ params({
66115
+ days: args5.days,
66116
+ all: args5.all,
66117
+ country: args5.country,
66118
+ region: args5.region,
66119
+ product: args5.product,
66120
+ limit: args5.limit,
66121
+ full: args5.full
66122
+ })
66123
+ );
66124
+ const { data, meta } = split(answer);
66125
+ emit2(data, meta, [...storeStateHints(answer.store), ...countriesHints(answer)]);
66126
+ } catch (err) {
66127
+ fail9(err);
66128
+ }
66129
+ }
66130
+ });
66131
+ registerSchema({
66132
+ command: "shopify.bestSellers",
66133
+ description: `Start here for 'what sells best' and 'which product should we lead with': best sellers by units ordered (before anything came back) beside refunded units, orders, buyers, revenue (the lines' price after discounts, before tax and shipping), refunds, refund rate and growth. --view variants (one product's options, with --product), trend (top products by month), together (products bought in one order), repeat (first buys whose buyers came back most) or momentum (products selling 30% or more below the period before). ${PERIOD_NOTE} For the catalog itself use \`baker shopify products\`.`,
66134
+ args: {
66135
+ ...periodArgs,
66136
+ view: {
66137
+ type: "string",
66138
+ description: "products (default) | variants | trend | together | repeat | momentum",
66139
+ required: false,
66140
+ enum: ["products", "variants", "trend", "together", "repeat", "momentum"]
66141
+ },
66142
+ sort: {
66143
+ type: "string",
66144
+ description: "units (default) | revenue | orders",
66145
+ required: false,
66146
+ enum: ["units", "revenue", "orders"]
66147
+ },
66148
+ product: { type: "string", description: "One product (id or handle): its variants or its trend", required: false },
66149
+ limit: { type: "number", description: "Rows, 1-100 (default 10; 5 products for trend)", required: false },
66150
+ full: { ...fullArg, description: "Add product and variant ids and the period before" }
66151
+ }
66152
+ });
66153
+ var bestSellersCommand = defineCommand209({
66154
+ meta: {
66155
+ name: "best-sellers",
66156
+ description: "What sells: best sellers, variants, trend, bought together, repeat leaders, losing momentum. Start here: baker shopify best-sellers --days 90"
66157
+ },
66158
+ args: {
66159
+ ...periodFlags,
66160
+ view: {
66161
+ type: "string",
66162
+ description: "products | variants | trend | together | repeat | momentum",
66163
+ required: false
66164
+ },
66165
+ sort: { type: "string", description: "units | revenue | orders", required: false },
66166
+ product: { type: "string", description: "A product's id or handle", required: false },
66167
+ limit: { type: "string", description: "Rows, 1-100", required: false },
66168
+ full: { type: "boolean", description: "Add ids and the period before", required: false, default: false }
66169
+ },
66170
+ run: async ({ args: args5 }) => {
66171
+ try {
66172
+ const answer = await apiGet(
66173
+ "/api/shopify/best-sellers",
66174
+ params({
66175
+ days: args5.days,
66176
+ all: args5.all,
66177
+ country: args5.country,
66178
+ view: args5.view,
66179
+ sort: args5.sort,
66180
+ product: args5.product,
66181
+ limit: args5.limit,
66182
+ full: args5.full
66183
+ })
66184
+ );
66185
+ const { data, meta } = split(answer);
66186
+ emit2(data, meta, [...storeStateHints(answer.store), ...bestSellersHints(answer)]);
66187
+ } catch (err) {
66188
+ fail9(err);
66189
+ }
66190
+ }
66191
+ });
66192
+ registerSchema({
66193
+ command: "shopify.cohorts",
66194
+ description: "Buyers grouped by the month of their first order (the last --months, default 12): how many, the share who ordered again 1, 2, 3, 6 and 12 months later, and what they spent per buyer by month 12. Months not reached yet are null. Use it for 'are buyers coming back more than they used to' and lifetime value over time.",
66195
+ args: {
66196
+ months: { type: "number", description: "Cohorts, the last 3-36 months (default 12)", required: false },
66197
+ country: { type: "string", description: "Only orders to this country (two-letter code)", required: false },
66198
+ full: {
66199
+ ...fullArg,
66200
+ description: "Add every month of every cohort: active buyers, orders, net, cumulative net per buyer"
66201
+ }
66202
+ }
66203
+ });
66204
+ var cohortsCommand = defineCommand209({
66205
+ meta: {
66206
+ name: "cohorts",
66207
+ description: "Buyers by first-order month: who came back and what they spent. Example: baker shopify cohorts --months 12"
66208
+ },
66209
+ args: {
66210
+ months: { type: "string", description: "The last 3-36 months", required: false },
66211
+ country: { type: "string", description: "A two-letter country code", required: false },
66212
+ full: { type: "boolean", description: "Every month of every cohort", required: false, default: false }
66213
+ },
66214
+ run: async ({ args: args5 }) => {
66215
+ try {
66216
+ const answer = await apiGet(
66217
+ "/api/shopify/cohorts",
66218
+ params({ months: args5.months, country: args5.country, full: args5.full })
66219
+ );
66220
+ const { data, meta } = split(answer);
66221
+ emit2(data, meta, [...storeStateHints(answer.store), ...cohortsHints(answer)]);
66222
+ } catch (err) {
66223
+ fail9(err);
66224
+ }
66225
+ }
66226
+ });
66227
+ registerSchema({
66228
+ command: "shopify.segments",
66229
+ description: "Every buyer at the period's end by segment \u2014 best buyers, loyal, at risk, new and one-time \u2014 with how many, their share, net, average orders, days since their last order, the countries they are in and the products their first orders held. Each segment's rule is given in words with this store's own numbers (its typical gap between orders and the spend of its top fifth). Start here for 'who are our best customers' and 'who should we win back'.",
66230
+ args: {
66231
+ ...periodArgs,
66232
+ full: { ...fullArg, description: "Add the first products' ids" }
66233
+ }
66234
+ });
66235
+ var segmentsCommand = defineCommand209({
66236
+ meta: {
66237
+ name: "segments",
66238
+ description: "Buyers by segment: best, loyal, at risk, new, one-time. Example: baker shopify segments"
66239
+ },
66240
+ args: {
66241
+ ...periodFlags,
66242
+ full: { type: "boolean", description: "Add product ids", required: false, default: false }
66243
+ },
66244
+ run: async ({ args: args5 }) => {
66245
+ try {
66246
+ const answer = await apiGet(
66247
+ "/api/shopify/segments",
66248
+ params({ days: args5.days, all: args5.all, country: args5.country, full: args5.full })
66249
+ );
66250
+ const { data, meta } = split(answer);
66251
+ emit2(data, meta, [...storeStateHints(answer.store), ...segmentsHints(answer)]);
66252
+ } catch (err) {
66253
+ fail9(err);
66254
+ }
66255
+ }
66256
+ });
66257
+
66258
+ // src/commands/shopify/index.ts
65202
66259
  registerSchema({
65203
66260
  command: "shopify.store",
65204
66261
  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.',
@@ -65294,7 +66351,77 @@ registerSchema({
65294
66351
  full: { ...fullArg, description: "Add each option's SKU and each product's Baker link" }
65295
66352
  }
65296
66353
  });
65297
- var storeCommand = defineCommand209({
66354
+ registerSchema({
66355
+ command: "shopify.sales",
66356
+ description: "Start here for 'how much did we sell', 'which campaigns or pages brought sales' and 'which products sold most': the connected store's orders, sales, refunds and net, in the store's currency. --by day (default) and --by product count each sale, addition and refund in the period it happened; --by campaign and --by landing take the orders paid in the period and credit each to the campaign or Baker page that brought its buyer, with what was added to and refunded on it since \u2014 so the rows add up to `totals` exactly, with named rows for sales from before Baker measured the store and sales from no campaign. A sale counts when it is paid, at what was received. `salesReporting` says who reports the sales to Meta and Google: `undecided` and `shopify_channel` mean Baker only reports them, and only a manager can change that in Brain \u2192 Store. --by weekday|hour|month|month_of_year says when buyers order, in the store's own time, from the store's whole order history (orders placed in the period, with what was refunded on them since): use it for when to run or schedule campaigns; only these take --days up to 3650 or --all.",
66357
+ args: {
66358
+ days: {
66359
+ type: "number",
66360
+ description: "The last N days: 1-395 by day, product, campaign or landing; 1-3650 by weekday, hour or month (default 30)",
66361
+ required: false
66362
+ },
66363
+ all: {
66364
+ type: "boolean",
66365
+ description: "Every order the store has (by weekday, hour, month or month_of_year only)",
66366
+ required: false,
66367
+ default: false
66368
+ },
66369
+ by: {
66370
+ type: "string",
66371
+ description: "day | product | campaign | landing | weekday | hour | month | month_of_year (default day)",
66372
+ required: false,
66373
+ enum: ["day", "product", "campaign", "landing", "weekday", "hour", "month", "month_of_year"]
66374
+ },
66375
+ limit: {
66376
+ type: "number",
66377
+ description: "Rows of a breakdown, 1-100 (by campaign or landing the rest are folded into one 'other' row)",
66378
+ required: false
66379
+ },
66380
+ full: {
66381
+ ...fullArg,
66382
+ description: "Add each row's source, medium, campaign, click platform, landing id or product id"
66383
+ }
66384
+ }
66385
+ });
66386
+ var salesCommand = defineCommand210({
66387
+ meta: {
66388
+ name: "sales",
66389
+ 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"
66390
+ },
66391
+ args: {
66392
+ days: {
66393
+ type: "string",
66394
+ description: "The last N days (1-395; up to 3650 by weekday, hour or month)",
66395
+ required: false
66396
+ },
66397
+ all: {
66398
+ type: "boolean",
66399
+ description: "Every order (by weekday, hour or month only)",
66400
+ required: false,
66401
+ default: false
66402
+ },
66403
+ by: {
66404
+ type: "string",
66405
+ description: "day | product | campaign | landing | weekday | hour | month | month_of_year (default day)",
66406
+ required: false
66407
+ },
66408
+ limit: { type: "string", description: "Rows of a breakdown, 1-100", required: false },
66409
+ full: { type: "boolean", description: "Add sources, campaigns and ids per row", required: false, default: false }
66410
+ },
66411
+ run: async ({ args: args5 }) => {
66412
+ try {
66413
+ const answer = await apiGet(
66414
+ "/api/shopify/sales",
66415
+ params({ days: args5.days, all: args5.all, by: args5.by, limit: args5.limit, full: args5.full })
66416
+ );
66417
+ const { store, salesReporting, history, ...data } = answer;
66418
+ emit2(data, { store, salesReporting, history }, [...storeStateHints(store), ...salesHints(answer)]);
66419
+ } catch (err) {
66420
+ fail9(err);
66421
+ }
66422
+ }
66423
+ });
66424
+ var storeCommand = defineCommand210({
65298
66425
  meta: {
65299
66426
  name: "store",
65300
66427
  description: "The connected Shopify store: currency, countries, how many products, what is out of stock. Example: baker shopify store"
@@ -65311,7 +66438,7 @@ var storeCommand = defineCommand209({
65311
66438
  }
65312
66439
  }
65313
66440
  });
65314
- var productsCommand = defineCommand209({
66441
+ var productsCommand = defineCommand210({
65315
66442
  meta: {
65316
66443
  name: "products",
65317
66444
  description: 'What the store sells, with price and stock. Example: baker shopify products --search "snowboard" --stock out_of_stock'
@@ -65357,7 +66484,7 @@ var productsCommand = defineCommand209({
65357
66484
  }
65358
66485
  }
65359
66486
  });
65360
- var productCommand = defineCommand209({
66487
+ var productCommand = defineCommand210({
65361
66488
  meta: {
65362
66489
  name: "product",
65363
66490
  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"
@@ -65420,7 +66547,7 @@ var productCommand = defineCommand209({
65420
66547
  }
65421
66548
  }
65422
66549
  });
65423
- var collectionsCommand = defineCommand209({
66550
+ var collectionsCommand = defineCommand210({
65424
66551
  meta: {
65425
66552
  name: "collections",
65426
66553
  description: "The store's collections, with the handles --collection takes. Example: baker shopify collections --full"
@@ -65445,7 +66572,7 @@ var collectionsCommand = defineCommand209({
65445
66572
  }
65446
66573
  }
65447
66574
  });
65448
- var stockCommand2 = defineCommand209({
66575
+ var stockCommand2 = defineCommand210({
65449
66576
  meta: {
65450
66577
  name: "stock",
65451
66578
  description: "Every product with options out of stock, naming them. Example: baker shopify stock"
@@ -65462,18 +66589,18 @@ var stockCommand2 = defineCommand209({
65462
66589
  params({ collection: args5.collection, limit: args5.limit, full: args5.full })
65463
66590
  );
65464
66591
  const { store, ...data } = answer;
65465
- const limit = Number(args5.limit ?? 50);
65466
- emit2(data, { store }, [...storeStateHints(store), ...stockHints(answer, Number.isFinite(limit) ? limit : 50)]);
66592
+ const limit2 = Number(args5.limit ?? 50);
66593
+ emit2(data, { store }, [...storeStateHints(store), ...stockHints(answer, Number.isFinite(limit2) ? limit2 : 50)]);
65467
66594
  } catch (err) {
65468
66595
  fail9(err);
65469
66596
  }
65470
66597
  }
65471
66598
  });
65472
- var shopifyCommand = defineCommand209({
66599
+ var shopifyCommand = defineCommand210({
65473
66600
  meta: {
65474
66601
  name: "shopify",
65475
- description: `Read the connected Shopify store as Baker keeps it \u2014 products, prices by country, stock, collections
65476
- and product photos. Read-only: nothing here changes the store.
66602
+ description: `Read the connected Shopify store as Baker keeps it \u2014 products, prices by country, stock, collections,
66603
+ product photos, what it sold, and who buys, where and what. Read-only: nothing here changes the store.
65477
66604
 
65478
66605
  Examples:
65479
66606
  baker shopify store # currency, countries, how many products, what is out of stock
@@ -65487,6 +66614,14 @@ Examples:
65487
66614
  baker shopify product <id> --out-of-stock # only its options that are out of stock
65488
66615
  baker shopify stock # every product with options out of stock
65489
66616
  baker shopify collections # collections, with handles for --collection
66617
+ baker shopify sales --days 7 --by campaign # what sold and which campaigns brought it (--by day|product|landing)
66618
+ baker shopify sales --all --by weekday # when buyers order, in the store's time (--by hour|month|month_of_year)
66619
+ baker shopify countries --days 90 # where buyers are, with growth (--country DE for its regions)
66620
+ baker shopify best-sellers --days 90 # what sells (--view variants|trend|together|repeat|momentum)
66621
+ baker shopify buyers --days 90 --by channel # who buys, repeat rate, segments, value by acquisition channel
66622
+ baker shopify buyer 1042 # one numbered customer's orders
66623
+ baker shopify segments # best, loyal, at risk, new, one-time \u2014 and who to find more of
66624
+ baker shopify cohorts --months 12 # buyers by first-order month: who came back
65490
66625
 
65491
66626
  Product photos are already in the image library: place them by imageId
65492
66627
  (baker images download <imageIds> --out src/pages/<page>/_images/), never by a cdn.shopify.com link.
@@ -65502,15 +66637,22 @@ Full guide: __tooling__/docs/tools/baker/shopify.md`
65502
66637
  products: productsCommand,
65503
66638
  product: productCommand,
65504
66639
  collections: collectionsCommand,
65505
- stock: stockCommand2
66640
+ stock: stockCommand2,
66641
+ sales: salesCommand,
66642
+ buyers: buyersCommand,
66643
+ buyer: buyerCommand,
66644
+ countries: countriesCommand2,
66645
+ "best-sellers": bestSellersCommand,
66646
+ cohorts: cohortsCommand,
66647
+ segments: segmentsCommand
65506
66648
  }
65507
66649
  });
65508
66650
 
65509
66651
  // src/commands/studio/index.ts
65510
- import { defineCommand as defineCommand218 } from "citty";
66652
+ import { defineCommand as defineCommand219 } from "citty";
65511
66653
 
65512
66654
  // src/commands/studio/animate.ts
65513
- import { defineCommand as defineCommand210 } from "citty";
66655
+ import { defineCommand as defineCommand211 } from "citty";
65514
66656
 
65515
66657
  // src/commands/studio/spokenPace.ts
65516
66658
  var WORDS_PER_SECOND3 = SPOKEN_WORDS_PER_SECOND;
@@ -65521,9 +66663,9 @@ function wordsThatFit(seconds) {
65521
66663
  function spokenPaceHint(prompt, seconds, model) {
65522
66664
  const words2 = spokenWords(prompt);
65523
66665
  if (words2 === 0) return null;
65524
- const split = model ? splitScriptFor(words2, model) : null;
65525
- if (split) {
65526
- return `The line is ${words2} words \u2014 about ${Math.ceil(words2 / WORDS_PER_SECOND3)}s of speech \u2014 and ${model} renders at most ${split.maxSeconds}s (~${Math.floor(split.maxSeconds * WORDS_PER_SECOND3)} words). It will not refuse and it will not cut: it will RUSH, swallowing syllables. Split the script into ${split.clips} takes of roughly ${split.wordsPerClip} words each, rendered separately and cut together \u2014 break it where the thought breaks, not mid-sentence. Or move to a model that renders longer (bytedance/seedance-2.5 goes to 30s).`;
66666
+ const split2 = model ? splitScriptFor(words2, model) : null;
66667
+ if (split2) {
66668
+ return `The line is ${words2} words \u2014 about ${Math.ceil(words2 / WORDS_PER_SECOND3)}s of speech \u2014 and ${model} renders at most ${split2.maxSeconds}s (~${Math.floor(split2.maxSeconds * WORDS_PER_SECOND3)} words). It will not refuse and it will not cut: it will RUSH, swallowing syllables. Split the script into ${split2.clips} takes of roughly ${split2.wordsPerClip} words each, rendered separately and cut together \u2014 break it where the thought breaks, not mid-sentence. Or move to a model that renders longer (bytedance/seedance-2.5 goes to 30s).`;
65527
66669
  }
65528
66670
  if (!seconds) return null;
65529
66671
  const fits = wordsThatFit(seconds);
@@ -65738,10 +66880,10 @@ var defaultDeps = {
65738
66880
  ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
65739
66881
  upload: (path48) => uploadLocalImage({ file: path48, contentType: detectImageContentType(path48), source: "uploaded" })
65740
66882
  };
65741
- async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
66883
+ async function resolveLibraryImageIds(spec, limit2, deps = defaultDeps) {
65742
66884
  const refs = parseImageRefs(spec);
65743
- if (limit && refs.length > limit.max) {
65744
- throw new ApiError("VALIDATION_ERROR", limit.refuse(refs.length));
66885
+ if (limit2 && refs.length > limit2.max) {
66886
+ throw new ApiError("VALIDATION_ERROR", limit2.refuse(refs.length));
65745
66887
  }
65746
66888
  const imageIds = [];
65747
66889
  const added = [];
@@ -66023,7 +67165,7 @@ async function adByHandHintFor(body) {
66023
67165
  return null;
66024
67166
  }
66025
67167
  }
66026
- var animateCommand = defineCommand210({
67168
+ var animateCommand = defineCommand211({
66027
67169
  meta: {
66028
67170
  name: "animate",
66029
67171
  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"
@@ -66148,7 +67290,7 @@ var animateCommand = defineCommand210({
66148
67290
  });
66149
67291
 
66150
67292
  // src/commands/studio/generate.ts
66151
- import { defineCommand as defineCommand211 } from "citty";
67293
+ import { defineCommand as defineCommand212 } from "citty";
66152
67294
  var MODEL_LIST2 = IMAGE_MODEL_IDS;
66153
67295
  var DEFAULT_MAX_WAIT_MS2 = 24e4;
66154
67296
  registerSchema({
@@ -66236,11 +67378,11 @@ registerSchema({
66236
67378
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
66237
67379
  }
66238
67380
  });
66239
- function parseJsonArg(raw, flag2) {
67381
+ function parseJsonArg(raw, flag3) {
66240
67382
  try {
66241
67383
  return JSON.parse(raw);
66242
67384
  } catch {
66243
- throw new ApiError("VALIDATION_ERROR", `--${flag2} must be valid JSON (got: ${raw})`);
67385
+ throw new ApiError("VALIDATION_ERROR", `--${flag3} must be valid JSON (got: ${raw})`);
66244
67386
  }
66245
67387
  }
66246
67388
  function referenceLimitFor(model) {
@@ -66284,7 +67426,7 @@ function buildGenerateBody(args5, prompt) {
66284
67426
  }
66285
67427
  return body;
66286
67428
  }
66287
- var generateCommand = defineCommand211({
67429
+ var generateCommand = defineCommand212({
66288
67430
  meta: {
66289
67431
  name: "generate",
66290
67432
  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]]'"
@@ -66367,7 +67509,7 @@ var generateCommand = defineCommand211({
66367
67509
  });
66368
67510
 
66369
67511
  // src/commands/studio/get.ts
66370
- import { defineCommand as defineCommand212 } from "citty";
67512
+ import { defineCommand as defineCommand213 } from "citty";
66371
67513
  registerSchema({
66372
67514
  command: "studio.get",
66373
67515
  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.",
@@ -66376,7 +67518,7 @@ registerSchema({
66376
67518
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
66377
67519
  }
66378
67520
  });
66379
- var getCommand5 = defineCommand212({
67521
+ var getCommand5 = defineCommand213({
66380
67522
  meta: {
66381
67523
  name: "get",
66382
67524
  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"
@@ -66405,7 +67547,7 @@ var getCommand5 = defineCommand212({
66405
67547
  });
66406
67548
 
66407
67549
  // src/commands/studio/improve.ts
66408
- import { defineCommand as defineCommand213 } from "citty";
67550
+ import { defineCommand as defineCommand214 } from "citty";
66409
67551
  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.";
66410
67552
  registerSchema({
66411
67553
  command: "studio.improve",
@@ -66430,7 +67572,7 @@ registerSchema({
66430
67572
  }
66431
67573
  }
66432
67574
  });
66433
- var improveCommand = defineCommand213({
67575
+ var improveCommand = defineCommand214({
66434
67576
  meta: {
66435
67577
  name: "improve",
66436
67578
  description: `${DESCRIPTION2}
@@ -66474,7 +67616,7 @@ Examples:
66474
67616
  });
66475
67617
 
66476
67618
  // src/commands/studio/keep.ts
66477
- import { defineCommand as defineCommand214 } from "citty";
67619
+ import { defineCommand as defineCommand215 } from "citty";
66478
67620
  registerSchema({
66479
67621
  command: "studio.keep",
66480
67622
  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.",
@@ -66484,7 +67626,7 @@ registerSchema({
66484
67626
  undo: { type: "boolean", description: "Un-star an image, or take a kept clip back out", required: false }
66485
67627
  }
66486
67628
  });
66487
- var keepCommand = defineCommand214({
67629
+ var keepCommand = defineCommand215({
66488
67630
  meta: {
66489
67631
  name: "keep",
66490
67632
  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"
@@ -66535,7 +67677,7 @@ var keepCommand = defineCommand214({
66535
67677
  });
66536
67678
 
66537
67679
  // src/commands/studio/list.ts
66538
- import { defineCommand as defineCommand215 } from "citty";
67680
+ import { defineCommand as defineCommand216 } from "citty";
66539
67681
  registerSchema({
66540
67682
  command: "studio.list",
66541
67683
  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.",
@@ -66546,7 +67688,7 @@ registerSchema({
66546
67688
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
66547
67689
  }
66548
67690
  });
66549
- var listCommand18 = defineCommand215({
67691
+ var listCommand18 = defineCommand216({
66550
67692
  meta: {
66551
67693
  name: "list",
66552
67694
  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"
@@ -66580,7 +67722,7 @@ var listCommand18 = defineCommand215({
66580
67722
  });
66581
67723
 
66582
67724
  // src/commands/studio/models.ts
66583
- import { defineCommand as defineCommand216 } from "citty";
67725
+ import { defineCommand as defineCommand217 } from "citty";
66584
67726
  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.";
66585
67727
  registerSchema({
66586
67728
  command: "studio.models",
@@ -66667,7 +67809,7 @@ function buildModelCards(kind, model) {
66667
67809
  const selected = model ? ids.filter((id) => id === model) : ids;
66668
67810
  return selected.map((id) => build(id));
66669
67811
  }
66670
- var modelsCommand = defineCommand216({
67812
+ var modelsCommand = defineCommand217({
66671
67813
  meta: {
66672
67814
  name: "models",
66673
67815
  description: `${DESCRIPTION3}
@@ -66714,13 +67856,13 @@ Examples:
66714
67856
  });
66715
67857
 
66716
67858
  // src/commands/studio/skills.ts
66717
- import { defineCommand as defineCommand217 } from "citty";
67859
+ import { defineCommand as defineCommand218 } from "citty";
66718
67860
  registerSchema({
66719
67861
  command: "studio.skills",
66720
67862
  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.",
66721
67863
  args: {}
66722
67864
  });
66723
- var skillsCommand = defineCommand217({
67865
+ var skillsCommand = defineCommand218({
66724
67866
  meta: {
66725
67867
  name: "skills",
66726
67868
  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"
@@ -66744,7 +67886,7 @@ var skillsCommand = defineCommand217({
66744
67886
  });
66745
67887
 
66746
67888
  // src/commands/studio/index.ts
66747
- var studioCommand = defineCommand218({
67889
+ var studioCommand = defineCommand219({
66748
67890
  meta: {
66749
67891
  name: "studio",
66750
67892
  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.
@@ -66787,10 +67929,10 @@ Full guide: __tooling__/docs/tools/baker/studio.md`
66787
67929
  });
66788
67930
 
66789
67931
  // src/commands/tag-manager/index.ts
66790
- import { defineCommand as defineCommand222 } from "citty";
67932
+ import { defineCommand as defineCommand223 } from "citty";
66791
67933
 
66792
67934
  // src/commands/tag-manager/draft.ts
66793
- import { defineCommand as defineCommand219 } from "citty";
67935
+ import { defineCommand as defineCommand220 } from "citty";
66794
67936
 
66795
67937
  // src/commands/tag-manager/shared.ts
66796
67938
  import { readFileSync as readFileSync20 } from "fs";
@@ -66806,21 +67948,21 @@ function requireTarget6(args5, entity) {
66806
67948
  }
66807
67949
  return target2;
66808
67950
  }
66809
- function loadJsonArg2(args5, flag2 = "json") {
66810
- const inline = args5[flag2];
67951
+ function loadJsonArg2(args5, flag3 = "json") {
67952
+ const inline = args5[flag3];
66811
67953
  const file = args5.file;
66812
67954
  const raw = typeof file === "string" && file.length > 0 ? readFileSync20(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
66813
67955
  if (raw === void 0) {
66814
- failValidation5(`pass --${flag2} with inline JSON or --file with a path to a JSON file`);
67956
+ failValidation5(`pass --${flag3} with inline JSON or --file with a path to a JSON file`);
66815
67957
  }
66816
67958
  try {
66817
67959
  const parsed = JSON.parse(raw);
66818
67960
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
66819
- failValidation5(`--${flag2} must be a JSON object`);
67961
+ failValidation5(`--${flag3} must be a JSON object`);
66820
67962
  }
66821
67963
  return parsed;
66822
67964
  } catch {
66823
- return failValidation5(`--${flag2} is not valid JSON`);
67965
+ return failValidation5(`--${flag3} is not valid JSON`);
66824
67966
  }
66825
67967
  }
66826
67968
  var RETRYABLE_CODES2 = /* @__PURE__ */ new Set(["RATE_LIMITED", "UNAVAILABLE", "INTERNAL_ERROR", "NETWORK_ERROR", "TIMEOUT"]);
@@ -66913,13 +68055,13 @@ registerSchema({
66913
68055
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
66914
68056
  }
66915
68057
  });
66916
- var draftCommand5 = defineCommand219({
68058
+ var draftCommand5 = defineCommand220({
66917
68059
  meta: {
66918
68060
  name: "draft",
66919
68061
  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."
66920
68062
  },
66921
68063
  subCommands: {
66922
- list: defineCommand219({
68064
+ list: defineCommand220({
66923
68065
  meta: {
66924
68066
  name: "list",
66925
68067
  description: "Review everything staged on this chat (--json for the raw envelope)"
@@ -66932,7 +68074,7 @@ var draftCommand5 = defineCommand219({
66932
68074
  await draftList2(args5.json === true, args5.chat);
66933
68075
  }
66934
68076
  }),
66935
- show: defineCommand219({
68077
+ show: defineCommand220({
66936
68078
  meta: {
66937
68079
  name: "show",
66938
68080
  description: "Print the full staged payload for one change \u2014 the receipt to verify it looks right before publish (never truncated)."
@@ -66949,7 +68091,7 @@ var draftCommand5 = defineCommand219({
66949
68091
  );
66950
68092
  }
66951
68093
  }),
66952
- amend: defineCommand219({
68094
+ amend: defineCommand220({
66953
68095
  meta: {
66954
68096
  name: "amend",
66955
68097
  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."
@@ -66966,7 +68108,7 @@ var draftCommand5 = defineCommand219({
66966
68108
  });
66967
68109
  }
66968
68110
  }),
66969
- remove: defineCommand219({
68111
+ remove: defineCommand220({
66970
68112
  meta: { name: "remove", description: "Remove one staged change (cascades to anything depending on it)" },
66971
68113
  args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
66972
68114
  run: async ({ args: args5 }) => {
@@ -66975,7 +68117,7 @@ var draftCommand5 = defineCommand219({
66975
68117
  });
66976
68118
  }
66977
68119
  }),
66978
- clear: defineCommand219({
68120
+ clear: defineCommand220({
66979
68121
  meta: { name: "clear", description: "Discard all Tag Manager changes staged on this chat" },
66980
68122
  run: async () => {
66981
68123
  await draftAction3("/api/tag-manager/draft/clear", {});
@@ -66985,7 +68127,7 @@ var draftCommand5 = defineCommand219({
66985
68127
  });
66986
68128
 
66987
68129
  // src/commands/tag-manager/read.ts
66988
- import { defineCommand as defineCommand220 } from "citty";
68130
+ import { defineCommand as defineCommand221 } from "citty";
66989
68131
  registerSchema({
66990
68132
  command: "tagManager.containers",
66991
68133
  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.",
@@ -67026,7 +68168,7 @@ function containersHints(containers) {
67026
68168
  }))
67027
68169
  });
67028
68170
  }
67029
- var containersCommand = defineCommand220({
68171
+ var containersCommand = defineCommand221({
67030
68172
  meta: {
67031
68173
  name: "containers",
67032
68174
  description: `List Tag Manager containers reachable by this company's connection.
@@ -67043,7 +68185,7 @@ Start here:
67043
68185
  }
67044
68186
  }
67045
68187
  });
67046
- var readCommand = defineCommand220({
68188
+ var readCommand = defineCommand221({
67047
68189
  meta: {
67048
68190
  name: "read",
67049
68191
  description: `Read the current contents of the Tag Manager container \u2014 always do this before staging changes.
@@ -67085,7 +68227,7 @@ Examples:
67085
68227
  });
67086
68228
 
67087
68229
  // src/commands/tag-manager/write-commands.ts
67088
- import { defineCommand as defineCommand221 } from "citty";
68230
+ import { defineCommand as defineCommand222 } from "citty";
67089
68231
  var CONTAINER_ARG_DESCRIPTION = "Numeric container id (optional only when one container is connected \u2014 run `baker tag-manager containers`)";
67090
68232
  var ENTITIES = [
67091
68233
  {
@@ -67141,10 +68283,10 @@ for (const { entity, noun, createHint } of ENTITIES) {
67141
68283
  });
67142
68284
  }
67143
68285
  function entityCommand(entity, noun, example) {
67144
- return defineCommand221({
68286
+ return defineCommand222({
67145
68287
  meta: { name: entity, description: `Stage ${noun} changes on this chat's Tag Manager draft` },
67146
68288
  subCommands: {
67147
- create: defineCommand221({
68289
+ create: defineCommand222({
67148
68290
  meta: {
67149
68291
  name: "create",
67150
68292
  description: `Stage a new ${noun}
@@ -67166,7 +68308,7 @@ Examples:
67166
68308
  });
67167
68309
  }
67168
68310
  }),
67169
- update: defineCommand221({
68311
+ update: defineCommand222({
67170
68312
  meta: {
67171
68313
  name: "update",
67172
68314
  description: `Stage an update to an existing ${noun} (pass its id or path)`
@@ -67186,7 +68328,7 @@ Examples:
67186
68328
  });
67187
68329
  }
67188
68330
  }),
67189
- delete: defineCommand221({
68331
+ delete: defineCommand222({
67190
68332
  meta: { name: "delete", description: `Stage the deletion of a ${noun} (pass its id or path)` },
67191
68333
  args: {
67192
68334
  id: { type: "positional", description: `${noun} id or path`, required: false },
@@ -67227,7 +68369,7 @@ function builtinTypes(args5) {
67227
68369
  }
67228
68370
  return raw.split(",").map((entry) => entry.trim());
67229
68371
  }
67230
- var builtinCommand = defineCommand221({
68372
+ var builtinCommand = defineCommand222({
67231
68373
  meta: {
67232
68374
  name: "builtin",
67233
68375
  description: `Enable or disable built-in variables
@@ -67237,7 +68379,7 @@ Examples:
67237
68379
  baker tag-manager builtin disable --types formId`
67238
68380
  },
67239
68381
  subCommands: {
67240
- enable: defineCommand221({
68382
+ enable: defineCommand222({
67241
68383
  meta: { name: "enable", description: "Stage enabling built-in variables" },
67242
68384
  args: {
67243
68385
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -67251,7 +68393,7 @@ Examples:
67251
68393
  });
67252
68394
  }
67253
68395
  }),
67254
- disable: defineCommand221({
68396
+ disable: defineCommand222({
67255
68397
  meta: { name: "disable", description: "Stage disabling built-in variables" },
67256
68398
  args: {
67257
68399
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -67269,7 +68411,7 @@ Examples:
67269
68411
  });
67270
68412
 
67271
68413
  // src/commands/tag-manager/index.ts
67272
- var tagManagerCommand = defineCommand222({
68414
+ var tagManagerCommand = defineCommand223({
67273
68415
  meta: {
67274
68416
  name: "tag-manager",
67275
68417
  description: `Read and change what lives inside the client's Google Tag Manager container \u2014 tags, triggers, variables, folders and built-in variables.
@@ -67306,7 +68448,7 @@ Full guide: __tooling__/docs/tools/baker/tag-manager.md`
67306
68448
  });
67307
68449
 
67308
68450
  // src/commands/tags/index.ts
67309
- import { defineCommand as defineCommand223 } from "citty";
68451
+ import { defineCommand as defineCommand224 } from "citty";
67310
68452
 
67311
68453
  // src/commands/tags/shared.ts
67312
68454
  function failApi3(err) {
@@ -67386,7 +68528,7 @@ async function listTags(json) {
67386
68528
  var listArgs11 = {
67387
68529
  json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list" }
67388
68530
  };
67389
- var listCommand19 = defineCommand223({
68531
+ var listCommand19 = defineCommand224({
67390
68532
  meta: {
67391
68533
  name: "list",
67392
68534
  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"
@@ -67405,7 +68547,7 @@ async function listDraft4(chat) {
67405
68547
  failApi3(err);
67406
68548
  }
67407
68549
  }
67408
- var draftCommand6 = defineCommand223({
68550
+ var draftCommand6 = defineCommand224({
67409
68551
  meta: {
67410
68552
  name: "draft",
67411
68553
  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."
@@ -67415,7 +68557,7 @@ var draftCommand6 = defineCommand223({
67415
68557
  await listDraft4(args5.chat);
67416
68558
  }
67417
68559
  });
67418
- var tagsCommand4 = defineCommand223({
68560
+ var tagsCommand4 = defineCommand224({
67419
68561
  meta: {
67420
68562
  name: "tags",
67421
68563
  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.
@@ -67444,10 +68586,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
67444
68586
  });
67445
68587
 
67446
68588
  // src/commands/testimonials/index.ts
67447
- import { defineCommand as defineCommand227 } from "citty";
68589
+ import { defineCommand as defineCommand228 } from "citty";
67448
68590
 
67449
68591
  // src/commands/testimonials/get.ts
67450
- import { defineCommand as defineCommand224 } from "citty";
68592
+ import { defineCommand as defineCommand225 } from "citty";
67451
68593
  registerSchema({
67452
68594
  command: "testimonials.get",
67453
68595
  description: "Get a single testimonial by ID",
@@ -67455,7 +68597,7 @@ registerSchema({
67455
68597
  id: { type: "string", description: "Testimonial ID", required: true }
67456
68598
  }
67457
68599
  });
67458
- var getCommand6 = defineCommand224({
68600
+ var getCommand6 = defineCommand225({
67459
68601
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
67460
68602
  args: {
67461
68603
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -67492,7 +68634,7 @@ var getCommand6 = defineCommand224({
67492
68634
  });
67493
68635
 
67494
68636
  // src/commands/testimonials/list.ts
67495
- import { defineCommand as defineCommand225 } from "citty";
68637
+ import { defineCommand as defineCommand226 } from "citty";
67496
68638
 
67497
68639
  // src/commands/testimonials/emptyCorpusHints.ts
67498
68640
  function resolveEmptyReason({
@@ -67641,15 +68783,15 @@ var PARAM_NAMES = {
67641
68783
  };
67642
68784
  function buildListParams(args5) {
67643
68785
  const params2 = {};
67644
- for (const [flag2, param] of Object.entries(PARAM_NAMES)) {
67645
- const value = args5[flag2];
68786
+ for (const [flag3, param] of Object.entries(PARAM_NAMES)) {
68787
+ const value = args5[flag3];
67646
68788
  if (value !== void 0 && value !== "") {
67647
68789
  params2[param] = String(value);
67648
68790
  }
67649
68791
  }
67650
68792
  return params2;
67651
68793
  }
67652
- var listCommand20 = defineCommand225({
68794
+ var listCommand20 = defineCommand226({
67653
68795
  meta: {
67654
68796
  name: "list",
67655
68797
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -67709,7 +68851,7 @@ var listCommand20 = defineCommand225({
67709
68851
  });
67710
68852
 
67711
68853
  // src/commands/testimonials/search.ts
67712
- import { defineCommand as defineCommand226 } from "citty";
68854
+ import { defineCommand as defineCommand227 } from "citty";
67713
68855
  var FILTER_FLAGS2 = ["source", "rating-min", "rating-max", "status", "sentiment", "language", "tags"];
67714
68856
  function languageBiasHint(results, requestedLanguage) {
67715
68857
  if (requestedLanguage) {
@@ -67788,7 +68930,7 @@ function buildSearchRequest(query, args5) {
67788
68930
  }
67789
68931
  return body;
67790
68932
  }
67791
- var searchCommand3 = defineCommand226({
68933
+ var searchCommand3 = defineCommand227({
67792
68934
  meta: {
67793
68935
  name: "search",
67794
68936
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -67852,7 +68994,7 @@ var searchCommand3 = defineCommand226({
67852
68994
  var tagsCommand5 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
67853
68995
 
67854
68996
  // src/commands/testimonials/index.ts
67855
- var testimonialsCommand = defineCommand227({
68997
+ var testimonialsCommand = defineCommand228({
67856
68998
  meta: {
67857
68999
  name: "testimonials",
67858
69000
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -67874,10 +69016,10 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
67874
69016
  });
67875
69017
 
67876
69018
  // src/commands/videos/index.ts
67877
- import { defineCommand as defineCommand235 } from "citty";
69019
+ import { defineCommand as defineCommand236 } from "citty";
67878
69020
 
67879
69021
  // src/commands/videos/delete.ts
67880
- import { defineCommand as defineCommand228 } from "citty";
69022
+ import { defineCommand as defineCommand229 } from "citty";
67881
69023
  registerSchema({
67882
69024
  command: "videos.delete",
67883
69025
  description: "Delete a video by ID",
@@ -67891,7 +69033,7 @@ registerSchema({
67891
69033
  }
67892
69034
  }
67893
69035
  });
67894
- var deleteCommand4 = defineCommand228({
69036
+ var deleteCommand4 = defineCommand229({
67895
69037
  meta: {
67896
69038
  name: "delete",
67897
69039
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -67932,7 +69074,7 @@ var deleteCommand4 = defineCommand228({
67932
69074
  });
67933
69075
 
67934
69076
  // src/commands/videos/describe.ts
67935
- import { defineCommand as defineCommand229 } from "citty";
69077
+ import { defineCommand as defineCommand230 } from "citty";
67936
69078
  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.";
67937
69079
  registerSchema({
67938
69080
  command: "videos.describe",
@@ -67949,7 +69091,7 @@ registerSchema({
67949
69091
  full: { type: "boolean", description: "Return the whole library row, not just what changed", required: false }
67950
69092
  }
67951
69093
  });
67952
- var describeCommand2 = defineCommand229({
69094
+ var describeCommand2 = defineCommand230({
67953
69095
  meta: {
67954
69096
  name: "describe",
67955
69097
  description: `Correct what the library says a video is. Only the fields you pass change; the rest keep their current values.
@@ -68007,7 +69149,7 @@ Example: baker videos describe j571abc123 --description "Customer explaining how
68007
69149
  });
68008
69150
 
68009
69151
  // src/commands/videos/get.ts
68010
- import { defineCommand as defineCommand230 } from "citty";
69152
+ import { defineCommand as defineCommand231 } from "citty";
68011
69153
  registerSchema({
68012
69154
  command: "videos.get",
68013
69155
  description: "Get a single video by ID",
@@ -68015,7 +69157,7 @@ registerSchema({
68015
69157
  id: { type: "string", description: "Video ID", required: true }
68016
69158
  }
68017
69159
  });
68018
- var getCommand7 = defineCommand230({
69160
+ var getCommand7 = defineCommand231({
68019
69161
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
68020
69162
  args: {
68021
69163
  id: { type: "positional", description: "Video ID", required: false },
@@ -68052,7 +69194,7 @@ var getCommand7 = defineCommand230({
68052
69194
  });
68053
69195
 
68054
69196
  // src/commands/videos/group.ts
68055
- import { defineCommand as defineCommand231 } from "citty";
69197
+ import { defineCommand as defineCommand232 } from "citty";
68056
69198
  registerSchema({
68057
69199
  command: "videos.group",
68058
69200
  description: "List every clip and image that arrived in the same set as this video (carousel slides, one page)",
@@ -68061,7 +69203,7 @@ registerSchema({
68061
69203
  "group-key": { type: "string", description: "The set key directly, when you already have it", required: false }
68062
69204
  }
68063
69205
  });
68064
- var groupCommand2 = defineCommand231({
69206
+ var groupCommand2 = defineCommand232({
68065
69207
  meta: {
68066
69208
  name: "group",
68067
69209
  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>"
@@ -68084,7 +69226,7 @@ var groupCommand2 = defineCommand231({
68084
69226
  import { mkdtemp as mkdtemp3, rm as rm9, stat as stat11 } from "fs/promises";
68085
69227
  import { tmpdir as tmpdir4 } from "os";
68086
69228
  import path47 from "path";
68087
- import { defineCommand as defineCommand232 } from "citty";
69229
+ import { defineCommand as defineCommand233 } from "citty";
68088
69230
 
68089
69231
  // src/lib/streamUpload.ts
68090
69232
  import { createHash as createHash2 } from "crypto";
@@ -68248,7 +69390,7 @@ registerSchema({
68248
69390
  "dry-run": { type: "boolean", description: "Preview the operation without executing", required: false }
68249
69391
  }
68250
69392
  });
68251
- var ingestCommand2 = defineCommand232({
69393
+ var ingestCommand2 = defineCommand233({
68252
69394
  meta: {
68253
69395
  name: "ingest",
68254
69396
  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"
@@ -68510,7 +69652,7 @@ async function uploadToAssetStore(filePath, sizeBytes) {
68510
69652
  }
68511
69653
 
68512
69654
  // src/commands/videos/search.ts
68513
- import { defineCommand as defineCommand233 } from "citty";
69655
+ import { defineCommand as defineCommand234 } from "citty";
68514
69656
  registerSchema({
68515
69657
  command: "videos.search",
68516
69658
  description: "Search videos by text query. Only returns ready videos.",
@@ -68520,7 +69662,7 @@ registerSchema({
68520
69662
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
68521
69663
  }
68522
69664
  });
68523
- var searchCommand4 = defineCommand233({
69665
+ var searchCommand4 = defineCommand234({
68524
69666
  meta: {
68525
69667
  name: "search",
68526
69668
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -68572,7 +69714,7 @@ var tagsCommand6 = makeTagsCommand("videos", "video", "/api/videos/tags");
68572
69714
  // src/commands/videos/upload.ts
68573
69715
  import { readFile as readFile33, stat as stat12 } from "fs/promises";
68574
69716
  import { basename as basename3, extname as extname4 } from "path";
68575
- import { defineCommand as defineCommand234 } from "citty";
69717
+ import { defineCommand as defineCommand235 } from "citty";
68576
69718
  var MIME_MAP = {
68577
69719
  ".mp4": "video/mp4",
68578
69720
  ".mov": "video/quicktime",
@@ -68614,7 +69756,7 @@ function detectContentType(filePath) {
68614
69756
  function isRemoteUrl3(value) {
68615
69757
  return /^https?:\/\//i.test(value);
68616
69758
  }
68617
- var uploadCommand2 = defineCommand234({
69759
+ var uploadCommand2 = defineCommand235({
68618
69760
  meta: {
68619
69761
  name: "upload",
68620
69762
  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"
@@ -68698,7 +69840,7 @@ var uploadCommand2 = defineCommand234({
68698
69840
  });
68699
69841
 
68700
69842
  // src/commands/videos/index.ts
68701
- var videosCommand = defineCommand235({
69843
+ var videosCommand = defineCommand236({
68702
69844
  meta: {
68703
69845
  name: "videos",
68704
69846
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, ingest, describe, delete, tags.
@@ -68727,10 +69869,10 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
68727
69869
  });
68728
69870
 
68729
69871
  // src/commands/winning-ads/index.ts
68730
- import { defineCommand as defineCommand248 } from "citty";
69872
+ import { defineCommand as defineCommand249 } from "citty";
68731
69873
 
68732
69874
  // src/commands/winning-ads/advertisers.ts
68733
- import { defineCommand as defineCommand236 } from "citty";
69875
+ import { defineCommand as defineCommand237 } from "citty";
68734
69876
 
68735
69877
  // src/commands/winning-ads/shared.ts
68736
69878
  function splitList3(value) {
@@ -68783,7 +69925,7 @@ function advertiserNormalizer(record, full) {
68783
69925
  last_synced_at: record.last_synced_at ?? null
68784
69926
  };
68785
69927
  }
68786
- var advertisersCommand2 = defineCommand236({
69928
+ var advertisersCommand2 = defineCommand237({
68787
69929
  meta: {
68788
69930
  name: "advertisers",
68789
69931
  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'
@@ -68841,7 +69983,7 @@ var advertisersCommand2 = defineCommand236({
68841
69983
  });
68842
69984
 
68843
69985
  // src/commands/winning-ads/brief.ts
68844
- import { defineCommand as defineCommand237 } from "citty";
69986
+ import { defineCommand as defineCommand238 } from "citty";
68845
69987
  registerSchema({
68846
69988
  command: "winning-ads.brief",
68847
69989
  description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
@@ -68887,7 +70029,7 @@ function parseDna(raw) {
68887
70029
  }
68888
70030
  return parsed;
68889
70031
  }
68890
- var briefCommand = defineCommand237({
70032
+ var briefCommand = defineCommand238({
68891
70033
  meta: {
68892
70034
  name: "brief",
68893
70035
  description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
@@ -68923,7 +70065,7 @@ var briefCommand = defineCommand237({
68923
70065
  });
68924
70066
 
68925
70067
  // src/commands/winning-ads/content.ts
68926
- import { defineCommand as defineCommand238 } from "citty";
70068
+ import { defineCommand as defineCommand239 } from "citty";
68927
70069
  registerSchema({
68928
70070
  command: "winning-ads.content",
68929
70071
  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.",
@@ -68936,7 +70078,7 @@ registerSchema({
68936
70078
  }
68937
70079
  }
68938
70080
  });
68939
- var contentCommand = defineCommand238({
70081
+ var contentCommand = defineCommand239({
68940
70082
  meta: {
68941
70083
  name: "content",
68942
70084
  description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
@@ -68985,7 +70127,7 @@ var contentCommand = defineCommand238({
68985
70127
  });
68986
70128
 
68987
70129
  // src/commands/winning-ads/feed.ts
68988
- import { defineCommand as defineCommand239 } from "citty";
70130
+ import { defineCommand as defineCommand240 } from "citty";
68989
70131
  function buildFeedParams(input) {
68990
70132
  const params2 = {};
68991
70133
  const advertiser = splitList3(input.advertiser);
@@ -69037,7 +70179,7 @@ registerSchema({
69037
70179
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
69038
70180
  }
69039
70181
  });
69040
- var feedCommand2 = defineCommand239({
70182
+ var feedCommand2 = defineCommand240({
69041
70183
  meta: {
69042
70184
  name: "feed",
69043
70185
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -69122,7 +70264,7 @@ var feedCommand2 = defineCommand239({
69122
70264
  });
69123
70265
 
69124
70266
  // src/commands/winning-ads/follow.ts
69125
- import { defineCommand as defineCommand240 } from "citty";
70267
+ import { defineCommand as defineCommand241 } from "citty";
69126
70268
  var PLATFORMS = adLibraryPlatformSchema.options;
69127
70269
  registerSchema({
69128
70270
  command: "winning-ads.follow",
@@ -69137,7 +70279,7 @@ registerSchema({
69137
70279
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
69138
70280
  }
69139
70281
  });
69140
- var followCommand = defineCommand240({
70282
+ var followCommand = defineCommand241({
69141
70283
  meta: {
69142
70284
  name: "follow",
69143
70285
  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'
@@ -69184,7 +70326,7 @@ var followCommand = defineCommand240({
69184
70326
  });
69185
70327
 
69186
70328
  // src/commands/winning-ads/follow-competitors.ts
69187
- import { defineCommand as defineCommand241 } from "citty";
70329
+ import { defineCommand as defineCommand242 } from "citty";
69188
70330
  var PLATFORMS2 = adLibraryPlatformSchema.options;
69189
70331
  var BATCH_TIMEOUT_MS = 3e5;
69190
70332
  function buildFollowBatchBody(input) {
@@ -69217,7 +70359,7 @@ registerSchema({
69217
70359
  }
69218
70360
  }
69219
70361
  });
69220
- var followCompetitorsCommand = defineCommand241({
70362
+ var followCompetitorsCommand = defineCommand242({
69221
70363
  meta: {
69222
70364
  name: "follow-competitors",
69223
70365
  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"'
@@ -69292,7 +70434,7 @@ var followCompetitorsCommand = defineCommand241({
69292
70434
  });
69293
70435
 
69294
70436
  // src/commands/winning-ads/following.ts
69295
- import { defineCommand as defineCommand242 } from "citty";
70437
+ import { defineCommand as defineCommand243 } from "citty";
69296
70438
  registerSchema({
69297
70439
  command: "winning-ads.following",
69298
70440
  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.",
@@ -69346,7 +70488,7 @@ function followingNormalizer(record, full) {
69346
70488
  platforms: Array.isArray(record.platforms) ? record.platforms : []
69347
70489
  };
69348
70490
  }
69349
- var followingCommand = defineCommand242({
70491
+ var followingCommand = defineCommand243({
69350
70492
  meta: {
69351
70493
  name: "following",
69352
70494
  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"
@@ -69382,7 +70524,7 @@ var followingCommand = defineCommand242({
69382
70524
  });
69383
70525
 
69384
70526
  // src/commands/winning-ads/patterns.ts
69385
- import { defineCommand as defineCommand243 } from "citty";
70527
+ import { defineCommand as defineCommand244 } from "citty";
69386
70528
  registerSchema({
69387
70529
  command: "winning-ads.patterns",
69388
70530
  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.",
@@ -69421,7 +70563,7 @@ function discriminatorRow(record) {
69421
70563
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
69422
70564
  };
69423
70565
  }
69424
- var patternsCommand = defineCommand243({
70566
+ var patternsCommand = defineCommand244({
69425
70567
  meta: {
69426
70568
  name: "patterns",
69427
70569
  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"
@@ -69477,7 +70619,7 @@ var patternsCommand = defineCommand243({
69477
70619
  });
69478
70620
 
69479
70621
  // src/commands/winning-ads/search.ts
69480
- import { defineCommand as defineCommand244 } from "citty";
70622
+ import { defineCommand as defineCommand245 } from "citty";
69481
70623
  registerSchema({
69482
70624
  command: "winning-ads.search",
69483
70625
  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.",
@@ -69615,7 +70757,7 @@ function reportSearchError(err) {
69615
70757
  }
69616
70758
  reportError2(err);
69617
70759
  }
69618
- var searchCommand5 = defineCommand244({
70760
+ var searchCommand5 = defineCommand245({
69619
70761
  meta: {
69620
70762
  name: "search",
69621
70763
  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"
@@ -69735,7 +70877,7 @@ var searchCommand5 = defineCommand244({
69735
70877
  });
69736
70878
 
69737
70879
  // src/commands/winning-ads/seeds.ts
69738
- import { defineCommand as defineCommand245 } from "citty";
70880
+ import { defineCommand as defineCommand246 } from "citty";
69739
70881
  function leanRow(r) {
69740
70882
  return {
69741
70883
  key: r.key,
@@ -69763,7 +70905,7 @@ function makeSeedCommand(opts) {
69763
70905
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
69764
70906
  }
69765
70907
  });
69766
- return defineCommand245({
70908
+ return defineCommand246({
69767
70909
  meta: { name: opts.name, description: opts.description },
69768
70910
  args: {
69769
70911
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -69812,7 +70954,7 @@ var formatsCommand = makeSeedCommand({
69812
70954
  });
69813
70955
 
69814
70956
  // src/commands/winning-ads/unfollow.ts
69815
- import { defineCommand as defineCommand246 } from "citty";
70957
+ import { defineCommand as defineCommand247 } from "citty";
69816
70958
  registerSchema({
69817
70959
  command: "winning-ads.unfollow",
69818
70960
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -69820,7 +70962,7 @@ registerSchema({
69820
70962
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
69821
70963
  }
69822
70964
  });
69823
- var unfollowCommand = defineCommand246({
70965
+ var unfollowCommand = defineCommand247({
69824
70966
  meta: {
69825
70967
  name: "unfollow",
69826
70968
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -69841,7 +70983,7 @@ var unfollowCommand = defineCommand246({
69841
70983
  });
69842
70984
 
69843
70985
  // src/commands/winning-ads/winners.ts
69844
- import { defineCommand as defineCommand247 } from "citty";
70986
+ import { defineCommand as defineCommand248 } from "citty";
69845
70987
  registerSchema({
69846
70988
  command: "winning-ads.winners",
69847
70989
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -69851,7 +70993,7 @@ registerSchema({
69851
70993
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin|tiktok", required: false }
69852
70994
  }
69853
70995
  });
69854
- var winnersCommand = defineCommand247({
70996
+ var winnersCommand = defineCommand248({
69855
70997
  meta: {
69856
70998
  name: "winners",
69857
70999
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -69901,7 +71043,7 @@ var winnersCommand = defineCommand247({
69901
71043
  });
69902
71044
 
69903
71045
  // src/commands/winning-ads/index.ts
69904
- var winningAdsCommand = defineCommand248({
71046
+ var winningAdsCommand = defineCommand249({
69905
71047
  meta: {
69906
71048
  name: "winning-ads",
69907
71049
  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.
@@ -70093,16 +71235,16 @@ function commandFor(root, argv) {
70093
71235
  return resolved ? command : null;
70094
71236
  }
70095
71237
  function suggestFlag(unknown, command) {
70096
- const flag2 = closestFlag(unknown, command);
70097
- if (!flag2) {
71238
+ const flag3 = closestFlag(unknown, command);
71239
+ if (!flag3) {
70098
71240
  return null;
70099
71241
  }
70100
71242
  const args5 = argsOf(command) ?? {};
70101
- const name = flag2.slice(2);
71243
+ const name = flag3.slice(2);
70102
71244
  const def = args5[name] ?? args5[camel(name)];
70103
71245
  const described = def && typeof def === "object" ? def.description : void 0;
70104
71246
  const description = typeof described === "string" ? described.split(/(?<!\b(?:e\.g|i\.e|vs))\.\s/)[0]?.trim().replace(/\.$/, "") : "";
70105
- return description ? { flag: flag2, description } : { flag: flag2 };
71247
+ return description ? { flag: flag3, description } : { flag: flag3 };
70106
71248
  }
70107
71249
  function suggestionText(suggestion) {
70108
71250
  if (!suggestion) {
@@ -70120,7 +71262,7 @@ function unknownFlagEnvelope(unknown, commandPath, suggestion, hasSchema) {
70120
71262
  message: `${list} ${unknown.length > 1 ? "are not options" : "is not an option"} of \`baker ${commandPath}\`.`,
70121
71263
  fix: {
70122
71264
  action: hasSchema ? "check_schema" : "check_help",
70123
- explanation: (unknown.some((flag2) => flag2 === "--output" || flag2.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.`
71265
+ 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.`
70124
71266
  },
70125
71267
  retryable: false
70126
71268
  }
@@ -70178,13 +71320,13 @@ function declaredArg(command, name) {
70178
71320
  }
70179
71321
  function envelopeRequests(argv) {
70180
71322
  const end = argv.indexOf("--");
70181
- const limit = end === -1 ? argv.length : end;
71323
+ const limit2 = end === -1 ? argv.length : end;
70182
71324
  const found = [];
70183
- for (let index = 0; index < limit; index++) {
71325
+ for (let index = 0; index < limit2; index++) {
70184
71326
  const token = argv[index];
70185
71327
  if (token === `${FLAG}=${ENVELOPE}`) {
70186
71328
  found.push({ index, length: 1 });
70187
- } else if (token === FLAG && argv[index + 1] === ENVELOPE && index + 1 < limit) {
71329
+ } else if (token === FLAG && argv[index + 1] === ENVELOPE && index + 1 < limit2) {
70188
71330
  found.push({ index, length: 2 });
70189
71331
  index++;
70190
71332
  }
@@ -70228,7 +71370,7 @@ function getCliVersion() {
70228
71370
  }
70229
71371
 
70230
71372
  // src/cli.ts
70231
- var main = defineCommand249({
71373
+ var main = defineCommand250({
70232
71374
  meta: {
70233
71375
  name: "baker",
70234
71376
  version: getCliVersion(),