@koda-sl/baker-cli 0.182.0-dev.3c0641b3f → 0.182.1-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/README.md CHANGED
@@ -4989,7 +4989,7 @@ baker landing inspiration code <section id> # the standalone bundle
4989
4989
  baker landing inspiration page <source id> # a whole page as a section sequence
4990
4990
  baker landing inspiration add https://linear.app --note "client likes this density"
4991
4991
  baker landing inspiration favorites # what this company has saved
4992
- baker landing inspiration scrape <url> --out <dir> # run the capture locally (ops/debug)
4992
+ baker landing inspiration scrape <url> --out <dir> # capture any page now, synchronously
4993
4993
  ```
4994
4994
 
4995
4995
  - **Hybrid search over three signals** — keywords, meaning, and *appearance* (the screenshot is embedded, so a query like "dark developer hero with a terminal" can match a section whose text never says "terminal"). Filters: `--type --composition --register --interaction --motion --media --device --theme --max-rank --min-craft --min-fidelity --domain --similar-to --scope --limit`.
@@ -4997,7 +4997,8 @@ baker landing inspiration scrape <url> --out <dir> # run the capture locally (
4997
4997
  - **`fidelity` says how much to trust the code.** A measured 0–1 match between the live section and our standalone re-render, always reported. Above ~0.95 the markup renders like the original; below ~0.75 the section is scroll- or JS-driven and it does not.
4998
4998
  - **`used_on_pages`** appears when a section shows up on more than one of that site's pages — a nav or footer they ship everywhere, which is a stronger reference than a one-off.
4999
4999
  - **The corpus is shared across companies; favorites are per-company**, and search defaults to this company's saved sections.
5000
- - **Inspiration, never a clipboard.** `code` records what was consulted, and `critique`'s `originality` family blocks a publish that reuses a reference's headline, subhead, or CTA verbatim.
5000
+ - **`add` studies in the background; `scrape` returns with the page on disk.** `add` grows the shared library and takes minutes, so it can never answer "build our page like this one" within the same turn. `scrape` runs the identical capture locally and blocks until it finishes, writing section screenshots, standalone markup, a whole-page reproduction and `report.html`. `--no-motion` roughly halves the runtime; `--no-mobile`, `--no-code` and `--no-report` skip further passes.
5001
+ - **Inspiration, never a clipboard.** Both `code` and `scrape` record what was consulted, and `critique`'s `originality` family blocks a publish that reuses a reference's copy verbatim.
5001
5002
 
5002
5003
  ### `baker landing critique`
5003
5004
 
package/dist/cli.js CHANGED
@@ -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(path34) {
8129
- if (typeof path34 !== "string" || path34.length === 0) {
8128
+ function rawFileEntries(path35) {
8129
+ if (typeof path35 !== "string" || path35.length === 0) {
8130
8130
  return [];
8131
8131
  }
8132
- return readFileSync2(path34, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8132
+ return readFileSync2(path35, "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(path34) {
8156
- if (typeof path34 !== "string" || path34.length === 0) {
8155
+ function loadJsonFileArg(path35) {
8156
+ if (typeof path35 !== "string" || path35.length === 0) {
8157
8157
  return {};
8158
8158
  }
8159
8159
  try {
8160
- const parsed = JSON.parse(readFileSync2(path34, "utf8"));
8160
+ const parsed = JSON.parse(readFileSync2(path35, "utf8"));
8161
8161
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
8162
- failWriteValidation(`${path34} must contain a JSON object`);
8162
+ failWriteValidation(`${path35} must contain a JSON object`);
8163
8163
  }
8164
8164
  return parsed;
8165
8165
  } catch (err) {
8166
8166
  if (err instanceof SyntaxError) {
8167
- failWriteValidation(`${path34} is not valid JSON: ${err.message}`);
8167
+ failWriteValidation(`${path35} 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(path34, body, chat) {
8297
+ async function draftAction(path35, body, chat) {
8298
8298
  try {
8299
8299
  const chatId = resolveChatId(chat);
8300
- const response = await apiPost(path34, { chatId, ...body });
8300
+ const response = await apiPost(path35, { 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(path34) {
10969
- if (typeof path34 !== "string" || path34.length === 0) {
10968
+ function loadJsonFileArg2(path35) {
10969
+ if (typeof path35 !== "string" || path35.length === 0) {
10970
10970
  return {};
10971
10971
  }
10972
10972
  try {
10973
- const parsed = JSON.parse(readFileSync4(path34, "utf8"));
10973
+ const parsed = JSON.parse(readFileSync4(path35, "utf8"));
10974
10974
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
10975
- failWriteValidation2(`${path34} must contain a JSON object`);
10975
+ failWriteValidation2(`${path35} must contain a JSON object`);
10976
10976
  }
10977
10977
  return parsed;
10978
10978
  } catch (err) {
10979
10979
  if (err instanceof SyntaxError) {
10980
- failWriteValidation2(`${path34} is not valid JSON: ${err.message}`);
10980
+ failWriteValidation2(`${path35} 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(path34) {
11066
- if (typeof path34 !== "string" || path34.length === 0) {
11065
+ function loadTargetingFileArg(path35) {
11066
+ if (typeof path35 !== "string" || path35.length === 0) {
11067
11067
  return void 0;
11068
11068
  }
11069
- const parsed = loadJsonFileArg2(path34);
11069
+ const parsed = loadJsonFileArg2(path35);
11070
11070
  const criteria = parsed.targetingCriteria ?? parsed;
11071
11071
  if (!criteria.include) {
11072
11072
  failWriteValidation2(
11073
- `${path34} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
11073
+ `${path35} 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(path34, maxRows) {
11109
- if (typeof path34 !== "string" || path34.length === 0) {
11108
+ function parseListFileArg(path35, maxRows) {
11109
+ if (typeof path35 !== "string" || path35.length === 0) {
11110
11110
  return void 0;
11111
11111
  }
11112
- const raw = readFileSync4(path34, "utf8");
11112
+ const raw = readFileSync4(path35, "utf8");
11113
11113
  const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
11114
11114
  if (lines.length < 2) {
11115
- failWriteValidation2(`${path34} needs a header row and at least one data row`);
11115
+ failWriteValidation2(`${path35} 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(path34, maxRows) {
11131
11131
  }
11132
11132
  }
11133
11133
  if (rows.length > maxRows) {
11134
- failWriteValidation2(`${path34} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
11134
+ failWriteValidation2(`${path35} 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(path34) {
11231
- if (typeof path34 !== "string" || path34.length === 0) {
11230
+ function idsFileEntries(path35) {
11231
+ if (typeof path35 !== "string" || path35.length === 0) {
11232
11232
  return [];
11233
11233
  }
11234
- return readFileSync4(path34, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
11234
+ return readFileSync4(path35, "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, path34) {
13857
+ function readPath(row, path35) {
13858
13858
  let current = row;
13859
- for (const segment of path34.split(".")) {
13859
+ for (const segment of path35.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, path34) {
13866
13866
  function projectFields(rows, paths) {
13867
13867
  return rows.map((row) => {
13868
13868
  const projected = {};
13869
- for (const path34 of paths) {
13870
- const value = readPath(row, path34);
13869
+ for (const path35 of paths) {
13870
+ const value = readPath(row, path35);
13871
13871
  if (value !== void 0) {
13872
- projected[path34] = value;
13872
+ projected[path35] = 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, path34) {
15079
+ function validateDailyBudgetFloor(money, ctx, path35) {
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: path34, message: `below the ${min} ${money.currencyCode} daily minimum` });
15083
+ ctx.addIssue({ code: "custom", path: path35, 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(path34) {
15727
- if (typeof path34 !== "string" || path34.length === 0) {
15726
+ function loadJsonFileArg3(path35) {
15727
+ if (typeof path35 !== "string" || path35.length === 0) {
15728
15728
  return {};
15729
15729
  }
15730
15730
  try {
15731
- const parsed = JSON.parse(readFileSync8(path34, "utf8"));
15731
+ const parsed = JSON.parse(readFileSync8(path35, "utf8"));
15732
15732
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
15733
- failWriteValidation3(`${path34} must contain a JSON object`);
15733
+ failWriteValidation3(`${path35} must contain a JSON object`);
15734
15734
  }
15735
15735
  return parsed;
15736
15736
  } catch (err) {
15737
15737
  if (err instanceof SyntaxError) {
15738
- failWriteValidation3(`${path34} is not valid JSON: ${err.message}`);
15738
+ failWriteValidation3(`${path35} is not valid JSON: ${err.message}`);
15739
15739
  }
15740
15740
  throw err;
15741
15741
  }
@@ -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 path34 = join3(flowsDir(), slug, "_data.json");
26535
- if (!existsSync4(path34)) {
26534
+ const path35 = join3(flowsDir(), slug, "_data.json");
26535
+ if (!existsSync4(path35)) {
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(path34, "utf-8"));
26539
+ return JSON.parse(readFileSync9(path35, "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(path34, body, chat) {
26952
+ async function draftAction2(path35, body, chat) {
26953
26953
  const chatId = resolveChatId(chat);
26954
26954
  try {
26955
- const data = await apiPost(path34, { chatId, ...body });
26955
+ const data = await apiPost(path35, { 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(path34) {
28979
+ async function isDirectory(path35) {
28980
28980
  try {
28981
- const s = await stat4(path34);
28981
+ const s = await stat4(path35);
28982
28982
  return s.isDirectory();
28983
28983
  } catch {
28984
28984
  return false;
@@ -32870,9 +32870,14 @@ async function readReferences(projectRoot) {
32870
32870
  }
32871
32871
  }
32872
32872
  async function recordReference(projectRoot, reference) {
32873
+ return await recordReferences(projectRoot, [reference]);
32874
+ }
32875
+ async function recordReferences(projectRoot, references) {
32876
+ if (references.length === 0) return true;
32873
32877
  try {
32874
32878
  const existing = await readReferences(projectRoot);
32875
- const merged = [...existing.filter((entry) => entry.sectionId !== reference.sectionId), reference];
32879
+ const replaced = new Set(references.map((reference) => reference.sectionId));
32880
+ const merged = [...existing.filter((entry) => !replaced.has(entry.sectionId)), ...references];
32876
32881
  const file = path25.join(projectRoot, REFERENCES_FILE);
32877
32882
  await mkdir8(path25.dirname(file), { recursive: true });
32878
32883
  await writeFile11(file, `${JSON.stringify(merged, null, 2)}
@@ -33419,8 +33424,25 @@ var pageCommand = defineCommand146({
33419
33424
  });
33420
33425
 
33421
33426
  // src/commands/landing/inspiration/scrape.ts
33427
+ import { readFile as readFile24 } from "fs/promises";
33428
+ import path32 from "path";
33422
33429
  import { defineCommand as defineCommand147 } from "citty";
33423
33430
 
33431
+ // src/engine/landing/lib/capturedReferences.ts
33432
+ var MAX_STRINGS_PER_SECTION = 40;
33433
+ function bodyOf(markup) {
33434
+ const body = markup.indexOf("<body");
33435
+ return body === -1 ? markup : markup.slice(body);
33436
+ }
33437
+ function capturedCopyStrings(markup) {
33438
+ const seen = /* @__PURE__ */ new Set();
33439
+ for (const { value } of extractVisibleStrings(bodyOf(markup))) {
33440
+ seen.add(value);
33441
+ if (seen.size >= MAX_STRINGS_PER_SECTION) break;
33442
+ }
33443
+ return [...seen];
33444
+ }
33445
+
33424
33446
  // src/engine/landing-library/blocked.ts
33425
33447
  var CHALLENGE_PHRASES = [
33426
33448
  "just a moment",
@@ -33846,7 +33868,10 @@ async function toGreyGrid(image) {
33846
33868
  // src/engine/landing-library/motion.ts
33847
33869
  var EMPTY_MOTION = {
33848
33870
  hasMotion: false,
33849
- summary: "no motion",
33871
+ // `none` rather than "no motion": it is the `MOTION_KINDS` member for this,
33872
+ // so a section with no movement is findable by the same facet as everything
33873
+ // else instead of being a special case only prose describes.
33874
+ summary: "none",
33850
33875
  entrance: [],
33851
33876
  hover: [],
33852
33877
  scroll: [],
@@ -33862,19 +33887,43 @@ async function collectMotion(page, selector) {
33862
33887
  if (!raw) return EMPTY_MOTION;
33863
33888
  return { ...raw, summary: summarizeMotion(raw) };
33864
33889
  }
33865
- function summarizeMotion(motion) {
33866
- const parts = [];
33867
- const describe = (label, effects) => {
33868
- if (effects.length === 0) return;
33869
- const kinds = [...new Set(effects.map((effect) => effect.kind))].slice(0, 3);
33870
- parts.push(`${label} ${kinds.join("/")}`);
33890
+ var HOVER_TRANSFORMS = /* @__PURE__ */ new Set(["lift", "grow", "tilt", "shift", "spin", "shadow", "animate"]);
33891
+ var KEYFRAME_PATTERNS = [
33892
+ [/marquee|ticker|scroll(ing)?-?(x|left|right)/, "marquee"],
33893
+ [/parallax/, "parallax"],
33894
+ [/sticky|pin(ned)?/, "sticky-pin"],
33895
+ [/count(er|up)/, "counter"],
33896
+ [/zoom|scale|pulse|grow/, "zoom"],
33897
+ [/fade|opacity|appear/, "fade"],
33898
+ [/slide|translate|in-?(left|right|up|down)/, "slide-in"]
33899
+ ];
33900
+ function toMotionKinds(kind, channel) {
33901
+ if (channel === "hover") return [HOVER_TRANSFORMS.has(kind) ? "hover-lift" : "fade"];
33902
+ const name = kind.toLowerCase();
33903
+ const matched = KEYFRAME_PATTERNS.filter(([pattern]) => pattern.test(name)).map(([, motionKind]) => motionKind);
33904
+ if (matched.length > 0) return matched;
33905
+ return [channel === "loop" ? "marquee" : "scroll-reveal"];
33906
+ }
33907
+ function motionKindsOf(motion) {
33908
+ const kinds = /* @__PURE__ */ new Set();
33909
+ const collect = (effects, channel) => {
33910
+ for (const effect of effects) {
33911
+ for (const kind of toMotionKinds(effect.kind, channel)) kinds.add(kind);
33912
+ }
33871
33913
  };
33872
- describe("entrance", motion.entrance);
33873
- describe("hover", motion.hover);
33874
- describe("scroll", motion.scroll);
33875
- describe("loop", motion.loop);
33914
+ collect(motion.entrance, "entrance");
33915
+ collect(motion.hover, "hover");
33916
+ collect(motion.scroll, "scroll");
33917
+ collect(motion.loop, "loop");
33918
+ const delays = new Set(motion.entrance.map((effect) => effect.delayMs ?? 0).filter((ms) => ms > 0));
33919
+ if (delays.size > 1) kinds.add("stagger");
33920
+ return [...kinds];
33921
+ }
33922
+ function summarizeMotion(motion) {
33923
+ const kinds = motionKindsOf(motion);
33924
+ if (kinds.length === 0 && motion.libraries.length === 0) return "none";
33925
+ const parts = [...kinds];
33876
33926
  if (motion.libraries.length > 0) parts.push(`via ${motion.libraries.join("/")}`);
33877
- if (parts.length === 0) return "no motion";
33878
33927
  return parts.join(", ") + (motion.respectsReducedMotion ? "" : " (ignores reduced-motion)");
33879
33928
  }
33880
33929
  var inPageCollectMotion = (selector) => {
@@ -34968,53 +35017,74 @@ async function scrapeLanding(options) {
34968
35017
  }
34969
35018
 
34970
35019
  // src/commands/landing/inspiration/scrape.ts
35020
+ async function recordCapture(manifest, outDir) {
35021
+ const consultedAt = (/* @__PURE__ */ new Date()).toISOString();
35022
+ const references = [];
35023
+ for (const section of manifest.sections) {
35024
+ if (!section.bundle) continue;
35025
+ try {
35026
+ const markup = await readFile24(path32.join(outDir, section.bundle), "utf8");
35027
+ const copyStrings = capturedCopyStrings(markup);
35028
+ if (copyStrings.length === 0) continue;
35029
+ references.push({
35030
+ sectionId: `local:${manifest.finalUrl}#${section.index}`,
35031
+ sourceUrl: manifest.finalUrl,
35032
+ copyStrings,
35033
+ consultedAt
35034
+ });
35035
+ } catch {
35036
+ }
35037
+ }
35038
+ return await recordReferences(process.cwd(), references);
35039
+ }
34971
35040
  registerSchema({
34972
35041
  command: "landing.inspiration.scrape",
34973
- 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.",
35042
+ description: "Start here when the user points at a specific page and wants it now: capture one landing page into a directory of section screenshots, standalone HTML bundles, a whole-page reproduction and motion filmstrips. Returns when the capture is done, unlike `add`. Read the screenshots. Also records every captured section for the originality check.",
34974
35043
  args: {
34975
35044
  url: { type: "string", description: "Page to capture", required: true },
34976
35045
  out: { type: "string", description: "Output directory", required: true },
34977
- "no-mobile": { type: "boolean", description: "Skip the phone-viewport pass", required: false },
34978
- "no-code": {
35046
+ mobile: { type: "boolean", description: "Phone-viewport pass. `--no-mobile` to skip", required: false },
35047
+ code: {
34979
35048
  type: "boolean",
34980
- description: "Segment and screenshot only \u2014 no bundles, no fidelity",
35049
+ description: "Standalone HTML+CSS bundles and fidelity scores. `--no-code` to segment and screenshot only",
34981
35050
  required: false
34982
35051
  },
34983
- "no-motion": {
35052
+ motion: {
34984
35053
  type: "boolean",
34985
- description: "Skip filming sections that move (roughly halves runtime)",
35054
+ description: "Film sections that move. `--no-motion` to skip \u2014 roughly halves runtime",
34986
35055
  required: false
34987
35056
  },
34988
- "no-report": { type: "boolean", description: "Skip writing report.html", required: false }
35057
+ report: { type: "boolean", description: "Write report.html. `--no-report` to skip", required: false }
34989
35058
  }
34990
35059
  });
34991
35060
  var scrapeCommand = defineCommand147({
34992
35061
  meta: {
34993
35062
  name: "scrape",
34994
- description: "Internal/ops: capture a landing page to a directory. Example: baker landing inspiration scrape https://linear.app --out /tmp/linear"
35063
+ description: "Capture a landing page to a directory, now. Example: baker landing inspiration scrape https://linear.app --out .baker/inspiration/linear.app"
34995
35064
  },
34996
35065
  args: {
34997
35066
  url: { type: "positional", description: "Page to capture", required: true },
34998
35067
  out: { type: "string", description: "Output directory", required: true },
34999
- "no-mobile": { type: "boolean", description: "Skip the phone-viewport pass", required: false, default: false },
35000
- "no-code": { type: "boolean", description: "Segment and screenshot only", required: false, default: false },
35001
- "no-motion": { type: "boolean", description: "Skip filming sections that move", required: false, default: false },
35002
- "no-report": { type: "boolean", description: "Skip writing report.html", required: false, default: false }
35068
+ mobile: { type: "boolean", description: "Phone-viewport pass (--no-mobile to skip)", required: false, default: true },
35069
+ code: { type: "boolean", description: "Bundles + fidelity (--no-code to skip)", required: false, default: true },
35070
+ motion: { type: "boolean", description: "Film moving sections (--no-motion to skip)", required: false, default: true },
35071
+ report: { type: "boolean", description: "Write report.html (--no-report to skip)", required: false, default: true }
35003
35072
  },
35004
35073
  run: async ({ args }) => {
35005
35074
  try {
35006
35075
  const manifest = await scrapeLanding({
35007
35076
  url: args.url,
35008
35077
  outDir: args.out,
35009
- mobile: !args["no-mobile"],
35010
- code: !args["no-code"],
35011
- motion: !args["no-motion"],
35012
- report: !args["no-report"],
35078
+ mobile: args.mobile,
35079
+ code: args.code,
35080
+ motion: args.motion,
35081
+ report: args.report,
35013
35082
  // Progress goes to stderr so stdout stays a clean JSON envelope.
35014
35083
  onProgress: (message) => process.stderr.write(`${message}
35015
35084
  `)
35016
35085
  });
35017
35086
  const scored = manifest.sections.map((section) => section.fidelity).filter((value) => value !== null).sort((a, b) => a - b);
35087
+ const recorded = await recordCapture(manifest, args.out);
35018
35088
  writeJson({
35019
35089
  ok: true,
35020
35090
  data: {
@@ -35023,8 +35093,13 @@ var scrapeCommand = defineCommand147({
35023
35093
  medianFidelity: scored.length > 0 ? scored[Math.floor(scored.length / 2)] : null,
35024
35094
  pageFidelity: manifest.page.fidelity,
35025
35095
  out: args.out,
35026
- report: args["no-report"] ? null : `${args.out}/report.html`
35027
- }
35096
+ report: args.report ? `${args.out}/report.html` : null
35097
+ },
35098
+ hints: [
35099
+ INSPIRATION_HINTS.structureNotCopy,
35100
+ INSPIRATION_HINTS.adapt,
35101
+ recorded ? "These sections are recorded for the originality check \u2014 `baker landing critique` will block a publish that ships their copy." : "Could not record this capture, so the originality check cannot see it. Be especially careful not to reuse its copy."
35102
+ ]
35028
35103
  });
35029
35104
  } catch (error) {
35030
35105
  if (error instanceof BlockedPageError) {
@@ -35038,7 +35113,7 @@ var scrapeCommand = defineCommand147({
35038
35113
 
35039
35114
  // src/commands/landing/inspiration/search.ts
35040
35115
  import { mkdir as mkdir12, writeFile as writeFile16 } from "fs/promises";
35041
- import path32 from "path";
35116
+ import path33 from "path";
35042
35117
  import { defineCommand as defineCommand148 } from "citty";
35043
35118
  registerSchema({
35044
35119
  command: "landing.inspiration.search",
@@ -35120,7 +35195,7 @@ function buildSearchBody(args) {
35120
35195
  return body;
35121
35196
  }
35122
35197
  async function downloadShots(results) {
35123
- const dir = path32.join(process.cwd(), ".baker", "inspiration");
35198
+ const dir = path33.join(process.cwd(), ".baker", "inspiration");
35124
35199
  await mkdir12(dir, { recursive: true });
35125
35200
  const saved = /* @__PURE__ */ new Map();
35126
35201
  await Promise.all(
@@ -35129,9 +35204,9 @@ async function downloadShots(results) {
35129
35204
  try {
35130
35205
  const response = await fetch(result.desktopShotUrl);
35131
35206
  if (!response.ok) return;
35132
- const file = path32.join(dir, `${result.id}.png`);
35207
+ const file = path33.join(dir, `${result.id}.png`);
35133
35208
  await writeFile16(file, Buffer.from(await response.arrayBuffer()));
35134
- saved.set(result.id, path32.relative(process.cwd(), file));
35209
+ saved.set(result.id, path33.relative(process.cwd(), file));
35135
35210
  } catch {
35136
35211
  }
35137
35212
  })
@@ -35218,7 +35293,7 @@ var searchCommand2 = defineCommand148({
35218
35293
 
35219
35294
  // src/commands/landing/inspiration/view.ts
35220
35295
  import { mkdir as mkdir13, writeFile as writeFile17 } from "fs/promises";
35221
- import path33 from "path";
35296
+ import path34 from "path";
35222
35297
  import { defineCommand as defineCommand149 } from "citty";
35223
35298
  registerSchema({
35224
35299
  command: "landing.inspiration.view",
@@ -35230,9 +35305,9 @@ async function download(url, file) {
35230
35305
  try {
35231
35306
  const response = await fetch(url);
35232
35307
  if (!response.ok) return null;
35233
- await mkdir13(path33.dirname(file), { recursive: true });
35308
+ await mkdir13(path34.dirname(file), { recursive: true });
35234
35309
  await writeFile17(file, Buffer.from(await response.arrayBuffer()));
35235
- return path33.relative(process.cwd(), file);
35310
+ return path34.relative(process.cwd(), file);
35236
35311
  } catch {
35237
35312
  return null;
35238
35313
  }
@@ -35248,11 +35323,11 @@ var viewCommand2 = defineCommand149({
35248
35323
  const id = args.id;
35249
35324
  const data = await apiGet("/api/landing-inspiration/section", { id });
35250
35325
  const section = data.section;
35251
- const dir = path33.join(process.cwd(), ".baker", "inspiration", id);
35326
+ const dir = path34.join(process.cwd(), ".baker", "inspiration", id);
35252
35327
  const [desktop, mobile, filmstrip] = await Promise.all([
35253
- download(section.desktopShotUrl, path33.join(dir, "desktop.png")),
35254
- download(section.mobileShotUrl, path33.join(dir, "mobile.png")),
35255
- download(section.motionFilmstripUrl, path33.join(dir, "motion-filmstrip.png"))
35328
+ download(section.desktopShotUrl, path34.join(dir, "desktop.png")),
35329
+ download(section.mobileShotUrl, path34.join(dir, "mobile.png")),
35330
+ download(section.motionFilmstripUrl, path34.join(dir, "motion-filmstrip.png"))
35256
35331
  ]);
35257
35332
  const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
35258
35333
  const fidelity = fidelityHint(section.fidelity);
@@ -37081,10 +37156,10 @@ async function stageOp4(op) {
37081
37156
  handleError4(err);
37082
37157
  }
37083
37158
  }
37084
- async function draftAction3(path34, body, chat) {
37159
+ async function draftAction3(path35, body, chat) {
37085
37160
  const chatId = resolveChatId(chat);
37086
37161
  try {
37087
- const data = await apiPost(path34, { chatId, ...body });
37162
+ const data = await apiPost(path35, { chatId, ...body });
37088
37163
  writeJsonEnvelope({ ok: true, data });
37089
37164
  return data;
37090
37165
  } catch (err) {
@@ -38103,7 +38178,7 @@ var searchCommand4 = defineCommand183({
38103
38178
  var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
38104
38179
 
38105
38180
  // src/commands/videos/upload.ts
38106
- import { readFile as readFile24, stat as stat7 } from "fs/promises";
38181
+ import { readFile as readFile25, stat as stat7 } from "fs/promises";
38107
38182
  import { extname as extname3 } from "path";
38108
38183
  import { defineCommand as defineCommand184 } from "citty";
38109
38184
  var MIME_MAP = {
@@ -38168,7 +38243,7 @@ var uploadCommand2 = defineCommand184({
38168
38243
  return;
38169
38244
  }
38170
38245
  const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
38171
- const fileBuffer = await readFile24(filePath);
38246
+ const fileBuffer = await readFile25(filePath);
38172
38247
  const uploadResponse = await fetch(uploadUrl, {
38173
38248
  method: "PUT",
38174
38249
  headers: { "Content-Type": contentType },