@koda-sl/baker-cli 0.445.0-dev.71ff4e44a → 0.449.0-dev.b6179c7f5

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
@@ -45,6 +45,7 @@ import {
45
45
  SEEDANCE_DURATIONS,
46
46
  SEEDANCE_PROFILE,
47
47
  SOUND_EFFECT_MODEL,
48
+ SPEC_SHAPES_HINT,
48
49
  SPOKEN_CLIP_DURATION_S,
49
50
  SPOKEN_WORDS_PER_SECOND,
50
51
  STATIC_ADAPT_FORMAT_ROLE,
@@ -200,7 +201,7 @@ import {
200
201
  withoutCastName,
201
202
  wordCount,
202
203
  ytDlpBlockSignal
203
- } from "./chunk-CYZ7H4F5.js";
204
+ } from "./chunk-Y33XAZ5M.js";
204
205
  import {
205
206
  NAME_CONTAINS_ARG,
206
207
  csvOrJson,
@@ -71147,7 +71148,10 @@ function fieldProblems(f) {
71147
71148
  "message is missing: the one promise a stranger should get, what is offered and what changes for them"
71148
71149
  );
71149
71150
  if (!f.why) problems.push("why is missing: the brief finding it answers");
71150
- if (!f.awareness) problems.push(`awareness must be one of ${AWARENESS_STAGES.join(", ")}`);
71151
+ if (!f.awareness) {
71152
+ const written = f.awarenessWritten ? `"${f.awarenessWritten}" ` : "";
71153
+ problems.push(`awareness ${written}must be one of ${AWARENESS_STAGES.join(", ")}`);
71154
+ }
71151
71155
  if (!(f.angle && IDEA_ANGLES.includes(f.angle)))
71152
71156
  problems.push(`angle ${f.angle ? `"${f.angle}" ` : ""}must be one of ${IDEA_ANGLES.join(", ")}`);
71153
71157
  if (!(f.format && IDEA_FORMATS.includes(f.format)))
@@ -71164,7 +71168,8 @@ function parseIdea(id, markdown) {
71164
71168
  why: text2(fm.why),
71165
71169
  angle: text2(fm.angle),
71166
71170
  format: text2(fm.format),
71167
- awareness: oneOf(AWARENESS_STAGES, fm.awareness)
71171
+ awareness: oneOf(AWARENESS_STAGES, fm.awareness),
71172
+ awarenessWritten: text2(fm.awareness)
71168
71173
  };
71169
71174
  const problems = fieldProblems(f);
71170
71175
  const { who, message, why, angle, format, awareness } = f;
@@ -71730,33 +71735,45 @@ async function staticSpecGate(canvas, canvasDir) {
71730
71735
  fix: "Write the ad in prompt.json: the scene the viewer sees and each line of copy as it prints. Then re-run."
71731
71736
  };
71732
71737
  }
71733
- const sceneWords = spec.native ? [] : sceneWordProblems(spec);
71734
- if (sceneWords.length > 0) {
71735
- return {
71736
- code: "scene_words_not_in_copy",
71737
- problems: sceneWords,
71738
- fix: "Edit prompt.json: every word or figure the picture shows is a `copy` line (`in_scene`, with `on`); describe any other surface without words. Then re-run."
71739
- };
71740
- }
71741
- const concept = conceptProblems(spec);
71742
- if (concept.length > 0) {
71743
- return {
71744
- code: "concept_does_not_fit",
71745
- problems: concept,
71746
- fix: "Edit prompt.json's `concept`: name what of ours carries each missing element (`ours`), or pick another reference with `baker winning-ads fit`. Never render a copy that has lost what the reference wins on."
71747
- };
71748
- }
71738
+ const concept = {
71739
+ code: "concept_does_not_fit",
71740
+ problems: conceptProblems(spec),
71741
+ fix: "Edit prompt.json's `concept`: name what of ours carries each missing element (`ours`), or pick another reference with `baker winning-ads fit`. Never render a copy that has lost what the reference wins on."
71742
+ };
71749
71743
  const root = await findBrandRoot(canvasDir) ?? findWorkspaceRoot(canvasDir) ?? canvasDir;
71750
71744
  const records = await readRecords(root);
71751
- if (spec.native) {
71752
- const native = nativeGateProblems(spec.native, records, spec.brand?.name);
71753
- return native.length > 0 ? { code: "native_screen_invalid", problems: native, fix: "Fix prompt.json's `native` block, then re-run." } : null;
71754
- }
71755
- const proof = proofProblems(spec, records);
71756
- if (proof.length > 0) {
71757
- return { code: "proof_without_source", problems: proof, fix: "Wire the badge or drop the claim in prompt.json, then re-run." };
71758
- }
71759
- return null;
71745
+ const gates = spec.native ? [
71746
+ concept,
71747
+ {
71748
+ // A native screen's people are photos it describes, briefed as ordinary people of the market.
71749
+ code: "native_screen_invalid",
71750
+ problems: nativeGateProblems(spec.native, records, spec.brand?.name),
71751
+ fix: "Fix prompt.json's `native` block."
71752
+ }
71753
+ ] : [
71754
+ {
71755
+ // Words the scene asks for that the copy does not hold: refused here, free, rather than erased by a paid round.
71756
+ code: "scene_words_not_in_copy",
71757
+ problems: sceneWordProblems(spec),
71758
+ fix: "Edit prompt.json: every word or figure the picture shows is a `copy` line (`in_scene`, with `on`); describe any other surface without words."
71759
+ },
71760
+ concept,
71761
+ {
71762
+ code: "proof_without_source",
71763
+ problems: proofProblems(spec, records),
71764
+ fix: "Wire the badge or drop the claim in prompt.json, everywhere it appears (copy and scene)."
71765
+ }
71766
+ ];
71767
+ return oneRefusal(gates.filter((g) => g.problems.length > 0));
71768
+ }
71769
+ function oneRefusal(gates) {
71770
+ const first = gates[0];
71771
+ if (!first) return null;
71772
+ return {
71773
+ code: first.code,
71774
+ problems: [...new Set(gates.flatMap((g) => g.problems))],
71775
+ fix: `${gates.map((g) => g.fix).join(" ")} Then re-run.`
71776
+ };
71760
71777
  }
71761
71778
  async function readRecords(root) {
71762
71779
  const parts = [await readFile12(path14.join(root, "COMPANY.md"), "utf8").catch(() => "")];
@@ -73565,6 +73582,7 @@ var PAST_BAND = /^(.+?) sits? (?:close to )?where (.+?) covers? (?:it|them) \((.
73565
73582
  var OFF_EDGE = /^(.+?) runs? off the edge of the picture \((.+)\)\.?$/;
73566
73583
  var OUTSIDE_SPACE = /^(.+?) sits? outside the space every size keeps clear \((.+)\)\.?$/;
73567
73584
  var EXTRA_WORDS = /^the picture adds words that aren't in the copy: (.+?)\. Remove them, or add them to the copy\.?$/;
73585
+ var SCENE_PRICES = /^the picture shows prices the copy does not hold: (.+?)\. Make them unreadable\.?$/;
73568
73586
  var PLACED = [PAST_BAND, OFF_EDGE, OUTSIDE_SPACE];
73569
73587
  var MISREAD = /^Text: (".+") reads (".*")\.?$/;
73570
73588
  var MISSING = /^Text: (".+") is missing\.?$/;
@@ -73600,19 +73618,25 @@ function correctionOf(finding, band) {
73600
73618
  if (extra) {
73601
73619
  return `Erase these words wherever they appear, leaving the surface they are on blank or with plain lines: ${extra[1]}.`;
73602
73620
  }
73621
+ const prices = what.match(SCENE_PRICES);
73622
+ if (prices) {
73623
+ return `Blur these figures until no digit can be read, keeping the document or screen they are on as it is: ${prices[1]}.`;
73624
+ }
73603
73625
  const misread2 = what.match(MISREAD);
73604
73626
  if (misread2) return `Re-letter ${misread2[1]} exactly as written, letter for letter (it now reads ${misread2[2]}).`;
73605
73627
  const missing = what.match(MISSING);
73606
73628
  if (missing) return `Add the line ${missing[1]}, spelled exactly, where it belongs in the layout.`;
73629
+ if (SIZES_FINDING.test(what)) return FOLLOW_PICTURE;
73607
73630
  const described2 = what.replace(CATEGORY, "").replace(/\.$/, "");
73608
73631
  return `Fix this: ${described2.charAt(0).toLowerCase()}${described2.slice(1)}.`;
73609
73632
  }
73610
73633
  function autoFixStep(canvas, review, opts = {}) {
73611
73634
  if ((opts.round ?? 0) >= AUTO_FIX_ROUNDS) return null;
73612
- const blocking = (review ?? []).filter((f) => f.severity === "blocking");
73613
- if (blocking.length === 0) return null;
73614
- if (isNativeCanvas(canvas)) return { kind: "respec", findings: unique(blocking.map((f) => f.what)) };
73635
+ const flagged = (review ?? []).filter((f) => f.severity === "blocking");
73636
+ if (flagged.length === 0) return null;
73637
+ if (isNativeCanvas(canvas)) return { kind: "respec", findings: unique(flagged.map((f) => f.what)) };
73615
73638
  const relaid = relaidSizes(canvas);
73639
+ const blocking = onDerivedSizes(flagged, relaid);
73616
73640
  const unplaceable = new Set((opts.tooFar ?? []).filter((format) => relaid.some((r) => r.format === format)));
73617
73641
  const misplaced = (f) => {
73618
73642
  const what = withoutSize(f.what);
@@ -73656,8 +73680,8 @@ function autoFixStep(canvas, review, opts = {}) {
73656
73680
  pictureFindings.filter((f) => !BRIEF.test(withoutSize(f.what))).flatMap((f) => correctionOf(f.what) ?? [])
73657
73681
  );
73658
73682
  if (changes.length === 0) return sizesOnly();
73659
- const flagged = new Set(blocking.flatMap((f) => sizesOf(f.what)));
73660
- const keep = relaid.map((r) => r.format).filter((format) => !flagged.has(format));
73683
+ const named = new Set(blocking.flatMap((f) => sizesOf(f.what)));
73684
+ const keep = relaid.map((r) => r.format).filter((format) => !named.has(format));
73661
73685
  const remade = every.filter((size) => !keep.includes(size));
73662
73686
  const kept = {
73663
73687
  ...keep.length > 0 ? { keep } : {},
@@ -73670,6 +73694,17 @@ function autoFixStep(canvas, review, opts = {}) {
73670
73694
  return { kind: "fix", instruction, ...Object.keys(edits).length > 0 ? { relay: edits } : {}, ...kept };
73671
73695
  }
73672
73696
  var unique = (lines) => [...new Set(lines)];
73697
+ var SIZES_FINDING = /^SIZES:/i;
73698
+ var FOLLOW_PICTURE = "Show exactly what the finished ad (Image 2) shows: its people, products, panels and their order, and nothing it does not show.";
73699
+ function onDerivedSizes(findings, relaid) {
73700
+ if (relaid.length === 0) return [...findings];
73701
+ return findings.flatMap((f) => {
73702
+ const sizes = sizesOf(f.what);
73703
+ const onPicture = sizes.length > 0 && sizes.every((size) => !relaid.some((r) => r.format === size));
73704
+ if (!onPicture || !SIZES_FINDING.test(withoutSize(f.what))) return [f];
73705
+ return relaid.map((r) => ({ ...f, what: `${r.format}: ${withoutSize(f.what)}` }));
73706
+ });
73707
+ }
73673
73708
  function deliveredSizes(canvas) {
73674
73709
  const nodes = canvas?.nodes;
73675
73710
  const labels = Array.isArray(nodes) ? nodes.find((n) => n.id === "final" && n.type === "collect")?.params?.labels : void 0;
@@ -73897,7 +73932,7 @@ var CHOSEN = [
73897
73932
  (id, out) => id === "ad" && out.role === void 0
73898
73933
  ];
73899
73934
  function urlOf(out) {
73900
- const url = out.images?.find((o) => typeof o?.url === "string")?.url ?? out.image?.url;
73935
+ const url = out.images?.find((o) => typeof o?.url === "string")?.url ?? out.image?.url ?? out.asset?.url;
73901
73936
  return typeof url === "string" ? url : null;
73902
73937
  }
73903
73938
  function chosenUrl(byNode) {
@@ -74141,7 +74176,7 @@ function turnRenderRefusal(ledger, turnId, slug) {
74141
74176
  return {
74142
74177
  code: "CONFLICT",
74143
74178
  message: `This ad was rendered twice in this turn, its automatic corrections included, and is still not ready.`,
74144
- fix: "Show the best version and say in one plain sentence what is left, in the person's words; they decide what changes next."
74179
+ fix: `Show the best version as a version ("Version 3"), never a file path or a run id, and say in one plain sentence what is left, in the person's words. End there: no question and no options; their next message says what changes.`
74145
74180
  };
74146
74181
  }
74147
74182
  return null;
@@ -75278,7 +75313,7 @@ var runCommand = defineCommand139({
75278
75313
  },
75279
75314
  concurrency: {
75280
75315
  type: "string",
75281
- description: "Max nodes per layer in flight at once (default 8; env BAKER_CANVAS_CONCURRENCY)"
75316
+ description: "Max nodes running at once (default 8; env BAKER_CANVAS_CONCURRENCY)"
75282
75317
  },
75283
75318
  parallel: {
75284
75319
  type: "string",
@@ -75594,7 +75629,8 @@ ${describeRewrites(prepared.rewrites)}
75594
75629
  );
75595
75630
  process.exit(2);
75596
75631
  }
75597
- for (const w of [...validation.warnings ?? [], ...await outputRatioWarningsFor(parsed, filePath)]) {
75632
+ const advisories = (validation.warnings ?? []).filter((w) => w.code !== "ORPHAN_NODE");
75633
+ for (const w of [...advisories, ...await outputRatioWarningsFor(parsed, filePath)]) {
75598
75634
  process.stdout.write(`[warn] ${w.code}: ${w.message}
75599
75635
  `);
75600
75636
  }
@@ -76062,7 +76098,7 @@ var rerunCommand = defineCommand140({
76062
76098
  },
76063
76099
  fresh: { type: "boolean", default: false, description: "Start a new run id instead of resuming an interrupted one" },
76064
76100
  regenerate: { type: "string", description: "Comma-separated node ids to force fresh this run" },
76065
- concurrency: { type: "string", description: "Max nodes per layer in flight at once" }
76101
+ concurrency: { type: "string", description: "Max nodes running at once" }
76066
76102
  },
76067
76103
  async run({ args: args8 }) {
76068
76104
  process.stderr.write("[deprecated] `baker canvas rerun <slug>` is now `baker canvas run --slug <slug>`.\n");
@@ -81345,6 +81381,7 @@ async function readViewer(root, adSet) {
81345
81381
  }
81346
81382
  var MARKET_ADS = 40;
81347
81383
  var MARKET_FALLBACK_ADS = 15;
81384
+ var MARKET_SEARCH_MS = 1e4;
81348
81385
  function marketQuery(companyDoc) {
81349
81386
  const prose = companyDoc.split("\n").filter((line) => line.trim() && !line.startsWith("#")).join(" ").replace(/\s+/g, " ");
81350
81387
  return prose.slice(0, 240);
@@ -81352,7 +81389,14 @@ function marketQuery(companyDoc) {
81352
81389
  async function readMarket(root) {
81353
81390
  const query = marketQuery(await readFile36(path44.join(root, "COMPANY.md"), "utf8").catch(() => ""));
81354
81391
  if (!query) return null;
81355
- const search = (body) => apiPost("/api/ad-library/winners/search", body);
81392
+ const search = (body) => apiPost("/api/ad-library/winners/search", body, {
81393
+ timeoutMs: MARKET_SEARCH_MS
81394
+ }).catch((err) => {
81395
+ if (err instanceof ApiError && err.code === "TIMEOUT") {
81396
+ throw new Error("the ad library did not answer in time, so competitors' claims are left out");
81397
+ }
81398
+ throw err;
81399
+ });
81356
81400
  const followed = await search(
81357
81401
  buildSearchBody({ scope: "followed", platform: "meta", limit: MARKET_ADS, maxPerAdvertiser: 8 })
81358
81402
  );
@@ -81450,7 +81494,7 @@ function briefHints(parts) {
81450
81494
  hints2.push(`Ideas ready to build: ${parts.buildNext.join(", ")} (\`baker creatives make <idea>\`).`);
81451
81495
  } else {
81452
81496
  hints2.push(
81453
- "Write 4-8 ideas in src/creatives/_ideas/<idea>.md (who, message, evidence, awareness, angle, format, job, why), then `baker creatives ideas`."
81497
+ `Write 4-8 ideas in src/creatives/_ideas/<idea>.md (who, message, evidence, awareness, angle, format, job, why), then \`baker creatives ideas\`. awareness: ${AWARENESS_STAGES.join(" | ")}. angle: ${IDEA_ANGLES.join(", ")}. format: ${IDEA_FORMATS.join(", ")}.`
81454
81498
  );
81455
81499
  }
81456
81500
  return hints2;
@@ -81953,7 +81997,7 @@ var learnCommand = defineCommand158({
81953
81997
 
81954
81998
  // src/commands/creatives/make.ts
81955
81999
  import { execFile as execFile3 } from "child_process";
81956
- import { mkdtemp as mkdtemp2, readFile as readFile38, rm as rm7, writeFile as writeFile24 } from "fs/promises";
82000
+ import { mkdtemp as mkdtemp2, readFile as readFile38, rm as rm7, stat as stat7, writeFile as writeFile24 } from "fs/promises";
81957
82001
  import { tmpdir as tmpdir3 } from "os";
81958
82002
  import path46 from "path";
81959
82003
  import { promisify as promisify3 } from "util";
@@ -82013,11 +82057,11 @@ function lastJsonObject(stdout) {
82013
82057
  }
82014
82058
  return null;
82015
82059
  }
82016
- async function baker(args8) {
82060
+ async function baker(args8, signal) {
82017
82061
  const entry = process.argv[1];
82018
82062
  const [file, argv] = entry ? [process.execPath, [...process.execArgv, entry, ...args8]] : ["baker", args8];
82019
82063
  try {
82020
- const { stdout } = await run2(file, argv, { maxBuffer: 32 * 1024 * 1024 });
82064
+ const { stdout } = await run2(file, argv, { maxBuffer: 32 * 1024 * 1024, ...signal ? { signal } : {} });
82021
82065
  return lastJsonObject(stdout) ?? { ok: false, error: { code: "INTERNAL_ERROR", message: "no answer" } };
82022
82066
  } catch (err) {
82023
82067
  const { stdout = "", stderr = "" } = err;
@@ -82051,8 +82095,17 @@ async function findCandidates(searches, deps) {
82051
82095
  }
82052
82096
  return [];
82053
82097
  }
82054
- async function pickReference(root, searches, deps = liveReferenceDeps(root)) {
82098
+ var candidateOf = (c) => ({
82099
+ adId: String(c.ad_id),
82100
+ platform: asPlatform(c.platform),
82101
+ ...c.advertiser_id ? { advertiserId: c.advertiser_id } : {}
82102
+ });
82103
+ async function pickReference(root, searches, deps = liveReferenceDeps(root), prepare) {
82055
82104
  const candidates = (await findCandidates(searches, deps)).slice(0, FITS_TRIED);
82105
+ const first = candidates[0];
82106
+ const stop = new AbortController();
82107
+ const building = first && prepare ? prepare(candidateOf(first), stop.signal) : void 0;
82108
+ void building?.catch(() => void 0);
82056
82109
  const fits = await Promise.all(candidates.map((c) => deps.fit(asPlatform(c.platform), String(c.ad_id))));
82057
82110
  let unchecked = null;
82058
82111
  let checked = 0;
@@ -82063,15 +82116,16 @@ async function pickReference(root, searches, deps = liveReferenceDeps(root)) {
82063
82116
  else checked += 1;
82064
82117
  if (!(fit.ok && fitsIdea(fit.fit))) continue;
82065
82118
  const layers = fit.fit.why_it_wins;
82119
+ if (index > 0) stop.abort();
82066
82120
  return {
82067
- adId: String(candidate.ad_id),
82068
- platform: asPlatform(candidate.platform),
82069
- ...candidate.advertiser_id ? { advertiserId: candidate.advertiser_id } : {},
82121
+ ...candidateOf(candidate),
82070
82122
  why: [layers.disguise, layers.trigger, layers.logic].filter(Boolean).join("; "),
82071
- tried: index + 1
82123
+ tried: index + 1,
82124
+ ...building ? index === 0 ? { prepared: building } : { discarded: building } : {}
82072
82125
  };
82073
82126
  }
82074
- return noReference(candidates.length, checked, unchecked);
82127
+ stop.abort();
82128
+ return { ...noReference(candidates.length, checked, unchecked), ...building ? { discarded: building } : {} };
82075
82129
  }
82076
82130
  function noReference(found, checked, unchecked) {
82077
82131
  if (found > 0 && checked === 0 && unchecked) {
@@ -82087,6 +82141,18 @@ function noReference(found, checked, unchecked) {
82087
82141
  fix: "Change the idea's format to another container (`baker creatives ideas --full` lists them) and run make again, or pass --reference <adId> for one you chose. Do not stop the batch: build the other ideas."
82088
82142
  };
82089
82143
  }
82144
+ function alreadyWritten(promptJson, slug) {
82145
+ if (promptJson === null || promptJson === void 0) return null;
82146
+ try {
82147
+ if (unwrittenProblems(readAdSpec(promptJson)).length > 0) return null;
82148
+ } catch {
82149
+ }
82150
+ return {
82151
+ code: "ALREADY_MADE",
82152
+ message: `"${slug}" is already made and its ad is written: building it again would replace that ad with placeholders.`,
82153
+ fix: `To change this ad, edit src/creatives/${slug}/prompt.json and run \`baker canvas run\` on it. For another ad of the same idea, run make again with --slug <new-slug>.`
82154
+ };
82155
+ }
82090
82156
  var flag = (value2) => typeof value2 === "string" && value2 ? value2 : void 0;
82091
82157
  async function loadIdea(root, ideaId) {
82092
82158
  const { ideas, unreadable } = await readIdeas(root);
@@ -82139,24 +82205,40 @@ async function scaffoldIdea(root, idea, args8, slug, temperature) {
82139
82205
  reference: null
82140
82206
  };
82141
82207
  }
82142
- const reference = args8.reference ? { adId: args8.reference, platform: asPlatform(args8.platform), why: "chosen by hand", tried: 0 } : args8.fromIdea ? { code: "FROM_IDEA", message: "Asked to start from the idea." } : await pickReference(root, referenceSearchesFor(idea, temperature).map(buildSearchBody));
82208
+ const onReference = (c, signal) => baker(
82209
+ [
82210
+ "canvas",
82211
+ "scaffold-static-ad",
82212
+ "--source-ad-id",
82213
+ c.adId,
82214
+ "--source-platform",
82215
+ c.platform,
82216
+ ...c.advertiserId ? ["--source-advertiser-id", c.advertiserId] : [],
82217
+ "--slug",
82218
+ slug,
82219
+ "--title",
82220
+ idea.message.slice(0, 120),
82221
+ ...viewerFlags
82222
+ ],
82223
+ signal
82224
+ );
82225
+ const dir = path46.join(root, "src", "creatives", slug);
82226
+ const fresh = !await stat7(dir).catch(() => null);
82227
+ const reference = args8.reference ? { adId: args8.reference, platform: asPlatform(args8.platform), why: "chosen by hand", tried: 0 } : args8.fromIdea ? { code: "FROM_IDEA", message: "Asked to start from the idea." } : await pickReference(
82228
+ root,
82229
+ referenceSearchesFor(idea, temperature).map(buildSearchBody),
82230
+ void 0,
82231
+ fresh ? onReference : void 0
82232
+ );
82233
+ if ("discarded" in reference && reference.discarded) {
82234
+ await reference.discarded.catch(() => void 0);
82235
+ await rm7(dir, { recursive: true, force: true });
82236
+ }
82143
82237
  if ("code" in reference) {
82144
82238
  return { scaffold: await scaffoldFromIdea(idea, slug, viewerFlags), reference: null, fromIdea: reference.message };
82145
82239
  }
82146
- const scaffold = await baker([
82147
- "canvas",
82148
- "scaffold-static-ad",
82149
- "--source-ad-id",
82150
- reference.adId,
82151
- "--source-platform",
82152
- reference.platform,
82153
- ...reference.advertiserId ? ["--source-advertiser-id", reference.advertiserId] : [],
82154
- "--slug",
82155
- slug,
82156
- "--title",
82157
- idea.message.slice(0, 120),
82158
- ...viewerFlags
82159
- ]);
82240
+ const prepared = "prepared" in reference ? await reference.prepared?.catch(() => null) : null;
82241
+ const scaffold = prepared?.ok === true ? prepared : await onReference(reference);
82160
82242
  return { scaffold, reference };
82161
82243
  }
82162
82244
  async function scaffoldFromIdea(idea, slug, viewerFlags) {
@@ -82253,6 +82335,7 @@ function makeEnvelope(made) {
82253
82335
  `Rewrite before running: ${made.unsourcedCopy.map((l) => JSON.stringify(l)).join(", ")} claim a figure nothing the company wrote holds (the reference's promise, not ours).`
82254
82336
  ] : [],
82255
82337
  `Write the copy lines and the scene in src/creatives/${made.slug}/prompt.json${made.reference ? ": the drafted lines are the reference's, so keep one only if the Knowledge says it (badges, ratings and guarantees too)" : ""}. Say the message in the customer's words${quote ? ` ("${quote.slice(0, 80)}")` : ""}; keep \`message\` as it is.`,
82338
+ SPEC_SHAPES_HINT,
82256
82339
  `Then: baker canvas run ${made.canvasPath} (it validates and asks the viewer itself: nothing to run before it).`,
82257
82340
  // The scaffold's own next steps name a separate validate; the run does it (dev, Oct 2026: five validates before one run).
82258
82341
  ...made.scaffoldHints.filter((h) => !/canvas (?:validate|viewer-check)/.test(h)).slice(0, 4)
@@ -82267,6 +82350,18 @@ var makeCommand = defineCommand159({
82267
82350
  const opts = makeArgsOf(args8);
82268
82351
  const { idea, check } = await loadIdea(root, String(args8.idea));
82269
82352
  const slug = opts.slug ?? idea.id;
82353
+ const written = await readFile38(path46.join(root, "src", "creatives", slug, "prompt.json"), "utf8").then(
82354
+ (text3) => {
82355
+ try {
82356
+ return JSON.parse(text3);
82357
+ } catch {
82358
+ return {};
82359
+ }
82360
+ },
82361
+ (err) => err.code === "ENOENT" ? null : {}
82362
+ );
82363
+ const refused = alreadyWritten(written, slug);
82364
+ if (refused) fail6(refused);
82270
82365
  const temperature = await viewerTemperature(root, opts.adset, idea.awareness);
82271
82366
  const { scaffold, reference, fromIdea } = await scaffoldIdea(root, idea, opts, slug, temperature);
82272
82367
  if (scaffold.ok !== true) fail6(scaffoldFailure(scaffold));
@@ -84030,7 +84125,7 @@ import { readdir as readdir19, readFile as readFile42 } from "fs/promises";
84030
84125
  import path48 from "path";
84031
84126
 
84032
84127
  // src/commands/landing/import-graph.ts
84033
- import { readdir as readdir18, readFile as readFile41, stat as stat7 } from "fs/promises";
84128
+ import { readdir as readdir18, readFile as readFile41, stat as stat8 } from "fs/promises";
84034
84129
  import path47 from "path";
84035
84130
  var SOURCE_EXT = /\.(astro|ts|tsx|js|jsx|mjs|cjs|vue|svelte)$/;
84036
84131
  var RESOLVE_CANDIDATES = [
@@ -84050,7 +84145,7 @@ var IMPORT_RE = /(?<![A-Za-z0-9_$])(?:import|export)\s*(?:[\s\S]*?\bfrom\s*)?["'
84050
84145
  var DYNAMIC_IMPORT_RE = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
84051
84146
  async function isFile(p) {
84052
84147
  try {
84053
- return (await stat7(p)).isFile();
84148
+ return (await stat8(p)).isFile();
84054
84149
  } catch {
84055
84150
  return false;
84056
84151
  }
@@ -84182,7 +84277,7 @@ async function readForms(root, slug) {
84182
84277
 
84183
84278
  // src/commands/experiment/fold.ts
84184
84279
  import { statSync } from "fs";
84185
- import { cp as cp3, mkdir as mkdir15, readdir as readdir20, readFile as readFile43, rm as rm8, stat as stat8, writeFile as writeFile25 } from "fs/promises";
84280
+ import { cp as cp3, mkdir as mkdir15, readdir as readdir20, readFile as readFile43, rm as rm8, stat as stat9, writeFile as writeFile25 } from "fs/promises";
84186
84281
  import path50 from "path";
84187
84282
 
84188
84283
  // ../api/src/experiments/variants.ts
@@ -84249,7 +84344,7 @@ function posix2(p) {
84249
84344
  }
84250
84345
  async function isDir(p) {
84251
84346
  try {
84252
- return (await stat8(p)).isDirectory();
84347
+ return (await stat9(p)).isDirectory();
84253
84348
  } catch {
84254
84349
  return false;
84255
84350
  }
@@ -91651,7 +91746,7 @@ function cropSprite(input, region) {
91651
91746
 
91652
91747
  // src/lib/image/io.ts
91653
91748
  import { randomBytes } from "crypto";
91654
- import { glob as fsGlob, readFile as readFile46, rename as rename2, stat as stat9, writeFile as writeFile26 } from "fs/promises";
91749
+ import { glob as fsGlob, readFile as readFile46, rename as rename2, stat as stat10, writeFile as writeFile26 } from "fs/promises";
91655
91750
  import { dirname as dirname2, extname as extname3, join as join5, resolve as resolve4 } from "path";
91656
91751
  var REMOTE_RE = /^https?:\/\//i;
91657
91752
  var GLOB_RE = /[*?[\]{}]/;
@@ -91688,7 +91783,7 @@ async function readImageBuffer(pathOrUrl) {
91688
91783
  }
91689
91784
  async function isDirectory(path68) {
91690
91785
  try {
91691
- const s = await stat9(path68);
91786
+ const s = await stat10(path68);
91692
91787
  return s.isDirectory();
91693
91788
  } catch {
91694
91789
  return false;
@@ -95146,7 +95241,7 @@ Full guide: __tooling__/docs/tools/baker/images.md`
95146
95241
  import { defineCommand as defineCommand226 } from "citty";
95147
95242
 
95148
95243
  // src/commands/landing/critique.ts
95149
- import { readdir as readdir22, readFile as readFile48, stat as stat11 } from "fs/promises";
95244
+ import { readdir as readdir22, readFile as readFile48, stat as stat12 } from "fs/promises";
95150
95245
  import path55 from "path";
95151
95246
  import { defineCommand as defineCommand213 } from "citty";
95152
95247
 
@@ -96357,7 +96452,7 @@ async function writeCritiqueSnapshot(projectRoot, snapshot) {
96357
96452
  }
96358
96453
 
96359
96454
  // src/commands/landing/source-version.ts
96360
- import { readdir as readdir21, readFile as readFile47, stat as stat10 } from "fs/promises";
96455
+ import { readdir as readdir21, readFile as readFile47, stat as stat11 } from "fs/promises";
96361
96456
  import path54 from "path";
96362
96457
  var CRITIQUED_ROOTS = ["src/pages/", "src/components/"];
96363
96458
  async function landingSourceRelPaths(root, slug) {
@@ -96405,7 +96500,7 @@ async function computeLegacyLandingSourceSha(landingDir) {
96405
96500
  }
96406
96501
  async function isFile2(p) {
96407
96502
  try {
96408
- return (await stat10(p)).isFile();
96503
+ return (await stat11(p)).isFile();
96409
96504
  } catch {
96410
96505
  return false;
96411
96506
  }
@@ -96583,7 +96678,7 @@ function present(f) {
96583
96678
  }
96584
96679
  async function isDir2(p) {
96585
96680
  try {
96586
- return (await stat11(p)).isDirectory();
96681
+ return (await stat12(p)).isDirectory();
96587
96682
  } catch {
96588
96683
  return false;
96589
96684
  }
@@ -99901,7 +99996,7 @@ Full guide: __tooling__/docs/tools/baker/landing.md`
99901
99996
 
99902
99997
  // src/commands/landing/variant.ts
99903
99998
  import { randomUUID as randomUUID4 } from "crypto";
99904
- import { mkdir as mkdir20, readdir as readdir23, readFile as readFile49, stat as stat12, writeFile as writeFile32 } from "fs/promises";
99999
+ import { mkdir as mkdir20, readdir as readdir23, readFile as readFile49, stat as stat13, writeFile as writeFile32 } from "fs/promises";
99905
100000
  import path63 from "path";
99906
100001
  import { defineCommand as defineCommand225 } from "citty";
99907
100002
  registerSchema({
@@ -99953,14 +100048,14 @@ function fail11(code, message, fix) {
99953
100048
  }
99954
100049
  async function isDir3(p) {
99955
100050
  try {
99956
- return (await stat12(p)).isDirectory();
100051
+ return (await stat13(p)).isDirectory();
99957
100052
  } catch {
99958
100053
  return false;
99959
100054
  }
99960
100055
  }
99961
100056
  async function exists2(p) {
99962
100057
  try {
99963
- await stat12(p);
100058
+ await stat13(p);
99964
100059
  return true;
99965
100060
  } catch {
99966
100061
  return false;
@@ -108029,7 +108124,7 @@ var groupCommand2 = defineCommand295({
108029
108124
  });
108030
108125
 
108031
108126
  // src/commands/videos/ingest.ts
108032
- import { mkdtemp as mkdtemp4, rm as rm10, stat as stat13 } from "fs/promises";
108127
+ import { mkdtemp as mkdtemp4, rm as rm10, stat as stat14 } from "fs/promises";
108033
108128
  import { tmpdir as tmpdir5 } from "os";
108034
108129
  import path67 from "path";
108035
108130
  import { defineCommand as defineCommand296 } from "citty";
@@ -108302,7 +108397,7 @@ async function downloadThenIngest(args8, country) {
108302
108397
  // we allow to fetch it cannot drift apart.
108303
108398
  timeoutMs: downloadTimeoutMs(durationSeconds(probe.info))
108304
108399
  });
108305
- const stats = await stat13(filePath);
108400
+ const stats = await stat14(filePath);
108306
108401
  if (stats.size > MAX_VIDEO_INGEST_BYTES) {
108307
108402
  throw new ApiError(
108308
108403
  "VALIDATION_ERROR",
@@ -108463,7 +108558,7 @@ var searchCommand6 = defineCommand298({
108463
108558
  var tagsCommand6 = makeTagsCommand("videos", "video", "/api/videos/tags");
108464
108559
 
108465
108560
  // src/commands/videos/upload.ts
108466
- import { stat as stat14 } from "fs/promises";
108561
+ import { stat as stat15 } from "fs/promises";
108467
108562
  import { basename as basename4 } from "path";
108468
108563
  import { defineCommand as defineCommand299 } from "citty";
108469
108564
  registerSchema({
@@ -108531,7 +108626,7 @@ var uploadCommand2 = defineCommand299({
108531
108626
  const contentType = args8["content-type"] || detectVideoContentType(filePath);
108532
108627
  const descriptionContext = args8.context;
108533
108628
  if (args8["dry-run"]) {
108534
- const fileStats = await stat14(filePath);
108629
+ const fileStats = await stat15(filePath);
108535
108630
  const originalFilename = basename4(filePath);
108536
108631
  writeJson({
108537
108632
  ok: true,