@koda-sl/baker-cli 0.181.1 → 0.182.0-dev.c8185c1b5

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
@@ -38,7 +38,7 @@ import {
38
38
  toModelSafeImage,
39
39
  ulid,
40
40
  validateCanvasDeep
41
- } from "./chunk-RWHEFQXI.js";
41
+ } from "./chunk-UZ37VVP4.js";
42
42
  import {
43
43
  csvOrJson,
44
44
  daysAgoIso,
@@ -81,7 +81,7 @@ import {
81
81
  } from "./chunk-YL3HDEIJ.js";
82
82
 
83
83
  // src/cli.ts
84
- import { defineCommand as defineCommand191, runMain } from "citty";
84
+ import { defineCommand as defineCommand199, runMain } from "citty";
85
85
 
86
86
  // src/commands/actions/index.ts
87
87
  import { defineCommand as defineCommand18 } from "citty";
@@ -7388,19 +7388,19 @@ Examples:
7388
7388
  },
7389
7389
  run: async ({ args }) => {
7390
7390
  const customerId = await resolveCustomerId(args);
7391
- const window = resolveChangesWindow({
7391
+ const window2 = resolveChangesWindow({
7392
7392
  days: args.days ? Number(args.days) : void 0,
7393
7393
  scope: args.scope
7394
7394
  });
7395
- if (!window.ok) {
7396
- writeJsonEnvelope({ ok: false, error: { ...window.error, retryable: false } });
7395
+ if (!window2.ok) {
7396
+ writeJsonEnvelope({ ok: false, error: { ...window2.error, retryable: false } });
7397
7397
  process.exit(1);
7398
7398
  return;
7399
7399
  }
7400
7400
  const body = {
7401
7401
  customerId,
7402
- days: window.days,
7403
- scope: window.scope,
7402
+ days: window2.days,
7403
+ scope: window2.scope,
7404
7404
  limit: args.limit ? Number(args.limit) : 50
7405
7405
  };
7406
7406
  const managerId = getManagerIdForCustomer(customerId);
@@ -7417,7 +7417,7 @@ Examples:
7417
7417
  const first = data[0];
7418
7418
  const fields = first ? Object.keys(first) : [];
7419
7419
  const fieldDescs = getFieldDescriptions(fields);
7420
- writeJsonEnvelope({ ok: true, data, fields: fieldDescs, hints: window.hints });
7420
+ writeJsonEnvelope({ ok: true, data, fields: fieldDescs, hints: window2.hints });
7421
7421
  } catch (err) {
7422
7422
  if (err instanceof ApiError) {
7423
7423
  writeAdsJson(parseApiError(err.message, "", customerId, err.code));
@@ -7774,8 +7774,8 @@ function countStaccato(text) {
7774
7774
  let first = "";
7775
7775
  let runStart = 0;
7776
7776
  for (const [i, s] of sentences.entries()) {
7777
- const words2 = s.split(/\s+/).filter(Boolean).length;
7778
- if (words2 > 0 && words2 <= STACCATO_MAX_WORDS) {
7777
+ const words3 = s.split(/\s+/).filter(Boolean).length;
7778
+ if (words3 > 0 && words3 <= STACCATO_MAX_WORDS) {
7779
7779
  if (run === 0) runStart = i;
7780
7780
  run++;
7781
7781
  if (run === STACCATO_RUN) {
@@ -7793,10 +7793,10 @@ function countTitleCaseHeadings(text) {
7793
7793
  let first = "";
7794
7794
  for (const m of text.matchAll(/^\s{0,3}#{1,6}\s+(.+)$/gm)) {
7795
7795
  const heading = (m[1] ?? "").trim();
7796
- const words2 = heading.split(/\s+/).filter((w) => new RegExp("\\p{L}", "u").test(w));
7797
- if (words2.length < 4) continue;
7798
- const capitalized = words2.filter((w) => new RegExp("^\\p{Lu}", "u").test(w)).length;
7799
- if (capitalized / words2.length < 0.8) continue;
7796
+ const words3 = heading.split(/\s+/).filter((w) => new RegExp("\\p{L}", "u").test(w));
7797
+ if (words3.length < 4) continue;
7798
+ const capitalized = words3.filter((w) => new RegExp("^\\p{Lu}", "u").test(w)).length;
7799
+ if (capitalized / words3.length < 0.8) continue;
7800
7800
  count++;
7801
7801
  if (!first) first = heading;
7802
7802
  }
@@ -8125,11 +8125,11 @@ function rawTextEntries(value) {
8125
8125
  const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
8126
8126
  return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
8127
8127
  }
8128
- function rawFileEntries(path28) {
8129
- if (typeof path28 !== "string" || path28.length === 0) {
8128
+ function rawFileEntries(path34) {
8129
+ if (typeof path34 !== "string" || path34.length === 0) {
8130
8130
  return [];
8131
8131
  }
8132
- return readFileSync2(path28, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8132
+ return readFileSync2(path34, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8133
8133
  }
8134
8134
  function keywordEntries(args) {
8135
8135
  const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
@@ -8152,19 +8152,19 @@ function keywordEntries(args) {
8152
8152
  }
8153
8153
  return entries;
8154
8154
  }
8155
- function loadJsonFileArg(path28) {
8156
- if (typeof path28 !== "string" || path28.length === 0) {
8155
+ function loadJsonFileArg(path34) {
8156
+ if (typeof path34 !== "string" || path34.length === 0) {
8157
8157
  return {};
8158
8158
  }
8159
8159
  try {
8160
- const parsed = JSON.parse(readFileSync2(path28, "utf8"));
8160
+ const parsed = JSON.parse(readFileSync2(path34, "utf8"));
8161
8161
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
8162
- failWriteValidation(`${path28} must contain a JSON object`);
8162
+ failWriteValidation(`${path34} must contain a JSON object`);
8163
8163
  }
8164
8164
  return parsed;
8165
8165
  } catch (err) {
8166
8166
  if (err instanceof SyntaxError) {
8167
- failWriteValidation(`${path28} is not valid JSON: ${err.message}`);
8167
+ failWriteValidation(`${path34} is not valid JSON: ${err.message}`);
8168
8168
  }
8169
8169
  throw err;
8170
8170
  }
@@ -8294,10 +8294,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
8294
8294
  async function stageTarget(kind, customerId, target, hints) {
8295
8295
  await stageGoogleOp({ kind, customerId, target }, hints);
8296
8296
  }
8297
- async function draftAction(path28, body, chat) {
8297
+ async function draftAction(path34, body, chat) {
8298
8298
  try {
8299
8299
  const chatId = resolveChatId(chat);
8300
- const response = await apiPost(path28, { chatId, ...body });
8300
+ const response = await apiPost(path34, { chatId, ...body });
8301
8301
  writeJsonEnvelope(response);
8302
8302
  } catch (err) {
8303
8303
  handleGoogleError(err);
@@ -10965,19 +10965,19 @@ function failWriteValidation2(message) {
10965
10965
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
10966
10966
  process.exit(1);
10967
10967
  }
10968
- function loadJsonFileArg2(path28) {
10969
- if (typeof path28 !== "string" || path28.length === 0) {
10968
+ function loadJsonFileArg2(path34) {
10969
+ if (typeof path34 !== "string" || path34.length === 0) {
10970
10970
  return {};
10971
10971
  }
10972
10972
  try {
10973
- const parsed = JSON.parse(readFileSync4(path28, "utf8"));
10973
+ const parsed = JSON.parse(readFileSync4(path34, "utf8"));
10974
10974
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
10975
- failWriteValidation2(`${path28} must contain a JSON object`);
10975
+ failWriteValidation2(`${path34} must contain a JSON object`);
10976
10976
  }
10977
10977
  return parsed;
10978
10978
  } catch (err) {
10979
10979
  if (err instanceof SyntaxError) {
10980
- failWriteValidation2(`${path28} is not valid JSON: ${err.message}`);
10980
+ failWriteValidation2(`${path34} is not valid JSON: ${err.message}`);
10981
10981
  }
10982
10982
  throw err;
10983
10983
  }
@@ -11062,15 +11062,15 @@ function parseLocaleFlag(value) {
11062
11062
  }
11063
11063
  return { language: match[1], country: match[2].toUpperCase() };
11064
11064
  }
11065
- function loadTargetingFileArg(path28) {
11066
- if (typeof path28 !== "string" || path28.length === 0) {
11065
+ function loadTargetingFileArg(path34) {
11066
+ if (typeof path34 !== "string" || path34.length === 0) {
11067
11067
  return void 0;
11068
11068
  }
11069
- const parsed = loadJsonFileArg2(path28);
11069
+ const parsed = loadJsonFileArg2(path34);
11070
11070
  const criteria = parsed.targetingCriteria ?? parsed;
11071
11071
  if (!criteria.include) {
11072
11072
  failWriteValidation2(
11073
- `${path28} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
11073
+ `${path34} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
11074
11074
  );
11075
11075
  }
11076
11076
  return criteria;
@@ -11105,14 +11105,14 @@ function parseCsvLine(line) {
11105
11105
  cells.push(current);
11106
11106
  return cells.map((cell) => cell.trim());
11107
11107
  }
11108
- function parseListFileArg(path28, maxRows) {
11109
- if (typeof path28 !== "string" || path28.length === 0) {
11108
+ function parseListFileArg(path34, maxRows) {
11109
+ if (typeof path34 !== "string" || path34.length === 0) {
11110
11110
  return void 0;
11111
11111
  }
11112
- const raw = readFileSync4(path28, "utf8");
11112
+ const raw = readFileSync4(path34, "utf8");
11113
11113
  const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
11114
11114
  if (lines.length < 2) {
11115
- failWriteValidation2(`${path28} needs a header row and at least one data row`);
11115
+ failWriteValidation2(`${path34} needs a header row and at least one data row`);
11116
11116
  }
11117
11117
  const columns = parseCsvLine(lines[0]).map((column) => column.trim());
11118
11118
  const rows = [];
@@ -11131,7 +11131,7 @@ function parseListFileArg(path28, maxRows) {
11131
11131
  }
11132
11132
  }
11133
11133
  if (rows.length > maxRows) {
11134
- failWriteValidation2(`${path28} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
11134
+ failWriteValidation2(`${path34} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
11135
11135
  }
11136
11136
  return { columns, rows };
11137
11137
  }
@@ -11227,11 +11227,11 @@ function readPositionals(args) {
11227
11227
  function splitIdList(raw) {
11228
11228
  return raw.split(",").map((id) => id.trim()).filter(Boolean);
11229
11229
  }
11230
- function idsFileEntries(path28) {
11231
- if (typeof path28 !== "string" || path28.length === 0) {
11230
+ function idsFileEntries(path34) {
11231
+ if (typeof path34 !== "string" || path34.length === 0) {
11232
11232
  return [];
11233
11233
  }
11234
- return readFileSync4(path28, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
11234
+ return readFileSync4(path34, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
11235
11235
  }
11236
11236
  function requireTargets(args, entity) {
11237
11237
  const positionals = readPositionals(args);
@@ -13854,9 +13854,9 @@ function compactRow(row) {
13854
13854
  ...destination.postUrn ? { postUrn: destination.postUrn } : {}
13855
13855
  };
13856
13856
  }
13857
- function readPath(row, path28) {
13857
+ function readPath(row, path34) {
13858
13858
  let current = row;
13859
- for (const segment of path28.split(".")) {
13859
+ for (const segment of path34.split(".")) {
13860
13860
  const record = asRecord2(current);
13861
13861
  if (!record) return void 0;
13862
13862
  current = record[segment];
@@ -13866,10 +13866,10 @@ function readPath(row, path28) {
13866
13866
  function projectFields(rows, paths) {
13867
13867
  return rows.map((row) => {
13868
13868
  const projected = {};
13869
- for (const path28 of paths) {
13870
- const value = readPath(row, path28);
13869
+ for (const path34 of paths) {
13870
+ const value = readPath(row, path34);
13871
13871
  if (value !== void 0) {
13872
- projected[path28] = value;
13872
+ projected[path34] = value;
13873
13873
  }
13874
13874
  }
13875
13875
  return projected;
@@ -15076,11 +15076,11 @@ var updateStatusSchema = z18.enum(UPDATE_STATUSES);
15076
15076
  function currencyMinimums2(currencyCode) {
15077
15077
  return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
15078
15078
  }
15079
- function validateDailyBudgetFloor(money, ctx, path28) {
15079
+ function validateDailyBudgetFloor(money, ctx, path34) {
15080
15080
  if (money?.currencyCode) {
15081
15081
  const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
15082
15082
  if (Number(money.amount) < min) {
15083
- ctx.addIssue({ code: "custom", path: path28, message: `below the ${min} ${money.currencyCode} daily minimum` });
15083
+ ctx.addIssue({ code: "custom", path: path34, message: `below the ${min} ${money.currencyCode} daily minimum` });
15084
15084
  }
15085
15085
  }
15086
15086
  }
@@ -15723,19 +15723,19 @@ function failWriteValidation3(message) {
15723
15723
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
15724
15724
  process.exit(1);
15725
15725
  }
15726
- function loadJsonFileArg3(path28) {
15727
- if (typeof path28 !== "string" || path28.length === 0) {
15726
+ function loadJsonFileArg3(path34) {
15727
+ if (typeof path34 !== "string" || path34.length === 0) {
15728
15728
  return {};
15729
15729
  }
15730
15730
  try {
15731
- const parsed = JSON.parse(readFileSync8(path28, "utf8"));
15731
+ const parsed = JSON.parse(readFileSync8(path34, "utf8"));
15732
15732
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
15733
- failWriteValidation3(`${path28} must contain a JSON object`);
15733
+ failWriteValidation3(`${path34} must contain a JSON object`);
15734
15734
  }
15735
15735
  return parsed;
15736
15736
  } catch (err) {
15737
15737
  if (err instanceof SyntaxError) {
15738
- failWriteValidation3(`${path28} is not valid JSON: ${err.message}`);
15738
+ failWriteValidation3(`${path34} is not valid JSON: ${err.message}`);
15739
15739
  }
15740
15740
  throw err;
15741
15741
  }
@@ -19797,11 +19797,11 @@ function estSpeechS(text) {
19797
19797
  var OBSERVED_WPS_MIN = 1;
19798
19798
  var OBSERVED_WPS_MAX = 6;
19799
19799
  function estSpeechWindowS(text, startS, endS) {
19800
- const words2 = wordCount(text);
19801
- const window = (endS ?? 0) - (startS ?? 0);
19802
- if (words2 > 0 && window > 0.3) {
19803
- const wps = words2 / window;
19804
- if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return window;
19800
+ const words3 = wordCount(text);
19801
+ const window2 = (endS ?? 0) - (startS ?? 0);
19802
+ if (words3 > 0 && window2 > 0.3) {
19803
+ const wps = words3 / window2;
19804
+ if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return window2;
19805
19805
  }
19806
19806
  return estSpeechS(text);
19807
19807
  }
@@ -20656,10 +20656,10 @@ function scrubFloatSentences(text, floatDescs) {
20656
20656
  if (floatDescs.length === 0 || !text) return text;
20657
20657
  const tokenSets = floatDescs.map((d) => new Set(floatTokens(d)));
20658
20658
  const kept = text.split(/(?<=[.!?])\s+/).filter((sentence) => {
20659
- const words2 = new Set(floatTokens(sentence));
20659
+ const words3 = new Set(floatTokens(sentence));
20660
20660
  return !tokenSets.some((ts) => {
20661
20661
  let hits = 0;
20662
- for (const w of words2) if (ts.has(w)) hits++;
20662
+ for (const w of words3) if (ts.has(w)) hits++;
20663
20663
  return hits >= 2;
20664
20664
  });
20665
20665
  }).join(" ").trim();
@@ -22850,15 +22850,15 @@ function collectClipAdvisories(scene, i, out) {
22850
22850
  const round22 = (n) => Math.round(n * 100) / 100;
22851
22851
  const original = scene.duration_s ?? 5;
22852
22852
  if (original > 15) out.clamped.push({ scene: i, original_s: original, clip_s: snapToSeedance(original) });
22853
- const window = sceneDurationS(scene);
22854
- if (window > SEEDANCE_SAFE_MAX_S)
22855
- out.oversize.push({ scene: i, scene_s: round22(window), clip_s: ceilToSeedance(window) });
22853
+ const window2 = sceneDurationS(scene);
22854
+ if (window2 > SEEDANCE_SAFE_MAX_S)
22855
+ out.oversize.push({ scene: i, scene_s: round22(window2), clip_s: ceilToSeedance(window2) });
22856
22856
  const speech = (scene.dialogue ?? []).reduce(
22857
22857
  (s, line) => s + (line.line ? estSpeechWindowS(line.line, line.start_s, line.end_s) : 0),
22858
22858
  0
22859
22859
  );
22860
- if (speech > window * OVERSTUFF_RATIO)
22861
- out.overstuffed.push({ scene: i, scene_s: round22(window), est_speech_s: round22(speech) });
22860
+ if (speech > window2 * OVERSTUFF_RATIO)
22861
+ out.overstuffed.push({ scene: i, scene_s: round22(window2), est_speech_s: round22(speech) });
22862
22862
  }
22863
22863
  function videoReport(input, elementsInput) {
22864
22864
  const blueprint = VideoBlueprint.parse(input);
@@ -26531,12 +26531,12 @@ function listFlowSlugs() {
26531
26531
  return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
26532
26532
  }
26533
26533
  function readFlowTree(slug) {
26534
- const path28 = join3(flowsDir(), slug, "_data.json");
26535
- if (!existsSync4(path28)) {
26534
+ const path34 = join3(flowsDir(), slug, "_data.json");
26535
+ if (!existsSync4(path34)) {
26536
26536
  failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
26537
26537
  }
26538
26538
  try {
26539
- return JSON.parse(readFileSync9(path28, "utf-8"));
26539
+ return JSON.parse(readFileSync9(path34, "utf-8"));
26540
26540
  } catch (error) {
26541
26541
  failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
26542
26542
  }
@@ -26949,10 +26949,10 @@ async function stageOps(ops) {
26949
26949
  handleError(err);
26950
26950
  }
26951
26951
  }
26952
- async function draftAction2(path28, body, chat) {
26952
+ async function draftAction2(path34, body, chat) {
26953
26953
  const chatId = resolveChatId(chat);
26954
26954
  try {
26955
- const data = await apiPost(path28, { chatId, ...body });
26955
+ const data = await apiPost(path34, { chatId, ...body });
26956
26956
  writeJsonEnvelope({ ok: true, data });
26957
26957
  return data;
26958
26958
  } catch (err) {
@@ -28976,9 +28976,9 @@ async function readImageBuffer(pathOrUrl) {
28976
28976
  }
28977
28977
  return readFile19(pathOrUrl);
28978
28978
  }
28979
- async function isDirectory(path28) {
28979
+ async function isDirectory(path34) {
28980
28980
  try {
28981
- const s = await stat4(path28);
28981
+ const s = await stat4(path34);
28982
28982
  return s.isDirectory();
28983
28983
  } catch {
28984
28984
  return false;
@@ -31598,11 +31598,11 @@ Full guide: __tooling__/docs/tools/baker/images.md`
31598
31598
  });
31599
31599
 
31600
31600
  // src/commands/landing/index.ts
31601
- import { defineCommand as defineCommand143 } from "citty";
31601
+ import { defineCommand as defineCommand151 } from "citty";
31602
31602
 
31603
31603
  // src/commands/landing/critique.ts
31604
31604
  import { readdir as readdir8, stat as stat6 } from "fs/promises";
31605
- import path27 from "path";
31605
+ import path28 from "path";
31606
31606
  import { defineCommand as defineCommand142 } from "citty";
31607
31607
 
31608
31608
  // src/engine/landing/lib/brand-tokens.ts
@@ -32011,6 +32011,11 @@ var RULE_META = {
32011
32011
  severity: "block",
32012
32012
  note: "Gradient text is a top AI tell. Emphasis comes from weight or size, not a clipped gradient fill."
32013
32013
  },
32014
+ "copied-reference-copy": {
32015
+ family: "originality",
32016
+ severity: "block",
32017
+ 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."
32018
+ },
32014
32019
  "broken-image": {
32015
32020
  family: "integrity",
32016
32021
  severity: "block",
@@ -32191,6 +32196,64 @@ var SEVERITY_WEIGHT = {
32191
32196
  advisory: 0.05
32192
32197
  };
32193
32198
 
32199
+ // src/engine/landing/lib/originality.ts
32200
+ var MIN_COMPARABLE_LENGTH = 12;
32201
+ var NEAR_MATCH_RATIO = 0.8;
32202
+ function normalize(value) {
32203
+ return value.toLowerCase().replace(/[‘’“”]/g, "'").replace(/[^a-z0-9']+/g, " ").trim();
32204
+ }
32205
+ function words2(value) {
32206
+ return normalize(value).split(" ").filter(Boolean);
32207
+ }
32208
+ function copyOverlapRatio(candidate, reference) {
32209
+ const referenceWords = words2(reference);
32210
+ if (referenceWords.length === 0) return 0;
32211
+ const candidateWords = new Set(words2(candidate));
32212
+ const shared = referenceWords.filter((word) => candidateWords.has(word)).length;
32213
+ return shared / referenceWords.length;
32214
+ }
32215
+ function isVerbatimReuse(candidate, reference) {
32216
+ const normalizedCandidate = normalize(candidate);
32217
+ const normalizedReference = normalize(reference);
32218
+ if (normalizedReference.length < MIN_COMPARABLE_LENGTH) return false;
32219
+ if (normalizedCandidate.includes(normalizedReference)) return true;
32220
+ return copyOverlapRatio(candidate, reference) >= NEAR_MATCH_RATIO;
32221
+ }
32222
+ function extractVisibleStrings(text) {
32223
+ const found = [];
32224
+ const lines = text.split("\n");
32225
+ for (const [index, line] of lines.entries()) {
32226
+ for (const match of line.matchAll(/>([^<>{}]{12,200})</g)) {
32227
+ const value = match[1]?.trim();
32228
+ if (value && /[a-zA-Z]{3}/.test(value)) found.push({ value, line: index + 1 });
32229
+ }
32230
+ }
32231
+ return found;
32232
+ }
32233
+ function detectOriginality(sources, references) {
32234
+ if (references.length === 0) return [];
32235
+ const findings = [];
32236
+ const seen = /* @__PURE__ */ new Set();
32237
+ for (const source of sources) {
32238
+ for (const { value, line } of extractVisibleStrings(source.text)) {
32239
+ for (const reference of references) {
32240
+ const hit = reference.copyStrings.find((copy) => isVerbatimReuse(value, copy));
32241
+ if (!hit) continue;
32242
+ const key = `${source.path}:${line}:${normalize(hit)}`;
32243
+ if (seen.has(key)) continue;
32244
+ seen.add(key);
32245
+ findings.push({
32246
+ id: "copied-reference-copy",
32247
+ snippet: value.slice(0, 120),
32248
+ file: source.path,
32249
+ line
32250
+ });
32251
+ }
32252
+ }
32253
+ }
32254
+ return findings;
32255
+ }
32256
+
32194
32257
  // src/engine/landing/lib/rules.ts
32195
32258
  var cap2 = (m, i) => m[i] ?? "";
32196
32259
  var num = (m, i) => Number(m[i] ?? 0);
@@ -32595,8 +32658,8 @@ var ANALYZERS = [
32595
32658
  const lines = text.split("\n");
32596
32659
  for (const m of text.matchAll(/(?:-webkit-)?background-clip\s*:\s*text/gi)) {
32597
32660
  const line = lineOf(text, m.index ?? 0);
32598
- const window = lines.slice(Math.max(0, line - 7), Math.min(lines.length, line + 6)).join("\n");
32599
- if (/gradient\(/i.test(window)) {
32661
+ const window2 = lines.slice(Math.max(0, line - 7), Math.min(lines.length, line + 6)).join("\n");
32662
+ if (/gradient\(/i.test(window2)) {
32600
32663
  out.push({ id: "gradient-text", snippet: "background-clip: text + gradient", file, line });
32601
32664
  }
32602
32665
  }
@@ -32718,7 +32781,16 @@ function dedupe(findings) {
32718
32781
  }
32719
32782
 
32720
32783
  // src/engine/landing/lib/critique.ts
32721
- var FAMILIES = ["typography", "color", "borders_depth", "motion", "spacing", "copy", "integrity"];
32784
+ var FAMILIES = [
32785
+ "typography",
32786
+ "color",
32787
+ "borders_depth",
32788
+ "motion",
32789
+ "spacing",
32790
+ "copy",
32791
+ "integrity",
32792
+ "originality"
32793
+ ];
32722
32794
  function round4(n) {
32723
32795
  return Math.round(n * 100) / 100;
32724
32796
  }
@@ -32737,6 +32809,7 @@ function critiqueLanding(input) {
32737
32809
  const raws = [];
32738
32810
  for (const source of sources) raws.push(...detectSource(source));
32739
32811
  raws.push(...detectPage(sources));
32812
+ raws.push(...detectOriginality(sources, input.references ?? []));
32740
32813
  for (const raw of raws) {
32741
32814
  const meta = RULE_META[raw.id];
32742
32815
  if (!meta) continue;
@@ -32774,41 +32847,77 @@ function describeCounts(findings) {
32774
32847
  return [b ? `${b} block` : "", w ? `${w} warn` : "", a ? `${a} advisory` : ""].filter(Boolean).join(", ");
32775
32848
  }
32776
32849
 
32777
- // src/commands/landing/snapshot.ts
32778
- import { mkdir as mkdir8, rename as rename2, writeFile as writeFile11 } from "fs/promises";
32850
+ // src/engine/landing/lib/referenceStore.ts
32851
+ import { mkdir as mkdir8, readFile as readFile22, writeFile as writeFile11 } from "fs/promises";
32779
32852
  import path25 from "path";
32853
+ var REFERENCES_FILE = ".cache/inspiration-refs.json";
32854
+ var REFERENCE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
32855
+ async function readReferences(projectRoot) {
32856
+ try {
32857
+ const raw = await readFile22(path25.join(projectRoot, REFERENCES_FILE), "utf8");
32858
+ const parsed = JSON.parse(raw);
32859
+ if (!Array.isArray(parsed)) return [];
32860
+ const cutoff = Date.now() - REFERENCE_TTL_MS;
32861
+ return parsed.filter((entry) => {
32862
+ if (typeof entry !== "object" || entry === null) return false;
32863
+ const candidate = entry;
32864
+ if (typeof candidate.sectionId !== "string" || !Array.isArray(candidate.copyStrings)) return false;
32865
+ const at = Date.parse(candidate.consultedAt ?? "");
32866
+ return Number.isNaN(at) ? true : at >= cutoff;
32867
+ });
32868
+ } catch {
32869
+ return [];
32870
+ }
32871
+ }
32872
+ async function recordReference(projectRoot, reference) {
32873
+ try {
32874
+ const existing = await readReferences(projectRoot);
32875
+ const merged = [...existing.filter((entry) => entry.sectionId !== reference.sectionId), reference];
32876
+ const file = path25.join(projectRoot, REFERENCES_FILE);
32877
+ await mkdir8(path25.dirname(file), { recursive: true });
32878
+ await writeFile11(file, `${JSON.stringify(merged, null, 2)}
32879
+ `);
32880
+ return true;
32881
+ } catch {
32882
+ return false;
32883
+ }
32884
+ }
32885
+
32886
+ // src/commands/landing/snapshot.ts
32887
+ import { mkdir as mkdir9, rename as rename2, writeFile as writeFile12 } from "fs/promises";
32888
+ import path26 from "path";
32780
32889
  var CRITIC_VERSION = "2";
32781
32890
  function critiqueCacheDir(projectRoot) {
32782
- return path25.join(projectRoot, ".cache", "landing-critique");
32891
+ return path26.join(projectRoot, ".cache", "landing-critique");
32783
32892
  }
32784
32893
  function snapshotPath(projectRoot, slug) {
32785
- return path25.join(critiqueCacheDir(projectRoot), `${slug}.json`);
32894
+ return path26.join(critiqueCacheDir(projectRoot), `${slug}.json`);
32786
32895
  }
32787
32896
  async function writeCritiqueSnapshot(projectRoot, snapshot) {
32788
- await mkdir8(critiqueCacheDir(projectRoot), { recursive: true });
32897
+ await mkdir9(critiqueCacheDir(projectRoot), { recursive: true });
32789
32898
  const dest = snapshotPath(projectRoot, snapshot.slug);
32790
32899
  const tmp = `${dest}.tmp`;
32791
- await writeFile11(tmp, `${JSON.stringify(snapshot, null, 2)}
32900
+ await writeFile12(tmp, `${JSON.stringify(snapshot, null, 2)}
32792
32901
  `, "utf8");
32793
32902
  await rename2(tmp, dest);
32794
32903
  }
32795
32904
 
32796
32905
  // src/commands/landing/source-version.ts
32797
- import { readdir as readdir7, readFile as readFile22, stat as stat5 } from "fs/promises";
32798
- import path26 from "path";
32906
+ import { readdir as readdir7, readFile as readFile23, stat as stat5 } from "fs/promises";
32907
+ import path27 from "path";
32799
32908
  async function landingSourceRelPaths(landingDir) {
32800
32909
  const rel = [];
32801
- if (await isFile(path26.join(landingDir, "index.astro"))) rel.push("index.astro");
32802
- const componentsDir = path26.join(landingDir, "_components");
32910
+ if (await isFile(path27.join(landingDir, "index.astro"))) rel.push("index.astro");
32911
+ const componentsDir = path27.join(landingDir, "_components");
32803
32912
  for (const abs of await walkAstro(componentsDir)) {
32804
- rel.push(path26.relative(landingDir, abs).split(path26.sep).join("/"));
32913
+ rel.push(path27.relative(landingDir, abs).split(path27.sep).join("/"));
32805
32914
  }
32806
32915
  return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
32807
32916
  }
32808
32917
  async function readLandingSources(landingDir) {
32809
32918
  const rel = await landingSourceRelPaths(landingDir);
32810
32919
  const out = [];
32811
- for (const r of rel) out.push({ path: r, text: await readFile22(path26.join(landingDir, r), "utf8") });
32920
+ for (const r of rel) out.push({ path: r, text: await readFile23(path27.join(landingDir, r), "utf8") });
32812
32921
  return out;
32813
32922
  }
32814
32923
  async function computeLandingSourceSha(landingDir) {
@@ -32817,7 +32926,7 @@ async function computeLandingSourceSha(landingDir) {
32817
32926
  for (const r of rel) {
32818
32927
  let bytes;
32819
32928
  try {
32820
- bytes = await readFile22(path26.join(landingDir, r));
32929
+ bytes = await readFile23(path27.join(landingDir, r));
32821
32930
  } catch {
32822
32931
  bytes = Buffer.alloc(0);
32823
32932
  }
@@ -32841,7 +32950,7 @@ async function walkAstro(dir) {
32841
32950
  }
32842
32951
  const out = [];
32843
32952
  for (const entry of entries) {
32844
- const abs = path26.join(dir, entry.name);
32953
+ const abs = path27.join(dir, entry.name);
32845
32954
  if (entry.isDirectory()) out.push(...await walkAstro(abs));
32846
32955
  else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
32847
32956
  }
@@ -32902,14 +33011,14 @@ var critiqueCommand2 = defineCommand142({
32902
33011
  { availableSlugs: await listLandingSlugs(projectRoot) }
32903
33012
  );
32904
33013
  }
32905
- if (!await isDir(path27.resolve(projectRoot, "src", "pages", slug))) {
33014
+ if (!await isDir(path28.resolve(projectRoot, "src", "pages", slug))) {
32906
33015
  fail5("NOT_FOUND", `No landing at src/pages/${slug}/`, {
32907
33016
  availableSlugs: await listLandingSlugs(projectRoot)
32908
33017
  });
32909
33018
  }
32910
33019
  }
32911
- const brand = await loadBrandTokens(projectRoot);
32912
- const results = await Promise.all(slugs.map((slug) => critiqueOne(projectRoot, slug, brand)));
33020
+ const [brand, references] = await Promise.all([loadBrandTokens(projectRoot), readReferences(projectRoot)]);
33021
+ const results = await Promise.all(slugs.map((slug) => critiqueOne(projectRoot, slug, brand, references)));
32913
33022
  const landings = results.map(({ slug, report }) => ({
32914
33023
  slug,
32915
33024
  overall: report.overall,
@@ -32940,10 +33049,10 @@ var critiqueCommand2 = defineCommand142({
32940
33049
  );
32941
33050
  }
32942
33051
  });
32943
- async function critiqueOne(projectRoot, slug, brand) {
32944
- const landingDir = path27.resolve(projectRoot, "src", "pages", slug);
33052
+ async function critiqueOne(projectRoot, slug, brand, references) {
33053
+ const landingDir = path28.resolve(projectRoot, "src", "pages", slug);
32945
33054
  const [sources, sourceSha] = await Promise.all([readLandingSources(landingDir), computeLandingSourceSha(landingDir)]);
32946
- const report = critiqueLanding({ slug, sources, brand });
33055
+ const report = critiqueLanding({ slug, sources, brand, references });
32947
33056
  let snapshotFailed = false;
32948
33057
  try {
32949
33058
  await writeCritiqueSnapshot(projectRoot, {
@@ -32961,7 +33070,7 @@ async function critiqueOne(projectRoot, slug, brand) {
32961
33070
  }
32962
33071
  async function listLandingSlugs(projectRoot) {
32963
33072
  try {
32964
- const entries = await readdir8(path27.join(projectRoot, "src", "pages"), { withFileTypes: true });
33073
+ const entries = await readdir8(path28.join(projectRoot, "src", "pages"), { withFileTypes: true });
32965
33074
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
32966
33075
  } catch {
32967
33076
  return [];
@@ -32986,8 +33095,2283 @@ async function isDir(p) {
32986
33095
  }
32987
33096
  }
32988
33097
 
33098
+ // src/commands/landing/inspiration/index.ts
33099
+ import { defineCommand as defineCommand150 } from "citty";
33100
+
33101
+ // src/commands/landing/inspiration/add.ts
33102
+ import { defineCommand as defineCommand143 } from "citty";
33103
+
33104
+ // src/commands/landing/inspiration/shared.ts
33105
+ var INSPIRATION_HINTS = {
33106
+ 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.",
33107
+ 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."
33108
+ };
33109
+ function fidelityHint(fidelity) {
33110
+ if (fidelity == null) return null;
33111
+ if (fidelity >= 0.9) return null;
33112
+ if (fidelity >= 0.75) {
33113
+ return `Reproduction fidelity ${fidelity.toFixed(2)} \u2014 the markup is close but not exact. Trust the screenshot over the code.`;
33114
+ }
33115
+ 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.`;
33116
+ }
33117
+ function reportError(error) {
33118
+ if (error instanceof ApiError) {
33119
+ writeJson({ ok: false, error: { code: error.code, message: error.message } });
33120
+ } else {
33121
+ writeJson({
33122
+ ok: false,
33123
+ error: { code: "UNKNOWN", message: error instanceof Error ? error.message : String(error) }
33124
+ });
33125
+ }
33126
+ process.exit(1);
33127
+ }
33128
+ function splitList(value) {
33129
+ if (!value) return void 0;
33130
+ const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
33131
+ return parts.length > 0 ? parts : void 0;
33132
+ }
33133
+ function parseNumber(value) {
33134
+ if (value === void 0 || value === "") return void 0;
33135
+ const parsed = Number(value);
33136
+ return Number.isFinite(parsed) ? parsed : void 0;
33137
+ }
33138
+
33139
+ // src/commands/landing/inspiration/add.ts
33140
+ registerSchema({
33141
+ command: "landing.inspiration.add",
33142
+ 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.",
33143
+ args: {
33144
+ url: { type: "string", description: "Landing page address", required: true },
33145
+ note: { type: "string", description: "Why this page is worth keeping", required: false }
33146
+ }
33147
+ });
33148
+ var addCommand = defineCommand143({
33149
+ meta: {
33150
+ name: "add",
33151
+ description: "Add a landing page to the reference library. Example: baker landing inspiration add https://linear.app --note 'the client likes this density'"
33152
+ },
33153
+ args: {
33154
+ url: { type: "positional", description: "Landing page address", required: true },
33155
+ note: { type: "string", description: "Why this page is worth keeping", required: false }
33156
+ },
33157
+ run: async ({ args }) => {
33158
+ try {
33159
+ const data = await apiPost("/api/landing-inspiration/add", {
33160
+ url: args.url,
33161
+ note: args.note,
33162
+ favorite: true
33163
+ });
33164
+ const hints = data.alreadyKnown ? [
33165
+ "This page was already in the library, so nothing was re-studied \u2014 its sections are searchable now.",
33166
+ `Search it with: baker landing inspiration search --domain ${new URL(data.canonicalUrl).hostname}`
33167
+ ] : [
33168
+ "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.",
33169
+ "Meanwhile, search what is already in the library: baker landing inspiration search '<what you want to see>' --scope all"
33170
+ ];
33171
+ writeJson({
33172
+ ok: true,
33173
+ data: { id: data.sourceId, url: data.canonicalUrl, status: data.status, already_known: data.alreadyKnown },
33174
+ hints
33175
+ });
33176
+ } catch (error) {
33177
+ reportError(error);
33178
+ }
33179
+ }
33180
+ });
33181
+
33182
+ // src/commands/landing/inspiration/code.ts
33183
+ import { mkdir as mkdir10, writeFile as writeFile13 } from "fs/promises";
33184
+ import path29 from "path";
33185
+ import { defineCommand as defineCommand144 } from "citty";
33186
+ registerSchema({
33187
+ command: "landing.inspiration.code",
33188
+ 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.",
33189
+ args: {
33190
+ id: { type: "string", description: "Section id from search", required: true },
33191
+ full: { type: "boolean", description: "Print the markup inline as well as writing it", required: false }
33192
+ }
33193
+ });
33194
+ var codeCommand = defineCommand144({
33195
+ meta: {
33196
+ name: "code",
33197
+ 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."
33198
+ },
33199
+ args: {
33200
+ id: { type: "positional", description: "Section id from search", required: true },
33201
+ full: { type: "boolean", description: "Also print the markup inline", required: false, default: false }
33202
+ },
33203
+ run: async ({ args }) => {
33204
+ try {
33205
+ const id = args.id;
33206
+ const data = await apiGet("/api/landing-inspiration/section-code", { id });
33207
+ const dir = path29.join(process.cwd(), ".baker", "inspiration", id);
33208
+ await mkdir10(dir, { recursive: true });
33209
+ const file = path29.join(dir, "section.html");
33210
+ await writeFile13(file, data.html);
33211
+ const recorded = await recordReference(process.cwd(), {
33212
+ sectionId: id,
33213
+ sourceUrl: data.sourceUrl,
33214
+ copyStrings: data.copyStrings,
33215
+ consultedAt: (/* @__PURE__ */ new Date()).toISOString()
33216
+ });
33217
+ const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
33218
+ const fidelity = fidelityHint(data.fidelity);
33219
+ if (fidelity) hints.push(fidelity);
33220
+ if (data.fidelityNote) hints.push(data.fidelityNote);
33221
+ if (!recorded) {
33222
+ hints.push(
33223
+ "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."
33224
+ );
33225
+ }
33226
+ writeJson({
33227
+ ok: true,
33228
+ data: {
33229
+ id,
33230
+ file: path29.relative(process.cwd(), file),
33231
+ bytes: data.html.length,
33232
+ fidelity: data.fidelity,
33233
+ css_custom_properties: data.cssCustomProperties,
33234
+ reproduction_notes: data.reproductionNotes,
33235
+ adaptation_notes: data.adaptationNotes,
33236
+ source_url: data.sourceUrl,
33237
+ ...args.full ? { html: data.html } : {}
33238
+ },
33239
+ hints
33240
+ });
33241
+ } catch (error) {
33242
+ reportError(error);
33243
+ }
33244
+ }
33245
+ });
33246
+
33247
+ // src/commands/landing/inspiration/favorites.ts
33248
+ import { defineCommand as defineCommand145 } from "citty";
33249
+ registerSchema({
33250
+ command: "landing.inspiration.favorites",
33251
+ 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.",
33252
+ args: {
33253
+ type: { type: "string", description: "Comma list of section types to filter by", required: false },
33254
+ limit: { type: "number", description: "Max results (default 30)", required: false }
33255
+ }
33256
+ });
33257
+ var favoritesCommand = defineCommand145({
33258
+ meta: {
33259
+ name: "favorites",
33260
+ description: "List this company's saved reference sections. Example: baker landing inspiration favorites --type hero,pricing"
33261
+ },
33262
+ args: {
33263
+ type: { type: "string", description: "Comma list of section types", required: false },
33264
+ limit: { type: "string", description: "Max results (default 30)", required: false }
33265
+ },
33266
+ run: async ({ args }) => {
33267
+ try {
33268
+ const params = {};
33269
+ const types = splitList(args.type);
33270
+ if (types) params.type = types.join(",");
33271
+ const limit = parseNumber(args.limit);
33272
+ params.limit = String(limit ?? 30);
33273
+ const data = await apiGet("/api/landing-inspiration/favorites", params);
33274
+ const sections = Array.isArray(data?.sections) ? data.sections : [];
33275
+ writeJson({
33276
+ ok: true,
33277
+ data: {
33278
+ sections: sections.map((section) => ({
33279
+ id: section.id,
33280
+ section: section.sectionType,
33281
+ composition: section.composition,
33282
+ look: section.visualRegister,
33283
+ why_it_works: section.whyItWorks,
33284
+ domain: section.domain
33285
+ }))
33286
+ },
33287
+ hints: sections.length === 0 ? [
33288
+ "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`."
33289
+ ] : [
33290
+ "These are the client's taste signals \u2014 read them as direction, not as a component library.",
33291
+ INSPIRATION_HINTS.adapt
33292
+ ]
33293
+ });
33294
+ } catch (error) {
33295
+ reportError(error);
33296
+ }
33297
+ }
33298
+ });
33299
+ registerSchema({
33300
+ command: "landing.inspiration.favorite",
33301
+ description: "Save a reference section (or a whole page) to this company, so it shows up in the default search scope.",
33302
+ args: {
33303
+ id: { type: "string", description: "Section id, or page id with --page", required: true },
33304
+ page: { type: "boolean", description: "Treat the id as a page rather than a section", required: false },
33305
+ note: { type: "string", description: "Why this is worth keeping", required: false }
33306
+ }
33307
+ });
33308
+ var favoriteCommand = defineCommand145({
33309
+ meta: {
33310
+ name: "favorite",
33311
+ description: "Save a reference section to this company. Example: baker landing inspiration favorite k57abc\u2026"
33312
+ },
33313
+ args: {
33314
+ id: { type: "positional", description: "Section id (or page id with --page)", required: true },
33315
+ page: { type: "boolean", description: "Treat the id as a page", required: false, default: false },
33316
+ note: { type: "string", description: "Why this is worth keeping", required: false }
33317
+ },
33318
+ run: async ({ args }) => {
33319
+ try {
33320
+ const id = args.id;
33321
+ const body = args.page ? { sourceId: id } : { sectionId: id };
33322
+ const data = await apiPost("/api/landing-inspiration/favorite", {
33323
+ ...body,
33324
+ note: args.note
33325
+ });
33326
+ writeJson({ ok: true, data: { id, favorited: data.favorited } });
33327
+ } catch (error) {
33328
+ reportError(error);
33329
+ }
33330
+ }
33331
+ });
33332
+ registerSchema({
33333
+ command: "landing.inspiration.unfavorite",
33334
+ description: "Remove a reference section (or page) from this company's saved set.",
33335
+ args: {
33336
+ id: { type: "string", description: "Section id, or page id with --page", required: true },
33337
+ page: { type: "boolean", description: "Treat the id as a page rather than a section", required: false }
33338
+ }
33339
+ });
33340
+ var unfavoriteCommand = defineCommand145({
33341
+ meta: {
33342
+ name: "unfavorite",
33343
+ description: "Remove a reference section from this company's saved set. Example: baker landing inspiration unfavorite k57abc\u2026"
33344
+ },
33345
+ args: {
33346
+ id: { type: "positional", description: "Section id (or page id with --page)", required: true },
33347
+ page: { type: "boolean", description: "Treat the id as a page", required: false, default: false }
33348
+ },
33349
+ run: async ({ args }) => {
33350
+ try {
33351
+ const id = args.id;
33352
+ const body = args.page ? { sourceId: id } : { sectionId: id };
33353
+ const data = await apiPost("/api/landing-inspiration/unfavorite", body);
33354
+ writeJson({ ok: true, data: { id, favorited: data.favorited } });
33355
+ } catch (error) {
33356
+ reportError(error);
33357
+ }
33358
+ }
33359
+ });
33360
+
33361
+ // src/commands/landing/inspiration/page.ts
33362
+ import { defineCommand as defineCommand146 } from "citty";
33363
+ registerSchema({
33364
+ command: "landing.inspiration.page",
33365
+ 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.",
33366
+ args: { id: { type: "string", description: "Page id (from a search result's source id)", required: true } }
33367
+ });
33368
+ var pageCommand = defineCommand146({
33369
+ meta: {
33370
+ name: "page",
33371
+ description: "Show how a reference page sequences its sections. Example: baker landing inspiration page j91xyz\u2026 \u2014 the blueprint, not the pixels."
33372
+ },
33373
+ args: { id: { type: "positional", description: "Page id", required: true } },
33374
+ run: async ({ args }) => {
33375
+ try {
33376
+ const data = await apiGet("/api/landing-inspiration/page", { id: args.id });
33377
+ if (data.status !== "indexed") {
33378
+ writeJson({
33379
+ ok: true,
33380
+ data: { id: data.id, url: data.url, status: data.status, sections: [] },
33381
+ hints: [
33382
+ 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."
33383
+ ]
33384
+ });
33385
+ return;
33386
+ }
33387
+ writeJson({
33388
+ ok: true,
33389
+ data: {
33390
+ id: data.id,
33391
+ url: data.url,
33392
+ domain: data.domain,
33393
+ title: data.title,
33394
+ archetype: data.archetype,
33395
+ stack: data.detectedStack,
33396
+ page_fidelity: data.pageFidelity,
33397
+ blueprint: data.sectionOrder,
33398
+ sections: data.sections.map((section) => ({
33399
+ id: section.id,
33400
+ position: section.index,
33401
+ section: section.sectionType,
33402
+ composition: section.composition,
33403
+ look: section.visualRegister,
33404
+ motion: section.motionSummary,
33405
+ why_it_works: section.whyItWorks,
33406
+ craft: section.craftScore
33407
+ }))
33408
+ },
33409
+ hints: [
33410
+ "The blueprint is the section order top to bottom \u2014 the answer to 'how do good pages in this category sequence themselves'.",
33411
+ "Use `baker landing inspiration view <section id>` for any section worth a closer look.",
33412
+ INSPIRATION_HINTS.adapt
33413
+ ]
33414
+ });
33415
+ } catch (error) {
33416
+ reportError(error);
33417
+ }
33418
+ }
33419
+ });
33420
+
33421
+ // src/commands/landing/inspiration/scrape.ts
33422
+ import { defineCommand as defineCommand147 } from "citty";
33423
+
33424
+ // src/engine/landing-library/blocked.ts
33425
+ var CHALLENGE_PHRASES = [
33426
+ "just a moment",
33427
+ "attention required",
33428
+ "verify you are human",
33429
+ "checking your browser",
33430
+ "enable javascript and cookies to continue",
33431
+ "unusual traffic",
33432
+ "access denied",
33433
+ "you have been blocked",
33434
+ "request unsuccessful",
33435
+ "are you a robot",
33436
+ "security check",
33437
+ "ddos protection",
33438
+ "captcha"
33439
+ ];
33440
+ var CHALLENGE_MARKERS = ["cf-browser-verification", "cf_chl_", "px-captcha", "_incapsula_", "distil_r_captcha"];
33441
+ function detectBlockedPage(page) {
33442
+ if (page.status !== null && page.status >= 400) {
33443
+ return {
33444
+ code: "HTTP_ERROR",
33445
+ message: `The site returned ${page.status} instead of the page \u2014 it may be blocking automated visits.`
33446
+ };
33447
+ }
33448
+ const haystack = `${page.title}
33449
+ ${page.bodyText.slice(0, 2e3)}`.toLowerCase();
33450
+ const phrase = CHALLENGE_PHRASES.find((candidate) => haystack.includes(candidate));
33451
+ if (phrase) {
33452
+ return { code: "BOT_CHALLENGE", message: "The site showed a security check instead of the page." };
33453
+ }
33454
+ const html = page.html?.toLowerCase() ?? "";
33455
+ if (CHALLENGE_MARKERS.some((marker) => html.includes(marker))) {
33456
+ return { code: "BOT_CHALLENGE", message: "The site showed a security check instead of the page." };
33457
+ }
33458
+ return null;
33459
+ }
33460
+ var BlockedPageError = class extends Error {
33461
+ code;
33462
+ constructor(blocked) {
33463
+ super(blocked.message);
33464
+ this.name = "BlockedPageError";
33465
+ this.code = blocked.code;
33466
+ }
33467
+ };
33468
+
33469
+ // src/engine/landing-library/run.ts
33470
+ import { mkdir as mkdir11, writeFile as writeFile15 } from "fs/promises";
33471
+ import path31 from "path";
33472
+
33473
+ // src/engine/landing-library/browser.ts
33474
+ import { createRequire } from "module";
33475
+ var require_ = createRequire(import.meta.url);
33476
+ var pwSpecifier = ["play", "wright"].join("");
33477
+ var DESKTOP_VIEWPORT = { width: 1440, height: 900 };
33478
+ var MOBILE_VIEWPORT = { width: 390, height: 844 };
33479
+ var DEVICE_SCALE_FACTOR = 2;
33480
+ async function launchBrowser() {
33481
+ const playwright = require_(pwSpecifier);
33482
+ return await playwright.chromium.launch({
33483
+ headless: true,
33484
+ args: ["--hide-scrollbars", "--disable-blink-features=AutomationControlled", "--mute-audio"]
33485
+ });
33486
+ }
33487
+ async function newPage(browser, viewport, opts = { motion: false }) {
33488
+ const context = await browser.newContext({
33489
+ viewport,
33490
+ deviceScaleFactor: DEVICE_SCALE_FACTOR,
33491
+ // Still captures freeze motion so screenshots are reproducible; the motion
33492
+ // pass re-opens a context with animation enabled.
33493
+ reducedMotion: opts.motion ? "no-preference" : "reduce",
33494
+ 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"
33495
+ });
33496
+ const page = await context.newPage();
33497
+ return { context, page };
33498
+ }
33499
+
33500
+ // src/engine/landing-library/usedCss.ts
33501
+ async function collectUsedCss(page, selector) {
33502
+ const collected = await page.evaluate(inPageCollectUsedCss, selector);
33503
+ const extra = [];
33504
+ for (const href of collected.unreadableHrefs) {
33505
+ const text = await fetchStylesheet(page, href);
33506
+ if (text) extra.push(`/* ${href} */
33507
+ ${text}`);
33508
+ }
33509
+ return { ...collected, css: [collected.css, ...extra].filter(Boolean).join("\n\n") };
33510
+ }
33511
+ async function fetchStylesheet(page, href) {
33512
+ try {
33513
+ const response = await page.request.get(href, { timeout: 1e4 });
33514
+ if (!response.ok()) return null;
33515
+ return await response.text();
33516
+ } catch {
33517
+ return null;
33518
+ }
33519
+ }
33520
+ var inPageCollectUsedCss = (selector) => {
33521
+ const root = document.querySelector(selector);
33522
+ const empty = {
33523
+ css: "",
33524
+ variables: "",
33525
+ inheritedSeed: "",
33526
+ unreadableHrefs: [],
33527
+ stats: { totalRules: 0, keptRules: 0, fontFaces: 0, keyframes: 0 }
33528
+ };
33529
+ if (!root) return empty;
33530
+ const INHERITED = [
33531
+ "color",
33532
+ "font-family",
33533
+ "font-size",
33534
+ "font-weight",
33535
+ "line-height",
33536
+ "letter-spacing",
33537
+ "text-align",
33538
+ "background-color",
33539
+ "-webkit-font-smoothing"
33540
+ ];
33541
+ const unreadableHrefs = [];
33542
+ const kept = [];
33543
+ const fontFaces = [];
33544
+ const keyframesByName = /* @__PURE__ */ new Map();
33545
+ let totalRules = 0;
33546
+ let keptStyleRules = 0;
33547
+ const matchesInSection = (selectorText) => {
33548
+ for (const part of selectorText.split(",")) {
33549
+ const base = part.replace(/::[a-zA-Z-]+(\([^)]*\))?/g, "").replace(/:(hover|focus|focus-visible|focus-within|active|visited|target|checked|disabled)\b/g, "").trim();
33550
+ if (!base) continue;
33551
+ try {
33552
+ if (root.matches(base) || root.querySelector(base)) return true;
33553
+ } catch {
33554
+ return true;
33555
+ }
33556
+ }
33557
+ return false;
33558
+ };
33559
+ const walk = (rules, sink) => {
33560
+ for (const rule of Array.from(rules)) {
33561
+ totalRules++;
33562
+ if (rule instanceof CSSStyleRule) {
33563
+ if (matchesInSection(rule.selectorText)) {
33564
+ sink.push(rule.cssText);
33565
+ keptStyleRules++;
33566
+ }
33567
+ continue;
33568
+ }
33569
+ if (rule instanceof CSSFontFaceRule) {
33570
+ fontFaces.push(rule.cssText);
33571
+ continue;
33572
+ }
33573
+ if (rule instanceof CSSKeyframesRule) {
33574
+ keyframesByName.set(rule.name, rule.cssText);
33575
+ continue;
33576
+ }
33577
+ const grouping = rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule || typeof CSSLayerBlockRule !== "undefined" && rule instanceof CSSLayerBlockRule || typeof CSSContainerRule !== "undefined" && rule instanceof CSSContainerRule;
33578
+ if (grouping) {
33579
+ const inner = [];
33580
+ walk(rule.cssRules, inner);
33581
+ if (inner.length === 0) continue;
33582
+ const condition = rule.conditionText ?? "";
33583
+ const prelude = rule instanceof CSSMediaRule ? `@media ${condition}` : rule.cssText.split("{")[0]?.trim();
33584
+ sink.push(`${prelude} {
33585
+ ${inner.join("\n")}
33586
+ }`);
33587
+ }
33588
+ }
33589
+ };
33590
+ for (const sheet of Array.from(document.styleSheets)) {
33591
+ const owner = sheet.ownerNode;
33592
+ if (owner?.hasAttribute?.("data-baker-freeze")) continue;
33593
+ try {
33594
+ walk(sheet.cssRules, kept);
33595
+ } catch {
33596
+ if (sheet.href) unreadableHrefs.push(sheet.href);
33597
+ }
33598
+ }
33599
+ const keptText = kept.join("\n");
33600
+ const usedKeyframes = [];
33601
+ for (const [name, text] of keyframesByName) {
33602
+ if (new RegExp(`(^|[\\s:,])${name}([\\s;,}]|$)`).test(keptText)) usedKeyframes.push(text);
33603
+ }
33604
+ const rootStyle = getComputedStyle(root);
33605
+ const variableDeclarations = [];
33606
+ for (const property of Array.from(rootStyle)) {
33607
+ if (!property.startsWith("--")) continue;
33608
+ const value = rootStyle.getPropertyValue(property).trim();
33609
+ if (value) variableDeclarations.push(` ${property}: ${value};`);
33610
+ }
33611
+ const seed = INHERITED.map((property) => ` ${property}: ${rootStyle.getPropertyValue(property)};`).join("\n");
33612
+ return {
33613
+ css: [...fontFaces, ...usedKeyframes, ...kept].join("\n"),
33614
+ variables: variableDeclarations.length ? `:root {
33615
+ ${variableDeclarations.join("\n")}
33616
+ }` : "",
33617
+ inheritedSeed: seed,
33618
+ unreadableHrefs,
33619
+ stats: {
33620
+ totalRules,
33621
+ keptRules: keptStyleRules,
33622
+ fontFaces: fontFaces.length,
33623
+ keyframes: usedKeyframes.length
33624
+ }
33625
+ };
33626
+ };
33627
+
33628
+ // src/engine/landing-library/bundle.ts
33629
+ async function buildSectionBundle(page, selector, pageUrl) {
33630
+ const used = await collectUsedCss(page, selector);
33631
+ const extracted = await page.evaluate(inPageExtractMarkup, {
33632
+ selector,
33633
+ pageUrl,
33634
+ rootAttribute: SECTION_ROOT_ATTRIBUTE
33635
+ });
33636
+ const css = [
33637
+ "*, *::before, *::after { box-sizing: border-box; }",
33638
+ "html, body { margin: 0; padding: 0; }",
33639
+ used.variables,
33640
+ `body {
33641
+ ${used.inheritedSeed}
33642
+ }`,
33643
+ used.css
33644
+ ].filter(Boolean).join("\n\n");
33645
+ const html = `<!doctype html>
33646
+ <html ${extracted.rootAttributes}>
33647
+ <head>
33648
+ <meta charset="utf-8">
33649
+ <meta name="viewport" content="width=device-width, initial-scale=1">
33650
+ <title>Section from ${escapeHtml2(extracted.host)}</title>
33651
+ <style>
33652
+ ${css}
33653
+ </style>
33654
+ </head>
33655
+ <body ${extracted.bodyAttributes}>
33656
+ ${wrapInLayoutContext(extracted.html, extracted.layoutContext)}
33657
+ </body>
33658
+ </html>
33659
+ `;
33660
+ return { html, css, assetUrls: extracted.assetUrls, stats: used.stats };
33661
+ }
33662
+ function escapeHtml2(value) {
33663
+ return value.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c] ?? c);
33664
+ }
33665
+ var SECTION_ROOT_ATTRIBUTE = "data-baker-section-root";
33666
+ function wrapInLayoutContext(html, context) {
33667
+ let wrapped = context.width > 0 && context.width < context.viewportWidth ? `<div style="width:${context.width}px;margin:0 auto;">
33668
+ ${html}
33669
+ </div>` : html;
33670
+ for (const ancestor of [...context.ancestors].reverse()) {
33671
+ const attributes = ancestor.attributes ? ` ${ancestor.attributes}` : "";
33672
+ const container = ancestor.containerType && ancestor.containerType !== "normal" ? `container-type:${ancestor.containerType};${ancestor.containerName && ancestor.containerName !== "none" ? `container-name:${ancestor.containerName};` : ""}` : "";
33673
+ wrapped = `<${ancestor.tag}${attributes} style="${NEUTRALIZED_ANCESTOR_STYLE}${container}">
33674
+ ${wrapped}
33675
+ </${ancestor.tag}>`;
33676
+ }
33677
+ return wrapped;
33678
+ }
33679
+ var NEUTRALIZED_ANCESTOR_STYLE = [
33680
+ "display:block !important",
33681
+ "position:static !important",
33682
+ "margin:0 !important",
33683
+ "padding:0 !important",
33684
+ "border:0 !important",
33685
+ "width:auto !important",
33686
+ "min-width:0 !important",
33687
+ "max-width:none !important",
33688
+ "height:auto !important",
33689
+ "min-height:0 !important",
33690
+ "max-height:none !important",
33691
+ "transform:none !important",
33692
+ "overflow:visible !important",
33693
+ ""
33694
+ ].join(";");
33695
+ var inPageExtractMarkup = ({
33696
+ selector,
33697
+ pageUrl,
33698
+ rootAttribute
33699
+ }) => {
33700
+ const root = document.querySelector(selector);
33701
+ const emptyContext = { ancestors: [], width: 0, viewportWidth: 0 };
33702
+ const serializeAttributes = (element) => {
33703
+ if (!element) return "";
33704
+ return Array.from(element.attributes).filter((attribute) => attribute.name !== "style").map((attribute) => `${attribute.name}="${attribute.value.replace(/"/g, "&quot;")}"`).join(" ");
33705
+ };
33706
+ const rootAttributes = serializeAttributes(document.documentElement) || 'lang="en"';
33707
+ const bodyAttributes = serializeAttributes(document.body);
33708
+ if (!root) {
33709
+ return {
33710
+ html: "",
33711
+ assetUrls: [],
33712
+ host: "",
33713
+ layoutContext: emptyContext,
33714
+ rootAttributes,
33715
+ bodyAttributes
33716
+ };
33717
+ }
33718
+ const ancestors = [];
33719
+ for (let ancestor = root.parentElement; ancestor && ancestor !== document.body; ) {
33720
+ const style = getComputedStyle(ancestor);
33721
+ ancestors.unshift({
33722
+ tag: /^[a-zA-Z][a-zA-Z0-9-]*$/.test(ancestor.tagName) ? ancestor.tagName.toLowerCase() : "div",
33723
+ 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(" "),
33724
+ containerType: style.containerType,
33725
+ containerName: style.containerName
33726
+ });
33727
+ ancestor = ancestor.parentElement;
33728
+ }
33729
+ const layoutContext = {
33730
+ ancestors,
33731
+ width: Math.round(root.getBoundingClientRect().width),
33732
+ viewportWidth: window.innerWidth
33733
+ };
33734
+ const absolute = (value) => {
33735
+ try {
33736
+ return new URL(value, pageUrl).href;
33737
+ } catch {
33738
+ return value;
33739
+ }
33740
+ };
33741
+ const clone = root.cloneNode(true);
33742
+ clone.setAttribute(rootAttribute, "");
33743
+ const assetUrls = /* @__PURE__ */ new Set();
33744
+ for (const element of [clone, ...Array.from(clone.querySelectorAll("*"))]) {
33745
+ for (const attribute of ["src", "href", "poster"]) {
33746
+ const value = element.getAttribute(attribute);
33747
+ if (!value || value.startsWith("data:") || value.startsWith("#")) continue;
33748
+ const url = absolute(value);
33749
+ element.setAttribute(attribute, url);
33750
+ if (attribute !== "href" || element.tagName === "LINK") assetUrls.add(url);
33751
+ }
33752
+ const srcset = element.getAttribute("srcset");
33753
+ if (srcset) {
33754
+ element.setAttribute(
33755
+ "srcset",
33756
+ srcset.split(",").map((candidate) => {
33757
+ const [url, descriptor] = candidate.trim().split(/\s+/, 2);
33758
+ if (!url) return candidate;
33759
+ const resolved = absolute(url);
33760
+ assetUrls.add(resolved);
33761
+ return descriptor ? `${resolved} ${descriptor}` : resolved;
33762
+ }).join(", ")
33763
+ );
33764
+ }
33765
+ const style = element.getAttribute("style");
33766
+ if (style?.includes("url(")) {
33767
+ element.setAttribute(
33768
+ "style",
33769
+ style.replace(/url\((['"]?)([^'")]+)\1\)/g, (_match, quote, url) => {
33770
+ if (url.startsWith("data:")) return `url(${quote}${url}${quote})`;
33771
+ const resolved = absolute(url);
33772
+ assetUrls.add(resolved);
33773
+ return `url(${quote}${resolved}${quote})`;
33774
+ })
33775
+ );
33776
+ }
33777
+ }
33778
+ return {
33779
+ html: clone.outerHTML,
33780
+ assetUrls: Array.from(assetUrls),
33781
+ host: location.host,
33782
+ layoutContext,
33783
+ rootAttributes,
33784
+ bodyAttributes
33785
+ };
33786
+ };
33787
+
33788
+ // src/engine/landing-library/capture.ts
33789
+ async function captureSection(page, section) {
33790
+ const locator = page.locator(section.selector).first();
33791
+ if (await locator.count().catch(() => 0)) {
33792
+ const shot = await locator.screenshot({ type: "png", timeout: 15e3 }).catch(() => null);
33793
+ if (shot) return shot;
33794
+ }
33795
+ const doc = await page.evaluate(() => ({
33796
+ width: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),
33797
+ height: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0)
33798
+ }));
33799
+ const width = Math.min(section.rect.width, doc.width - section.rect.left);
33800
+ const height = Math.min(section.rect.height, doc.height - section.rect.top);
33801
+ if (width <= 0 || height <= 0) return null;
33802
+ return await page.screenshot({
33803
+ type: "png",
33804
+ clip: { x: section.rect.left, y: section.rect.top, width, height },
33805
+ timeout: 15e3
33806
+ }).catch(() => null);
33807
+ }
33808
+ async function captureSectionOnMobile(page, section) {
33809
+ const locator = page.locator(section.selector).first();
33810
+ if (!await locator.count().catch(() => 0)) return null;
33811
+ if (!await locator.isVisible().catch(() => false)) return null;
33812
+ return await locator.screenshot({ type: "png", timeout: 15e3 }).catch(() => null);
33813
+ }
33814
+
33815
+ // src/engine/landing-library/fidelity.ts
33816
+ import sharp4 from "sharp";
33817
+ var COMPARISON_SIZE = 32;
33818
+ function pixelSimilarity(a, b) {
33819
+ const length = Math.min(a.length, b.length);
33820
+ if (length === 0) return 0;
33821
+ let total = 0;
33822
+ for (let i = 0; i < length; i++) {
33823
+ total += Math.abs((a[i] ?? 0) - (b[i] ?? 0));
33824
+ }
33825
+ return 1 - total / (length * 255);
33826
+ }
33827
+ async function scoreFidelity(live, rendered) {
33828
+ const [liveMeta, renderedMeta] = await Promise.all([sharp4(live).metadata(), sharp4(rendered).metadata()]);
33829
+ const liveSize = { width: liveMeta.width ?? 0, height: liveMeta.height ?? 0 };
33830
+ const renderedSize = { width: renderedMeta.width ?? 0, height: renderedMeta.height ?? 0 };
33831
+ const [livePixels, renderedPixels] = await Promise.all([toGreyGrid(live), toGreyGrid(rendered)]);
33832
+ const score = pixelSimilarity(livePixels, renderedPixels);
33833
+ const heightRatio = liveSize.height > 0 && renderedSize.height > 0 ? Math.min(liveSize.height, renderedSize.height) / Math.max(liveSize.height, renderedSize.height) : 0;
33834
+ return {
33835
+ score,
33836
+ liveSize,
33837
+ renderedSize,
33838
+ ...heightRatio < 0.8 ? { note: `height differs by ${Math.round((1 - heightRatio) * 100)}% \u2014 the bundle reflowed` } : {}
33839
+ };
33840
+ }
33841
+ async function toGreyGrid(image) {
33842
+ const raw = await sharp4(image).greyscale().resize(COMPARISON_SIZE, COMPARISON_SIZE, { fit: "fill" }).raw().toBuffer();
33843
+ return new Uint8Array(raw);
33844
+ }
33845
+
33846
+ // src/engine/landing-library/motion.ts
33847
+ var EMPTY_MOTION = {
33848
+ hasMotion: false,
33849
+ // `none` rather than "no motion": it is the `MOTION_KINDS` member for this,
33850
+ // so a section with no movement is findable by the same facet as everything
33851
+ // else instead of being a special case only prose describes.
33852
+ summary: "none",
33853
+ entrance: [],
33854
+ hover: [],
33855
+ scroll: [],
33856
+ loop: [],
33857
+ libraries: [],
33858
+ respectsReducedMotion: false
33859
+ };
33860
+ function isWorthFilming(motion) {
33861
+ return motion.entrance.length + motion.scroll.length + motion.loop.length > 0;
33862
+ }
33863
+ async function collectMotion(page, selector) {
33864
+ const raw = await page.evaluate(inPageCollectMotion, selector).catch(() => null);
33865
+ if (!raw) return EMPTY_MOTION;
33866
+ return { ...raw, summary: summarizeMotion(raw) };
33867
+ }
33868
+ var HOVER_TRANSFORMS = /* @__PURE__ */ new Set(["lift", "grow", "tilt", "shift", "spin", "shadow", "animate"]);
33869
+ var KEYFRAME_PATTERNS = [
33870
+ [/marquee|ticker|scroll(ing)?-?(x|left|right)/, "marquee"],
33871
+ [/parallax/, "parallax"],
33872
+ [/sticky|pin(ned)?/, "sticky-pin"],
33873
+ [/count(er|up)/, "counter"],
33874
+ [/zoom|scale|pulse|grow/, "zoom"],
33875
+ [/fade|opacity|appear/, "fade"],
33876
+ [/slide|translate|in-?(left|right|up|down)/, "slide-in"]
33877
+ ];
33878
+ function toMotionKinds(kind, channel) {
33879
+ if (channel === "hover") return [HOVER_TRANSFORMS.has(kind) ? "hover-lift" : "fade"];
33880
+ const name = kind.toLowerCase();
33881
+ const matched = KEYFRAME_PATTERNS.filter(([pattern]) => pattern.test(name)).map(([, motionKind]) => motionKind);
33882
+ if (matched.length > 0) return matched;
33883
+ return [channel === "loop" ? "marquee" : "scroll-reveal"];
33884
+ }
33885
+ function motionKindsOf(motion) {
33886
+ const kinds = /* @__PURE__ */ new Set();
33887
+ const collect = (effects, channel) => {
33888
+ for (const effect of effects) {
33889
+ for (const kind of toMotionKinds(effect.kind, channel)) kinds.add(kind);
33890
+ }
33891
+ };
33892
+ collect(motion.entrance, "entrance");
33893
+ collect(motion.hover, "hover");
33894
+ collect(motion.scroll, "scroll");
33895
+ collect(motion.loop, "loop");
33896
+ const delays = new Set(motion.entrance.map((effect) => effect.delayMs ?? 0).filter((ms) => ms > 0));
33897
+ if (delays.size > 1) kinds.add("stagger");
33898
+ return [...kinds];
33899
+ }
33900
+ function summarizeMotion(motion) {
33901
+ const kinds = motionKindsOf(motion);
33902
+ if (kinds.length === 0 && motion.libraries.length === 0) return "none";
33903
+ const parts = [...kinds];
33904
+ if (motion.libraries.length > 0) parts.push(`via ${motion.libraries.join("/")}`);
33905
+ return parts.join(", ") + (motion.respectsReducedMotion ? "" : " (ignores reduced-motion)");
33906
+ }
33907
+ var inPageCollectMotion = (selector) => {
33908
+ const root = document.querySelector(selector);
33909
+ const empty = {
33910
+ hasMotion: false,
33911
+ entrance: [],
33912
+ hover: [],
33913
+ scroll: [],
33914
+ loop: [],
33915
+ libraries: [],
33916
+ respectsReducedMotion: false
33917
+ };
33918
+ if (!root) return empty;
33919
+ const entrance = [];
33920
+ const hover = [];
33921
+ const scroll = [];
33922
+ const loop = [];
33923
+ const keyframesByName = /* @__PURE__ */ new Map();
33924
+ let respectsReducedMotion = false;
33925
+ const inSection = (selectorText) => {
33926
+ for (const part of selectorText.split(",")) {
33927
+ const base = part.replace(/::[a-zA-Z-]+(\([^)]*\))?/g, "").replace(/:(hover|focus|focus-visible|focus-within|active|visited|target|checked|disabled)\b/g, "").trim();
33928
+ if (!base) continue;
33929
+ try {
33930
+ if (root.matches(base) || root.querySelector(base)) return true;
33931
+ } catch {
33932
+ return true;
33933
+ }
33934
+ }
33935
+ return false;
33936
+ };
33937
+ const toMs = (value) => {
33938
+ const first = value.split(",")[0]?.trim() ?? "";
33939
+ if (first.endsWith("ms")) return Number.parseFloat(first);
33940
+ if (first.endsWith("s")) return Number.parseFloat(first) * 1e3;
33941
+ return void 0;
33942
+ };
33943
+ const classifyKeyframes = (body) => {
33944
+ const text = body.toLowerCase();
33945
+ const fades = /opacity\s*:\s*0(\.0+)?\s*[;}]/.test(text);
33946
+ if (/translatey\(\s*-?\d/.test(text)) {
33947
+ const upward = /translatey\(\s*(\d|\.)/.test(text);
33948
+ return fades ? upward ? "fade-up" : "fade-down" : upward ? "slide-up" : "slide-down";
33949
+ }
33950
+ if (/translatex\(\s*-?\d/.test(text)) return fades ? "fade-in-x" : "slide-in-x";
33951
+ if (/scale\(/.test(text)) return fades ? "fade-zoom" : "zoom";
33952
+ if (/rotate\(/.test(text)) return "spin";
33953
+ if (fades) return "fade";
33954
+ return "animate";
33955
+ };
33956
+ const classifyHover = (style) => {
33957
+ const transform = style.transform ?? "";
33958
+ if (/translatey\(\s*-/i.test(transform)) return "lift";
33959
+ if (/scale\(\s*(1\.\d|[2-9])/i.test(transform)) return "grow";
33960
+ if (/rotate\(/i.test(transform)) return "tilt";
33961
+ if (transform && transform !== "none") return "shift";
33962
+ if (style.boxShadow) return "shadow";
33963
+ if (style.opacity) return "dim";
33964
+ if (style.backgroundColor || style.color) return "recolor";
33965
+ return null;
33966
+ };
33967
+ const readStyleRule = (rule, insideScrollTimeline) => {
33968
+ const style = rule.style;
33969
+ const isHover = /:hover\b/.test(rule.selectorText);
33970
+ if (isHover) {
33971
+ const kind = classifyHover(style);
33972
+ if (!kind || !inSection(rule.selectorText)) return;
33973
+ hover.push({
33974
+ kind,
33975
+ selector: rule.selectorText.slice(0, 120),
33976
+ durationMs: toMs(style.transitionDuration ?? ""),
33977
+ easing: style.transitionTimingFunction || void 0
33978
+ });
33979
+ return;
33980
+ }
33981
+ const animationName = style.animationName;
33982
+ if (!animationName || animationName === "none") return;
33983
+ if (!inSection(rule.selectorText)) return;
33984
+ const effect = {
33985
+ kind: animationName,
33986
+ selector: rule.selectorText.slice(0, 120),
33987
+ durationMs: toMs(style.animationDuration ?? ""),
33988
+ delayMs: toMs(style.animationDelay ?? ""),
33989
+ easing: style.animationTimingFunction || void 0
33990
+ };
33991
+ const infinite = (style.animationIterationCount ?? "").includes("infinite");
33992
+ const scrollDriven = insideScrollTimeline || Boolean(style.getPropertyValue("animation-timeline"));
33993
+ if (scrollDriven) scroll.push(effect);
33994
+ else if (infinite) loop.push(effect);
33995
+ else entrance.push(effect);
33996
+ };
33997
+ const walk = (rules, insideScrollTimeline) => {
33998
+ for (const rule of Array.from(rules)) {
33999
+ if (rule instanceof CSSKeyframesRule) {
34000
+ keyframesByName.set(rule.name, rule.cssText);
34001
+ continue;
34002
+ }
34003
+ if (rule instanceof CSSStyleRule) {
34004
+ try {
34005
+ readStyleRule(rule, insideScrollTimeline);
34006
+ } catch {
34007
+ }
34008
+ continue;
34009
+ }
34010
+ if (rule instanceof CSSMediaRule) {
34011
+ if (rule.conditionText.includes("prefers-reduced-motion")) {
34012
+ respectsReducedMotion = true;
34013
+ continue;
34014
+ }
34015
+ walk(rule.cssRules, insideScrollTimeline);
34016
+ continue;
34017
+ }
34018
+ const grouping = rule;
34019
+ if (grouping.cssRules) walk(grouping.cssRules, insideScrollTimeline);
34020
+ }
34021
+ };
34022
+ for (const sheet of Array.from(document.styleSheets)) {
34023
+ if (sheet.ownerNode?.hasAttribute?.("data-baker-freeze")) continue;
34024
+ try {
34025
+ walk(sheet.cssRules, false);
34026
+ } catch {
34027
+ }
34028
+ }
34029
+ for (const effect of [...entrance, ...loop, ...scroll]) {
34030
+ const body = keyframesByName.get(effect.kind);
34031
+ if (!body) continue;
34032
+ const classified = classifyKeyframes(body);
34033
+ effect.kind = loop.includes(effect) && classified.startsWith("slide") ? "marquee" : classified;
34034
+ }
34035
+ const libraries = [];
34036
+ const scoped = window ?? {};
34037
+ if (scoped.gsap || document.querySelector("[data-gsap]")) libraries.push("gsap");
34038
+ if (document.querySelector("[data-framer-name], [data-projection-id]")) libraries.push("framer-motion");
34039
+ if (document.querySelector("[data-aos]")) libraries.push("aos");
34040
+ if (scoped.Lenis || document.querySelector("[data-lenis]")) libraries.push("lenis");
34041
+ if (document.querySelector("[data-scroll], [data-scroll-container]")) libraries.push("locomotive");
34042
+ return {
34043
+ hasMotion: entrance.length + hover.length + scroll.length + loop.length + libraries.length > 0,
34044
+ entrance: entrance.slice(0, 12),
34045
+ hover: hover.slice(0, 12),
34046
+ scroll: scroll.slice(0, 12),
34047
+ loop: loop.slice(0, 12),
34048
+ libraries,
34049
+ respectsReducedMotion
34050
+ };
34051
+ };
34052
+
34053
+ // src/engine/landing-library/motionTake.ts
34054
+ import sharp5 from "sharp";
34055
+
34056
+ // src/engine/landing-library/prepare.ts
34057
+ var CONSENT_SELECTORS = [
34058
+ "#onetrust-accept-btn-handler",
34059
+ "#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll",
34060
+ "button#didomi-notice-agree-button",
34061
+ "[aria-label='Accept all']",
34062
+ "[data-testid='uc-accept-all-button']",
34063
+ ".cc-allow",
34064
+ ".cookie-accept"
34065
+ ];
34066
+ var CONSENT_TEXTS = ["Accept all", "Accept All", "Allow all", "I agree", "Got it", "Aceptar todo"];
34067
+ var CONSENT_HOSTS = [
34068
+ "transcend-cdn.com",
34069
+ "cookielaw.org",
34070
+ "onetrust.com",
34071
+ "cookiebot.com",
34072
+ "osano.com",
34073
+ "trustarc.com",
34074
+ "truste.com",
34075
+ "usercentrics.eu",
34076
+ "didomi.io",
34077
+ "privacy-center.org",
34078
+ "iubenda.com",
34079
+ "termly.io",
34080
+ "cookieyes.com",
34081
+ "sp-prod.net",
34082
+ "quantcast.com",
34083
+ "consensu.org",
34084
+ "ketch.com",
34085
+ "secureprivacy.ai",
34086
+ "civicuk.com"
34087
+ ];
34088
+ async function blockConsentManagers(page) {
34089
+ await page.route("**/*", (route) => {
34090
+ let host = "";
34091
+ try {
34092
+ host = new URL(route.request().url()).host;
34093
+ } catch {
34094
+ return route.continue();
34095
+ }
34096
+ const isConsentVendor = CONSENT_HOSTS.some((vendor) => host === vendor || host.endsWith(`.${vendor}`));
34097
+ return isConsentVendor ? route.abort() : route.continue();
34098
+ });
34099
+ }
34100
+ var ignore = () => void 0;
34101
+ async function preparePage(page, url, timeoutMs) {
34102
+ const response = await page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
34103
+ const status = response?.status() ?? null;
34104
+ await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(ignore);
34105
+ await dismissConsent(page, 4e3);
34106
+ await scrollThroughPage(page);
34107
+ await dismissConsent(page, 1e3);
34108
+ await page.evaluate(async () => {
34109
+ await document.fonts.ready;
34110
+ });
34111
+ await freezeMotion(page);
34112
+ await unpinOverlays(page);
34113
+ const measured = await page.evaluate(() => ({
34114
+ finalUrl: location.href,
34115
+ title: document.title,
34116
+ documentHeight: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0),
34117
+ bodyText: (document.body?.innerText ?? "").slice(0, 2e3)
34118
+ }));
34119
+ return { ...measured, status };
34120
+ }
34121
+ async function settleConsent(page) {
34122
+ await dismissConsent(page, 4e3);
34123
+ await page.mouse.wheel(0, 400).catch(ignore);
34124
+ await page.waitForTimeout(1500);
34125
+ await dismissConsent(page, 2e3);
34126
+ await page.evaluate(() => window.scrollTo(0, 0)).catch(ignore);
34127
+ }
34128
+ async function dismissConsent(page, waitForBannerMs) {
34129
+ await page.locator(CONSENT_SELECTORS.join(", ")).first().waitFor({ state: "attached", timeout: waitForBannerMs }).catch(ignore);
34130
+ for (const selector of CONSENT_SELECTORS) {
34131
+ const found = page.locator(selector).first();
34132
+ if (!await found.count().catch(() => 0)) continue;
34133
+ await found.click({ timeout: 2e3, force: true }).catch(ignore);
34134
+ await page.waitForTimeout(400);
34135
+ return;
34136
+ }
34137
+ for (const text of CONSENT_TEXTS) {
34138
+ const button = page.getByRole("button", { name: text, exact: false }).first();
34139
+ if (!await button.count().catch(() => 0)) continue;
34140
+ if (!await button.isVisible().catch(() => false)) continue;
34141
+ await button.click({ timeout: 2e3, force: true }).catch(ignore);
34142
+ await page.waitForTimeout(400);
34143
+ return;
34144
+ }
34145
+ }
34146
+ async function scrollThroughPage(page) {
34147
+ await page.evaluate(async () => {
34148
+ const step = 600;
34149
+ const pause = () => new Promise((resolve5) => setTimeout(resolve5, 250));
34150
+ for (let i = 0; i < 40; i++) {
34151
+ window.scrollBy(0, step);
34152
+ await pause();
34153
+ const reachedBottom = window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 2;
34154
+ if (reachedBottom) break;
34155
+ }
34156
+ window.scrollTo(0, 0);
34157
+ await pause();
34158
+ });
34159
+ await page.waitForTimeout(500);
34160
+ }
34161
+ var MAX_HEADER_HEIGHT = 220;
34162
+ async function unpinOverlays(page) {
34163
+ await page.evaluate((maxHeaderHeight) => {
34164
+ window.scrollTo(0, 0);
34165
+ for (const element of Array.from(document.querySelectorAll("*"))) {
34166
+ const position = getComputedStyle(element).position;
34167
+ if (position === "sticky") {
34168
+ element.style.setProperty("position", "static", "important");
34169
+ continue;
34170
+ }
34171
+ if (position !== "fixed") continue;
34172
+ const rect = element.getBoundingClientRect();
34173
+ const isTopAnchoredHeader = rect.top <= 8 && rect.height > 0 && rect.height <= maxHeaderHeight;
34174
+ if (isTopAnchoredHeader) {
34175
+ element.style.setProperty("position", "absolute", "important");
34176
+ element.style.setProperty("bottom", "auto", "important");
34177
+ } else {
34178
+ element.style.setProperty("display", "none", "important");
34179
+ }
34180
+ }
34181
+ }, MAX_HEADER_HEIGHT);
34182
+ await page.waitForTimeout(250);
34183
+ }
34184
+ async function freezeMotion(page) {
34185
+ await page.evaluate(() => {
34186
+ const highestTimer = window.setTimeout(() => void 0, 0);
34187
+ for (let id = 1; id <= highestTimer; id++) window.clearInterval(id);
34188
+ const style = document.createElement("style");
34189
+ style.setAttribute("data-baker-freeze", "true");
34190
+ style.textContent = `*, *::before, *::after {
34191
+ animation-play-state: paused !important;
34192
+ animation-delay: 0s !important;
34193
+ transition: none !important;
34194
+ }`;
34195
+ document.head.appendChild(style);
34196
+ for (const video of Array.from(document.querySelectorAll("video"))) {
34197
+ video.pause();
34198
+ }
34199
+ });
34200
+ await page.waitForTimeout(250);
34201
+ }
34202
+
34203
+ // src/engine/landing-library/geometry.ts
34204
+ function adaptiveGapThreshold(bands) {
34205
+ const sorted = [...bands].sort((a, b) => a.top - b.top);
34206
+ const gaps = [];
34207
+ for (let i = 0; i < sorted.length - 1; i++) {
34208
+ const current = sorted[i];
34209
+ const next = sorted[i + 1];
34210
+ if (!current || !next) continue;
34211
+ if (next.top > current.bottom) {
34212
+ const gap = next.top - current.bottom;
34213
+ if (gap < 200) gaps.push(gap);
34214
+ }
34215
+ }
34216
+ gaps.sort((a, b) => a - b);
34217
+ const p75 = gaps[Math.floor(gaps.length * 0.75)];
34218
+ if (p75 === void 0) return 15;
34219
+ return Math.max(10, Math.min(50, p75));
34220
+ }
34221
+ function bandsCollide(a, b, margin, maxGap) {
34222
+ const shrunkA = { top: a.top + margin, bottom: a.bottom - margin };
34223
+ const shrunkB = { top: b.top + margin, bottom: b.bottom - margin };
34224
+ const overlaps = shrunkA.top <= shrunkB.bottom && shrunkA.bottom >= shrunkB.top || shrunkB.top <= shrunkA.bottom && shrunkB.bottom >= shrunkA.top;
34225
+ if (overlaps) return true;
34226
+ const gap = Math.min(Math.abs(shrunkA.bottom - shrunkB.top), Math.abs(shrunkB.bottom - shrunkA.top));
34227
+ return gap <= maxGap;
34228
+ }
34229
+ function shouldPromoteToParent(group) {
34230
+ const isRunt = group.childCount < 3 || group.height < 80;
34231
+ return isRunt && group.parentHeight < 1600;
34232
+ }
34233
+
34234
+ // src/engine/landing-library/segment.ts
34235
+ async function installPageRuntime(page) {
34236
+ await page.addInitScript({
34237
+ content: `
34238
+ window.__name = window.__name || function (target) { return target; };
34239
+ window.__bakerGeom = {
34240
+ adaptiveGapThreshold: ${adaptiveGapThreshold.toString()},
34241
+ bandsCollide: ${bandsCollide.toString()},
34242
+ shouldPromoteToParent: ${shouldPromoteToParent.toString()},
34243
+ };`
34244
+ });
34245
+ }
34246
+ async function segmentPage(page, options) {
34247
+ return await page.evaluate(inPageSegment, options);
34248
+ }
34249
+ var inPageSegment = (options) => {
34250
+ const geom = window.__bakerGeom;
34251
+ const scrollX = window.scrollX;
34252
+ const scrollY = window.scrollY;
34253
+ const docWidth = Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0);
34254
+ const docHeight = Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0);
34255
+ const rectOf = (element) => {
34256
+ const r = element.getBoundingClientRect();
34257
+ const top = Math.max(0, Math.round(r.top + scrollY));
34258
+ const left = Math.max(0, Math.round(r.left + scrollX));
34259
+ return {
34260
+ top,
34261
+ left,
34262
+ bottom: Math.min(docHeight, Math.round(r.bottom + scrollY)),
34263
+ right: Math.min(docWidth, Math.round(r.right + scrollX))
34264
+ };
34265
+ };
34266
+ const syntheticRect = (element) => {
34267
+ const children = Array.from(element.children);
34268
+ if (children.length === 0) return null;
34269
+ let top = Number.POSITIVE_INFINITY;
34270
+ let left = Number.POSITIVE_INFINITY;
34271
+ let bottom = Number.NEGATIVE_INFINITY;
34272
+ let right = Number.NEGATIVE_INFINITY;
34273
+ for (const child of children) {
34274
+ const r = rectOf(child);
34275
+ if (r.bottom - r.top <= 0 && r.right - r.left <= 0) continue;
34276
+ top = Math.min(top, r.top);
34277
+ left = Math.min(left, r.left);
34278
+ bottom = Math.max(bottom, r.bottom);
34279
+ right = Math.max(right, r.right);
34280
+ }
34281
+ if (!Number.isFinite(top) || !Number.isFinite(bottom)) return null;
34282
+ return { top, left, bottom, right };
34283
+ };
34284
+ const boxOf = (element) => {
34285
+ const style = getComputedStyle(element);
34286
+ const r = style.display === "contents" ? syntheticRect(element) : rectOf(element);
34287
+ if (!r) return null;
34288
+ return { ...r, height: r.bottom - r.top, width: r.right - r.left };
34289
+ };
34290
+ const hasHiddenAncestor = (element) => {
34291
+ let current = element;
34292
+ while (current && current !== document.documentElement) {
34293
+ const style = getComputedStyle(current);
34294
+ if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0) return true;
34295
+ current = current.parentElement;
34296
+ }
34297
+ return false;
34298
+ };
34299
+ const isClippedByAncestor = (element, box) => {
34300
+ let parent = element.parentElement;
34301
+ while (parent && parent !== document.documentElement) {
34302
+ const style = getComputedStyle(parent);
34303
+ const clips = style.overflow === "hidden" || style.overflowX === "hidden" || style.overflowY === "hidden" || style.overflow === "clip";
34304
+ if (clips) {
34305
+ const p = rectOf(parent);
34306
+ const intersects = box.left < p.right && box.right > p.left && box.top < p.bottom && box.bottom > p.top;
34307
+ if (!intersects) return true;
34308
+ }
34309
+ parent = parent.parentElement;
34310
+ }
34311
+ return false;
34312
+ };
34313
+ const directText = (element) => {
34314
+ let text = "";
34315
+ for (const node of Array.from(element.childNodes)) {
34316
+ if (node.nodeType === 3) text += node.textContent ?? "";
34317
+ }
34318
+ return text.trim();
34319
+ };
34320
+ const NON_COPY_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEMPLATE"]);
34321
+ const visibleText = (root) => {
34322
+ let text = "";
34323
+ const hiddenCache = /* @__PURE__ */ new Map();
34324
+ const isHidden = (element) => {
34325
+ const cached = hiddenCache.get(element);
34326
+ if (cached !== void 0) return cached;
34327
+ const style = getComputedStyle(element);
34328
+ const hidden = style.display === "none" || style.visibility === "hidden";
34329
+ hiddenCache.set(element, hidden);
34330
+ return hidden;
34331
+ };
34332
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
34333
+ acceptNode: (node) => {
34334
+ let parent = node.parentElement;
34335
+ while (parent) {
34336
+ if (NON_COPY_TAGS.has(parent.tagName) || isHidden(parent)) return NodeFilter.FILTER_REJECT;
34337
+ if (parent === root) break;
34338
+ parent = parent.parentElement;
34339
+ }
34340
+ return NodeFilter.FILTER_ACCEPT;
34341
+ }
34342
+ });
34343
+ while (walker.nextNode() && text.length < 400) {
34344
+ text += ` ${walker.currentNode.textContent ?? ""}`;
34345
+ }
34346
+ return text.replace(/\s+/g, " ").trim();
34347
+ };
34348
+ const GRAPHIC_TAGS = /* @__PURE__ */ new Set(["img", "svg", "video", "picture", "canvas", "iframe"]);
34349
+ const hasBackgroundImage = (element) => {
34350
+ const bg = getComputedStyle(element).backgroundImage;
34351
+ return Boolean(bg) && bg !== "none" && bg.includes("url(");
34352
+ };
34353
+ const isContentLeaf = (element) => {
34354
+ const tag = element.tagName.toLowerCase();
34355
+ if (GRAPHIC_TAGS.has(tag)) return true;
34356
+ if (directText(element).length > 0) return true;
34357
+ return hasBackgroundImage(element);
34358
+ };
34359
+ const selectorFor = (element) => {
34360
+ const parts = [];
34361
+ let current = element;
34362
+ while (current && current !== document.documentElement) {
34363
+ const tag = current.tagName.toLowerCase();
34364
+ if (tag === "body") {
34365
+ parts.unshift("body");
34366
+ break;
34367
+ }
34368
+ const parent = current.parentElement;
34369
+ if (!parent) {
34370
+ parts.unshift(tag);
34371
+ break;
34372
+ }
34373
+ const sameTag = Array.from(parent.children).filter((c) => c.tagName === current?.tagName);
34374
+ const position = sameTag.indexOf(current) + 1;
34375
+ parts.unshift(sameTag.length > 1 ? `${tag}:nth-of-type(${position})` : tag);
34376
+ current = parent;
34377
+ }
34378
+ return parts.join(" > ");
34379
+ };
34380
+ const leaves = [];
34381
+ const all = document.querySelectorAll("*");
34382
+ const limit = Math.min(all.length, options.maxElements);
34383
+ for (let i = 0; i < limit; i++) {
34384
+ const element = all[i];
34385
+ if (!element) continue;
34386
+ const tag = element.tagName.toLowerCase();
34387
+ if (tag === "script" || tag === "style" || tag === "noscript" || tag === "link" || tag === "head") continue;
34388
+ if (!isContentLeaf(element)) continue;
34389
+ const box = boxOf(element);
34390
+ if (!box || box.height <= 0 || box.width <= 0) continue;
34391
+ if (box.height > options.maxSectionHeight) continue;
34392
+ if (hasHiddenAncestor(element)) continue;
34393
+ if (isClippedByAncestor(element, box)) continue;
34394
+ leaves.push({
34395
+ element,
34396
+ top: box.top,
34397
+ bottom: box.bottom,
34398
+ left: box.left,
34399
+ right: box.right,
34400
+ height: box.height
34401
+ });
34402
+ }
34403
+ if (leaves.length === 0) return [];
34404
+ const gapThreshold = geom.adaptiveGapThreshold(leaves.map((l) => ({ top: l.top, bottom: l.bottom })));
34405
+ const commonAncestor = (elements) => {
34406
+ let ancestor = elements[0] ?? null;
34407
+ while (ancestor && !elements.every((e) => ancestor?.contains(e))) {
34408
+ ancestor = ancestor.parentElement;
34409
+ }
34410
+ return ancestor;
34411
+ };
34412
+ const abandon = (members) => members.map((m) => ({ ...m, sealed: true }));
34413
+ const findCoveringAncestor = (start, bounds) => {
34414
+ let element = start;
34415
+ while (element) {
34416
+ const box = boxOf(element);
34417
+ if (!box) return null;
34418
+ if (box.height > options.maxSectionHeight) return null;
34419
+ const covers = box.top <= bounds.top && box.bottom >= bounds.bottom && box.left <= bounds.left && box.right >= bounds.right;
34420
+ if (covers) return { element, box };
34421
+ if (!element.parentElement || element.parentElement === document.documentElement) return null;
34422
+ element = element.parentElement;
34423
+ }
34424
+ return null;
34425
+ };
34426
+ const mergeBucket = (members) => {
34427
+ const bounds = {
34428
+ top: Math.min(...members.map((m) => m.top)),
34429
+ bottom: Math.max(...members.map((m) => m.bottom)),
34430
+ left: Math.min(...members.map((m) => m.left)),
34431
+ right: Math.max(...members.map((m) => m.right))
34432
+ };
34433
+ const covering = findCoveringAncestor(commonAncestor(members.map((m) => m.root)), bounds);
34434
+ if (!covering) return abandon(members);
34435
+ return [
34436
+ {
34437
+ root: covering.element,
34438
+ top: covering.box.top,
34439
+ bottom: covering.box.bottom,
34440
+ left: covering.box.left,
34441
+ right: covering.box.right,
34442
+ height: covering.box.height,
34443
+ leaves: members.flatMap((m) => m.leaves),
34444
+ sealed: false
34445
+ }
34446
+ ];
34447
+ };
34448
+ const clusterOnce = (groups2) => {
34449
+ const buckets = [];
34450
+ for (const group of groups2) {
34451
+ if (group.sealed) {
34452
+ buckets.push([group]);
34453
+ continue;
34454
+ }
34455
+ const target = buckets.find(
34456
+ (bucket) => bucket.some(
34457
+ (member) => !member.sealed && geom.bandsCollide(
34458
+ { top: member.top, bottom: member.bottom },
34459
+ { top: group.top, bottom: group.bottom },
34460
+ options.collisionMargin,
34461
+ gapThreshold
34462
+ )
34463
+ )
34464
+ );
34465
+ if (target) target.push(group);
34466
+ else buckets.push([group]);
34467
+ }
34468
+ if (buckets.length === groups2.length) return groups2;
34469
+ return buckets.flatMap((bucket) => bucket.length === 1 ? [bucket[0]] : mergeBucket(bucket));
34470
+ };
34471
+ let groups = leaves.map((leaf) => ({
34472
+ root: leaf.element,
34473
+ top: leaf.top,
34474
+ bottom: leaf.bottom,
34475
+ left: leaf.left,
34476
+ right: leaf.right,
34477
+ height: leaf.height,
34478
+ leaves: [leaf],
34479
+ sealed: false
34480
+ }));
34481
+ for (let pass = 0; pass < 40; pass++) {
34482
+ const next = clusterOnce(groups);
34483
+ if (next.length === groups.length) break;
34484
+ groups = next;
34485
+ }
34486
+ for (let pass = 0; pass < 10; pass++) {
34487
+ let promoted = false;
34488
+ groups = groups.map((group) => {
34489
+ const parent = group.root.parentElement;
34490
+ if (!parent || parent === document.documentElement || parent === document.body) return group;
34491
+ const parentBox = boxOf(parent);
34492
+ if (!parentBox) return group;
34493
+ if (!geom.shouldPromoteToParent({
34494
+ childCount: group.leaves.length,
34495
+ height: group.height,
34496
+ parentHeight: parentBox.height
34497
+ })) {
34498
+ return group;
34499
+ }
34500
+ promoted = true;
34501
+ return {
34502
+ ...group,
34503
+ root: parent,
34504
+ top: parentBox.top,
34505
+ bottom: parentBox.bottom,
34506
+ left: parentBox.left,
34507
+ right: parentBox.right,
34508
+ height: parentBox.height
34509
+ };
34510
+ });
34511
+ if (!promoted) break;
34512
+ groups = clusterOnce(groups);
34513
+ }
34514
+ 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;
34515
+ const containsBox = (outer, inner) => outer.top - 4 <= inner.top && outer.bottom + 4 >= inner.bottom && outer.left - 4 <= inner.left && outer.right + 4 >= inner.right;
34516
+ const deduped = [];
34517
+ const area = (g) => (g.right - g.left) * g.height;
34518
+ for (const group of groups.slice().sort((a, b) => area(b) - area(a) || b.leaves.length - a.leaves.length)) {
34519
+ const duplicate = deduped.some(
34520
+ (kept) => kept.root === group.root || kept.root.contains(group.root) || sameBox(kept, group) || containsBox(kept, group)
34521
+ );
34522
+ if (!duplicate) deduped.push(group);
34523
+ }
34524
+ return deduped.filter((group) => group.height > 0 && group.right - group.left > 0).sort((a, b) => a.top - b.top).map((group, index) => ({
34525
+ index,
34526
+ selector: selectorFor(group.root),
34527
+ rect: {
34528
+ top: group.top,
34529
+ left: group.left,
34530
+ width: group.right - group.left,
34531
+ height: group.height
34532
+ },
34533
+ leafCount: group.leaves.length,
34534
+ textPreview: visibleText(group.root).slice(0, 200)
34535
+ }));
34536
+ };
34537
+
34538
+ // src/engine/landing-library/motionTake.ts
34539
+ var FRAME_TIMES_MS = [0, 120, 260, 450, 800, 1400];
34540
+ var FRAME_WIDTH = 460;
34541
+ var GRID_COLUMNS = 3;
34542
+ var LABEL_HEIGHT = 22;
34543
+ async function captureMotionTake(browser, url, selector, timeoutMs = 45e3) {
34544
+ const { context, page } = await newPage(browser, DESKTOP_VIEWPORT, { motion: true });
34545
+ try {
34546
+ await blockConsentManagers(page);
34547
+ await installPageRuntime(page);
34548
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
34549
+ await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => void 0);
34550
+ await settleConsent(page);
34551
+ const target = page.locator(selector).first();
34552
+ if (!await target.count().catch(() => 0)) return null;
34553
+ await page.evaluate((sectionSelector) => {
34554
+ const element = document.querySelector(sectionSelector);
34555
+ if (!element) return;
34556
+ const top = element.getBoundingClientRect().top + window.scrollY;
34557
+ window.scrollTo(0, Math.max(0, top - window.innerHeight - 200));
34558
+ }, selector);
34559
+ await page.waitForTimeout(600);
34560
+ await page.evaluate((sectionSelector) => {
34561
+ document.querySelector(sectionSelector)?.scrollIntoView({ block: "center" });
34562
+ }, selector);
34563
+ const frames = [];
34564
+ let previous = 0;
34565
+ for (const time of FRAME_TIMES_MS) {
34566
+ await page.waitForTimeout(Math.max(0, time - previous));
34567
+ previous = time;
34568
+ const shot = await page.screenshot({ type: "png", timeout: 1e4 }).catch(() => null);
34569
+ if (shot) frames.push(shot);
34570
+ }
34571
+ if (frames.length === 0) return null;
34572
+ return { filmstrip: await composeFilmstrip(frames), frameCount: frames.length };
34573
+ } catch {
34574
+ return null;
34575
+ } finally {
34576
+ await context.close();
34577
+ }
34578
+ }
34579
+ async function composeFilmstrip(frames) {
34580
+ const scaled = await Promise.all(frames.map((frame) => sharp5(frame).resize({ width: FRAME_WIDTH }).png().toBuffer()));
34581
+ const first = await sharp5(scaled[0]).metadata();
34582
+ const frameHeight = first.height ?? 300;
34583
+ const cellHeight = frameHeight + LABEL_HEIGHT;
34584
+ const rows = Math.ceil(scaled.length / GRID_COLUMNS);
34585
+ const width = FRAME_WIDTH * Math.min(GRID_COLUMNS, scaled.length);
34586
+ const composites = scaled.flatMap((frame, index) => {
34587
+ const column = index % GRID_COLUMNS;
34588
+ const row = Math.floor(index / GRID_COLUMNS);
34589
+ const label = Buffer.from(
34590
+ `<svg width="${FRAME_WIDTH}" height="${LABEL_HEIGHT}">
34591
+ <rect width="100%" height="100%" fill="#111827"/>
34592
+ <text x="8" y="15" font-family="monospace" font-size="12" fill="#f9fafb">+${FRAME_TIMES_MS[index] ?? 0}ms</text>
34593
+ </svg>`
34594
+ );
34595
+ return [
34596
+ { input: label, left: column * FRAME_WIDTH, top: row * cellHeight },
34597
+ { input: frame, left: column * FRAME_WIDTH, top: row * cellHeight + LABEL_HEIGHT }
34598
+ ];
34599
+ });
34600
+ return await sharp5({
34601
+ create: {
34602
+ width,
34603
+ height: cellHeight * rows,
34604
+ channels: 3,
34605
+ background: { r: 17, g: 24, b: 39 }
34606
+ }
34607
+ }).composite(composites).png().toBuffer();
34608
+ }
34609
+
34610
+ // src/engine/landing-library/renderBundle.ts
34611
+ var SETTLE_ANIMATIONS_CSS = `*, *::before, *::after {
34612
+ animation-play-state: running !important;
34613
+ animation-delay: 0s !important;
34614
+ animation-duration: 1ms !important;
34615
+ animation-iteration-count: 1 !important;
34616
+ animation-fill-mode: forwards !important;
34617
+ transition: none !important;
34618
+ }`;
34619
+ async function renderBundleToPng(browser, html, viewportWidth, options = {}) {
34620
+ const { wholePage = false, timeoutMs = 2e4 } = options;
34621
+ const { context, page } = await newPage(browser, { width: viewportWidth, height: 900 });
34622
+ try {
34623
+ await page.setContent(html, { waitUntil: "load", timeout: timeoutMs });
34624
+ await page.addStyleTag({ content: SETTLE_ANIMATIONS_CSS });
34625
+ await page.evaluate(async () => {
34626
+ await document.fonts.ready;
34627
+ });
34628
+ await page.waitForTimeout(300);
34629
+ if (!wholePage) {
34630
+ for (const selector of [`[${SECTION_ROOT_ATTRIBUTE}]`, "body > *"]) {
34631
+ const target = page.locator(selector).first();
34632
+ if (!await target.count()) continue;
34633
+ const shot = await target.screenshot({ type: "png", timeout: timeoutMs }).catch(() => null);
34634
+ if (shot) return shot;
34635
+ }
34636
+ }
34637
+ return await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
34638
+ } catch {
34639
+ return null;
34640
+ } finally {
34641
+ await context.close();
34642
+ }
34643
+ }
34644
+
34645
+ // src/engine/landing-library/report.ts
34646
+ import { writeFile as writeFile14 } from "fs/promises";
34647
+ import path30 from "path";
34648
+ async function writeCaptureReport(manifest, outDir) {
34649
+ const file = path30.join(outDir, "report.html");
34650
+ await writeFile14(file, renderReport(manifest));
34651
+ return file;
34652
+ }
34653
+ function escapeHtml3(value) {
34654
+ return value.replace(
34655
+ /[&<>"']/g,
34656
+ (character) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[character] ?? character
34657
+ );
34658
+ }
34659
+ function fidelityTone(fidelity) {
34660
+ if (fidelity === null) return "unknown";
34661
+ if (fidelity >= 0.95) return "good";
34662
+ if (fidelity >= 0.85) return "fair";
34663
+ return "poor";
34664
+ }
34665
+ function medianFidelity(sections) {
34666
+ const scored = sections.map((section) => section.fidelity).filter((value) => value !== null).sort((a, b) => a - b);
34667
+ return scored.length === 0 ? null : scored[Math.floor(scored.length / 2)] ?? null;
34668
+ }
34669
+ function formatFidelity(fidelity) {
34670
+ return fidelity === null ? "\u2014" : fidelity.toFixed(2);
34671
+ }
34672
+ function renderSection3(section) {
34673
+ const tone = fidelityTone(section.fidelity);
34674
+ const rendered = section.bundle ? section.bundle.replace(/section\.html$/, "section-rendered.png") : null;
34675
+ const shot = (label, src, note) => {
34676
+ if (!src) return `<figure class="shot empty"><figcaption>${label} \u2014 none</figcaption></figure>`;
34677
+ return `<figure class="shot">
34678
+ <figcaption>${label}${note ? ` <span class="note">${escapeHtml3(note)}</span>` : ""}</figcaption>
34679
+ <a href="${escapeHtml3(src)}" target="_blank" rel="noopener"><img src="${escapeHtml3(src)}" alt="" loading="lazy"></a>
34680
+ </figure>`;
34681
+ };
34682
+ return `<section class="card">
34683
+ <header>
34684
+ <h2><span class="index">${String(section.index).padStart(2, "0")}</span> ${section.rect.width}\xD7${section.rect.height}</h2>
34685
+ <span class="badge ${tone}">fidelity ${formatFidelity(section.fidelity)}</span>
34686
+ ${section.fidelityNote ? `<span class="badge warn">${escapeHtml3(section.fidelityNote)}</span>` : ""}
34687
+ ${section.motion.hasMotion ? `<span class="badge motion">${escapeHtml3(section.motion.summary)}</span>` : ""}
34688
+ </header>
34689
+ <p class="preview">${escapeHtml3(section.textPreview.slice(0, 220)) || "<em>no text</em>"}</p>
34690
+ <div class="shots">
34691
+ ${shot("Live", section.desktopShot)}
34692
+ ${shot("Reproduction", rendered, "rendered from the extracted bundle")}
34693
+ ${shot("Mobile", section.mobileShot)}
34694
+ </div>
34695
+ ${section.motionFilmstrip ? `<div class="filmstrip">${shot("Motion \u2014 six frames, left to right", section.motionFilmstrip)}</div>` : ""}
34696
+ <footer>
34697
+ <code>${escapeHtml3(section.selector)}</code>
34698
+ ${section.bundle ? `<a href="${escapeHtml3(section.bundle)}" target="_blank" rel="noopener">open the standalone bundle \u2192</a>` : ""}
34699
+ </footer>
34700
+ </section>`;
34701
+ }
34702
+ function renderReport(manifest) {
34703
+ const median = medianFidelity(manifest.sections);
34704
+ const moving = manifest.sections.filter((section) => section.motionFilmstrip !== null).length;
34705
+ return `<!doctype html>
34706
+ <html lang="en">
34707
+ <head>
34708
+ <meta charset="utf-8">
34709
+ <meta name="viewport" content="width=device-width, initial-scale=1">
34710
+ <title>Capture report \u2014 ${escapeHtml3(manifest.title || manifest.url)}</title>
34711
+ <style>
34712
+ :root { color-scheme: light dark; --line: #e5e7eb; --muted: #6b7280; --bg: #fafafa; --card: #fff; }
34713
+ @media (prefers-color-scheme: dark) {
34714
+ :root { --line: #27272a; --muted: #a1a1aa; --bg: #09090b; --card: #131316; }
34715
+ }
34716
+ * { box-sizing: border-box; }
34717
+ body { margin: 0; padding: 24px; background: var(--bg);
34718
+ font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
34719
+ h1 { margin: 0 0 4px; font-size: 20px; }
34720
+ a { color: inherit; }
34721
+ .sub { color: var(--muted); margin: 0 0 20px; }
34722
+ .stats { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; }
34723
+ .stat { border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px; background: var(--card); }
34724
+ .stat b { display: block; font-size: 18px; }
34725
+ .stat span { color: var(--muted); font-size: 12px; }
34726
+ .card { border: 1px solid var(--line); border-radius: 12px; background: var(--card);
34727
+ padding: 16px; margin-bottom: 16px; }
34728
+ .card header { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
34729
+ .card h2 { font-size: 15px; margin: 0; font-weight: 600; }
34730
+ .index { display: inline-block; min-width: 26px; color: var(--muted); }
34731
+ .badge { font-size: 12px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--line); }
34732
+ .badge.good { background: #dcfce7; color: #166534; border-color: #bbf7d0; }
34733
+ .badge.fair { background: #fef3c7; color: #92400e; border-color: #fde68a; }
34734
+ .badge.poor { background: #fee2e2; color: #991b1b; border-color: #fecaca; }
34735
+ .badge.warn { background: #fee2e2; color: #991b1b; border-color: #fecaca; }
34736
+ .badge.motion { background: #ede9fe; color: #5b21b6; border-color: #ddd6fe; }
34737
+ .preview { color: var(--muted); margin: 0 0 12px; font-size: 13px; }
34738
+ .shots { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; align-items: start; }
34739
+ .filmstrip { margin-top: 12px; }
34740
+ figure { margin: 0; }
34741
+ figcaption { font-size: 12px; color: var(--muted); margin-bottom: 4px; }
34742
+ figcaption .note { opacity: .75; }
34743
+ .shot img { width: 100%; height: auto; border: 1px solid var(--line); border-radius: 8px;
34744
+ background: #fff; display: block; }
34745
+ .shot.empty { border: 1px dashed var(--line); border-radius: 8px; padding: 20px; text-align: center; }
34746
+ .card footer { display: flex; justify-content: space-between; gap: 12px; margin-top: 12px;
34747
+ font-size: 12px; color: var(--muted); }
34748
+ code { font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
34749
+ </style>
34750
+ </head>
34751
+ <body>
34752
+ <h1>${escapeHtml3(manifest.title || "Capture report")}</h1>
34753
+ <p class="sub"><a href="${escapeHtml3(manifest.finalUrl)}" target="_blank" rel="noopener">${escapeHtml3(manifest.finalUrl)}</a> \xB7 captured ${escapeHtml3(manifest.capturedAt)}</p>
34754
+
34755
+ <div class="stats">
34756
+ <div class="stat"><b>${manifest.sections.length}</b><span>sections</span></div>
34757
+ <div class="stat"><b>${formatFidelity(median)}</b><span>median fidelity</span></div>
34758
+ <div class="stat"><b>${formatFidelity(manifest.page.fidelity)}</b><span>whole page</span></div>
34759
+ <div class="stat"><b>${moving}</b><span>filmed moving</span></div>
34760
+ <div class="stat"><b>${manifest.documentHeight}px</b><span>page height</span></div>
34761
+ </div>
34762
+
34763
+ <section class="card">
34764
+ <header>
34765
+ <h2>Whole page</h2>
34766
+ <span class="badge ${fidelityTone(manifest.page.fidelity)}">fidelity ${formatFidelity(manifest.page.fidelity)}</span>
34767
+ </header>
34768
+ <p class="preview">The same extractor rooted at &lt;body&gt; \u2014 one standalone file that should render like the original.</p>
34769
+ <div class="shots">
34770
+ <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>
34771
+ ${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>`}
34772
+ </div>
34773
+ </section>
34774
+
34775
+ ${manifest.sections.map(renderSection3).join("\n")}
34776
+ </body>
34777
+ </html>
34778
+ `;
34779
+ }
34780
+
34781
+ // src/engine/landing-library/types.ts
34782
+ var DEFAULT_SEGMENT_OPTIONS = {
34783
+ maxSectionHeight: 2160,
34784
+ collisionMargin: 2,
34785
+ maxElements: 15e3
34786
+ };
34787
+
34788
+ // src/engine/landing-library/visualHash.ts
34789
+ import sharp6 from "sharp";
34790
+ var HASH_WIDTH = 9;
34791
+ var HASH_HEIGHT = 8;
34792
+ async function perceptualHash(image) {
34793
+ try {
34794
+ const raw = await sharp6(image).greyscale().resize(HASH_WIDTH, HASH_HEIGHT, { fit: "fill" }).raw().toBuffer();
34795
+ return bitsToHex(rowGradientBits(new Uint8Array(raw)));
34796
+ } catch {
34797
+ return null;
34798
+ }
34799
+ }
34800
+ function rowGradientBits(pixels) {
34801
+ const bits = [];
34802
+ for (let y = 0; y < HASH_HEIGHT; y++) {
34803
+ for (let x = 0; x < HASH_WIDTH - 1; x++) {
34804
+ const left = pixels[y * HASH_WIDTH + x] ?? 0;
34805
+ const right = pixels[y * HASH_WIDTH + x + 1] ?? 0;
34806
+ bits.push(left > right);
34807
+ }
34808
+ }
34809
+ return bits;
34810
+ }
34811
+ function bitsToHex(bits) {
34812
+ let hex = "";
34813
+ for (let index = 0; index < bits.length; index += 4) {
34814
+ let nibble = 0;
34815
+ for (let offset = 0; offset < 4; offset++) {
34816
+ if (bits[index + offset]) nibble |= 1 << 3 - offset;
34817
+ }
34818
+ hex += nibble.toString(16);
34819
+ }
34820
+ return hex;
34821
+ }
34822
+
34823
+ // src/engine/landing-library/run.ts
34824
+ async function reproducePage(args) {
34825
+ const { browser, page, outDir, pageUrl, livePageShot } = args;
34826
+ const built = await buildSectionBundle(page, "body", pageUrl).catch(() => null);
34827
+ if (!built) return { bundle: null, fidelity: null };
34828
+ await writeFile15(path31.join(outDir, "page.html"), built.html);
34829
+ const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width, {
34830
+ wholePage: true,
34831
+ timeoutMs: 6e4
34832
+ });
34833
+ if (!rendered || !livePageShot) return { bundle: "page.html", fidelity: null };
34834
+ await writeFile15(path31.join(outDir, "page-rendered.png"), rendered);
34835
+ const { score, note } = await scoreFidelity(livePageShot, rendered);
34836
+ return { bundle: "page.html", fidelity: score, ...note ? { fidelityNote: note } : {} };
34837
+ }
34838
+ async function captureOneSection(args) {
34839
+ const { browser, page, candidate, sectionsDir, outDir, pageUrl, withCode } = args;
34840
+ const dir = path31.join(sectionsDir, String(candidate.index).padStart(2, "0"));
34841
+ await mkdir11(dir, { recursive: true });
34842
+ const desktop = await captureSection(page, candidate);
34843
+ if (desktop) await writeFile15(path31.join(dir, "desktop.png"), desktop);
34844
+ const visualHash = desktop ? await perceptualHash(desktop) : null;
34845
+ const motion = await collectMotion(page, candidate.selector);
34846
+ const built = withCode ? await buildSectionBundle(page, candidate.selector, pageUrl) : null;
34847
+ let fidelity = null;
34848
+ let fidelityNote;
34849
+ if (built) {
34850
+ await writeFile15(path31.join(dir, "section.html"), built.html);
34851
+ const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width);
34852
+ if (rendered && desktop) {
34853
+ await writeFile15(path31.join(dir, "section-rendered.png"), rendered);
34854
+ const result = await scoreFidelity(desktop, rendered);
34855
+ fidelity = result.score;
34856
+ fidelityNote = result.note;
34857
+ }
34858
+ }
34859
+ return {
34860
+ ...candidate,
34861
+ desktopShot: desktop ? path31.relative(outDir, path31.join(dir, "desktop.png")) : null,
34862
+ mobileShot: null,
34863
+ bundle: built ? path31.relative(outDir, path31.join(dir, "section.html")) : null,
34864
+ fidelity,
34865
+ ...fidelityNote ? { fidelityNote } : {},
34866
+ ...built ? { cssStats: built.stats } : {},
34867
+ motion,
34868
+ motionFilmstrip: null,
34869
+ visualHash
34870
+ };
34871
+ }
34872
+ async function captureMobileShots(args) {
34873
+ const { browser, sections, sectionsDir, outDir, pageUrl, timeoutMs } = args;
34874
+ const mobile = await newPage(browser, MOBILE_VIEWPORT);
34875
+ try {
34876
+ await blockConsentManagers(mobile.page);
34877
+ await installPageRuntime(mobile.page);
34878
+ await preparePage(mobile.page, pageUrl, timeoutMs);
34879
+ for (const section of sections) {
34880
+ const shot = await captureSectionOnMobile(mobile.page, section);
34881
+ if (!shot) continue;
34882
+ const file = path31.join(sectionsDir, String(section.index).padStart(2, "0"), "mobile.png");
34883
+ await writeFile15(file, shot);
34884
+ section.mobileShot = path31.relative(outDir, file);
34885
+ }
34886
+ } finally {
34887
+ await mobile.context.close();
34888
+ }
34889
+ }
34890
+ async function captureMotionTakes(args) {
34891
+ const { browser, sections, sectionsDir, outDir, pageUrl, log } = args;
34892
+ const moving = sections.filter((section) => isWorthFilming(section.motion));
34893
+ if (moving.length === 0) return;
34894
+ log(`filming ${moving.length} moving sections`);
34895
+ for (const section of moving) {
34896
+ const take = await captureMotionTake(browser, pageUrl, section.selector);
34897
+ if (!take) continue;
34898
+ const dir = path31.join(sectionsDir, String(section.index).padStart(2, "0"));
34899
+ const file = path31.join(dir, "motion-filmstrip.png");
34900
+ await writeFile15(file, take.filmstrip);
34901
+ section.motionFilmstrip = path31.relative(outDir, file);
34902
+ log(` [${section.index}] ${section.motion.summary}`);
34903
+ }
34904
+ }
34905
+ async function scrapeLanding(options) {
34906
+ const timeoutMs = options.timeoutMs ?? 45e3;
34907
+ const log = options.onProgress ?? (() => void 0);
34908
+ const sectionsDir = path31.join(options.outDir, "sections");
34909
+ const browser = await launchBrowser();
34910
+ try {
34911
+ const { context, page } = await newPage(browser, DESKTOP_VIEWPORT);
34912
+ await blockConsentManagers(page);
34913
+ await installPageRuntime(page);
34914
+ log(`loading ${options.url}`);
34915
+ const prepared = await preparePage(page, options.url, timeoutMs);
34916
+ const blocked = detectBlockedPage({
34917
+ status: prepared.status,
34918
+ title: prepared.title,
34919
+ bodyText: prepared.bodyText,
34920
+ html: await page.content().catch(() => "")
34921
+ });
34922
+ if (blocked) throw new BlockedPageError(blocked);
34923
+ await mkdir11(sectionsDir, { recursive: true });
34924
+ log("segmenting");
34925
+ const candidates = await segmentPage(page, DEFAULT_SEGMENT_OPTIONS);
34926
+ log(`found ${candidates.length} sections`);
34927
+ const sections = [];
34928
+ for (const candidate of candidates) {
34929
+ const section = await captureOneSection({
34930
+ browser,
34931
+ page,
34932
+ candidate,
34933
+ sectionsDir,
34934
+ outDir: options.outDir,
34935
+ pageUrl: prepared.finalUrl,
34936
+ withCode: options.code !== false
34937
+ });
34938
+ sections.push(section);
34939
+ log(
34940
+ ` [${candidate.index}] ${candidate.rect.width}x${candidate.rect.height}${section.fidelity === null ? "" : ` fidelity=${section.fidelity.toFixed(2)}`} \u2014 ${candidate.textPreview.slice(0, 50)}`
34941
+ );
34942
+ }
34943
+ const fullPage = await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
34944
+ if (fullPage) await writeFile15(path31.join(options.outDir, "full-page.png"), fullPage);
34945
+ const reproduction = options.code === false ? { bundle: null, fidelity: null } : await reproducePage({
34946
+ browser,
34947
+ page,
34948
+ outDir: options.outDir,
34949
+ pageUrl: prepared.finalUrl,
34950
+ livePageShot: fullPage
34951
+ });
34952
+ log(`page reproduction: ${reproduction.fidelity === null ? "unavailable" : reproduction.fidelity.toFixed(2)}`);
34953
+ await context.close();
34954
+ if (options.mobile !== false) {
34955
+ log("capturing mobile");
34956
+ await captureMobileShots({
34957
+ browser,
34958
+ sections,
34959
+ sectionsDir,
34960
+ outDir: options.outDir,
34961
+ pageUrl: prepared.finalUrl,
34962
+ timeoutMs
34963
+ });
34964
+ }
34965
+ if (options.motion !== false) {
34966
+ await captureMotionTakes({
34967
+ browser,
34968
+ sections,
34969
+ sectionsDir,
34970
+ outDir: options.outDir,
34971
+ pageUrl: prepared.finalUrl,
34972
+ log
34973
+ });
34974
+ }
34975
+ const manifest = {
34976
+ url: options.url,
34977
+ finalUrl: prepared.finalUrl,
34978
+ title: prepared.title,
34979
+ documentHeight: prepared.documentHeight,
34980
+ viewport: DESKTOP_VIEWPORT,
34981
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
34982
+ sections,
34983
+ page: reproduction
34984
+ };
34985
+ await writeFile15(path31.join(options.outDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
34986
+ `);
34987
+ if (options.report !== false) {
34988
+ const reportPath = await writeCaptureReport(manifest, options.outDir);
34989
+ log(`report: ${reportPath}`);
34990
+ }
34991
+ return manifest;
34992
+ } finally {
34993
+ await browser.close();
34994
+ }
34995
+ }
34996
+
34997
+ // src/commands/landing/inspiration/scrape.ts
34998
+ registerSchema({
34999
+ command: "landing.inspiration.scrape",
35000
+ description: "Internal/ops: capture one landing page into a directory of section screenshots, standalone HTML bundles and motion filmstrips. The ingest job runs this; use it locally to debug a page that studied badly.",
35001
+ args: {
35002
+ url: { type: "string", description: "Page to capture", required: true },
35003
+ out: { type: "string", description: "Output directory", required: true },
35004
+ "no-mobile": { type: "boolean", description: "Skip the phone-viewport pass", required: false },
35005
+ "no-code": {
35006
+ type: "boolean",
35007
+ description: "Segment and screenshot only \u2014 no bundles, no fidelity",
35008
+ required: false
35009
+ },
35010
+ "no-motion": {
35011
+ type: "boolean",
35012
+ description: "Skip filming sections that move (roughly halves runtime)",
35013
+ required: false
35014
+ },
35015
+ "no-report": { type: "boolean", description: "Skip writing report.html", required: false }
35016
+ }
35017
+ });
35018
+ var scrapeCommand = defineCommand147({
35019
+ meta: {
35020
+ name: "scrape",
35021
+ description: "Internal/ops: capture a landing page to a directory. Example: baker landing inspiration scrape https://linear.app --out /tmp/linear"
35022
+ },
35023
+ args: {
35024
+ url: { type: "positional", description: "Page to capture", required: true },
35025
+ out: { type: "string", description: "Output directory", required: true },
35026
+ "no-mobile": { type: "boolean", description: "Skip the phone-viewport pass", required: false, default: false },
35027
+ "no-code": { type: "boolean", description: "Segment and screenshot only", required: false, default: false },
35028
+ "no-motion": { type: "boolean", description: "Skip filming sections that move", required: false, default: false },
35029
+ "no-report": { type: "boolean", description: "Skip writing report.html", required: false, default: false }
35030
+ },
35031
+ run: async ({ args }) => {
35032
+ try {
35033
+ const manifest = await scrapeLanding({
35034
+ url: args.url,
35035
+ outDir: args.out,
35036
+ mobile: !args["no-mobile"],
35037
+ code: !args["no-code"],
35038
+ motion: !args["no-motion"],
35039
+ report: !args["no-report"],
35040
+ // Progress goes to stderr so stdout stays a clean JSON envelope.
35041
+ onProgress: (message) => process.stderr.write(`${message}
35042
+ `)
35043
+ });
35044
+ const scored = manifest.sections.map((section) => section.fidelity).filter((value) => value !== null).sort((a, b) => a - b);
35045
+ writeJson({
35046
+ ok: true,
35047
+ data: {
35048
+ title: manifest.title,
35049
+ sections: manifest.sections.length,
35050
+ medianFidelity: scored.length > 0 ? scored[Math.floor(scored.length / 2)] : null,
35051
+ pageFidelity: manifest.page.fidelity,
35052
+ out: args.out,
35053
+ report: args["no-report"] ? null : `${args.out}/report.html`
35054
+ }
35055
+ });
35056
+ } catch (error) {
35057
+ if (error instanceof BlockedPageError) {
35058
+ writeJson({ ok: false, error: { code: error.code, message: error.message } });
35059
+ process.exit(1);
35060
+ }
35061
+ reportError(error);
35062
+ }
35063
+ }
35064
+ });
35065
+
35066
+ // src/commands/landing/inspiration/search.ts
35067
+ import { mkdir as mkdir12, writeFile as writeFile16 } from "fs/promises";
35068
+ import path32 from "path";
35069
+ import { defineCommand as defineCommand148 } from "citty";
35070
+ registerSchema({
35071
+ command: "landing.inspiration.search",
35072
+ 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.",
35073
+ args: {
35074
+ query: { type: "string", description: "What you want to see, in plain English", required: false },
35075
+ type: {
35076
+ type: "string",
35077
+ description: "Comma list of section types: hero,pricing,faq,testimonials,\u2026",
35078
+ required: false
35079
+ },
35080
+ composition: {
35081
+ type: "string",
35082
+ description: "Comma list of composition patterns (references/composition.md)",
35083
+ required: false
35084
+ },
35085
+ register: {
35086
+ type: "string",
35087
+ description: "Comma list of visual registers: dev-tool-minimal,luxury-high-end,\u2026",
35088
+ required: false
35089
+ },
35090
+ interaction: { type: "string", description: "Comma list of interaction patterns", required: false },
35091
+ motion: {
35092
+ type: "string",
35093
+ description: "Comma list of motion kinds: scroll-reveal,marquee,parallax,\u2026",
35094
+ required: false
35095
+ },
35096
+ media: { type: "string", description: "Comma list of media kinds", required: false },
35097
+ device: { type: "string", description: "Comma list of content devices", required: false },
35098
+ theme: { type: "string", description: "light | dark | mixed", required: false },
35099
+ "max-rank": {
35100
+ type: "number",
35101
+ description: "Show>Tell rank ceiling 1-7; 3 means 'rank 3 or better'",
35102
+ required: false
35103
+ },
35104
+ "min-craft": { type: "number", description: "Craft floor 0-1", required: false },
35105
+ "min-fidelity": {
35106
+ type: "number",
35107
+ description: "Only sections whose markup reproduces this well (0-1)",
35108
+ required: false
35109
+ },
35110
+ domain: { type: "string", description: "Restrict to one site", required: false },
35111
+ "similar-to": {
35112
+ type: "string",
35113
+ description: "Section id \u2014 find sections that look like this one",
35114
+ required: false
35115
+ },
35116
+ scope: { type: "string", description: "favorites (default) | all", required: false },
35117
+ limit: { type: "number", description: "Max results (default 8)", required: false },
35118
+ "no-images": { type: "boolean", description: "Skip downloading screenshots", required: false }
35119
+ }
35120
+ });
35121
+ function buildSearchBody(args) {
35122
+ const body = {};
35123
+ const setList2 = (key, value) => {
35124
+ const list = splitList(value);
35125
+ if (list) Object.assign(body, { [key]: list });
35126
+ };
35127
+ if (args.query) body.query = String(args.query);
35128
+ if (args["similar-to"]) body.similarToSectionId = String(args["similar-to"]);
35129
+ setList2("sectionType", args.type);
35130
+ setList2("composition", args.composition);
35131
+ setList2("visualRegister", args.register);
35132
+ setList2("interaction", args.interaction);
35133
+ setList2("motion", args.motion);
35134
+ setList2("mediaKind", args.media);
35135
+ setList2("contentDevice", args.device);
35136
+ if (args.theme) body.theme = String(args.theme);
35137
+ if (args.domain) body.domain = String(args.domain);
35138
+ const maxRank = parseNumber(args["max-rank"]);
35139
+ if (maxRank !== void 0) body.maxShowTellRank = maxRank;
35140
+ const minCraft = parseNumber(args["min-craft"]);
35141
+ if (minCraft !== void 0) body.minCraftScore = minCraft;
35142
+ const minFidelity = parseNumber(args["min-fidelity"]);
35143
+ if (minFidelity !== void 0) body.minFidelity = minFidelity;
35144
+ const limit = parseNumber(args.limit);
35145
+ body.limit = limit ?? 8;
35146
+ body.scope = args.scope === "all" ? "all" : "favorites";
35147
+ return body;
35148
+ }
35149
+ async function downloadShots(results) {
35150
+ const dir = path32.join(process.cwd(), ".baker", "inspiration");
35151
+ await mkdir12(dir, { recursive: true });
35152
+ const saved = /* @__PURE__ */ new Map();
35153
+ await Promise.all(
35154
+ results.map(async (result) => {
35155
+ if (!result.desktopShotUrl) return;
35156
+ try {
35157
+ const response = await fetch(result.desktopShotUrl);
35158
+ if (!response.ok) return;
35159
+ const file = path32.join(dir, `${result.id}.png`);
35160
+ await writeFile16(file, Buffer.from(await response.arrayBuffer()));
35161
+ saved.set(result.id, path32.relative(process.cwd(), file));
35162
+ } catch {
35163
+ }
35164
+ })
35165
+ );
35166
+ return saved;
35167
+ }
35168
+ var searchCommand2 = defineCommand148({
35169
+ meta: {
35170
+ name: "search",
35171
+ 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"
35172
+ },
35173
+ args: {
35174
+ query: { type: "positional", description: "What you want to see, in plain English", required: false },
35175
+ type: { type: "string", description: "Comma list of section types", required: false },
35176
+ composition: { type: "string", description: "Comma list of composition patterns", required: false },
35177
+ register: { type: "string", description: "Comma list of visual registers", required: false },
35178
+ interaction: { type: "string", description: "Comma list of interaction patterns", required: false },
35179
+ motion: { type: "string", description: "Comma list of motion kinds", required: false },
35180
+ media: { type: "string", description: "Comma list of media kinds", required: false },
35181
+ device: { type: "string", description: "Comma list of content devices", required: false },
35182
+ theme: { type: "string", description: "light | dark | mixed", required: false },
35183
+ "max-rank": { type: "string", description: "Show>Tell rank ceiling 1-7", required: false },
35184
+ "min-craft": { type: "string", description: "Craft floor 0-1", required: false },
35185
+ "min-fidelity": { type: "string", description: "Reproduction-fidelity floor 0-1", required: false },
35186
+ domain: { type: "string", description: "Restrict to one site", required: false },
35187
+ "similar-to": { type: "string", description: "Section id to find lookalikes of", required: false },
35188
+ scope: { type: "string", description: "favorites (default) | all", required: false, default: "favorites" },
35189
+ limit: { type: "string", description: "Max results (default 8)", required: false },
35190
+ "no-images": { type: "boolean", description: "Skip downloading screenshots", required: false, default: false },
35191
+ full: { type: "boolean", description: "Include every classification facet", required: false, default: false }
35192
+ },
35193
+ run: async ({ args }) => {
35194
+ try {
35195
+ const body = buildSearchBody(args);
35196
+ const data = await apiPost("/api/landing-inspiration/search", body);
35197
+ const results = Array.isArray(data?.results) ? data.results : [];
35198
+ const shots = args["no-images"] ? /* @__PURE__ */ new Map() : await downloadShots(results);
35199
+ const full = args.full;
35200
+ const rows = results.map((result) => ({
35201
+ id: result.id,
35202
+ section: result.sectionType,
35203
+ composition: result.composition,
35204
+ look: result.visualRegister,
35205
+ motion: result.motionSummary,
35206
+ why_it_works: result.whyItWorks,
35207
+ craft: Number(result.craftScore?.toFixed?.(2) ?? result.craftScore),
35208
+ fidelity: result.fidelity,
35209
+ domain: result.domain,
35210
+ // Only worth saying when it means something: >1 marks a section the site
35211
+ // reuses across its pages, which is a stronger signal than a one-off.
35212
+ ...result.pageCount > 1 ? { used_on_pages: result.pageCount } : {},
35213
+ screenshot: shots.get(result.id) ?? null,
35214
+ ...full ? {
35215
+ theme: result.theme,
35216
+ density: result.density,
35217
+ show_tell_rank: result.showTellRank,
35218
+ interactions: result.interactions,
35219
+ media: result.mediaKinds,
35220
+ devices: result.contentDevices,
35221
+ tags: result.tags,
35222
+ headline: result.headline,
35223
+ source_url: result.sourceUrl
35224
+ } : {}
35225
+ }));
35226
+ const hints = [INSPIRATION_HINTS.adapt];
35227
+ if (rows.length === 0) {
35228
+ hints.push(
35229
+ 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>`."
35230
+ );
35231
+ } else if (data.moreInFullLibrary > 0) {
35232
+ hints.push(`${data.moreInFullLibrary} more match in the full library \u2014 re-run with --scope all.`);
35233
+ }
35234
+ if (shots.size > 0) hints.push(`Screenshots saved to .baker/inspiration/ \u2014 Read them before deciding.`);
35235
+ writeJson({
35236
+ ok: true,
35237
+ data: { results: rows, scope: data.scope, more_in_full_library: data.moreInFullLibrary, total: data.total },
35238
+ hints
35239
+ });
35240
+ } catch (error) {
35241
+ reportError(error);
35242
+ }
35243
+ }
35244
+ });
35245
+
35246
+ // src/commands/landing/inspiration/view.ts
35247
+ import { mkdir as mkdir13, writeFile as writeFile17 } from "fs/promises";
35248
+ import path33 from "path";
35249
+ import { defineCommand as defineCommand149 } from "citty";
35250
+ registerSchema({
35251
+ command: "landing.inspiration.view",
35252
+ 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.",
35253
+ args: { id: { type: "string", description: "Section id from search", required: true } }
35254
+ });
35255
+ async function download(url, file) {
35256
+ if (!url) return null;
35257
+ try {
35258
+ const response = await fetch(url);
35259
+ if (!response.ok) return null;
35260
+ await mkdir13(path33.dirname(file), { recursive: true });
35261
+ await writeFile17(file, Buffer.from(await response.arrayBuffer()));
35262
+ return path33.relative(process.cwd(), file);
35263
+ } catch {
35264
+ return null;
35265
+ }
35266
+ }
35267
+ var viewCommand2 = defineCommand149({
35268
+ meta: {
35269
+ name: "view",
35270
+ description: "Full detail for one reference section. Example: baker landing inspiration view k57abc\u2026 \u2014 read the screenshots it saves before you build."
35271
+ },
35272
+ args: { id: { type: "positional", description: "Section id from search", required: true } },
35273
+ run: async ({ args }) => {
35274
+ try {
35275
+ const id = args.id;
35276
+ const data = await apiGet("/api/landing-inspiration/section", { id });
35277
+ const section = data.section;
35278
+ const dir = path33.join(process.cwd(), ".baker", "inspiration", id);
35279
+ const [desktop, mobile, filmstrip] = await Promise.all([
35280
+ download(section.desktopShotUrl, path33.join(dir, "desktop.png")),
35281
+ download(section.mobileShotUrl, path33.join(dir, "mobile.png")),
35282
+ download(section.motionFilmstripUrl, path33.join(dir, "motion-filmstrip.png"))
35283
+ ]);
35284
+ const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
35285
+ const fidelity = fidelityHint(section.fidelity);
35286
+ if (fidelity) hints.push(fidelity);
35287
+ if (filmstrip) {
35288
+ hints.push(
35289
+ "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."
35290
+ );
35291
+ }
35292
+ writeJson({
35293
+ ok: true,
35294
+ data: {
35295
+ id: section.id,
35296
+ section: section.sectionType,
35297
+ composition: section.composition,
35298
+ look: section.visualRegister,
35299
+ theme: section.theme,
35300
+ density: section.density,
35301
+ show_tell_rank: section.showTellRank,
35302
+ interactions: section.interactions,
35303
+ media: section.mediaKinds,
35304
+ devices: section.contentDevices,
35305
+ tags: section.tags,
35306
+ motion: section.motionSummary,
35307
+ design_tokens: section.designTokens,
35308
+ size: section.boundingBox,
35309
+ copy: {
35310
+ headline: section.headline,
35311
+ subhead: section.subhead,
35312
+ ctas: section.ctaLabels,
35313
+ proof: section.proofSignals
35314
+ },
35315
+ why_it_works: section.whyItWorks,
35316
+ adaptation_notes: section.adaptationNotes,
35317
+ reproduction_notes: section.reproductionNotes,
35318
+ craft: section.craftScore,
35319
+ fidelity: section.fidelity,
35320
+ source_url: section.sourceUrl,
35321
+ screenshots: { desktop, mobile, motion_filmstrip: filmstrip }
35322
+ },
35323
+ hints
35324
+ });
35325
+ } catch (error) {
35326
+ reportError(error);
35327
+ }
35328
+ }
35329
+ });
35330
+
35331
+ // src/commands/landing/inspiration/index.ts
35332
+ var inspirationCommand = defineCommand150({
35333
+ meta: {
35334
+ name: "inspiration",
35335
+ description: `Reference library of real landing-page sections \u2014 look at how good pages actually solve a problem before you design one.
35336
+
35337
+ Start here: \`baker landing inspiration search "<what you want to see>"\` during research, BEFORE you write the Direction Contract.
35338
+
35339
+ 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.
35340
+
35341
+ Subcommands:
35342
+ baker landing inspiration search "<query>" \u2014 search by look, section type, composition, register or motion; saves screenshots you can Read
35343
+ baker landing inspiration view <id> \u2014 one section in full: tokens, motion filmstrip, why it works, what to change
35344
+ baker landing inspiration code <id> \u2014 its standalone HTML+CSS, for structure only
35345
+ baker landing inspiration page <id> \u2014 a whole page as a sequence: how it orders its sections
35346
+ baker landing inspiration add <url> \u2014 add a page to the library and save it to this company (returns immediately)
35347
+ baker landing inspiration favorites \u2014 what this company has saved; the default search scope
35348
+ baker landing inspiration favorite <id> \u2014 save a section
35349
+ baker landing inspiration unfavorite <id> \u2014 unsave a section
35350
+ baker landing inspiration scrape <url> \u2014 internal/ops: run the capture locally
35351
+
35352
+ Examples:
35353
+ baker landing inspiration search "pricing with a monthly/annual toggle" --max-rank 3
35354
+ baker landing inspiration search "dark developer hero with a terminal" --register dev-tool-minimal --scope all
35355
+ baker landing inspiration search "testimonial wall with faces and company logos" --motion scroll-reveal
35356
+ baker landing inspiration add https://linear.app
35357
+
35358
+ Full guide: __tooling__/docs/tools/baker/landing.md`
35359
+ },
35360
+ subCommands: {
35361
+ search: searchCommand2,
35362
+ view: viewCommand2,
35363
+ code: codeCommand,
35364
+ page: pageCommand,
35365
+ add: addCommand,
35366
+ favorites: favoritesCommand,
35367
+ favorite: favoriteCommand,
35368
+ unfavorite: unfavoriteCommand,
35369
+ scrape: scrapeCommand
35370
+ }
35371
+ });
35372
+
32989
35373
  // src/commands/landing/index.ts
32990
- var landingCommand = defineCommand143({
35374
+ var landingCommand = defineCommand151({
32991
35375
  meta: {
32992
35376
  name: "landing",
32993
35377
  description: `Design-quality tools for landing pages (src/pages/<slug>/).
@@ -32995,15 +35379,17 @@ var landingCommand = defineCommand143({
32995
35379
  Start here: \`baker landing critique <slug>\` after building or editing a landing.
32996
35380
 
32997
35381
  Subcommands:
35382
+ 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.
32998
35383
  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.`
32999
35384
  },
33000
35385
  subCommands: {
35386
+ inspiration: inspirationCommand,
33001
35387
  critique: critiqueCommand2
33002
35388
  }
33003
35389
  });
33004
35390
 
33005
35391
  // src/commands/mcp/index.ts
33006
- import { defineCommand as defineCommand144 } from "citty";
35392
+ import { defineCommand as defineCommand152 } from "citty";
33007
35393
 
33008
35394
  // src/commands/mcp/platforms.ts
33009
35395
  function readsKey(label) {
@@ -33072,7 +35458,7 @@ registerSchema({
33072
35458
  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.",
33073
35459
  args: {}
33074
35460
  });
33075
- var connectedCommand = defineCommand144({
35461
+ var connectedCommand = defineCommand152({
33076
35462
  meta: {
33077
35463
  name: "connected",
33078
35464
  description: `Everything this chat can reach \u2014 managed integrations, custom MCP servers, and connected platforms.
@@ -33131,7 +35517,7 @@ registerSchema({
33131
35517
  description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
33132
35518
  args: {}
33133
35519
  });
33134
- var listCommand13 = defineCommand144({
35520
+ var listCommand13 = defineCommand152({
33135
35521
  meta: { name: "list", description: "List custom MCP servers visible to this company's chats." },
33136
35522
  run: async () => {
33137
35523
  try {
@@ -33168,7 +35554,7 @@ registerSchema({
33168
35554
  header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
33169
35555
  }
33170
35556
  });
33171
- var addCommand = defineCommand144({
35557
+ var addCommand2 = defineCommand152({
33172
35558
  meta: {
33173
35559
  name: "add",
33174
35560
  description: `Register a custom MCP server. Tools appear as mcp__<name>__* on the NEXT message.
@@ -33220,7 +35606,7 @@ registerSchema({
33220
35606
  description: "Remove a company custom MCP server by name.",
33221
35607
  args: { name: { type: "string", description: "Server name to remove", required: true } }
33222
35608
  });
33223
- var removeCommand4 = defineCommand144({
35609
+ var removeCommand4 = defineCommand152({
33224
35610
  meta: {
33225
35611
  name: "remove",
33226
35612
  description: `Remove a company custom MCP server by name.
@@ -33242,7 +35628,7 @@ Example:
33242
35628
  }
33243
35629
  }
33244
35630
  });
33245
- var mcpCommand = defineCommand144({
35631
+ var mcpCommand = defineCommand152({
33246
35632
  meta: {
33247
35633
  name: "mcp",
33248
35634
  description: `Third-party tools for this company \u2014 see what's connected, register custom HTTPS MCP endpoints.
@@ -33262,16 +35648,16 @@ Full guide: __tooling__/docs/tools/baker/mcp.md`
33262
35648
  subCommands: {
33263
35649
  connected: connectedCommand,
33264
35650
  list: listCommand13,
33265
- add: addCommand,
35651
+ add: addCommand2,
33266
35652
  remove: removeCommand4
33267
35653
  }
33268
35654
  });
33269
35655
 
33270
35656
  // src/commands/research/index.ts
33271
- import { defineCommand as defineCommand155 } from "citty";
35657
+ import { defineCommand as defineCommand163 } from "citty";
33272
35658
 
33273
35659
  // src/commands/research/advertisers.ts
33274
- import { defineCommand as defineCommand145 } from "citty";
35660
+ import { defineCommand as defineCommand153 } from "citty";
33275
35661
 
33276
35662
  // src/commands/research/output.ts
33277
35663
  var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
@@ -33384,7 +35770,7 @@ var FIELDS3 = {
33384
35770
  etv: "Estimated traffic value (USD)",
33385
35771
  visibility: "SERP visibility score (0-1)"
33386
35772
  };
33387
- var advertisersCommand = defineCommand145({
35773
+ var advertisersCommand = defineCommand153({
33388
35774
  meta: {
33389
35775
  name: "advertisers",
33390
35776
  description: `Find domains competing for a keyword in Google SERPs.
@@ -33404,15 +35790,15 @@ Examples:
33404
35790
  },
33405
35791
  run: async ({ args }) => {
33406
35792
  const keyword = args.keyword;
33407
- const location = args.location || void 0;
35793
+ const location2 = args.location || void 0;
33408
35794
  const language = args.language || void 0;
33409
35795
  const limit = args.limit ? Number(args.limit) : void 0;
33410
35796
  const skipCache = args["no-cache"] ? true : void 0;
33411
- const queryContext = buildResearchQueryContext(location, language);
35797
+ const queryContext = buildResearchQueryContext(location2, language);
33412
35798
  try {
33413
35799
  const data = await apiPost("/api/research/advertisers", {
33414
35800
  keyword,
33415
- location,
35801
+ location: location2,
33416
35802
  language,
33417
35803
  limit,
33418
35804
  skipCache
@@ -33431,7 +35817,7 @@ Examples:
33431
35817
  });
33432
35818
 
33433
35819
  // src/commands/research/autocomplete.ts
33434
- import { defineCommand as defineCommand146 } from "citty";
35820
+ import { defineCommand as defineCommand154 } from "citty";
33435
35821
  registerSchema({
33436
35822
  command: "research.autocomplete",
33437
35823
  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).",
@@ -33454,7 +35840,7 @@ registerSchema({
33454
35840
  var FIELDS4 = {
33455
35841
  suggestion: "Autocomplete suggestion from Google"
33456
35842
  };
33457
- var autocompleteCommand = defineCommand146({
35843
+ var autocompleteCommand = defineCommand154({
33458
35844
  meta: {
33459
35845
  name: "autocomplete",
33460
35846
  description: `Get Google Autocomplete suggestions for keyword expansion.
@@ -33473,15 +35859,15 @@ Examples:
33473
35859
  },
33474
35860
  run: async ({ args }) => {
33475
35861
  const keyword = args.keyword;
33476
- const location = args.location || void 0;
35862
+ const location2 = args.location || void 0;
33477
35863
  const language = args.language || void 0;
33478
35864
  const limit = args.limit ? Number(args.limit) : void 0;
33479
35865
  const skipCache = args["no-cache"] ? true : void 0;
33480
- const queryContext = buildResearchQueryContext(location, language);
35866
+ const queryContext = buildResearchQueryContext(location2, language);
33481
35867
  try {
33482
35868
  const data = await apiPost("/api/research/autocomplete", {
33483
35869
  keyword,
33484
- location,
35870
+ location: location2,
33485
35871
  language,
33486
35872
  limit,
33487
35873
  skipCache
@@ -33500,7 +35886,7 @@ Examples:
33500
35886
  });
33501
35887
 
33502
35888
  // src/commands/research/countries.ts
33503
- import { defineCommand as defineCommand147 } from "citty";
35889
+ import { defineCommand as defineCommand155 } from "citty";
33504
35890
  registerSchema({
33505
35891
  command: "research.countries",
33506
35892
  description: "List all supported country codes for --location flag in research commands.",
@@ -33557,7 +35943,7 @@ var FIELDS5 = {
33557
35943
  code: "Country code to pass as --location",
33558
35944
  name: "Country name"
33559
35945
  };
33560
- var countriesCommand = defineCommand147({
35946
+ var countriesCommand = defineCommand155({
33561
35947
  meta: {
33562
35948
  name: "countries",
33563
35949
  description: "List all supported country codes for --location flag."
@@ -33568,7 +35954,7 @@ var countriesCommand = defineCommand147({
33568
35954
  });
33569
35955
 
33570
35956
  // src/commands/research/intent.ts
33571
- import { defineCommand as defineCommand148 } from "citty";
35957
+ import { defineCommand as defineCommand156 } from "citty";
33572
35958
  registerSchema({
33573
35959
  command: "research.intent",
33574
35960
  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.",
@@ -33591,7 +35977,7 @@ var FIELDS6 = {
33591
35977
  intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
33592
35978
  probability: "Confidence score 0.0-1.0"
33593
35979
  };
33594
- var intentCommand = defineCommand148({
35980
+ var intentCommand = defineCommand156({
33595
35981
  meta: {
33596
35982
  name: "intent",
33597
35983
  description: `Classify Google Search intent for keywords. Returns intent type and confidence.
@@ -33639,7 +36025,7 @@ Examples:
33639
36025
  });
33640
36026
 
33641
36027
  // src/commands/research/keyword-gap.ts
33642
- import { defineCommand as defineCommand149 } from "citty";
36028
+ import { defineCommand as defineCommand157 } from "citty";
33643
36029
  registerSchema({
33644
36030
  command: "research.keyword-gap",
33645
36031
  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.",
@@ -33668,7 +36054,7 @@ var FIELDS7 = {
33668
36054
  cpc: "Cost per click USD",
33669
36055
  their_position: "Competitor's ranking position"
33670
36056
  };
33671
- var keywordGapCommand = defineCommand149({
36057
+ var keywordGapCommand = defineCommand157({
33672
36058
  meta: {
33673
36059
  name: "keyword-gap",
33674
36060
  description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
@@ -33692,18 +36078,18 @@ Examples:
33692
36078
  run: async ({ args }) => {
33693
36079
  const competitor = args.competitor;
33694
36080
  const ours = args.ours;
33695
- const location = args.location || void 0;
36081
+ const location2 = args.location || void 0;
33696
36082
  const language = args.language || void 0;
33697
36083
  const type = args.type || void 0;
33698
36084
  const limit = args.limit ? Number(args.limit) : void 0;
33699
36085
  const offset = args.offset ? Number(args.offset) : void 0;
33700
36086
  const skipCache = args["no-cache"] ? true : void 0;
33701
- const queryContext = buildResearchQueryContext(location, language);
36087
+ const queryContext = buildResearchQueryContext(location2, language);
33702
36088
  try {
33703
36089
  const result = await apiPost("/api/research/keyword-gap", {
33704
36090
  competitor,
33705
36091
  ours,
33706
- location,
36092
+ location: location2,
33707
36093
  language,
33708
36094
  type,
33709
36095
  limit,
@@ -33742,7 +36128,7 @@ Examples:
33742
36128
  });
33743
36129
 
33744
36130
  // src/commands/research/keywords-for-site.ts
33745
- import { defineCommand as defineCommand150 } from "citty";
36131
+ import { defineCommand as defineCommand158 } from "citty";
33746
36132
  registerSchema({
33747
36133
  command: "research.keywords-for-site",
33748
36134
  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.",
@@ -33775,7 +36161,7 @@ var FIELDS8 = {
33775
36161
  competition: "LOW, MEDIUM, or HIGH",
33776
36162
  competition_index: "Competition score 0-100"
33777
36163
  };
33778
- var keywordsForSiteCommand = defineCommand150({
36164
+ var keywordsForSiteCommand = defineCommand158({
33779
36165
  meta: {
33780
36166
  name: "keywords-for-site",
33781
36167
  description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
@@ -33797,17 +36183,17 @@ Examples:
33797
36183
  },
33798
36184
  run: async ({ args }) => {
33799
36185
  const target = args.target;
33800
- const location = args.location || void 0;
36186
+ const location2 = args.location || void 0;
33801
36187
  const language = args.language || void 0;
33802
36188
  const sort = args.sort || void 0;
33803
36189
  const type = args.type || void 0;
33804
36190
  const limit = args.limit ? Number(args.limit) : void 0;
33805
36191
  const skipCache = args["no-cache"] ? true : void 0;
33806
- const queryContext = buildResearchQueryContext(location, language);
36192
+ const queryContext = buildResearchQueryContext(location2, language);
33807
36193
  try {
33808
36194
  const data = await apiPost("/api/research/keywords-for-site", {
33809
36195
  target,
33810
- location,
36196
+ location: location2,
33811
36197
  language,
33812
36198
  sort,
33813
36199
  type,
@@ -33828,7 +36214,7 @@ Examples:
33828
36214
  });
33829
36215
 
33830
36216
  // src/commands/research/languages.ts
33831
- import { defineCommand as defineCommand151 } from "citty";
36217
+ import { defineCommand as defineCommand159 } from "citty";
33832
36218
  registerSchema({
33833
36219
  command: "research.languages",
33834
36220
  description: "List all supported language codes for --language flag in research commands.",
@@ -33858,7 +36244,7 @@ var FIELDS9 = {
33858
36244
  code: "Language code to pass as --language",
33859
36245
  name: "Language name (also accepted by --language)"
33860
36246
  };
33861
- var languagesCommand2 = defineCommand151({
36247
+ var languagesCommand2 = defineCommand159({
33862
36248
  meta: {
33863
36249
  name: "languages",
33864
36250
  description: "List all supported language codes for --language flag."
@@ -33869,7 +36255,7 @@ var languagesCommand2 = defineCommand151({
33869
36255
  });
33870
36256
 
33871
36257
  // src/commands/research/lighthouse.ts
33872
- import { defineCommand as defineCommand152 } from "citty";
36258
+ import { defineCommand as defineCommand160 } from "citty";
33873
36259
  registerSchema({
33874
36260
  command: "research.lighthouse",
33875
36261
  description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
@@ -33888,7 +36274,7 @@ var FIELDS10 = {
33888
36274
  speed_index_ms: "Speed Index in ms (good: < 3400)",
33889
36275
  interactive_ms: "Time to Interactive in ms (good: < 3800)"
33890
36276
  };
33891
- var lighthouseCommand = defineCommand152({
36277
+ var lighthouseCommand = defineCommand160({
33892
36278
  meta: {
33893
36279
  name: "lighthouse",
33894
36280
  description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
@@ -33926,7 +36312,7 @@ Examples:
33926
36312
  });
33927
36313
 
33928
36314
  // src/commands/research/relevant-pages.ts
33929
- import { defineCommand as defineCommand153 } from "citty";
36315
+ import { defineCommand as defineCommand161 } from "citty";
33930
36316
  registerSchema({
33931
36317
  command: "research.relevant-pages",
33932
36318
  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).",
@@ -33952,7 +36338,7 @@ var FIELDS11 = {
33952
36338
  keywords: "Total organic keywords the page ranks for",
33953
36339
  top_10: "Keywords in positions 1-10"
33954
36340
  };
33955
- var relevantPagesCommand = defineCommand153({
36341
+ var relevantPagesCommand = defineCommand161({
33956
36342
  meta: {
33957
36343
  name: "relevant-pages",
33958
36344
  description: `Get the top pages of a competitor domain with traffic data.
@@ -33971,15 +36357,15 @@ Examples:
33971
36357
  },
33972
36358
  run: async ({ args }) => {
33973
36359
  const target = args.target;
33974
- const location = args.location || void 0;
36360
+ const location2 = args.location || void 0;
33975
36361
  const language = args.language || void 0;
33976
36362
  const limit = args.limit ? Number(args.limit) : void 0;
33977
36363
  const skipCache = args["no-cache"] ? true : void 0;
33978
- const queryContext = buildResearchQueryContext(location, language);
36364
+ const queryContext = buildResearchQueryContext(location2, language);
33979
36365
  try {
33980
36366
  const data = await apiPost("/api/research/relevant-pages", {
33981
36367
  target,
33982
- location,
36368
+ location: location2,
33983
36369
  language,
33984
36370
  limit,
33985
36371
  skipCache
@@ -33998,7 +36384,7 @@ Examples:
33998
36384
  });
33999
36385
 
34000
36386
  // src/commands/research/web.ts
34001
- import { defineCommand as defineCommand154 } from "citty";
36387
+ import { defineCommand as defineCommand162 } from "citty";
34002
36388
  registerSchema({
34003
36389
  command: "research.web",
34004
36390
  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).",
@@ -34049,7 +36435,7 @@ async function runDeepResearch(question) {
34049
36435
  }
34050
36436
  throw new Error("Deep research timed out");
34051
36437
  }
34052
- var webCommand = defineCommand154({
36438
+ var webCommand = defineCommand162({
34053
36439
  meta: {
34054
36440
  name: "web",
34055
36441
  description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
@@ -34109,7 +36495,7 @@ Examples:
34109
36495
  });
34110
36496
 
34111
36497
  // src/commands/research/index.ts
34112
- var researchCommand = defineCommand155({
36498
+ var researchCommand = defineCommand163({
34113
36499
  meta: {
34114
36500
  name: "research",
34115
36501
  description: `Competitive intelligence and AI-powered research commands.
@@ -34150,10 +36536,10 @@ Full guide: __tooling__/docs/tools/baker/research.md`
34150
36536
  });
34151
36537
 
34152
36538
  // src/commands/scheduled-actions/index.ts
34153
- import { defineCommand as defineCommand162 } from "citty";
36539
+ import { defineCommand as defineCommand170 } from "citty";
34154
36540
 
34155
36541
  // src/commands/scheduled-actions/create.ts
34156
- import { defineCommand as defineCommand156 } from "citty";
36542
+ import { defineCommand as defineCommand164 } from "citty";
34157
36543
 
34158
36544
  // src/commands/scheduled-actions/shared.ts
34159
36545
  var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
@@ -34268,7 +36654,7 @@ registerSchema({
34268
36654
  prompt: { type: "string", description: "Additional prompt instructions for the spawned agent", required: false }
34269
36655
  }
34270
36656
  });
34271
- var createCommand2 = defineCommand156({
36657
+ var createCommand2 = defineCommand164({
34272
36658
  meta: {
34273
36659
  name: "create",
34274
36660
  description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
@@ -34317,7 +36703,7 @@ var createCommand2 = defineCommand156({
34317
36703
  });
34318
36704
 
34319
36705
  // src/commands/scheduled-actions/delete.ts
34320
- import { defineCommand as defineCommand157 } from "citty";
36706
+ import { defineCommand as defineCommand165 } from "citty";
34321
36707
  registerSchema({
34322
36708
  command: "scheduled-actions.delete",
34323
36709
  description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
@@ -34325,7 +36711,7 @@ registerSchema({
34325
36711
  id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
34326
36712
  }
34327
36713
  });
34328
- var deleteCommand2 = defineCommand157({
36714
+ var deleteCommand2 = defineCommand165({
34329
36715
  meta: {
34330
36716
  name: "delete",
34331
36717
  description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
@@ -34354,7 +36740,7 @@ var deleteCommand2 = defineCommand157({
34354
36740
  });
34355
36741
 
34356
36742
  // src/commands/scheduled-actions/get.ts
34357
- import { defineCommand as defineCommand158 } from "citty";
36743
+ import { defineCommand as defineCommand166 } from "citty";
34358
36744
  registerSchema({
34359
36745
  command: "scheduled-actions.get",
34360
36746
  description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
@@ -34363,7 +36749,7 @@ registerSchema({
34363
36749
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
34364
36750
  }
34365
36751
  });
34366
- var getCommand3 = defineCommand158({
36752
+ var getCommand3 = defineCommand166({
34367
36753
  meta: {
34368
36754
  name: "get",
34369
36755
  description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
@@ -34402,7 +36788,7 @@ var getCommand3 = defineCommand158({
34402
36788
  });
34403
36789
 
34404
36790
  // src/commands/scheduled-actions/list.ts
34405
- import { defineCommand as defineCommand159 } from "citty";
36791
+ import { defineCommand as defineCommand167 } from "citty";
34406
36792
  registerSchema({
34407
36793
  command: "scheduled-actions.list",
34408
36794
  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.",
@@ -34410,7 +36796,7 @@ registerSchema({
34410
36796
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
34411
36797
  }
34412
36798
  });
34413
- var listCommand14 = defineCommand159({
36799
+ var listCommand14 = defineCommand167({
34414
36800
  meta: {
34415
36801
  name: "list",
34416
36802
  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."
@@ -34433,7 +36819,7 @@ var listCommand14 = defineCommand159({
34433
36819
  });
34434
36820
 
34435
36821
  // src/commands/scheduled-actions/trigger.ts
34436
- import { defineCommand as defineCommand160 } from "citty";
36822
+ import { defineCommand as defineCommand168 } from "citty";
34437
36823
  registerSchema({
34438
36824
  command: "scheduled-actions.trigger",
34439
36825
  description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
@@ -34441,7 +36827,7 @@ registerSchema({
34441
36827
  id: { type: "string", description: "Published scheduled action ID", required: true }
34442
36828
  }
34443
36829
  });
34444
- var triggerCommand = defineCommand160({
36830
+ var triggerCommand = defineCommand168({
34445
36831
  meta: {
34446
36832
  name: "trigger",
34447
36833
  description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
@@ -34478,7 +36864,7 @@ var triggerCommand = defineCommand160({
34478
36864
  });
34479
36865
 
34480
36866
  // src/commands/scheduled-actions/update.ts
34481
- import { defineCommand as defineCommand161 } from "citty";
36867
+ import { defineCommand as defineCommand169 } from "citty";
34482
36868
  registerSchema({
34483
36869
  command: "scheduled-actions.update",
34484
36870
  description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
@@ -34503,7 +36889,7 @@ registerSchema({
34503
36889
  prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
34504
36890
  }
34505
36891
  });
34506
- var updateCommand2 = defineCommand161({
36892
+ var updateCommand2 = defineCommand169({
34507
36893
  meta: {
34508
36894
  name: "update",
34509
36895
  description: "Stage a scheduled action update. Example: baker scheduled-actions update <id> --enabled false"
@@ -34574,7 +36960,7 @@ var updateCommand2 = defineCommand161({
34574
36960
  });
34575
36961
 
34576
36962
  // src/commands/scheduled-actions/index.ts
34577
- var scheduledActionsCommand = defineCommand162({
36963
+ var scheduledActionsCommand = defineCommand170({
34578
36964
  meta: {
34579
36965
  name: "scheduled-actions",
34580
36966
  description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger.
@@ -34601,14 +36987,14 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
34601
36987
  });
34602
36988
 
34603
36989
  // src/commands/schema.ts
34604
- import { defineCommand as defineCommand163 } from "citty";
36990
+ import { defineCommand as defineCommand171 } from "citty";
34605
36991
  function narrowToFamily(commandName, available) {
34606
36992
  const segments = commandName.split(".");
34607
36993
  const prefix = segments[0] === "ads" && segments[1] ? `ads.${segments[1]}.` : `${segments[0]}.`;
34608
36994
  const siblings = available.filter((name) => name.startsWith(prefix));
34609
36995
  return siblings.length > 0 ? siblings : available;
34610
36996
  }
34611
- var schemaCommand = defineCommand163({
36997
+ var schemaCommand = defineCommand171({
34612
36998
  meta: {
34613
36999
  name: "schema",
34614
37000
  description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
@@ -34652,10 +37038,10 @@ var schemaCommand = defineCommand163({
34652
37038
  });
34653
37039
 
34654
37040
  // src/commands/tag-manager/index.ts
34655
- import { defineCommand as defineCommand167 } from "citty";
37041
+ import { defineCommand as defineCommand175 } from "citty";
34656
37042
 
34657
37043
  // src/commands/tag-manager/draft.ts
34658
- import { defineCommand as defineCommand164 } from "citty";
37044
+ import { defineCommand as defineCommand172 } from "citty";
34659
37045
 
34660
37046
  // src/commands/tag-manager/shared.ts
34661
37047
  import { readFileSync as readFileSync13 } from "fs";
@@ -34722,10 +37108,10 @@ async function stageOp4(op) {
34722
37108
  handleError4(err);
34723
37109
  }
34724
37110
  }
34725
- async function draftAction3(path28, body, chat) {
37111
+ async function draftAction3(path34, body, chat) {
34726
37112
  const chatId = resolveChatId(chat);
34727
37113
  try {
34728
- const data = await apiPost(path28, { chatId, ...body });
37114
+ const data = await apiPost(path34, { chatId, ...body });
34729
37115
  writeJsonEnvelope({ ok: true, data });
34730
37116
  return data;
34731
37117
  } catch (err) {
@@ -34778,13 +37164,13 @@ registerSchema({
34778
37164
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
34779
37165
  }
34780
37166
  });
34781
- var draftCommand4 = defineCommand164({
37167
+ var draftCommand4 = defineCommand172({
34782
37168
  meta: {
34783
37169
  name: "draft",
34784
37170
  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."
34785
37171
  },
34786
37172
  subCommands: {
34787
- list: defineCommand164({
37173
+ list: defineCommand172({
34788
37174
  meta: {
34789
37175
  name: "list",
34790
37176
  description: "Review everything staged on this chat (--json for the raw envelope)"
@@ -34797,7 +37183,7 @@ var draftCommand4 = defineCommand164({
34797
37183
  await draftList2(args.json === true, args.chat);
34798
37184
  }
34799
37185
  }),
34800
- show: defineCommand164({
37186
+ show: defineCommand172({
34801
37187
  meta: {
34802
37188
  name: "show",
34803
37189
  description: "Print the full staged payload for one change \u2014 the receipt to verify it looks right before publish (never truncated)."
@@ -34814,7 +37200,7 @@ var draftCommand4 = defineCommand164({
34814
37200
  );
34815
37201
  }
34816
37202
  }),
34817
- amend: defineCommand164({
37203
+ amend: defineCommand172({
34818
37204
  meta: {
34819
37205
  name: "amend",
34820
37206
  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."
@@ -34831,7 +37217,7 @@ var draftCommand4 = defineCommand164({
34831
37217
  });
34832
37218
  }
34833
37219
  }),
34834
- remove: defineCommand164({
37220
+ remove: defineCommand172({
34835
37221
  meta: { name: "remove", description: "Remove one staged change (cascades to anything depending on it)" },
34836
37222
  args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
34837
37223
  run: async ({ args }) => {
@@ -34840,7 +37226,7 @@ var draftCommand4 = defineCommand164({
34840
37226
  });
34841
37227
  }
34842
37228
  }),
34843
- clear: defineCommand164({
37229
+ clear: defineCommand172({
34844
37230
  meta: { name: "clear", description: "Discard all Tag Manager changes staged on this chat" },
34845
37231
  run: async () => {
34846
37232
  await draftAction3("/api/tag-manager/draft/clear", {});
@@ -34850,7 +37236,7 @@ var draftCommand4 = defineCommand164({
34850
37236
  });
34851
37237
 
34852
37238
  // src/commands/tag-manager/read.ts
34853
- import { defineCommand as defineCommand165 } from "citty";
37239
+ import { defineCommand as defineCommand173 } from "citty";
34854
37240
  registerSchema({
34855
37241
  command: "tagManager.containers",
34856
37242
  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.",
@@ -34891,7 +37277,7 @@ function containersHints(containers) {
34891
37277
  }))
34892
37278
  });
34893
37279
  }
34894
- var containersCommand = defineCommand165({
37280
+ var containersCommand = defineCommand173({
34895
37281
  meta: {
34896
37282
  name: "containers",
34897
37283
  description: `List Tag Manager containers reachable by this company's connection.
@@ -34908,7 +37294,7 @@ Start here:
34908
37294
  }
34909
37295
  }
34910
37296
  });
34911
- var readCommand = defineCommand165({
37297
+ var readCommand = defineCommand173({
34912
37298
  meta: {
34913
37299
  name: "read",
34914
37300
  description: `Read the current contents of the Tag Manager container \u2014 always do this before staging changes.
@@ -34950,7 +37336,7 @@ Examples:
34950
37336
  });
34951
37337
 
34952
37338
  // src/commands/tag-manager/write-commands.ts
34953
- import { defineCommand as defineCommand166 } from "citty";
37339
+ import { defineCommand as defineCommand174 } from "citty";
34954
37340
  var CONTAINER_ARG_DESCRIPTION = "Numeric container id (optional only when one container is connected \u2014 run `baker tag-manager containers`)";
34955
37341
  var ENTITIES = [
34956
37342
  {
@@ -35006,10 +37392,10 @@ for (const { entity, noun, createHint } of ENTITIES) {
35006
37392
  });
35007
37393
  }
35008
37394
  function entityCommand(entity, noun, example) {
35009
- return defineCommand166({
37395
+ return defineCommand174({
35010
37396
  meta: { name: entity, description: `Stage ${noun} changes on this chat's Tag Manager draft` },
35011
37397
  subCommands: {
35012
- create: defineCommand166({
37398
+ create: defineCommand174({
35013
37399
  meta: {
35014
37400
  name: "create",
35015
37401
  description: `Stage a new ${noun}
@@ -35031,7 +37417,7 @@ Examples:
35031
37417
  });
35032
37418
  }
35033
37419
  }),
35034
- update: defineCommand166({
37420
+ update: defineCommand174({
35035
37421
  meta: {
35036
37422
  name: "update",
35037
37423
  description: `Stage an update to an existing ${noun} (pass its id or path)`
@@ -35051,7 +37437,7 @@ Examples:
35051
37437
  });
35052
37438
  }
35053
37439
  }),
35054
- delete: defineCommand166({
37440
+ delete: defineCommand174({
35055
37441
  meta: { name: "delete", description: `Stage the deletion of a ${noun} (pass its id or path)` },
35056
37442
  args: {
35057
37443
  id: { type: "positional", description: `${noun} id or path`, required: false },
@@ -35092,7 +37478,7 @@ function builtinTypes(args) {
35092
37478
  }
35093
37479
  return raw.split(",").map((entry) => entry.trim());
35094
37480
  }
35095
- var builtinCommand = defineCommand166({
37481
+ var builtinCommand = defineCommand174({
35096
37482
  meta: {
35097
37483
  name: "builtin",
35098
37484
  description: `Enable or disable built-in variables
@@ -35102,7 +37488,7 @@ Examples:
35102
37488
  baker tag-manager builtin disable --types formId`
35103
37489
  },
35104
37490
  subCommands: {
35105
- enable: defineCommand166({
37491
+ enable: defineCommand174({
35106
37492
  meta: { name: "enable", description: "Stage enabling built-in variables" },
35107
37493
  args: {
35108
37494
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -35116,7 +37502,7 @@ Examples:
35116
37502
  });
35117
37503
  }
35118
37504
  }),
35119
- disable: defineCommand166({
37505
+ disable: defineCommand174({
35120
37506
  meta: { name: "disable", description: "Stage disabling built-in variables" },
35121
37507
  args: {
35122
37508
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -35134,7 +37520,7 @@ Examples:
35134
37520
  });
35135
37521
 
35136
37522
  // src/commands/tag-manager/index.ts
35137
- var tagManagerCommand = defineCommand167({
37523
+ var tagManagerCommand = defineCommand175({
35138
37524
  meta: {
35139
37525
  name: "tag-manager",
35140
37526
  description: `Read and change what lives inside the client's Google Tag Manager container \u2014 tags, triggers, variables, folders and built-in variables.
@@ -35171,7 +37557,7 @@ Full guide: __tooling__/docs/tools/baker/tag-manager.md`
35171
37557
  });
35172
37558
 
35173
37559
  // src/commands/tags/index.ts
35174
- import { defineCommand as defineCommand168 } from "citty";
37560
+ import { defineCommand as defineCommand176 } from "citty";
35175
37561
 
35176
37562
  // src/commands/tags/shared.ts
35177
37563
  function failApi3(err) {
@@ -35240,7 +37626,7 @@ async function listTags(json) {
35240
37626
  var listArgs9 = {
35241
37627
  json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list" }
35242
37628
  };
35243
- var listCommand15 = defineCommand168({
37629
+ var listCommand15 = defineCommand176({
35244
37630
  meta: {
35245
37631
  name: "list",
35246
37632
  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"
@@ -35259,7 +37645,7 @@ async function listDraft3(chat) {
35259
37645
  failApi3(err);
35260
37646
  }
35261
37647
  }
35262
- var draftCommand5 = defineCommand168({
37648
+ var draftCommand5 = defineCommand176({
35263
37649
  meta: {
35264
37650
  name: "draft",
35265
37651
  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."
@@ -35269,7 +37655,7 @@ var draftCommand5 = defineCommand168({
35269
37655
  await listDraft3(args.chat);
35270
37656
  }
35271
37657
  });
35272
- var tagsCommand3 = defineCommand168({
37658
+ var tagsCommand3 = defineCommand176({
35273
37659
  meta: {
35274
37660
  name: "tags",
35275
37661
  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.
@@ -35298,10 +37684,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
35298
37684
  });
35299
37685
 
35300
37686
  // src/commands/testimonials/index.ts
35301
- import { defineCommand as defineCommand172 } from "citty";
37687
+ import { defineCommand as defineCommand180 } from "citty";
35302
37688
 
35303
37689
  // src/commands/testimonials/get.ts
35304
- import { defineCommand as defineCommand169 } from "citty";
37690
+ import { defineCommand as defineCommand177 } from "citty";
35305
37691
  registerSchema({
35306
37692
  command: "testimonials.get",
35307
37693
  description: "Get a single testimonial by ID",
@@ -35309,7 +37695,7 @@ registerSchema({
35309
37695
  id: { type: "string", description: "Testimonial ID", required: true }
35310
37696
  }
35311
37697
  });
35312
- var getCommand4 = defineCommand169({
37698
+ var getCommand4 = defineCommand177({
35313
37699
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
35314
37700
  args: {
35315
37701
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -35346,7 +37732,7 @@ var getCommand4 = defineCommand169({
35346
37732
  });
35347
37733
 
35348
37734
  // src/commands/testimonials/list.ts
35349
- import { defineCommand as defineCommand170 } from "citty";
37735
+ import { defineCommand as defineCommand178 } from "citty";
35350
37736
  registerSchema({
35351
37737
  command: "testimonials.list",
35352
37738
  description: "List testimonials with optional filters.",
@@ -35376,7 +37762,7 @@ registerSchema({
35376
37762
  limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
35377
37763
  }
35378
37764
  });
35379
- var listCommand16 = defineCommand170({
37765
+ var listCommand16 = defineCommand178({
35380
37766
  meta: {
35381
37767
  name: "list",
35382
37768
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -35425,7 +37811,7 @@ var listCommand16 = defineCommand170({
35425
37811
  });
35426
37812
 
35427
37813
  // src/commands/testimonials/search.ts
35428
- import { defineCommand as defineCommand171 } from "citty";
37814
+ import { defineCommand as defineCommand179 } from "citty";
35429
37815
  function languageBiasHint(results, requestedLanguage) {
35430
37816
  if (requestedLanguage) {
35431
37817
  return null;
@@ -35503,7 +37889,7 @@ function buildSearchRequest(query, args) {
35503
37889
  }
35504
37890
  return body;
35505
37891
  }
35506
- var searchCommand2 = defineCommand171({
37892
+ var searchCommand3 = defineCommand179({
35507
37893
  meta: {
35508
37894
  name: "search",
35509
37895
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -35559,7 +37945,7 @@ var searchCommand2 = defineCommand171({
35559
37945
  var tagsCommand4 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
35560
37946
 
35561
37947
  // src/commands/testimonials/index.ts
35562
- var testimonialsCommand = defineCommand172({
37948
+ var testimonialsCommand = defineCommand180({
35563
37949
  meta: {
35564
37950
  name: "testimonials",
35565
37951
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -35574,17 +37960,17 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
35574
37960
  },
35575
37961
  subCommands: {
35576
37962
  get: getCommand4,
35577
- search: searchCommand2,
37963
+ search: searchCommand3,
35578
37964
  list: listCommand16,
35579
37965
  tags: tagsCommand4
35580
37966
  }
35581
37967
  });
35582
37968
 
35583
37969
  // src/commands/videos/index.ts
35584
- import { defineCommand as defineCommand177 } from "citty";
37970
+ import { defineCommand as defineCommand185 } from "citty";
35585
37971
 
35586
37972
  // src/commands/videos/delete.ts
35587
- import { defineCommand as defineCommand173 } from "citty";
37973
+ import { defineCommand as defineCommand181 } from "citty";
35588
37974
  registerSchema({
35589
37975
  command: "videos.delete",
35590
37976
  description: "Delete a video by ID",
@@ -35598,7 +37984,7 @@ registerSchema({
35598
37984
  }
35599
37985
  }
35600
37986
  });
35601
- var deleteCommand3 = defineCommand173({
37987
+ var deleteCommand3 = defineCommand181({
35602
37988
  meta: {
35603
37989
  name: "delete",
35604
37990
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -35639,7 +38025,7 @@ var deleteCommand3 = defineCommand173({
35639
38025
  });
35640
38026
 
35641
38027
  // src/commands/videos/get.ts
35642
- import { defineCommand as defineCommand174 } from "citty";
38028
+ import { defineCommand as defineCommand182 } from "citty";
35643
38029
  registerSchema({
35644
38030
  command: "videos.get",
35645
38031
  description: "Get a single video by ID",
@@ -35647,7 +38033,7 @@ registerSchema({
35647
38033
  id: { type: "string", description: "Video ID", required: true }
35648
38034
  }
35649
38035
  });
35650
- var getCommand5 = defineCommand174({
38036
+ var getCommand5 = defineCommand182({
35651
38037
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
35652
38038
  args: {
35653
38039
  id: { type: "positional", description: "Video ID", required: false },
@@ -35684,7 +38070,7 @@ var getCommand5 = defineCommand174({
35684
38070
  });
35685
38071
 
35686
38072
  // src/commands/videos/search.ts
35687
- import { defineCommand as defineCommand175 } from "citty";
38073
+ import { defineCommand as defineCommand183 } from "citty";
35688
38074
  registerSchema({
35689
38075
  command: "videos.search",
35690
38076
  description: "Search videos by text query. Only returns ready videos.",
@@ -35694,7 +38080,7 @@ registerSchema({
35694
38080
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
35695
38081
  }
35696
38082
  });
35697
- var searchCommand3 = defineCommand175({
38083
+ var searchCommand4 = defineCommand183({
35698
38084
  meta: {
35699
38085
  name: "search",
35700
38086
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -35744,9 +38130,9 @@ var searchCommand3 = defineCommand175({
35744
38130
  var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
35745
38131
 
35746
38132
  // src/commands/videos/upload.ts
35747
- import { readFile as readFile23, stat as stat7 } from "fs/promises";
38133
+ import { readFile as readFile24, stat as stat7 } from "fs/promises";
35748
38134
  import { extname as extname3 } from "path";
35749
- import { defineCommand as defineCommand176 } from "citty";
38135
+ import { defineCommand as defineCommand184 } from "citty";
35750
38136
  var MIME_MAP = {
35751
38137
  ".mp4": "video/mp4",
35752
38138
  ".mov": "video/quicktime",
@@ -35780,7 +38166,7 @@ function detectContentType(filePath) {
35780
38166
  }
35781
38167
  return mime;
35782
38168
  }
35783
- var uploadCommand2 = defineCommand176({
38169
+ var uploadCommand2 = defineCommand184({
35784
38170
  meta: {
35785
38171
  name: "upload",
35786
38172
  description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
@@ -35809,7 +38195,7 @@ var uploadCommand2 = defineCommand176({
35809
38195
  return;
35810
38196
  }
35811
38197
  const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
35812
- const fileBuffer = await readFile23(filePath);
38198
+ const fileBuffer = await readFile24(filePath);
35813
38199
  const uploadResponse = await fetch(uploadUrl, {
35814
38200
  method: "PUT",
35815
38201
  headers: { "Content-Type": contentType },
@@ -35834,7 +38220,7 @@ var uploadCommand2 = defineCommand176({
35834
38220
  });
35835
38221
 
35836
38222
  // src/commands/videos/index.ts
35837
- var videosCommand = defineCommand177({
38223
+ var videosCommand = defineCommand185({
35838
38224
  meta: {
35839
38225
  name: "videos",
35840
38226
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
@@ -35850,7 +38236,7 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
35850
38236
  },
35851
38237
  subCommands: {
35852
38238
  get: getCommand5,
35853
- search: searchCommand3,
38239
+ search: searchCommand4,
35854
38240
  upload: uploadCommand2,
35855
38241
  delete: deleteCommand3,
35856
38242
  tags: tagsCommand5
@@ -35858,19 +38244,19 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
35858
38244
  });
35859
38245
 
35860
38246
  // src/commands/winning-ads/index.ts
35861
- import { defineCommand as defineCommand190 } from "citty";
38247
+ import { defineCommand as defineCommand198 } from "citty";
35862
38248
 
35863
38249
  // src/commands/winning-ads/advertisers.ts
35864
- import { defineCommand as defineCommand178 } from "citty";
38250
+ import { defineCommand as defineCommand186 } from "citty";
35865
38251
 
35866
38252
  // src/commands/winning-ads/shared.ts
35867
- function splitList(value) {
38253
+ function splitList2(value) {
35868
38254
  if (!value) {
35869
38255
  return [];
35870
38256
  }
35871
38257
  return value.split(",").map((v) => v.trim()).filter(Boolean);
35872
38258
  }
35873
- function reportError(err) {
38259
+ function reportError2(err) {
35874
38260
  if (err instanceof ApiError) {
35875
38261
  writeJson({ ok: false, error: { code: err.code, message: err.message } });
35876
38262
  process.exit(1);
@@ -35914,7 +38300,7 @@ function advertiserNormalizer(record, full) {
35914
38300
  last_synced_at: record.last_synced_at ?? null
35915
38301
  };
35916
38302
  }
35917
- var advertisersCommand2 = defineCommand178({
38303
+ var advertisersCommand2 = defineCommand186({
35918
38304
  meta: {
35919
38305
  name: "advertisers",
35920
38306
  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'
@@ -35966,13 +38352,13 @@ var advertisersCommand2 = defineCommand178({
35966
38352
  advertiserNormalizer
35967
38353
  );
35968
38354
  } catch (err) {
35969
- reportError(err);
38355
+ reportError2(err);
35970
38356
  }
35971
38357
  }
35972
38358
  });
35973
38359
 
35974
38360
  // src/commands/winning-ads/brief.ts
35975
- import { defineCommand as defineCommand179 } from "citty";
38361
+ import { defineCommand as defineCommand187 } from "citty";
35976
38362
  registerSchema({
35977
38363
  command: "winning-ads.brief",
35978
38364
  description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
@@ -36018,7 +38404,7 @@ function parseDna(raw) {
36018
38404
  }
36019
38405
  return parsed;
36020
38406
  }
36021
- var briefCommand = defineCommand179({
38407
+ var briefCommand = defineCommand187({
36022
38408
  meta: {
36023
38409
  name: "brief",
36024
38410
  description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
@@ -36048,13 +38434,13 @@ var briefCommand = defineCommand179({
36048
38434
  const data = await apiPost("/api/ad-library/brief", body);
36049
38435
  writeJson({ ok: true, data });
36050
38436
  } catch (err) {
36051
- reportError(err);
38437
+ reportError2(err);
36052
38438
  }
36053
38439
  }
36054
38440
  });
36055
38441
 
36056
38442
  // src/commands/winning-ads/content.ts
36057
- import { defineCommand as defineCommand180 } from "citty";
38443
+ import { defineCommand as defineCommand188 } from "citty";
36058
38444
  registerSchema({
36059
38445
  command: "winning-ads.content",
36060
38446
  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.",
@@ -36067,7 +38453,7 @@ registerSchema({
36067
38453
  }
36068
38454
  }
36069
38455
  });
36070
- var contentCommand = defineCommand180({
38456
+ var contentCommand = defineCommand188({
36071
38457
  meta: {
36072
38458
  name: "content",
36073
38459
  description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
@@ -36110,16 +38496,16 @@ var contentCommand = defineCommand180({
36110
38496
  adContentNormalizer
36111
38497
  );
36112
38498
  } catch (err) {
36113
- reportError(err);
38499
+ reportError2(err);
36114
38500
  }
36115
38501
  }
36116
38502
  });
36117
38503
 
36118
38504
  // src/commands/winning-ads/feed.ts
36119
- import { defineCommand as defineCommand181 } from "citty";
38505
+ import { defineCommand as defineCommand189 } from "citty";
36120
38506
  function buildFeedParams(input) {
36121
38507
  const params = {};
36122
- const advertiser = splitList(input.advertiser);
38508
+ const advertiser = splitList2(input.advertiser);
36123
38509
  if (advertiser.length > 0) {
36124
38510
  params.advertiser = advertiser.join(",");
36125
38511
  }
@@ -36132,11 +38518,11 @@ function buildFeedParams(input) {
36132
38518
  if (input.limit !== void 0 && input.limit !== "") {
36133
38519
  params.limit = input.limit;
36134
38520
  }
36135
- const winnerCategory = splitList(input.winnerCategory);
38521
+ const winnerCategory = splitList2(input.winnerCategory);
36136
38522
  if (winnerCategory.length > 0) {
36137
38523
  params.winner_category = winnerCategory.join(",");
36138
38524
  }
36139
- const format = splitList(input.format);
38525
+ const format = splitList2(input.format);
36140
38526
  if (format.length > 0) {
36141
38527
  params.format = format.join(",");
36142
38528
  }
@@ -36168,7 +38554,7 @@ registerSchema({
36168
38554
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
36169
38555
  }
36170
38556
  });
36171
- var feedCommand = defineCommand181({
38557
+ var feedCommand = defineCommand189({
36172
38558
  meta: {
36173
38559
  name: "feed",
36174
38560
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -36247,13 +38633,13 @@ var feedCommand = defineCommand181({
36247
38633
  `);
36248
38634
  }
36249
38635
  } catch (err) {
36250
- reportError(err);
38636
+ reportError2(err);
36251
38637
  }
36252
38638
  }
36253
38639
  });
36254
38640
 
36255
38641
  // src/commands/winning-ads/follow.ts
36256
- import { defineCommand as defineCommand182 } from "citty";
38642
+ import { defineCommand as defineCommand190 } from "citty";
36257
38643
  var PLATFORMS = ["meta", "linkedin"];
36258
38644
  registerSchema({
36259
38645
  command: "winning-ads.follow",
@@ -36268,7 +38654,7 @@ registerSchema({
36268
38654
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
36269
38655
  }
36270
38656
  });
36271
- var followCommand = defineCommand182({
38657
+ var followCommand = defineCommand190({
36272
38658
  meta: {
36273
38659
  name: "follow",
36274
38660
  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'
@@ -36309,19 +38695,19 @@ var followCommand = defineCommand182({
36309
38695
  }
36310
38696
  writeJson({ ok: true, data, hints });
36311
38697
  } catch (err) {
36312
- reportError(err);
38698
+ reportError2(err);
36313
38699
  }
36314
38700
  }
36315
38701
  });
36316
38702
 
36317
38703
  // src/commands/winning-ads/follow-competitors.ts
36318
- import { defineCommand as defineCommand183 } from "citty";
38704
+ import { defineCommand as defineCommand191 } from "citty";
36319
38705
  var PLATFORMS2 = ["meta", "linkedin"];
36320
38706
  var BATCH_TIMEOUT_MS = 3e5;
36321
38707
  function buildFollowBatchBody(input) {
36322
38708
  const seen = /* @__PURE__ */ new Set();
36323
38709
  const inputs = [];
36324
- for (const domain of splitList(input.domains)) {
38710
+ for (const domain of splitList2(input.domains)) {
36325
38711
  const key = domain.toLowerCase();
36326
38712
  if (seen.has(key)) {
36327
38713
  continue;
@@ -36348,7 +38734,7 @@ registerSchema({
36348
38734
  }
36349
38735
  }
36350
38736
  });
36351
- var followCompetitorsCommand = defineCommand183({
38737
+ var followCompetitorsCommand = defineCommand191({
36352
38738
  meta: {
36353
38739
  name: "follow-competitors",
36354
38740
  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"'
@@ -36417,13 +38803,13 @@ var followCompetitorsCommand = defineCommand183({
36417
38803
  }
36418
38804
  writeJson({ ok: true, data, hints: hints.length > 0 ? hints : void 0 });
36419
38805
  } catch (err) {
36420
- reportError(err);
38806
+ reportError2(err);
36421
38807
  }
36422
38808
  }
36423
38809
  });
36424
38810
 
36425
38811
  // src/commands/winning-ads/following.ts
36426
- import { defineCommand as defineCommand184 } from "citty";
38812
+ import { defineCommand as defineCommand192 } from "citty";
36427
38813
  registerSchema({
36428
38814
  command: "winning-ads.following",
36429
38815
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
@@ -36456,7 +38842,7 @@ function followingNormalizer(record, full) {
36456
38842
  platforms: Array.isArray(record.platforms) ? record.platforms : []
36457
38843
  };
36458
38844
  }
36459
- var followingCommand = defineCommand184({
38845
+ var followingCommand = defineCommand192({
36460
38846
  meta: {
36461
38847
  name: "following",
36462
38848
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -36485,13 +38871,13 @@ var followingCommand = defineCommand184({
36485
38871
  followingNormalizer
36486
38872
  );
36487
38873
  } catch (err) {
36488
- reportError(err);
38874
+ reportError2(err);
36489
38875
  }
36490
38876
  }
36491
38877
  });
36492
38878
 
36493
38879
  // src/commands/winning-ads/patterns.ts
36494
- import { defineCommand as defineCommand185 } from "citty";
38880
+ import { defineCommand as defineCommand193 } from "citty";
36495
38881
  registerSchema({
36496
38882
  command: "winning-ads.patterns",
36497
38883
  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.",
@@ -36506,8 +38892,8 @@ registerSchema({
36506
38892
  }
36507
38893
  });
36508
38894
  function buildPatternsBody(args) {
36509
- const winners = splitList(args.winners);
36510
- const duds = splitList(args.duds);
38895
+ const winners = splitList2(args.winners);
38896
+ const duds = splitList2(args.duds);
36511
38897
  if (!winners.length || !duds.length) {
36512
38898
  throw new Error("Provide at least one ad id for both --winners and --duds");
36513
38899
  }
@@ -36530,7 +38916,7 @@ function discriminatorRow(record) {
36530
38916
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
36531
38917
  };
36532
38918
  }
36533
- var patternsCommand = defineCommand185({
38919
+ var patternsCommand = defineCommand193({
36534
38920
  meta: {
36535
38921
  name: "patterns",
36536
38922
  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"
@@ -36580,13 +38966,13 @@ var patternsCommand = defineCommand185({
36580
38966
  (record) => discriminatorRow(record)
36581
38967
  );
36582
38968
  } catch (err) {
36583
- reportError(err);
38969
+ reportError2(err);
36584
38970
  }
36585
38971
  }
36586
38972
  });
36587
38973
 
36588
38974
  // src/commands/winning-ads/search.ts
36589
- import { defineCommand as defineCommand186 } from "citty";
38975
+ import { defineCommand as defineCommand194 } from "citty";
36590
38976
  registerSchema({
36591
38977
  command: "winning-ads.search",
36592
38978
  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.",
@@ -36656,7 +39042,7 @@ function setNumber(body, key, value) {
36656
39042
  }
36657
39043
  }
36658
39044
  function setList(target, key, value) {
36659
- const list = splitList(value);
39045
+ const list = splitList2(value);
36660
39046
  if (list.length) {
36661
39047
  target[key] = list;
36662
39048
  }
@@ -36666,7 +39052,7 @@ function setString(target, key, value) {
36666
39052
  target[key] = value;
36667
39053
  }
36668
39054
  }
36669
- function buildSearchBody(args) {
39055
+ function buildSearchBody2(args) {
36670
39056
  const body = {};
36671
39057
  if (args.query) {
36672
39058
  body.free_text_query = args.query;
@@ -36694,7 +39080,7 @@ function buildSearchBody(args) {
36694
39080
  }
36695
39081
  return body;
36696
39082
  }
36697
- var searchCommand4 = defineCommand186({
39083
+ var searchCommand5 = defineCommand194({
36698
39084
  meta: {
36699
39085
  name: "search",
36700
39086
  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"
@@ -36770,7 +39156,7 @@ var searchCommand4 = defineCommand186({
36770
39156
  firstSeenBefore: args["first-seen-before"],
36771
39157
  hookArchetype: args["hook-archetype"]
36772
39158
  };
36773
- const body = buildSearchBody(searchArgs);
39159
+ const body = buildSearchBody2(searchArgs);
36774
39160
  if (!("free_text_query" in body) && !("ref_ad_id" in body) && !("hard_filters" in body)) {
36775
39161
  writeJson({
36776
39162
  ok: false,
@@ -36803,13 +39189,13 @@ var searchCommand4 = defineCommand186({
36803
39189
  winningAdNormalizer
36804
39190
  );
36805
39191
  } catch (err) {
36806
- reportError(err);
39192
+ reportError2(err);
36807
39193
  }
36808
39194
  }
36809
39195
  });
36810
39196
 
36811
39197
  // src/commands/winning-ads/seeds.ts
36812
- import { defineCommand as defineCommand187 } from "citty";
39198
+ import { defineCommand as defineCommand195 } from "citty";
36813
39199
  function leanRow(r) {
36814
39200
  return {
36815
39201
  key: r.key,
@@ -36837,7 +39223,7 @@ function makeSeedCommand(opts) {
36837
39223
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
36838
39224
  }
36839
39225
  });
36840
- return defineCommand187({
39226
+ return defineCommand195({
36841
39227
  meta: { name: opts.name, description: opts.description },
36842
39228
  args: {
36843
39229
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -36864,7 +39250,7 @@ function makeSeedCommand(opts) {
36864
39250
  }
36865
39251
  writeOutput({ ok: true, data: projected }, output);
36866
39252
  } catch (err) {
36867
- reportError(err);
39253
+ reportError2(err);
36868
39254
  }
36869
39255
  }
36870
39256
  });
@@ -36886,7 +39272,7 @@ var formatsCommand = makeSeedCommand({
36886
39272
  });
36887
39273
 
36888
39274
  // src/commands/winning-ads/unfollow.ts
36889
- import { defineCommand as defineCommand188 } from "citty";
39275
+ import { defineCommand as defineCommand196 } from "citty";
36890
39276
  registerSchema({
36891
39277
  command: "winning-ads.unfollow",
36892
39278
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -36894,7 +39280,7 @@ registerSchema({
36894
39280
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
36895
39281
  }
36896
39282
  });
36897
- var unfollowCommand = defineCommand188({
39283
+ var unfollowCommand = defineCommand196({
36898
39284
  meta: {
36899
39285
  name: "unfollow",
36900
39286
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -36909,13 +39295,13 @@ var unfollowCommand = defineCommand188({
36909
39295
  });
36910
39296
  writeJson({ ok: true, data });
36911
39297
  } catch (err) {
36912
- reportError(err);
39298
+ reportError2(err);
36913
39299
  }
36914
39300
  }
36915
39301
  });
36916
39302
 
36917
39303
  // src/commands/winning-ads/winners.ts
36918
- import { defineCommand as defineCommand189 } from "citty";
39304
+ import { defineCommand as defineCommand197 } from "citty";
36919
39305
  registerSchema({
36920
39306
  command: "winning-ads.winners",
36921
39307
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -36925,7 +39311,7 @@ registerSchema({
36925
39311
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
36926
39312
  }
36927
39313
  });
36928
- var winnersCommand = defineCommand189({
39314
+ var winnersCommand = defineCommand197({
36929
39315
  meta: {
36930
39316
  name: "winners",
36931
39317
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -36969,13 +39355,13 @@ var winnersCommand = defineCommand189({
36969
39355
  winningAdNormalizer
36970
39356
  );
36971
39357
  } catch (err) {
36972
- reportError(err);
39358
+ reportError2(err);
36973
39359
  }
36974
39360
  }
36975
39361
  });
36976
39362
 
36977
39363
  // src/commands/winning-ads/index.ts
36978
- var winningAdsCommand = defineCommand190({
39364
+ var winningAdsCommand = defineCommand198({
36979
39365
  meta: {
36980
39366
  name: "winning-ads",
36981
39367
  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.
@@ -37013,7 +39399,7 @@ Examples:
37013
39399
  Full guide: __tooling__/docs/tools/baker/winning-ads.md`
37014
39400
  },
37015
39401
  subCommands: {
37016
- search: searchCommand4,
39402
+ search: searchCommand5,
37017
39403
  advertisers: advertisersCommand2,
37018
39404
  follow: followCommand,
37019
39405
  "follow-competitors": followCompetitorsCommand,
@@ -37047,7 +39433,7 @@ function getCliVersion() {
37047
39433
  }
37048
39434
 
37049
39435
  // src/cli.ts
37050
- var main = defineCommand191({
39436
+ var main = defineCommand199({
37051
39437
  meta: {
37052
39438
  name: "baker",
37053
39439
  version: getCliVersion(),