@koda-sl/baker-cli 0.185.2 → 0.186.0-dev.6d88981c0

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
@@ -46,7 +46,7 @@ import {
46
46
  toModelSafeImage,
47
47
  ulid,
48
48
  validateCanvasDeep
49
- } from "./chunk-3TXZKSAR.js";
49
+ } from "./chunk-PJ5KH27L.js";
50
50
  import {
51
51
  csvOrJson,
52
52
  daysAgoIso,
@@ -89,7 +89,7 @@ import {
89
89
  } from "./chunk-YL3HDEIJ.js";
90
90
 
91
91
  // src/cli.ts
92
- import { defineCommand as defineCommand199, runMain } from "citty";
92
+ import { defineCommand as defineCommand207, runMain } from "citty";
93
93
 
94
94
  // src/commands/actions/index.ts
95
95
  import { defineCommand as defineCommand18 } from "citty";
@@ -7531,19 +7531,19 @@ Examples:
7531
7531
  },
7532
7532
  run: async ({ args }) => {
7533
7533
  const customerId = await resolveCustomerId(args);
7534
- const window = resolveChangesWindow({
7534
+ const window2 = resolveChangesWindow({
7535
7535
  days: args.days ? Number(args.days) : void 0,
7536
7536
  scope: args.scope
7537
7537
  });
7538
- if (!window.ok) {
7539
- writeJsonEnvelope({ ok: false, error: { ...window.error, retryable: false } });
7538
+ if (!window2.ok) {
7539
+ writeJsonEnvelope({ ok: false, error: { ...window2.error, retryable: false } });
7540
7540
  process.exit(1);
7541
7541
  return;
7542
7542
  }
7543
7543
  const body = {
7544
7544
  customerId,
7545
- days: window.days,
7546
- scope: window.scope,
7545
+ days: window2.days,
7546
+ scope: window2.scope,
7547
7547
  limit: args.limit ? Number(args.limit) : 50
7548
7548
  };
7549
7549
  const managerId = getManagerIdForCustomer(customerId);
@@ -7560,7 +7560,7 @@ Examples:
7560
7560
  const first = data[0];
7561
7561
  const fields = first ? Object.keys(first) : [];
7562
7562
  const fieldDescs = getFieldDescriptions(fields);
7563
- writeJsonEnvelope({ ok: true, data, fields: fieldDescs, hints: window.hints });
7563
+ writeJsonEnvelope({ ok: true, data, fields: fieldDescs, hints: window2.hints });
7564
7564
  } catch (err) {
7565
7565
  if (err instanceof ApiError) {
7566
7566
  writeAdsJson(parseApiError(err.message, "", customerId, err.code));
@@ -7917,8 +7917,8 @@ function countStaccato(text) {
7917
7917
  let first = "";
7918
7918
  let runStart = 0;
7919
7919
  for (const [i, s] of sentences.entries()) {
7920
- const words2 = s.split(/\s+/).filter(Boolean).length;
7921
- if (words2 > 0 && words2 <= STACCATO_MAX_WORDS) {
7920
+ const words3 = s.split(/\s+/).filter(Boolean).length;
7921
+ if (words3 > 0 && words3 <= STACCATO_MAX_WORDS) {
7922
7922
  if (run === 0) runStart = i;
7923
7923
  run++;
7924
7924
  if (run === STACCATO_RUN) {
@@ -7936,10 +7936,10 @@ function countTitleCaseHeadings(text) {
7936
7936
  let first = "";
7937
7937
  for (const m of text.matchAll(/^\s{0,3}#{1,6}\s+(.+)$/gm)) {
7938
7938
  const heading = (m[1] ?? "").trim();
7939
- const words2 = heading.split(/\s+/).filter((w) => new RegExp("\\p{L}", "u").test(w));
7940
- if (words2.length < 4) continue;
7941
- const capitalized = words2.filter((w) => new RegExp("^\\p{Lu}", "u").test(w)).length;
7942
- if (capitalized / words2.length < 0.8) continue;
7939
+ const words3 = heading.split(/\s+/).filter((w) => new RegExp("\\p{L}", "u").test(w));
7940
+ if (words3.length < 4) continue;
7941
+ const capitalized = words3.filter((w) => new RegExp("^\\p{Lu}", "u").test(w)).length;
7942
+ if (capitalized / words3.length < 0.8) continue;
7943
7943
  count++;
7944
7944
  if (!first) first = heading;
7945
7945
  }
@@ -8268,11 +8268,11 @@ function rawTextEntries(value) {
8268
8268
  const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
8269
8269
  return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
8270
8270
  }
8271
- function rawFileEntries(path28) {
8272
- if (typeof path28 !== "string" || path28.length === 0) {
8271
+ function rawFileEntries(path35) {
8272
+ if (typeof path35 !== "string" || path35.length === 0) {
8273
8273
  return [];
8274
8274
  }
8275
- return readFileSync2(path28, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8275
+ return readFileSync2(path35, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8276
8276
  }
8277
8277
  function keywordEntries(args) {
8278
8278
  const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
@@ -8295,19 +8295,19 @@ function keywordEntries(args) {
8295
8295
  }
8296
8296
  return entries;
8297
8297
  }
8298
- function loadJsonFileArg(path28) {
8299
- if (typeof path28 !== "string" || path28.length === 0) {
8298
+ function loadJsonFileArg(path35) {
8299
+ if (typeof path35 !== "string" || path35.length === 0) {
8300
8300
  return {};
8301
8301
  }
8302
8302
  try {
8303
- const parsed = JSON.parse(readFileSync2(path28, "utf8"));
8303
+ const parsed = JSON.parse(readFileSync2(path35, "utf8"));
8304
8304
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
8305
- failWriteValidation(`${path28} must contain a JSON object`);
8305
+ failWriteValidation(`${path35} must contain a JSON object`);
8306
8306
  }
8307
8307
  return parsed;
8308
8308
  } catch (err) {
8309
8309
  if (err instanceof SyntaxError) {
8310
- failWriteValidation(`${path28} is not valid JSON: ${err.message}`);
8310
+ failWriteValidation(`${path35} is not valid JSON: ${err.message}`);
8311
8311
  }
8312
8312
  throw err;
8313
8313
  }
@@ -8437,10 +8437,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
8437
8437
  async function stageTarget(kind, customerId, target, hints) {
8438
8438
  await stageGoogleOp({ kind, customerId, target }, hints);
8439
8439
  }
8440
- async function draftAction(path28, body, chat) {
8440
+ async function draftAction(path35, body, chat) {
8441
8441
  try {
8442
8442
  const chatId = resolveChatId(chat);
8443
- const response = await apiPost(path28, { chatId, ...body });
8443
+ const response = await apiPost(path35, { chatId, ...body });
8444
8444
  writeJsonEnvelope(response);
8445
8445
  } catch (err) {
8446
8446
  handleGoogleError(err);
@@ -11108,19 +11108,19 @@ function failWriteValidation2(message) {
11108
11108
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
11109
11109
  process.exit(1);
11110
11110
  }
11111
- function loadJsonFileArg2(path28) {
11112
- if (typeof path28 !== "string" || path28.length === 0) {
11111
+ function loadJsonFileArg2(path35) {
11112
+ if (typeof path35 !== "string" || path35.length === 0) {
11113
11113
  return {};
11114
11114
  }
11115
11115
  try {
11116
- const parsed = JSON.parse(readFileSync4(path28, "utf8"));
11116
+ const parsed = JSON.parse(readFileSync4(path35, "utf8"));
11117
11117
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
11118
- failWriteValidation2(`${path28} must contain a JSON object`);
11118
+ failWriteValidation2(`${path35} must contain a JSON object`);
11119
11119
  }
11120
11120
  return parsed;
11121
11121
  } catch (err) {
11122
11122
  if (err instanceof SyntaxError) {
11123
- failWriteValidation2(`${path28} is not valid JSON: ${err.message}`);
11123
+ failWriteValidation2(`${path35} is not valid JSON: ${err.message}`);
11124
11124
  }
11125
11125
  throw err;
11126
11126
  }
@@ -11205,15 +11205,15 @@ function parseLocaleFlag(value) {
11205
11205
  }
11206
11206
  return { language: match[1], country: match[2].toUpperCase() };
11207
11207
  }
11208
- function loadTargetingFileArg(path28) {
11209
- if (typeof path28 !== "string" || path28.length === 0) {
11208
+ function loadTargetingFileArg(path35) {
11209
+ if (typeof path35 !== "string" || path35.length === 0) {
11210
11210
  return void 0;
11211
11211
  }
11212
- const parsed = loadJsonFileArg2(path28);
11212
+ const parsed = loadJsonFileArg2(path35);
11213
11213
  const criteria = parsed.targetingCriteria ?? parsed;
11214
11214
  if (!criteria.include) {
11215
11215
  failWriteValidation2(
11216
- `${path28} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
11216
+ `${path35} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
11217
11217
  );
11218
11218
  }
11219
11219
  return criteria;
@@ -11248,14 +11248,14 @@ function parseCsvLine(line) {
11248
11248
  cells.push(current);
11249
11249
  return cells.map((cell) => cell.trim());
11250
11250
  }
11251
- function parseListFileArg(path28, maxRows) {
11252
- if (typeof path28 !== "string" || path28.length === 0) {
11251
+ function parseListFileArg(path35, maxRows) {
11252
+ if (typeof path35 !== "string" || path35.length === 0) {
11253
11253
  return void 0;
11254
11254
  }
11255
- const raw = readFileSync4(path28, "utf8");
11255
+ const raw = readFileSync4(path35, "utf8");
11256
11256
  const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
11257
11257
  if (lines.length < 2) {
11258
- failWriteValidation2(`${path28} needs a header row and at least one data row`);
11258
+ failWriteValidation2(`${path35} needs a header row and at least one data row`);
11259
11259
  }
11260
11260
  const columns = parseCsvLine(lines[0]).map((column) => column.trim());
11261
11261
  const rows = [];
@@ -11274,7 +11274,7 @@ function parseListFileArg(path28, maxRows) {
11274
11274
  }
11275
11275
  }
11276
11276
  if (rows.length > maxRows) {
11277
- failWriteValidation2(`${path28} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
11277
+ failWriteValidation2(`${path35} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
11278
11278
  }
11279
11279
  return { columns, rows };
11280
11280
  }
@@ -11370,11 +11370,11 @@ function readPositionals(args) {
11370
11370
  function splitIdList(raw) {
11371
11371
  return raw.split(",").map((id) => id.trim()).filter(Boolean);
11372
11372
  }
11373
- function idsFileEntries(path28) {
11374
- if (typeof path28 !== "string" || path28.length === 0) {
11373
+ function idsFileEntries(path35) {
11374
+ if (typeof path35 !== "string" || path35.length === 0) {
11375
11375
  return [];
11376
11376
  }
11377
- return readFileSync4(path28, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
11377
+ return readFileSync4(path35, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
11378
11378
  }
11379
11379
  function requireTargets(args, entity) {
11380
11380
  const positionals = readPositionals(args);
@@ -13997,9 +13997,9 @@ function compactRow(row) {
13997
13997
  ...destination.postUrn ? { postUrn: destination.postUrn } : {}
13998
13998
  };
13999
13999
  }
14000
- function readPath(row, path28) {
14000
+ function readPath(row, path35) {
14001
14001
  let current = row;
14002
- for (const segment of path28.split(".")) {
14002
+ for (const segment of path35.split(".")) {
14003
14003
  const record = asRecord2(current);
14004
14004
  if (!record) return void 0;
14005
14005
  current = record[segment];
@@ -14009,10 +14009,10 @@ function readPath(row, path28) {
14009
14009
  function projectFields(rows, paths) {
14010
14010
  return rows.map((row) => {
14011
14011
  const projected = {};
14012
- for (const path28 of paths) {
14013
- const value = readPath(row, path28);
14012
+ for (const path35 of paths) {
14013
+ const value = readPath(row, path35);
14014
14014
  if (value !== void 0) {
14015
- projected[path28] = value;
14015
+ projected[path35] = value;
14016
14016
  }
14017
14017
  }
14018
14018
  return projected;
@@ -15219,11 +15219,11 @@ var updateStatusSchema = z19.enum(UPDATE_STATUSES);
15219
15219
  function currencyMinimums2(currencyCode) {
15220
15220
  return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
15221
15221
  }
15222
- function validateDailyBudgetFloor(money, ctx, path28) {
15222
+ function validateDailyBudgetFloor(money, ctx, path35) {
15223
15223
  if (money?.currencyCode) {
15224
15224
  const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
15225
15225
  if (Number(money.amount) < min) {
15226
- ctx.addIssue({ code: "custom", path: path28, message: `below the ${min} ${money.currencyCode} daily minimum` });
15226
+ ctx.addIssue({ code: "custom", path: path35, message: `below the ${min} ${money.currencyCode} daily minimum` });
15227
15227
  }
15228
15228
  }
15229
15229
  }
@@ -15866,19 +15866,19 @@ function failWriteValidation3(message) {
15866
15866
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
15867
15867
  process.exit(1);
15868
15868
  }
15869
- function loadJsonFileArg3(path28) {
15870
- if (typeof path28 !== "string" || path28.length === 0) {
15869
+ function loadJsonFileArg3(path35) {
15870
+ if (typeof path35 !== "string" || path35.length === 0) {
15871
15871
  return {};
15872
15872
  }
15873
15873
  try {
15874
- const parsed = JSON.parse(readFileSync8(path28, "utf8"));
15874
+ const parsed = JSON.parse(readFileSync8(path35, "utf8"));
15875
15875
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
15876
- failWriteValidation3(`${path28} must contain a JSON object`);
15876
+ failWriteValidation3(`${path35} must contain a JSON object`);
15877
15877
  }
15878
15878
  return parsed;
15879
15879
  } catch (err) {
15880
15880
  if (err instanceof SyntaxError) {
15881
- failWriteValidation3(`${path28} is not valid JSON: ${err.message}`);
15881
+ failWriteValidation3(`${path35} is not valid JSON: ${err.message}`);
15882
15882
  }
15883
15883
  throw err;
15884
15884
  }
@@ -19940,11 +19940,11 @@ function estSpeechS(text) {
19940
19940
  var OBSERVED_WPS_MIN = 1;
19941
19941
  var OBSERVED_WPS_MAX = 6;
19942
19942
  function estSpeechWindowS(text, startS, endS) {
19943
- const words2 = wordCount(text);
19944
- const window = (endS ?? 0) - (startS ?? 0);
19945
- if (words2 > 0 && window > 0.3) {
19946
- const wps = words2 / window;
19947
- if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return window;
19943
+ const words3 = wordCount(text);
19944
+ const window2 = (endS ?? 0) - (startS ?? 0);
19945
+ if (words3 > 0 && window2 > 0.3) {
19946
+ const wps = words3 / window2;
19947
+ if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return window2;
19948
19948
  }
19949
19949
  return estSpeechS(text);
19950
19950
  }
@@ -20799,10 +20799,10 @@ function scrubFloatSentences(text, floatDescs) {
20799
20799
  if (floatDescs.length === 0 || !text) return text;
20800
20800
  const tokenSets = floatDescs.map((d) => new Set(floatTokens(d)));
20801
20801
  const kept = text.split(/(?<=[.!?])\s+/).filter((sentence) => {
20802
- const words2 = new Set(floatTokens(sentence));
20802
+ const words3 = new Set(floatTokens(sentence));
20803
20803
  return !tokenSets.some((ts) => {
20804
20804
  let hits = 0;
20805
- for (const w of words2) if (ts.has(w)) hits++;
20805
+ for (const w of words3) if (ts.has(w)) hits++;
20806
20806
  return hits >= 2;
20807
20807
  });
20808
20808
  }).join(" ").trim();
@@ -22993,15 +22993,15 @@ function collectClipAdvisories(scene, i, out) {
22993
22993
  const round22 = (n) => Math.round(n * 100) / 100;
22994
22994
  const original = scene.duration_s ?? 5;
22995
22995
  if (original > 15) out.clamped.push({ scene: i, original_s: original, clip_s: snapToSeedance(original) });
22996
- const window = sceneDurationS(scene);
22997
- if (window > SEEDANCE_SAFE_MAX_S)
22998
- out.oversize.push({ scene: i, scene_s: round22(window), clip_s: ceilToSeedance(window) });
22996
+ const window2 = sceneDurationS(scene);
22997
+ if (window2 > SEEDANCE_SAFE_MAX_S)
22998
+ out.oversize.push({ scene: i, scene_s: round22(window2), clip_s: ceilToSeedance(window2) });
22999
22999
  const speech = (scene.dialogue ?? []).reduce(
23000
23000
  (s, line) => s + (line.line ? estSpeechWindowS(line.line, line.start_s, line.end_s) : 0),
23001
23001
  0
23002
23002
  );
23003
- if (speech > window * OVERSTUFF_RATIO)
23004
- out.overstuffed.push({ scene: i, scene_s: round22(window), est_speech_s: round22(speech) });
23003
+ if (speech > window2 * OVERSTUFF_RATIO)
23004
+ out.overstuffed.push({ scene: i, scene_s: round22(window2), est_speech_s: round22(speech) });
23005
23005
  }
23006
23006
  function videoReport(input, elementsInput) {
23007
23007
  const blueprint = VideoBlueprint.parse(input);
@@ -26674,12 +26674,12 @@ function listFlowSlugs() {
26674
26674
  return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
26675
26675
  }
26676
26676
  function readFlowTree(slug) {
26677
- const path28 = join3(flowsDir(), slug, "_data.json");
26678
- if (!existsSync4(path28)) {
26677
+ const path35 = join3(flowsDir(), slug, "_data.json");
26678
+ if (!existsSync4(path35)) {
26679
26679
  failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
26680
26680
  }
26681
26681
  try {
26682
- return JSON.parse(readFileSync9(path28, "utf-8"));
26682
+ return JSON.parse(readFileSync9(path35, "utf-8"));
26683
26683
  } catch (error) {
26684
26684
  failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
26685
26685
  }
@@ -27092,10 +27092,10 @@ async function stageOps(ops) {
27092
27092
  handleError(err);
27093
27093
  }
27094
27094
  }
27095
- async function draftAction2(path28, body, chat) {
27095
+ async function draftAction2(path35, body, chat) {
27096
27096
  const chatId = resolveChatId(chat);
27097
27097
  try {
27098
- const data = await apiPost(path28, { chatId, ...body });
27098
+ const data = await apiPost(path35, { chatId, ...body });
27099
27099
  writeJsonEnvelope({ ok: true, data });
27100
27100
  return data;
27101
27101
  } catch (err) {
@@ -29119,9 +29119,9 @@ async function readImageBuffer(pathOrUrl) {
29119
29119
  }
29120
29120
  return readFile19(pathOrUrl);
29121
29121
  }
29122
- async function isDirectory(path28) {
29122
+ async function isDirectory(path35) {
29123
29123
  try {
29124
- const s = await stat4(path28);
29124
+ const s = await stat4(path35);
29125
29125
  return s.isDirectory();
29126
29126
  } catch {
29127
29127
  return false;
@@ -31584,11 +31584,11 @@ Full guide: __tooling__/docs/tools/baker/images.md`
31584
31584
  });
31585
31585
 
31586
31586
  // src/commands/landing/index.ts
31587
- import { defineCommand as defineCommand142 } from "citty";
31587
+ import { defineCommand as defineCommand150 } from "citty";
31588
31588
 
31589
31589
  // src/commands/landing/critique.ts
31590
31590
  import { readdir as readdir8, stat as stat6 } from "fs/promises";
31591
- import path27 from "path";
31591
+ import path28 from "path";
31592
31592
  import { defineCommand as defineCommand141 } from "citty";
31593
31593
 
31594
31594
  // src/engine/landing/lib/brand-tokens.ts
@@ -31997,6 +31997,11 @@ var RULE_META = {
31997
31997
  severity: "block",
31998
31998
  note: "Gradient text is a top AI tell. Emphasis comes from weight or size, not a clipped gradient fill."
31999
31999
  },
32000
+ "copied-reference-copy": {
32001
+ family: "originality",
32002
+ severity: "block",
32003
+ note: "This line is lifted from a section you consulted in the inspiration library. Reference sections are for structure and mechanism, never words \u2014 a visitor who has seen the original reads this as a clone, and the claim is not yours to make. Rewrite it from the client's own offer."
32004
+ },
32000
32005
  "broken-image": {
32001
32006
  family: "integrity",
32002
32007
  severity: "block",
@@ -32177,6 +32182,64 @@ var SEVERITY_WEIGHT = {
32177
32182
  advisory: 0.05
32178
32183
  };
32179
32184
 
32185
+ // src/engine/landing/lib/originality.ts
32186
+ var MIN_COMPARABLE_LENGTH = 12;
32187
+ var NEAR_MATCH_RATIO = 0.8;
32188
+ function normalize(value) {
32189
+ return value.toLowerCase().replace(/[‘’“”]/g, "'").replace(/[^a-z0-9']+/g, " ").trim();
32190
+ }
32191
+ function words2(value) {
32192
+ return normalize(value).split(" ").filter(Boolean);
32193
+ }
32194
+ function copyOverlapRatio(candidate, reference) {
32195
+ const referenceWords = words2(reference);
32196
+ if (referenceWords.length === 0) return 0;
32197
+ const candidateWords = new Set(words2(candidate));
32198
+ const shared = referenceWords.filter((word) => candidateWords.has(word)).length;
32199
+ return shared / referenceWords.length;
32200
+ }
32201
+ function isVerbatimReuse(candidate, reference) {
32202
+ const normalizedCandidate = normalize(candidate);
32203
+ const normalizedReference = normalize(reference);
32204
+ if (normalizedReference.length < MIN_COMPARABLE_LENGTH) return false;
32205
+ if (normalizedCandidate.includes(normalizedReference)) return true;
32206
+ return copyOverlapRatio(candidate, reference) >= NEAR_MATCH_RATIO;
32207
+ }
32208
+ function extractVisibleStrings(text) {
32209
+ const found = [];
32210
+ const lines = text.split("\n");
32211
+ for (const [index, line] of lines.entries()) {
32212
+ for (const match of line.matchAll(/>([^<>{}]{12,200})</g)) {
32213
+ const value = match[1]?.trim();
32214
+ if (value && /[a-zA-Z]{3}/.test(value)) found.push({ value, line: index + 1 });
32215
+ }
32216
+ }
32217
+ return found;
32218
+ }
32219
+ function detectOriginality(sources, references) {
32220
+ if (references.length === 0) return [];
32221
+ const findings = [];
32222
+ const seen = /* @__PURE__ */ new Set();
32223
+ for (const source of sources) {
32224
+ for (const { value, line } of extractVisibleStrings(source.text)) {
32225
+ for (const reference of references) {
32226
+ const hit = reference.copyStrings.find((copy) => isVerbatimReuse(value, copy));
32227
+ if (!hit) continue;
32228
+ const key = `${source.path}:${line}:${normalize(hit)}`;
32229
+ if (seen.has(key)) continue;
32230
+ seen.add(key);
32231
+ findings.push({
32232
+ id: "copied-reference-copy",
32233
+ snippet: value.slice(0, 120),
32234
+ file: source.path,
32235
+ line
32236
+ });
32237
+ }
32238
+ }
32239
+ }
32240
+ return findings;
32241
+ }
32242
+
32180
32243
  // src/engine/landing/lib/rules.ts
32181
32244
  var cap2 = (m, i) => m[i] ?? "";
32182
32245
  var num = (m, i) => Number(m[i] ?? 0);
@@ -32581,8 +32644,8 @@ var ANALYZERS = [
32581
32644
  const lines = text.split("\n");
32582
32645
  for (const m of text.matchAll(/(?:-webkit-)?background-clip\s*:\s*text/gi)) {
32583
32646
  const line = lineOf(text, m.index ?? 0);
32584
- const window = lines.slice(Math.max(0, line - 7), Math.min(lines.length, line + 6)).join("\n");
32585
- if (/gradient\(/i.test(window)) {
32647
+ const window2 = lines.slice(Math.max(0, line - 7), Math.min(lines.length, line + 6)).join("\n");
32648
+ if (/gradient\(/i.test(window2)) {
32586
32649
  out.push({ id: "gradient-text", snippet: "background-clip: text + gradient", file, line });
32587
32650
  }
32588
32651
  }
@@ -32704,7 +32767,16 @@ function dedupe(findings) {
32704
32767
  }
32705
32768
 
32706
32769
  // src/engine/landing/lib/critique.ts
32707
- var FAMILIES = ["typography", "color", "borders_depth", "motion", "spacing", "copy", "integrity"];
32770
+ var FAMILIES = [
32771
+ "typography",
32772
+ "color",
32773
+ "borders_depth",
32774
+ "motion",
32775
+ "spacing",
32776
+ "copy",
32777
+ "integrity",
32778
+ "originality"
32779
+ ];
32708
32780
  function round4(n) {
32709
32781
  return Math.round(n * 100) / 100;
32710
32782
  }
@@ -32723,6 +32795,7 @@ function critiqueLanding(input) {
32723
32795
  const raws = [];
32724
32796
  for (const source of sources) raws.push(...detectSource(source));
32725
32797
  raws.push(...detectPage(sources));
32798
+ raws.push(...detectOriginality(sources, input.references ?? []));
32726
32799
  for (const raw of raws) {
32727
32800
  const meta = RULE_META[raw.id];
32728
32801
  if (!meta) continue;
@@ -32760,41 +32833,82 @@ function describeCounts(findings) {
32760
32833
  return [b ? `${b} block` : "", w ? `${w} warn` : "", a ? `${a} advisory` : ""].filter(Boolean).join(", ");
32761
32834
  }
32762
32835
 
32763
- // src/commands/landing/snapshot.ts
32764
- import { mkdir as mkdir8, rename as rename2, writeFile as writeFile11 } from "fs/promises";
32836
+ // src/engine/landing/lib/referenceStore.ts
32837
+ import { mkdir as mkdir8, readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
32765
32838
  import path25 from "path";
32839
+ var REFERENCES_FILE = ".cache/inspiration-refs.json";
32840
+ var REFERENCE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
32841
+ async function readReferences(projectRoot) {
32842
+ try {
32843
+ const raw = await readFile21(path25.join(projectRoot, REFERENCES_FILE), "utf8");
32844
+ const parsed = JSON.parse(raw);
32845
+ if (!Array.isArray(parsed)) return [];
32846
+ const cutoff = Date.now() - REFERENCE_TTL_MS;
32847
+ return parsed.filter((entry) => {
32848
+ if (typeof entry !== "object" || entry === null) return false;
32849
+ const candidate = entry;
32850
+ if (typeof candidate.sectionId !== "string" || !Array.isArray(candidate.copyStrings)) return false;
32851
+ const at = Date.parse(candidate.consultedAt ?? "");
32852
+ return Number.isNaN(at) ? true : at >= cutoff;
32853
+ });
32854
+ } catch {
32855
+ return [];
32856
+ }
32857
+ }
32858
+ async function recordReference(projectRoot, reference) {
32859
+ return await recordReferences(projectRoot, [reference]);
32860
+ }
32861
+ async function recordReferences(projectRoot, references) {
32862
+ if (references.length === 0) return true;
32863
+ try {
32864
+ const existing = await readReferences(projectRoot);
32865
+ const replaced = new Set(references.map((reference) => reference.sectionId));
32866
+ const merged = [...existing.filter((entry) => !replaced.has(entry.sectionId)), ...references];
32867
+ const file = path25.join(projectRoot, REFERENCES_FILE);
32868
+ await mkdir8(path25.dirname(file), { recursive: true });
32869
+ await writeFile11(file, `${JSON.stringify(merged, null, 2)}
32870
+ `);
32871
+ return true;
32872
+ } catch {
32873
+ return false;
32874
+ }
32875
+ }
32876
+
32877
+ // src/commands/landing/snapshot.ts
32878
+ import { mkdir as mkdir9, rename as rename2, writeFile as writeFile12 } from "fs/promises";
32879
+ import path26 from "path";
32766
32880
  var CRITIC_VERSION = "2";
32767
32881
  function critiqueCacheDir(projectRoot) {
32768
- return path25.join(projectRoot, ".cache", "landing-critique");
32882
+ return path26.join(projectRoot, ".cache", "landing-critique");
32769
32883
  }
32770
32884
  function snapshotPath(projectRoot, slug) {
32771
- return path25.join(critiqueCacheDir(projectRoot), `${slug}.json`);
32885
+ return path26.join(critiqueCacheDir(projectRoot), `${slug}.json`);
32772
32886
  }
32773
32887
  async function writeCritiqueSnapshot(projectRoot, snapshot) {
32774
- await mkdir8(critiqueCacheDir(projectRoot), { recursive: true });
32888
+ await mkdir9(critiqueCacheDir(projectRoot), { recursive: true });
32775
32889
  const dest = snapshotPath(projectRoot, snapshot.slug);
32776
32890
  const tmp = `${dest}.tmp`;
32777
- await writeFile11(tmp, `${JSON.stringify(snapshot, null, 2)}
32891
+ await writeFile12(tmp, `${JSON.stringify(snapshot, null, 2)}
32778
32892
  `, "utf8");
32779
32893
  await rename2(tmp, dest);
32780
32894
  }
32781
32895
 
32782
32896
  // src/commands/landing/source-version.ts
32783
- import { readdir as readdir7, readFile as readFile21, stat as stat5 } from "fs/promises";
32784
- import path26 from "path";
32897
+ import { readdir as readdir7, readFile as readFile22, stat as stat5 } from "fs/promises";
32898
+ import path27 from "path";
32785
32899
  async function landingSourceRelPaths(landingDir) {
32786
32900
  const rel = [];
32787
- if (await isFile(path26.join(landingDir, "index.astro"))) rel.push("index.astro");
32788
- const componentsDir = path26.join(landingDir, "_components");
32901
+ if (await isFile(path27.join(landingDir, "index.astro"))) rel.push("index.astro");
32902
+ const componentsDir = path27.join(landingDir, "_components");
32789
32903
  for (const abs of await walkAstro(componentsDir)) {
32790
- rel.push(path26.relative(landingDir, abs).split(path26.sep).join("/"));
32904
+ rel.push(path27.relative(landingDir, abs).split(path27.sep).join("/"));
32791
32905
  }
32792
32906
  return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
32793
32907
  }
32794
32908
  async function readLandingSources(landingDir) {
32795
32909
  const rel = await landingSourceRelPaths(landingDir);
32796
32910
  const out = [];
32797
- for (const r of rel) out.push({ path: r, text: await readFile21(path26.join(landingDir, r), "utf8") });
32911
+ for (const r of rel) out.push({ path: r, text: await readFile22(path27.join(landingDir, r), "utf8") });
32798
32912
  return out;
32799
32913
  }
32800
32914
  async function computeLandingSourceSha(landingDir) {
@@ -32803,7 +32917,7 @@ async function computeLandingSourceSha(landingDir) {
32803
32917
  for (const r of rel) {
32804
32918
  let bytes;
32805
32919
  try {
32806
- bytes = await readFile21(path26.join(landingDir, r));
32920
+ bytes = await readFile22(path27.join(landingDir, r));
32807
32921
  } catch {
32808
32922
  bytes = Buffer.alloc(0);
32809
32923
  }
@@ -32827,7 +32941,7 @@ async function walkAstro(dir) {
32827
32941
  }
32828
32942
  const out = [];
32829
32943
  for (const entry of entries) {
32830
- const abs = path26.join(dir, entry.name);
32944
+ const abs = path27.join(dir, entry.name);
32831
32945
  if (entry.isDirectory()) out.push(...await walkAstro(abs));
32832
32946
  else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
32833
32947
  }
@@ -32888,14 +33002,14 @@ var critiqueCommand2 = defineCommand141({
32888
33002
  { availableSlugs: await listLandingSlugs(projectRoot) }
32889
33003
  );
32890
33004
  }
32891
- if (!await isDir(path27.resolve(projectRoot, "src", "pages", slug))) {
33005
+ if (!await isDir(path28.resolve(projectRoot, "src", "pages", slug))) {
32892
33006
  fail5("NOT_FOUND", `No landing at src/pages/${slug}/`, {
32893
33007
  availableSlugs: await listLandingSlugs(projectRoot)
32894
33008
  });
32895
33009
  }
32896
33010
  }
32897
- const brand = await loadBrandTokens(projectRoot);
32898
- const results = await Promise.all(slugs.map((slug) => critiqueOne(projectRoot, slug, brand)));
33011
+ const [brand, references] = await Promise.all([loadBrandTokens(projectRoot), readReferences(projectRoot)]);
33012
+ const results = await Promise.all(slugs.map((slug) => critiqueOne(projectRoot, slug, brand, references)));
32899
33013
  const landings = results.map(({ slug, report }) => ({
32900
33014
  slug,
32901
33015
  overall: report.overall,
@@ -32926,10 +33040,10 @@ var critiqueCommand2 = defineCommand141({
32926
33040
  );
32927
33041
  }
32928
33042
  });
32929
- async function critiqueOne(projectRoot, slug, brand) {
32930
- const landingDir = path27.resolve(projectRoot, "src", "pages", slug);
33043
+ async function critiqueOne(projectRoot, slug, brand, references) {
33044
+ const landingDir = path28.resolve(projectRoot, "src", "pages", slug);
32931
33045
  const [sources, sourceSha] = await Promise.all([readLandingSources(landingDir), computeLandingSourceSha(landingDir)]);
32932
- const report = critiqueLanding({ slug, sources, brand });
33046
+ const report = critiqueLanding({ slug, sources, brand, references });
32933
33047
  let snapshotFailed = false;
32934
33048
  try {
32935
33049
  await writeCritiqueSnapshot(projectRoot, {
@@ -32947,7 +33061,7 @@ async function critiqueOne(projectRoot, slug, brand) {
32947
33061
  }
32948
33062
  async function listLandingSlugs(projectRoot) {
32949
33063
  try {
32950
- const entries = await readdir8(path27.join(projectRoot, "src", "pages"), { withFileTypes: true });
33064
+ const entries = await readdir8(path28.join(projectRoot, "src", "pages"), { withFileTypes: true });
32951
33065
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
32952
33066
  } catch {
32953
33067
  return [];
@@ -32972,8 +33086,2336 @@ async function isDir(p) {
32972
33086
  }
32973
33087
  }
32974
33088
 
33089
+ // src/commands/landing/inspiration/index.ts
33090
+ import { defineCommand as defineCommand149 } from "citty";
33091
+
33092
+ // src/commands/landing/inspiration/add.ts
33093
+ import { defineCommand as defineCommand142 } from "citty";
33094
+
33095
+ // src/commands/landing/inspiration/shared.ts
33096
+ var INSPIRATION_HINTS = {
33097
+ adapt: "Reference only. Re-express this in the client's BRAND.md palette, type and imagery register. Reusing a headline, subhead or CTA verbatim is a Tier 0 message-match failure (references/gotchas.md) and `baker landing critique` will block the publish.",
33098
+ structureNotCopy: "Take the structural decision, not the furniture: what the eye hits first, the grid ratio, what was deliberately left out. Your copy must come from the client's own offer."
33099
+ };
33100
+ function fidelityHint(fidelity) {
33101
+ if (fidelity == null) return null;
33102
+ if (fidelity >= 0.9) return null;
33103
+ if (fidelity >= 0.75) {
33104
+ return `Reproduction fidelity ${fidelity.toFixed(2)} \u2014 the markup is close but not exact. Trust the screenshot over the code.`;
33105
+ }
33106
+ return `Reproduction fidelity ${fidelity.toFixed(2)} \u2014 this section is scroll- or JS-driven and the extracted markup does NOT render like the original. Use the screenshot and the notes; treat the code as a hint only.`;
33107
+ }
33108
+ function reportError(error) {
33109
+ if (error instanceof ApiError) {
33110
+ writeJson({ ok: false, error: { code: error.code, message: error.message } });
33111
+ } else {
33112
+ writeJson({
33113
+ ok: false,
33114
+ error: { code: "UNKNOWN", message: error instanceof Error ? error.message : String(error) }
33115
+ });
33116
+ }
33117
+ process.exit(1);
33118
+ }
33119
+ function splitList(value) {
33120
+ if (!value) return void 0;
33121
+ const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
33122
+ return parts.length > 0 ? parts : void 0;
33123
+ }
33124
+ function parseNumber(value) {
33125
+ if (value === void 0 || value === "") return void 0;
33126
+ const parsed = Number(value);
33127
+ return Number.isFinite(parsed) ? parsed : void 0;
33128
+ }
33129
+
33130
+ // src/commands/landing/inspiration/add.ts
33131
+ registerSchema({
33132
+ command: "landing.inspiration.add",
33133
+ description: "Add a landing page to the reference library and save it to this company. Returns immediately \u2014 studying a page takes a few minutes, so never wait on it. Use it when the client names a site they admire, or when you find a competitor page worth learning from.",
33134
+ args: {
33135
+ url: { type: "string", description: "Landing page address", required: true },
33136
+ note: { type: "string", description: "Why this page is worth keeping", required: false }
33137
+ }
33138
+ });
33139
+ var addCommand = defineCommand142({
33140
+ meta: {
33141
+ name: "add",
33142
+ description: "Add a landing page to the reference library. Example: baker landing inspiration add https://linear.app --note 'the client likes this density'"
33143
+ },
33144
+ args: {
33145
+ url: { type: "positional", description: "Landing page address", required: true },
33146
+ note: { type: "string", description: "Why this page is worth keeping", required: false }
33147
+ },
33148
+ run: async ({ args }) => {
33149
+ try {
33150
+ const data = await apiPost("/api/landing-inspiration/add", {
33151
+ url: args.url,
33152
+ note: args.note,
33153
+ favorite: true
33154
+ });
33155
+ const hints = data.alreadyKnown ? [
33156
+ "This page was already in the library, so nothing was re-studied \u2014 its sections are searchable now.",
33157
+ `Search it with: baker landing inspiration search --domain ${new URL(data.canonicalUrl).hostname}`
33158
+ ] : [
33159
+ "Studying this page takes a few minutes. Do NOT wait on it \u2014 carry on, and search for it later in the turn or in a later one.",
33160
+ "Meanwhile, search what is already in the library: baker landing inspiration search '<what you want to see>' --scope all"
33161
+ ];
33162
+ writeJson({
33163
+ ok: true,
33164
+ data: { id: data.sourceId, url: data.canonicalUrl, status: data.status, already_known: data.alreadyKnown },
33165
+ hints
33166
+ });
33167
+ } catch (error) {
33168
+ reportError(error);
33169
+ }
33170
+ }
33171
+ });
33172
+
33173
+ // src/commands/landing/inspiration/code.ts
33174
+ import { mkdir as mkdir10, writeFile as writeFile13 } from "fs/promises";
33175
+ import path29 from "path";
33176
+ import { defineCommand as defineCommand143 } from "citty";
33177
+ registerSchema({
33178
+ command: "landing.inspiration.code",
33179
+ description: "Write a reference section's standalone HTML+CSS to .baker/inspiration/<id>/ so you can read how it is built. Reference only \u2014 the structure is the lesson, the words are not yours to reuse. Consulting a section records it, and `baker landing critique` blocks a publish that ships its copy verbatim.",
33180
+ args: {
33181
+ id: { type: "string", description: "Section id from search", required: true },
33182
+ full: { type: "boolean", description: "Print the markup inline as well as writing it", required: false }
33183
+ }
33184
+ });
33185
+ var codeCommand = defineCommand143({
33186
+ meta: {
33187
+ name: "code",
33188
+ description: "Write one reference section's standalone markup to disk. Example: baker landing inspiration code k57abc\u2026 \u2014 read it for structure, then build your own."
33189
+ },
33190
+ args: {
33191
+ id: { type: "positional", description: "Section id from search", required: true },
33192
+ full: { type: "boolean", description: "Also print the markup inline", required: false, default: false }
33193
+ },
33194
+ run: async ({ args }) => {
33195
+ try {
33196
+ const id = args.id;
33197
+ const data = await apiGet("/api/landing-inspiration/section-code", { id });
33198
+ const dir = path29.join(process.cwd(), ".baker", "inspiration", id);
33199
+ await mkdir10(dir, { recursive: true });
33200
+ const file = path29.join(dir, "section.html");
33201
+ await writeFile13(file, data.html);
33202
+ const recorded = await recordReference(process.cwd(), {
33203
+ sectionId: id,
33204
+ sourceUrl: data.sourceUrl,
33205
+ copyStrings: data.copyStrings,
33206
+ consultedAt: (/* @__PURE__ */ new Date()).toISOString()
33207
+ });
33208
+ const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
33209
+ const fidelity = fidelityHint(data.fidelity);
33210
+ if (fidelity) hints.push(fidelity);
33211
+ if (data.fidelityNote) hints.push(data.fidelityNote);
33212
+ if (!recorded) {
33213
+ hints.push(
33214
+ "Could not record this reference (.cache/ not writable?) \u2014 the originality check at publish will not be able to see it, so be especially careful not to reuse its copy."
33215
+ );
33216
+ }
33217
+ writeJson({
33218
+ ok: true,
33219
+ data: {
33220
+ id,
33221
+ file: path29.relative(process.cwd(), file),
33222
+ bytes: data.html.length,
33223
+ fidelity: data.fidelity,
33224
+ css_custom_properties: data.cssCustomProperties,
33225
+ reproduction_notes: data.reproductionNotes,
33226
+ adaptation_notes: data.adaptationNotes,
33227
+ source_url: data.sourceUrl,
33228
+ ...args.full ? { html: data.html } : {}
33229
+ },
33230
+ hints
33231
+ });
33232
+ } catch (error) {
33233
+ reportError(error);
33234
+ }
33235
+ }
33236
+ });
33237
+
33238
+ // src/commands/landing/inspiration/favorites.ts
33239
+ import { defineCommand as defineCommand144 } from "citty";
33240
+ registerSchema({
33241
+ command: "landing.inspiration.favorites",
33242
+ description: "List the reference sections this company has saved. This is what `search` looks at by default, so it is the client's own taste profile \u2014 read it before proposing a direction.",
33243
+ args: {
33244
+ type: { type: "string", description: "Comma list of section types to filter by", required: false },
33245
+ limit: { type: "number", description: "Max results (default 30)", required: false }
33246
+ }
33247
+ });
33248
+ var favoritesCommand = defineCommand144({
33249
+ meta: {
33250
+ name: "favorites",
33251
+ description: "List this company's saved reference sections. Example: baker landing inspiration favorites --type hero,pricing"
33252
+ },
33253
+ args: {
33254
+ type: { type: "string", description: "Comma list of section types", required: false },
33255
+ limit: { type: "string", description: "Max results (default 30)", required: false }
33256
+ },
33257
+ run: async ({ args }) => {
33258
+ try {
33259
+ const params = {};
33260
+ const types = splitList(args.type);
33261
+ if (types) params.type = types.join(",");
33262
+ const limit = parseNumber(args.limit);
33263
+ params.limit = String(limit ?? 30);
33264
+ const data = await apiGet("/api/landing-inspiration/favorites", params);
33265
+ const sections = Array.isArray(data?.sections) ? data.sections : [];
33266
+ writeJson({
33267
+ ok: true,
33268
+ data: {
33269
+ sections: sections.map((section) => ({
33270
+ id: section.id,
33271
+ section: section.sectionType,
33272
+ composition: section.composition,
33273
+ look: section.visualRegister,
33274
+ why_it_works: section.whyItWorks,
33275
+ domain: section.domain
33276
+ }))
33277
+ },
33278
+ hints: sections.length === 0 ? [
33279
+ "Nothing saved yet. Add a page the client admires with `baker landing inspiration add <url>`, or browse the whole library with `baker landing inspiration search --scope all`."
33280
+ ] : [
33281
+ "These are the client's taste signals \u2014 read them as direction, not as a component library.",
33282
+ INSPIRATION_HINTS.adapt
33283
+ ]
33284
+ });
33285
+ } catch (error) {
33286
+ reportError(error);
33287
+ }
33288
+ }
33289
+ });
33290
+ registerSchema({
33291
+ command: "landing.inspiration.favorite",
33292
+ description: "Save a reference section (or a whole page) to this company, so it shows up in the default search scope.",
33293
+ args: {
33294
+ id: { type: "string", description: "Section id, or page id with --page", required: true },
33295
+ page: { type: "boolean", description: "Treat the id as a page rather than a section", required: false },
33296
+ note: { type: "string", description: "Why this is worth keeping", required: false }
33297
+ }
33298
+ });
33299
+ var favoriteCommand = defineCommand144({
33300
+ meta: {
33301
+ name: "favorite",
33302
+ description: "Save a reference section to this company. Example: baker landing inspiration favorite k57abc\u2026"
33303
+ },
33304
+ args: {
33305
+ id: { type: "positional", description: "Section id (or page id with --page)", required: true },
33306
+ page: { type: "boolean", description: "Treat the id as a page", required: false, default: false },
33307
+ note: { type: "string", description: "Why this is worth keeping", required: false }
33308
+ },
33309
+ run: async ({ args }) => {
33310
+ try {
33311
+ const id = args.id;
33312
+ const body = args.page ? { sourceId: id } : { sectionId: id };
33313
+ const data = await apiPost("/api/landing-inspiration/favorite", {
33314
+ ...body,
33315
+ note: args.note
33316
+ });
33317
+ writeJson({ ok: true, data: { id, favorited: data.favorited } });
33318
+ } catch (error) {
33319
+ reportError(error);
33320
+ }
33321
+ }
33322
+ });
33323
+ registerSchema({
33324
+ command: "landing.inspiration.unfavorite",
33325
+ description: "Remove a reference section (or page) from this company's saved set.",
33326
+ args: {
33327
+ id: { type: "string", description: "Section id, or page id with --page", required: true },
33328
+ page: { type: "boolean", description: "Treat the id as a page rather than a section", required: false }
33329
+ }
33330
+ });
33331
+ var unfavoriteCommand = defineCommand144({
33332
+ meta: {
33333
+ name: "unfavorite",
33334
+ description: "Remove a reference section from this company's saved set. Example: baker landing inspiration unfavorite k57abc\u2026"
33335
+ },
33336
+ args: {
33337
+ id: { type: "positional", description: "Section id (or page id with --page)", required: true },
33338
+ page: { type: "boolean", description: "Treat the id as a page", required: false, default: false }
33339
+ },
33340
+ run: async ({ args }) => {
33341
+ try {
33342
+ const id = args.id;
33343
+ const body = args.page ? { sourceId: id } : { sectionId: id };
33344
+ const data = await apiPost("/api/landing-inspiration/unfavorite", body);
33345
+ writeJson({ ok: true, data: { id, favorited: data.favorited } });
33346
+ } catch (error) {
33347
+ reportError(error);
33348
+ }
33349
+ }
33350
+ });
33351
+
33352
+ // src/commands/landing/inspiration/page.ts
33353
+ import { defineCommand as defineCommand145 } from "citty";
33354
+ registerSchema({
33355
+ command: "landing.inspiration.page",
33356
+ description: "Show a whole reference page as a sequence: every section top to bottom with its type and the idea behind it. This is the view to use when the question is how a good page is ORDERED rather than what one section looks like.",
33357
+ args: { id: { type: "string", description: "Page id (from a search result's source id)", required: true } }
33358
+ });
33359
+ var pageCommand = defineCommand145({
33360
+ meta: {
33361
+ name: "page",
33362
+ description: "Show how a reference page sequences its sections. Example: baker landing inspiration page j91xyz\u2026 \u2014 the blueprint, not the pixels."
33363
+ },
33364
+ args: { id: { type: "positional", description: "Page id", required: true } },
33365
+ run: async ({ args }) => {
33366
+ try {
33367
+ const data = await apiGet("/api/landing-inspiration/page", { id: args.id });
33368
+ if (data.status !== "indexed") {
33369
+ writeJson({
33370
+ ok: true,
33371
+ data: { id: data.id, url: data.url, status: data.status, sections: [] },
33372
+ hints: [
33373
+ data.status === "error" ? "We couldn't read this page. Try a different address, or a different page on the same site." : "Still being studied \u2014 check back later in the turn or in a later one."
33374
+ ]
33375
+ });
33376
+ return;
33377
+ }
33378
+ writeJson({
33379
+ ok: true,
33380
+ data: {
33381
+ id: data.id,
33382
+ url: data.url,
33383
+ domain: data.domain,
33384
+ title: data.title,
33385
+ archetype: data.archetype,
33386
+ stack: data.detectedStack,
33387
+ page_fidelity: data.pageFidelity,
33388
+ blueprint: data.sectionOrder,
33389
+ sections: data.sections.map((section) => ({
33390
+ id: section.id,
33391
+ position: section.index,
33392
+ section: section.sectionType,
33393
+ composition: section.composition,
33394
+ look: section.visualRegister,
33395
+ motion: section.motionSummary,
33396
+ why_it_works: section.whyItWorks,
33397
+ craft: section.craftScore
33398
+ }))
33399
+ },
33400
+ hints: [
33401
+ "The blueprint is the section order top to bottom \u2014 the answer to 'how do good pages in this category sequence themselves'.",
33402
+ "Use `baker landing inspiration view <section id>` for any section worth a closer look.",
33403
+ INSPIRATION_HINTS.adapt
33404
+ ]
33405
+ });
33406
+ } catch (error) {
33407
+ reportError(error);
33408
+ }
33409
+ }
33410
+ });
33411
+
33412
+ // src/commands/landing/inspiration/scrape.ts
33413
+ import { readFile as readFile23 } from "fs/promises";
33414
+ import path32 from "path";
33415
+ import { defineCommand as defineCommand146 } from "citty";
33416
+
33417
+ // src/engine/landing/lib/capturedReferences.ts
33418
+ var MAX_STRINGS_PER_SECTION = 40;
33419
+ function bodyOf(markup) {
33420
+ const body = markup.indexOf("<body");
33421
+ return body === -1 ? markup : markup.slice(body);
33422
+ }
33423
+ function capturedCopyStrings(markup) {
33424
+ const seen = /* @__PURE__ */ new Set();
33425
+ for (const { value } of extractVisibleStrings(bodyOf(markup))) {
33426
+ seen.add(value);
33427
+ if (seen.size >= MAX_STRINGS_PER_SECTION) break;
33428
+ }
33429
+ return [...seen];
33430
+ }
33431
+
33432
+ // src/engine/landing-library/blocked.ts
33433
+ var CHALLENGE_PHRASES = [
33434
+ "just a moment",
33435
+ "attention required",
33436
+ "verify you are human",
33437
+ "checking your browser",
33438
+ "enable javascript and cookies to continue",
33439
+ "unusual traffic",
33440
+ "access denied",
33441
+ "you have been blocked",
33442
+ "request unsuccessful",
33443
+ "are you a robot",
33444
+ "security check",
33445
+ "ddos protection",
33446
+ "captcha"
33447
+ ];
33448
+ var CHALLENGE_MARKERS = ["cf-browser-verification", "cf_chl_", "px-captcha", "_incapsula_", "distil_r_captcha"];
33449
+ function detectBlockedPage(page) {
33450
+ if (page.status !== null && page.status >= 400) {
33451
+ return {
33452
+ code: "HTTP_ERROR",
33453
+ message: `The site returned ${page.status} instead of the page \u2014 it may be blocking automated visits.`
33454
+ };
33455
+ }
33456
+ const haystack = `${page.title}
33457
+ ${page.bodyText.slice(0, 2e3)}`.toLowerCase();
33458
+ const phrase = CHALLENGE_PHRASES.find((candidate) => haystack.includes(candidate));
33459
+ if (phrase) {
33460
+ return { code: "BOT_CHALLENGE", message: "The site showed a security check instead of the page." };
33461
+ }
33462
+ const html = page.html?.toLowerCase() ?? "";
33463
+ if (CHALLENGE_MARKERS.some((marker) => html.includes(marker))) {
33464
+ return { code: "BOT_CHALLENGE", message: "The site showed a security check instead of the page." };
33465
+ }
33466
+ return null;
33467
+ }
33468
+ var BlockedPageError = class extends Error {
33469
+ code;
33470
+ constructor(blocked) {
33471
+ super(blocked.message);
33472
+ this.name = "BlockedPageError";
33473
+ this.code = blocked.code;
33474
+ }
33475
+ };
33476
+
33477
+ // src/engine/landing-library/run.ts
33478
+ import { mkdir as mkdir11, writeFile as writeFile15 } from "fs/promises";
33479
+ import path31 from "path";
33480
+
33481
+ // src/engine/landing-library/browser.ts
33482
+ import { createRequire } from "module";
33483
+ var require_ = createRequire(import.meta.url);
33484
+ var pwSpecifier = ["play", "wright"].join("");
33485
+ var DESKTOP_VIEWPORT = { width: 1440, height: 900 };
33486
+ var MOBILE_VIEWPORT = { width: 390, height: 844 };
33487
+ var DEVICE_SCALE_FACTOR = 2;
33488
+ async function launchBrowser() {
33489
+ const playwright = require_(pwSpecifier);
33490
+ return await playwright.chromium.launch({
33491
+ headless: true,
33492
+ args: ["--hide-scrollbars", "--disable-blink-features=AutomationControlled", "--mute-audio"]
33493
+ });
33494
+ }
33495
+ async function newPage(browser, viewport, opts = { motion: false }) {
33496
+ const context = await browser.newContext({
33497
+ viewport,
33498
+ deviceScaleFactor: DEVICE_SCALE_FACTOR,
33499
+ // Still captures freeze motion so screenshots are reproducible; the motion
33500
+ // pass re-opens a context with animation enabled.
33501
+ reducedMotion: opts.motion ? "no-preference" : "reduce",
33502
+ userAgent: viewport.width < 500 ? "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"
33503
+ });
33504
+ const page = await context.newPage();
33505
+ return { context, page };
33506
+ }
33507
+
33508
+ // src/engine/landing-library/usedCss.ts
33509
+ async function collectUsedCss(page, selector) {
33510
+ const collected = await page.evaluate(inPageCollectUsedCss, selector);
33511
+ const extra = [];
33512
+ for (const href of collected.unreadableHrefs) {
33513
+ const text = await fetchStylesheet(page, href);
33514
+ if (text) extra.push(`/* ${href} */
33515
+ ${text}`);
33516
+ }
33517
+ return { ...collected, css: [collected.css, ...extra].filter(Boolean).join("\n\n") };
33518
+ }
33519
+ async function fetchStylesheet(page, href) {
33520
+ try {
33521
+ const response = await page.request.get(href, { timeout: 1e4 });
33522
+ if (!response.ok()) return null;
33523
+ return await response.text();
33524
+ } catch {
33525
+ return null;
33526
+ }
33527
+ }
33528
+ var inPageCollectUsedCss = (selector) => {
33529
+ const root = document.querySelector(selector);
33530
+ const empty = {
33531
+ css: "",
33532
+ variables: "",
33533
+ inheritedSeed: "",
33534
+ unreadableHrefs: [],
33535
+ stats: { totalRules: 0, keptRules: 0, fontFaces: 0, keyframes: 0 }
33536
+ };
33537
+ if (!root) return empty;
33538
+ const INHERITED = [
33539
+ "color",
33540
+ "font-family",
33541
+ "font-size",
33542
+ "font-weight",
33543
+ "line-height",
33544
+ "letter-spacing",
33545
+ "text-align",
33546
+ "background-color",
33547
+ "-webkit-font-smoothing"
33548
+ ];
33549
+ const unreadableHrefs = [];
33550
+ const kept = [];
33551
+ const fontFaces = [];
33552
+ const keyframesByName = /* @__PURE__ */ new Map();
33553
+ let totalRules = 0;
33554
+ let keptStyleRules = 0;
33555
+ const matchesInSection = (selectorText) => {
33556
+ for (const part of selectorText.split(",")) {
33557
+ const base = part.replace(/::[a-zA-Z-]+(\([^)]*\))?/g, "").replace(/:(hover|focus|focus-visible|focus-within|active|visited|target|checked|disabled)\b/g, "").trim();
33558
+ if (!base) continue;
33559
+ try {
33560
+ if (root.matches(base) || root.querySelector(base)) return true;
33561
+ } catch {
33562
+ return true;
33563
+ }
33564
+ }
33565
+ return false;
33566
+ };
33567
+ const walk = (rules, sink) => {
33568
+ for (const rule of Array.from(rules)) {
33569
+ totalRules++;
33570
+ if (rule instanceof CSSStyleRule) {
33571
+ if (matchesInSection(rule.selectorText)) {
33572
+ sink.push(rule.cssText);
33573
+ keptStyleRules++;
33574
+ }
33575
+ continue;
33576
+ }
33577
+ if (rule instanceof CSSFontFaceRule) {
33578
+ fontFaces.push(rule.cssText);
33579
+ continue;
33580
+ }
33581
+ if (rule instanceof CSSKeyframesRule) {
33582
+ keyframesByName.set(rule.name, rule.cssText);
33583
+ continue;
33584
+ }
33585
+ const grouping = rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule || typeof CSSLayerBlockRule !== "undefined" && rule instanceof CSSLayerBlockRule || typeof CSSContainerRule !== "undefined" && rule instanceof CSSContainerRule;
33586
+ if (grouping) {
33587
+ const inner = [];
33588
+ walk(rule.cssRules, inner);
33589
+ if (inner.length === 0) continue;
33590
+ const condition = rule.conditionText ?? "";
33591
+ const prelude = rule instanceof CSSMediaRule ? `@media ${condition}` : rule.cssText.split("{")[0]?.trim();
33592
+ sink.push(`${prelude} {
33593
+ ${inner.join("\n")}
33594
+ }`);
33595
+ }
33596
+ }
33597
+ };
33598
+ for (const sheet of Array.from(document.styleSheets)) {
33599
+ const owner = sheet.ownerNode;
33600
+ if (owner?.hasAttribute?.("data-baker-freeze")) continue;
33601
+ try {
33602
+ walk(sheet.cssRules, kept);
33603
+ } catch {
33604
+ if (sheet.href) unreadableHrefs.push(sheet.href);
33605
+ }
33606
+ }
33607
+ const keptText = kept.join("\n");
33608
+ const usedKeyframes = [];
33609
+ for (const [name, text] of keyframesByName) {
33610
+ if (new RegExp(`(^|[\\s:,])${name}([\\s;,}]|$)`).test(keptText)) usedKeyframes.push(text);
33611
+ }
33612
+ const rootStyle = getComputedStyle(root);
33613
+ const variableDeclarations = [];
33614
+ for (const property of Array.from(rootStyle)) {
33615
+ if (!property.startsWith("--")) continue;
33616
+ const value = rootStyle.getPropertyValue(property).trim();
33617
+ if (value) variableDeclarations.push(` ${property}: ${value};`);
33618
+ }
33619
+ const seed = INHERITED.map((property) => ` ${property}: ${rootStyle.getPropertyValue(property)};`).join("\n");
33620
+ return {
33621
+ css: [...fontFaces, ...usedKeyframes, ...kept].join("\n"),
33622
+ variables: variableDeclarations.length ? `:root {
33623
+ ${variableDeclarations.join("\n")}
33624
+ }` : "",
33625
+ inheritedSeed: seed,
33626
+ unreadableHrefs,
33627
+ stats: {
33628
+ totalRules,
33629
+ keptRules: keptStyleRules,
33630
+ fontFaces: fontFaces.length,
33631
+ keyframes: usedKeyframes.length
33632
+ }
33633
+ };
33634
+ };
33635
+
33636
+ // src/engine/landing-library/bundle.ts
33637
+ async function buildSectionBundle(page, selector, pageUrl) {
33638
+ const used = await collectUsedCss(page, selector);
33639
+ const extracted = await page.evaluate(inPageExtractMarkup, {
33640
+ selector,
33641
+ pageUrl,
33642
+ rootAttribute: SECTION_ROOT_ATTRIBUTE
33643
+ });
33644
+ const css = [
33645
+ "*, *::before, *::after { box-sizing: border-box; }",
33646
+ "html, body { margin: 0; padding: 0; }",
33647
+ used.variables,
33648
+ `body {
33649
+ ${used.inheritedSeed}
33650
+ }`,
33651
+ used.css
33652
+ ].filter(Boolean).join("\n\n");
33653
+ const html = `<!doctype html>
33654
+ <html ${extracted.rootAttributes}>
33655
+ <head>
33656
+ <meta charset="utf-8">
33657
+ <meta name="viewport" content="width=device-width, initial-scale=1">
33658
+ <title>Section from ${escapeHtml2(extracted.host)}</title>
33659
+ <style>
33660
+ ${css}
33661
+ </style>
33662
+ </head>
33663
+ <body ${extracted.bodyAttributes}>
33664
+ ${wrapInLayoutContext(extracted.html, extracted.layoutContext)}
33665
+ </body>
33666
+ </html>
33667
+ `;
33668
+ return { html, css, assetUrls: extracted.assetUrls, stats: used.stats };
33669
+ }
33670
+ function escapeHtml2(value) {
33671
+ return value.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c] ?? c);
33672
+ }
33673
+ var SECTION_ROOT_ATTRIBUTE = "data-baker-section-root";
33674
+ function wrapInLayoutContext(html, context) {
33675
+ let wrapped = context.width > 0 && context.width < context.viewportWidth ? `<div style="width:${context.width}px;margin:0 auto;">
33676
+ ${html}
33677
+ </div>` : html;
33678
+ for (const ancestor of [...context.ancestors].reverse()) {
33679
+ const attributes = ancestor.attributes ? ` ${ancestor.attributes}` : "";
33680
+ const container = ancestor.containerType && ancestor.containerType !== "normal" ? `container-type:${ancestor.containerType};${ancestor.containerName && ancestor.containerName !== "none" ? `container-name:${ancestor.containerName};` : ""}` : "";
33681
+ wrapped = `<${ancestor.tag}${attributes} style="${NEUTRALIZED_ANCESTOR_STYLE}${container}">
33682
+ ${wrapped}
33683
+ </${ancestor.tag}>`;
33684
+ }
33685
+ return wrapped;
33686
+ }
33687
+ var NEUTRALIZED_ANCESTOR_STYLE = [
33688
+ "display:block !important",
33689
+ "position:static !important",
33690
+ "margin:0 !important",
33691
+ "padding:0 !important",
33692
+ "border:0 !important",
33693
+ "width:auto !important",
33694
+ "min-width:0 !important",
33695
+ "max-width:none !important",
33696
+ "height:auto !important",
33697
+ "min-height:0 !important",
33698
+ "max-height:none !important",
33699
+ "transform:none !important",
33700
+ "overflow:visible !important",
33701
+ ""
33702
+ ].join(";");
33703
+ var inPageExtractMarkup = ({
33704
+ selector,
33705
+ pageUrl,
33706
+ rootAttribute
33707
+ }) => {
33708
+ const root = document.querySelector(selector);
33709
+ const emptyContext = { ancestors: [], width: 0, viewportWidth: 0 };
33710
+ const serializeAttributes = (element) => {
33711
+ if (!element) return "";
33712
+ return Array.from(element.attributes).filter((attribute) => attribute.name !== "style").map((attribute) => `${attribute.name}="${attribute.value.replace(/"/g, "&quot;")}"`).join(" ");
33713
+ };
33714
+ const rootAttributes = serializeAttributes(document.documentElement) || 'lang="en"';
33715
+ const bodyAttributes = serializeAttributes(document.body);
33716
+ if (!root) {
33717
+ return {
33718
+ html: "",
33719
+ assetUrls: [],
33720
+ host: "",
33721
+ layoutContext: emptyContext,
33722
+ rootAttributes,
33723
+ bodyAttributes
33724
+ };
33725
+ }
33726
+ const ancestors = [];
33727
+ for (let ancestor = root.parentElement; ancestor && ancestor !== document.body; ) {
33728
+ const style = getComputedStyle(ancestor);
33729
+ ancestors.unshift({
33730
+ tag: /^[a-zA-Z][a-zA-Z0-9-]*$/.test(ancestor.tagName) ? ancestor.tagName.toLowerCase() : "div",
33731
+ attributes: Array.from(ancestor.attributes).filter((a) => a.name === "class" || a.name === "id" || a.name.startsWith("data-")).map((a) => `${a.name}="${a.value.replace(/"/g, "&quot;")}"`).join(" "),
33732
+ containerType: style.containerType,
33733
+ containerName: style.containerName
33734
+ });
33735
+ ancestor = ancestor.parentElement;
33736
+ }
33737
+ const layoutContext = {
33738
+ ancestors,
33739
+ width: Math.round(root.getBoundingClientRect().width),
33740
+ viewportWidth: window.innerWidth
33741
+ };
33742
+ const absolute = (value) => {
33743
+ try {
33744
+ return new URL(value, pageUrl).href;
33745
+ } catch {
33746
+ return value;
33747
+ }
33748
+ };
33749
+ const clone = root.cloneNode(true);
33750
+ clone.setAttribute(rootAttribute, "");
33751
+ const assetUrls = /* @__PURE__ */ new Set();
33752
+ for (const element of [clone, ...Array.from(clone.querySelectorAll("*"))]) {
33753
+ for (const attribute of ["src", "href", "poster"]) {
33754
+ const value = element.getAttribute(attribute);
33755
+ if (!value || value.startsWith("data:") || value.startsWith("#")) continue;
33756
+ const url = absolute(value);
33757
+ element.setAttribute(attribute, url);
33758
+ if (attribute !== "href" || element.tagName === "LINK") assetUrls.add(url);
33759
+ }
33760
+ const srcset = element.getAttribute("srcset");
33761
+ if (srcset) {
33762
+ element.setAttribute(
33763
+ "srcset",
33764
+ srcset.split(",").map((candidate) => {
33765
+ const [url, descriptor] = candidate.trim().split(/\s+/, 2);
33766
+ if (!url) return candidate;
33767
+ const resolved = absolute(url);
33768
+ assetUrls.add(resolved);
33769
+ return descriptor ? `${resolved} ${descriptor}` : resolved;
33770
+ }).join(", ")
33771
+ );
33772
+ }
33773
+ const style = element.getAttribute("style");
33774
+ if (style?.includes("url(")) {
33775
+ element.setAttribute(
33776
+ "style",
33777
+ style.replace(/url\((['"]?)([^'")]+)\1\)/g, (_match, quote, url) => {
33778
+ if (url.startsWith("data:")) return `url(${quote}${url}${quote})`;
33779
+ const resolved = absolute(url);
33780
+ assetUrls.add(resolved);
33781
+ return `url(${quote}${resolved}${quote})`;
33782
+ })
33783
+ );
33784
+ }
33785
+ }
33786
+ return {
33787
+ html: clone.outerHTML,
33788
+ assetUrls: Array.from(assetUrls),
33789
+ host: location.host,
33790
+ layoutContext,
33791
+ rootAttributes,
33792
+ bodyAttributes
33793
+ };
33794
+ };
33795
+
33796
+ // src/engine/landing-library/capture.ts
33797
+ async function captureSection(page, section) {
33798
+ const locator = page.locator(section.selector).first();
33799
+ if (await locator.count().catch(() => 0)) {
33800
+ const shot = await locator.screenshot({ type: "png", timeout: 15e3 }).catch(() => null);
33801
+ if (shot) return shot;
33802
+ }
33803
+ const doc = await page.evaluate(() => ({
33804
+ width: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),
33805
+ height: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0)
33806
+ }));
33807
+ const width = Math.min(section.rect.width, doc.width - section.rect.left);
33808
+ const height = Math.min(section.rect.height, doc.height - section.rect.top);
33809
+ if (width <= 0 || height <= 0) return null;
33810
+ return await page.screenshot({
33811
+ type: "png",
33812
+ clip: { x: section.rect.left, y: section.rect.top, width, height },
33813
+ timeout: 15e3
33814
+ }).catch(() => null);
33815
+ }
33816
+ async function captureSectionOnMobile(page, section) {
33817
+ const locator = page.locator(section.selector).first();
33818
+ if (!await locator.count().catch(() => 0)) return null;
33819
+ if (!await locator.isVisible().catch(() => false)) return null;
33820
+ return await locator.screenshot({ type: "png", timeout: 15e3 }).catch(() => null);
33821
+ }
33822
+
33823
+ // src/engine/landing-library/fidelity.ts
33824
+ import sharp3 from "sharp";
33825
+ var COMPARISON_SIZE = 32;
33826
+ function pixelSimilarity(a, b) {
33827
+ const length = Math.min(a.length, b.length);
33828
+ if (length === 0) return 0;
33829
+ let total = 0;
33830
+ for (let i = 0; i < length; i++) {
33831
+ total += Math.abs((a[i] ?? 0) - (b[i] ?? 0));
33832
+ }
33833
+ return 1 - total / (length * 255);
33834
+ }
33835
+ async function scoreFidelity(live, rendered) {
33836
+ const [liveMeta, renderedMeta] = await Promise.all([sharp3(live).metadata(), sharp3(rendered).metadata()]);
33837
+ const liveSize = { width: liveMeta.width ?? 0, height: liveMeta.height ?? 0 };
33838
+ const renderedSize = { width: renderedMeta.width ?? 0, height: renderedMeta.height ?? 0 };
33839
+ const [livePixels, renderedPixels] = await Promise.all([toGreyGrid(live), toGreyGrid(rendered)]);
33840
+ const score = pixelSimilarity(livePixels, renderedPixels);
33841
+ const heightRatio = liveSize.height > 0 && renderedSize.height > 0 ? Math.min(liveSize.height, renderedSize.height) / Math.max(liveSize.height, renderedSize.height) : 0;
33842
+ return {
33843
+ score,
33844
+ liveSize,
33845
+ renderedSize,
33846
+ ...heightRatio < 0.8 ? { note: `height differs by ${Math.round((1 - heightRatio) * 100)}% \u2014 the bundle reflowed` } : {}
33847
+ };
33848
+ }
33849
+ async function toGreyGrid(image) {
33850
+ const raw = await sharp3(image).greyscale().resize(COMPARISON_SIZE, COMPARISON_SIZE, { fit: "fill" }).raw().toBuffer();
33851
+ return new Uint8Array(raw);
33852
+ }
33853
+
33854
+ // src/engine/landing-library/motion.ts
33855
+ var EMPTY_MOTION = {
33856
+ hasMotion: false,
33857
+ // `none` rather than "no motion": it is the `MOTION_KINDS` member for this,
33858
+ // so a section with no movement is findable by the same facet as everything
33859
+ // else instead of being a special case only prose describes.
33860
+ summary: "none",
33861
+ entrance: [],
33862
+ hover: [],
33863
+ scroll: [],
33864
+ loop: [],
33865
+ libraries: [],
33866
+ respectsReducedMotion: false
33867
+ };
33868
+ function isWorthFilming(motion) {
33869
+ return motion.entrance.length + motion.scroll.length + motion.loop.length > 0;
33870
+ }
33871
+ async function collectMotion(page, selector) {
33872
+ const raw = await page.evaluate(inPageCollectMotion, selector).catch(() => null);
33873
+ if (!raw) return EMPTY_MOTION;
33874
+ return { ...raw, summary: summarizeMotion(raw) };
33875
+ }
33876
+ var HOVER_TRANSFORMS = /* @__PURE__ */ new Set(["lift", "grow", "tilt", "shift", "spin", "shadow", "animate"]);
33877
+ var KEYFRAME_PATTERNS = [
33878
+ [/marquee|ticker|scroll(ing)?-?(x|left|right)/, "marquee"],
33879
+ [/parallax/, "parallax"],
33880
+ [/sticky|pin(ned)?/, "sticky-pin"],
33881
+ [/count(er|up)/, "counter"],
33882
+ [/zoom|scale|pulse|grow/, "zoom"],
33883
+ [/fade|opacity|appear/, "fade"],
33884
+ [/slide|translate|in-?(left|right|up|down)/, "slide-in"]
33885
+ ];
33886
+ function toMotionKinds(kind, channel) {
33887
+ if (channel === "hover") return [HOVER_TRANSFORMS.has(kind) ? "hover-lift" : "fade"];
33888
+ const name = kind.toLowerCase();
33889
+ const matched = KEYFRAME_PATTERNS.filter(([pattern]) => pattern.test(name)).map(([, motionKind]) => motionKind);
33890
+ if (matched.length > 0) return matched;
33891
+ return [channel === "loop" ? "marquee" : "scroll-reveal"];
33892
+ }
33893
+ function motionKindsOf(motion) {
33894
+ const kinds = /* @__PURE__ */ new Set();
33895
+ const collect = (effects, channel) => {
33896
+ for (const effect of effects) {
33897
+ for (const kind of toMotionKinds(effect.kind, channel)) kinds.add(kind);
33898
+ }
33899
+ };
33900
+ collect(motion.entrance, "entrance");
33901
+ collect(motion.hover, "hover");
33902
+ collect(motion.scroll, "scroll");
33903
+ collect(motion.loop, "loop");
33904
+ const delays = new Set(motion.entrance.map((effect) => effect.delayMs ?? 0).filter((ms) => ms > 0));
33905
+ if (delays.size > 1) kinds.add("stagger");
33906
+ return [...kinds];
33907
+ }
33908
+ function summarizeMotion(motion) {
33909
+ const kinds = motionKindsOf(motion);
33910
+ if (kinds.length === 0 && motion.libraries.length === 0) return "none";
33911
+ const parts = [...kinds];
33912
+ if (motion.libraries.length > 0) parts.push(`via ${motion.libraries.join("/")}`);
33913
+ return parts.join(", ") + (motion.respectsReducedMotion ? "" : " (ignores reduced-motion)");
33914
+ }
33915
+ var inPageCollectMotion = (selector) => {
33916
+ const root = document.querySelector(selector);
33917
+ const empty = {
33918
+ hasMotion: false,
33919
+ entrance: [],
33920
+ hover: [],
33921
+ scroll: [],
33922
+ loop: [],
33923
+ libraries: [],
33924
+ respectsReducedMotion: false
33925
+ };
33926
+ if (!root) return empty;
33927
+ const entrance = [];
33928
+ const hover = [];
33929
+ const scroll = [];
33930
+ const loop = [];
33931
+ const keyframesByName = /* @__PURE__ */ new Map();
33932
+ let respectsReducedMotion = false;
33933
+ const inSection = (selectorText) => {
33934
+ for (const part of selectorText.split(",")) {
33935
+ const base = part.replace(/::[a-zA-Z-]+(\([^)]*\))?/g, "").replace(/:(hover|focus|focus-visible|focus-within|active|visited|target|checked|disabled)\b/g, "").trim();
33936
+ if (!base) continue;
33937
+ try {
33938
+ if (root.matches(base) || root.querySelector(base)) return true;
33939
+ } catch {
33940
+ return true;
33941
+ }
33942
+ }
33943
+ return false;
33944
+ };
33945
+ const toMs = (value) => {
33946
+ const first = value.split(",")[0]?.trim() ?? "";
33947
+ if (first.endsWith("ms")) return Number.parseFloat(first);
33948
+ if (first.endsWith("s")) return Number.parseFloat(first) * 1e3;
33949
+ return void 0;
33950
+ };
33951
+ const classifyKeyframes = (body) => {
33952
+ const text = body.toLowerCase();
33953
+ const fades = /opacity\s*:\s*0(\.0+)?\s*[;}]/.test(text);
33954
+ if (/translatey\(\s*-?\d/.test(text)) {
33955
+ const upward = /translatey\(\s*(\d|\.)/.test(text);
33956
+ return fades ? upward ? "fade-up" : "fade-down" : upward ? "slide-up" : "slide-down";
33957
+ }
33958
+ if (/translatex\(\s*-?\d/.test(text)) return fades ? "fade-in-x" : "slide-in-x";
33959
+ if (/scale\(/.test(text)) return fades ? "fade-zoom" : "zoom";
33960
+ if (/rotate\(/.test(text)) return "spin";
33961
+ if (fades) return "fade";
33962
+ return "animate";
33963
+ };
33964
+ const classifyHover = (style) => {
33965
+ const transform = style.transform ?? "";
33966
+ if (/translatey\(\s*-/i.test(transform)) return "lift";
33967
+ if (/scale\(\s*(1\.\d|[2-9])/i.test(transform)) return "grow";
33968
+ if (/rotate\(/i.test(transform)) return "tilt";
33969
+ if (transform && transform !== "none") return "shift";
33970
+ if (style.boxShadow) return "shadow";
33971
+ if (style.opacity) return "dim";
33972
+ if (style.backgroundColor || style.color) return "recolor";
33973
+ return null;
33974
+ };
33975
+ const readStyleRule = (rule, insideScrollTimeline) => {
33976
+ const style = rule.style;
33977
+ const isHover = /:hover\b/.test(rule.selectorText);
33978
+ if (isHover) {
33979
+ const kind = classifyHover(style);
33980
+ if (!kind || !inSection(rule.selectorText)) return;
33981
+ hover.push({
33982
+ kind,
33983
+ selector: rule.selectorText.slice(0, 120),
33984
+ durationMs: toMs(style.transitionDuration ?? ""),
33985
+ easing: style.transitionTimingFunction || void 0
33986
+ });
33987
+ return;
33988
+ }
33989
+ const animationName = style.animationName;
33990
+ if (!animationName || animationName === "none") return;
33991
+ if (!inSection(rule.selectorText)) return;
33992
+ const effect = {
33993
+ kind: animationName,
33994
+ selector: rule.selectorText.slice(0, 120),
33995
+ durationMs: toMs(style.animationDuration ?? ""),
33996
+ delayMs: toMs(style.animationDelay ?? ""),
33997
+ easing: style.animationTimingFunction || void 0
33998
+ };
33999
+ const infinite = (style.animationIterationCount ?? "").includes("infinite");
34000
+ const scrollDriven = insideScrollTimeline || Boolean(style.getPropertyValue("animation-timeline"));
34001
+ if (scrollDriven) scroll.push(effect);
34002
+ else if (infinite) loop.push(effect);
34003
+ else entrance.push(effect);
34004
+ };
34005
+ const walk = (rules, insideScrollTimeline) => {
34006
+ for (const rule of Array.from(rules)) {
34007
+ if (rule instanceof CSSKeyframesRule) {
34008
+ keyframesByName.set(rule.name, rule.cssText);
34009
+ continue;
34010
+ }
34011
+ if (rule instanceof CSSStyleRule) {
34012
+ try {
34013
+ readStyleRule(rule, insideScrollTimeline);
34014
+ } catch {
34015
+ }
34016
+ continue;
34017
+ }
34018
+ if (rule instanceof CSSMediaRule) {
34019
+ if (rule.conditionText.includes("prefers-reduced-motion")) {
34020
+ respectsReducedMotion = true;
34021
+ continue;
34022
+ }
34023
+ walk(rule.cssRules, insideScrollTimeline);
34024
+ continue;
34025
+ }
34026
+ const grouping = rule;
34027
+ if (grouping.cssRules) walk(grouping.cssRules, insideScrollTimeline);
34028
+ }
34029
+ };
34030
+ for (const sheet of Array.from(document.styleSheets)) {
34031
+ if (sheet.ownerNode?.hasAttribute?.("data-baker-freeze")) continue;
34032
+ try {
34033
+ walk(sheet.cssRules, false);
34034
+ } catch {
34035
+ }
34036
+ }
34037
+ for (const effect of [...entrance, ...loop, ...scroll]) {
34038
+ const body = keyframesByName.get(effect.kind);
34039
+ if (!body) continue;
34040
+ const classified = classifyKeyframes(body);
34041
+ effect.kind = loop.includes(effect) && classified.startsWith("slide") ? "marquee" : classified;
34042
+ }
34043
+ const libraries = [];
34044
+ const scoped = window ?? {};
34045
+ if (scoped.gsap || document.querySelector("[data-gsap]")) libraries.push("gsap");
34046
+ if (document.querySelector("[data-framer-name], [data-projection-id]")) libraries.push("framer-motion");
34047
+ if (document.querySelector("[data-aos]")) libraries.push("aos");
34048
+ if (scoped.Lenis || document.querySelector("[data-lenis]")) libraries.push("lenis");
34049
+ if (document.querySelector("[data-scroll], [data-scroll-container]")) libraries.push("locomotive");
34050
+ return {
34051
+ hasMotion: entrance.length + hover.length + scroll.length + loop.length + libraries.length > 0,
34052
+ entrance: entrance.slice(0, 12),
34053
+ hover: hover.slice(0, 12),
34054
+ scroll: scroll.slice(0, 12),
34055
+ loop: loop.slice(0, 12),
34056
+ libraries,
34057
+ respectsReducedMotion
34058
+ };
34059
+ };
34060
+
34061
+ // src/engine/landing-library/motionTake.ts
34062
+ import sharp4 from "sharp";
34063
+
34064
+ // src/engine/landing-library/prepare.ts
34065
+ var CONSENT_SELECTORS = [
34066
+ "#onetrust-accept-btn-handler",
34067
+ "#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll",
34068
+ "button#didomi-notice-agree-button",
34069
+ "[aria-label='Accept all']",
34070
+ "[data-testid='uc-accept-all-button']",
34071
+ ".cc-allow",
34072
+ ".cookie-accept"
34073
+ ];
34074
+ var CONSENT_TEXTS = ["Accept all", "Accept All", "Allow all", "I agree", "Got it", "Aceptar todo"];
34075
+ var CONSENT_HOSTS = [
34076
+ "transcend-cdn.com",
34077
+ "cookielaw.org",
34078
+ "onetrust.com",
34079
+ "cookiebot.com",
34080
+ "osano.com",
34081
+ "trustarc.com",
34082
+ "truste.com",
34083
+ "usercentrics.eu",
34084
+ "didomi.io",
34085
+ "privacy-center.org",
34086
+ "iubenda.com",
34087
+ "termly.io",
34088
+ "cookieyes.com",
34089
+ "sp-prod.net",
34090
+ "quantcast.com",
34091
+ "consensu.org",
34092
+ "ketch.com",
34093
+ "secureprivacy.ai",
34094
+ "civicuk.com"
34095
+ ];
34096
+ async function blockConsentManagers(page) {
34097
+ await page.route("**/*", (route) => {
34098
+ let host = "";
34099
+ try {
34100
+ host = new URL(route.request().url()).host;
34101
+ } catch {
34102
+ return route.continue();
34103
+ }
34104
+ const isConsentVendor = CONSENT_HOSTS.some((vendor) => host === vendor || host.endsWith(`.${vendor}`));
34105
+ return isConsentVendor ? route.abort() : route.continue();
34106
+ });
34107
+ }
34108
+ var ignore = () => void 0;
34109
+ async function preparePage(page, url, timeoutMs) {
34110
+ const response = await page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
34111
+ const status = response?.status() ?? null;
34112
+ await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(ignore);
34113
+ await dismissConsent(page, 4e3);
34114
+ await scrollThroughPage(page);
34115
+ await dismissConsent(page, 1e3);
34116
+ await page.evaluate(async () => {
34117
+ await document.fonts.ready;
34118
+ });
34119
+ await freezeMotion(page);
34120
+ await unpinOverlays(page);
34121
+ const measured = await page.evaluate(() => ({
34122
+ finalUrl: location.href,
34123
+ title: document.title,
34124
+ documentHeight: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0),
34125
+ bodyText: (document.body?.innerText ?? "").slice(0, 2e3)
34126
+ }));
34127
+ return { ...measured, status };
34128
+ }
34129
+ async function settleConsent(page) {
34130
+ await dismissConsent(page, 4e3);
34131
+ await page.mouse.wheel(0, 400).catch(ignore);
34132
+ await page.waitForTimeout(1500);
34133
+ await dismissConsent(page, 2e3);
34134
+ await page.evaluate(() => window.scrollTo(0, 0)).catch(ignore);
34135
+ }
34136
+ async function dismissConsent(page, waitForBannerMs) {
34137
+ await page.locator(CONSENT_SELECTORS.join(", ")).first().waitFor({ state: "attached", timeout: waitForBannerMs }).catch(ignore);
34138
+ for (const selector of CONSENT_SELECTORS) {
34139
+ const found = page.locator(selector).first();
34140
+ if (!await found.count().catch(() => 0)) continue;
34141
+ await found.click({ timeout: 2e3, force: true }).catch(ignore);
34142
+ await page.waitForTimeout(400);
34143
+ return;
34144
+ }
34145
+ for (const text of CONSENT_TEXTS) {
34146
+ const button = page.getByRole("button", { name: text, exact: false }).first();
34147
+ if (!await button.count().catch(() => 0)) continue;
34148
+ if (!await button.isVisible().catch(() => false)) continue;
34149
+ await button.click({ timeout: 2e3, force: true }).catch(ignore);
34150
+ await page.waitForTimeout(400);
34151
+ return;
34152
+ }
34153
+ }
34154
+ async function scrollThroughPage(page) {
34155
+ await page.evaluate(async () => {
34156
+ const step = 600;
34157
+ const pause = () => new Promise((resolve5) => setTimeout(resolve5, 250));
34158
+ for (let i = 0; i < 40; i++) {
34159
+ window.scrollBy(0, step);
34160
+ await pause();
34161
+ const reachedBottom = window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 2;
34162
+ if (reachedBottom) break;
34163
+ }
34164
+ window.scrollTo(0, 0);
34165
+ await pause();
34166
+ });
34167
+ await page.waitForTimeout(500);
34168
+ }
34169
+ var MAX_HEADER_HEIGHT = 220;
34170
+ async function unpinOverlays(page) {
34171
+ await page.evaluate((maxHeaderHeight) => {
34172
+ window.scrollTo(0, 0);
34173
+ for (const element of Array.from(document.querySelectorAll("*"))) {
34174
+ const position = getComputedStyle(element).position;
34175
+ if (position === "sticky") {
34176
+ element.style.setProperty("position", "static", "important");
34177
+ continue;
34178
+ }
34179
+ if (position !== "fixed") continue;
34180
+ const rect = element.getBoundingClientRect();
34181
+ const isTopAnchoredHeader = rect.top <= 8 && rect.height > 0 && rect.height <= maxHeaderHeight;
34182
+ if (isTopAnchoredHeader) {
34183
+ element.style.setProperty("position", "absolute", "important");
34184
+ element.style.setProperty("bottom", "auto", "important");
34185
+ } else {
34186
+ element.style.setProperty("display", "none", "important");
34187
+ }
34188
+ }
34189
+ }, MAX_HEADER_HEIGHT);
34190
+ await page.waitForTimeout(250);
34191
+ }
34192
+ async function freezeMotion(page) {
34193
+ await page.evaluate(() => {
34194
+ const highestTimer = window.setTimeout(() => void 0, 0);
34195
+ for (let id = 1; id <= highestTimer; id++) window.clearInterval(id);
34196
+ const style = document.createElement("style");
34197
+ style.setAttribute("data-baker-freeze", "true");
34198
+ style.textContent = `*, *::before, *::after {
34199
+ animation-play-state: paused !important;
34200
+ animation-delay: 0s !important;
34201
+ transition: none !important;
34202
+ }`;
34203
+ document.head.appendChild(style);
34204
+ for (const video of Array.from(document.querySelectorAll("video"))) {
34205
+ video.pause();
34206
+ }
34207
+ });
34208
+ await page.waitForTimeout(250);
34209
+ }
34210
+
34211
+ // src/engine/landing-library/geometry.ts
34212
+ function adaptiveGapThreshold(bands) {
34213
+ const sorted = [...bands].sort((a, b) => a.top - b.top);
34214
+ const gaps = [];
34215
+ for (let i = 0; i < sorted.length - 1; i++) {
34216
+ const current = sorted[i];
34217
+ const next = sorted[i + 1];
34218
+ if (!current || !next) continue;
34219
+ if (next.top > current.bottom) {
34220
+ const gap = next.top - current.bottom;
34221
+ if (gap < 200) gaps.push(gap);
34222
+ }
34223
+ }
34224
+ gaps.sort((a, b) => a - b);
34225
+ const p75 = gaps[Math.floor(gaps.length * 0.75)];
34226
+ if (p75 === void 0) return 15;
34227
+ return Math.max(10, Math.min(50, p75));
34228
+ }
34229
+ function bandsCollide(a, b, margin, maxGap) {
34230
+ const shrunkA = { top: a.top + margin, bottom: a.bottom - margin };
34231
+ const shrunkB = { top: b.top + margin, bottom: b.bottom - margin };
34232
+ const overlaps = shrunkA.top <= shrunkB.bottom && shrunkA.bottom >= shrunkB.top || shrunkB.top <= shrunkA.bottom && shrunkB.bottom >= shrunkA.top;
34233
+ if (overlaps) return true;
34234
+ const gap = Math.min(Math.abs(shrunkA.bottom - shrunkB.top), Math.abs(shrunkB.bottom - shrunkA.top));
34235
+ return gap <= maxGap;
34236
+ }
34237
+ function shouldPromoteToParent(group) {
34238
+ const isRunt = group.childCount < 3 || group.height < 80;
34239
+ return isRunt && group.parentHeight < 1600;
34240
+ }
34241
+
34242
+ // src/engine/landing-library/segment.ts
34243
+ async function installPageRuntime(page) {
34244
+ await page.addInitScript({
34245
+ content: `
34246
+ window.__name = window.__name || function (target) { return target; };
34247
+ window.__bakerGeom = {
34248
+ adaptiveGapThreshold: ${adaptiveGapThreshold.toString()},
34249
+ bandsCollide: ${bandsCollide.toString()},
34250
+ shouldPromoteToParent: ${shouldPromoteToParent.toString()},
34251
+ };`
34252
+ });
34253
+ }
34254
+ async function segmentPage(page, options) {
34255
+ return await page.evaluate(inPageSegment, options);
34256
+ }
34257
+ var inPageSegment = (options) => {
34258
+ const geom = window.__bakerGeom;
34259
+ const scrollX = window.scrollX;
34260
+ const scrollY = window.scrollY;
34261
+ const docWidth = Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0);
34262
+ const docHeight = Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0);
34263
+ const rectOf = (element) => {
34264
+ const r = element.getBoundingClientRect();
34265
+ const top = Math.max(0, Math.round(r.top + scrollY));
34266
+ const left = Math.max(0, Math.round(r.left + scrollX));
34267
+ return {
34268
+ top,
34269
+ left,
34270
+ bottom: Math.min(docHeight, Math.round(r.bottom + scrollY)),
34271
+ right: Math.min(docWidth, Math.round(r.right + scrollX))
34272
+ };
34273
+ };
34274
+ const syntheticRect = (element) => {
34275
+ const children = Array.from(element.children);
34276
+ if (children.length === 0) return null;
34277
+ let top = Number.POSITIVE_INFINITY;
34278
+ let left = Number.POSITIVE_INFINITY;
34279
+ let bottom = Number.NEGATIVE_INFINITY;
34280
+ let right = Number.NEGATIVE_INFINITY;
34281
+ for (const child of children) {
34282
+ const r = rectOf(child);
34283
+ if (r.bottom - r.top <= 0 && r.right - r.left <= 0) continue;
34284
+ top = Math.min(top, r.top);
34285
+ left = Math.min(left, r.left);
34286
+ bottom = Math.max(bottom, r.bottom);
34287
+ right = Math.max(right, r.right);
34288
+ }
34289
+ if (!Number.isFinite(top) || !Number.isFinite(bottom)) return null;
34290
+ return { top, left, bottom, right };
34291
+ };
34292
+ const boxOf = (element) => {
34293
+ const style = getComputedStyle(element);
34294
+ const r = style.display === "contents" ? syntheticRect(element) : rectOf(element);
34295
+ if (!r) return null;
34296
+ return { ...r, height: r.bottom - r.top, width: r.right - r.left };
34297
+ };
34298
+ const hasHiddenAncestor = (element) => {
34299
+ let current = element;
34300
+ while (current && current !== document.documentElement) {
34301
+ const style = getComputedStyle(current);
34302
+ if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0) return true;
34303
+ current = current.parentElement;
34304
+ }
34305
+ return false;
34306
+ };
34307
+ const isClippedByAncestor = (element, box) => {
34308
+ let parent = element.parentElement;
34309
+ while (parent && parent !== document.documentElement) {
34310
+ const style = getComputedStyle(parent);
34311
+ const clips = style.overflow === "hidden" || style.overflowX === "hidden" || style.overflowY === "hidden" || style.overflow === "clip";
34312
+ if (clips) {
34313
+ const p = rectOf(parent);
34314
+ const intersects = box.left < p.right && box.right > p.left && box.top < p.bottom && box.bottom > p.top;
34315
+ if (!intersects) return true;
34316
+ }
34317
+ parent = parent.parentElement;
34318
+ }
34319
+ return false;
34320
+ };
34321
+ const directText = (element) => {
34322
+ let text = "";
34323
+ for (const node of Array.from(element.childNodes)) {
34324
+ if (node.nodeType === 3) text += node.textContent ?? "";
34325
+ }
34326
+ return text.trim();
34327
+ };
34328
+ const NON_COPY_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEMPLATE"]);
34329
+ const visibleText = (root) => {
34330
+ let text = "";
34331
+ const hiddenCache = /* @__PURE__ */ new Map();
34332
+ const isHidden = (element) => {
34333
+ const cached = hiddenCache.get(element);
34334
+ if (cached !== void 0) return cached;
34335
+ const style = getComputedStyle(element);
34336
+ const hidden = style.display === "none" || style.visibility === "hidden";
34337
+ hiddenCache.set(element, hidden);
34338
+ return hidden;
34339
+ };
34340
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
34341
+ acceptNode: (node) => {
34342
+ let parent = node.parentElement;
34343
+ while (parent) {
34344
+ if (NON_COPY_TAGS.has(parent.tagName) || isHidden(parent)) return NodeFilter.FILTER_REJECT;
34345
+ if (parent === root) break;
34346
+ parent = parent.parentElement;
34347
+ }
34348
+ return NodeFilter.FILTER_ACCEPT;
34349
+ }
34350
+ });
34351
+ while (walker.nextNode() && text.length < 400) {
34352
+ text += ` ${walker.currentNode.textContent ?? ""}`;
34353
+ }
34354
+ return text.replace(/\s+/g, " ").trim();
34355
+ };
34356
+ const GRAPHIC_TAGS = /* @__PURE__ */ new Set(["img", "svg", "video", "picture", "canvas", "iframe"]);
34357
+ const hasBackgroundImage = (element) => {
34358
+ const bg = getComputedStyle(element).backgroundImage;
34359
+ return Boolean(bg) && bg !== "none" && bg.includes("url(");
34360
+ };
34361
+ const isContentLeaf = (element) => {
34362
+ const tag = element.tagName.toLowerCase();
34363
+ if (GRAPHIC_TAGS.has(tag)) return true;
34364
+ if (directText(element).length > 0) return true;
34365
+ return hasBackgroundImage(element);
34366
+ };
34367
+ const selectorFor = (element) => {
34368
+ const parts = [];
34369
+ let current = element;
34370
+ while (current && current !== document.documentElement) {
34371
+ const tag = current.tagName.toLowerCase();
34372
+ if (tag === "body") {
34373
+ parts.unshift("body");
34374
+ break;
34375
+ }
34376
+ const parent = current.parentElement;
34377
+ if (!parent) {
34378
+ parts.unshift(tag);
34379
+ break;
34380
+ }
34381
+ const sameTag = Array.from(parent.children).filter((c) => c.tagName === current?.tagName);
34382
+ const position = sameTag.indexOf(current) + 1;
34383
+ parts.unshift(sameTag.length > 1 ? `${tag}:nth-of-type(${position})` : tag);
34384
+ current = parent;
34385
+ }
34386
+ return parts.join(" > ");
34387
+ };
34388
+ const leaves = [];
34389
+ const all = document.querySelectorAll("*");
34390
+ const limit = Math.min(all.length, options.maxElements);
34391
+ for (let i = 0; i < limit; i++) {
34392
+ const element = all[i];
34393
+ if (!element) continue;
34394
+ const tag = element.tagName.toLowerCase();
34395
+ if (tag === "script" || tag === "style" || tag === "noscript" || tag === "link" || tag === "head") continue;
34396
+ if (!isContentLeaf(element)) continue;
34397
+ const box = boxOf(element);
34398
+ if (!box || box.height <= 0 || box.width <= 0) continue;
34399
+ if (box.height > options.maxSectionHeight) continue;
34400
+ if (hasHiddenAncestor(element)) continue;
34401
+ if (isClippedByAncestor(element, box)) continue;
34402
+ leaves.push({
34403
+ element,
34404
+ top: box.top,
34405
+ bottom: box.bottom,
34406
+ left: box.left,
34407
+ right: box.right,
34408
+ height: box.height
34409
+ });
34410
+ }
34411
+ if (leaves.length === 0) return [];
34412
+ const gapThreshold = geom.adaptiveGapThreshold(leaves.map((l) => ({ top: l.top, bottom: l.bottom })));
34413
+ const commonAncestor = (elements) => {
34414
+ let ancestor = elements[0] ?? null;
34415
+ while (ancestor && !elements.every((e) => ancestor?.contains(e))) {
34416
+ ancestor = ancestor.parentElement;
34417
+ }
34418
+ return ancestor;
34419
+ };
34420
+ const abandon = (members) => members.map((m) => ({ ...m, sealed: true }));
34421
+ const findCoveringAncestor = (start, bounds) => {
34422
+ let element = start;
34423
+ while (element) {
34424
+ const box = boxOf(element);
34425
+ if (!box) return null;
34426
+ if (box.height > options.maxSectionHeight) return null;
34427
+ const covers = box.top <= bounds.top && box.bottom >= bounds.bottom && box.left <= bounds.left && box.right >= bounds.right;
34428
+ if (covers) return { element, box };
34429
+ if (!element.parentElement || element.parentElement === document.documentElement) return null;
34430
+ element = element.parentElement;
34431
+ }
34432
+ return null;
34433
+ };
34434
+ const mergeBucket = (members) => {
34435
+ const bounds = {
34436
+ top: Math.min(...members.map((m) => m.top)),
34437
+ bottom: Math.max(...members.map((m) => m.bottom)),
34438
+ left: Math.min(...members.map((m) => m.left)),
34439
+ right: Math.max(...members.map((m) => m.right))
34440
+ };
34441
+ const covering = findCoveringAncestor(commonAncestor(members.map((m) => m.root)), bounds);
34442
+ if (!covering) return abandon(members);
34443
+ return [
34444
+ {
34445
+ root: covering.element,
34446
+ top: covering.box.top,
34447
+ bottom: covering.box.bottom,
34448
+ left: covering.box.left,
34449
+ right: covering.box.right,
34450
+ height: covering.box.height,
34451
+ leaves: members.flatMap((m) => m.leaves),
34452
+ sealed: false
34453
+ }
34454
+ ];
34455
+ };
34456
+ const clusterOnce = (groups2) => {
34457
+ const buckets = [];
34458
+ for (const group of groups2) {
34459
+ if (group.sealed) {
34460
+ buckets.push([group]);
34461
+ continue;
34462
+ }
34463
+ const target = buckets.find(
34464
+ (bucket) => bucket.some(
34465
+ (member) => !member.sealed && geom.bandsCollide(
34466
+ { top: member.top, bottom: member.bottom },
34467
+ { top: group.top, bottom: group.bottom },
34468
+ options.collisionMargin,
34469
+ gapThreshold
34470
+ )
34471
+ )
34472
+ );
34473
+ if (target) target.push(group);
34474
+ else buckets.push([group]);
34475
+ }
34476
+ if (buckets.length === groups2.length) return groups2;
34477
+ return buckets.flatMap((bucket) => bucket.length === 1 ? [bucket[0]] : mergeBucket(bucket));
34478
+ };
34479
+ let groups = leaves.map((leaf) => ({
34480
+ root: leaf.element,
34481
+ top: leaf.top,
34482
+ bottom: leaf.bottom,
34483
+ left: leaf.left,
34484
+ right: leaf.right,
34485
+ height: leaf.height,
34486
+ leaves: [leaf],
34487
+ sealed: false
34488
+ }));
34489
+ for (let pass = 0; pass < 40; pass++) {
34490
+ const next = clusterOnce(groups);
34491
+ if (next.length === groups.length) break;
34492
+ groups = next;
34493
+ }
34494
+ for (let pass = 0; pass < 10; pass++) {
34495
+ let promoted = false;
34496
+ groups = groups.map((group) => {
34497
+ const parent = group.root.parentElement;
34498
+ if (!parent || parent === document.documentElement || parent === document.body) return group;
34499
+ const parentBox = boxOf(parent);
34500
+ if (!parentBox) return group;
34501
+ if (!geom.shouldPromoteToParent({
34502
+ childCount: group.leaves.length,
34503
+ height: group.height,
34504
+ parentHeight: parentBox.height
34505
+ })) {
34506
+ return group;
34507
+ }
34508
+ promoted = true;
34509
+ return {
34510
+ ...group,
34511
+ root: parent,
34512
+ top: parentBox.top,
34513
+ bottom: parentBox.bottom,
34514
+ left: parentBox.left,
34515
+ right: parentBox.right,
34516
+ height: parentBox.height
34517
+ };
34518
+ });
34519
+ if (!promoted) break;
34520
+ groups = clusterOnce(groups);
34521
+ }
34522
+ const sameBox = (a, b) => Math.abs(a.top - b.top) <= 4 && Math.abs(a.bottom - b.bottom) <= 4 && Math.abs(a.left - b.left) <= 4 && Math.abs(a.right - b.right) <= 4;
34523
+ const containsBox = (outer, inner) => outer.top - 4 <= inner.top && outer.bottom + 4 >= inner.bottom && outer.left - 4 <= inner.left && outer.right + 4 >= inner.right;
34524
+ const deduped = [];
34525
+ const area = (g) => (g.right - g.left) * g.height;
34526
+ for (const group of groups.slice().sort((a, b) => area(b) - area(a) || b.leaves.length - a.leaves.length)) {
34527
+ const duplicate = deduped.some(
34528
+ (kept) => kept.root === group.root || kept.root.contains(group.root) || sameBox(kept, group) || containsBox(kept, group)
34529
+ );
34530
+ if (!duplicate) deduped.push(group);
34531
+ }
34532
+ return deduped.filter((group) => group.height > 0 && group.right - group.left > 0).sort((a, b) => a.top - b.top).map((group, index) => ({
34533
+ index,
34534
+ selector: selectorFor(group.root),
34535
+ rect: {
34536
+ top: group.top,
34537
+ left: group.left,
34538
+ width: group.right - group.left,
34539
+ height: group.height
34540
+ },
34541
+ leafCount: group.leaves.length,
34542
+ textPreview: visibleText(group.root).slice(0, 200)
34543
+ }));
34544
+ };
34545
+
34546
+ // src/engine/landing-library/motionTake.ts
34547
+ var FRAME_TIMES_MS = [0, 120, 260, 450, 800, 1400];
34548
+ var FRAME_WIDTH = 460;
34549
+ var GRID_COLUMNS = 3;
34550
+ var LABEL_HEIGHT = 22;
34551
+ async function captureMotionTake(browser, url, selector, timeoutMs = 45e3) {
34552
+ const { context, page } = await newPage(browser, DESKTOP_VIEWPORT, { motion: true });
34553
+ try {
34554
+ await blockConsentManagers(page);
34555
+ await installPageRuntime(page);
34556
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
34557
+ await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => void 0);
34558
+ await settleConsent(page);
34559
+ const target = page.locator(selector).first();
34560
+ if (!await target.count().catch(() => 0)) return null;
34561
+ await page.evaluate((sectionSelector) => {
34562
+ const element = document.querySelector(sectionSelector);
34563
+ if (!element) return;
34564
+ const top = element.getBoundingClientRect().top + window.scrollY;
34565
+ window.scrollTo(0, Math.max(0, top - window.innerHeight - 200));
34566
+ }, selector);
34567
+ await page.waitForTimeout(600);
34568
+ await page.evaluate((sectionSelector) => {
34569
+ document.querySelector(sectionSelector)?.scrollIntoView({ block: "center" });
34570
+ }, selector);
34571
+ const frames = [];
34572
+ let previous = 0;
34573
+ for (const time of FRAME_TIMES_MS) {
34574
+ await page.waitForTimeout(Math.max(0, time - previous));
34575
+ previous = time;
34576
+ const shot = await page.screenshot({ type: "png", timeout: 1e4 }).catch(() => null);
34577
+ if (shot) frames.push(shot);
34578
+ }
34579
+ if (frames.length === 0) return null;
34580
+ return { filmstrip: await composeFilmstrip(frames), frameCount: frames.length };
34581
+ } catch {
34582
+ return null;
34583
+ } finally {
34584
+ await context.close();
34585
+ }
34586
+ }
34587
+ async function composeFilmstrip(frames) {
34588
+ const scaled = await Promise.all(frames.map((frame) => sharp4(frame).resize({ width: FRAME_WIDTH }).png().toBuffer()));
34589
+ const first = await sharp4(scaled[0]).metadata();
34590
+ const frameHeight = first.height ?? 300;
34591
+ const cellHeight = frameHeight + LABEL_HEIGHT;
34592
+ const rows = Math.ceil(scaled.length / GRID_COLUMNS);
34593
+ const width = FRAME_WIDTH * Math.min(GRID_COLUMNS, scaled.length);
34594
+ const composites = scaled.flatMap((frame, index) => {
34595
+ const column = index % GRID_COLUMNS;
34596
+ const row = Math.floor(index / GRID_COLUMNS);
34597
+ const label = Buffer.from(
34598
+ `<svg width="${FRAME_WIDTH}" height="${LABEL_HEIGHT}">
34599
+ <rect width="100%" height="100%" fill="#111827"/>
34600
+ <text x="8" y="15" font-family="monospace" font-size="12" fill="#f9fafb">+${FRAME_TIMES_MS[index] ?? 0}ms</text>
34601
+ </svg>`
34602
+ );
34603
+ return [
34604
+ { input: label, left: column * FRAME_WIDTH, top: row * cellHeight },
34605
+ { input: frame, left: column * FRAME_WIDTH, top: row * cellHeight + LABEL_HEIGHT }
34606
+ ];
34607
+ });
34608
+ return await sharp4({
34609
+ create: {
34610
+ width,
34611
+ height: cellHeight * rows,
34612
+ channels: 3,
34613
+ background: { r: 17, g: 24, b: 39 }
34614
+ }
34615
+ }).composite(composites).png().toBuffer();
34616
+ }
34617
+
34618
+ // src/engine/landing-library/renderBundle.ts
34619
+ var SETTLE_ANIMATIONS_CSS = `*, *::before, *::after {
34620
+ animation-play-state: running !important;
34621
+ animation-delay: 0s !important;
34622
+ animation-duration: 1ms !important;
34623
+ animation-iteration-count: 1 !important;
34624
+ animation-fill-mode: forwards !important;
34625
+ transition: none !important;
34626
+ }`;
34627
+ async function renderBundleToPng(browser, html, viewportWidth, options = {}) {
34628
+ const { wholePage = false, timeoutMs = 2e4 } = options;
34629
+ const { context, page } = await newPage(browser, { width: viewportWidth, height: 900 });
34630
+ try {
34631
+ await page.setContent(html, { waitUntil: "load", timeout: timeoutMs });
34632
+ await page.addStyleTag({ content: SETTLE_ANIMATIONS_CSS });
34633
+ await page.evaluate(async () => {
34634
+ await document.fonts.ready;
34635
+ });
34636
+ await page.waitForTimeout(300);
34637
+ if (!wholePage) {
34638
+ for (const selector of [`[${SECTION_ROOT_ATTRIBUTE}]`, "body > *"]) {
34639
+ const target = page.locator(selector).first();
34640
+ if (!await target.count()) continue;
34641
+ const shot = await target.screenshot({ type: "png", timeout: timeoutMs }).catch(() => null);
34642
+ if (shot) return shot;
34643
+ }
34644
+ }
34645
+ return await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
34646
+ } catch {
34647
+ return null;
34648
+ } finally {
34649
+ await context.close();
34650
+ }
34651
+ }
34652
+
34653
+ // src/engine/landing-library/report.ts
34654
+ import { writeFile as writeFile14 } from "fs/promises";
34655
+ import path30 from "path";
34656
+ async function writeCaptureReport(manifest, outDir) {
34657
+ const file = path30.join(outDir, "report.html");
34658
+ await writeFile14(file, renderReport(manifest));
34659
+ return file;
34660
+ }
34661
+ function escapeHtml3(value) {
34662
+ return value.replace(
34663
+ /[&<>"']/g,
34664
+ (character) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[character] ?? character
34665
+ );
34666
+ }
34667
+ function fidelityTone(fidelity) {
34668
+ if (fidelity === null) return "unknown";
34669
+ if (fidelity >= 0.95) return "good";
34670
+ if (fidelity >= 0.85) return "fair";
34671
+ return "poor";
34672
+ }
34673
+ function medianFidelity(sections) {
34674
+ const scored = sections.map((section) => section.fidelity).filter((value) => value !== null).sort((a, b) => a - b);
34675
+ return scored.length === 0 ? null : scored[Math.floor(scored.length / 2)] ?? null;
34676
+ }
34677
+ function formatFidelity(fidelity) {
34678
+ return fidelity === null ? "\u2014" : fidelity.toFixed(2);
34679
+ }
34680
+ function renderSection3(section) {
34681
+ const tone = fidelityTone(section.fidelity);
34682
+ const rendered = section.bundle ? section.bundle.replace(/section\.html$/, "section-rendered.png") : null;
34683
+ const shot = (label, src, note) => {
34684
+ if (!src) return `<figure class="shot empty"><figcaption>${label} \u2014 none</figcaption></figure>`;
34685
+ return `<figure class="shot">
34686
+ <figcaption>${label}${note ? ` <span class="note">${escapeHtml3(note)}</span>` : ""}</figcaption>
34687
+ <a href="${escapeHtml3(src)}" target="_blank" rel="noopener"><img src="${escapeHtml3(src)}" alt="" loading="lazy"></a>
34688
+ </figure>`;
34689
+ };
34690
+ return `<section class="card">
34691
+ <header>
34692
+ <h2><span class="index">${String(section.index).padStart(2, "0")}</span> ${section.rect.width}\xD7${section.rect.height}</h2>
34693
+ <span class="badge ${tone}">fidelity ${formatFidelity(section.fidelity)}</span>
34694
+ ${section.fidelityNote ? `<span class="badge warn">${escapeHtml3(section.fidelityNote)}</span>` : ""}
34695
+ ${section.motion.hasMotion ? `<span class="badge motion">${escapeHtml3(section.motion.summary)}</span>` : ""}
34696
+ </header>
34697
+ <p class="preview">${escapeHtml3(section.textPreview.slice(0, 220)) || "<em>no text</em>"}</p>
34698
+ <div class="shots">
34699
+ ${shot("Live", section.desktopShot)}
34700
+ ${shot("Reproduction", rendered, "rendered from the extracted bundle")}
34701
+ ${shot("Mobile", section.mobileShot)}
34702
+ </div>
34703
+ ${section.motionFilmstrip ? `<div class="filmstrip">${shot("Motion \u2014 six frames, left to right", section.motionFilmstrip)}</div>` : ""}
34704
+ <footer>
34705
+ <code>${escapeHtml3(section.selector)}</code>
34706
+ ${section.bundle ? `<a href="${escapeHtml3(section.bundle)}" target="_blank" rel="noopener">open the standalone bundle \u2192</a>` : ""}
34707
+ </footer>
34708
+ </section>`;
34709
+ }
34710
+ function renderReport(manifest) {
34711
+ const median = medianFidelity(manifest.sections);
34712
+ const moving = manifest.sections.filter((section) => section.motionFilmstrip !== null).length;
34713
+ return `<!doctype html>
34714
+ <html lang="en">
34715
+ <head>
34716
+ <meta charset="utf-8">
34717
+ <meta name="viewport" content="width=device-width, initial-scale=1">
34718
+ <title>Capture report \u2014 ${escapeHtml3(manifest.title || manifest.url)}</title>
34719
+ <style>
34720
+ :root { color-scheme: light dark; --line: #e5e7eb; --muted: #6b7280; --bg: #fafafa; --card: #fff; }
34721
+ @media (prefers-color-scheme: dark) {
34722
+ :root { --line: #27272a; --muted: #a1a1aa; --bg: #09090b; --card: #131316; }
34723
+ }
34724
+ * { box-sizing: border-box; }
34725
+ body { margin: 0; padding: 24px; background: var(--bg);
34726
+ font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
34727
+ h1 { margin: 0 0 4px; font-size: 20px; }
34728
+ a { color: inherit; }
34729
+ .sub { color: var(--muted); margin: 0 0 20px; }
34730
+ .stats { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; }
34731
+ .stat { border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px; background: var(--card); }
34732
+ .stat b { display: block; font-size: 18px; }
34733
+ .stat span { color: var(--muted); font-size: 12px; }
34734
+ .card { border: 1px solid var(--line); border-radius: 12px; background: var(--card);
34735
+ padding: 16px; margin-bottom: 16px; }
34736
+ .card header { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
34737
+ .card h2 { font-size: 15px; margin: 0; font-weight: 600; }
34738
+ .index { display: inline-block; min-width: 26px; color: var(--muted); }
34739
+ .badge { font-size: 12px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--line); }
34740
+ .badge.good { background: #dcfce7; color: #166534; border-color: #bbf7d0; }
34741
+ .badge.fair { background: #fef3c7; color: #92400e; border-color: #fde68a; }
34742
+ .badge.poor { background: #fee2e2; color: #991b1b; border-color: #fecaca; }
34743
+ .badge.warn { background: #fee2e2; color: #991b1b; border-color: #fecaca; }
34744
+ .badge.motion { background: #ede9fe; color: #5b21b6; border-color: #ddd6fe; }
34745
+ .preview { color: var(--muted); margin: 0 0 12px; font-size: 13px; }
34746
+ .shots { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; align-items: start; }
34747
+ .filmstrip { margin-top: 12px; }
34748
+ figure { margin: 0; }
34749
+ figcaption { font-size: 12px; color: var(--muted); margin-bottom: 4px; }
34750
+ figcaption .note { opacity: .75; }
34751
+ .shot img { width: 100%; height: auto; border: 1px solid var(--line); border-radius: 8px;
34752
+ background: #fff; display: block; }
34753
+ .shot.empty { border: 1px dashed var(--line); border-radius: 8px; padding: 20px; text-align: center; }
34754
+ .card footer { display: flex; justify-content: space-between; gap: 12px; margin-top: 12px;
34755
+ font-size: 12px; color: var(--muted); }
34756
+ code { font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
34757
+ </style>
34758
+ </head>
34759
+ <body>
34760
+ <h1>${escapeHtml3(manifest.title || "Capture report")}</h1>
34761
+ <p class="sub"><a href="${escapeHtml3(manifest.finalUrl)}" target="_blank" rel="noopener">${escapeHtml3(manifest.finalUrl)}</a> \xB7 captured ${escapeHtml3(manifest.capturedAt)}</p>
34762
+
34763
+ <div class="stats">
34764
+ <div class="stat"><b>${manifest.sections.length}</b><span>sections</span></div>
34765
+ <div class="stat"><b>${formatFidelity(median)}</b><span>median fidelity</span></div>
34766
+ <div class="stat"><b>${formatFidelity(manifest.page.fidelity)}</b><span>whole page</span></div>
34767
+ <div class="stat"><b>${moving}</b><span>filmed moving</span></div>
34768
+ <div class="stat"><b>${manifest.documentHeight}px</b><span>page height</span></div>
34769
+ </div>
34770
+
34771
+ <section class="card">
34772
+ <header>
34773
+ <h2>Whole page</h2>
34774
+ <span class="badge ${fidelityTone(manifest.page.fidelity)}">fidelity ${formatFidelity(manifest.page.fidelity)}</span>
34775
+ </header>
34776
+ <p class="preview">The same extractor rooted at &lt;body&gt; \u2014 one standalone file that should render like the original.</p>
34777
+ <div class="shots">
34778
+ <figure class="shot"><figcaption>Live</figcaption><a href="full-page.png" target="_blank" rel="noopener"><img src="full-page.png" alt="" loading="lazy"></a></figure>
34779
+ ${manifest.page.bundle ? `<figure class="shot"><figcaption>Reproduction <span class="note">from page.html</span></figcaption><a href="page-rendered.png" target="_blank" rel="noopener"><img src="page-rendered.png" alt="" loading="lazy"></a></figure>` : `<figure class="shot empty"><figcaption>Reproduction \u2014 none</figcaption></figure>`}
34780
+ </div>
34781
+ </section>
34782
+
34783
+ ${manifest.sections.map(renderSection3).join("\n")}
34784
+ </body>
34785
+ </html>
34786
+ `;
34787
+ }
34788
+
34789
+ // src/engine/landing-library/types.ts
34790
+ var DEFAULT_SEGMENT_OPTIONS = {
34791
+ maxSectionHeight: 2160,
34792
+ collisionMargin: 2,
34793
+ maxElements: 15e3
34794
+ };
34795
+
34796
+ // src/engine/landing-library/visualHash.ts
34797
+ import sharp5 from "sharp";
34798
+ var HASH_WIDTH = 9;
34799
+ var HASH_HEIGHT = 8;
34800
+ async function perceptualHash(image) {
34801
+ try {
34802
+ const raw = await sharp5(image).greyscale().resize(HASH_WIDTH, HASH_HEIGHT, { fit: "fill" }).raw().toBuffer();
34803
+ return bitsToHex(rowGradientBits(new Uint8Array(raw)));
34804
+ } catch {
34805
+ return null;
34806
+ }
34807
+ }
34808
+ function rowGradientBits(pixels) {
34809
+ const bits = [];
34810
+ for (let y = 0; y < HASH_HEIGHT; y++) {
34811
+ for (let x = 0; x < HASH_WIDTH - 1; x++) {
34812
+ const left = pixels[y * HASH_WIDTH + x] ?? 0;
34813
+ const right = pixels[y * HASH_WIDTH + x + 1] ?? 0;
34814
+ bits.push(left > right);
34815
+ }
34816
+ }
34817
+ return bits;
34818
+ }
34819
+ function bitsToHex(bits) {
34820
+ let hex = "";
34821
+ for (let index = 0; index < bits.length; index += 4) {
34822
+ let nibble = 0;
34823
+ for (let offset = 0; offset < 4; offset++) {
34824
+ if (bits[index + offset]) nibble |= 1 << 3 - offset;
34825
+ }
34826
+ hex += nibble.toString(16);
34827
+ }
34828
+ return hex;
34829
+ }
34830
+
34831
+ // src/engine/landing-library/run.ts
34832
+ async function reproducePage(args) {
34833
+ const { browser, page, outDir, pageUrl, livePageShot } = args;
34834
+ const built = await buildSectionBundle(page, "body", pageUrl).catch(() => null);
34835
+ if (!built) return { bundle: null, fidelity: null };
34836
+ await writeFile15(path31.join(outDir, "page.html"), built.html);
34837
+ const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width, {
34838
+ wholePage: true,
34839
+ timeoutMs: 6e4
34840
+ });
34841
+ if (!rendered || !livePageShot) return { bundle: "page.html", fidelity: null };
34842
+ await writeFile15(path31.join(outDir, "page-rendered.png"), rendered);
34843
+ const { score, note } = await scoreFidelity(livePageShot, rendered);
34844
+ return { bundle: "page.html", fidelity: score, ...note ? { fidelityNote: note } : {} };
34845
+ }
34846
+ async function captureOneSection(args) {
34847
+ const { browser, page, candidate, sectionsDir, outDir, pageUrl, withCode } = args;
34848
+ const dir = path31.join(sectionsDir, String(candidate.index).padStart(2, "0"));
34849
+ await mkdir11(dir, { recursive: true });
34850
+ const desktop = await captureSection(page, candidate);
34851
+ if (desktop) await writeFile15(path31.join(dir, "desktop.png"), desktop);
34852
+ const visualHash = desktop ? await perceptualHash(desktop) : null;
34853
+ const motion = await collectMotion(page, candidate.selector);
34854
+ const built = withCode ? await buildSectionBundle(page, candidate.selector, pageUrl) : null;
34855
+ let fidelity = null;
34856
+ let fidelityNote;
34857
+ if (built) {
34858
+ await writeFile15(path31.join(dir, "section.html"), built.html);
34859
+ const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width);
34860
+ if (rendered && desktop) {
34861
+ await writeFile15(path31.join(dir, "section-rendered.png"), rendered);
34862
+ const result = await scoreFidelity(desktop, rendered);
34863
+ fidelity = result.score;
34864
+ fidelityNote = result.note;
34865
+ }
34866
+ }
34867
+ return {
34868
+ ...candidate,
34869
+ desktopShot: desktop ? path31.relative(outDir, path31.join(dir, "desktop.png")) : null,
34870
+ mobileShot: null,
34871
+ bundle: built ? path31.relative(outDir, path31.join(dir, "section.html")) : null,
34872
+ fidelity,
34873
+ ...fidelityNote ? { fidelityNote } : {},
34874
+ ...built ? { cssStats: built.stats } : {},
34875
+ motion,
34876
+ motionFilmstrip: null,
34877
+ visualHash
34878
+ };
34879
+ }
34880
+ async function captureMobileShots(args) {
34881
+ const { browser, sections, sectionsDir, outDir, pageUrl, timeoutMs } = args;
34882
+ const mobile = await newPage(browser, MOBILE_VIEWPORT);
34883
+ try {
34884
+ await blockConsentManagers(mobile.page);
34885
+ await installPageRuntime(mobile.page);
34886
+ await preparePage(mobile.page, pageUrl, timeoutMs);
34887
+ for (const section of sections) {
34888
+ const shot = await captureSectionOnMobile(mobile.page, section);
34889
+ if (!shot) continue;
34890
+ const file = path31.join(sectionsDir, String(section.index).padStart(2, "0"), "mobile.png");
34891
+ await writeFile15(file, shot);
34892
+ section.mobileShot = path31.relative(outDir, file);
34893
+ }
34894
+ } finally {
34895
+ await mobile.context.close();
34896
+ }
34897
+ }
34898
+ async function captureMotionTakes(args) {
34899
+ const { browser, sections, sectionsDir, outDir, pageUrl, log } = args;
34900
+ const moving = sections.filter((section) => isWorthFilming(section.motion));
34901
+ if (moving.length === 0) return;
34902
+ log(`filming ${moving.length} moving sections`);
34903
+ for (const section of moving) {
34904
+ const take = await captureMotionTake(browser, pageUrl, section.selector);
34905
+ if (!take) continue;
34906
+ const dir = path31.join(sectionsDir, String(section.index).padStart(2, "0"));
34907
+ const file = path31.join(dir, "motion-filmstrip.png");
34908
+ await writeFile15(file, take.filmstrip);
34909
+ section.motionFilmstrip = path31.relative(outDir, file);
34910
+ log(` [${section.index}] ${section.motion.summary}`);
34911
+ }
34912
+ }
34913
+ async function scrapeLanding(options) {
34914
+ const timeoutMs = options.timeoutMs ?? 45e3;
34915
+ const log = options.onProgress ?? (() => void 0);
34916
+ const sectionsDir = path31.join(options.outDir, "sections");
34917
+ const browser = await launchBrowser();
34918
+ try {
34919
+ const { context, page } = await newPage(browser, DESKTOP_VIEWPORT);
34920
+ await blockConsentManagers(page);
34921
+ await installPageRuntime(page);
34922
+ log(`loading ${options.url}`);
34923
+ const prepared = await preparePage(page, options.url, timeoutMs);
34924
+ const blocked = detectBlockedPage({
34925
+ status: prepared.status,
34926
+ title: prepared.title,
34927
+ bodyText: prepared.bodyText,
34928
+ html: await page.content().catch(() => "")
34929
+ });
34930
+ if (blocked) throw new BlockedPageError(blocked);
34931
+ await mkdir11(sectionsDir, { recursive: true });
34932
+ log("segmenting");
34933
+ const candidates = await segmentPage(page, DEFAULT_SEGMENT_OPTIONS);
34934
+ log(`found ${candidates.length} sections`);
34935
+ const sections = [];
34936
+ for (const candidate of candidates) {
34937
+ const section = await captureOneSection({
34938
+ browser,
34939
+ page,
34940
+ candidate,
34941
+ sectionsDir,
34942
+ outDir: options.outDir,
34943
+ pageUrl: prepared.finalUrl,
34944
+ withCode: options.code !== false
34945
+ });
34946
+ sections.push(section);
34947
+ log(
34948
+ ` [${candidate.index}] ${candidate.rect.width}x${candidate.rect.height}${section.fidelity === null ? "" : ` fidelity=${section.fidelity.toFixed(2)}`} \u2014 ${candidate.textPreview.slice(0, 50)}`
34949
+ );
34950
+ }
34951
+ const fullPage = await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
34952
+ if (fullPage) await writeFile15(path31.join(options.outDir, "full-page.png"), fullPage);
34953
+ const reproduction = options.code === false ? { bundle: null, fidelity: null } : await reproducePage({
34954
+ browser,
34955
+ page,
34956
+ outDir: options.outDir,
34957
+ pageUrl: prepared.finalUrl,
34958
+ livePageShot: fullPage
34959
+ });
34960
+ log(`page reproduction: ${reproduction.fidelity === null ? "unavailable" : reproduction.fidelity.toFixed(2)}`);
34961
+ await context.close();
34962
+ if (options.mobile !== false) {
34963
+ log("capturing mobile");
34964
+ await captureMobileShots({
34965
+ browser,
34966
+ sections,
34967
+ sectionsDir,
34968
+ outDir: options.outDir,
34969
+ pageUrl: prepared.finalUrl,
34970
+ timeoutMs
34971
+ });
34972
+ }
34973
+ if (options.motion !== false) {
34974
+ await captureMotionTakes({
34975
+ browser,
34976
+ sections,
34977
+ sectionsDir,
34978
+ outDir: options.outDir,
34979
+ pageUrl: prepared.finalUrl,
34980
+ log
34981
+ });
34982
+ }
34983
+ const manifest = {
34984
+ url: options.url,
34985
+ finalUrl: prepared.finalUrl,
34986
+ title: prepared.title,
34987
+ documentHeight: prepared.documentHeight,
34988
+ viewport: DESKTOP_VIEWPORT,
34989
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
34990
+ sections,
34991
+ page: reproduction
34992
+ };
34993
+ await writeFile15(path31.join(options.outDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
34994
+ `);
34995
+ if (options.report !== false) {
34996
+ const reportPath = await writeCaptureReport(manifest, options.outDir);
34997
+ log(`report: ${reportPath}`);
34998
+ }
34999
+ return manifest;
35000
+ } finally {
35001
+ await browser.close();
35002
+ }
35003
+ }
35004
+
35005
+ // src/commands/landing/inspiration/scrape.ts
35006
+ async function recordCapture(manifest, outDir) {
35007
+ const consultedAt = (/* @__PURE__ */ new Date()).toISOString();
35008
+ const references = [];
35009
+ for (const section of manifest.sections) {
35010
+ if (!section.bundle) continue;
35011
+ try {
35012
+ const markup = await readFile23(path32.join(outDir, section.bundle), "utf8");
35013
+ const copyStrings = capturedCopyStrings(markup);
35014
+ if (copyStrings.length === 0) continue;
35015
+ references.push({
35016
+ sectionId: `local:${manifest.finalUrl}#${section.index}`,
35017
+ sourceUrl: manifest.finalUrl,
35018
+ copyStrings,
35019
+ consultedAt
35020
+ });
35021
+ } catch {
35022
+ }
35023
+ }
35024
+ return await recordReferences(process.cwd(), references);
35025
+ }
35026
+ registerSchema({
35027
+ command: "landing.inspiration.scrape",
35028
+ description: "Start here when the user points at a specific page and wants it now: capture one landing page into a directory of section screenshots, standalone HTML bundles, a whole-page reproduction and motion filmstrips. Returns when the capture is done, unlike `add`. Read the screenshots. Also records every captured section for the originality check.",
35029
+ args: {
35030
+ url: { type: "string", description: "Page to capture", required: true },
35031
+ out: { type: "string", description: "Output directory", required: true },
35032
+ mobile: { type: "boolean", description: "Phone-viewport pass. `--no-mobile` to skip", required: false },
35033
+ code: {
35034
+ type: "boolean",
35035
+ description: "Standalone HTML+CSS bundles and fidelity scores. `--no-code` to segment and screenshot only",
35036
+ required: false
35037
+ },
35038
+ motion: {
35039
+ type: "boolean",
35040
+ description: "Film sections that move. `--no-motion` to skip \u2014 roughly halves runtime",
35041
+ required: false
35042
+ },
35043
+ report: { type: "boolean", description: "Write report.html. `--no-report` to skip", required: false }
35044
+ }
35045
+ });
35046
+ var scrapeCommand = defineCommand146({
35047
+ meta: {
35048
+ name: "scrape",
35049
+ description: "Capture a landing page to a directory, now. Example: baker landing inspiration scrape https://linear.app --out .baker/inspiration/linear.app"
35050
+ },
35051
+ args: {
35052
+ url: { type: "positional", description: "Page to capture", required: true },
35053
+ out: { type: "string", description: "Output directory", required: true },
35054
+ mobile: {
35055
+ type: "boolean",
35056
+ description: "Phone-viewport pass (--no-mobile to skip)",
35057
+ required: false,
35058
+ default: true
35059
+ },
35060
+ code: { type: "boolean", description: "Bundles + fidelity (--no-code to skip)", required: false, default: true },
35061
+ motion: {
35062
+ type: "boolean",
35063
+ description: "Film moving sections (--no-motion to skip)",
35064
+ required: false,
35065
+ default: true
35066
+ },
35067
+ report: { type: "boolean", description: "Write report.html (--no-report to skip)", required: false, default: true }
35068
+ },
35069
+ run: async ({ args }) => {
35070
+ try {
35071
+ const manifest = await scrapeLanding({
35072
+ url: args.url,
35073
+ outDir: args.out,
35074
+ mobile: args.mobile,
35075
+ code: args.code,
35076
+ motion: args.motion,
35077
+ report: args.report,
35078
+ // Progress goes to stderr so stdout stays a clean JSON envelope.
35079
+ onProgress: (message) => process.stderr.write(`${message}
35080
+ `)
35081
+ });
35082
+ const scored = manifest.sections.map((section) => section.fidelity).filter((value) => value !== null).sort((a, b) => a - b);
35083
+ const recorded = await recordCapture(manifest, args.out);
35084
+ writeJson({
35085
+ ok: true,
35086
+ data: {
35087
+ title: manifest.title,
35088
+ sections: manifest.sections.length,
35089
+ medianFidelity: scored.length > 0 ? scored[Math.floor(scored.length / 2)] : null,
35090
+ pageFidelity: manifest.page.fidelity,
35091
+ out: args.out,
35092
+ report: args.report ? `${args.out}/report.html` : null
35093
+ },
35094
+ hints: [
35095
+ INSPIRATION_HINTS.structureNotCopy,
35096
+ INSPIRATION_HINTS.adapt,
35097
+ recorded ? "These sections are recorded for the originality check \u2014 `baker landing critique` will block a publish that ships their copy." : "Could not record this capture, so the originality check cannot see it. Be especially careful not to reuse its copy."
35098
+ ]
35099
+ });
35100
+ } catch (error) {
35101
+ if (error instanceof BlockedPageError) {
35102
+ writeJson({ ok: false, error: { code: error.code, message: error.message } });
35103
+ process.exit(1);
35104
+ }
35105
+ reportError(error);
35106
+ }
35107
+ }
35108
+ });
35109
+
35110
+ // src/commands/landing/inspiration/search.ts
35111
+ import { mkdir as mkdir12, writeFile as writeFile16 } from "fs/promises";
35112
+ import path33 from "path";
35113
+ import { defineCommand as defineCommand147 } from "citty";
35114
+ registerSchema({
35115
+ command: "landing.inspiration.search",
35116
+ description: "Search the shared library of real landing-page sections for reference before you design. Start here: baker landing inspiration search 'pricing with a monthly/annual toggle'. Returns the idea behind each section plus a downloaded screenshot you can Read. Defaults to this company's saved sections; pass --scope all for the whole library.",
35117
+ args: {
35118
+ query: { type: "string", description: "What you want to see, in plain English", required: false },
35119
+ type: {
35120
+ type: "string",
35121
+ description: "Comma list of section types: hero,pricing,faq,testimonials,\u2026",
35122
+ required: false
35123
+ },
35124
+ composition: {
35125
+ type: "string",
35126
+ description: "Comma list of composition patterns (references/composition.md)",
35127
+ required: false
35128
+ },
35129
+ register: {
35130
+ type: "string",
35131
+ description: "Comma list of visual registers: dev-tool-minimal,luxury-high-end,\u2026",
35132
+ required: false
35133
+ },
35134
+ interaction: { type: "string", description: "Comma list of interaction patterns", required: false },
35135
+ motion: {
35136
+ type: "string",
35137
+ description: "Comma list of motion kinds: scroll-reveal,marquee,parallax,\u2026",
35138
+ required: false
35139
+ },
35140
+ media: { type: "string", description: "Comma list of media kinds", required: false },
35141
+ device: { type: "string", description: "Comma list of content devices", required: false },
35142
+ theme: { type: "string", description: "light | dark | mixed", required: false },
35143
+ "max-rank": {
35144
+ type: "number",
35145
+ description: "Show>Tell rank ceiling 1-7; 3 means 'rank 3 or better'",
35146
+ required: false
35147
+ },
35148
+ "min-craft": { type: "number", description: "Craft floor 0-1", required: false },
35149
+ "min-fidelity": {
35150
+ type: "number",
35151
+ description: "Only sections whose markup reproduces this well (0-1)",
35152
+ required: false
35153
+ },
35154
+ domain: { type: "string", description: "Restrict to one site", required: false },
35155
+ "similar-to": {
35156
+ type: "string",
35157
+ description: "Section id \u2014 find sections that look like this one",
35158
+ required: false
35159
+ },
35160
+ scope: { type: "string", description: "favorites (default) | all", required: false },
35161
+ limit: { type: "number", description: "Max results (default 8)", required: false },
35162
+ "no-images": { type: "boolean", description: "Skip downloading screenshots", required: false }
35163
+ }
35164
+ });
35165
+ function buildSearchBody(args) {
35166
+ const body = {};
35167
+ const setList2 = (key, value) => {
35168
+ const list = splitList(value);
35169
+ if (list) Object.assign(body, { [key]: list });
35170
+ };
35171
+ if (args.query) body.query = String(args.query);
35172
+ if (args["similar-to"]) body.similarToSectionId = String(args["similar-to"]);
35173
+ setList2("sectionType", args.type);
35174
+ setList2("composition", args.composition);
35175
+ setList2("visualRegister", args.register);
35176
+ setList2("interaction", args.interaction);
35177
+ setList2("motion", args.motion);
35178
+ setList2("mediaKind", args.media);
35179
+ setList2("contentDevice", args.device);
35180
+ if (args.theme) body.theme = String(args.theme);
35181
+ if (args.domain) body.domain = String(args.domain);
35182
+ const maxRank = parseNumber(args["max-rank"]);
35183
+ if (maxRank !== void 0) body.maxShowTellRank = maxRank;
35184
+ const minCraft = parseNumber(args["min-craft"]);
35185
+ if (minCraft !== void 0) body.minCraftScore = minCraft;
35186
+ const minFidelity = parseNumber(args["min-fidelity"]);
35187
+ if (minFidelity !== void 0) body.minFidelity = minFidelity;
35188
+ const limit = parseNumber(args.limit);
35189
+ body.limit = limit ?? 8;
35190
+ body.scope = args.scope === "all" ? "all" : "favorites";
35191
+ return body;
35192
+ }
35193
+ async function downloadShots(results) {
35194
+ const dir = path33.join(process.cwd(), ".baker", "inspiration");
35195
+ await mkdir12(dir, { recursive: true });
35196
+ const saved = /* @__PURE__ */ new Map();
35197
+ await Promise.all(
35198
+ results.map(async (result) => {
35199
+ if (!result.desktopShotUrl) return;
35200
+ try {
35201
+ const response = await fetch(result.desktopShotUrl);
35202
+ if (!response.ok) return;
35203
+ const file = path33.join(dir, `${result.id}.png`);
35204
+ await writeFile16(file, Buffer.from(await response.arrayBuffer()));
35205
+ saved.set(result.id, path33.relative(process.cwd(), file));
35206
+ } catch {
35207
+ }
35208
+ })
35209
+ );
35210
+ return saved;
35211
+ }
35212
+ var searchCommand2 = defineCommand147({
35213
+ meta: {
35214
+ name: "search",
35215
+ description: "Search real landing-page sections for reference. Example: baker landing inspiration search 'dark developer hero with a terminal' --register dev-tool-minimal --scope all"
35216
+ },
35217
+ args: {
35218
+ query: { type: "positional", description: "What you want to see, in plain English", required: false },
35219
+ type: { type: "string", description: "Comma list of section types", required: false },
35220
+ composition: { type: "string", description: "Comma list of composition patterns", required: false },
35221
+ register: { type: "string", description: "Comma list of visual registers", required: false },
35222
+ interaction: { type: "string", description: "Comma list of interaction patterns", required: false },
35223
+ motion: { type: "string", description: "Comma list of motion kinds", required: false },
35224
+ media: { type: "string", description: "Comma list of media kinds", required: false },
35225
+ device: { type: "string", description: "Comma list of content devices", required: false },
35226
+ theme: { type: "string", description: "light | dark | mixed", required: false },
35227
+ "max-rank": { type: "string", description: "Show>Tell rank ceiling 1-7", required: false },
35228
+ "min-craft": { type: "string", description: "Craft floor 0-1", required: false },
35229
+ "min-fidelity": { type: "string", description: "Reproduction-fidelity floor 0-1", required: false },
35230
+ domain: { type: "string", description: "Restrict to one site", required: false },
35231
+ "similar-to": { type: "string", description: "Section id to find lookalikes of", required: false },
35232
+ scope: { type: "string", description: "favorites (default) | all", required: false, default: "favorites" },
35233
+ limit: { type: "string", description: "Max results (default 8)", required: false },
35234
+ "no-images": { type: "boolean", description: "Skip downloading screenshots", required: false, default: false },
35235
+ full: { type: "boolean", description: "Include every classification facet", required: false, default: false }
35236
+ },
35237
+ run: async ({ args }) => {
35238
+ try {
35239
+ const body = buildSearchBody(args);
35240
+ const data = await apiPost("/api/landing-inspiration/search", body);
35241
+ const results = Array.isArray(data?.results) ? data.results : [];
35242
+ const shots = args["no-images"] ? /* @__PURE__ */ new Map() : await downloadShots(results);
35243
+ const full = args.full;
35244
+ const rows = results.map((result) => ({
35245
+ id: result.id,
35246
+ section: result.sectionType,
35247
+ composition: result.composition,
35248
+ look: result.visualRegister,
35249
+ motion: result.motionSummary,
35250
+ why_it_works: result.whyItWorks,
35251
+ craft: Number(result.craftScore?.toFixed?.(2) ?? result.craftScore),
35252
+ fidelity: result.fidelity,
35253
+ domain: result.domain,
35254
+ // Only worth saying when it means something: >1 marks a section the site
35255
+ // reuses across its pages, which is a stronger signal than a one-off.
35256
+ ...result.pageCount > 1 ? { used_on_pages: result.pageCount } : {},
35257
+ screenshot: shots.get(result.id) ?? null,
35258
+ ...full ? {
35259
+ theme: result.theme,
35260
+ density: result.density,
35261
+ show_tell_rank: result.showTellRank,
35262
+ interactions: result.interactions,
35263
+ media: result.mediaKinds,
35264
+ devices: result.contentDevices,
35265
+ tags: result.tags,
35266
+ headline: result.headline,
35267
+ source_url: result.sourceUrl
35268
+ } : {}
35269
+ }));
35270
+ const hints = [INSPIRATION_HINTS.adapt];
35271
+ if (rows.length === 0) {
35272
+ hints.push(
35273
+ body.scope === "favorites" ? "No matches in this company's saved sections. Retry with --scope all to search the whole library, or add a page with `baker landing inspiration add <url>`." : "No matches. Loosen the filters, or add reference pages with `baker landing inspiration add <url>`."
35274
+ );
35275
+ } else if (data.moreInFullLibrary > 0) {
35276
+ hints.push(`${data.moreInFullLibrary} more match in the full library \u2014 re-run with --scope all.`);
35277
+ }
35278
+ if (shots.size > 0) hints.push(`Screenshots saved to .baker/inspiration/ \u2014 Read them before deciding.`);
35279
+ writeJson({
35280
+ ok: true,
35281
+ data: { results: rows, scope: data.scope, more_in_full_library: data.moreInFullLibrary, total: data.total },
35282
+ hints
35283
+ });
35284
+ } catch (error) {
35285
+ reportError(error);
35286
+ }
35287
+ }
35288
+ });
35289
+
35290
+ // src/commands/landing/inspiration/view.ts
35291
+ import { mkdir as mkdir13, writeFile as writeFile17 } from "fs/promises";
35292
+ import path34 from "path";
35293
+ import { defineCommand as defineCommand148 } from "citty";
35294
+ registerSchema({
35295
+ command: "landing.inspiration.view",
35296
+ description: "Everything known about one section: composition, motion, design tokens, the copy it uses, why it works, and what must change to make it yours. Downloads the desktop and mobile screenshots plus the motion filmstrip so you can look at them.",
35297
+ args: { id: { type: "string", description: "Section id from search", required: true } }
35298
+ });
35299
+ async function download(url, file) {
35300
+ if (!url) return null;
35301
+ try {
35302
+ const response = await fetch(url);
35303
+ if (!response.ok) return null;
35304
+ await mkdir13(path34.dirname(file), { recursive: true });
35305
+ await writeFile17(file, Buffer.from(await response.arrayBuffer()));
35306
+ return path34.relative(process.cwd(), file);
35307
+ } catch {
35308
+ return null;
35309
+ }
35310
+ }
35311
+ var viewCommand2 = defineCommand148({
35312
+ meta: {
35313
+ name: "view",
35314
+ description: "Full detail for one reference section. Example: baker landing inspiration view k57abc\u2026 \u2014 read the screenshots it saves before you build."
35315
+ },
35316
+ args: { id: { type: "positional", description: "Section id from search", required: true } },
35317
+ run: async ({ args }) => {
35318
+ try {
35319
+ const id = args.id;
35320
+ const data = await apiGet("/api/landing-inspiration/section", { id });
35321
+ const section = data.section;
35322
+ const dir = path34.join(process.cwd(), ".baker", "inspiration", id);
35323
+ const [desktop, mobile, filmstrip] = await Promise.all([
35324
+ download(section.desktopShotUrl, path34.join(dir, "desktop.png")),
35325
+ download(section.mobileShotUrl, path34.join(dir, "mobile.png")),
35326
+ download(section.motionFilmstripUrl, path34.join(dir, "motion-filmstrip.png"))
35327
+ ]);
35328
+ const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
35329
+ const fidelity = fidelityHint(section.fidelity);
35330
+ if (fidelity) hints.push(fidelity);
35331
+ if (filmstrip) {
35332
+ hints.push(
35333
+ "motion-filmstrip.png shows this section animating in, six frames left-to-right then top-to-bottom, with the elapsed time on each. Read it if you want the motion, not just the layout."
35334
+ );
35335
+ }
35336
+ writeJson({
35337
+ ok: true,
35338
+ data: {
35339
+ id: section.id,
35340
+ section: section.sectionType,
35341
+ composition: section.composition,
35342
+ look: section.visualRegister,
35343
+ theme: section.theme,
35344
+ density: section.density,
35345
+ show_tell_rank: section.showTellRank,
35346
+ interactions: section.interactions,
35347
+ media: section.mediaKinds,
35348
+ devices: section.contentDevices,
35349
+ tags: section.tags,
35350
+ motion: section.motionSummary,
35351
+ design_tokens: section.designTokens,
35352
+ size: section.boundingBox,
35353
+ copy: {
35354
+ headline: section.headline,
35355
+ subhead: section.subhead,
35356
+ ctas: section.ctaLabels,
35357
+ proof: section.proofSignals
35358
+ },
35359
+ why_it_works: section.whyItWorks,
35360
+ adaptation_notes: section.adaptationNotes,
35361
+ reproduction_notes: section.reproductionNotes,
35362
+ craft: section.craftScore,
35363
+ fidelity: section.fidelity,
35364
+ source_url: section.sourceUrl,
35365
+ screenshots: { desktop, mobile, motion_filmstrip: filmstrip }
35366
+ },
35367
+ hints
35368
+ });
35369
+ } catch (error) {
35370
+ reportError(error);
35371
+ }
35372
+ }
35373
+ });
35374
+
35375
+ // src/commands/landing/inspiration/index.ts
35376
+ var inspirationCommand = defineCommand149({
35377
+ meta: {
35378
+ name: "inspiration",
35379
+ description: `Reference library of real landing-page sections \u2014 look at how good pages actually solve a problem before you design one.
35380
+
35381
+ Start here: \`baker landing inspiration search "<what you want to see>"\` during research, BEFORE you write the Direction Contract.
35382
+
35383
+ This is inspiration, never a clipboard. Take the mechanism \u2014 what the eye hits first, what proof arrives before the ask, how the grid is split. The words are never yours to reuse: shipping a reference's headline is a Tier 0 message-match failure and \`baker landing critique\` will block the publish.
35384
+
35385
+ Subcommands:
35386
+ baker landing inspiration search "<query>" \u2014 search by look, section type, composition, register or motion; saves screenshots you can Read
35387
+ baker landing inspiration view <id> \u2014 one section in full: tokens, motion filmstrip, why it works, what to change
35388
+ baker landing inspiration code <id> \u2014 its standalone HTML+CSS, for structure only
35389
+ baker landing inspiration page <id> \u2014 a whole page as a sequence: how it orders its sections
35390
+ baker landing inspiration add <url> \u2014 add a page to the library and save it to this company (returns immediately)
35391
+ baker landing inspiration favorites \u2014 what this company has saved; the default search scope
35392
+ baker landing inspiration favorite <id> \u2014 save a section
35393
+ baker landing inspiration unfavorite <id> \u2014 unsave a section
35394
+ baker landing inspiration scrape <url> \u2014 internal/ops: run the capture locally
35395
+
35396
+ Examples:
35397
+ baker landing inspiration search "pricing with a monthly/annual toggle" --max-rank 3
35398
+ baker landing inspiration search "dark developer hero with a terminal" --register dev-tool-minimal --scope all
35399
+ baker landing inspiration search "testimonial wall with faces and company logos" --motion scroll-reveal
35400
+ baker landing inspiration add https://linear.app
35401
+
35402
+ Full guide: __tooling__/docs/tools/baker/landing.md`
35403
+ },
35404
+ subCommands: {
35405
+ search: searchCommand2,
35406
+ view: viewCommand2,
35407
+ code: codeCommand,
35408
+ page: pageCommand,
35409
+ add: addCommand,
35410
+ favorites: favoritesCommand,
35411
+ favorite: favoriteCommand,
35412
+ unfavorite: unfavoriteCommand,
35413
+ scrape: scrapeCommand
35414
+ }
35415
+ });
35416
+
32975
35417
  // src/commands/landing/index.ts
32976
- var landingCommand = defineCommand142({
35418
+ var landingCommand = defineCommand150({
32977
35419
  meta: {
32978
35420
  name: "landing",
32979
35421
  description: `Design-quality tools for landing pages (src/pages/<slug>/).
@@ -32981,15 +35423,17 @@ var landingCommand = defineCommand142({
32981
35423
  Start here: \`baker landing critique <slug>\` after building or editing a landing.
32982
35424
 
32983
35425
  Subcommands:
35426
+ baker landing inspiration \u2014 reference library of real landing-page sections; search it during research, BEFORE writing the Direction Contract. Inspiration only: take the mechanism, never the words.
32984
35427
  baker landing critique <slug> \u2014 deterministic design-quality critic (advisory): flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) tiered block/warn/advisory, respecting the client's BRAND.md. Records the critique the publish quality gate requires \u2014 run it before finishing a landing.`
32985
35428
  },
32986
35429
  subCommands: {
35430
+ inspiration: inspirationCommand,
32987
35431
  critique: critiqueCommand2
32988
35432
  }
32989
35433
  });
32990
35434
 
32991
35435
  // src/commands/mcp/index.ts
32992
- import { defineCommand as defineCommand143 } from "citty";
35436
+ import { defineCommand as defineCommand151 } from "citty";
32993
35437
 
32994
35438
  // src/commands/mcp/platforms.ts
32995
35439
  function readsKey(label) {
@@ -33058,7 +35502,7 @@ registerSchema({
33058
35502
  description: "List everything this chat can reach: managed integrations (Attio, Slack, Gmail, Google Sheets, \u2026), custom MCP servers, and the platforms the company signed in to (HubSpot, Google Ads, GA4, Search Console, Tag Manager) which you read through their own `baker` commands. Start here when the user mentions an external tool or platform.",
33059
35503
  args: {}
33060
35504
  });
33061
- var connectedCommand = defineCommand143({
35505
+ var connectedCommand = defineCommand151({
33062
35506
  meta: {
33063
35507
  name: "connected",
33064
35508
  description: `Everything this chat can reach \u2014 managed integrations, custom MCP servers, and connected platforms.
@@ -33117,7 +35561,7 @@ registerSchema({
33117
35561
  description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
33118
35562
  args: {}
33119
35563
  });
33120
- var listCommand13 = defineCommand143({
35564
+ var listCommand13 = defineCommand151({
33121
35565
  meta: { name: "list", description: "List custom MCP servers visible to this company's chats." },
33122
35566
  run: async () => {
33123
35567
  try {
@@ -33154,7 +35598,7 @@ registerSchema({
33154
35598
  header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
33155
35599
  }
33156
35600
  });
33157
- var addCommand = defineCommand143({
35601
+ var addCommand2 = defineCommand151({
33158
35602
  meta: {
33159
35603
  name: "add",
33160
35604
  description: `Register a custom MCP server. Tools appear as mcp__<name>__* on the NEXT message.
@@ -33206,7 +35650,7 @@ registerSchema({
33206
35650
  description: "Remove a company custom MCP server by name.",
33207
35651
  args: { name: { type: "string", description: "Server name to remove", required: true } }
33208
35652
  });
33209
- var removeCommand4 = defineCommand143({
35653
+ var removeCommand4 = defineCommand151({
33210
35654
  meta: {
33211
35655
  name: "remove",
33212
35656
  description: `Remove a company custom MCP server by name.
@@ -33228,7 +35672,7 @@ Example:
33228
35672
  }
33229
35673
  }
33230
35674
  });
33231
- var mcpCommand = defineCommand143({
35675
+ var mcpCommand = defineCommand151({
33232
35676
  meta: {
33233
35677
  name: "mcp",
33234
35678
  description: `Third-party tools for this company \u2014 see what's connected, register custom HTTPS MCP endpoints.
@@ -33248,16 +35692,16 @@ Full guide: __tooling__/docs/tools/baker/mcp.md`
33248
35692
  subCommands: {
33249
35693
  connected: connectedCommand,
33250
35694
  list: listCommand13,
33251
- add: addCommand,
35695
+ add: addCommand2,
33252
35696
  remove: removeCommand4
33253
35697
  }
33254
35698
  });
33255
35699
 
33256
35700
  // src/commands/research/index.ts
33257
- import { defineCommand as defineCommand154 } from "citty";
35701
+ import { defineCommand as defineCommand162 } from "citty";
33258
35702
 
33259
35703
  // src/commands/research/advertisers.ts
33260
- import { defineCommand as defineCommand144 } from "citty";
35704
+ import { defineCommand as defineCommand152 } from "citty";
33261
35705
 
33262
35706
  // src/commands/research/output.ts
33263
35707
  var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
@@ -33370,7 +35814,7 @@ var FIELDS3 = {
33370
35814
  etv: "Estimated traffic value (USD)",
33371
35815
  visibility: "SERP visibility score (0-1)"
33372
35816
  };
33373
- var advertisersCommand = defineCommand144({
35817
+ var advertisersCommand = defineCommand152({
33374
35818
  meta: {
33375
35819
  name: "advertisers",
33376
35820
  description: `Find domains competing for a keyword in Google SERPs.
@@ -33390,15 +35834,15 @@ Examples:
33390
35834
  },
33391
35835
  run: async ({ args }) => {
33392
35836
  const keyword = args.keyword;
33393
- const location = args.location || void 0;
35837
+ const location2 = args.location || void 0;
33394
35838
  const language = args.language || void 0;
33395
35839
  const limit = args.limit ? Number(args.limit) : void 0;
33396
35840
  const skipCache = args["no-cache"] ? true : void 0;
33397
- const queryContext = buildResearchQueryContext(location, language);
35841
+ const queryContext = buildResearchQueryContext(location2, language);
33398
35842
  try {
33399
35843
  const data = await apiPost("/api/research/advertisers", {
33400
35844
  keyword,
33401
- location,
35845
+ location: location2,
33402
35846
  language,
33403
35847
  limit,
33404
35848
  skipCache
@@ -33417,7 +35861,7 @@ Examples:
33417
35861
  });
33418
35862
 
33419
35863
  // src/commands/research/autocomplete.ts
33420
- import { defineCommand as defineCommand145 } from "citty";
35864
+ import { defineCommand as defineCommand153 } from "citty";
33421
35865
  registerSchema({
33422
35866
  command: "research.autocomplete",
33423
35867
  description: "Get Google Autocomplete suggestions for a seed keyword. Useful for keyword expansion and discovering what people actually search for. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en).",
@@ -33440,7 +35884,7 @@ registerSchema({
33440
35884
  var FIELDS4 = {
33441
35885
  suggestion: "Autocomplete suggestion from Google"
33442
35886
  };
33443
- var autocompleteCommand = defineCommand145({
35887
+ var autocompleteCommand = defineCommand153({
33444
35888
  meta: {
33445
35889
  name: "autocomplete",
33446
35890
  description: `Get Google Autocomplete suggestions for keyword expansion.
@@ -33459,15 +35903,15 @@ Examples:
33459
35903
  },
33460
35904
  run: async ({ args }) => {
33461
35905
  const keyword = args.keyword;
33462
- const location = args.location || void 0;
35906
+ const location2 = args.location || void 0;
33463
35907
  const language = args.language || void 0;
33464
35908
  const limit = args.limit ? Number(args.limit) : void 0;
33465
35909
  const skipCache = args["no-cache"] ? true : void 0;
33466
- const queryContext = buildResearchQueryContext(location, language);
35910
+ const queryContext = buildResearchQueryContext(location2, language);
33467
35911
  try {
33468
35912
  const data = await apiPost("/api/research/autocomplete", {
33469
35913
  keyword,
33470
- location,
35914
+ location: location2,
33471
35915
  language,
33472
35916
  limit,
33473
35917
  skipCache
@@ -33486,7 +35930,7 @@ Examples:
33486
35930
  });
33487
35931
 
33488
35932
  // src/commands/research/countries.ts
33489
- import { defineCommand as defineCommand146 } from "citty";
35933
+ import { defineCommand as defineCommand154 } from "citty";
33490
35934
  registerSchema({
33491
35935
  command: "research.countries",
33492
35936
  description: "List all supported country codes for --location flag in research commands.",
@@ -33543,7 +35987,7 @@ var FIELDS5 = {
33543
35987
  code: "Country code to pass as --location",
33544
35988
  name: "Country name"
33545
35989
  };
33546
- var countriesCommand = defineCommand146({
35990
+ var countriesCommand = defineCommand154({
33547
35991
  meta: {
33548
35992
  name: "countries",
33549
35993
  description: "List all supported country codes for --location flag."
@@ -33554,7 +35998,7 @@ var countriesCommand = defineCommand146({
33554
35998
  });
33555
35999
 
33556
36000
  // src/commands/research/intent.ts
33557
- import { defineCommand as defineCommand147 } from "citty";
36001
+ import { defineCommand as defineCommand155 } from "citty";
33558
36002
  registerSchema({
33559
36003
  command: "research.intent",
33560
36004
  description: "Classify Google Search intent for keywords. Determines if someone searching is looking to buy, research, or navigate. IMPORTANT: If --language is omitted, defaults to English (en). The response includes a query_context object showing which language was used.",
@@ -33577,7 +36021,7 @@ var FIELDS6 = {
33577
36021
  intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
33578
36022
  probability: "Confidence score 0.0-1.0"
33579
36023
  };
33580
- var intentCommand = defineCommand147({
36024
+ var intentCommand = defineCommand155({
33581
36025
  meta: {
33582
36026
  name: "intent",
33583
36027
  description: `Classify Google Search intent for keywords. Returns intent type and confidence.
@@ -33625,7 +36069,7 @@ Examples:
33625
36069
  });
33626
36070
 
33627
36071
  // src/commands/research/keyword-gap.ts
33628
- import { defineCommand as defineCommand148 } from "citty";
36072
+ import { defineCommand as defineCommand156 } from "citty";
33629
36073
  registerSchema({
33630
36074
  command: "research.keyword-gap",
33631
36075
  description: "Find keywords a competitor ranks for (organic or paid) that you don't. Discovers expansion opportunities. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
@@ -33654,7 +36098,7 @@ var FIELDS7 = {
33654
36098
  cpc: "Cost per click USD",
33655
36099
  their_position: "Competitor's ranking position"
33656
36100
  };
33657
- var keywordGapCommand = defineCommand148({
36101
+ var keywordGapCommand = defineCommand156({
33658
36102
  meta: {
33659
36103
  name: "keyword-gap",
33660
36104
  description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
@@ -33678,18 +36122,18 @@ Examples:
33678
36122
  run: async ({ args }) => {
33679
36123
  const competitor = args.competitor;
33680
36124
  const ours = args.ours;
33681
- const location = args.location || void 0;
36125
+ const location2 = args.location || void 0;
33682
36126
  const language = args.language || void 0;
33683
36127
  const type = args.type || void 0;
33684
36128
  const limit = args.limit ? Number(args.limit) : void 0;
33685
36129
  const offset = args.offset ? Number(args.offset) : void 0;
33686
36130
  const skipCache = args["no-cache"] ? true : void 0;
33687
- const queryContext = buildResearchQueryContext(location, language);
36131
+ const queryContext = buildResearchQueryContext(location2, language);
33688
36132
  try {
33689
36133
  const result = await apiPost("/api/research/keyword-gap", {
33690
36134
  competitor,
33691
36135
  ours,
33692
- location,
36136
+ location: location2,
33693
36137
  language,
33694
36138
  type,
33695
36139
  limit,
@@ -33728,7 +36172,7 @@ Examples:
33728
36172
  });
33729
36173
 
33730
36174
  // src/commands/research/keywords-for-site.ts
33731
- import { defineCommand as defineCommand149 } from "citty";
36175
+ import { defineCommand as defineCommand157 } from "citty";
33732
36176
  registerSchema({
33733
36177
  command: "research.keywords-for-site",
33734
36178
  description: "Get keywords a competitor targets in Google. Use --type paid to see only paid keywords, --type organic for organic only. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
@@ -33761,7 +36205,7 @@ var FIELDS8 = {
33761
36205
  competition: "LOW, MEDIUM, or HIGH",
33762
36206
  competition_index: "Competition score 0-100"
33763
36207
  };
33764
- var keywordsForSiteCommand = defineCommand149({
36208
+ var keywordsForSiteCommand = defineCommand157({
33765
36209
  meta: {
33766
36210
  name: "keywords-for-site",
33767
36211
  description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
@@ -33783,17 +36227,17 @@ Examples:
33783
36227
  },
33784
36228
  run: async ({ args }) => {
33785
36229
  const target = args.target;
33786
- const location = args.location || void 0;
36230
+ const location2 = args.location || void 0;
33787
36231
  const language = args.language || void 0;
33788
36232
  const sort = args.sort || void 0;
33789
36233
  const type = args.type || void 0;
33790
36234
  const limit = args.limit ? Number(args.limit) : void 0;
33791
36235
  const skipCache = args["no-cache"] ? true : void 0;
33792
- const queryContext = buildResearchQueryContext(location, language);
36236
+ const queryContext = buildResearchQueryContext(location2, language);
33793
36237
  try {
33794
36238
  const data = await apiPost("/api/research/keywords-for-site", {
33795
36239
  target,
33796
- location,
36240
+ location: location2,
33797
36241
  language,
33798
36242
  sort,
33799
36243
  type,
@@ -33814,7 +36258,7 @@ Examples:
33814
36258
  });
33815
36259
 
33816
36260
  // src/commands/research/languages.ts
33817
- import { defineCommand as defineCommand150 } from "citty";
36261
+ import { defineCommand as defineCommand158 } from "citty";
33818
36262
  registerSchema({
33819
36263
  command: "research.languages",
33820
36264
  description: "List all supported language codes for --language flag in research commands.",
@@ -33844,7 +36288,7 @@ var FIELDS9 = {
33844
36288
  code: "Language code to pass as --language",
33845
36289
  name: "Language name (also accepted by --language)"
33846
36290
  };
33847
- var languagesCommand2 = defineCommand150({
36291
+ var languagesCommand2 = defineCommand158({
33848
36292
  meta: {
33849
36293
  name: "languages",
33850
36294
  description: "List all supported language codes for --language flag."
@@ -33855,7 +36299,7 @@ var languagesCommand2 = defineCommand150({
33855
36299
  });
33856
36300
 
33857
36301
  // src/commands/research/lighthouse.ts
33858
- import { defineCommand as defineCommand151 } from "citty";
36302
+ import { defineCommand as defineCommand159 } from "citty";
33859
36303
  registerSchema({
33860
36304
  command: "research.lighthouse",
33861
36305
  description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
@@ -33874,7 +36318,7 @@ var FIELDS10 = {
33874
36318
  speed_index_ms: "Speed Index in ms (good: < 3400)",
33875
36319
  interactive_ms: "Time to Interactive in ms (good: < 3800)"
33876
36320
  };
33877
- var lighthouseCommand = defineCommand151({
36321
+ var lighthouseCommand = defineCommand159({
33878
36322
  meta: {
33879
36323
  name: "lighthouse",
33880
36324
  description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
@@ -33912,7 +36356,7 @@ Examples:
33912
36356
  });
33913
36357
 
33914
36358
  // src/commands/research/relevant-pages.ts
33915
- import { defineCommand as defineCommand152 } from "citty";
36359
+ import { defineCommand as defineCommand160 } from "citty";
33916
36360
  registerSchema({
33917
36361
  command: "research.relevant-pages",
33918
36362
  description: "Get the top pages of a competitor domain with organic traffic and ranking data. Shows which pages drive the most traffic. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en).",
@@ -33938,7 +36382,7 @@ var FIELDS11 = {
33938
36382
  keywords: "Total organic keywords the page ranks for",
33939
36383
  top_10: "Keywords in positions 1-10"
33940
36384
  };
33941
- var relevantPagesCommand = defineCommand152({
36385
+ var relevantPagesCommand = defineCommand160({
33942
36386
  meta: {
33943
36387
  name: "relevant-pages",
33944
36388
  description: `Get the top pages of a competitor domain with traffic data.
@@ -33957,15 +36401,15 @@ Examples:
33957
36401
  },
33958
36402
  run: async ({ args }) => {
33959
36403
  const target = args.target;
33960
- const location = args.location || void 0;
36404
+ const location2 = args.location || void 0;
33961
36405
  const language = args.language || void 0;
33962
36406
  const limit = args.limit ? Number(args.limit) : void 0;
33963
36407
  const skipCache = args["no-cache"] ? true : void 0;
33964
- const queryContext = buildResearchQueryContext(location, language);
36408
+ const queryContext = buildResearchQueryContext(location2, language);
33965
36409
  try {
33966
36410
  const data = await apiPost("/api/research/relevant-pages", {
33967
36411
  target,
33968
- location,
36412
+ location: location2,
33969
36413
  language,
33970
36414
  limit,
33971
36415
  skipCache
@@ -33984,7 +36428,7 @@ Examples:
33984
36428
  });
33985
36429
 
33986
36430
  // src/commands/research/web.ts
33987
- import { defineCommand as defineCommand153 } from "citty";
36431
+ import { defineCommand as defineCommand161 } from "citty";
33988
36432
  registerSchema({
33989
36433
  command: "research.web",
33990
36434
  description: "Search the web with AI to answer marketing questions \u2014 competitors, ICP, pricing, pain points, market trends. Three depth levels: medium (quick, default), high (thorough), xhigh (exhaustive deep research).",
@@ -34035,7 +36479,7 @@ async function runDeepResearch(question) {
34035
36479
  }
34036
36480
  throw new Error("Deep research timed out");
34037
36481
  }
34038
- var webCommand = defineCommand153({
36482
+ var webCommand = defineCommand161({
34039
36483
  meta: {
34040
36484
  name: "web",
34041
36485
  description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
@@ -34095,7 +36539,7 @@ Examples:
34095
36539
  });
34096
36540
 
34097
36541
  // src/commands/research/index.ts
34098
- var researchCommand = defineCommand154({
36542
+ var researchCommand = defineCommand162({
34099
36543
  meta: {
34100
36544
  name: "research",
34101
36545
  description: `Competitive intelligence and AI-powered research commands.
@@ -34136,10 +36580,10 @@ Full guide: __tooling__/docs/tools/baker/research.md`
34136
36580
  });
34137
36581
 
34138
36582
  // src/commands/scheduled-actions/index.ts
34139
- import { defineCommand as defineCommand161 } from "citty";
36583
+ import { defineCommand as defineCommand169 } from "citty";
34140
36584
 
34141
36585
  // src/commands/scheduled-actions/create.ts
34142
- import { defineCommand as defineCommand155 } from "citty";
36586
+ import { defineCommand as defineCommand163 } from "citty";
34143
36587
 
34144
36588
  // src/commands/scheduled-actions/shared.ts
34145
36589
  var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
@@ -34254,7 +36698,7 @@ registerSchema({
34254
36698
  prompt: { type: "string", description: "Additional prompt instructions for the spawned agent", required: false }
34255
36699
  }
34256
36700
  });
34257
- var createCommand2 = defineCommand155({
36701
+ var createCommand2 = defineCommand163({
34258
36702
  meta: {
34259
36703
  name: "create",
34260
36704
  description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
@@ -34303,7 +36747,7 @@ var createCommand2 = defineCommand155({
34303
36747
  });
34304
36748
 
34305
36749
  // src/commands/scheduled-actions/delete.ts
34306
- import { defineCommand as defineCommand156 } from "citty";
36750
+ import { defineCommand as defineCommand164 } from "citty";
34307
36751
  registerSchema({
34308
36752
  command: "scheduled-actions.delete",
34309
36753
  description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
@@ -34311,7 +36755,7 @@ registerSchema({
34311
36755
  id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
34312
36756
  }
34313
36757
  });
34314
- var deleteCommand2 = defineCommand156({
36758
+ var deleteCommand2 = defineCommand164({
34315
36759
  meta: {
34316
36760
  name: "delete",
34317
36761
  description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
@@ -34340,7 +36784,7 @@ var deleteCommand2 = defineCommand156({
34340
36784
  });
34341
36785
 
34342
36786
  // src/commands/scheduled-actions/get.ts
34343
- import { defineCommand as defineCommand157 } from "citty";
36787
+ import { defineCommand as defineCommand165 } from "citty";
34344
36788
  registerSchema({
34345
36789
  command: "scheduled-actions.get",
34346
36790
  description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
@@ -34349,7 +36793,7 @@ registerSchema({
34349
36793
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
34350
36794
  }
34351
36795
  });
34352
- var getCommand3 = defineCommand157({
36796
+ var getCommand3 = defineCommand165({
34353
36797
  meta: {
34354
36798
  name: "get",
34355
36799
  description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
@@ -34388,7 +36832,7 @@ var getCommand3 = defineCommand157({
34388
36832
  });
34389
36833
 
34390
36834
  // src/commands/scheduled-actions/list.ts
34391
- import { defineCommand as defineCommand158 } from "citty";
36835
+ import { defineCommand as defineCommand166 } from "citty";
34392
36836
  registerSchema({
34393
36837
  command: "scheduled-actions.list",
34394
36838
  description: "List published scheduled actions. Includes draft state when BAKER_CHAT_ID is set, or --chat <id> to read an earlier chat's staged schedules instead.",
@@ -34396,7 +36840,7 @@ registerSchema({
34396
36840
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
34397
36841
  }
34398
36842
  });
34399
- var listCommand14 = defineCommand158({
36843
+ var listCommand14 = defineCommand166({
34400
36844
  meta: {
34401
36845
  name: "list",
34402
36846
  description: "List scheduled actions. Includes staged draft ops when BAKER_CHAT_ID is set, or --chat <id> to read an earlier chat's staged schedules instead."
@@ -34419,7 +36863,7 @@ var listCommand14 = defineCommand158({
34419
36863
  });
34420
36864
 
34421
36865
  // src/commands/scheduled-actions/trigger.ts
34422
- import { defineCommand as defineCommand159 } from "citty";
36866
+ import { defineCommand as defineCommand167 } from "citty";
34423
36867
  registerSchema({
34424
36868
  command: "scheduled-actions.trigger",
34425
36869
  description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
@@ -34427,7 +36871,7 @@ registerSchema({
34427
36871
  id: { type: "string", description: "Published scheduled action ID", required: true }
34428
36872
  }
34429
36873
  });
34430
- var triggerCommand = defineCommand159({
36874
+ var triggerCommand = defineCommand167({
34431
36875
  meta: {
34432
36876
  name: "trigger",
34433
36877
  description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
@@ -34464,7 +36908,7 @@ var triggerCommand = defineCommand159({
34464
36908
  });
34465
36909
 
34466
36910
  // src/commands/scheduled-actions/update.ts
34467
- import { defineCommand as defineCommand160 } from "citty";
36911
+ import { defineCommand as defineCommand168 } from "citty";
34468
36912
  registerSchema({
34469
36913
  command: "scheduled-actions.update",
34470
36914
  description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
@@ -34489,7 +36933,7 @@ registerSchema({
34489
36933
  prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
34490
36934
  }
34491
36935
  });
34492
- var updateCommand2 = defineCommand160({
36936
+ var updateCommand2 = defineCommand168({
34493
36937
  meta: {
34494
36938
  name: "update",
34495
36939
  description: "Stage a scheduled action update. Example: baker scheduled-actions update <id> --enabled false"
@@ -34560,7 +37004,7 @@ var updateCommand2 = defineCommand160({
34560
37004
  });
34561
37005
 
34562
37006
  // src/commands/scheduled-actions/index.ts
34563
- var scheduledActionsCommand = defineCommand161({
37007
+ var scheduledActionsCommand = defineCommand169({
34564
37008
  meta: {
34565
37009
  name: "scheduled-actions",
34566
37010
  description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger.
@@ -34587,14 +37031,14 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
34587
37031
  });
34588
37032
 
34589
37033
  // src/commands/schema.ts
34590
- import { defineCommand as defineCommand162 } from "citty";
37034
+ import { defineCommand as defineCommand170 } from "citty";
34591
37035
  function narrowToFamily(commandName, available) {
34592
37036
  const segments = commandName.split(".");
34593
37037
  const prefix = segments[0] === "ads" && segments[1] ? `ads.${segments[1]}.` : `${segments[0]}.`;
34594
37038
  const siblings = available.filter((name) => name.startsWith(prefix));
34595
37039
  return siblings.length > 0 ? siblings : available;
34596
37040
  }
34597
- var schemaCommand = defineCommand162({
37041
+ var schemaCommand = defineCommand170({
34598
37042
  meta: {
34599
37043
  name: "schema",
34600
37044
  description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
@@ -34638,10 +37082,10 @@ var schemaCommand = defineCommand162({
34638
37082
  });
34639
37083
 
34640
37084
  // src/commands/studio/index.ts
34641
- import { defineCommand as defineCommand171 } from "citty";
37085
+ import { defineCommand as defineCommand179 } from "citty";
34642
37086
 
34643
37087
  // src/commands/studio/animate.ts
34644
- import { defineCommand as defineCommand163 } from "citty";
37088
+ import { defineCommand as defineCommand171 } from "citty";
34645
37089
 
34646
37090
  // src/commands/studio/batch.ts
34647
37091
  function projectBatch(generation, full) {
@@ -34830,7 +37274,7 @@ function parseImageRefs(spec) {
34830
37274
  }
34831
37275
  var defaultDeps = {
34832
37276
  ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
34833
- upload: (path28) => uploadLocalImage({ file: path28, contentType: detectImageContentType(path28), source: "uploaded" })
37277
+ upload: (path35) => uploadLocalImage({ file: path35, contentType: detectImageContentType(path35), source: "uploaded" })
34834
37278
  };
34835
37279
  async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
34836
37280
  const refs = parseImageRefs(spec);
@@ -34850,10 +37294,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
34850
37294
  }
34851
37295
  return { imageIds, added };
34852
37296
  }
34853
- function uploadFailure(path28) {
37297
+ function uploadFailure(path35) {
34854
37298
  return (error) => {
34855
37299
  if (error instanceof ApiError) throw error;
34856
- throw new ApiError("VALIDATION_ERROR", `Could not read "${path28}" as an image.`);
37300
+ throw new ApiError("VALIDATION_ERROR", `Could not read "${path35}" as an image.`);
34857
37301
  };
34858
37302
  }
34859
37303
 
@@ -35034,7 +37478,7 @@ function costHintsFor(body) {
35034
37478
  }
35035
37479
  return hints;
35036
37480
  }
35037
- var animateCommand = defineCommand163({
37481
+ var animateCommand = defineCommand171({
35038
37482
  meta: {
35039
37483
  name: "animate",
35040
37484
  description: "Turn a still into a clip. The image fixes the look, so the prompt describes MOVEMENT \u2014 what the camera does, what the subject does, in what order.\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\nExamples:\n baker studio animate 'slow push in, model turns to camera and smiles' --image j57abc123def456ghi789\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"
@@ -35137,7 +37581,7 @@ var animateCommand = defineCommand163({
35137
37581
  });
35138
37582
 
35139
37583
  // src/commands/studio/generate.ts
35140
- import { defineCommand as defineCommand164 } from "citty";
37584
+ import { defineCommand as defineCommand172 } from "citty";
35141
37585
  var MODEL_LIST2 = IMAGE_MODEL_IDS;
35142
37586
  var DEFAULT_MAX_WAIT_MS2 = 24e4;
35143
37587
  registerSchema({
@@ -35267,7 +37711,7 @@ function buildGenerateBody(args, prompt) {
35267
37711
  }
35268
37712
  return body;
35269
37713
  }
35270
- var generateCommand = defineCommand164({
37714
+ var generateCommand = defineCommand172({
35271
37715
  meta: {
35272
37716
  name: "generate",
35273
37717
  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]]'"
@@ -35345,7 +37789,7 @@ var generateCommand = defineCommand164({
35345
37789
  });
35346
37790
 
35347
37791
  // src/commands/studio/get.ts
35348
- import { defineCommand as defineCommand165 } from "citty";
37792
+ import { defineCommand as defineCommand173 } from "citty";
35349
37793
  registerSchema({
35350
37794
  command: "studio.get",
35351
37795
  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.",
@@ -35354,7 +37798,7 @@ registerSchema({
35354
37798
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
35355
37799
  }
35356
37800
  });
35357
- var getCommand4 = defineCommand165({
37801
+ var getCommand4 = defineCommand173({
35358
37802
  meta: {
35359
37803
  name: "get",
35360
37804
  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"
@@ -35383,7 +37827,7 @@ var getCommand4 = defineCommand165({
35383
37827
  });
35384
37828
 
35385
37829
  // src/commands/studio/improve.ts
35386
- import { defineCommand as defineCommand166 } from "citty";
37830
+ import { defineCommand as defineCommand174 } from "citty";
35387
37831
  var DESCRIPTION = "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.";
35388
37832
  registerSchema({
35389
37833
  command: "studio.improve",
@@ -35408,7 +37852,7 @@ registerSchema({
35408
37852
  }
35409
37853
  }
35410
37854
  });
35411
- var improveCommand = defineCommand166({
37855
+ var improveCommand = defineCommand174({
35412
37856
  meta: {
35413
37857
  name: "improve",
35414
37858
  description: `${DESCRIPTION}
@@ -35452,7 +37896,7 @@ Examples:
35452
37896
  });
35453
37897
 
35454
37898
  // src/commands/studio/keep.ts
35455
- import { defineCommand as defineCommand167 } from "citty";
37899
+ import { defineCommand as defineCommand175 } from "citty";
35456
37900
  registerSchema({
35457
37901
  command: "studio.keep",
35458
37902
  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.",
@@ -35462,7 +37906,7 @@ registerSchema({
35462
37906
  undo: { type: "boolean", description: "Un-star an image, or take a kept clip back out", required: false }
35463
37907
  }
35464
37908
  });
35465
- var keepCommand = defineCommand167({
37909
+ var keepCommand = defineCommand175({
35466
37910
  meta: {
35467
37911
  name: "keep",
35468
37912
  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"
@@ -35513,7 +37957,7 @@ var keepCommand = defineCommand167({
35513
37957
  });
35514
37958
 
35515
37959
  // src/commands/studio/list.ts
35516
- import { defineCommand as defineCommand168 } from "citty";
37960
+ import { defineCommand as defineCommand176 } from "citty";
35517
37961
  registerSchema({
35518
37962
  command: "studio.list",
35519
37963
  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.",
@@ -35524,7 +37968,7 @@ registerSchema({
35524
37968
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
35525
37969
  }
35526
37970
  });
35527
- var listCommand15 = defineCommand168({
37971
+ var listCommand15 = defineCommand176({
35528
37972
  meta: {
35529
37973
  name: "list",
35530
37974
  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"
@@ -35558,7 +38002,7 @@ var listCommand15 = defineCommand168({
35558
38002
  });
35559
38003
 
35560
38004
  // src/commands/studio/models.ts
35561
- import { defineCommand as defineCommand169 } from "citty";
38005
+ import { defineCommand as defineCommand177 } from "citty";
35562
38006
  var DESCRIPTION2 = "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.";
35563
38007
  registerSchema({
35564
38008
  command: "studio.models",
@@ -35639,7 +38083,7 @@ function buildModelCards(kind, model) {
35639
38083
  const selected = model ? ids.filter((id) => id === model) : ids;
35640
38084
  return selected.map((id) => build(id));
35641
38085
  }
35642
- var modelsCommand = defineCommand169({
38086
+ var modelsCommand = defineCommand177({
35643
38087
  meta: {
35644
38088
  name: "models",
35645
38089
  description: `${DESCRIPTION2}
@@ -35686,13 +38130,13 @@ Examples:
35686
38130
  });
35687
38131
 
35688
38132
  // src/commands/studio/skills.ts
35689
- import { defineCommand as defineCommand170 } from "citty";
38133
+ import { defineCommand as defineCommand178 } from "citty";
35690
38134
  registerSchema({
35691
38135
  command: "studio.skills",
35692
38136
  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.",
35693
38137
  args: {}
35694
38138
  });
35695
- var skillsCommand = defineCommand170({
38139
+ var skillsCommand = defineCommand178({
35696
38140
  meta: {
35697
38141
  name: "skills",
35698
38142
  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"
@@ -35716,7 +38160,7 @@ var skillsCommand = defineCommand170({
35716
38160
  });
35717
38161
 
35718
38162
  // src/commands/studio/index.ts
35719
- var studioCommand = defineCommand171({
38163
+ var studioCommand = defineCommand179({
35720
38164
  meta: {
35721
38165
  name: "studio",
35722
38166
  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.
@@ -35759,10 +38203,10 @@ Full guide: __tooling__/docs/tools/baker/studio.md`
35759
38203
  });
35760
38204
 
35761
38205
  // src/commands/tag-manager/index.ts
35762
- import { defineCommand as defineCommand175 } from "citty";
38206
+ import { defineCommand as defineCommand183 } from "citty";
35763
38207
 
35764
38208
  // src/commands/tag-manager/draft.ts
35765
- import { defineCommand as defineCommand172 } from "citty";
38209
+ import { defineCommand as defineCommand180 } from "citty";
35766
38210
 
35767
38211
  // src/commands/tag-manager/shared.ts
35768
38212
  import { readFileSync as readFileSync13 } from "fs";
@@ -35829,10 +38273,10 @@ async function stageOp4(op) {
35829
38273
  handleError4(err);
35830
38274
  }
35831
38275
  }
35832
- async function draftAction3(path28, body, chat) {
38276
+ async function draftAction3(path35, body, chat) {
35833
38277
  const chatId = resolveChatId(chat);
35834
38278
  try {
35835
- const data = await apiPost(path28, { chatId, ...body });
38279
+ const data = await apiPost(path35, { chatId, ...body });
35836
38280
  writeJsonEnvelope({ ok: true, data });
35837
38281
  return data;
35838
38282
  } catch (err) {
@@ -35885,13 +38329,13 @@ registerSchema({
35885
38329
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
35886
38330
  }
35887
38331
  });
35888
- var draftCommand4 = defineCommand172({
38332
+ var draftCommand4 = defineCommand180({
35889
38333
  meta: {
35890
38334
  name: "draft",
35891
38335
  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."
35892
38336
  },
35893
38337
  subCommands: {
35894
- list: defineCommand172({
38338
+ list: defineCommand180({
35895
38339
  meta: {
35896
38340
  name: "list",
35897
38341
  description: "Review everything staged on this chat (--json for the raw envelope)"
@@ -35904,7 +38348,7 @@ var draftCommand4 = defineCommand172({
35904
38348
  await draftList2(args.json === true, args.chat);
35905
38349
  }
35906
38350
  }),
35907
- show: defineCommand172({
38351
+ show: defineCommand180({
35908
38352
  meta: {
35909
38353
  name: "show",
35910
38354
  description: "Print the full staged payload for one change \u2014 the receipt to verify it looks right before publish (never truncated)."
@@ -35921,7 +38365,7 @@ var draftCommand4 = defineCommand172({
35921
38365
  );
35922
38366
  }
35923
38367
  }),
35924
- amend: defineCommand172({
38368
+ amend: defineCommand180({
35925
38369
  meta: {
35926
38370
  name: "amend",
35927
38371
  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."
@@ -35938,7 +38382,7 @@ var draftCommand4 = defineCommand172({
35938
38382
  });
35939
38383
  }
35940
38384
  }),
35941
- remove: defineCommand172({
38385
+ remove: defineCommand180({
35942
38386
  meta: { name: "remove", description: "Remove one staged change (cascades to anything depending on it)" },
35943
38387
  args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
35944
38388
  run: async ({ args }) => {
@@ -35947,7 +38391,7 @@ var draftCommand4 = defineCommand172({
35947
38391
  });
35948
38392
  }
35949
38393
  }),
35950
- clear: defineCommand172({
38394
+ clear: defineCommand180({
35951
38395
  meta: { name: "clear", description: "Discard all Tag Manager changes staged on this chat" },
35952
38396
  run: async () => {
35953
38397
  await draftAction3("/api/tag-manager/draft/clear", {});
@@ -35957,7 +38401,7 @@ var draftCommand4 = defineCommand172({
35957
38401
  });
35958
38402
 
35959
38403
  // src/commands/tag-manager/read.ts
35960
- import { defineCommand as defineCommand173 } from "citty";
38404
+ import { defineCommand as defineCommand181 } from "citty";
35961
38405
  registerSchema({
35962
38406
  command: "tagManager.containers",
35963
38407
  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.",
@@ -35998,7 +38442,7 @@ function containersHints(containers) {
35998
38442
  }))
35999
38443
  });
36000
38444
  }
36001
- var containersCommand = defineCommand173({
38445
+ var containersCommand = defineCommand181({
36002
38446
  meta: {
36003
38447
  name: "containers",
36004
38448
  description: `List Tag Manager containers reachable by this company's connection.
@@ -36015,7 +38459,7 @@ Start here:
36015
38459
  }
36016
38460
  }
36017
38461
  });
36018
- var readCommand = defineCommand173({
38462
+ var readCommand = defineCommand181({
36019
38463
  meta: {
36020
38464
  name: "read",
36021
38465
  description: `Read the current contents of the Tag Manager container \u2014 always do this before staging changes.
@@ -36057,7 +38501,7 @@ Examples:
36057
38501
  });
36058
38502
 
36059
38503
  // src/commands/tag-manager/write-commands.ts
36060
- import { defineCommand as defineCommand174 } from "citty";
38504
+ import { defineCommand as defineCommand182 } from "citty";
36061
38505
  var CONTAINER_ARG_DESCRIPTION = "Numeric container id (optional only when one container is connected \u2014 run `baker tag-manager containers`)";
36062
38506
  var ENTITIES = [
36063
38507
  {
@@ -36113,10 +38557,10 @@ for (const { entity, noun, createHint } of ENTITIES) {
36113
38557
  });
36114
38558
  }
36115
38559
  function entityCommand(entity, noun, example) {
36116
- return defineCommand174({
38560
+ return defineCommand182({
36117
38561
  meta: { name: entity, description: `Stage ${noun} changes on this chat's Tag Manager draft` },
36118
38562
  subCommands: {
36119
- create: defineCommand174({
38563
+ create: defineCommand182({
36120
38564
  meta: {
36121
38565
  name: "create",
36122
38566
  description: `Stage a new ${noun}
@@ -36138,7 +38582,7 @@ Examples:
36138
38582
  });
36139
38583
  }
36140
38584
  }),
36141
- update: defineCommand174({
38585
+ update: defineCommand182({
36142
38586
  meta: {
36143
38587
  name: "update",
36144
38588
  description: `Stage an update to an existing ${noun} (pass its id or path)`
@@ -36158,7 +38602,7 @@ Examples:
36158
38602
  });
36159
38603
  }
36160
38604
  }),
36161
- delete: defineCommand174({
38605
+ delete: defineCommand182({
36162
38606
  meta: { name: "delete", description: `Stage the deletion of a ${noun} (pass its id or path)` },
36163
38607
  args: {
36164
38608
  id: { type: "positional", description: `${noun} id or path`, required: false },
@@ -36199,7 +38643,7 @@ function builtinTypes(args) {
36199
38643
  }
36200
38644
  return raw.split(",").map((entry) => entry.trim());
36201
38645
  }
36202
- var builtinCommand = defineCommand174({
38646
+ var builtinCommand = defineCommand182({
36203
38647
  meta: {
36204
38648
  name: "builtin",
36205
38649
  description: `Enable or disable built-in variables
@@ -36209,7 +38653,7 @@ Examples:
36209
38653
  baker tag-manager builtin disable --types formId`
36210
38654
  },
36211
38655
  subCommands: {
36212
- enable: defineCommand174({
38656
+ enable: defineCommand182({
36213
38657
  meta: { name: "enable", description: "Stage enabling built-in variables" },
36214
38658
  args: {
36215
38659
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -36223,7 +38667,7 @@ Examples:
36223
38667
  });
36224
38668
  }
36225
38669
  }),
36226
- disable: defineCommand174({
38670
+ disable: defineCommand182({
36227
38671
  meta: { name: "disable", description: "Stage disabling built-in variables" },
36228
38672
  args: {
36229
38673
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -36241,7 +38685,7 @@ Examples:
36241
38685
  });
36242
38686
 
36243
38687
  // src/commands/tag-manager/index.ts
36244
- var tagManagerCommand = defineCommand175({
38688
+ var tagManagerCommand = defineCommand183({
36245
38689
  meta: {
36246
38690
  name: "tag-manager",
36247
38691
  description: `Read and change what lives inside the client's Google Tag Manager container \u2014 tags, triggers, variables, folders and built-in variables.
@@ -36278,7 +38722,7 @@ Full guide: __tooling__/docs/tools/baker/tag-manager.md`
36278
38722
  });
36279
38723
 
36280
38724
  // src/commands/tags/index.ts
36281
- import { defineCommand as defineCommand176 } from "citty";
38725
+ import { defineCommand as defineCommand184 } from "citty";
36282
38726
 
36283
38727
  // src/commands/tags/shared.ts
36284
38728
  function failApi3(err) {
@@ -36347,7 +38791,7 @@ async function listTags(json) {
36347
38791
  var listArgs9 = {
36348
38792
  json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list" }
36349
38793
  };
36350
- var listCommand16 = defineCommand176({
38794
+ var listCommand16 = defineCommand184({
36351
38795
  meta: {
36352
38796
  name: "list",
36353
38797
  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"
@@ -36366,7 +38810,7 @@ async function listDraft3(chat) {
36366
38810
  failApi3(err);
36367
38811
  }
36368
38812
  }
36369
- var draftCommand5 = defineCommand176({
38813
+ var draftCommand5 = defineCommand184({
36370
38814
  meta: {
36371
38815
  name: "draft",
36372
38816
  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."
@@ -36376,7 +38820,7 @@ var draftCommand5 = defineCommand176({
36376
38820
  await listDraft3(args.chat);
36377
38821
  }
36378
38822
  });
36379
- var tagsCommand3 = defineCommand176({
38823
+ var tagsCommand3 = defineCommand184({
36380
38824
  meta: {
36381
38825
  name: "tags",
36382
38826
  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.
@@ -36405,10 +38849,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
36405
38849
  });
36406
38850
 
36407
38851
  // src/commands/testimonials/index.ts
36408
- import { defineCommand as defineCommand180 } from "citty";
38852
+ import { defineCommand as defineCommand188 } from "citty";
36409
38853
 
36410
38854
  // src/commands/testimonials/get.ts
36411
- import { defineCommand as defineCommand177 } from "citty";
38855
+ import { defineCommand as defineCommand185 } from "citty";
36412
38856
  registerSchema({
36413
38857
  command: "testimonials.get",
36414
38858
  description: "Get a single testimonial by ID",
@@ -36416,7 +38860,7 @@ registerSchema({
36416
38860
  id: { type: "string", description: "Testimonial ID", required: true }
36417
38861
  }
36418
38862
  });
36419
- var getCommand5 = defineCommand177({
38863
+ var getCommand5 = defineCommand185({
36420
38864
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
36421
38865
  args: {
36422
38866
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -36453,7 +38897,7 @@ var getCommand5 = defineCommand177({
36453
38897
  });
36454
38898
 
36455
38899
  // src/commands/testimonials/list.ts
36456
- import { defineCommand as defineCommand178 } from "citty";
38900
+ import { defineCommand as defineCommand186 } from "citty";
36457
38901
  registerSchema({
36458
38902
  command: "testimonials.list",
36459
38903
  description: "List testimonials with optional filters.",
@@ -36483,7 +38927,7 @@ registerSchema({
36483
38927
  limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
36484
38928
  }
36485
38929
  });
36486
- var listCommand17 = defineCommand178({
38930
+ var listCommand17 = defineCommand186({
36487
38931
  meta: {
36488
38932
  name: "list",
36489
38933
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -36532,7 +38976,7 @@ var listCommand17 = defineCommand178({
36532
38976
  });
36533
38977
 
36534
38978
  // src/commands/testimonials/search.ts
36535
- import { defineCommand as defineCommand179 } from "citty";
38979
+ import { defineCommand as defineCommand187 } from "citty";
36536
38980
  function languageBiasHint(results, requestedLanguage) {
36537
38981
  if (requestedLanguage) {
36538
38982
  return null;
@@ -36610,7 +39054,7 @@ function buildSearchRequest(query, args) {
36610
39054
  }
36611
39055
  return body;
36612
39056
  }
36613
- var searchCommand2 = defineCommand179({
39057
+ var searchCommand3 = defineCommand187({
36614
39058
  meta: {
36615
39059
  name: "search",
36616
39060
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -36666,7 +39110,7 @@ var searchCommand2 = defineCommand179({
36666
39110
  var tagsCommand4 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
36667
39111
 
36668
39112
  // src/commands/testimonials/index.ts
36669
- var testimonialsCommand = defineCommand180({
39113
+ var testimonialsCommand = defineCommand188({
36670
39114
  meta: {
36671
39115
  name: "testimonials",
36672
39116
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -36681,17 +39125,17 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
36681
39125
  },
36682
39126
  subCommands: {
36683
39127
  get: getCommand5,
36684
- search: searchCommand2,
39128
+ search: searchCommand3,
36685
39129
  list: listCommand17,
36686
39130
  tags: tagsCommand4
36687
39131
  }
36688
39132
  });
36689
39133
 
36690
39134
  // src/commands/videos/index.ts
36691
- import { defineCommand as defineCommand185 } from "citty";
39135
+ import { defineCommand as defineCommand193 } from "citty";
36692
39136
 
36693
39137
  // src/commands/videos/delete.ts
36694
- import { defineCommand as defineCommand181 } from "citty";
39138
+ import { defineCommand as defineCommand189 } from "citty";
36695
39139
  registerSchema({
36696
39140
  command: "videos.delete",
36697
39141
  description: "Delete a video by ID",
@@ -36705,7 +39149,7 @@ registerSchema({
36705
39149
  }
36706
39150
  }
36707
39151
  });
36708
- var deleteCommand3 = defineCommand181({
39152
+ var deleteCommand3 = defineCommand189({
36709
39153
  meta: {
36710
39154
  name: "delete",
36711
39155
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -36746,7 +39190,7 @@ var deleteCommand3 = defineCommand181({
36746
39190
  });
36747
39191
 
36748
39192
  // src/commands/videos/get.ts
36749
- import { defineCommand as defineCommand182 } from "citty";
39193
+ import { defineCommand as defineCommand190 } from "citty";
36750
39194
  registerSchema({
36751
39195
  command: "videos.get",
36752
39196
  description: "Get a single video by ID",
@@ -36754,7 +39198,7 @@ registerSchema({
36754
39198
  id: { type: "string", description: "Video ID", required: true }
36755
39199
  }
36756
39200
  });
36757
- var getCommand6 = defineCommand182({
39201
+ var getCommand6 = defineCommand190({
36758
39202
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
36759
39203
  args: {
36760
39204
  id: { type: "positional", description: "Video ID", required: false },
@@ -36791,7 +39235,7 @@ var getCommand6 = defineCommand182({
36791
39235
  });
36792
39236
 
36793
39237
  // src/commands/videos/search.ts
36794
- import { defineCommand as defineCommand183 } from "citty";
39238
+ import { defineCommand as defineCommand191 } from "citty";
36795
39239
  registerSchema({
36796
39240
  command: "videos.search",
36797
39241
  description: "Search videos by text query. Only returns ready videos.",
@@ -36801,7 +39245,7 @@ registerSchema({
36801
39245
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
36802
39246
  }
36803
39247
  });
36804
- var searchCommand3 = defineCommand183({
39248
+ var searchCommand4 = defineCommand191({
36805
39249
  meta: {
36806
39250
  name: "search",
36807
39251
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -36851,9 +39295,9 @@ var searchCommand3 = defineCommand183({
36851
39295
  var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
36852
39296
 
36853
39297
  // src/commands/videos/upload.ts
36854
- import { readFile as readFile22, stat as stat7 } from "fs/promises";
39298
+ import { readFile as readFile24, stat as stat7 } from "fs/promises";
36855
39299
  import { extname as extname3 } from "path";
36856
- import { defineCommand as defineCommand184 } from "citty";
39300
+ import { defineCommand as defineCommand192 } from "citty";
36857
39301
  var MIME_MAP = {
36858
39302
  ".mp4": "video/mp4",
36859
39303
  ".mov": "video/quicktime",
@@ -36887,7 +39331,7 @@ function detectContentType(filePath) {
36887
39331
  }
36888
39332
  return mime;
36889
39333
  }
36890
- var uploadCommand2 = defineCommand184({
39334
+ var uploadCommand2 = defineCommand192({
36891
39335
  meta: {
36892
39336
  name: "upload",
36893
39337
  description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
@@ -36916,7 +39360,7 @@ var uploadCommand2 = defineCommand184({
36916
39360
  return;
36917
39361
  }
36918
39362
  const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
36919
- const fileBuffer = await readFile22(filePath);
39363
+ const fileBuffer = await readFile24(filePath);
36920
39364
  const uploadResponse = await fetch(uploadUrl, {
36921
39365
  method: "PUT",
36922
39366
  headers: { "Content-Type": contentType },
@@ -36941,7 +39385,7 @@ var uploadCommand2 = defineCommand184({
36941
39385
  });
36942
39386
 
36943
39387
  // src/commands/videos/index.ts
36944
- var videosCommand = defineCommand185({
39388
+ var videosCommand = defineCommand193({
36945
39389
  meta: {
36946
39390
  name: "videos",
36947
39391
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
@@ -36957,7 +39401,7 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
36957
39401
  },
36958
39402
  subCommands: {
36959
39403
  get: getCommand6,
36960
- search: searchCommand3,
39404
+ search: searchCommand4,
36961
39405
  upload: uploadCommand2,
36962
39406
  delete: deleteCommand3,
36963
39407
  tags: tagsCommand5
@@ -36965,19 +39409,19 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
36965
39409
  });
36966
39410
 
36967
39411
  // src/commands/winning-ads/index.ts
36968
- import { defineCommand as defineCommand198 } from "citty";
39412
+ import { defineCommand as defineCommand206 } from "citty";
36969
39413
 
36970
39414
  // src/commands/winning-ads/advertisers.ts
36971
- import { defineCommand as defineCommand186 } from "citty";
39415
+ import { defineCommand as defineCommand194 } from "citty";
36972
39416
 
36973
39417
  // src/commands/winning-ads/shared.ts
36974
- function splitList(value) {
39418
+ function splitList2(value) {
36975
39419
  if (!value) {
36976
39420
  return [];
36977
39421
  }
36978
39422
  return value.split(",").map((v) => v.trim()).filter(Boolean);
36979
39423
  }
36980
- function reportError(err) {
39424
+ function reportError2(err) {
36981
39425
  if (err instanceof ApiError) {
36982
39426
  writeJson({ ok: false, error: { code: err.code, message: err.message } });
36983
39427
  process.exit(1);
@@ -37021,7 +39465,7 @@ function advertiserNormalizer(record, full) {
37021
39465
  last_synced_at: record.last_synced_at ?? null
37022
39466
  };
37023
39467
  }
37024
- var advertisersCommand2 = defineCommand186({
39468
+ var advertisersCommand2 = defineCommand194({
37025
39469
  meta: {
37026
39470
  name: "advertisers",
37027
39471
  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'
@@ -37073,13 +39517,13 @@ var advertisersCommand2 = defineCommand186({
37073
39517
  advertiserNormalizer
37074
39518
  );
37075
39519
  } catch (err) {
37076
- reportError(err);
39520
+ reportError2(err);
37077
39521
  }
37078
39522
  }
37079
39523
  });
37080
39524
 
37081
39525
  // src/commands/winning-ads/brief.ts
37082
- import { defineCommand as defineCommand187 } from "citty";
39526
+ import { defineCommand as defineCommand195 } from "citty";
37083
39527
  registerSchema({
37084
39528
  command: "winning-ads.brief",
37085
39529
  description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
@@ -37125,7 +39569,7 @@ function parseDna(raw) {
37125
39569
  }
37126
39570
  return parsed;
37127
39571
  }
37128
- var briefCommand = defineCommand187({
39572
+ var briefCommand = defineCommand195({
37129
39573
  meta: {
37130
39574
  name: "brief",
37131
39575
  description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
@@ -37155,13 +39599,13 @@ var briefCommand = defineCommand187({
37155
39599
  const data = await apiPost("/api/ad-library/brief", body);
37156
39600
  writeJson({ ok: true, data });
37157
39601
  } catch (err) {
37158
- reportError(err);
39602
+ reportError2(err);
37159
39603
  }
37160
39604
  }
37161
39605
  });
37162
39606
 
37163
39607
  // src/commands/winning-ads/content.ts
37164
- import { defineCommand as defineCommand188 } from "citty";
39608
+ import { defineCommand as defineCommand196 } from "citty";
37165
39609
  registerSchema({
37166
39610
  command: "winning-ads.content",
37167
39611
  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.",
@@ -37174,7 +39618,7 @@ registerSchema({
37174
39618
  }
37175
39619
  }
37176
39620
  });
37177
- var contentCommand = defineCommand188({
39621
+ var contentCommand = defineCommand196({
37178
39622
  meta: {
37179
39623
  name: "content",
37180
39624
  description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
@@ -37217,16 +39661,16 @@ var contentCommand = defineCommand188({
37217
39661
  adContentNormalizer
37218
39662
  );
37219
39663
  } catch (err) {
37220
- reportError(err);
39664
+ reportError2(err);
37221
39665
  }
37222
39666
  }
37223
39667
  });
37224
39668
 
37225
39669
  // src/commands/winning-ads/feed.ts
37226
- import { defineCommand as defineCommand189 } from "citty";
39670
+ import { defineCommand as defineCommand197 } from "citty";
37227
39671
  function buildFeedParams(input) {
37228
39672
  const params = {};
37229
- const advertiser = splitList(input.advertiser);
39673
+ const advertiser = splitList2(input.advertiser);
37230
39674
  if (advertiser.length > 0) {
37231
39675
  params.advertiser = advertiser.join(",");
37232
39676
  }
@@ -37239,11 +39683,11 @@ function buildFeedParams(input) {
37239
39683
  if (input.limit !== void 0 && input.limit !== "") {
37240
39684
  params.limit = input.limit;
37241
39685
  }
37242
- const winnerCategory = splitList(input.winnerCategory);
39686
+ const winnerCategory = splitList2(input.winnerCategory);
37243
39687
  if (winnerCategory.length > 0) {
37244
39688
  params.winner_category = winnerCategory.join(",");
37245
39689
  }
37246
- const format = splitList(input.format);
39690
+ const format = splitList2(input.format);
37247
39691
  if (format.length > 0) {
37248
39692
  params.format = format.join(",");
37249
39693
  }
@@ -37275,7 +39719,7 @@ registerSchema({
37275
39719
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
37276
39720
  }
37277
39721
  });
37278
- var feedCommand = defineCommand189({
39722
+ var feedCommand = defineCommand197({
37279
39723
  meta: {
37280
39724
  name: "feed",
37281
39725
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -37354,13 +39798,13 @@ var feedCommand = defineCommand189({
37354
39798
  `);
37355
39799
  }
37356
39800
  } catch (err) {
37357
- reportError(err);
39801
+ reportError2(err);
37358
39802
  }
37359
39803
  }
37360
39804
  });
37361
39805
 
37362
39806
  // src/commands/winning-ads/follow.ts
37363
- import { defineCommand as defineCommand190 } from "citty";
39807
+ import { defineCommand as defineCommand198 } from "citty";
37364
39808
  var PLATFORMS = ["meta", "linkedin"];
37365
39809
  registerSchema({
37366
39810
  command: "winning-ads.follow",
@@ -37375,7 +39819,7 @@ registerSchema({
37375
39819
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
37376
39820
  }
37377
39821
  });
37378
- var followCommand = defineCommand190({
39822
+ var followCommand = defineCommand198({
37379
39823
  meta: {
37380
39824
  name: "follow",
37381
39825
  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 both Meta + LinkedIn. Example: baker winning-ads follow "deel.com" --platform meta'
@@ -37416,19 +39860,19 @@ var followCommand = defineCommand190({
37416
39860
  }
37417
39861
  writeJson({ ok: true, data, hints });
37418
39862
  } catch (err) {
37419
- reportError(err);
39863
+ reportError2(err);
37420
39864
  }
37421
39865
  }
37422
39866
  });
37423
39867
 
37424
39868
  // src/commands/winning-ads/follow-competitors.ts
37425
- import { defineCommand as defineCommand191 } from "citty";
39869
+ import { defineCommand as defineCommand199 } from "citty";
37426
39870
  var PLATFORMS2 = ["meta", "linkedin"];
37427
39871
  var BATCH_TIMEOUT_MS = 3e5;
37428
39872
  function buildFollowBatchBody(input) {
37429
39873
  const seen = /* @__PURE__ */ new Set();
37430
39874
  const inputs = [];
37431
- for (const domain of splitList(input.domains)) {
39875
+ for (const domain of splitList2(input.domains)) {
37432
39876
  const key = domain.toLowerCase();
37433
39877
  if (seen.has(key)) {
37434
39878
  continue;
@@ -37455,7 +39899,7 @@ registerSchema({
37455
39899
  }
37456
39900
  }
37457
39901
  });
37458
- var followCompetitorsCommand = defineCommand191({
39902
+ var followCompetitorsCommand = defineCommand199({
37459
39903
  meta: {
37460
39904
  name: "follow-competitors",
37461
39905
  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"'
@@ -37524,13 +39968,13 @@ var followCompetitorsCommand = defineCommand191({
37524
39968
  }
37525
39969
  writeJson({ ok: true, data, hints: hints.length > 0 ? hints : void 0 });
37526
39970
  } catch (err) {
37527
- reportError(err);
39971
+ reportError2(err);
37528
39972
  }
37529
39973
  }
37530
39974
  });
37531
39975
 
37532
39976
  // src/commands/winning-ads/following.ts
37533
- import { defineCommand as defineCommand192 } from "citty";
39977
+ import { defineCommand as defineCommand200 } from "citty";
37534
39978
  registerSchema({
37535
39979
  command: "winning-ads.following",
37536
39980
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
@@ -37563,7 +40007,7 @@ function followingNormalizer(record, full) {
37563
40007
  platforms: Array.isArray(record.platforms) ? record.platforms : []
37564
40008
  };
37565
40009
  }
37566
- var followingCommand = defineCommand192({
40010
+ var followingCommand = defineCommand200({
37567
40011
  meta: {
37568
40012
  name: "following",
37569
40013
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -37592,13 +40036,13 @@ var followingCommand = defineCommand192({
37592
40036
  followingNormalizer
37593
40037
  );
37594
40038
  } catch (err) {
37595
- reportError(err);
40039
+ reportError2(err);
37596
40040
  }
37597
40041
  }
37598
40042
  });
37599
40043
 
37600
40044
  // src/commands/winning-ads/patterns.ts
37601
- import { defineCommand as defineCommand193 } from "citty";
40045
+ import { defineCommand as defineCommand201 } from "citty";
37602
40046
  registerSchema({
37603
40047
  command: "winning-ads.patterns",
37604
40048
  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.",
@@ -37613,8 +40057,8 @@ registerSchema({
37613
40057
  }
37614
40058
  });
37615
40059
  function buildPatternsBody(args) {
37616
- const winners = splitList(args.winners);
37617
- const duds = splitList(args.duds);
40060
+ const winners = splitList2(args.winners);
40061
+ const duds = splitList2(args.duds);
37618
40062
  if (!winners.length || !duds.length) {
37619
40063
  throw new Error("Provide at least one ad id for both --winners and --duds");
37620
40064
  }
@@ -37637,7 +40081,7 @@ function discriminatorRow(record) {
37637
40081
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
37638
40082
  };
37639
40083
  }
37640
- var patternsCommand = defineCommand193({
40084
+ var patternsCommand = defineCommand201({
37641
40085
  meta: {
37642
40086
  name: "patterns",
37643
40087
  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"
@@ -37687,13 +40131,13 @@ var patternsCommand = defineCommand193({
37687
40131
  (record) => discriminatorRow(record)
37688
40132
  );
37689
40133
  } catch (err) {
37690
- reportError(err);
40134
+ reportError2(err);
37691
40135
  }
37692
40136
  }
37693
40137
  });
37694
40138
 
37695
40139
  // src/commands/winning-ads/search.ts
37696
- import { defineCommand as defineCommand194 } from "citty";
40140
+ import { defineCommand as defineCommand202 } from "citty";
37697
40141
  registerSchema({
37698
40142
  command: "winning-ads.search",
37699
40143
  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.",
@@ -37763,7 +40207,7 @@ function setNumber(body, key, value) {
37763
40207
  }
37764
40208
  }
37765
40209
  function setList(target, key, value) {
37766
- const list = splitList(value);
40210
+ const list = splitList2(value);
37767
40211
  if (list.length) {
37768
40212
  target[key] = list;
37769
40213
  }
@@ -37773,7 +40217,7 @@ function setString(target, key, value) {
37773
40217
  target[key] = value;
37774
40218
  }
37775
40219
  }
37776
- function buildSearchBody(args) {
40220
+ function buildSearchBody2(args) {
37777
40221
  const body = {};
37778
40222
  if (args.query) {
37779
40223
  body.free_text_query = args.query;
@@ -37801,7 +40245,7 @@ function buildSearchBody(args) {
37801
40245
  }
37802
40246
  return body;
37803
40247
  }
37804
- var searchCommand4 = defineCommand194({
40248
+ var searchCommand5 = defineCommand202({
37805
40249
  meta: {
37806
40250
  name: "search",
37807
40251
  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"
@@ -37877,7 +40321,7 @@ var searchCommand4 = defineCommand194({
37877
40321
  firstSeenBefore: args["first-seen-before"],
37878
40322
  hookArchetype: args["hook-archetype"]
37879
40323
  };
37880
- const body = buildSearchBody(searchArgs);
40324
+ const body = buildSearchBody2(searchArgs);
37881
40325
  if (!("free_text_query" in body) && !("ref_ad_id" in body) && !("hard_filters" in body)) {
37882
40326
  writeJson({
37883
40327
  ok: false,
@@ -37910,13 +40354,13 @@ var searchCommand4 = defineCommand194({
37910
40354
  winningAdNormalizer
37911
40355
  );
37912
40356
  } catch (err) {
37913
- reportError(err);
40357
+ reportError2(err);
37914
40358
  }
37915
40359
  }
37916
40360
  });
37917
40361
 
37918
40362
  // src/commands/winning-ads/seeds.ts
37919
- import { defineCommand as defineCommand195 } from "citty";
40363
+ import { defineCommand as defineCommand203 } from "citty";
37920
40364
  function leanRow(r) {
37921
40365
  return {
37922
40366
  key: r.key,
@@ -37944,7 +40388,7 @@ function makeSeedCommand(opts) {
37944
40388
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
37945
40389
  }
37946
40390
  });
37947
- return defineCommand195({
40391
+ return defineCommand203({
37948
40392
  meta: { name: opts.name, description: opts.description },
37949
40393
  args: {
37950
40394
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -37971,7 +40415,7 @@ function makeSeedCommand(opts) {
37971
40415
  }
37972
40416
  writeOutput({ ok: true, data: projected }, output);
37973
40417
  } catch (err) {
37974
- reportError(err);
40418
+ reportError2(err);
37975
40419
  }
37976
40420
  }
37977
40421
  });
@@ -37993,7 +40437,7 @@ var formatsCommand = makeSeedCommand({
37993
40437
  });
37994
40438
 
37995
40439
  // src/commands/winning-ads/unfollow.ts
37996
- import { defineCommand as defineCommand196 } from "citty";
40440
+ import { defineCommand as defineCommand204 } from "citty";
37997
40441
  registerSchema({
37998
40442
  command: "winning-ads.unfollow",
37999
40443
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -38001,7 +40445,7 @@ registerSchema({
38001
40445
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
38002
40446
  }
38003
40447
  });
38004
- var unfollowCommand = defineCommand196({
40448
+ var unfollowCommand = defineCommand204({
38005
40449
  meta: {
38006
40450
  name: "unfollow",
38007
40451
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -38016,13 +40460,13 @@ var unfollowCommand = defineCommand196({
38016
40460
  });
38017
40461
  writeJson({ ok: true, data });
38018
40462
  } catch (err) {
38019
- reportError(err);
40463
+ reportError2(err);
38020
40464
  }
38021
40465
  }
38022
40466
  });
38023
40467
 
38024
40468
  // src/commands/winning-ads/winners.ts
38025
- import { defineCommand as defineCommand197 } from "citty";
40469
+ import { defineCommand as defineCommand205 } from "citty";
38026
40470
  registerSchema({
38027
40471
  command: "winning-ads.winners",
38028
40472
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -38032,7 +40476,7 @@ registerSchema({
38032
40476
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
38033
40477
  }
38034
40478
  });
38035
- var winnersCommand = defineCommand197({
40479
+ var winnersCommand = defineCommand205({
38036
40480
  meta: {
38037
40481
  name: "winners",
38038
40482
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -38076,13 +40520,13 @@ var winnersCommand = defineCommand197({
38076
40520
  winningAdNormalizer
38077
40521
  );
38078
40522
  } catch (err) {
38079
- reportError(err);
40523
+ reportError2(err);
38080
40524
  }
38081
40525
  }
38082
40526
  });
38083
40527
 
38084
40528
  // src/commands/winning-ads/index.ts
38085
- var winningAdsCommand = defineCommand198({
40529
+ var winningAdsCommand = defineCommand206({
38086
40530
  meta: {
38087
40531
  name: "winning-ads",
38088
40532
  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.
@@ -38120,7 +40564,7 @@ Examples:
38120
40564
  Full guide: __tooling__/docs/tools/baker/winning-ads.md`
38121
40565
  },
38122
40566
  subCommands: {
38123
- search: searchCommand4,
40567
+ search: searchCommand5,
38124
40568
  advertisers: advertisersCommand2,
38125
40569
  follow: followCommand,
38126
40570
  "follow-competitors": followCompetitorsCommand,
@@ -38154,7 +40598,7 @@ function getCliVersion() {
38154
40598
  }
38155
40599
 
38156
40600
  // src/cli.ts
38157
- var main = defineCommand199({
40601
+ var main = defineCommand207({
38158
40602
  meta: {
38159
40603
  name: "baker",
38160
40604
  version: getCliVersion(),