@koda-sl/baker-cli 0.436.0-dev.7e3937022 → 0.437.0-dev.ee2f8e252

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
@@ -211,7 +211,7 @@ import {
211
211
  withoutCastName,
212
212
  wordCount,
213
213
  ytDlpBlockSignal
214
- } from "./chunk-QQX5LPMQ.js";
214
+ } from "./chunk-FB3GTXQG.js";
215
215
  import {
216
216
  NAME_CONTAINS_ARG,
217
217
  csvOrJson,
@@ -28236,7 +28236,12 @@ var creativesFeedbackRecordRequestSchema = z20.object({
28236
28236
  /** Why it was rejected. Ignored on an approval. */
28237
28237
  reasons: z20.array(creativeFeedbackReasonSchema).max(CREATIVE_FEEDBACK_REASONS.length).optional(),
28238
28238
  /** The person's own words. Stored up to 1000 characters. */
28239
- note: z20.string().max(4e3).optional()
28239
+ note: z20.string().max(4e3).optional(),
28240
+ /**
28241
+ * The CLI could not tell whether `note` is the person's words or the agent's own option (the
28242
+ * decision model gave no answer). The verdict is kept; the note is never read back as taste.
28243
+ */
28244
+ noteUnconfirmed: z20.boolean().optional()
28240
28245
  });
28241
28246
  var creativesFeedbackRecordResponseSchema = z20.object({
28242
28247
  ok: z20.literal(true),
@@ -68611,7 +68616,7 @@ var catalogCommand = defineCommand137({
68611
68616
  });
68612
68617
 
68613
68618
  // src/commands/canvas/critique.ts
68614
- import path22 from "path";
68619
+ import path24 from "path";
68615
68620
  import { defineCommand as defineCommand138 } from "citty";
68616
68621
 
68617
68622
  // src/engine/scaffold/lib/shoot-modes.ts
@@ -69367,8 +69372,8 @@ function critiqueCanvas(canvas) {
69367
69372
  }
69368
69373
 
69369
69374
  // src/commands/canvas/prepare-canvas.ts
69370
- import { readdir as readdir9, readFile as readFile17, writeFile as writeFile11 } from "fs/promises";
69371
- import path21 from "path";
69375
+ import { readdir as readdir9, readFile as readFile19, writeFile as writeFile14 } from "fs/promises";
69376
+ import path23 from "path";
69372
69377
 
69373
69378
  // src/commands/canvas/placeholders.ts
69374
69379
  function unsuppliedPlaceholderAssets(canvas) {
@@ -69479,34 +69484,45 @@ var CLAIMS_REQUEST = {
69479
69484
  var CLAIMS_TIMEOUT_MS = 3e4;
69480
69485
  var SUPERLATIVE_HELD = 0.7;
69481
69486
  var RECORDS_BATCH_CHARS = 4e4;
69487
+ var CHUNK_OVERLAP = 200;
69488
+ function pack(pieces, joiner) {
69489
+ const runs = [];
69490
+ let run3 = "";
69491
+ for (const piece of pieces) {
69492
+ if (run3 && run3.length + joiner.length + piece.length > RECORDS_BATCH_CHARS) {
69493
+ runs.push(run3);
69494
+ run3 = "";
69495
+ }
69496
+ run3 = run3 ? `${run3}${joiner}${piece}` : piece;
69497
+ }
69498
+ return run3 ? [...runs, run3] : runs;
69499
+ }
69500
+ function withinReading(text4, cuts) {
69501
+ if (text4.length <= RECORDS_BATCH_CHARS) return [text4];
69502
+ const [cut, ...finer] = cuts;
69503
+ if (!cut) {
69504
+ const chunks = [];
69505
+ for (let at = 0; at < text4.length; at += RECORDS_BATCH_CHARS - CHUNK_OVERLAP) {
69506
+ chunks.push(text4.slice(at, at + RECORDS_BATCH_CHARS));
69507
+ if (at + RECORDS_BATCH_CHARS >= text4.length) break;
69508
+ }
69509
+ return chunks;
69510
+ }
69511
+ return pack(
69512
+ text4.split(cut.at).flatMap((piece) => withinReading(piece, finer)),
69513
+ cut.joiner
69514
+ );
69515
+ }
69516
+ var CUTS = [
69517
+ { at: /\n\s*\n/, joiner: "\n\n" },
69518
+ { at: /\n/, joiner: "\n" }
69519
+ ];
69482
69520
  function recordBatches(parts) {
69483
- const pieces = parts.flatMap((part) => {
69484
- if (part.text.length <= RECORDS_BATCH_CHARS) return [part.text];
69485
- const out = [];
69486
- let piece = "";
69487
- for (const paragraph of part.text.split(/\n\s*\n/)) {
69488
- if (piece && piece.length + paragraph.length > RECORDS_BATCH_CHARS) {
69489
- out.push(piece);
69490
- piece = "";
69491
- }
69492
- piece = piece ? `${piece}
69493
-
69494
- ${paragraph.slice(0, RECORDS_BATCH_CHARS)}` : paragraph.slice(0, RECORDS_BATCH_CHARS);
69495
- }
69496
- return piece ? [...out, piece] : out;
69497
- });
69498
- const batches = [];
69499
- let batch = "";
69500
- for (const piece of pieces.filter((p) => p.trim())) {
69501
- if (batch && batch.length + piece.length > RECORDS_BATCH_CHARS) {
69502
- batches.push(batch);
69503
- batch = "";
69504
- }
69505
- batch = batch ? `${batch}
69506
-
69507
- ${piece}` : piece;
69508
- }
69509
- return batch ? [...batches, batch] : batches;
69521
+ const pieces = parts.flatMap((part) => withinReading(part.text, CUTS));
69522
+ return pack(
69523
+ pieces.filter((p) => p.trim()),
69524
+ "\n\n"
69525
+ );
69510
69526
  }
69511
69527
  var RECORDS_SAY = "the company's records (src/content, COMPANY.md, its customers' words) do not hold";
69512
69528
  function lineOf(claim, lines) {
@@ -71161,10 +71177,15 @@ function badgeWired(spec, name) {
71161
71177
  }
71162
71178
  var QUOTED_RUN = 5;
71163
71179
  var SIGNING_ROLE = /^(attribution|author|byline|name|reviewer|source|signature)$/i;
71164
- var SIGNED = /(?:^\s*|[\p{Pf}\p{Pe}"'.!?…。!?]\s*)[—–―-]\s*[\p{L}\p{N}]/u;
71180
+ var SIGNED_AFTER_STOP = /(?:^\s*|[\p{Pf}\p{Pe}"'.!?…。!?]\s*)[—–―-]\s*[\p{L}\p{N}]/u;
71181
+ var SIGNED_AT_END = new RegExp("\\s[\u2014\u2013\u2015-]\\s+\\p{Lu}[\\p{L}\\p{M}.'\u2019-]*(?:(?:\\s+[\\p{L}\\p{M}.'\u2019-]+){0,2}\\s+\\p{Lu}[\\p{L}\\p{M}.'\u2019-]*)?(?:,[^,\u2014\u2013\u2015]{1,40})?\\s*$", "u");
71182
+ var signed = (text4) => SIGNED_AFTER_STOP.test(text4) || SIGNED_AT_END.test(text4);
71165
71183
  var QUOTE_ROLE = /^(quote|testimonial|review)$/i;
71184
+ function isSignatureLine(line) {
71185
+ return SIGNING_ROLE.test(line.role ?? "") || /^\s*[—–―-]\s*[\p{L}\p{N}]/u.test(line.text);
71186
+ }
71166
71187
  function isCustomersWords(line) {
71167
- return SIGNED.test(line.text) || QUOTE_ROLE.test(line.role ?? "") || SIGNING_ROLE.test(line.role ?? "");
71188
+ return signed(line.text) || QUOTE_ROLE.test(line.role ?? "") || SIGNING_ROLE.test(line.role ?? "");
71168
71189
  }
71169
71190
  var quoteWords = (text4) => text4.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
71170
71191
  function quotes(line, quote) {
@@ -71184,7 +71205,7 @@ function namesAuthor(line, by) {
71184
71205
  function unattributedQuotes(spec, said) {
71185
71206
  const signedApart = spec.copy.some((line) => SIGNING_ROLE.test(line.role ?? ""));
71186
71207
  return spec.copy.flatMap((line) => {
71187
- if (signedApart || SIGNED.test(line.text)) return [];
71208
+ if (signedApart || signed(line.text)) return [];
71188
71209
  const by = said.find((q) => quotes(line.text, q.quote));
71189
71210
  if (!by || namesAuthor(line.text, by)) return [];
71190
71211
  const name = by.author ? `"\u2014 ${by.author}"` : '"\u2014 <their first name and initial>"';
@@ -71577,7 +71598,7 @@ function backendDurableUrlFor(log = () => void 0) {
71577
71598
  }
71578
71599
 
71579
71600
  // src/commands/canvas/scene-projection.ts
71580
- import { readFile as readFile13 } from "fs/promises";
71601
+ import { readFile as readFile13, writeFile as writeFile10 } from "fs/promises";
71581
71602
  import path18 from "path";
71582
71603
 
71583
71604
  // src/engine/scaffold/video/aspect.ts
@@ -72452,14 +72473,21 @@ function spokenLanguage(code) {
72452
72473
  }
72453
72474
  var NAMES2 = new Intl.DisplayNames(["en"], { type: "language", fallback: "none" });
72454
72475
  var LETTERS = "abcdefghijklmnopqrstuvwxyz";
72455
- var LANGUAGE_OPTIONS = Object.fromEntries(
72456
- [...LETTERS].flatMap(
72457
- (a) => [...LETTERS].flatMap((b) => {
72458
- const name = NAMES2.of(`${a}${b}`);
72459
- return name && name.toLowerCase() !== `${a}${b}` ? [[`${a}${b}`, name]] : [];
72460
- })
72461
- )
72462
- );
72476
+ var LANGUAGE_OPTIONS = (() => {
72477
+ const options = {};
72478
+ const named = /* @__PURE__ */ new Set();
72479
+ for (const a of LETTERS) {
72480
+ for (const b of LETTERS) {
72481
+ const code = `${a}${b}`;
72482
+ const name = NAMES2.of(code);
72483
+ if (!name || name.toLowerCase() === code || named.has(name)) continue;
72484
+ if (Intl.getCanonicalLocales(code)[0] !== code) continue;
72485
+ named.add(name);
72486
+ options[code] = name;
72487
+ }
72488
+ }
72489
+ return options;
72490
+ })();
72463
72491
  var LANGUAGE_HELD = 0.8;
72464
72492
  function languageQuestion(lines) {
72465
72493
  return {
@@ -74128,6 +74156,81 @@ function scaffoldVideoCanvas(input, elementsInput, requested) {
74128
74156
  };
74129
74157
  }
74130
74158
 
74159
+ // src/engine/nodes/local/lib/caption-groups.ts
74160
+ function captionWordKey(text4) {
74161
+ return text4.replace(/[.,;:!?…]+$/, "").toLocaleLowerCase();
74162
+ }
74163
+ var CARRIES_ON_HELD = 0.4;
74164
+ var CARRIES_ON_QUESTION = "Is `word`, as used in `line`, a function word that cannot close a phrase: an article, a preposition, a conjunction, a possessive or another determiner? A noun, verb, adjective, adverb, name or number is not.";
74165
+ function carriesOnQuestions(lines) {
74166
+ const seen = /* @__PURE__ */ new Set();
74167
+ const questions = [];
74168
+ for (const line of lines) {
74169
+ for (const text4 of line.split(/\s+/)) {
74170
+ const word = captionWordKey(text4);
74171
+ if (!word || seen.has(word)) continue;
74172
+ seen.add(word);
74173
+ questions.push({ type: "noul", instructions: { line, word, question: CARRIES_ON_QUESTION } });
74174
+ }
74175
+ }
74176
+ return { questions, words: [...seen] };
74177
+ }
74178
+ function carriesOnFromAnswers(words, answers) {
74179
+ if (!answers) return [];
74180
+ return words.filter((_, i) => {
74181
+ const answer = answers[i];
74182
+ return answer !== void 0 && "noul" in answer && answer.noul >= CARRIES_ON_HELD;
74183
+ });
74184
+ }
74185
+
74186
+ // src/engine/scaffold/video/speech-reading.ts
74187
+ var asBlueprint = (input) => input && typeof input === "object" ? input : {};
74188
+ var dialogueOf = (b) => (b.scenes ?? []).flatMap((s) => s.dialogue ?? []);
74189
+ function blueprintVoices(input) {
74190
+ const b = asBlueprint(input);
74191
+ const all = [
74192
+ ...dialogueOf(b).map((l) => l.voice_description),
74193
+ ...(b.global?.cast ?? []).map((c) => c.description),
74194
+ b.global?.voiceover?.voice_description
74195
+ ];
74196
+ return [...new Set(all.filter((d) => typeof d === "string" && d.length > 0))];
74197
+ }
74198
+ async function readBlueprintSpeech(input, known, readers) {
74199
+ const lines = dialogueOf(asBlueprint(input)).map((l) => l.line?.trim() ?? "").filter(Boolean);
74200
+ const declared = known.declaredLanguage ? spokenLanguage(known.declaredLanguage)?.tag : void 0;
74201
+ const voices = blueprintVoices(input);
74202
+ const carries2 = carriesOnQuestions(lines);
74203
+ const language = !declared && lines.length > 0 ? [languageQuestion(lines)] : [];
74204
+ const [answers, genders] = await Promise.all([
74205
+ readers.ask([...language, ...carries2.questions]),
74206
+ readers.genders(voices).catch(() => null)
74207
+ ]);
74208
+ const voiceGenders = genders ?? {};
74209
+ if (!answers)
74210
+ return { ...declared ? { spokenLanguage: declared } : {}, voiceGenders, captionCarriesOn: [], read: false };
74211
+ const read = declared ?? (language.length > 0 ? languageFromAnswer(answers[0]) : void 0);
74212
+ return {
74213
+ ...read ? { spokenLanguage: read } : {},
74214
+ voiceGenders,
74215
+ captionCarriesOn: carriesOnFromAnswers(carries2.words, answers.slice(language.length)),
74216
+ read: true
74217
+ };
74218
+ }
74219
+ async function speechMissingFromRebuild(input, opts, readers) {
74220
+ if (opts.voiceGenders !== void 0) return null;
74221
+ const lines = dialogueOf(asBlueprint(input)).map((l) => l.line?.trim() ?? "").filter(Boolean);
74222
+ const voices = blueprintVoices(input);
74223
+ if (lines.length === 0 && voices.length === 0) return null;
74224
+ const askLanguage = !voiceLanguageOf(opts) && lines.length > 0;
74225
+ const [answers, genders] = await Promise.all([
74226
+ askLanguage ? readers.ask([languageQuestion(lines)]).catch(() => null) : Promise.resolve([]),
74227
+ readers.genders(voices).catch(() => null)
74228
+ ]);
74229
+ if (!answers || !genders) return null;
74230
+ const language = askLanguage ? languageFromAnswer(answers[0]) : void 0;
74231
+ return { ...language ? { spokenLanguage: language } : {}, voiceGenders: genders };
74232
+ }
74233
+
74131
74234
  // src/commands/canvas/scene-files.ts
74132
74235
  import { mkdir as mkdir7, readFile as readFile12, readdir as readdir6, rm, writeFile as writeFile9 } from "fs/promises";
74133
74236
  import path17 from "path";
@@ -74255,6 +74358,15 @@ function parseRebuild(raw) {
74255
74358
  const { elements, opts } = JSON.parse(raw);
74256
74359
  return { elements, ...registeredRebuildOpts(opts) };
74257
74360
  }
74361
+ async function withSpeechRead(file, rebuildRaw, blueprint, opts, readers) {
74362
+ const read = await speechMissingFromRebuild(blueprint, opts, readers);
74363
+ if (!read) return opts;
74364
+ const stored = JSON.parse(rebuildRaw);
74365
+ const kept = { ...stored, opts: { ...stored.opts, ...read } };
74366
+ await writeFile10(file, `${JSON.stringify(kept, null, 2)}
74367
+ `, "utf8").catch(() => void 0);
74368
+ return { ...opts, ...read };
74369
+ }
74258
74370
  async function sceneOwnedPrompts(canvasPath) {
74259
74371
  const creativeDir3 = path18.dirname(canvasPath);
74260
74372
  const blueprint = await reassembleBlueprint(creativeDir3);
@@ -74276,7 +74388,7 @@ async function sceneOwnedPrompts(canvasPath) {
74276
74388
  }
74277
74389
  return owned;
74278
74390
  }
74279
- async function syncSceneNodeParams(canvas, canvasPath, log) {
74391
+ async function syncSceneNodeParams(canvas, canvasPath, log, readers) {
74280
74392
  const creativeDir3 = path18.dirname(canvasPath);
74281
74393
  const blueprint = await reassembleBlueprint(creativeDir3);
74282
74394
  if (!blueprint) return "not_applicable";
@@ -74289,8 +74401,10 @@ async function syncSceneNodeParams(canvas, canvasPath, log) {
74289
74401
  );
74290
74402
  return "not_applicable";
74291
74403
  }
74292
- const { elements, opts, replaced } = parseRebuild(rebuildRaw);
74404
+ const parsed = parseRebuild(rebuildRaw);
74405
+ const { elements, replaced } = parsed;
74293
74406
  warnReplacedModels(replaced, log);
74407
+ const opts = readers ? await withSpeechRead(path18.join(creativeDir3, REBUILD_FILE), rebuildRaw, blueprint, parsed.opts, readers) : parsed.opts;
74294
74408
  const rebuilt = scaffoldVideoCanvas(blueprint, elements, opts);
74295
74409
  const rebuiltNodes = promptNodes(rebuilt);
74296
74410
  const liveNodes = promptNodes(canvas);
@@ -74430,6 +74544,93 @@ function toPosix(p) {
74430
74544
  return p.split(path19.sep).join("/");
74431
74545
  }
74432
74546
 
74547
+ // src/engine/nodes/local/lib/jev-cache.ts
74548
+ import { createHash as createHash7 } from "crypto";
74549
+ import { mkdir as mkdir8, readFile as readFile15, writeFile as writeFile11 } from "fs/promises";
74550
+ import path20 from "path";
74551
+ import { z as z71 } from "zod";
74552
+ var Kept = z71.array(
74553
+ z71.union([
74554
+ z71.object({ choice: z71.string(), probabilities: z71.record(z71.string(), z71.number()), confidence: z71.number() }),
74555
+ z71.object({ noul: z71.number() })
74556
+ ])
74557
+ );
74558
+ function cachedJev(cacheDir, ask) {
74559
+ const dir = path20.join(cacheDir, "jev");
74560
+ return async (questions) => {
74561
+ const key = createHash7("sha256").update(JSON.stringify([JEV_MODEL, questions])).digest("hex");
74562
+ const file = path20.join(dir, `${key}.json`);
74563
+ const kept = Kept.safeParse(
74564
+ await readFile15(file, "utf8").then(JSON.parse, () => null).catch(() => null)
74565
+ );
74566
+ if (kept.success && kept.data.length === questions.length) return kept.data;
74567
+ const answers = await ask(questions);
74568
+ if (answers) {
74569
+ await mkdir8(dir, { recursive: true });
74570
+ await writeFile11(file, JSON.stringify(answers), "utf8");
74571
+ }
74572
+ return answers;
74573
+ };
74574
+ }
74575
+
74576
+ // src/engine/scaffold/video/dialogue/voice-gender.ts
74577
+ import { createHash as createHash8 } from "crypto";
74578
+ import { mkdir as mkdir9, readFile as readFile16, writeFile as writeFile12 } from "fs/promises";
74579
+ import path21 from "path";
74580
+ import { z as z72 } from "zod";
74581
+ var VOICE_GENDER_MODEL = "google/gemini-2.5-flash-lite";
74582
+ var PROMPT_VERSION2 = 1;
74583
+ var REQUEST2 = {
74584
+ provider: { order: ["google-vertex"], allow_fallbacks: false },
74585
+ temperature: 0,
74586
+ response_format: { type: "json_object" }
74587
+ };
74588
+ var TIMEOUT_MS2 = 2e4;
74589
+ function voiceGenderPrompt(voices) {
74590
+ return [
74591
+ "Each line below describes the voice wanted for a speaker in an ad, written in any language.",
74592
+ "For each, say whether it asks for a woman's voice or a man's voice. Read it in its own language: a noun that is feminine or masculine by itself says so. Answer null when it does not say.",
74593
+ "",
74594
+ ...voices.map((voice, i) => `${i + 1}. ${JSON.stringify(voice)}`),
74595
+ "",
74596
+ `Reply with only this JSON: {"genders": ["female" | "male" | null, ... one per line, ${voices.length} in order]}`
74597
+ ].join("\n");
74598
+ }
74599
+ var Answer3 = z72.object({ genders: z72.array(z72.enum(["female", "male"]).nullable()) });
74600
+ function parseVoiceGenders(answer, voices) {
74601
+ const parsed = Answer3.safeParse(answer);
74602
+ if (!parsed.success || parsed.data.genders.length !== voices.length) return null;
74603
+ const out = {};
74604
+ voices.forEach((voice, i) => {
74605
+ const gender = parsed.data.genders[i];
74606
+ if (gender) out[voice] = gender;
74607
+ });
74608
+ return out;
74609
+ }
74610
+ function voiceGenderModel(cacheDir, onSpend) {
74611
+ const dir = path21.join(cacheDir, "voice-gender");
74612
+ return async (voices) => {
74613
+ if (voices.length === 0) return {};
74614
+ const key = createHash8("sha256").update(JSON.stringify([PROMPT_VERSION2, VOICE_GENDER_MODEL, voices])).digest("hex");
74615
+ const file = path21.join(dir, `${key}.json`);
74616
+ const kept = parseVoiceGenders(
74617
+ await readFile16(file, "utf8").then(JSON.parse, () => null).catch(() => null),
74618
+ voices
74619
+ );
74620
+ if (kept) return kept;
74621
+ const answer = await askAboutText(VOICE_GENDER_MODEL, voiceGenderPrompt(voices), onSpend, {
74622
+ timeoutMs: TIMEOUT_MS2,
74623
+ request: REQUEST2
74624
+ }).catch(() => null);
74625
+ const genders = parseVoiceGenders(answer, voices);
74626
+ if (genders) {
74627
+ await mkdir9(dir, { recursive: true });
74628
+ await writeFile12(file, JSON.stringify(answer), "utf8");
74629
+ }
74630
+ return genders;
74631
+ };
74632
+ }
74633
+
74433
74634
  // src/commands/creatives/ideas-model.ts
74434
74635
  import { parse as parseYaml2, YAMLParseError } from "yaml";
74435
74636
  var IDEA_ANGLES = [
@@ -74769,8 +74970,8 @@ ${ownQuotes.join("\n")}`);
74769
74970
  }
74770
74971
 
74771
74972
  // src/commands/creatives/ideas-workspace.ts
74772
- import { readdir as readdir8, readFile as readFile15 } from "fs/promises";
74773
- import path20 from "path";
74973
+ import { readdir as readdir8, readFile as readFile17 } from "fs/promises";
74974
+ import path22 from "path";
74774
74975
 
74775
74976
  // src/commands/creatives/ideas-judgments.ts
74776
74977
  var STATISTIC_QUESTION = `\`quote\` was filed as something a customer said. Is it instead a statistic about a group of people, a share or a figure from a survey, a study or an article ("39% of UK homeowners say\u2026", "a quarter of buyers\u2026", "1 in 3 owners\u2026"), rather than one person's own words about their own experience? A person giving their own number ("My bill dropped 40%") is their own words. The quote may be in any language.`;
@@ -74800,26 +75001,26 @@ async function sameMessageRead(ideas, ask) {
74800
75001
  }
74801
75002
 
74802
75003
  // src/commands/creatives/ideas-workspace.ts
74803
- var IDEAS_DIR = path20.join("src", "creatives", "_ideas");
74804
- var INSIGHTS_DIR = path20.join("src", "content", "insights");
75004
+ var IDEAS_DIR = path22.join("src", "creatives", "_ideas");
75005
+ var INSIGHTS_DIR = path22.join("src", "content", "insights");
74805
75006
  async function markdownFiles(dir, recursive = false) {
74806
75007
  const entries = await readdir8(dir, { recursive }).catch(() => []);
74807
75008
  return entries.filter((f) => f.endsWith(".md")).sort();
74808
75009
  }
74809
75010
  var idOf2 = (file) => file.replace(/\\/g, "/").replace(/\.md$/, "");
74810
75011
  function workspaceIdeasJudge(root) {
74811
- const cacheDir = path20.join(root, "canvas", ".cache");
75012
+ const cacheDir = path22.join(root, "canvas", ".cache");
74812
75013
  const spent = [];
74813
75014
  return {
74814
- ask: rememberedJev(path20.join(cacheDir, "ideas-answers.json"), (u) => spent.push(u)),
75015
+ ask: rememberedJev(path22.join(cacheDir, "ideas-answers.json"), (u) => spent.push(u)),
74815
75016
  done: () => holdUnbilledSpend(cacheDir, spent.splice(0)).catch(() => void 0)
74816
75017
  };
74817
75018
  }
74818
75019
  async function readInsights(root, opts = {}) {
74819
- const dir = path20.join(root, INSIGHTS_DIR);
75020
+ const dir = path22.join(root, INSIGHTS_DIR);
74820
75021
  const insights = [];
74821
75022
  for (const file of await markdownFiles(dir, true)) {
74822
- const insight = parseInsight(idOf2(file), await readFile15(path20.join(dir, file), "utf8").catch(() => ""));
75023
+ const insight = parseInsight(idOf2(file), await readFile17(path22.join(dir, file), "utf8").catch(() => ""));
74823
75024
  if (insight) insights.push(insight);
74824
75025
  }
74825
75026
  if (opts.ask) return withStatisticsRead(insights, opts.ask);
@@ -74829,41 +75030,41 @@ async function readInsights(root, opts = {}) {
74829
75030
  return read;
74830
75031
  }
74831
75032
  async function readIdeas(root) {
74832
- const dir = path20.join(root, IDEAS_DIR);
75033
+ const dir = path22.join(root, IDEAS_DIR);
74833
75034
  const ideas = [];
74834
75035
  const unreadable = [];
74835
75036
  for (const file of await markdownFiles(dir)) {
74836
75037
  const id = idOf2(file);
74837
- const parsed = parseIdea(id, await readFile15(path20.join(dir, file), "utf8").catch(() => ""));
75038
+ const parsed = parseIdea(id, await readFile17(path22.join(dir, file), "utf8").catch(() => ""));
74838
75039
  if (parsed.idea) ideas.push(parsed.idea);
74839
75040
  else unreadable.push({ id, problems: parsed.problems });
74840
75041
  }
74841
75042
  return { ideas, unreadable };
74842
75043
  }
74843
75044
  async function readPersonaIds(root) {
74844
- return (await markdownFiles(path20.join(root, "src", "content", "personas"), true)).map(idOf2);
75045
+ return (await markdownFiles(path22.join(root, "src", "content", "personas"), true)).map(idOf2);
74845
75046
  }
74846
75047
  var MAX_KNOWLEDGE_CHARS = 2e5;
74847
75048
  async function readKnowledgeCorpus(root) {
74848
75049
  const parts = [];
74849
- const company = await readFile15(path20.join(root, "COMPANY.md"), "utf8").catch(() => "");
75050
+ const company = await readFile17(path22.join(root, "COMPANY.md"), "utf8").catch(() => "");
74850
75051
  if (company) parts.push(company);
74851
- const content = path20.join(root, "src", "content");
74852
- const insights = path20.relative(content, path20.join(root, INSIGHTS_DIR));
75052
+ const content = path22.join(root, "src", "content");
75053
+ const insights = path22.relative(content, path22.join(root, INSIGHTS_DIR));
74853
75054
  const files = (await markdownFiles(content, true)).filter((f) => !f.replace(/\\/g, "/").startsWith(`${insights}/`));
74854
75055
  for (const file of files) {
74855
- parts.push(await readFile15(path20.join(content, file), "utf8").catch(() => ""));
75056
+ parts.push(await readFile17(path22.join(content, file), "utf8").catch(() => ""));
74856
75057
  if (parts.join("").length > MAX_KNOWLEDGE_CHARS) break;
74857
75058
  }
74858
75059
  return parts.join("\n").slice(0, MAX_KNOWLEDGE_CHARS);
74859
75060
  }
74860
75061
  async function readCreativeRecords(root) {
74861
- const dir = path20.join(root, "src", "creatives");
75062
+ const dir = path22.join(root, "src", "creatives");
74862
75063
  const entries = await readdir8(dir, { withFileTypes: true }).catch(() => []);
74863
75064
  const records = [];
74864
75065
  for (const entry of entries) {
74865
75066
  if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
74866
- const raw = await readFile15(path20.join(dir, entry.name, "_definition.md"), "utf8").catch(() => null);
75067
+ const raw = await readFile17(path22.join(dir, entry.name, "_definition.md"), "utf8").catch(() => null);
74867
75068
  if (raw === null) continue;
74868
75069
  const fm = frontmatterOf(raw) ?? {};
74869
75070
  const pick2 = (key) => typeof fm[key] === "string" && fm[key] ? { [key]: fm[key] } : {};
@@ -75101,7 +75302,7 @@ function upgradedDescription(text4, old, plan) {
75101
75302
  }
75102
75303
 
75103
75304
  // src/commands/canvas/style-projection.ts
75104
- import { readFile as readFile16, writeFile as writeFile10 } from "fs/promises";
75305
+ import { readFile as readFile18, writeFile as writeFile13 } from "fs/promises";
75105
75306
  function findBlueprintProjection(canvas) {
75106
75307
  if (!canvas || typeof canvas !== "object") return null;
75107
75308
  const nodes = canvas.nodes;
@@ -75129,10 +75330,10 @@ function renderStyleProjectionFromValue(blueprint) {
75129
75330
  async function syncStyleProjection(canvas, log) {
75130
75331
  const pair = findBlueprintProjection(canvas);
75131
75332
  if (!pair) return "not_applicable";
75132
- const rendered = renderStyleProjection(await readFile16(pair.promptPath, "utf8"));
75133
- const current = await readFile16(pair.stylePath, "utf8").catch(() => null);
75333
+ const rendered = renderStyleProjection(await readFile18(pair.promptPath, "utf8"));
75334
+ const current = await readFile18(pair.stylePath, "utf8").catch(() => null);
75134
75335
  if (current === rendered) return "up_to_date";
75135
- await writeFile10(pair.stylePath, rendered, "utf8");
75336
+ await writeFile13(pair.stylePath, rendered, "utf8");
75136
75337
  log(
75137
75338
  "[style] prompt.style.json regenerated from prompt.json \u2014 every frame's shared ad spec changed; affected image frames will re-bill on the next run"
75138
75339
  );
@@ -75166,7 +75367,7 @@ async function staticSpecGate(canvas, canvasDir) {
75166
75367
  const parts = await readRecordParts(root);
75167
75368
  const records = parts.map((p) => p.text).join("\n");
75168
75369
  const ours = (await readInsights(root)).filter(sourcesClaims);
75169
- const cacheDir = path21.join(findWorkspaceRoot(canvasDir) ?? root, "canvas", ".cache");
75370
+ const cacheDir = path23.join(findWorkspaceRoot(canvasDir) ?? root, "canvas", ".cache");
75170
75371
  const spent = [];
75171
75372
  const claims = spec.native ? { problems: [], unheld: [] } : await claimProblems(
75172
75373
  spec.copy.map((l) => l.text),
@@ -75217,14 +75418,14 @@ function oneRefusal(gates) {
75217
75418
  }
75218
75419
  async function readRecordParts(root) {
75219
75420
  const parts = [
75220
- { source: "COMPANY.md", text: await readFile17(path21.join(root, "COMPANY.md"), "utf8").catch(() => "") }
75421
+ { source: "COMPANY.md", text: await readFile19(path23.join(root, "COMPANY.md"), "utf8").catch(() => "") }
75221
75422
  ];
75222
- const content = path21.join(root, "src", "content");
75423
+ const content = path23.join(root, "src", "content");
75223
75424
  const files = (await readdir9(content, { recursive: true }).catch(() => [])).filter(
75224
75425
  (f) => /\.(md|mdx|json|ya?ml)$/.test(f) && !f.replace(/\\/g, "/").startsWith("insights/")
75225
75426
  );
75226
75427
  for (const file of files.slice(0, 400)) {
75227
- parts.push({ source: file, text: await readFile17(path21.join(content, file), "utf8").catch(() => "") });
75428
+ parts.push({ source: file, text: await readFile19(path23.join(content, file), "utf8").catch(() => "") });
75228
75429
  }
75229
75430
  const quotes2 = (await readInsights(root)).filter(sourcesClaims).map((insight) => insight.quote);
75230
75431
  if (quotes2.length > 0) parts.push({ source: "customers' words", text: quotes2.join("\n") });
@@ -75250,14 +75451,14 @@ var CanvasPrepareError = class extends Error {
75250
75451
  }
75251
75452
  };
75252
75453
  async function prepareCanvas(canvasPath, opts = {}) {
75253
- const filePath = path21.resolve(canvasPath);
75254
- const canvasDir = path21.dirname(filePath);
75454
+ const filePath = path23.resolve(canvasPath);
75455
+ const canvasDir = path23.dirname(filePath);
75255
75456
  const notes = [];
75256
75457
  const log = (line) => {
75257
75458
  notes.push(line);
75258
75459
  opts.log?.(line);
75259
75460
  };
75260
- const raw = await readFile17(filePath, "utf8");
75461
+ const raw = await readFile19(filePath, "utf8");
75261
75462
  let parsed;
75262
75463
  try {
75263
75464
  parsed = JSON.parse(raw);
@@ -75276,7 +75477,7 @@ async function prepareCanvas(canvasPath, opts = {}) {
75276
75477
  onDisk = promoted.canvas;
75277
75478
  text4 = `${JSON.stringify(onDisk, null, 2)}
75278
75479
  `;
75279
- await writeFile11(filePath, text4, "utf8");
75480
+ await writeFile14(filePath, text4, "utf8");
75280
75481
  rewrites.push(...promoted.rewrites);
75281
75482
  }
75282
75483
  }
@@ -75289,7 +75490,7 @@ async function prepareCanvas(canvasPath, opts = {}) {
75289
75490
  let staticCopy;
75290
75491
  try {
75291
75492
  if (write) style = await syncStyleProjection(canvas, log);
75292
- const cacheDir = path21.join(findWorkspaceRoot(canvasDir) ?? canvasDir, "canvas", ".cache");
75493
+ const cacheDir = path23.join(findWorkspaceRoot(canvasDir) ?? canvasDir, "canvas", ".cache");
75293
75494
  const writing = [];
75294
75495
  const native = await syncNativeGraph(canvas, log, interfaceWordsWriter(cacheDir, (u) => writing.push(u)));
75295
75496
  await holdUnbilledSpend(cacheDir, writing);
@@ -75300,19 +75501,26 @@ async function prepareCanvas(canvasPath, opts = {}) {
75300
75501
  } catch (e) {
75301
75502
  throw new CanvasPrepareError("style_projection", `prompt.json is not readable or not a valid ad:
75302
75503
  ${adSpecErrorText(e)}`, {
75303
- fix: `Fix ${path21.join(canvasDir, "prompt.json")} so it parses as JSON (and, for a static ad, matches the ad spec the message names), then re-run.`
75504
+ fix: `Fix ${path23.join(canvasDir, "prompt.json")} so it parses as JSON (and, for a static ad, matches the ad spec the message names), then re-run.`
75304
75505
  });
75305
75506
  }
75306
75507
  const gate = opts.gate && isStaticBriefCanvas(canvas) ? await staticSpecGate(canvas, canvasDir) : null;
75307
75508
  if (gate) throw new CanvasPrepareError(gate.code, gate.problems.join(" "), { fix: gate.fix });
75308
75509
  let scenes;
75309
75510
  try {
75310
- scenes = await syncSceneNodeParams(canvas, filePath, log);
75511
+ const speechCache = path23.join(findWorkspaceRoot(canvasDir) ?? canvasDir, "canvas", ".cache");
75512
+ const speechSpent = [];
75513
+ const readers = write ? {
75514
+ ask: cachedJev(speechCache, (questions) => askJevQuestions(questions, (u) => speechSpent.push(u))),
75515
+ genders: voiceGenderModel(speechCache, (u) => speechSpent.push(u))
75516
+ } : void 0;
75517
+ scenes = await syncSceneNodeParams(canvas, filePath, log, readers);
75518
+ await holdUnbilledSpend(speechCache, speechSpent).catch(() => void 0);
75311
75519
  } catch (e) {
75312
75520
  throw new CanvasPrepareError(
75313
75521
  "scene_projection",
75314
75522
  `a scenes/*.json file is not readable/valid or its rebuild failed: ${errorMessage(e)}`,
75315
- { fix: `Fix the scene files in ${path21.join(canvasDir, "scenes")} so each parses as JSON, then re-run.` }
75523
+ { fix: `Fix the scene files in ${path23.join(canvasDir, "scenes")} so each parses as JSON, then re-run.` }
75316
75524
  );
75317
75525
  }
75318
75526
  const projected = withFilePaths(canvas, onDisk, canvasDir);
@@ -75320,7 +75528,7 @@ ${adSpecErrorText(e)}`, {
75320
75528
  if (write && projected !== null && JSON.stringify(projected) !== JSON.stringify(onDisk)) {
75321
75529
  text4 = `${JSON.stringify(projected, null, 2)}
75322
75530
  `;
75323
- await writeFile11(filePath, text4, "utf8");
75531
+ await writeFile14(filePath, text4, "utf8");
75324
75532
  written = true;
75325
75533
  }
75326
75534
  if (opts.requireSuppliedAssets) {
@@ -75348,7 +75556,7 @@ ${adSpecErrorText(e)}`, {
75348
75556
  };
75349
75557
  }
75350
75558
  function healInMemory(filePath, canvas) {
75351
- const canvasDir = path21.dirname(filePath);
75559
+ const canvasDir = path23.dirname(filePath);
75352
75560
  const workspaceRoot = findWorkspaceRoot(canvasDir);
75353
75561
  if (!workspaceRoot) return { canvas, rewrites: [], text: null };
75354
75562
  return { ...normalizeCanvasPaths(canvas, canvasDir, workspaceRoot), text: null };
@@ -75369,10 +75577,10 @@ function withFilePaths(projected, onDisk, canvasDir) {
75369
75577
  if (!key || !node.params || typeof node.params[key] !== "string") continue;
75370
75578
  const value2 = node.params[key];
75371
75579
  const original = byId.get(node.id)?.params?.[key];
75372
- if (typeof original === "string" && path21.resolve(canvasDir, original) === path21.resolve(canvasDir, value2)) {
75580
+ if (typeof original === "string" && path23.resolve(canvasDir, original) === path23.resolve(canvasDir, value2)) {
75373
75581
  node.params[key] = original;
75374
- } else if (path21.isAbsolute(value2)) {
75375
- const rel = path21.relative(canvasDir, value2).split(path21.sep).join("/");
75582
+ } else if (path23.isAbsolute(value2)) {
75583
+ const rel = path23.relative(canvasDir, value2).split(path23.sep).join("/");
75376
75584
  node.params[key] = rel.startsWith(".") ? rel : `./${rel}`;
75377
75585
  }
75378
75586
  }
@@ -75396,7 +75604,7 @@ var critiqueCommand = defineCommand138({
75396
75604
  },
75397
75605
  args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
75398
75606
  async run({ args: args8 }) {
75399
- const filePath = path22.resolve(String(args8.file));
75607
+ const filePath = path24.resolve(String(args8.file));
75400
75608
  let prepared;
75401
75609
  try {
75402
75610
  prepared = await prepareCanvas(filePath, { log: (line) => process.stderr.write(`${line}
@@ -75424,8 +75632,8 @@ var critiqueCommand = defineCommand138({
75424
75632
 
75425
75633
  // src/commands/canvas/inspect.ts
75426
75634
  import { execFile } from "child_process";
75427
- import { readdir as readdir10, readFile as readFile18, stat as stat2 } from "fs/promises";
75428
- import path23 from "path";
75635
+ import { readdir as readdir10, readFile as readFile20, stat as stat2 } from "fs/promises";
75636
+ import path25 from "path";
75429
75637
  import { promisify } from "util";
75430
75638
  import { defineCommand as defineCommand139 } from "citty";
75431
75639
 
@@ -75457,12 +75665,12 @@ var inspectCommand = defineCommand139({
75457
75665
  },
75458
75666
  async run({ args: args8 }) {
75459
75667
  const cwd = process.cwd();
75460
- const outputsDir = args8["outputs-dir"] ? path23.resolve(String(args8["outputs-dir"])) : path23.join(findWorkspaceRoot(cwd) ?? cwd, "canvas");
75668
+ const outputsDir = args8["outputs-dir"] ? path25.resolve(String(args8["outputs-dir"])) : path25.join(findWorkspaceRoot(cwd) ?? cwd, "canvas");
75461
75669
  const runArg = String(args8.run);
75462
75670
  const runDir = await localRunDir(runArg, outputsDir);
75463
75671
  if (!runDir) {
75464
- if (path23.isAbsolute(runArg)) fail3("not_found", `${runArg} is not a run directory`);
75465
- await inspectFromHistory(runArg, path23.join(outputsDir, runArg));
75672
+ if (path25.isAbsolute(runArg)) fail3("not_found", `${runArg} is not a run directory`);
75673
+ await inspectFromHistory(runArg, path25.join(outputsDir, runArg));
75466
75674
  return;
75467
75675
  }
75468
75676
  const manifest = await loadManifest(runDir);
@@ -75475,7 +75683,7 @@ var inspectCommand = defineCommand139({
75475
75683
  const summary = {
75476
75684
  ok: true,
75477
75685
  source: "local",
75478
- run_id: manifest.run_id ?? path23.basename(runDir),
75686
+ run_id: manifest.run_id ?? path25.basename(runDir),
75479
75687
  run_dir: runDir,
75480
75688
  stats: manifest.stats ?? null,
75481
75689
  output: manifest.output ?? null,
@@ -75488,7 +75696,7 @@ var inspectCommand = defineCommand139({
75488
75696
  }
75489
75697
  });
75490
75698
  async function localRunDir(run3, outputsDir) {
75491
- const candidate = path23.isAbsolute(run3) ? run3 : path23.join(outputsDir, run3);
75699
+ const candidate = path25.isAbsolute(run3) ? run3 : path25.join(outputsDir, run3);
75492
75700
  const s = await stat2(candidate).catch(() => null);
75493
75701
  return s?.isDirectory() ? candidate : null;
75494
75702
  }
@@ -75543,9 +75751,9 @@ function fail3(code, message) {
75543
75751
  process.exit(2);
75544
75752
  }
75545
75753
  async function loadManifest(runDir) {
75546
- const manifestPath = path23.join(runDir, "manifest.json");
75754
+ const manifestPath = path25.join(runDir, "manifest.json");
75547
75755
  try {
75548
- const raw = await readFile18(manifestPath, "utf-8");
75756
+ const raw = await readFile20(manifestPath, "utf-8");
75549
75757
  return JSON.parse(raw);
75550
75758
  } catch {
75551
75759
  return {};
@@ -75555,7 +75763,7 @@ async function listRunFiles(runDir) {
75555
75763
  const out = [];
75556
75764
  const names = await readdir10(runDir);
75557
75765
  for (const name of names) {
75558
- const abs = path23.join(runDir, name);
75766
+ const abs = path25.join(runDir, name);
75559
75767
  const s = await stat2(abs).catch(() => null);
75560
75768
  if (!s?.isFile()) continue;
75561
75769
  out.push({ name, path: abs, size: s.size });
@@ -75603,18 +75811,18 @@ async function probeDuration(filePath) {
75603
75811
  import { defineCommand as defineCommand143 } from "citty";
75604
75812
 
75605
75813
  // src/commands/canvas/run.ts
75606
- import path39 from "path";
75814
+ import path41 from "path";
75607
75815
  import { defineCommand as defineCommand142 } from "citty";
75608
75816
 
75609
75817
  // src/commands/canvas/definition-status.ts
75610
- import { readFile as readFile19, writeFile as writeFile12 } from "fs/promises";
75611
- import path24 from "path";
75818
+ import { readFile as readFile21, writeFile as writeFile15 } from "fs/promises";
75819
+ import path26 from "path";
75612
75820
  import { parseDocument } from "yaml";
75613
75821
  var ORDER = { draft: 0, rendered: 1, published: 2 };
75614
75822
  async function advanceDefinitionStatus(definitionPath, to) {
75615
75823
  let raw;
75616
75824
  try {
75617
- raw = await readFile19(definitionPath, "utf8");
75825
+ raw = await readFile21(definitionPath, "utf8");
75618
75826
  } catch {
75619
75827
  return "missing";
75620
75828
  }
@@ -75632,17 +75840,17 @@ async function advanceDefinitionStatus(definitionPath, to) {
75632
75840
  const nextFrontmatter = statusLine.test(frontmatter) ? frontmatter.replace(statusLine, `status: ${to}`) : `${frontmatter}status: ${to}
75633
75841
  `;
75634
75842
  if (nextFrontmatter === frontmatter) return "unchanged";
75635
- await writeFile12(definitionPath, `---
75843
+ await writeFile15(definitionPath, `---
75636
75844
  ${nextFrontmatter}---${raw.slice(match[0].length)}`, "utf8");
75637
75845
  return "updated";
75638
75846
  }
75639
75847
  function definitionPathFor(workspaceRoot, slug) {
75640
- return path24.join(workspaceRoot, "src", "creatives", slug, "_definition.md");
75848
+ return path26.join(workspaceRoot, "src", "creatives", slug, "_definition.md");
75641
75849
  }
75642
75850
 
75643
75851
  // src/commands/canvas/output-ratio.ts
75644
- import { readFile as readFile20 } from "fs/promises";
75645
- import path25 from "path";
75852
+ import { readFile as readFile22 } from "fs/promises";
75853
+ import path27 from "path";
75646
75854
  function definitionFormats2(markdown) {
75647
75855
  const front = markdown.match(/^---\n([\s\S]*?)\n---/)?.[1];
75648
75856
  if (!front) return [];
@@ -75717,21 +75925,21 @@ function outputRatioWarnings(declared, produced) {
75717
75925
  });
75718
75926
  }
75719
75927
  async function outputRatioWarningsFor(canvas, canvasPath) {
75720
- const definition = await readFile20(path25.join(path25.dirname(canvasPath), "_definition.md"), "utf8").catch(() => null);
75928
+ const definition = await readFile22(path27.join(path27.dirname(canvasPath), "_definition.md"), "utf8").catch(() => null);
75721
75929
  if (!definition) return [];
75722
75930
  return outputRatioWarnings(definitionFormats2(definition), await producedRatios(canvas));
75723
75931
  }
75724
75932
 
75725
75933
  // src/commands/canvas/render-state.ts
75726
- import { mkdir as mkdir8, readdir as readdir11, readFile as readFile21, rm as rm2, writeFile as writeFile13 } from "fs/promises";
75727
- import path26 from "path";
75934
+ import { mkdir as mkdir10, readdir as readdir11, readFile as readFile23, rm as rm2, writeFile as writeFile16 } from "fs/promises";
75935
+ import path28 from "path";
75728
75936
  var RENDER_STATE_DIR = ".rendered";
75729
75937
  function statePath(canvasRoot, slug) {
75730
- return path26.join(canvasRoot, RENDER_STATE_DIR, `${slug}.json`);
75938
+ return path28.join(canvasRoot, RENDER_STATE_DIR, `${slug}.json`);
75731
75939
  }
75732
75940
  async function readRenderState(canvasRoot, slug) {
75733
75941
  try {
75734
- const parsed = JSON.parse(await readFile21(statePath(canvasRoot, slug), "utf8"));
75942
+ const parsed = JSON.parse(await readFile23(statePath(canvasRoot, slug), "utf8"));
75735
75943
  return parsed && typeof parsed === "object" ? parsed : {};
75736
75944
  } catch {
75737
75945
  return {};
@@ -75743,8 +75951,8 @@ async function latestVersion(canvasRoot, slug) {
75743
75951
  async function updateRenderState(canvasRoot, slug, patch) {
75744
75952
  try {
75745
75953
  const next = { ...await readRenderState(canvasRoot, slug), ...patch };
75746
- await mkdir8(path26.join(canvasRoot, RENDER_STATE_DIR), { recursive: true });
75747
- await writeFile13(statePath(canvasRoot, slug), `${JSON.stringify(next, null, 2)}
75954
+ await mkdir10(path28.join(canvasRoot, RENDER_STATE_DIR), { recursive: true });
75955
+ await writeFile16(statePath(canvasRoot, slug), `${JSON.stringify(next, null, 2)}
75748
75956
  `, "utf8");
75749
75957
  } catch {
75750
75958
  }
@@ -75775,8 +75983,8 @@ function isRenderStale(state, currentSourceSha) {
75775
75983
  return state.attempted?.sourceSha !== currentSourceSha;
75776
75984
  }
75777
75985
  async function staleCreatives(workspaceRoot) {
75778
- const creativesDir = path26.join(workspaceRoot, "src", "creatives");
75779
- const canvasRoot = path26.join(workspaceRoot, "canvas");
75986
+ const creativesDir = path28.join(workspaceRoot, "src", "creatives");
75987
+ const canvasRoot = path28.join(workspaceRoot, "canvas");
75780
75988
  let slugs;
75781
75989
  try {
75782
75990
  slugs = (await readdir11(creativesDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -75787,7 +75995,7 @@ async function staleCreatives(workspaceRoot) {
75787
75995
  for (const slug of slugs.sort()) {
75788
75996
  const state = await readRenderState(canvasRoot, slug);
75789
75997
  if (!state.rendered) continue;
75790
- const canvasPath = path26.join(creativesDir, slug, `${slug}.canvas.json`);
75998
+ const canvasPath = path28.join(creativesDir, slug, `${slug}.canvas.json`);
75791
75999
  let prepared;
75792
76000
  try {
75793
76001
  prepared = await prepareCanvas(canvasPath, { write: false });
@@ -75799,7 +76007,7 @@ async function staleCreatives(workspaceRoot) {
75799
76007
  if (!current || !isRenderStale(state, current)) continue;
75800
76008
  stale.push({
75801
76009
  slug,
75802
- canvasPath: path26.relative(workspaceRoot, canvasPath).split(path26.sep).join("/"),
76010
+ canvasPath: path28.relative(workspaceRoot, canvasPath).split(path28.sep).join("/"),
75803
76011
  renderedRunId: state.rendered.runId,
75804
76012
  sourceSha: current
75805
76013
  });
@@ -75807,7 +76015,7 @@ async function staleCreatives(workspaceRoot) {
75807
76015
  return stale;
75808
76016
  }
75809
76017
  async function clearLegacyDirtyMarker(canvasRoot, slug) {
75810
- await rm2(path26.join(canvasRoot, ".dirty", `${slug}.json`), { force: true }).catch(() => void 0);
76018
+ await rm2(path28.join(canvasRoot, ".dirty", `${slug}.json`), { force: true }).catch(() => void 0);
75811
76019
  }
75812
76020
 
75813
76021
  // src/commands/canvas/node-preview.ts
@@ -75863,13 +76071,13 @@ ${body}` : header || body || compactJson(record);
75863
76071
  }
75864
76072
 
75865
76073
  // src/commands/canvas/run-record.ts
75866
- import path27 from "path";
76074
+ import path29 from "path";
75867
76075
  var MAX_RUN_NODES = 200;
75868
76076
  var MAX_OUTPUTS_PER_NODE = 10;
75869
76077
  var MAX_FINAL_OUTPUTS = 10;
75870
76078
  var MAX_CREATIVE_SLUG_LENGTH = 100;
75871
76079
  function creativeSlugFromCanvasPath(filePath) {
75872
- const normalized2 = filePath.split(path27.sep).join("/");
76080
+ const normalized2 = filePath.split(path29.sep).join("/");
75873
76081
  const match = normalized2.match(/(?:^|\/)src\/creatives\/([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\//);
75874
76082
  const slug = match?.[1] ?? null;
75875
76083
  return slug && slug.length <= MAX_CREATIVE_SLUG_LENGTH ? slug : null;
@@ -76178,7 +76386,7 @@ var RunRecordPoster = class {
76178
76386
 
76179
76387
  // src/commands/canvas/run-retention.ts
76180
76388
  import { rm as rm3 } from "fs/promises";
76181
- import path28 from "path";
76389
+ import path30 from "path";
76182
76390
  function runDirsToPrune(entries, keep, currentRunId) {
76183
76391
  const runs = entries.filter((e) => /^r_[0-9A-Za-z]+$/.test(e) && e !== currentRunId).sort();
76184
76392
  if (keep <= 0) return runs;
@@ -76195,7 +76403,7 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
76195
76403
  const toPrune = runDirsToPrune(entries, keep, currentRunId);
76196
76404
  if (toPrune.length === 0) return;
76197
76405
  for (const dir of toPrune) {
76198
- await rm3(path28.join(outputsDir, dir), { recursive: true, force: true }).catch(
76406
+ await rm3(path30.join(outputsDir, dir), { recursive: true, force: true }).catch(
76199
76407
  (e) => log(`[prune ] could not remove ${dir}: ${e.message}`)
76200
76408
  );
76201
76409
  }
@@ -76205,15 +76413,15 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
76205
76413
  // src/commands/canvas/run-resume.ts
76206
76414
  import { randomUUID as randomUUID2 } from "crypto";
76207
76415
  import { rmSync } from "fs";
76208
- import { mkdir as mkdir9, readFile as readFile22, rm as rm4, writeFile as writeFile14 } from "fs/promises";
76209
- import path29 from "path";
76416
+ import { mkdir as mkdir11, readFile as readFile24, rm as rm4, writeFile as writeFile17 } from "fs/promises";
76417
+ import path31 from "path";
76210
76418
  function markerKey(canvasPath) {
76211
76419
  const slug = creativeSlugFromCanvasPath(canvasPath);
76212
- const identity = slug ?? path29.relative(process.cwd(), path29.resolve(canvasPath));
76420
+ const identity = slug ?? path31.relative(process.cwd(), path31.resolve(canvasPath));
76213
76421
  return sha256Hex(Buffer.from(identity)).slice(0, 32);
76214
76422
  }
76215
76423
  function markerFile(outputsDir, canvasPath) {
76216
- return path29.join(outputsDir, ".inflight", `${markerKey(canvasPath)}.json`);
76424
+ return path31.join(outputsDir, ".inflight", `${markerKey(canvasPath)}.json`);
76217
76425
  }
76218
76426
  function classifyRemoteRun(run3) {
76219
76427
  if (run3.interrupted === true || run3.status === "awaiting_approval") return "adopt";
@@ -76266,7 +76474,7 @@ async function resolveRunId(opts) {
76266
76474
  }
76267
76475
  async function readMarker(outputsDir, canvasPath) {
76268
76476
  try {
76269
- const parsed = JSON.parse(await readFile22(markerFile(outputsDir, canvasPath), "utf8"));
76477
+ const parsed = JSON.parse(await readFile24(markerFile(outputsDir, canvasPath), "utf8"));
76270
76478
  if (typeof parsed.runId !== "string" || parsed.runId.length === 0) return null;
76271
76479
  const num4 = (value2) => typeof value2 === "number" && Number.isFinite(value2) ? value2 : void 0;
76272
76480
  return {
@@ -76285,20 +76493,20 @@ async function markRunInFlight(outputsDir, canvasPath, runId, sourceSha) {
76285
76493
  const file = markerFile(outputsDir, canvasPath);
76286
76494
  const token = randomUUID2();
76287
76495
  const startedAt = Date.now();
76288
- const write = (heartbeatAt) => writeFile14(
76496
+ const write = (heartbeatAt) => writeFile17(
76289
76497
  file,
76290
76498
  JSON.stringify({
76291
76499
  runId,
76292
76500
  pid: process.pid,
76293
76501
  token,
76294
- canvasPath: path29.resolve(canvasPath),
76502
+ canvasPath: path31.resolve(canvasPath),
76295
76503
  startedAt,
76296
76504
  heartbeatAt,
76297
76505
  ...sourceSha ? { sourceSha } : {}
76298
76506
  })
76299
76507
  );
76300
76508
  try {
76301
- await mkdir9(path29.dirname(file), { recursive: true });
76509
+ await mkdir11(path31.dirname(file), { recursive: true });
76302
76510
  await write(startedAt);
76303
76511
  } catch {
76304
76512
  }
@@ -76309,13 +76517,13 @@ async function markRunInFlight(outputsDir, canvasPath, runId, sourceSha) {
76309
76517
  return { token, stop: () => clearInterval(timer) };
76310
76518
  }
76311
76519
  function busyFile(outputsDir, canvasPath) {
76312
- return path29.join(outputsDir, ".inflight", `${markerKey(canvasPath)}.busy.json`);
76520
+ return path31.join(outputsDir, ".inflight", `${markerKey(canvasPath)}.busy.json`);
76313
76521
  }
76314
76522
  async function markCanvasBusy(outputsDir, canvasPath, runId, pid = process.pid) {
76315
76523
  const file = busyFile(outputsDir, canvasPath);
76316
- const write = () => writeFile14(file, JSON.stringify({ runId, pid, heartbeatAt: Date.now() }));
76524
+ const write = () => writeFile17(file, JSON.stringify({ runId, pid, heartbeatAt: Date.now() }));
76317
76525
  try {
76318
- await mkdir9(path29.dirname(file), { recursive: true });
76526
+ await mkdir11(path31.dirname(file), { recursive: true });
76319
76527
  await write();
76320
76528
  } catch {
76321
76529
  }
@@ -76335,7 +76543,7 @@ async function markCanvasBusy(outputsDir, canvasPath, runId, pid = process.pid)
76335
76543
  }
76336
76544
  async function canvasBusyElsewhere(outputsDir, canvasPath, now = Date.now()) {
76337
76545
  try {
76338
- const held = JSON.parse(await readFile22(busyFile(outputsDir, canvasPath), "utf8"));
76546
+ const held = JSON.parse(await readFile24(busyFile(outputsDir, canvasPath), "utf8"));
76339
76547
  if (typeof held.runId !== "string" || held.pid === void 0 || held.pid === process.pid) return null;
76340
76548
  return isLiveRender(held, now) ? { runId: held.runId, pid: held.pid } : null;
76341
76549
  } catch {
@@ -76355,8 +76563,8 @@ async function clearRunMarker(outputsDir, canvasPath, token) {
76355
76563
 
76356
76564
  // src/commands/canvas/run-snapshot.ts
76357
76565
  import { existsSync as existsSync5 } from "fs";
76358
- import { mkdir as mkdir10, readdir as readdir12, readFile as readFile23, stat as stat3, writeFile as writeFile15 } from "fs/promises";
76359
- import path30 from "path";
76566
+ import { mkdir as mkdir12, readdir as readdir12, readFile as readFile25, stat as stat3, writeFile as writeFile18 } from "fs/promises";
76567
+ import path32 from "path";
76360
76568
  var SNAPSHOT_SCHEMA = "baker-canvas-snapshot/1";
76361
76569
  var MAX_SNAPSHOT_FILE_BYTES = 32 * 1024 * 1024;
76362
76570
  var EXT_TO_MIME = {
@@ -76383,15 +76591,15 @@ var EXT_TO_MIME = {
76383
76591
  woff2: "font/woff2"
76384
76592
  };
76385
76593
  function mimeForFile(filePath) {
76386
- const ext = path30.extname(filePath).slice(1).toLowerCase();
76594
+ const ext = path32.extname(filePath).slice(1).toLowerCase();
76387
76595
  return EXT_TO_MIME[ext] ?? "application/octet-stream";
76388
76596
  }
76389
76597
  function toPosix2(p) {
76390
- return p.split(path30.sep).join("/");
76598
+ return p.split(path32.sep).join("/");
76391
76599
  }
76392
76600
  function isInside2(dir, target2) {
76393
- const rel = path30.relative(dir, target2);
76394
- return !rel.startsWith("..") && !path30.isAbsolute(rel);
76601
+ const rel = path32.relative(dir, target2);
76602
+ return !rel.startsWith("..") && !path32.isAbsolute(rel);
76395
76603
  }
76396
76604
  function localPathRefsFromCanvas(parsed) {
76397
76605
  const nodes = parsed?.nodes;
@@ -76414,20 +76622,20 @@ var FINGERPRINTED_FILES = ["prompt.json", "_definition.md"];
76414
76622
  async function sourceRefsToSnapshot(canvasDir, parsed) {
76415
76623
  const refs = new Set(localPathRefsFromCanvas(parsed));
76416
76624
  for (const sceneFile of await listSceneFiles(canvasDir)) {
76417
- refs.add(toPosix2(path30.relative(canvasDir, sceneFile)));
76625
+ refs.add(toPosix2(path32.relative(canvasDir, sceneFile)));
76418
76626
  }
76419
76627
  refs.add(REBUILD_FILE);
76420
76628
  for (const fingerprinted of FINGERPRINTED_FILES) {
76421
- if (existsSync5(path30.join(canvasDir, fingerprinted))) refs.add(fingerprinted);
76629
+ if (existsSync5(path32.join(canvasDir, fingerprinted))) refs.add(fingerprinted);
76422
76630
  }
76423
76631
  return [...refs];
76424
76632
  }
76425
76633
  async function uploadRunSnapshot(client, opts) {
76426
76634
  try {
76427
- const canvasDir = path30.dirname(opts.canvasPath);
76635
+ const canvasDir = path32.dirname(opts.canvasPath);
76428
76636
  const canvasBytes = Buffer.from(opts.raw);
76429
76637
  const canvasEntry = {
76430
- path: path30.basename(opts.canvasPath),
76638
+ path: path32.basename(opts.canvasPath),
76431
76639
  bytes: canvasBytes,
76432
76640
  sha256: sha256Hex(canvasBytes),
76433
76641
  mime: "application/json"
@@ -76435,10 +76643,10 @@ async function uploadRunSnapshot(client, opts) {
76435
76643
  const files = [];
76436
76644
  const skipped = [];
76437
76645
  for (const refPath of await sourceRefsToSnapshot(canvasDir, opts.parsed)) {
76438
- const abs = path30.isAbsolute(refPath) ? refPath : path30.resolve(canvasDir, refPath);
76646
+ const abs = path32.isAbsolute(refPath) ? refPath : path32.resolve(canvasDir, refPath);
76439
76647
  if (!isInside2(canvasDir, abs)) {
76440
76648
  skipped.push({
76441
- path: toPosix2(path30.relative(canvasDir, abs)),
76649
+ path: toPosix2(path32.relative(canvasDir, abs)),
76442
76650
  reason: "outside the creative folder \u2014 read from the workspace on rerun"
76443
76651
  });
76444
76652
  continue;
@@ -76447,18 +76655,18 @@ async function uploadRunSnapshot(client, opts) {
76447
76655
  try {
76448
76656
  st = await stat3(abs);
76449
76657
  } catch {
76450
- skipped.push({ path: toPosix2(path30.relative(canvasDir, abs)), reason: "missing" });
76658
+ skipped.push({ path: toPosix2(path32.relative(canvasDir, abs)), reason: "missing" });
76451
76659
  continue;
76452
76660
  }
76453
76661
  const fileList = st.isDirectory() ? await listFilesRecursive(abs) : [abs];
76454
76662
  for (const file of fileList) {
76455
- const rel = toPosix2(path30.relative(canvasDir, file));
76663
+ const rel = toPosix2(path32.relative(canvasDir, file));
76456
76664
  const size = (await stat3(file)).size;
76457
76665
  if (size > MAX_SNAPSHOT_FILE_BYTES) {
76458
76666
  skipped.push({ path: rel, reason: `too large (${size} bytes)` });
76459
76667
  continue;
76460
76668
  }
76461
- const bytes = await readFile23(file);
76669
+ const bytes = await readFile25(file);
76462
76670
  files.push({ path: rel, bytes, sha256: sha256Hex(bytes), mime: mimeForFile(file) });
76463
76671
  }
76464
76672
  }
@@ -76515,7 +76723,7 @@ async function putContentAddressed(client, bytes, mime, signal) {
76515
76723
  }
76516
76724
  async function listFilesRecursive(dir) {
76517
76725
  const entries = await readdir12(dir, { recursive: true, withFileTypes: true });
76518
- return entries.filter((d) => d.isFile()).map((d) => path30.join(d.parentPath, d.name));
76726
+ return entries.filter((d) => d.isFile()).map((d) => path32.join(d.parentPath, d.name));
76519
76727
  }
76520
76728
  var SnapshotConflictError = class extends Error {
76521
76729
  conflicts;
@@ -76527,19 +76735,19 @@ var SnapshotConflictError = class extends Error {
76527
76735
  };
76528
76736
  async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
76529
76737
  const entries = [manifest.canvas, ...manifest.files];
76530
- const resolvedTarget = path30.resolve(targetDir);
76738
+ const resolvedTarget = path32.resolve(targetDir);
76531
76739
  const planned = [];
76532
76740
  const conflicts = [];
76533
76741
  const upToDate = [];
76534
76742
  for (const entry of entries) {
76535
- if (path30.isAbsolute(entry.path) || entry.path.split("/").includes("..")) {
76743
+ if (path32.isAbsolute(entry.path) || entry.path.split("/").includes("..")) {
76536
76744
  throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
76537
76745
  }
76538
- const target2 = path30.resolve(resolvedTarget, entry.path);
76539
- if (target2 !== resolvedTarget && !target2.startsWith(resolvedTarget + path30.sep)) {
76746
+ const target2 = path32.resolve(resolvedTarget, entry.path);
76747
+ if (target2 !== resolvedTarget && !target2.startsWith(resolvedTarget + path32.sep)) {
76540
76748
  throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
76541
76749
  }
76542
- const existing = await readFile23(target2).catch(() => null);
76750
+ const existing = await readFile25(target2).catch(() => null);
76543
76751
  if (existing) {
76544
76752
  if (sha256Hex(existing) === entry.sha256) {
76545
76753
  upToDate.push(entry.path);
@@ -76561,11 +76769,11 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
76561
76769
  if (sha256Hex(bytes) !== entry.sha256) {
76562
76770
  throw new Error(`snapshot download for ${entry.path} does not match its recorded sha256`);
76563
76771
  }
76564
- await mkdir10(path30.dirname(target2), { recursive: true });
76565
- await writeFile15(target2, bytes);
76772
+ await mkdir12(path32.dirname(target2), { recursive: true });
76773
+ await writeFile18(target2, bytes);
76566
76774
  restored.push(entry.path);
76567
76775
  }
76568
- return { canvasPath: path30.resolve(resolvedTarget, manifest.canvas.path), restored, upToDate };
76776
+ return { canvasPath: path32.resolve(resolvedTarget, manifest.canvas.path), restored, upToDate };
76569
76777
  }
76570
76778
 
76571
76779
  // src/commands/canvas/static-ad-canvas.ts
@@ -76580,18 +76788,18 @@ function isStaticAdCanvas(parsed) {
76580
76788
  }
76581
76789
 
76582
76790
  // src/commands/canvas/named-people-read.ts
76583
- import { createHash as createHash7 } from "crypto";
76584
- import { mkdir as mkdir11, readFile as readFile24, writeFile as writeFile16 } from "fs/promises";
76585
- import path31 from "path";
76791
+ import { createHash as createHash9 } from "crypto";
76792
+ import { mkdir as mkdir13, readFile as readFile26, writeFile as writeFile19 } from "fs/promises";
76793
+ import path33 from "path";
76586
76794
  var PEOPLE_TIMEOUT_MS = 3e4;
76587
76795
  function peopleReader(cacheDir, spent) {
76588
- const dir = path31.join(cacheDir, "named-people");
76796
+ const dir = path33.join(cacheDir, "named-people");
76589
76797
  return async (lines) => {
76590
76798
  const said = lines.filter((l) => l.trim());
76591
76799
  if (said.length === 0) return [];
76592
- const key = createHash7("sha256").update(JSON.stringify([PEOPLE_PROMPT_VERSION, CLAIMS_MODEL, said])).digest("hex");
76593
- const file = path31.join(dir, `${key}.json`);
76594
- const cached = await readFile24(file, "utf8").then((text4) => readPeople(JSON.parse(text4), said)).catch(() => null);
76800
+ const key = createHash9("sha256").update(JSON.stringify([PEOPLE_PROMPT_VERSION, CLAIMS_MODEL, said])).digest("hex");
76801
+ const file = path33.join(dir, `${key}.json`);
76802
+ const cached = await readFile26(file, "utf8").then((text4) => readPeople(JSON.parse(text4), said)).catch(() => null);
76595
76803
  if (cached) return cached;
76596
76804
  const answer = await askAboutText(CLAIMS_MODEL, peoplePrompt(said), spent, {
76597
76805
  timeoutMs: PEOPLE_TIMEOUT_MS,
@@ -76599,8 +76807,8 @@ function peopleReader(cacheDir, spent) {
76599
76807
  });
76600
76808
  const people = readPeople(answer, said);
76601
76809
  if (people) {
76602
- await mkdir11(dir, { recursive: true });
76603
- await writeFile16(file, JSON.stringify(answer), "utf8");
76810
+ await mkdir13(dir, { recursive: true });
76811
+ await writeFile19(file, JSON.stringify(answer), "utf8");
76604
76812
  }
76605
76813
  return people;
76606
76814
  };
@@ -76919,7 +77127,7 @@ async function fetchBrandLogo(domain) {
76919
77127
 
76920
77128
  // src/commands/canvas/slug-restore.ts
76921
77129
  import { existsSync as existsSync6 } from "fs";
76922
- import path32 from "path";
77130
+ import path34 from "path";
76923
77131
  var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
76924
77132
  var SlugRestoreError = class extends Error {
76925
77133
  code;
@@ -76935,8 +77143,8 @@ async function canvasForSlug(slug, opts) {
76935
77143
  if (!SLUG_PATTERN.test(slug)) {
76936
77144
  throw new SlugRestoreError("invalid_slug", `"${slug}" is not a valid creative slug`);
76937
77145
  }
76938
- const targetDir = path32.join(opts.workspaceRoot, "src", "creatives", slug);
76939
- const local = path32.join(targetDir, `${slug}.canvas.json`);
77146
+ const targetDir = path34.join(opts.workspaceRoot, "src", "creatives", slug);
77147
+ const local = path34.join(targetDir, `${slug}.canvas.json`);
76940
77148
  if (!opts.forceRemote && existsSync6(local)) return { canvasPath: local };
76941
77149
  const client = opts.client();
76942
77150
  if (!client) {
@@ -76995,8 +77203,8 @@ async function fetchManifest(url) {
76995
77203
 
76996
77204
  // src/commands/canvas/series-member.ts
76997
77205
  import { existsSync as existsSync7 } from "fs";
76998
- import { readdir as readdir13, readFile as readFile26, writeFile as writeFile17 } from "fs/promises";
76999
- import path34 from "path";
77206
+ import { readdir as readdir13, readFile as readFile28, writeFile as writeFile20 } from "fs/promises";
77207
+ import path36 from "path";
77000
77208
 
77001
77209
  // src/commands/canvas/creative-definition.ts
77002
77210
  import { parseDocument as parseDocument2 } from "yaml";
@@ -77122,11 +77330,11 @@ ${doc.toString()}---${raw.slice(match[0].length)}`;
77122
77330
  }
77123
77331
 
77124
77332
  // src/commands/canvas/static-fix.ts
77125
- import { readFile as readFile25 } from "fs/promises";
77126
- import path33 from "path";
77333
+ import { readFile as readFile27 } from "fs/promises";
77334
+ import path35 from "path";
77127
77335
  async function previousAdImage(canvasRoot, runId, fetchRecord = fetchRunRecord, roles = {}) {
77128
77336
  try {
77129
- const manifest = JSON.parse(await readFile25(path33.join(canvasRoot, runId, "manifest.json"), "utf8"));
77337
+ const manifest = JSON.parse(await readFile27(path35.join(canvasRoot, runId, "manifest.json"), "utf8"));
77130
77338
  const byNode = Object.fromEntries(
77131
77339
  Object.entries(manifest.outputs_by_node ?? {}).map(([id, out]) => [id, { ...out, ...roles[id] ? { role: roles[id] } : {} }])
77132
77340
  );
@@ -77147,7 +77355,7 @@ async function previousAdImage(canvasRoot, runId, fetchRecord = fetchRunRecord,
77147
77355
  }
77148
77356
  async function previousDelivered(canvasRoot, runId, finalNode, labels, opts = {}) {
77149
77357
  try {
77150
- const manifest = JSON.parse(await readFile25(path33.join(canvasRoot, runId, "manifest.json"), "utf8"));
77358
+ const manifest = JSON.parse(await readFile27(path35.join(canvasRoot, runId, "manifest.json"), "utf8"));
77151
77359
  const images = manifest.outputs_by_node?.[finalNode]?.images ?? [];
77152
77360
  return images.flatMap((image, i) => {
77153
77361
  const local = typeof image.path === "string" ? image.path : null;
@@ -77193,7 +77401,7 @@ function correctedUrl(byNode) {
77193
77401
  }
77194
77402
  async function correctedPicture(canvasRoot, runId, fetchRecord = fetchRunRecord) {
77195
77403
  try {
77196
- const manifest = JSON.parse(await readFile25(path33.join(canvasRoot, runId, "manifest.json"), "utf8"));
77404
+ const manifest = JSON.parse(await readFile27(path35.join(canvasRoot, runId, "manifest.json"), "utf8"));
77197
77405
  if (manifest.outputs_by_node) return correctedUrl(manifest.outputs_by_node);
77198
77406
  } catch {
77199
77407
  }
@@ -77225,7 +77433,7 @@ async function variantImage(canvasRoot, runId, sha2562, fetchRecord = fetchRunRe
77225
77433
  const sha = sha2562.trim().toLowerCase();
77226
77434
  if (!SHA_PREFIX.test(sha)) return null;
77227
77435
  try {
77228
- const manifest = JSON.parse(await readFile25(path33.join(canvasRoot, runId, "manifest.json"), "utf8"));
77436
+ const manifest = JSON.parse(await readFile27(path35.join(canvasRoot, runId, "manifest.json"), "utf8"));
77229
77437
  for (const [id, out] of Object.entries(manifest.outputs_by_node ?? {})) {
77230
77438
  if (roles[id] !== STATIC_VARIANTS_ROLE) continue;
77231
77439
  const url = matchVariant(out.images ?? [], sha);
@@ -77239,10 +77447,10 @@ async function variantImage(canvasRoot, runId, sha2562, fetchRecord = fetchRunRe
77239
77447
  }
77240
77448
 
77241
77449
  // src/commands/canvas/series-member.ts
77242
- var creativeDir = (root, slug) => path34.join(root, "src", "creatives", slug);
77243
- var canvasFile = (root, slug) => path34.join(creativeDir(root, slug), `${slug}.canvas.json`);
77450
+ var creativeDir = (root, slug) => path36.join(root, "src", "creatives", slug);
77451
+ var canvasFile = (root, slug) => path36.join(creativeDir(root, slug), `${slug}.canvas.json`);
77244
77452
  async function seriesFieldsOf(root, slug) {
77245
- const raw = await readFile26(path34.join(creativeDir(root, slug), "_definition.md"), "utf8").catch(() => null);
77453
+ const raw = await readFile28(path36.join(creativeDir(root, slug), "_definition.md"), "utf8").catch(() => null);
77246
77454
  return raw === null ? {} : parseDefinitionStrategyFields(raw);
77247
77455
  }
77248
77456
  async function seriesLeadOf(root, slug) {
@@ -77264,7 +77472,7 @@ async function seriesLeadsAbove(root, slug) {
77264
77472
  return leads;
77265
77473
  }
77266
77474
  async function seriesMembers(root, lead) {
77267
- const slugs = await readdir13(path34.join(root, "src", "creatives")).catch(() => []);
77475
+ const slugs = await readdir13(path36.join(root, "src", "creatives")).catch(() => []);
77268
77476
  const members = [];
77269
77477
  for (const slug of slugs.sort()) {
77270
77478
  if (slug !== lead && await seriesLeadOf(root, slug) === lead) members.push(slug);
@@ -77276,7 +77484,7 @@ function refusal(code, message, fix) {
77276
77484
  }
77277
77485
  async function leadVersion(root, lead, fetchRecord = fetchRunRecord) {
77278
77486
  const file = canvasFile(root, lead);
77279
- const canvas = existsSync7(file) ? JSON.parse(await readFile26(file, "utf8")) : null;
77487
+ const canvas = existsSync7(file) ? JSON.parse(await readFile28(file, "utf8")) : null;
77280
77488
  if (!canvas || !isStaticAdCanvas(canvas)) {
77281
77489
  return refusal(
77282
77490
  "series_lead_not_found",
@@ -77284,7 +77492,7 @@ async function leadVersion(root, lead, fetchRecord = fetchRunRecord) {
77284
77492
  "Name a static ad of this workspace as the lead: make it with `baker creatives make <idea>` and render it first."
77285
77493
  );
77286
77494
  }
77287
- const canvasRoot = path34.join(root, "canvas");
77495
+ const canvasRoot = path36.join(root, "canvas");
77288
77496
  const state = await readRenderState(canvasRoot, lead);
77289
77497
  const runId = state.rendered?.runId;
77290
77498
  if (!runId) {
@@ -77311,7 +77519,7 @@ async function leadVersion(root, lead, fetchRecord = fetchRunRecord) {
77311
77519
  `Render the lead again (baker canvas run --slug ${lead}), then run the series.`
77312
77520
  );
77313
77521
  }
77314
- const drawn = state.prompt ?? JSON.parse(await readFile26(path34.join(creativeDir(root, lead), "prompt.json"), "utf8"));
77522
+ const drawn = state.prompt ?? JSON.parse(await readFile28(path36.join(creativeDir(root, lead), "prompt.json"), "utf8"));
77315
77523
  const copy = readAdSpec(drawn).copy.map((l) => ({ text: l.text, ...l.role ? { role: l.role } : {} }));
77316
77524
  return { lead: { slug: lead, runId, copy, picture, sizes }, canvas, spec: drawn };
77317
77525
  }
@@ -77331,8 +77539,8 @@ function rebaseLeadPaths(canvas, leadDir, memberDir) {
77331
77539
  for (const node of out.nodes ?? []) {
77332
77540
  const key = localPathParam(node);
77333
77541
  const value2 = key ? node.params?.[key] : void 0;
77334
- if (!key || !node.params || typeof value2 !== "string" || node.id === "prompt" || path34.isAbsolute(value2)) continue;
77335
- const rel = path34.relative(memberDir, path34.resolve(leadDir, value2)).split(path34.sep).join("/");
77542
+ if (!key || !node.params || typeof value2 !== "string" || node.id === "prompt" || path36.isAbsolute(value2)) continue;
77543
+ const rel = path36.relative(memberDir, path36.resolve(leadDir, value2)).split(path36.sep).join("/");
77336
77544
  node.params[key] = rel.startsWith(".") ? rel : `./${rel}`;
77337
77545
  }
77338
77546
  return out;
@@ -77395,12 +77603,12 @@ async function writeSeriesMemberCanvas(root, slug, opts = {}) {
77395
77603
  const version = await leadVersion(root, lead, opts.fetchRecord);
77396
77604
  if ("refused" in version) return version;
77397
77605
  const dir = creativeDir(root, slug);
77398
- const promptPath = path34.join(dir, "prompt.json");
77399
- let memberSpec = JSON.parse(await readFile26(promptPath, "utf8").catch(() => "null"));
77606
+ const promptPath = path36.join(dir, "prompt.json");
77607
+ let memberSpec = JSON.parse(await readFile28(promptPath, "utf8").catch(() => "null"));
77400
77608
  const unchanged = opts.leadWords ? null : unchangedCopyRefusal(version.lead, memberSpec);
77401
77609
  if (unchanged) return unchanged;
77402
77610
  const file = canvasFile(root, slug);
77403
- const before = await readFile26(file, "utf8").catch(() => null);
77611
+ const before = await readFile28(file, "utf8").catch(() => null);
77404
77612
  const leadDesign = seriesDesignOf(version.spec);
77405
77613
  const ownDesign = seriesDesignOf(memberSpec);
77406
77614
  const previous = before ? JSON.parse(before).metadata?.series : void 0;
@@ -77411,7 +77619,7 @@ async function writeSeriesMemberCanvas(root, slug, opts = {}) {
77411
77619
  return designChangedRefusal(lead, await seriesLeadsAbove(root, slug));
77412
77620
  }
77413
77621
  memberSpec = withLeadDesign(memberSpec, version.spec);
77414
- await writeFile17(promptPath, `${JSON.stringify(memberSpec, null, 2)}
77622
+ await writeFile20(promptPath, `${JSON.stringify(memberSpec, null, 2)}
77415
77623
  `, "utf8");
77416
77624
  designFromLead = true;
77417
77625
  }
@@ -77423,8 +77631,8 @@ async function writeSeriesMemberCanvas(root, slug, opts = {}) {
77423
77631
  );
77424
77632
  const text4 = `${JSON.stringify(canvas, null, 2)}
77425
77633
  `;
77426
- if (before !== text4) await writeFile17(file, text4, "utf8");
77427
- const madeFrom = (await readRenderState(path34.join(root, "canvas"), slug)).seriesLeadRun;
77634
+ if (before !== text4) await writeFile20(file, text4, "utf8");
77635
+ const madeFrom = (await readRenderState(path36.join(root, "canvas"), slug)).seriesLeadRun;
77428
77636
  const newLeadVersion = madeFrom !== void 0 && madeFrom !== version.lead.runId;
77429
77637
  return { lead, leadRunId: version.lead.runId, canvasPath: file, written: before !== text4, designFromLead, newLeadVersion };
77430
77638
  }
@@ -77975,30 +78183,30 @@ function withDeliveredSizes(canvas, last) {
77975
78183
  }
77976
78184
 
77977
78185
  // src/commands/canvas/static-run-review.ts
77978
- import { readFile as readFile30 } from "fs/promises";
77979
- import path38 from "path";
78186
+ import { readFile as readFile32 } from "fs/promises";
78187
+ import path40 from "path";
77980
78188
 
77981
78189
  // src/commands/canvas/glance-check.ts
77982
78190
  import sharp2 from "sharp";
77983
- import { z as z74 } from "zod";
78191
+ import { z as z76 } from "zod";
77984
78192
 
77985
78193
  // src/commands/canvas/viewer-check.ts
77986
- import { createHash as createHash9 } from "crypto";
77987
- import { mkdir as mkdir14, readdir as readdir14, readFile as readFile29, stat as stat4, writeFile as writeFile20 } from "fs/promises";
77988
- import path37 from "path";
78194
+ import { createHash as createHash11 } from "crypto";
78195
+ import { mkdir as mkdir16, readdir as readdir14, readFile as readFile31, stat as stat4, writeFile as writeFile23 } from "fs/promises";
78196
+ import path39 from "path";
77989
78197
  import { defineCommand as defineCommand141 } from "citty";
77990
78198
  import { parse as parseYaml3 } from "yaml";
77991
- import { z as z73 } from "zod";
78199
+ import { z as z75 } from "zod";
77992
78200
 
77993
78201
  // src/commands/canvas/casting-check.ts
77994
- import { createHash as createHash8 } from "crypto";
77995
- import { mkdir as mkdir12, readFile as readFile27, writeFile as writeFile18 } from "fs/promises";
77996
- import path35 from "path";
77997
- import { z as z71 } from "zod";
77998
- var CastingAnswerSchema = z71.object({
77999
- passes: z71.boolean(),
78000
- why: z71.string().default(""),
78001
- instead: z71.string().default("")
78202
+ import { createHash as createHash10 } from "crypto";
78203
+ import { mkdir as mkdir14, readFile as readFile29, writeFile as writeFile21 } from "fs/promises";
78204
+ import path37 from "path";
78205
+ import { z as z73 } from "zod";
78206
+ var CastingAnswerSchema = z73.object({
78207
+ passes: z73.boolean(),
78208
+ why: z73.string().default(""),
78209
+ instead: z73.string().default("")
78002
78210
  });
78003
78211
  function buildCastingPrompt(input) {
78004
78212
  return [
@@ -78014,7 +78222,7 @@ function buildCastingPrompt(input) {
78014
78222
  ].join("\n");
78015
78223
  }
78016
78224
  function cacheKey(input, model) {
78017
- return createHash8("sha256").update(JSON.stringify({ prompt: buildCastingPrompt(input), model })).digest("hex");
78225
+ return createHash10("sha256").update(JSON.stringify({ prompt: buildCastingPrompt(input), model })).digest("hex");
78018
78226
  }
78019
78227
  function parseOrNull(raw) {
78020
78228
  try {
@@ -78025,16 +78233,16 @@ function parseOrNull(raw) {
78025
78233
  }
78026
78234
  async function castingCheck(input, opts) {
78027
78235
  if (input.people.length === 0) return { status: "skipped" };
78028
- const file = path35.join(opts.cacheDir, "casting-check", `${cacheKey(input, opts.model)}.json`);
78029
- const cachedRaw = await readFile27(file, "utf8").catch(() => null);
78236
+ const file = path37.join(opts.cacheDir, "casting-check", `${cacheKey(input, opts.model)}.json`);
78237
+ const cachedRaw = await readFile29(file, "utf8").catch(() => null);
78030
78238
  const cachedParse = cachedRaw ? CastingAnswerSchema.safeParse(parseOrNull(cachedRaw)) : null;
78031
78239
  if (cachedParse?.success) return { status: cachedParse.data.passes ? "passed" : "failed", answer: cachedParse.data, cached: true };
78032
78240
  const prompt = buildCastingPrompt(input);
78033
78241
  for (let attempt = 0; attempt < 2; attempt++) {
78034
78242
  const parsed = CastingAnswerSchema.safeParse(await opts.ask(prompt));
78035
78243
  if (!parsed.success) continue;
78036
- await mkdir12(path35.dirname(file), { recursive: true });
78037
- await writeFile18(file, `${JSON.stringify(parsed.data, null, 2)}
78244
+ await mkdir14(path37.dirname(file), { recursive: true });
78245
+ await writeFile21(file, `${JSON.stringify(parsed.data, null, 2)}
78038
78246
  `, "utf8");
78039
78247
  return { status: parsed.data.passes ? "passed" : "failed", answer: parsed.data, cached: false };
78040
78248
  }
@@ -78056,9 +78264,9 @@ function castingCheckFailure(answer, slug) {
78056
78264
  }
78057
78265
 
78058
78266
  // src/commands/canvas/turn-renders.ts
78059
- import { mkdir as mkdir13, readFile as readFile28, writeFile as writeFile19 } from "fs/promises";
78060
- import path36 from "path";
78061
- import { z as z72 } from "zod";
78267
+ import { mkdir as mkdir15, readFile as readFile30, writeFile as writeFile22 } from "fs/promises";
78268
+ import path38 from "path";
78269
+ import { z as z74 } from "zod";
78062
78270
  var RUNS_PER_TURN = 2;
78063
78271
  var VIEWER_FAILS_PER_TURN = 2;
78064
78272
  var AD_TURN_MS = 12 * 6e4;
@@ -78070,15 +78278,15 @@ var ROUND_MS = {
78070
78278
  regenerate: 22e4
78071
78279
  };
78072
78280
  var RENDER_MS = 15e4;
78073
- var LedgerSchema = z72.object({
78074
- turnId: z72.string(),
78075
- creatives: z72.record(
78076
- z72.string(),
78077
- z72.object({
78078
- runs: z72.number().default(0),
78079
- verdict: z72.string().optional(),
78080
- viewerFails: z72.number().optional(),
78081
- spentMs: z72.number().optional()
78281
+ var LedgerSchema = z74.object({
78282
+ turnId: z74.string(),
78283
+ creatives: z74.record(
78284
+ z74.string(),
78285
+ z74.object({
78286
+ runs: z74.number().default(0),
78287
+ verdict: z74.string().optional(),
78288
+ viewerFails: z74.number().optional(),
78289
+ spentMs: z74.number().optional()
78082
78290
  })
78083
78291
  )
78084
78292
  });
@@ -78134,18 +78342,18 @@ function viewerChecksSpent(ledger, turnId, slug) {
78134
78342
  const entry = turnId !== void 0 && ledger?.turnId === turnId ? ledger.creatives[slug] : void 0;
78135
78343
  return (entry?.viewerFails ?? 0) >= VIEWER_FAILS_PER_TURN;
78136
78344
  }
78137
- var ledgerPath = (cacheDir) => path36.join(cacheDir, "turn-renders.json");
78345
+ var ledgerPath = (cacheDir) => path38.join(cacheDir, "turn-renders.json");
78138
78346
  async function readTurnRenders(cacheDir) {
78139
78347
  try {
78140
- const parsed = LedgerSchema.safeParse(JSON.parse(await readFile28(ledgerPath(cacheDir), "utf8")));
78348
+ const parsed = LedgerSchema.safeParse(JSON.parse(await readFile30(ledgerPath(cacheDir), "utf8")));
78141
78349
  return parsed.success ? parsed.data : void 0;
78142
78350
  } catch {
78143
78351
  return void 0;
78144
78352
  }
78145
78353
  }
78146
78354
  async function writeTurnRenders(cacheDir, ledger) {
78147
- await mkdir13(cacheDir, { recursive: true });
78148
- await writeFile19(ledgerPath(cacheDir), JSON.stringify(ledger), "utf8");
78355
+ await mkdir15(cacheDir, { recursive: true });
78356
+ await writeFile22(ledgerPath(cacheDir), JSON.stringify(ledger), "utf8");
78149
78357
  }
78150
78358
 
78151
78359
  // src/commands/creatives/company-offer.ts
@@ -78160,14 +78368,14 @@ function marketQuery(companyDoc) {
78160
78368
  // src/commands/canvas/viewer-check.ts
78161
78369
  var VIEWER_QUESTIONS = ["offer", "for_whom", "next_step"];
78162
78370
  var VIEWER_CLARITY = ["clear", "guess", "unclear"];
78163
- var ViewerReply = z73.object({ answer: z73.string().default(""), clarity: z73.enum(VIEWER_CLARITY) });
78164
- var ViewerAnswerSchema = z73.object({
78371
+ var ViewerReply = z75.object({ answer: z75.string().default(""), clarity: z75.enum(VIEWER_CLARITY) });
78372
+ var ViewerAnswerSchema = z75.object({
78165
78373
  offer: ViewerReply,
78166
78374
  for_whom: ViewerReply,
78167
78375
  next_step: ViewerReply,
78168
78376
  /** The words that talk to the viewer as if they had already done something with the brand; "" when none. */
78169
- presumes: z73.string().default(""),
78170
- missing: z73.string().default("")
78377
+ presumes: z75.string().default(""),
78378
+ missing: z75.string().default("")
78171
78379
  });
78172
78380
  var VIEWER_REQUIRED_CLARITY = {
78173
78381
  offer: "clear",
@@ -78274,18 +78482,18 @@ function viewerCheckCacheKey(input, model) {
78274
78482
  intended: input.intended,
78275
78483
  model
78276
78484
  };
78277
- return createHash9("sha256").update(JSON.stringify(canonical)).digest("hex");
78485
+ return createHash11("sha256").update(JSON.stringify(canonical)).digest("hex");
78278
78486
  }
78279
78487
  async function cachedAnswer(file) {
78280
78488
  try {
78281
- const parsed = ViewerAnswerSchema.safeParse(JSON.parse(await readFile29(file, "utf8")));
78489
+ const parsed = ViewerAnswerSchema.safeParse(JSON.parse(await readFile31(file, "utf8")));
78282
78490
  return parsed.success ? parsed.data : null;
78283
78491
  } catch {
78284
78492
  return null;
78285
78493
  }
78286
78494
  }
78287
78495
  async function checkViewer(input, opts) {
78288
- const file = path37.join(opts.cacheDir, "viewer-check", `${viewerCheckCacheKey(input, opts.model)}.json`);
78496
+ const file = path39.join(opts.cacheDir, "viewer-check", `${viewerCheckCacheKey(input, opts.model)}.json`);
78289
78497
  let answer = await cachedAnswer(file);
78290
78498
  const cached = answer !== null;
78291
78499
  const prompt = buildViewerPrompt(input);
@@ -78295,8 +78503,8 @@ async function checkViewer(input, opts) {
78295
78503
  }
78296
78504
  if (answer === null) return { status: "unavailable", input };
78297
78505
  if (!cached) {
78298
- await mkdir14(path37.dirname(file), { recursive: true });
78299
- await writeFile20(file, `${JSON.stringify(answer, null, 2)}
78506
+ await mkdir16(path39.dirname(file), { recursive: true });
78507
+ await writeFile23(file, `${JSON.stringify(answer, null, 2)}
78300
78508
  `, "utf8");
78301
78509
  }
78302
78510
  const sure = sureEnough(answer.offer.clarity, VIEWER_REQUIRED_CLARITY.offer);
@@ -78355,17 +78563,17 @@ function viewerCheckFailure(outcome2, slug) {
78355
78563
  }
78356
78564
  };
78357
78565
  }
78358
- var RecordedViewer = z73.object({
78359
- temperature: z73.enum(VIEWER_TEMPERATURE_VALUES),
78360
- knows: z73.array(z73.enum(VIEWER_KNOWS_VALUES)).default([]),
78361
- placements: z73.array(z73.string().min(1)).default(["automatic"]),
78362
- source: z73.enum(VIEWER_SOURCE_VALUES),
78363
- reasons: z73.array(z73.string()).default([]),
78364
- market: z73.object({
78365
- country: z73.string().min(2),
78366
- language: z73.string().optional(),
78367
- currency: z73.string().optional(),
78368
- address: z73.string().optional()
78566
+ var RecordedViewer = z75.object({
78567
+ temperature: z75.enum(VIEWER_TEMPERATURE_VALUES),
78568
+ knows: z75.array(z75.enum(VIEWER_KNOWS_VALUES)).default([]),
78569
+ placements: z75.array(z75.string().min(1)).default(["automatic"]),
78570
+ source: z75.enum(VIEWER_SOURCE_VALUES),
78571
+ reasons: z75.array(z75.string()).default([]),
78572
+ market: z75.object({
78573
+ country: z75.string().min(2),
78574
+ language: z75.string().optional(),
78575
+ currency: z75.string().optional(),
78576
+ address: z75.string().optional()
78369
78577
  }).optional()
78370
78578
  });
78371
78579
  var nameKey = (text4) => text4.normalize("NFD").replace(new RegExp("\\p{M}", "gu"), "").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean).join(" ");
@@ -78424,12 +78632,12 @@ async function resolveViewerAlone(sources, lookups) {
78424
78632
  notes
78425
78633
  };
78426
78634
  }
78427
- var Frontmatter = z73.object({
78428
- viewer: z73.unknown().optional(),
78429
- targetAdSet: z73.union([z73.string(), z73.number()]).transform(String).optional(),
78430
- persona: z73.string().optional(),
78431
- awareness: z73.enum(AWARENESS_VALUES).optional().catch(void 0),
78432
- angle: z73.string().optional()
78635
+ var Frontmatter = z75.object({
78636
+ viewer: z75.unknown().optional(),
78637
+ targetAdSet: z75.union([z75.string(), z75.number()]).transform(String).optional(),
78638
+ persona: z75.string().optional(),
78639
+ awareness: z75.enum(AWARENESS_VALUES).optional().catch(void 0),
78640
+ angle: z75.string().optional()
78433
78641
  });
78434
78642
  function frontmatterOf2(raw) {
78435
78643
  const block2 = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
@@ -78442,12 +78650,12 @@ function frontmatterOf2(raw) {
78442
78650
  }
78443
78651
  }
78444
78652
  async function readCreativeForViewerCheck(creativeDir3) {
78445
- const spec = readAdSpec(JSON.parse(await readFile29(path37.join(creativeDir3, "prompt.json"), "utf8")));
78446
- const definition = frontmatterOf2(await readFile29(path37.join(creativeDir3, "_definition.md"), "utf8").catch(() => ""));
78653
+ const spec = readAdSpec(JSON.parse(await readFile31(path39.join(creativeDir3, "prompt.json"), "utf8")));
78654
+ const definition = frontmatterOf2(await readFile31(path39.join(creativeDir3, "_definition.md"), "utf8").catch(() => ""));
78447
78655
  const offer = spec.message?.trim() || definition.angle?.trim();
78448
78656
  const persona = spec.viewer?.who.trim() || definition.persona?.trim();
78449
78657
  const brand = spec.brand?.name?.trim();
78450
- const sells = marketQuery(await readFile29(path37.resolve(creativeDir3, "../../../COMPANY.md"), "utf8").catch(() => ""));
78658
+ const sells = marketQuery(await readFile31(path39.resolve(creativeDir3, "../../../COMPANY.md"), "utf8").catch(() => ""));
78451
78659
  return {
78452
78660
  ad: {
78453
78661
  copy: spec.copy.map((line) => ({
@@ -78473,7 +78681,7 @@ function castPeople(spec) {
78473
78681
  return people.length > 0 ? { people } : {};
78474
78682
  }
78475
78683
  async function readPersonas(workspaceRoot) {
78476
- const dir = path37.join(workspaceRoot, "src", "content", "personas");
78684
+ const dir = path39.join(workspaceRoot, "src", "content", "personas");
78477
78685
  let files;
78478
78686
  try {
78479
78687
  files = (await readdir14(dir, { recursive: true })).filter((f) => f.endsWith(".md"));
@@ -78482,7 +78690,7 @@ async function readPersonas(workspaceRoot) {
78482
78690
  }
78483
78691
  return await Promise.all(
78484
78692
  files.map(async (file) => {
78485
- const raw = await readFile29(path37.join(dir, file), "utf8");
78693
+ const raw = await readFile31(path39.join(dir, file), "utf8");
78486
78694
  const block2 = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
78487
78695
  let segment;
78488
78696
  try {
@@ -78492,7 +78700,7 @@ async function readPersonas(workspaceRoot) {
78492
78700
  }
78493
78701
  const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim();
78494
78702
  return {
78495
- slug: path37.basename(file, ".md"),
78703
+ slug: path39.basename(file, ".md"),
78496
78704
  ...title ? { title } : {},
78497
78705
  ...typeof segment === "string" && segment.trim() ? { segment: segment.trim() } : {}
78498
78706
  };
@@ -78523,11 +78731,11 @@ async function metaAdSetViewer(adSetId, workspaceRoot) {
78523
78731
  read("/api/ads/meta/audiences", params).catch(() => []),
78524
78732
  read("/api/ads/meta/campaigns", params).catch(() => [])
78525
78733
  ]);
78526
- const cacheDir = path37.join(workspaceRoot, "canvas", ".cache");
78734
+ const cacheDir = path39.join(workspaceRoot, "canvas", ".cache");
78527
78735
  const spent = [];
78528
78736
  const readName = await readAudienceNames(
78529
78737
  audienceNamesOf([adSet], audiences),
78530
- rememberedJev(path37.join(cacheDir, "audience-names.json"), (u) => spent.push(u))
78738
+ rememberedJev(path39.join(cacheDir, "audience-names.json"), (u) => spent.push(u))
78531
78739
  );
78532
78740
  await holdUnbilledSpend(cacheDir, spent).catch(() => void 0);
78533
78741
  return adSetViewerRows([adSet], audiences, campaigns, readName)[0]?.viewer ?? null;
@@ -78538,8 +78746,8 @@ function liveViewerLookups(workspaceRoot) {
78538
78746
  return {
78539
78747
  adSetViewer: (adSetId) => metaAdSetViewer(adSetId, workspaceRoot),
78540
78748
  personas: () => readPersonas(workspaceRoot),
78541
- companyMarket: async () => marketFromCompanyDoc(await readFile29(path37.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null)),
78542
- company: async () => (await readFile29(path37.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => "")).slice(0, 800)
78749
+ companyMarket: async () => marketFromCompanyDoc(await readFile31(path39.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null)),
78750
+ company: async () => (await readFile31(path39.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => "")).slice(0, 800)
78543
78751
  };
78544
78752
  }
78545
78753
  function gatewayViewerModel(model, onSpend) {
@@ -78617,11 +78825,11 @@ var ARGS5 = {
78617
78825
  };
78618
78826
  registerSchema({ command: "canvas.viewer-check", description: DESCRIPTION2, args: ARGS5 });
78619
78827
  async function creativeDirOf(arg, workspaceRoot) {
78620
- const direct = path37.resolve(arg);
78828
+ const direct = path39.resolve(arg);
78621
78829
  try {
78622
- return (await stat4(direct)).isDirectory() ? direct : path37.dirname(direct);
78830
+ return (await stat4(direct)).isDirectory() ? direct : path39.dirname(direct);
78623
78831
  } catch {
78624
- return path37.join(workspaceRoot, "src", "creatives", arg);
78832
+ return path39.join(workspaceRoot, "src", "creatives", arg);
78625
78833
  }
78626
78834
  }
78627
78835
  var viewerCheckCommand = defineCommand141({
@@ -78631,7 +78839,7 @@ var viewerCheckCommand = defineCommand141({
78631
78839
  const cwd = process.cwd();
78632
78840
  const workspaceRoot = findWorkspaceRoot(cwd) ?? cwd;
78633
78841
  const creativeDir3 = await creativeDirOf(String(args8.slug), workspaceRoot);
78634
- const slug = path37.basename(creativeDir3);
78842
+ const slug = path39.basename(creativeDir3);
78635
78843
  let creative;
78636
78844
  try {
78637
78845
  creative = await readCreativeForViewerCheck(creativeDir3);
@@ -78648,7 +78856,7 @@ var viewerCheckCommand = defineCommand141({
78648
78856
  }
78649
78857
  const resolved = await resolveViewer(creative.sources, liveViewerLookups(workspaceRoot));
78650
78858
  const input = { ...creative.ad, viewer: resolved.viewer };
78651
- const cacheDir = path37.join(workspaceRoot, "canvas", ".cache");
78859
+ const cacheDir = path39.join(workspaceRoot, "canvas", ".cache");
78652
78860
  const spent = [];
78653
78861
  const outcome2 = await checkViewer(input, {
78654
78862
  cacheDir,
@@ -78721,9 +78929,9 @@ var GLANCE_REQUIRED = {
78721
78929
  var GLANCE_WIDTH = 400;
78722
78930
  var GlanceAnswerSchema = ViewerAnswerSchema.extend({
78723
78931
  /** What kind of product or service they take it for (D4). */
78724
- category: z74.object({ answer: z74.string().default(""), clarity: z74.enum(["clear", "guess", "unclear"]).default("unclear") }).default({ answer: "", clarity: "unclear" }),
78725
- stopped: z74.string().default(""),
78726
- me: z74.enum(["yes", "no", "unsure"]).default("unsure")
78932
+ category: z76.object({ answer: z76.string().default(""), clarity: z76.enum(["clear", "guess", "unclear"]).default("unclear") }).default({ answer: "", clarity: "unclear" }),
78933
+ stopped: z76.string().default(""),
78934
+ me: z76.enum(["yes", "no", "unsure"]).default("unsure")
78727
78935
  });
78728
78936
  function wherePhrase2(placements) {
78729
78937
  const fullScreen = placements.length === 0 || placements.some((p) => /automatic|stor|reel/i.test(p));
@@ -78907,7 +79115,7 @@ function runCostHint(stats, kind) {
78907
79115
  async function textOf(ref2) {
78908
79116
  const r = ref2;
78909
79117
  try {
78910
- if (typeof r?.path === "string") return await readFile30(r.path, "utf8");
79118
+ if (typeof r?.path === "string") return await readFile32(r.path, "utf8");
78911
79119
  if (typeof r?.url === "string") {
78912
79120
  const res = await fetch(r.url);
78913
79121
  return res.ok ? await res.text() : null;
@@ -78974,7 +79182,7 @@ async function reviewStaticRun(opts) {
78974
79182
  let native;
78975
79183
  let palette = [];
78976
79184
  try {
78977
- const spec = readAdSpec(JSON.parse(await readFile30(targets.promptPath, "utf8")));
79185
+ const spec = readAdSpec(JSON.parse(await readFile32(targets.promptPath, "utf8")));
78978
79186
  copy = spec.copy.map((l) => l.text);
78979
79187
  lines = spec.copy.map((l) => ({ text: l.text, lettering: l.lettering, ...l.role ? { role: l.role } : {} }));
78980
79188
  if (spec.native) {
@@ -78986,7 +79194,7 @@ async function reviewStaticRun(opts) {
78986
79194
  mechanisms = spec.mechanism.map((m) => m.device);
78987
79195
  brand = spec.brand?.name ?? opts.brandName;
78988
79196
  const sells = marketQuery(
78989
- await readFile30(path38.resolve(path38.dirname(targets.promptPath), "../../../COMPANY.md"), "utf8").catch(() => "")
79197
+ await readFile32(path40.resolve(path40.dirname(targets.promptPath), "../../../COMPANY.md"), "utf8").catch(() => "")
78990
79198
  );
78991
79199
  intended = {
78992
79200
  ...spec.message?.trim() ? { offer: spec.message.trim() } : {},
@@ -79001,7 +79209,7 @@ async function reviewStaticRun(opts) {
79001
79209
  ];
79002
79210
  expectLogo = spec.logo && !spec.native && staticRunShape(opts.parsed).logo;
79003
79211
  concept = conceptSummary(spec);
79004
- const cacheDir = path38.join(findWorkspaceRoot(path38.dirname(targets.promptPath)) ?? path38.dirname(targets.promptPath), "canvas", ".cache");
79212
+ const cacheDir = path40.join(findWorkspaceRoot(path40.dirname(targets.promptPath)) ?? path40.dirname(targets.promptPath), "canvas", ".cache");
79005
79213
  const people = spec.native ? [] : await peopleReader(cacheDir, (u) => opts.onSpend?.(u))(copy) ?? [];
79006
79214
  uncast = uncastPeople(spec, people);
79007
79215
  } catch {
@@ -79173,12 +79381,12 @@ function variantHints(judge, prefilter) {
79173
79381
  }
79174
79382
 
79175
79383
  // src/commands/canvas/static-spec-repair.ts
79176
- import { readFile as readFile31, writeFile as writeFile21 } from "fs/promises";
79384
+ import { readFile as readFile33, writeFile as writeFile24 } from "fs/promises";
79177
79385
  import sharp3 from "sharp";
79178
- import { z as z75 } from "zod";
79179
- var Answer3 = z75.object({
79180
- changed: z75.string().min(1),
79181
- set: z75.array(z75.object({ path: z75.string().min(1), value: z75.unknown() }))
79386
+ import { z as z77 } from "zod";
79387
+ var Answer4 = z77.object({
79388
+ changed: z77.string().min(1),
79389
+ set: z77.array(z77.object({ path: z77.string().min(1), value: z77.unknown() }))
79182
79390
  });
79183
79391
  function withValueAt(root, path83, value2) {
79184
79392
  const [key, ...rest] = path83;
@@ -79197,7 +79405,7 @@ function printedWords(spec) {
79197
79405
  return [...spec.copy.map((l) => l.text), ...spec.native ? nativeCopy(spec.native) : []];
79198
79406
  }
79199
79407
  function acceptRepair(before, answer) {
79200
- const parsed = Answer3.safeParse(answer);
79408
+ const parsed = Answer4.safeParse(answer);
79201
79409
  if (!parsed.success || !isAdSpec(before)) return null;
79202
79410
  const spec = parsed.data.set.reduce((at, { path: path83, value: value2 }) => withValueAt(at, path83.split("."), value2), before);
79203
79411
  if (!isAdSpec(spec)) return null;
@@ -79232,7 +79440,7 @@ function buildSpecRepairPrompt(spec, findings, schema) {
79232
79440
  var REPAIR_TIMEOUT_MS = 15e4;
79233
79441
  var SHOWN_WIDTH = 768;
79234
79442
  async function repairSpec(opts) {
79235
- const text4 = await readFile31(opts.promptPath, "utf8").catch(() => null);
79443
+ const text4 = await readFile33(opts.promptPath, "utf8").catch(() => null);
79236
79444
  if (text4 === null) return null;
79237
79445
  let before;
79238
79446
  try {
@@ -79244,7 +79452,7 @@ async function repairSpec(opts) {
79244
79452
  const prompt = buildSpecRepairPrompt(text4, opts.findings, adSpecJsonSchema());
79245
79453
  const repair = acceptRepair(before, await askAboutImages(RENDER_REVIEW_MODEL, shown ? [shown] : [], prompt, opts.onSpend, REPAIR_TIMEOUT_MS));
79246
79454
  if (!repair) return null;
79247
- await writeFile21(opts.promptPath, `${JSON.stringify(repair.spec, null, 2)}
79455
+ await writeFile24(opts.promptPath, `${JSON.stringify(repair.spec, null, 2)}
79248
79456
  `, "utf8");
79249
79457
  return repair.changed;
79250
79458
  }
@@ -79379,7 +79587,7 @@ function failPreflight3(code, message, fix) {
79379
79587
  process.exit(2);
79380
79588
  }
79381
79589
  async function canvasFileFor(opts) {
79382
- if (opts.file) return { canvasPath: path39.resolve(opts.file) };
79590
+ if (opts.file) return { canvasPath: path41.resolve(opts.file) };
79383
79591
  if (!opts.slug) failPreflight3("missing_canvas", "Pass the canvas file, or --slug <slug> to run a creative by name.");
79384
79592
  const cwd = process.cwd();
79385
79593
  try {
@@ -79405,7 +79613,7 @@ async function executeSeriesRun(lead) {
79405
79613
  `Make each other message as an ad of the series first (baker creatives make <idea> --series ${lead}), or each language (baker creatives translate --slug ${lead} --to es-MX,de).`
79406
79614
  );
79407
79615
  }
79408
- const canvasRoot = path39.join(root, "canvas");
79616
+ const canvasRoot = path41.join(root, "canvas");
79409
79617
  process.stdout.write(`[series] rendering ${members.length} ads of ${lead} at once: ${members.join(", ")}
79410
79618
  `);
79411
79619
  const results = await runSeries(members, {
@@ -79437,9 +79645,9 @@ async function executeSeriesRun(lead) {
79437
79645
  `);
79438
79646
  }
79439
79647
  async function refreshSeriesAd(opts) {
79440
- const slug = opts.slug ?? (opts.file ? creativeSlugFromCanvasPath(path39.resolve(opts.file)) : null);
79648
+ const slug = opts.slug ?? (opts.file ? creativeSlugFromCanvasPath(path41.resolve(opts.file)) : null);
79441
79649
  if (!slug) return false;
79442
- const root = findWorkspaceRoot(opts.file ? path39.dirname(path39.resolve(opts.file)) : process.cwd());
79650
+ const root = findWorkspaceRoot(opts.file ? path41.dirname(path41.resolve(opts.file)) : process.cwd());
79443
79651
  if (!root) return false;
79444
79652
  const made = await writeSeriesMemberCanvas(root, slug);
79445
79653
  if (made === null) return false;
@@ -79459,7 +79667,7 @@ async function executeCanvasRun(opts) {
79459
79667
  const creativeSlug = creativeSlugFromCanvasPath(filePath) ?? void 0;
79460
79668
  if (creativeSlug) await clearLegacyDirtyMarker(canvasRoot, creativeSlug);
79461
79669
  const turnId = optionalTurnId();
79462
- const turnCache = opts.cacheDir ?? path39.join(canvasRoot, ".cache");
79670
+ const turnCache = opts.cacheDir ?? path41.join(canvasRoot, ".cache");
79463
79671
  if (creativeSlug && (opts.autoFixRound ?? 0) === 0) {
79464
79672
  const refusal2 = turnRenderRefusal(await readTurnRenders(turnCache), turnId, creativeSlug);
79465
79673
  if (refusal2) {
@@ -79472,7 +79680,7 @@ async function executeCanvasRun(opts) {
79472
79680
  }
79473
79681
  const citing = [];
79474
79682
  await wireProofBadges(
79475
- path39.join(path39.dirname(filePath), "prompt.json"),
79683
+ path41.join(path41.dirname(filePath), "prompt.json"),
79476
79684
  fetchBrandLogo,
79477
79685
  (line) => process.stderr.write(`${line}
79478
79686
  `),
@@ -79482,7 +79690,7 @@ async function executeCanvasRun(opts) {
79482
79690
  if ((opts.autoFixRound ?? 0) === 0) {
79483
79691
  const reading = [];
79484
79692
  await wireNamedPeople(
79485
- path39.join(path39.dirname(filePath), "prompt.json"),
79693
+ path41.join(path41.dirname(filePath), "prompt.json"),
79486
79694
  avatarOfNamedPerson,
79487
79695
  (line) => process.stderr.write(`${line}
79488
79696
  `),
@@ -79682,7 +79890,7 @@ ${describeRewrites(prepared.rewrites)}
79682
79890
  process.stdout.write(`[extend] placing ${opts.extendSizes.join(", ")} clear of what covers or cuts it, its margins painted
79683
79891
  `);
79684
79892
  }
79685
- const cacheDir = opts.cacheDir ?? path39.join(canvasRoot, ".cache");
79893
+ const cacheDir = opts.cacheDir ?? path41.join(canvasRoot, ".cache");
79686
79894
  const engine = createEngineFromEnv({
79687
79895
  cacheDir,
79688
79896
  outputsDir: opts.outputsDir ?? canvasRoot,
@@ -79706,7 +79914,7 @@ ${describeRewrites(prepared.rewrites)}
79706
79914
  const canvasSha = sha256Hex(Buffer.from(canvasText));
79707
79915
  const sourceSha = prepared.sourceSha;
79708
79916
  const client = opts.record === false ? null : buildBackendClient();
79709
- const outputsDir = opts.outputsDir ? path39.resolve(opts.outputsDir) : canvasRoot;
79917
+ const outputsDir = opts.outputsDir ? path41.resolve(opts.outputsDir) : canvasRoot;
79710
79918
  const { runId, resumed, source, sameSource, concurrentRunId, rendering } = await resolveRunId({
79711
79919
  explicitRunId: opts.runId,
79712
79920
  adoptedRunId: adoptRunId,
@@ -79737,7 +79945,7 @@ ${describeRewrites(prepared.rewrites)}
79737
79945
  }
79738
79946
  const viewerUsage = [];
79739
79947
  const viewerCheck = creativeSlug && isStaticAdCanvas(parsed) ? await viewerCheckBeforeRender({
79740
- creativeDir: path39.dirname(filePath),
79948
+ creativeDir: path41.dirname(filePath),
79741
79949
  cacheDir,
79742
79950
  slug: creativeSlug,
79743
79951
  skip: viewerCheckSkip({
@@ -79749,7 +79957,7 @@ ${describeRewrites(prepared.rewrites)}
79749
79957
  asked: opts.skipViewerCheck === true
79750
79958
  }),
79751
79959
  spent: viewerChecksSpent(await readTurnRenders(turnCache), turnId, creativeSlug),
79752
- lookups: liveViewerLookups(path39.dirname(canvasRoot)),
79960
+ lookups: liveViewerLookups(path41.dirname(canvasRoot)),
79753
79961
  model: VIEWER_CHECK_MODEL,
79754
79962
  ask: gatewayViewerModel(VIEWER_CHECK_MODEL, (usage) => viewerUsage.push(usage)),
79755
79963
  judge: jevReadJudge((usage) => viewerUsage.push(usage))
@@ -79807,7 +80015,7 @@ ${describeRewrites(prepared.rewrites)}
79807
80015
  if (snapshot && creativeSlug) await recordSnapshot(canvasRoot, creativeSlug, snapshot);
79808
80016
  const recordMeta = {
79809
80017
  creativeSlug,
79810
- canvasPath: path39.relative(path39.dirname(canvasRoot), filePath) || void 0,
80018
+ canvasPath: path41.relative(path41.dirname(canvasRoot), filePath) || void 0,
79811
80019
  canvasSha,
79812
80020
  // The fingerprint the dashboard compares against the current source to flag
79813
80021
  // "edited since last render": the same `computeSourceSha` the edit-sync posts,
@@ -79897,7 +80105,7 @@ ${describeRewrites(prepared.rewrites)}
79897
80105
  brief: opts.brief?.trim() || void 0,
79898
80106
  onSpend: (usage) => reviewUsage.push(usage)
79899
80107
  }) : null;
79900
- const brandKit = reviewable ? null : await readStaticBrandKit(await findBrandRoot(path39.dirname(filePath)) ?? process.cwd());
80108
+ const brandKit = reviewable ? null : await readStaticBrandKit(await findBrandRoot(path41.dirname(filePath)) ?? process.cwd());
79901
80109
  const runShape = {
79902
80110
  fix: fixInstruction !== void 0,
79903
80111
  useVariant: opts.useVariant !== void 0,
@@ -79932,7 +80140,7 @@ ${describeRewrites(prepared.rewrites)}
79932
80140
  }
79933
80141
  const costHint = staticReview ? runCostHint(result.stats, paidFor(runShape)) : null;
79934
80142
  const corrected = opts.autoFixed ?? [];
79935
- const seriesRoot = creativeSlug && staticReview ? findWorkspaceRoot(path39.dirname(filePath)) : null;
80143
+ const seriesRoot = creativeSlug && staticReview ? findWorkspaceRoot(path41.dirname(filePath)) : null;
79936
80144
  const seriesHints = seriesRoot && creativeSlug ? seriesRunHints(creativeSlug, {
79937
80145
  leads: await seriesLeadsAbove(seriesRoot, creativeSlug),
79938
80146
  members: await seriesMembers(seriesRoot, creativeSlug)
@@ -79956,7 +80164,7 @@ ${describeRewrites(prepared.rewrites)}
79956
80164
  ] : [];
79957
80165
  const findings = staticReview ? staticReview.review : review;
79958
80166
  const ready = staticReview ? staticReview.verdict === "ready" || staticReview.verdict === "ready_with_notes" : review !== null && !review.some((f) => f.severity === "blocking");
79959
- const definitionStatus = ready && creativeSlug ? await advanceDefinitionStatus(path39.join(path39.dirname(filePath), "_definition.md"), "rendered") : null;
80167
+ const definitionStatus = ready && creativeSlug ? await advanceDefinitionStatus(path41.join(path41.dirname(filePath), "_definition.md"), "rendered") : null;
79960
80168
  if (poster) {
79961
80169
  await flushBilled(
79962
80170
  poster,
@@ -80227,14 +80435,14 @@ var rerunCommand = defineCommand143({
80227
80435
  });
80228
80436
 
80229
80437
  // src/commands/canvas/scaffold-static-ad.ts
80230
- import { mkdir as mkdir17, readFile as readFile35, writeFile as writeFile24 } from "fs/promises";
80231
- import path45 from "path";
80438
+ import { mkdir as mkdir19, readFile as readFile37, writeFile as writeFile27 } from "fs/promises";
80439
+ import path47 from "path";
80232
80440
  import { defineCommand as defineCommand146 } from "citty";
80233
80441
 
80234
80442
  // src/commands/canvas/creative-limit.ts
80235
80443
  import { existsSync as existsSync8 } from "fs";
80236
- import { mkdir as mkdir15, readdir as readdir15, writeFile as writeFile22 } from "fs/promises";
80237
- import path40 from "path";
80444
+ import { mkdir as mkdir17, readdir as readdir15, writeFile as writeFile25 } from "fs/promises";
80445
+ import path42 from "path";
80238
80446
  var NEW_CREATIVES_PER_CHAT = 10;
80239
80447
  function claimNewCreative(made, slug) {
80240
80448
  if (made.includes(slug)) return { allowed: true, count: made.length };
@@ -80242,7 +80450,7 @@ function claimNewCreative(made, slug) {
80242
80450
  return { allowed: true, count: made.length + 1 };
80243
80451
  }
80244
80452
  function chatDir(canvasRoot, chatId) {
80245
- return path40.join(canvasRoot, ".creative-limit", (chatId ?? "no-chat").replace(/[^A-Za-z0-9_-]/g, "_"));
80453
+ return path42.join(canvasRoot, ".creative-limit", (chatId ?? "no-chat").replace(/[^A-Za-z0-9_-]/g, "_"));
80246
80454
  }
80247
80455
  async function madeInChat(dir) {
80248
80456
  return readdir15(dir).catch(() => []);
@@ -80254,8 +80462,8 @@ async function claimNewCreativeInWorkspace(canvasRoot, slug, chatId, definitionP
80254
80462
  const claim = claimNewCreative(made, slug);
80255
80463
  if (claim.allowed && !made.includes(slug)) {
80256
80464
  try {
80257
- await mkdir15(dir, { recursive: true });
80258
- await writeFile22(path40.join(dir, slug), "", { flag: "wx" });
80465
+ await mkdir17(dir, { recursive: true });
80466
+ await writeFile25(path42.join(dir, slug), "", { flag: "wx" });
80259
80467
  } catch {
80260
80468
  }
80261
80469
  }
@@ -80276,7 +80484,7 @@ async function enforceNewCreativeLimit(slug, canvasPath) {
80276
80484
  canvasOutputsRoot(canvasPath),
80277
80485
  slug,
80278
80486
  optionalChatId() ?? null,
80279
- path40.join(path40.dirname(path40.resolve(canvasPath)), "_definition.md")
80487
+ path42.join(path42.dirname(path42.resolve(canvasPath)), "_definition.md")
80280
80488
  );
80281
80489
  if (claim.allowed) return;
80282
80490
  process.stderr.write(`${JSON.stringify(creativeLimitRefusal(slug), null, 2)}
@@ -80285,22 +80493,22 @@ async function enforceNewCreativeLimit(slug, canvasPath) {
80285
80493
  }
80286
80494
 
80287
80495
  // src/commands/canvas/scaffold-common.ts
80288
- import { access, readFile as readFile32 } from "fs/promises";
80496
+ import { access, readFile as readFile34 } from "fs/promises";
80289
80497
 
80290
80498
  // src/commands/canvas/composition-path.ts
80291
80499
  import { existsSync as existsSync9 } from "fs";
80292
- import path41 from "path";
80500
+ import path43 from "path";
80293
80501
  function resolveShippedCanvasDir(name, startDir, exists3 = existsSync9, maxDepth = 8) {
80294
- const rel = path41.join("canvas", name);
80502
+ const rel = path43.join("canvas", name);
80295
80503
  let dir = startDir;
80296
80504
  for (let i = 0; i < maxDepth; i++) {
80297
- const candidate = path41.join(dir, rel);
80298
- if (exists3(path41.join(candidate, "meta.json"))) return candidate;
80299
- const parent = path41.dirname(dir);
80505
+ const candidate = path43.join(dir, rel);
80506
+ if (exists3(path43.join(candidate, "meta.json"))) return candidate;
80507
+ const parent = path43.dirname(dir);
80300
80508
  if (parent === dir) break;
80301
80509
  dir = parent;
80302
80510
  }
80303
- return path41.resolve(startDir, "../../../", rel);
80511
+ return path43.resolve(startDir, "../../../", rel);
80304
80512
  }
80305
80513
 
80306
80514
  // src/commands/canvas/scaffold-common.ts
@@ -80317,7 +80525,7 @@ function resolveModel(kind, requested, fallback) {
80317
80525
  }
80318
80526
  async function loadAssetText(ref2, label) {
80319
80527
  const r = ref2;
80320
- if (typeof r?.path === "string") return readFile32(r.path, "utf8");
80528
+ if (typeof r?.path === "string") return readFile34(r.path, "utf8");
80321
80529
  if (typeof r?.url === "string") {
80322
80530
  const res = await fetch(r.url);
80323
80531
  if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
@@ -80346,16 +80554,16 @@ function fileExists(target2) {
80346
80554
  }
80347
80555
 
80348
80556
  // src/commands/canvas/scaffold-static-ad-paths.ts
80349
- import path42 from "path";
80557
+ import path44 from "path";
80350
80558
  function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd(), slug) {
80351
80559
  const file = rawFile.trim();
80352
80560
  const imageIsUrl = /^https?:\/\//i.test(file);
80353
- const imageSource = imageIsUrl ? file : path42.resolve(cwd, file);
80354
- const outPath = out ? path42.resolve(cwd, out) : slug ? path42.join(cwd, "src", "creatives", slug, `${slug}.canvas.json`) : imageIsUrl ? path42.join(cwd, "static-ad.canvas.json") : path42.join(path42.dirname(imageSource), "static-ad.canvas.json");
80355
- const blueprintPath2 = path42.join(path42.dirname(outPath), "prompt.json");
80356
- const creativeDir3 = slug ? path42.dirname(outPath) : null;
80357
- const definitionPath = creativeDir3 ? path42.join(creativeDir3, "_definition.md") : null;
80358
- const referencesDir = creativeDir3 ? path42.join(creativeDir3, "references") : null;
80561
+ const imageSource = imageIsUrl ? file : path44.resolve(cwd, file);
80562
+ const outPath = out ? path44.resolve(cwd, out) : slug ? path44.join(cwd, "src", "creatives", slug, `${slug}.canvas.json`) : imageIsUrl ? path44.join(cwd, "static-ad.canvas.json") : path44.join(path44.dirname(imageSource), "static-ad.canvas.json");
80563
+ const blueprintPath2 = path44.join(path44.dirname(outPath), "prompt.json");
80564
+ const creativeDir3 = slug ? path44.dirname(outPath) : null;
80565
+ const definitionPath = creativeDir3 ? path44.join(creativeDir3, "_definition.md") : null;
80566
+ const referencesDir = creativeDir3 ? path44.join(creativeDir3, "references") : null;
80359
80567
  return { imageIsUrl, imageSource, outPath, blueprintPath: blueprintPath2, creativeDir: creativeDir3, definitionPath, referencesDir };
80360
80568
  }
80361
80569
  var SCAFFOLD_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
@@ -80365,8 +80573,8 @@ function isValidScaffoldSlug(slug) {
80365
80573
  }
80366
80574
 
80367
80575
  // src/commands/canvas/sync-definition.ts
80368
- import { readdir as readdir16, readFile as readFile33, stat as stat5 } from "fs/promises";
80369
- import path43 from "path";
80576
+ import { readdir as readdir16, readFile as readFile35, stat as stat5 } from "fs/promises";
80577
+ import path45 from "path";
80370
80578
  import { parseDocument as parseDocument3 } from "yaml";
80371
80579
  import { defineCommand as defineCommand144 } from "citty";
80372
80580
 
@@ -80424,14 +80632,14 @@ function buildDefinitionSyncPayload(input, graph, chatId) {
80424
80632
  }
80425
80633
  async function readDefinitionSourceFields(canvasPath) {
80426
80634
  try {
80427
- return parseDefinitionSourceFields(await readFile33(path43.join(path43.dirname(canvasPath), "_definition.md"), "utf8"));
80635
+ return parseDefinitionSourceFields(await readFile35(path45.join(path45.dirname(canvasPath), "_definition.md"), "utf8"));
80428
80636
  } catch {
80429
80637
  return {};
80430
80638
  }
80431
80639
  }
80432
80640
  async function readDefinitionTitle(canvasPath) {
80433
80641
  try {
80434
- const raw = await readFile33(path43.join(path43.dirname(canvasPath), "_definition.md"), "utf8");
80642
+ const raw = await readFile35(path45.join(path45.dirname(canvasPath), "_definition.md"), "utf8");
80435
80643
  const match = raw.match(/^---\r?\n([\s\S]*?\r?\n)?---/);
80436
80644
  const doc = match ? parseDocument3(match[1] ?? "") : null;
80437
80645
  const title = doc && doc.errors.length === 0 ? doc.get("title") : void 0;
@@ -80442,7 +80650,7 @@ async function readDefinitionTitle(canvasPath) {
80442
80650
  }
80443
80651
  async function readDefinitionStrategyFields(canvasPath) {
80444
80652
  try {
80445
- return parseDefinitionStrategyFields(await readFile33(path43.join(path43.dirname(canvasPath), "_definition.md"), "utf8"));
80653
+ return parseDefinitionStrategyFields(await readFile35(path45.join(path45.dirname(canvasPath), "_definition.md"), "utf8"));
80446
80654
  } catch {
80447
80655
  return {};
80448
80656
  }
@@ -80465,15 +80673,15 @@ async function syncCreativeDefinitionBestEffort(input) {
80465
80673
  }
80466
80674
  }
80467
80675
  async function resolveCanvasPath(inputPath) {
80468
- const resolved = path43.resolve(inputPath);
80676
+ const resolved = path45.resolve(inputPath);
80469
80677
  let dir = resolved;
80470
80678
  try {
80471
80679
  if ((await stat5(resolved)).isFile()) {
80472
80680
  if (resolved.endsWith(".canvas.json")) return resolved;
80473
- dir = path43.dirname(resolved);
80681
+ dir = path45.dirname(resolved);
80474
80682
  }
80475
80683
  } catch {
80476
- dir = resolved.endsWith(".canvas.json") ? path43.dirname(resolved) : resolved;
80684
+ dir = resolved.endsWith(".canvas.json") ? path45.dirname(resolved) : resolved;
80477
80685
  }
80478
80686
  let entries;
80479
80687
  try {
@@ -80484,7 +80692,7 @@ async function resolveCanvasPath(inputPath) {
80484
80692
  const canvases = entries.filter((name) => name.endsWith(".canvas.json"));
80485
80693
  const slug = creativeSlugFromCanvasPath(`${dir}/x/`);
80486
80694
  const chosen = (slug ? canvases.find((name) => name === `${slug}.canvas.json`) : void 0) ?? canvases[0];
80487
- return chosen ? path43.join(dir, chosen) : null;
80695
+ return chosen ? path45.join(dir, chosen) : null;
80488
80696
  }
80489
80697
  var syncDefinitionCommand = defineCommand144({
80490
80698
  meta: {
@@ -80760,32 +80968,32 @@ function mergeReferenceIntoBlueprint(blueprint, reference) {
80760
80968
  }
80761
80969
 
80762
80970
  // src/commands/winning-ads/fit.ts
80763
- import { mkdir as mkdir16, readdir as readdir17, readFile as readFile34, writeFile as writeFile23 } from "fs/promises";
80764
- import path44 from "path";
80971
+ import { mkdir as mkdir18, readdir as readdir17, readFile as readFile36, writeFile as writeFile26 } from "fs/promises";
80972
+ import path46 from "path";
80765
80973
  import { defineCommand as defineCommand145 } from "citty";
80766
80974
  import sharp4 from "sharp";
80767
- import { z as z76 } from "zod";
80768
- var Layers = z76.object({
80769
- disguise: z76.string().optional(),
80770
- voice: z76.string().optional(),
80771
- trigger: z76.string().optional(),
80772
- attention: z76.string().optional(),
80773
- logic: z76.string().optional()
80975
+ import { z as z78 } from "zod";
80976
+ var Layers = z78.object({
80977
+ disguise: z78.string().optional(),
80978
+ voice: z78.string().optional(),
80979
+ trigger: z78.string().optional(),
80980
+ attention: z78.string().optional(),
80981
+ logic: z78.string().optional()
80774
80982
  }).strip();
80775
- var FitAnswerSchema = z76.object({
80983
+ var FitAnswerSchema = z78.object({
80776
80984
  why_it_wins: Layers.default({}),
80777
- load_bearing: z76.array(
80778
- z76.object({
80779
- element: z76.string().trim().min(1),
80780
- needs: z76.string().optional(),
80781
- have: z76.enum(["have", "adapt", "missing"]),
80782
- ours: z76.string().optional()
80985
+ load_bearing: z78.array(
80986
+ z78.object({
80987
+ element: z78.string().trim().min(1),
80988
+ needs: z78.string().optional(),
80989
+ have: z78.enum(["have", "adapt", "missing"]),
80990
+ ours: z78.string().optional()
80783
80991
  }).strip()
80784
80992
  ).default([]),
80785
- verdict: z76.enum(["use", "adapt", "reject"]),
80786
- transfer_note: z76.string().optional(),
80787
- brand_presence: z76.enum(["native", "standard"]).optional(),
80788
- recognition: z76.string().optional()
80993
+ verdict: z78.enum(["use", "adapt", "reject"]),
80994
+ transfer_note: z78.string().optional(),
80995
+ brand_presence: z78.enum(["native", "standard"]).optional(),
80996
+ recognition: z78.string().optional()
80789
80997
  });
80790
80998
  function buildFitPrompt(input) {
80791
80999
  const lib = input.library;
@@ -80846,33 +81054,33 @@ var MAX_DOC_CHARS = 1500;
80846
81054
  var MAX_ENTRY_CHARS = 400;
80847
81055
  var KNOWLEDGE_COLLECTIONS = ["offers", "products", "personas"];
80848
81056
  function readText(file) {
80849
- return readFile34(file, "utf8").catch(() => null);
81057
+ return readFile36(file, "utf8").catch(() => null);
80850
81058
  }
80851
81059
  async function readKnowledge(root) {
80852
81060
  const parts = [];
80853
- const company = await readText(path44.join(root, "COMPANY.md"));
81061
+ const company = await readText(path46.join(root, "COMPANY.md"));
80854
81062
  if (company) parts.push(`COMPANY.md:
80855
81063
  ${company.slice(0, MAX_DOC_CHARS)}`);
80856
- const brand = await readText(path44.join(root, "src", "brand", "BRAND.md"));
81064
+ const brand = await readText(path46.join(root, "src", "brand", "BRAND.md"));
80857
81065
  if (brand) parts.push(`BRAND.md (start):
80858
81066
  ${brand.slice(0, MAX_DOC_CHARS)}`);
80859
81067
  for (const collection of KNOWLEDGE_COLLECTIONS) {
80860
- const dir = path44.join(root, "src", "content", collection);
81068
+ const dir = path46.join(root, "src", "content", collection);
80861
81069
  const files = (await readdir17(dir, { recursive: true }).catch(() => [])).filter(
80862
81070
  (f) => f.endsWith(".md")
80863
81071
  );
80864
81072
  for (const file of files.sort().slice(0, 12)) {
80865
- const text4 = await readText(path44.join(dir, file));
81073
+ const text4 = await readText(path46.join(dir, file));
80866
81074
  if (text4) parts.push(`${collection}/${file}:
80867
81075
  ${text4.slice(0, MAX_ENTRY_CHARS)}`);
80868
81076
  }
80869
81077
  }
80870
81078
  return parts.join("\n\n");
80871
81079
  }
80872
- var ReviewSchema = z76.object({
80873
- reviewText: z76.string(),
80874
- sourceType: z76.string().optional(),
80875
- rating: z76.number().optional()
81080
+ var ReviewSchema = z78.object({
81081
+ reviewText: z78.string(),
81082
+ sourceType: z78.string().optional(),
81083
+ rating: z78.number().optional()
80876
81084
  });
80877
81085
  function reviewsForFit(data) {
80878
81086
  const list2 = Array.isArray(data) ? data : [];
@@ -80894,7 +81102,7 @@ async function readReviews() {
80894
81102
  }
80895
81103
  }
80896
81104
  function fitCachePath(root, platform, adId) {
80897
- return path44.join(root, "canvas", ".cache", "fit", `${platform}-${adId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
81105
+ return path46.join(root, "canvas", ".cache", "fit", `${platform}-${adId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
80898
81106
  }
80899
81107
  async function readCachedFit(root, platform, adId) {
80900
81108
  const raw = await readText(fitCachePath(root, platform, adId));
@@ -80949,8 +81157,8 @@ async function fitOnce(root, platform, adId, ask) {
80949
81157
  const fit = await ask();
80950
81158
  if (!fit) return null;
80951
81159
  const file = fitCachePath(root, platform, adId);
80952
- await mkdir16(path44.dirname(file), { recursive: true });
80953
- await writeFile23(file, `${JSON.stringify(fit, null, 2)}
81160
+ await mkdir18(path46.dirname(file), { recursive: true });
81161
+ await writeFile26(file, `${JSON.stringify(fit, null, 2)}
80954
81162
  `, "utf8");
80955
81163
  return fit;
80956
81164
  }
@@ -80988,7 +81196,7 @@ async function fitReference(root, platform, adId) {
80988
81196
  const prompt = buildFitPrompt({ library: libraryRead(ad), knowledge: knowledge2 });
80989
81197
  const spent = [];
80990
81198
  const answer = await askFit(jpeg, prompt, (u) => spent.push(u));
80991
- await holdUnbilledSpend(path44.join(root, "canvas", ".cache"), spent);
81199
+ await holdUnbilledSpend(path46.join(root, "canvas", ".cache"), spent);
80992
81200
  return answer;
80993
81201
  });
80994
81202
  if (fit) return { ok: true, fit };
@@ -81041,7 +81249,7 @@ var fitCommand = defineCommand145({
81041
81249
 
81042
81250
  // src/commands/canvas/scaffold-static-ad.ts
81043
81251
  function toCanvasRelative2(canvasDir, file) {
81044
- const rel = path45.relative(canvasDir, file).split(path45.sep).join("/");
81252
+ const rel = path47.relative(canvasDir, file).split(path47.sep).join("/");
81045
81253
  return rel.startsWith(".") ? rel : `./${rel}`;
81046
81254
  }
81047
81255
  async function uploadSourceAsReference(source, isUrl, client) {
@@ -81053,7 +81261,7 @@ async function uploadSourceAsReference(source, isUrl, client) {
81053
81261
  throw new Error(`failed to download source image (${e instanceof Error ? e.message : String(e)})`);
81054
81262
  }
81055
81263
  } else {
81056
- bytes = await readFile35(source);
81264
+ bytes = await readFile37(source);
81057
81265
  }
81058
81266
  const safe = await toModelSafeImage(bytes);
81059
81267
  const sha2562 = sha256Hex(safe.bytes);
@@ -81333,25 +81541,25 @@ async function lookupReference(args8) {
81333
81541
  }
81334
81542
  async function scaffoldFromSpec(args8, slug) {
81335
81543
  if (!slug) return fail4("missing_slug", "--spec needs --slug: an ad started from a spec is a creative.");
81336
- const specPath = path45.resolve(process.cwd(), String(args8.spec));
81544
+ const specPath = path47.resolve(process.cwd(), String(args8.spec));
81337
81545
  let started;
81338
81546
  try {
81339
- started = readAdSpec(JSON.parse(await readFile35(specPath, "utf8")));
81547
+ started = readAdSpec(JSON.parse(await readFile37(specPath, "utf8")));
81340
81548
  } catch (e) {
81341
81549
  return fail4("invalid_spec", `${specPath} is not an ad spec: ${e instanceof Error ? e.message : String(e)}`);
81342
81550
  }
81343
- const outPath = path45.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`);
81344
- const dir = path45.dirname(outPath);
81345
- const blueprintPath2 = path45.join(dir, "prompt.json");
81346
- const definitionPath = path45.join(dir, "_definition.md");
81551
+ const outPath = path47.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`);
81552
+ const dir = path47.dirname(outPath);
81553
+ const blueprintPath2 = path47.join(dir, "prompt.json");
81554
+ const definitionPath = path47.join(dir, "_definition.md");
81347
81555
  await enforceNewCreativeLimit(slug, outPath);
81348
- const existingDefinition = await fileExists(definitionPath) ? await readFile35(definitionPath, "utf8") : null;
81556
+ const existingDefinition = await fileExists(definitionPath) ? await readFile37(definitionPath, "utf8") : null;
81349
81557
  const who = await scaffoldViewer(args8, existingDefinition, liveViewerLookups(process.cwd()).adSetViewer);
81350
81558
  if (!who.ok) return fail4("invalid_viewer", who.message);
81351
- await mkdir17(dir, { recursive: true });
81559
+ await mkdir19(dir, { recursive: true });
81352
81560
  const workspaceRoot = await findBrandRoot(dir) ?? process.cwd();
81353
81561
  const kit = await readStaticBrandKit(workspaceRoot);
81354
- const market = who.viewer?.market ?? marketFromCompanyDoc(await readFile35(path45.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null));
81562
+ const market = who.viewer?.market ?? marketFromCompanyDoc(await readFile37(path47.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null));
81355
81563
  const brand = brandBlock(kit);
81356
81564
  const platform = resolvePlatform(args8.platform ? String(args8.platform) : void 0);
81357
81565
  const placementsResult = resolvePlacements(args8.placements ? String(args8.placements) : void 0, platform);
@@ -81373,18 +81581,18 @@ async function scaffoldFromSpec(args8, slug) {
81373
81581
  logo: Boolean(kit.logo),
81374
81582
  ...market ? { viewer: { ...started.viewer ?? { who: "the people this ad is for" }, market } } : {}
81375
81583
  };
81376
- await writeFile24(blueprintPath2, `${JSON.stringify(spec, null, 2)}
81584
+ await writeFile27(blueprintPath2, `${JSON.stringify(spec, null, 2)}
81377
81585
  `, "utf8");
81378
81586
  const canvas = scaffoldStaticAd({}, [], { ...opts, spec });
81379
81587
  const validation = await validateScaffoldedStaticAd(canvas, dir);
81380
81588
  if (validation.blocking.length > 0) return fail4("validation", validation.blocking.map((i) => i.message).join("; "));
81381
- await writeFile24(outPath, `${JSON.stringify(canvas, null, 2)}
81589
+ await writeFile27(outPath, `${JSON.stringify(canvas, null, 2)}
81382
81590
  `, "utf8");
81383
81591
  const title = args8.title ? String(args8.title) : titleFromSlug(slug);
81384
81592
  const recorded = who.record ? { targetAdSet: who.targetAdSet, viewer: who.viewer } : {};
81385
81593
  if (existingDefinition === null) {
81386
81594
  const formats = resolveFormatList(report.output_formats);
81387
- await writeFile24(definitionPath, buildCreativeDefinition({ title, kind: "static", platform, formats, ...recorded }), "utf8");
81595
+ await writeFile27(definitionPath, buildCreativeDefinition({ title, kind: "static", platform, formats, ...recorded }), "utf8");
81388
81596
  }
81389
81597
  await syncCreativeDefinitionBestEffort({
81390
81598
  slug,
@@ -81498,14 +81706,14 @@ var scaffoldStaticAdCommand = defineCommand146({
81498
81706
  }
81499
81707
  const { imageIsUrl, imageSource, outPath, blueprintPath: blueprintPath2, definitionPath, referencesDir } = resolveScaffoldStaticAdPaths(sourceFile, args8.out ? String(args8.out) : void 0, process.cwd(), slug);
81500
81708
  if (slug) await enforceNewCreativeLimit(slug, outPath);
81501
- const existingDefinition = definitionPath && await fileExists(definitionPath) ? await readFile35(definitionPath, "utf8") : null;
81709
+ const existingDefinition = definitionPath && await fileExists(definitionPath) ? await readFile37(definitionPath, "utf8") : null;
81502
81710
  const who = await scaffoldViewer(args8, existingDefinition, liveViewerLookups(process.cwd()).adSetViewer);
81503
81711
  if (!who.ok) return fail4("invalid_viewer", who.message);
81504
81712
  const fitRoot = findWorkspaceRoot(process.cwd()) ?? process.cwd();
81505
81713
  const fit = reference || lookup.status === "unavailable" ? await readCachedFit(fitRoot, reference?.platform ?? (lookup.status === "unavailable" ? lookup.platform : "meta"), reference?.ad_id ?? (lookup.status === "unavailable" ? lookup.adId : "")) : null;
81506
81714
  const fitProblems = fit ? fitHints(fit, reference?.ad_id ?? "").filter((h) => h.startsWith("Pick another reference")) : [];
81507
81715
  if (fitProblems.length > 0) return fail4("concept_does_not_fit", fitProblems.join(" "));
81508
- await mkdir17(path45.dirname(outPath), { recursive: true });
81716
+ await mkdir19(path47.dirname(outPath), { recursive: true });
81509
81717
  let models;
81510
81718
  try {
81511
81719
  models = resolveModels(args8);
@@ -81539,10 +81747,10 @@ var scaffoldStaticAdCommand = defineCommand146({
81539
81747
  const persona = existingDefinition?.match(/^persona:\s*"?([^"\n]+)"?\s*$/m)?.[1];
81540
81748
  const elements = castAvatarsOnStaticElements(vision.elements, cast.avatars, persona);
81541
81749
  const uncast = staticAvatarsNotCast(elements, cast.handles);
81542
- const canvasDir = path45.dirname(outPath);
81750
+ const canvasDir = path47.dirname(outPath);
81543
81751
  const workspaceRoot = await findBrandRoot(canvasDir) ?? process.cwd();
81544
81752
  const kit = await readStaticBrandKit(workspaceRoot);
81545
- const market = who.viewer?.market ?? marketFromCompanyDoc(await readFile35(path45.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null));
81753
+ const market = who.viewer?.market ?? marketFromCompanyDoc(await readFile37(path47.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null));
81546
81754
  const brand = brandBlock(kit);
81547
81755
  let canvasImagePath = imageSource;
81548
81756
  let canvasImageIsUrl = imageIsUrl;
@@ -81592,7 +81800,7 @@ var scaffoldStaticAdCommand = defineCommand146({
81592
81800
  ...fit ? { fit: conceptFromFit(fit, reference?.ad_id ?? "") } : {},
81593
81801
  ...market ? { market } : {}
81594
81802
  });
81595
- await writeFile24(blueprintPath2, `${JSON.stringify(spec, null, 2)}
81803
+ await writeFile27(blueprintPath2, `${JSON.stringify(spec, null, 2)}
81596
81804
  `, "utf8");
81597
81805
  conceptNotes = conceptProblems(spec).map(
81598
81806
  (p) => `CONCEPT: ${p} Then \`baker canvas run\`; until then it refuses to render.`
@@ -81606,7 +81814,7 @@ var scaffoldStaticAdCommand = defineCommand146({
81606
81814
  } catch (e) {
81607
81815
  return fail4("scaffold", e instanceof Error ? e.message : String(e));
81608
81816
  }
81609
- const validation = await validateScaffoldedStaticAd(canvas, path45.dirname(outPath));
81817
+ const validation = await validateScaffoldedStaticAd(canvas, path47.dirname(outPath));
81610
81818
  if (validation.blocking.length > 0) {
81611
81819
  process.stderr.write(
81612
81820
  `${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.blocking } }, null, 2)}
@@ -81614,7 +81822,7 @@ var scaffoldStaticAdCommand = defineCommand146({
81614
81822
  );
81615
81823
  process.exit(2);
81616
81824
  }
81617
- await writeFile24(outPath, `${JSON.stringify(canvas, null, 2)}
81825
+ await writeFile27(outPath, `${JSON.stringify(canvas, null, 2)}
81618
81826
  `, "utf8");
81619
81827
  const sourceFields = reference ? {
81620
81828
  sourceAdId: reference.ad_id,
@@ -81627,10 +81835,10 @@ var scaffoldStaticAdCommand = defineCommand146({
81627
81835
  } : {};
81628
81836
  const recorded = who.record ? { targetAdSet: who.targetAdSet, viewer: who.viewer } : {};
81629
81837
  if (definitionPath && existingDefinition !== null && who.record) {
81630
- await writeFile24(definitionPath, setDefinitionViewer(existingDefinition, recorded), "utf8");
81838
+ await writeFile27(definitionPath, setDefinitionViewer(existingDefinition, recorded), "utf8");
81631
81839
  }
81632
81840
  if (definitionPath && existingDefinition === null) {
81633
- await writeFile24(
81841
+ await writeFile27(
81634
81842
  definitionPath,
81635
81843
  buildCreativeDefinition({
81636
81844
  title: args8.title ? String(args8.title) : titleFromSlug(slug ?? ""),
@@ -81703,7 +81911,7 @@ var scaffoldStaticAdCommand = defineCommand146({
81703
81911
  run_estimated_credits: validation.estimatedCredits
81704
81912
  },
81705
81913
  checklist: {
81706
- edit_prompt: `Edit ${path45.basename(blueprintPath2)}: it is the ad spec, read from your image, to rewrite into the ad you want. \`viewer\` (who sees it and what they already know), \`message\` (the one thing it tells them), \`copy\` (each line, \`in_scene\` or \`designed\`), \`scene\`, \`references\` (one picture each, by role), \`colors\` (each tied to an object, from the brand) and an optional \`direction\`. You never write a model prompt: every render prompt is compiled from this file and shown on its node.${reference ? " Its `reference` block is the library's read of the source ad (container, layout, mechanism, offer, text blocks): keep it as the account of why the ad won, and rebuild it for our brand." : ""}`,
81914
+ edit_prompt: `Edit ${path47.basename(blueprintPath2)}: it is the ad spec, read from your image, to rewrite into the ad you want. \`viewer\` (who sees it and what they already know), \`message\` (the one thing it tells them), \`copy\` (each line, \`in_scene\` or \`designed\`), \`scene\`, \`references\` (one picture each, by role), \`colors\` (each tied to an object, from the brand) and an optional \`direction\`. You never write a model prompt: every render prompt is compiled from this file and shown on its node.${reference ? " Its `reference` block is the library's read of the source ad (container, layout, mechanism, offer, text blocks): keep it as the account of why the ad won, and rebuild it for our brand." : ""}`,
81707
81915
  mechanism: report.winning_mechanisms,
81708
81916
  rebuild_the_mechanism: "`mechanism` is WHY the source ad works. Rebuild each device for our brand in prompt.json's copy and scene (a rhyme stays a rhyme in our words, a before/after stays a before/after of our product); never copy the source's literal copy.",
81709
81917
  assets_to_supply: report.elements,
@@ -81765,25 +81973,25 @@ function clientLogoMissNote(decision, domain) {
81765
81973
  }
81766
81974
 
81767
81975
  // src/commands/canvas/scaffold-ad.ts
81768
- import { copyFile, cp, mkdir as mkdir20, readFile as readFile39, stat as stat6, writeFile as writeFile28 } from "fs/promises";
81976
+ import { copyFile, cp, mkdir as mkdir20, readFile as readFile39, stat as stat6, writeFile as writeFile29 } from "fs/promises";
81769
81977
  import path49 from "path";
81770
81978
  import { defineCommand as defineCommand147 } from "citty";
81771
81979
 
81772
81980
  // src/engine/scaffold/ad-spec.ts
81773
- import { z as z77 } from "zod";
81981
+ import { z as z79 } from "zod";
81774
81982
  var oneClause = (line) => (line.match(/[.!?](\s|$)/g) ?? []).length <= 1;
81775
81983
  var isSpeech = (line) => new RegExp("\\p{L}\\p{L}", "u").test(line);
81776
- var Beat = z77.object({
81984
+ var Beat = z79.object({
81777
81985
  /**
81778
81986
  * The line the voice says in this beat — ONE clause, ending in its own
81779
81987
  * punctuation. It becomes the caption card verbatim.
81780
81988
  */
81781
- say: z77.string().min(1).refine(
81989
+ say: z79.string().min(1).refine(
81782
81990
  isSpeech,
81783
81991
  "a beat's line has to be words someone says \u2014 a dot or a dash is not a line, and an ad with no voiceover is not supported yet"
81784
81992
  ).refine(oneClause, "a beat is one clause: split a second sentence into its own beat"),
81785
81993
  /** What is on screen while it is said, as a shot brief for the video model. */
81786
- show: z77.string().min(1),
81994
+ show: z79.string().min(1),
81787
81995
  /**
81788
81996
  * What this shot SOUNDS like — one effect, in words. Usually absent.
81789
81997
  *
@@ -81800,7 +82008,7 @@ var Beat = z77.object({
81800
82008
  * Describe the SOUND, not the picture: "a cordless drill tightening a bolt", not "the
81801
82009
  * fitter works on the rail".
81802
82010
  */
81803
- sound: z77.string().min(1).max(200).optional(),
82011
+ sound: z79.string().min(1).max(200).optional(),
81804
82012
  /**
81805
82013
  * Whether this beat's subject is talking to camera.
81806
82014
  *
@@ -81814,36 +82022,36 @@ var Beat = z77.object({
81814
82022
  * way through" produced an ad with her face on screen and a narrator cast from a prose
81815
82023
  * description reading her lines, because nothing set this flag.
81816
82024
  */
81817
- on_camera: z77.boolean().optional(),
82025
+ on_camera: z79.boolean().optional(),
81818
82026
  /**
81819
82027
  * Whether the ad's recurring person is in this shot. Defaults to true whenever the
81820
82028
  * spec declares a `cast`, because an ad is usually about one person and defaulting
81821
82029
  * the other way is how continuity gets forgotten. Set false for a beat they are not
81822
82030
  * in — a fitter on a roof, a product close-up.
81823
82031
  */
81824
- cast: z77.boolean().optional()
82032
+ cast: z79.boolean().optional()
81825
82033
  });
81826
- var AdSpec = z77.object({
81827
- format: z77.object({
81828
- aspect_ratio: z77.string().default("9:16"),
81829
- resolution: z77.string().default("720p")
82034
+ var AdSpec = z79.object({
82035
+ format: z79.object({
82036
+ aspect_ratio: z79.string().default("9:16"),
82037
+ resolution: z79.string().default("720p")
81830
82038
  }).default({ aspect_ratio: "9:16", resolution: "720p" }),
81831
- brand: z77.object({
81832
- name: z77.string().optional(),
82039
+ brand: z79.object({
82040
+ name: z79.string().optional(),
81833
82041
  /** Figures to lift typographically — the one gold number every ad has. */
81834
- highlight: z77.array(z77.string()).optional(),
82042
+ highlight: z79.array(z79.string()).optional(),
81835
82043
  /**
81836
82044
  * Brand hex colours, most important first. They dress the brand plate and
81837
82045
  * the type — never the scenes: pushing hex into a shot brief makes the
81838
82046
  * model paint the whole frame that colour, which reads as a filter.
81839
82047
  */
81840
- palette: z77.array(z77.string()).optional(),
82048
+ palette: z79.array(z79.string()).optional(),
81841
82049
  /** Repo path to the brand mark, so the CTA carries it instead of a name in a font. */
81842
- logo: z77.string().optional()
82050
+ logo: z79.string().optional()
81843
82051
  }).optional(),
81844
- voice: z77.object({
81845
- description: z77.string().min(1),
81846
- language: z77.string().optional()
82052
+ voice: z79.object({
82053
+ description: z79.string().min(1),
82054
+ language: z79.string().optional()
81847
82055
  }).optional(),
81848
82056
  /**
81849
82057
  * Where the ad is set, as a place a model can picture: "Spain", "Mexico",
@@ -81855,7 +82063,7 @@ var AdSpec = z77.object({
81855
82063
  * houses — sash windows, slate roofs, English suburbs — in every single frame,
81856
82064
  * from briefs that were otherwise perfectly good.
81857
82065
  */
81858
- market: z77.string().min(1).optional(),
82066
+ market: z79.string().min(1).optional(),
81859
82067
  /**
81860
82068
  * The one person the ad is about.
81861
82069
  *
@@ -81865,7 +82073,7 @@ var AdSpec = z77.object({
81865
82073
  * with five different men playing one customer, which reads as a stock-footage
81866
82074
  * collage rather than a story.
81867
82075
  */
81868
- cast: z77.object({
82076
+ cast: z79.object({
81869
82077
  /**
81870
82078
  * A cast Avatar's handle — `baker avatars list` shows who there is.
81871
82079
  *
@@ -81875,9 +82083,9 @@ var AdSpec = z77.object({
81875
82083
  * rather than re-written per spec — re-phrasing it per render is the other reason
81876
82084
  * a face drifts across a set.
81877
82085
  */
81878
- avatar: z77.string().min(1).optional(),
82086
+ avatar: z79.string().min(1).optional(),
81879
82087
  /** Who they are, plainly: age, build, hair, wardrobe, the register of the ad. */
81880
- description: z77.string().min(1).optional()
82088
+ description: z79.string().min(1).optional()
81881
82089
  }).refine((c) => Boolean(c.avatar || c.description), {
81882
82090
  message: "give `avatar` (a cast avatar's handle) or `description` \u2014 a cast with neither grounds on nothing"
81883
82091
  }).optional(),
@@ -81894,7 +82102,7 @@ var AdSpec = z77.object({
81894
82102
  * has to read clean. That is the engine's rule, not this one, and it is why the prompt
81895
82103
  * is all this needs to carry.
81896
82104
  */
81897
- music: z77.union([z77.string().min(1), z77.literal(false)]).optional(),
82105
+ music: z79.union([z79.string().min(1), z79.literal(false)]).optional(),
81898
82106
  /**
81899
82107
  * The place the ad keeps coming back to, held still across every shot.
81900
82108
  *
@@ -81909,9 +82117,9 @@ var AdSpec = z77.object({
81909
82117
  * is `image_generate` with extra steps — the model registry says exactly that. Generate
81910
82118
  * the plate once with gpt-image-2 and pass its URL here.
81911
82119
  */
81912
- set: z77.object({
81913
- description: z77.string().min(1).max(400),
81914
- image: z77.string().min(1).optional()
82120
+ set: z79.object({
82121
+ description: z79.string().min(1).max(400),
82122
+ image: z79.string().min(1).optional()
81915
82123
  }).optional(),
81916
82124
  /**
81917
82125
  * How the ad closes.
@@ -81925,15 +82133,15 @@ var AdSpec = z77.object({
81925
82133
  *
81926
82134
  * `false` opts out, for an ad that genuinely ends on its footage.
81927
82135
  */
81928
- end_card: z77.union([
81929
- z77.literal(false),
81930
- z77.object({
82136
+ end_card: z79.union([
82137
+ z79.literal(false),
82138
+ z79.object({
81931
82139
  /**
81932
82140
  * The words on the button. No default — with none the card carries the mark
81933
82141
  * alone, because falling back to the last beat's line printed that sentence
81934
82142
  * twice on the closing frame. See `endCardCta`.
81935
82143
  */
81936
- cta: z77.string().min(1).optional()
82144
+ cta: z79.string().min(1).optional()
81937
82145
  })
81938
82146
  ]).optional(),
81939
82147
  /**
@@ -81953,8 +82161,8 @@ var AdSpec = z77.object({
81953
82161
  * rather than from transcribing a rendered read, so the text is exact and no speech
81954
82162
  * model is between the copy and the screen.
81955
82163
  */
81956
- voiceover: z77.boolean().default(true),
81957
- beats: z77.array(Beat).min(2)
82164
+ voiceover: z79.boolean().default(true),
82165
+ beats: z79.array(Beat).min(2)
81958
82166
  });
81959
82167
  function presenterIsInEveryShot(spec) {
81960
82168
  if (!spec.cast) return false;
@@ -82257,33 +82465,6 @@ function beatsTooShortToPlay(spec) {
82257
82465
  return spec.beats.map((beat, i) => ({ i, words: beat.say.trim().split(/\s+/).filter(Boolean).length })).filter(({ words }) => words < MIN_BEAT_WORDS).map(({ i }) => i + 1);
82258
82466
  }
82259
82467
 
82260
- // src/engine/nodes/local/lib/caption-groups.ts
82261
- function captionWordKey(text4) {
82262
- return text4.replace(/[.,;:!?…]+$/, "").toLocaleLowerCase();
82263
- }
82264
- var CARRIES_ON_HELD = 0.4;
82265
- var CARRIES_ON_QUESTION = "Is `word`, as used in `line`, a function word that cannot close a phrase: an article, a preposition, a conjunction, a possessive or another determiner? A noun, verb, adjective, adverb, name or number is not.";
82266
- function carriesOnQuestions(lines) {
82267
- const seen = /* @__PURE__ */ new Set();
82268
- const questions = [];
82269
- for (const line of lines) {
82270
- for (const text4 of line.split(/\s+/)) {
82271
- const word = captionWordKey(text4);
82272
- if (!word || seen.has(word)) continue;
82273
- seen.add(word);
82274
- questions.push({ type: "noul", instructions: { line, word, question: CARRIES_ON_QUESTION } });
82275
- }
82276
- }
82277
- return { questions, words: [...seen] };
82278
- }
82279
- function carriesOnFromAnswers(words, answers) {
82280
- if (!answers) return [];
82281
- return words.filter((_, i) => {
82282
- const answer = answers[i];
82283
- return answer !== void 0 && "noul" in answer && answer.noul >= CARRIES_ON_HELD;
82284
- });
82285
- }
82286
-
82287
82468
  // src/engine/scaffold/ad-spec-reading.ts
82288
82469
  var INTRODUCES_SPEAKER_HELD = 0.5;
82289
82470
  var WANTS_LEGIBLE_TEXT_HELD = 0.5;
@@ -82354,93 +82535,6 @@ async function readAdSpec2(spec, options, readers) {
82354
82535
  };
82355
82536
  }
82356
82537
 
82357
- // src/engine/nodes/local/lib/jev-cache.ts
82358
- import { createHash as createHash10 } from "crypto";
82359
- import { mkdir as mkdir18, readFile as readFile36, writeFile as writeFile25 } from "fs/promises";
82360
- import path46 from "path";
82361
- import { z as z78 } from "zod";
82362
- var Kept = z78.array(
82363
- z78.union([
82364
- z78.object({ choice: z78.string(), probabilities: z78.record(z78.string(), z78.number()), confidence: z78.number() }),
82365
- z78.object({ noul: z78.number() })
82366
- ])
82367
- );
82368
- function cachedJev(cacheDir, ask) {
82369
- const dir = path46.join(cacheDir, "jev");
82370
- return async (questions) => {
82371
- const key = createHash10("sha256").update(JSON.stringify([JEV_MODEL, questions])).digest("hex");
82372
- const file = path46.join(dir, `${key}.json`);
82373
- const kept = Kept.safeParse(
82374
- await readFile36(file, "utf8").then(JSON.parse, () => null).catch(() => null)
82375
- );
82376
- if (kept.success && kept.data.length === questions.length) return kept.data;
82377
- const answers = await ask(questions);
82378
- if (answers) {
82379
- await mkdir18(dir, { recursive: true });
82380
- await writeFile25(file, JSON.stringify(answers), "utf8");
82381
- }
82382
- return answers;
82383
- };
82384
- }
82385
-
82386
- // src/engine/scaffold/video/dialogue/voice-gender.ts
82387
- import { createHash as createHash11 } from "crypto";
82388
- import { mkdir as mkdir19, readFile as readFile37, writeFile as writeFile26 } from "fs/promises";
82389
- import path47 from "path";
82390
- import { z as z79 } from "zod";
82391
- var VOICE_GENDER_MODEL = "google/gemini-2.5-flash-lite";
82392
- var PROMPT_VERSION2 = 1;
82393
- var REQUEST2 = {
82394
- provider: { order: ["google-vertex"], allow_fallbacks: false },
82395
- temperature: 0,
82396
- response_format: { type: "json_object" }
82397
- };
82398
- var TIMEOUT_MS2 = 2e4;
82399
- function voiceGenderPrompt(voices) {
82400
- return [
82401
- "Each line below describes the voice wanted for a speaker in an ad, written in any language.",
82402
- "For each, say whether it asks for a woman's voice or a man's voice. Read it in its own language: a noun that is feminine or masculine by itself says so. Answer null when it does not say.",
82403
- "",
82404
- ...voices.map((voice, i) => `${i + 1}. ${JSON.stringify(voice)}`),
82405
- "",
82406
- `Reply with only this JSON: {"genders": ["female" | "male" | null, ... one per line, ${voices.length} in order]}`
82407
- ].join("\n");
82408
- }
82409
- var Answer4 = z79.object({ genders: z79.array(z79.enum(["female", "male"]).nullable()) });
82410
- function parseVoiceGenders(answer, voices) {
82411
- const parsed = Answer4.safeParse(answer);
82412
- if (!parsed.success || parsed.data.genders.length !== voices.length) return null;
82413
- const out = {};
82414
- voices.forEach((voice, i) => {
82415
- const gender = parsed.data.genders[i];
82416
- if (gender) out[voice] = gender;
82417
- });
82418
- return out;
82419
- }
82420
- function voiceGenderModel(cacheDir, onSpend) {
82421
- const dir = path47.join(cacheDir, "voice-gender");
82422
- return async (voices) => {
82423
- if (voices.length === 0) return {};
82424
- const key = createHash11("sha256").update(JSON.stringify([PROMPT_VERSION2, VOICE_GENDER_MODEL, voices])).digest("hex");
82425
- const file = path47.join(dir, `${key}.json`);
82426
- const kept = parseVoiceGenders(
82427
- await readFile37(file, "utf8").then(JSON.parse, () => null).catch(() => null),
82428
- voices
82429
- );
82430
- if (kept) return kept;
82431
- const answer = await askAboutText(VOICE_GENDER_MODEL, voiceGenderPrompt(voices), onSpend, {
82432
- timeoutMs: TIMEOUT_MS2,
82433
- request: REQUEST2
82434
- }).catch(() => null);
82435
- const genders = parseVoiceGenders(answer, voices);
82436
- if (genders) {
82437
- await mkdir19(dir, { recursive: true });
82438
- await writeFile26(file, JSON.stringify(answer), "utf8");
82439
- }
82440
- return genders;
82441
- };
82442
- }
82443
-
82444
82538
  // src/engine/scaffold/ad-display-face.ts
82445
82539
  var AD_DISPLAY_FAMILY = "AdDisplay";
82446
82540
  function displayFontStack(face) {
@@ -82646,7 +82740,7 @@ function injectAdBrandOverlay(indexHtml, overlayHtml, style = {}) {
82646
82740
  }
82647
82741
 
82648
82742
  // src/commands/canvas/composition-dims.ts
82649
- import { readFile as readFile38, writeFile as writeFile27 } from "fs/promises";
82743
+ import { readFile as readFile38, writeFile as writeFile28 } from "fs/promises";
82650
82744
  import path48 from "path";
82651
82745
  function patchCompositionMeta(metaJson, dims) {
82652
82746
  try {
@@ -82664,10 +82758,10 @@ function patchCompositionHtml(html, dims) {
82664
82758
  async function stampCompositionDims(compositionDir, dims) {
82665
82759
  const metaPath = path48.join(compositionDir, "meta.json");
82666
82760
  const rawMeta = await readFile38(metaPath, "utf8");
82667
- await writeFile27(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
82761
+ await writeFile28(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
82668
82762
  const htmlPath = path48.join(compositionDir, "index.html");
82669
82763
  const rawHtml = await readFile38(htmlPath, "utf8");
82670
- await writeFile27(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
82764
+ await writeFile28(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
82671
82765
  }
82672
82766
 
82673
82767
  // src/commands/canvas/scaffold-ad.ts
@@ -83011,21 +83105,21 @@ var scaffoldAdCommand = defineCommand147({
83011
83105
  await stampCompositionDims(compositionDest, outDims);
83012
83106
  await stampCompositionDims(captionsDest, outDims);
83013
83107
  const captionsIndex = path49.join(captionsDest, "index.html");
83014
- await writeFile28(captionsIndex, injectAdCaptionPlacement(await readFile39(captionsIndex, "utf-8"), outAr), "utf-8");
83108
+ await writeFile29(captionsIndex, injectAdCaptionPlacement(await readFile39(captionsIndex, "utf-8"), outAr), "utf-8");
83015
83109
  const face = await stageBrandDisplayFace(outDir, captionsDest);
83016
83110
  if (face) {
83017
- await writeFile28(captionsIndex, injectAdDisplayFace(await readFile39(captionsIndex, "utf-8"), face.file), "utf-8");
83111
+ await writeFile29(captionsIndex, injectAdDisplayFace(await readFile39(captionsIndex, "utf-8"), face.file), "utf-8");
83018
83112
  }
83019
83113
  const blueprintPath2 = path49.join(outDir, "prompt.json");
83020
83114
  const blueprintStylePath = path49.join(outDir, "prompt.style.json");
83021
83115
  await writeSceneFiles(outDir, blueprint);
83022
- await writeFile28(blueprintStylePath, renderStyleProjectionFromValue(blueprint), "utf8");
83023
- await writeFile28(path49.join(outDir, AD_SPEC_FILE), `${JSON.stringify(specArtifact, null, 2)}
83116
+ await writeFile29(blueprintStylePath, renderStyleProjectionFromValue(blueprint), "utf8");
83117
+ await writeFile29(path49.join(outDir, AD_SPEC_FILE), `${JSON.stringify(specArtifact, null, 2)}
83024
83118
  `, "utf8");
83025
83119
  const logoPath = noBrand ? void 0 : spec.data.brand?.logo?.trim();
83026
83120
  const transcriptPath = path49.join(outDir, "transcript.json");
83027
83121
  if (spec.data.voiceover === false) {
83028
- await writeFile28(transcriptPath, `${JSON.stringify(staticTranscript(spec.data, chosenVideoModel), null, 2)}
83122
+ await writeFile29(transcriptPath, `${JSON.stringify(staticTranscript(spec.data, chosenVideoModel), null, 2)}
83029
83123
  `, "utf-8");
83030
83124
  }
83031
83125
  const opts = {
@@ -83109,9 +83203,9 @@ var scaffoldAdCommand = defineCommand147({
83109
83203
  if (overlayHtml && renderedDir) {
83110
83204
  const indexPath = path49.join(renderedDir, "index.html");
83111
83205
  const html = await readFile39(indexPath, "utf-8");
83112
- await writeFile28(indexPath, injectAdBrandOverlay(html, overlayHtml, { aspect: outAr, ...face ? { face: AD_DISPLAY_FAMILY } : {} }), "utf-8");
83206
+ await writeFile29(indexPath, injectAdBrandOverlay(html, overlayHtml, { aspect: outAr, ...face ? { face: AD_DISPLAY_FAMILY } : {} }), "utf-8");
83113
83207
  }
83114
- await writeFile28(
83208
+ await writeFile29(
83115
83209
  path49.join(outDir, REBUILD_FILE),
83116
83210
  `${JSON.stringify({ elements: adSpecCastElements(spec.data, avatar), opts }, null, 2)}
83117
83211
  `,
@@ -83122,7 +83216,7 @@ var scaffoldAdCommand = defineCommand147({
83122
83216
  const meta = canvas.metadata;
83123
83217
  meta.todo = { ...meta.todo ?? {}, blocking_validation_issues: validation.issues };
83124
83218
  }
83125
- await writeFile28(outPath, `${JSON.stringify(canvas, null, 2)}
83219
+ await writeFile29(outPath, `${JSON.stringify(canvas, null, 2)}
83126
83220
  `, "utf-8");
83127
83221
  if (!validation.ok) {
83128
83222
  process.stderr.write(
@@ -83145,7 +83239,7 @@ var scaffoldAdCommand = defineCommand147({
83145
83239
  const definitionPath = path49.join(outDir, "_definition.md");
83146
83240
  const definitionExists = await stat6(definitionPath).then(() => true, () => false);
83147
83241
  if (!definitionExists) {
83148
- await writeFile28(
83242
+ await writeFile29(
83149
83243
  definitionPath,
83150
83244
  buildCreativeDefinition({
83151
83245
  title: titleFromSlug(slug),
@@ -83269,7 +83363,7 @@ async function fetchClientLogo() {
83269
83363
  const dir = path49.join(BRAND_DIR, "logos");
83270
83364
  await mkdir20(dir, { recursive: true });
83271
83365
  const file = path49.join(dir, `brand-mark.${extension}`);
83272
- await writeFile28(file, bytes);
83366
+ await writeFile29(file, bytes);
83273
83367
  return { ok: true, path: file };
83274
83368
  } catch {
83275
83369
  return { ok: false, reason: clientLogoMissNote({ use: false, reason: "no_hits" }, domain) };
@@ -83294,7 +83388,7 @@ async function stageBrandDisplayFace(outDir, compositionDir) {
83294
83388
  const { buffer } = await fetchExternalBytes(face.slot.url, { maxBytes: MAX_FONT_BYTES });
83295
83389
  const extension = /\.(woff2|woff|ttf|otf)(?:\?|$)/i.exec(face.slot.url)?.[1]?.toLowerCase() ?? "woff2";
83296
83390
  const file = `brand-face.${extension}`;
83297
- await writeFile28(path49.join(compositionDir, file), buffer);
83391
+ await writeFile29(path49.join(compositionDir, file), buffer);
83298
83392
  return { file, label: `${face.family ?? "the brand's typeface"}, loaded as a web font` };
83299
83393
  } catch {
83300
83394
  return null;
@@ -83302,7 +83396,7 @@ async function stageBrandDisplayFace(outDir, compositionDir) {
83302
83396
  }
83303
83397
 
83304
83398
  // src/commands/canvas/scaffold-video.ts
83305
- import { cp as cp2, mkdir as mkdir21, readFile as readFile42, rm as rm6, writeFile as writeFile29 } from "fs/promises";
83399
+ import { cp as cp2, mkdir as mkdir21, readFile as readFile42, rm as rm6, writeFile as writeFile30 } from "fs/promises";
83306
83400
  import { tmpdir as tmpdir2 } from "os";
83307
83401
  import path51 from "path";
83308
83402
  import { defineCommand as defineCommand148 } from "citty";
@@ -83469,40 +83563,6 @@ ${missing.join("\n")}
83469
83563
  `);
83470
83564
  }
83471
83565
 
83472
- // src/engine/scaffold/video/speech-reading.ts
83473
- var asBlueprint = (input) => input && typeof input === "object" ? input : {};
83474
- var dialogueOf = (b) => (b.scenes ?? []).flatMap((s) => s.dialogue ?? []);
83475
- function blueprintVoices(input) {
83476
- const b = asBlueprint(input);
83477
- const all = [
83478
- ...dialogueOf(b).map((l) => l.voice_description),
83479
- ...(b.global?.cast ?? []).map((c) => c.description),
83480
- b.global?.voiceover?.voice_description
83481
- ];
83482
- return [...new Set(all.filter((d) => typeof d === "string" && d.length > 0))];
83483
- }
83484
- async function readBlueprintSpeech(input, known, readers) {
83485
- const lines = dialogueOf(asBlueprint(input)).map((l) => l.line?.trim() ?? "").filter(Boolean);
83486
- const declared = known.declaredLanguage ? spokenLanguage(known.declaredLanguage)?.tag : void 0;
83487
- const voices = blueprintVoices(input);
83488
- const carries2 = carriesOnQuestions(lines);
83489
- const language = !declared && lines.length > 0 ? [languageQuestion(lines)] : [];
83490
- const [answers, genders] = await Promise.all([
83491
- readers.ask([...language, ...carries2.questions]),
83492
- readers.genders(voices).catch(() => null)
83493
- ]);
83494
- const voiceGenders = genders ?? {};
83495
- if (!answers)
83496
- return { ...declared ? { spokenLanguage: declared } : {}, voiceGenders, captionCarriesOn: [], read: false };
83497
- const read = declared ?? (language.length > 0 ? languageFromAnswer(answers[0]) : void 0);
83498
- return {
83499
- ...read ? { spokenLanguage: read } : {},
83500
- voiceGenders,
83501
- captionCarriesOn: carriesOnFromAnswers(carries2.words, answers.slice(language.length)),
83502
- read: true
83503
- };
83504
- }
83505
-
83506
83566
  // src/commands/canvas/scaffold-video.ts
83507
83567
  var SELECT_SYSTEM2 = 'You identify the RECURRING, identity-critical visual elements of a video ad from its deconstruction blueprint \u2014 the people, animals, products, and logos that must stay consistent every time they appear, so a faithful reproduction grounds each in ONE real reference image reused across every frame it appears in. Output ONLY a JSON object of the form { "elements": [ ... ] }, no prose.';
83508
83568
  var SELECT_PROMPT2 = `VIDEO DECONSTRUCTION BLUEPRINT (video-deconstruct/1):
@@ -83610,7 +83670,7 @@ async function materializeReferenceVideo(fileArg2) {
83610
83670
  tmpdir2(),
83611
83671
  `baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg2, contentType)}`
83612
83672
  );
83613
- await writeFile29(dest, bytes);
83673
+ await writeFile30(dest, bytes);
83614
83674
  return dest;
83615
83675
  }
83616
83676
  function resolveSeamDedup(raw) {
@@ -83906,7 +83966,7 @@ var scaffoldVideoCommand = defineCommand148({
83906
83966
  await mkdir21(outDir, { recursive: true });
83907
83967
  const annotated = annotateBlueprintWithElements(blueprint, elements);
83908
83968
  await writeSceneFiles(outDir, annotated);
83909
- await writeFile29(blueprintStylePath, renderStyleProjectionFromValue(annotated), "utf8");
83969
+ await writeFile30(blueprintStylePath, renderStyleProjectionFromValue(annotated), "utf8");
83910
83970
  let aspect;
83911
83971
  try {
83912
83972
  aspect = resolveAspect(
@@ -83937,7 +83997,7 @@ var scaffoldVideoCommand = defineCommand148({
83937
83997
  `video-overlay-composition/index.html is missing the <!--OVERLAYS--> marker \u2014 cannot inject the overlay layer`
83938
83998
  );
83939
83999
  }
83940
- await writeFile29(indexPath, injected, "utf8");
84000
+ await writeFile30(indexPath, injected, "utf8");
83941
84001
  const captions = await stageCaptions(outDir, transcript);
83942
84002
  if (captions.compositionPath) await stampCompositionDims(captions.compositionPath, outDims);
83943
84003
  const opts = {
@@ -83974,9 +84034,9 @@ var scaffoldVideoCommand = defineCommand148({
83974
84034
  todo.blocking_validation_issues = validation.issues;
83975
84035
  meta.todo = todo;
83976
84036
  }
83977
- await writeFile29(outPath, `${JSON.stringify(canvas, null, 2)}
84037
+ await writeFile30(outPath, `${JSON.stringify(canvas, null, 2)}
83978
84038
  `, "utf8");
83979
- await writeFile29(
84039
+ await writeFile30(
83980
84040
  path51.join(outDir, REBUILD_FILE),
83981
84041
  `${JSON.stringify({ elements, opts }, null, 2)}
83982
84042
  `,
@@ -84006,7 +84066,7 @@ var scaffoldVideoCommand = defineCommand148({
84006
84066
  if (slug) {
84007
84067
  const definitionPath = path51.join(outDir, "_definition.md");
84008
84068
  if (!await fileExists(definitionPath)) {
84009
- await writeFile29(
84069
+ await writeFile30(
84010
84070
  definitionPath,
84011
84071
  buildCreativeDefinition({
84012
84072
  title: titleFromSlug(slug),
@@ -84097,7 +84157,7 @@ var scaffoldVideoCommand = defineCommand148({
84097
84157
  });
84098
84158
 
84099
84159
  // src/commands/canvas/set-prompt.ts
84100
- import { readFile as readFile43, writeFile as writeFile30 } from "fs/promises";
84160
+ import { readFile as readFile43, writeFile as writeFile31 } from "fs/promises";
84101
84161
  import path52 from "path";
84102
84162
  import { defineCommand as defineCommand149 } from "citty";
84103
84163
  function setNodePrompt(canvas, nodeId, text4) {
@@ -84160,9 +84220,9 @@ async function setPromptInCanvasFile(canvasPath, nodeId, text4) {
84160
84220
  } catch (e) {
84161
84221
  throw new SetPromptError("node_not_found", e instanceof Error ? e.message : String(e));
84162
84222
  }
84163
- await writeFile30(filePath, `${JSON.stringify(updated, null, 2)}
84223
+ await writeFile31(filePath, `${JSON.stringify(updated, null, 2)}
84164
84224
  `, "utf8");
84165
- const restore = () => writeFile30(filePath, original, "utf8");
84225
+ const restore = () => writeFile31(filePath, original, "utf8");
84166
84226
  try {
84167
84227
  const prepared = await prepareCanvas(filePath);
84168
84228
  const validation = await validateCanvasDeep(prepared.canvas, defaultRegistry());
@@ -84711,7 +84771,7 @@ import path56 from "path";
84711
84771
  import { defineCommand as defineCommand160 } from "citty";
84712
84772
 
84713
84773
  // src/commands/research/customers.ts
84714
- import { mkdir as mkdir22, readdir as readdir19, readFile as readFile44, writeFile as writeFile31 } from "fs/promises";
84774
+ import { mkdir as mkdir22, readdir as readdir19, readFile as readFile44, writeFile as writeFile32 } from "fs/promises";
84715
84775
  import path54 from "path";
84716
84776
  import { defineCommand as defineCommand156 } from "citty";
84717
84777
  import { parse as parseYaml4 } from "yaml";
@@ -85686,7 +85746,7 @@ async function writeInsights(dir, writes, opts) {
85686
85746
  const written = [];
85687
85747
  for (const entry of writes) {
85688
85748
  const file = renderInsightFile(entry.insight, opts);
85689
- const ok = await writeFile31(path54.join(dir, `${entry.id}.md`), file, { encoding: "utf8", flag: "wx" }).then(
85749
+ const ok = await writeFile32(path54.join(dir, `${entry.id}.md`), file, { encoding: "utf8", flag: "wx" }).then(
85690
85750
  () => true,
85691
85751
  () => false
85692
85752
  );
@@ -86946,11 +87006,12 @@ function readsAsAgentOption(reading) {
86946
87006
  if (reading.tagged >= NOTE_BANDS.tagged) return true;
86947
87007
  return reading.bareFix >= NOTE_BANDS.bareFix && reading.namesProblem < NOTE_BANDS.namesProblem;
86948
87008
  }
86949
- async function isAgentOptionNote(note, judge) {
87009
+ async function readNoteAuthor(note, judge) {
86950
87010
  const text4 = note.trim();
86951
- if (!text4) return false;
87011
+ if (!text4) return "person";
86952
87012
  const reading = await judge(text4).catch(() => null);
86953
- return reading !== null && readsAsAgentOption(reading);
87013
+ if (reading === null) return "unconfirmed";
87014
+ return readsAsAgentOption(reading) ? "agent_option" : "person";
86954
87015
  }
86955
87016
 
86956
87017
  // src/commands/creatives/version-of.ts
@@ -86999,18 +87060,21 @@ function fail6(code, message) {
86999
87060
  process.exit(1);
87000
87061
  }
87001
87062
  async function refuseNotFeedback(body) {
87002
- if (body.verdict === "rejected" && body.note && await isAgentOptionNote(body.note, jevNoteJudge)) {
87003
- fail6(
87004
- "AGENT_OPTION_NOT_FEEDBACK",
87005
- `"${body.note}" is the answer to your own question, not the person's taste: a defect the checks found is yours to fix inside this version (\`baker canvas run --fix\`), never feedback to record. Record only what the person says about the ad in their own words.`
87006
- );
87007
- }
87008
87063
  if (body.verdict === "rejected" && !body.reasons?.length && !body.note) {
87009
87064
  fail6(
87010
87065
  "VALIDATION_ERROR",
87011
87066
  "A rejection needs --reasons or --note (the user's words), so the next version knows what to change."
87012
87067
  );
87013
87068
  }
87069
+ if (body.verdict !== "rejected" || !body.note) return false;
87070
+ const author = await readNoteAuthor(body.note, jevNoteJudge);
87071
+ if (author === "agent_option") {
87072
+ fail6(
87073
+ "AGENT_OPTION_NOT_FEEDBACK",
87074
+ `"${body.note}" is the answer to your own question, not the person's taste: a defect the checks found is yours to fix inside this version (\`baker canvas run --fix\`), never feedback to record. Record only what the person says about the ad in their own words.`
87075
+ );
87076
+ }
87077
+ return author === "unconfirmed";
87014
87078
  }
87015
87079
  var feedbackRecordCommand = defineCommand161({
87016
87080
  meta: { name: "record", description: DESCRIPTION8 },
@@ -87038,9 +87102,12 @@ var feedbackRecordCommand = defineCommand161({
87038
87102
  fail6("VALIDATION_ERROR", `--${String(issue?.path[0] ?? "args")}: ${issue?.message ?? "invalid"}`);
87039
87103
  }
87040
87104
  const body = parsed.data;
87041
- await refuseNotFeedback(body);
87105
+ const noteUnconfirmed = await refuseNotFeedback(body);
87042
87106
  try {
87043
- const response = await apiPost("/api/creatives/feedback/record", body);
87107
+ const response = await apiPost(
87108
+ "/api/creatives/feedback/record",
87109
+ noteUnconfirmed ? { ...body, noteUnconfirmed } : body
87110
+ );
87044
87111
  writeJson(response);
87045
87112
  } catch (err) {
87046
87113
  if (err instanceof ApiError) fail6(err.code, err.message);
@@ -87101,7 +87168,7 @@ var feedbackCommand = defineCommand162({
87101
87168
 
87102
87169
  // src/commands/creatives/learn.ts
87103
87170
  import { readFileSync as readFileSync19 } from "fs";
87104
- import { readFile as readFile46, writeFile as writeFile32 } from "fs/promises";
87171
+ import { readFile as readFile46, writeFile as writeFile33 } from "fs/promises";
87105
87172
  import path58 from "path";
87106
87173
  import { defineCommand as defineCommand163 } from "citty";
87107
87174
 
@@ -87326,7 +87393,7 @@ async function writeBack(root, learnings) {
87326
87393
  for (const learning of learnings) {
87327
87394
  const file = path58.join(root, IDEAS_DIR, `${learning.idea}.md`);
87328
87395
  const raw = await readFile46(file, "utf8");
87329
- await writeFile32(file, setDefinitionFields(raw, { status: learning.to, result: learning.result }), "utf8");
87396
+ await writeFile33(file, setDefinitionFields(raw, { status: learning.to, result: learning.result }), "utf8");
87330
87397
  }
87331
87398
  }
87332
87399
  function readDefinition2(root, slug) {
@@ -87368,13 +87435,13 @@ var learnCommand = defineCommand163({
87368
87435
  });
87369
87436
 
87370
87437
  // src/commands/creatives/make.ts
87371
- import { mkdtemp as mkdtemp2, readFile as readFile48, rm as rm7, stat as stat7, writeFile as writeFile34 } from "fs/promises";
87438
+ import { mkdtemp as mkdtemp2, readFile as readFile48, rm as rm7, stat as stat7, writeFile as writeFile35 } from "fs/promises";
87372
87439
  import { tmpdir as tmpdir3 } from "os";
87373
87440
  import path60 from "path";
87374
87441
  import { defineCommand as defineCommand164 } from "citty";
87375
87442
 
87376
87443
  // src/commands/creatives/make-series.ts
87377
- import { mkdir as mkdir23, readFile as readFile47, writeFile as writeFile33 } from "fs/promises";
87444
+ import { mkdir as mkdir23, readFile as readFile47, writeFile as writeFile34 } from "fs/promises";
87378
87445
  import path59 from "path";
87379
87446
  import { parseDocument as parseDocument5 } from "yaml";
87380
87447
  var LEAD_ONLY = ["idea", "message", "evidence", "angle", "persona", "awareness", "hypothesis", "series", "title"];
@@ -87402,7 +87469,7 @@ async function makeSeriesAd(root, idea, slug, lead, evidence, deps) {
87402
87469
  const claim = await claimNewCreativeInWorkspace(path59.join(root, "canvas"), slug, deps.chatId, definitionPath);
87403
87470
  if (!claim.allowed) return creativeLimitRefusal(slug).error;
87404
87471
  await mkdir23(dir, { recursive: true });
87405
- await writeFile33(path59.join(dir, "prompt.json"), setSpecMessage(JSON.stringify(version.spec), idea.message), "utf8");
87472
+ await writeFile34(path59.join(dir, "prompt.json"), setSpecMessage(JSON.stringify(version.spec), idea.message), "utf8");
87406
87473
  const leadDefinition = await readFile47(path59.join(root, "src", "creatives", lead, "_definition.md"), "utf8").catch(
87407
87474
  () => "---\nkind: static\n---\n"
87408
87475
  );
@@ -87412,7 +87479,7 @@ async function makeSeriesAd(root, idea, slug, lead, evidence, deps) {
87412
87479
  status: "draft",
87413
87480
  ...definitionFieldsOf(idea, evidence)
87414
87481
  });
87415
- await writeFile33(definitionPath, definition, "utf8");
87482
+ await writeFile34(definitionPath, definition, "utf8");
87416
87483
  const made = await writeSeriesMemberCanvas(root, slug, { leadWords: true });
87417
87484
  if (made === null || "refused" in made) {
87418
87485
  return made?.refused ?? { code: "series_not_made", message: `"${slug}" could not be made in the series of "${lead}".` };
@@ -87420,7 +87487,7 @@ async function makeSeriesAd(root, idea, slug, lead, evidence, deps) {
87420
87487
  if (idea.status === "idea") {
87421
87488
  const ideaPath = path59.join(root, IDEAS_DIR, `${idea.id}.md`);
87422
87489
  const raw = await readFile47(ideaPath, "utf8").catch(() => null);
87423
- if (raw !== null) await writeFile33(ideaPath, setDefinitionFields(raw, { status: "built" }), "utf8");
87490
+ if (raw !== null) await writeFile34(ideaPath, setDefinitionFields(raw, { status: "built" }), "utf8");
87424
87491
  }
87425
87492
  await deps.sync(dir);
87426
87493
  return { slug, lead, leadRunId: version.lead.runId, canvasPath: made.canvasPath };
@@ -87700,7 +87767,7 @@ async function scaffoldStatic(root, idea, args8, slug, temperature, viewerFlags)
87700
87767
  async function scaffoldFromIdea(idea, slug, viewerFlags) {
87701
87768
  const dir = await mkdtemp2(path60.join(tmpdir3(), "idea-spec-"));
87702
87769
  const specPath = path60.join(dir, "prompt.json");
87703
- await writeFile34(specPath, JSON.stringify(ideaSpec(idea, idea.who.replace(/[-_]/g, " ")), null, 2), "utf8");
87770
+ await writeFile35(specPath, JSON.stringify(ideaSpec(idea, idea.who.replace(/[-_]/g, " ")), null, 2), "utf8");
87704
87771
  try {
87705
87772
  return await runBaker([
87706
87773
  "canvas",
@@ -87722,15 +87789,15 @@ async function recordIdea(root, slug, idea, evidence) {
87722
87789
  const definitionPath = path60.join(dir, "_definition.md");
87723
87790
  const definition = await readFile48(definitionPath, "utf8").catch(() => null);
87724
87791
  if (definition !== null) {
87725
- await writeFile34(definitionPath, setDefinitionFields(definition, { ...definitionFieldsOf(idea, evidence) }), "utf8");
87792
+ await writeFile35(definitionPath, setDefinitionFields(definition, { ...definitionFieldsOf(idea, evidence) }), "utf8");
87726
87793
  }
87727
87794
  const promptPath = path60.join(dir, "prompt.json");
87728
87795
  const prompt = isVideoIdea(idea) ? null : await readFile48(promptPath, "utf8").catch(() => null);
87729
- if (prompt !== null) await writeFile34(promptPath, setSpecMessage(prompt, idea.message), "utf8").catch(() => void 0);
87796
+ if (prompt !== null) await writeFile35(promptPath, setSpecMessage(prompt, idea.message), "utf8").catch(() => void 0);
87730
87797
  const unsourcedCopy = prompt === null ? [] : await draftedClaims(root, prompt);
87731
87798
  if (idea.status === "idea") {
87732
87799
  const ideaPath = path60.join(root, IDEAS_DIR, `${idea.id}.md`);
87733
- await writeFile34(ideaPath, setDefinitionFields(await readFile48(ideaPath, "utf8"), { status: "built" }), "utf8");
87800
+ await writeFile35(ideaPath, setDefinitionFields(await readFile48(ideaPath, "utf8"), { status: "built" }), "utf8");
87734
87801
  }
87735
87802
  await runBaker(["canvas", "sync-definition", dir]);
87736
87803
  return { dir, unsourcedCopy };
@@ -88329,7 +88396,7 @@ import path62 from "path";
88329
88396
  import { defineCommand as defineCommand167 } from "citty";
88330
88397
 
88331
88398
  // src/commands/creatives/translate-ad.ts
88332
- import { mkdir as mkdir24, readFile as readFile51, writeFile as writeFile35 } from "fs/promises";
88399
+ import { mkdir as mkdir24, readFile as readFile51, writeFile as writeFile36 } from "fs/promises";
88333
88400
  import path61 from "path";
88334
88401
 
88335
88402
  // src/commands/creatives/translate-model.ts
@@ -88376,7 +88443,8 @@ async function customersWordsAmong(lines, judge) {
88376
88443
  const open = [];
88377
88444
  lines.forEach((line, i) => {
88378
88445
  if (line.role === MESSAGE_ROLE) return;
88379
- if (isCustomersWords(line)) kept.add(i);
88446
+ const below = lines[i + 1];
88447
+ if (isCustomersWords(line) || below !== void 0 && isSignatureLine(below)) kept.add(i);
88380
88448
  else open.push(i);
88381
88449
  });
88382
88450
  if (!judge || open.length === 0) return kept;
@@ -88567,7 +88635,7 @@ async function makeTranslation(root, t, deps) {
88567
88635
  const claim = await claimNewCreativeInWorkspace(path61.join(root, "canvas"), creative, deps.chatId, definitionPath);
88568
88636
  if (!claim.allowed) return refused(creativeLimitRefusal(creative).error);
88569
88637
  await mkdir24(dir, { recursive: true });
88570
- await writeFile35(
88638
+ await writeFile36(
88571
88639
  path61.join(dir, "prompt.json"),
88572
88640
  `${JSON.stringify(translatedSpec(t.spec, t.words, t.language, t.quoted), null, 2)}
88573
88641
  `,
@@ -88577,7 +88645,7 @@ async function makeTranslation(root, t, deps) {
88577
88645
  0,
88578
88646
  t.lines.findIndex((l) => l.role === "headline")
88579
88647
  );
88580
- await writeFile35(
88648
+ await writeFile36(
88581
88649
  definitionPath,
88582
88650
  translatedDefinition(t.sourceDefinition, {
88583
88651
  source: t.source,
@@ -89329,7 +89397,7 @@ async function readForms(root, slug) {
89329
89397
 
89330
89398
  // src/commands/experiment/fold.ts
89331
89399
  import { statSync } from "fs";
89332
- import { cp as cp3, mkdir as mkdir25, readdir as readdir23, readFile as readFile55, rm as rm8, stat as stat9, writeFile as writeFile36 } from "fs/promises";
89400
+ import { cp as cp3, mkdir as mkdir25, readdir as readdir23, readFile as readFile55, rm as rm8, stat as stat9, writeFile as writeFile37 } from "fs/promises";
89333
89401
  import path66 from "path";
89334
89402
 
89335
89403
  // ../api/src/experiments/variants.ts
@@ -89433,7 +89501,7 @@ async function archiveArm(opts) {
89433
89501
  await cp3(abs, dest);
89434
89502
  } else {
89435
89503
  const text4 = await readFile55(abs, "utf8");
89436
- await writeFile36(dest, repointIntoArchive(text4, abs, dest, archivedBySource), "utf8");
89504
+ await writeFile37(dest, repointIntoArchive(text4, abs, dest, archivedBySource), "utf8");
89437
89505
  }
89438
89506
  written.push(posix2(path66.relative(root, dest)));
89439
89507
  }
@@ -89504,7 +89572,7 @@ async function promoteVariantFiles(opts) {
89504
89572
  await cp3(from, to);
89505
89573
  } else {
89506
89574
  const text4 = await readFile55(from, "utf8");
89507
- await writeFile36(
89575
+ await writeFile37(
89508
89576
  to,
89509
89577
  repointMoved(text4, {
89510
89578
  fromDir: path66.dirname(from),
@@ -89564,7 +89632,7 @@ async function foldExperiment(root, target2) {
89564
89632
  archiveDir,
89565
89633
  armSlug: target2.survivor === "variant" ? target2.landingSlug : target2.variantSlug
89566
89634
  });
89567
- await writeFile36(path66.join(archiveDir, "_variant.json"), archiveRecord(target2, variantNumber), "utf8");
89635
+ await writeFile37(path66.join(archiveDir, "_variant.json"), archiveRecord(target2, variantNumber), "utf8");
89568
89636
  written.push(posix2(path66.join(ARCHIVE_ROOT, archiveKey, String(variantNumber), "_variant.json")));
89569
89637
  if (target2.survivor === "variant") {
89570
89638
  written.push(...await promoteVariantFiles({ root, pageDir, variantDir }));
@@ -96991,7 +97059,7 @@ function cropSprite(input, region) {
96991
97059
 
96992
97060
  // src/lib/image/io.ts
96993
97061
  import { randomBytes } from "crypto";
96994
- import { glob as fsGlob, readFile as readFile58, rename as rename3, stat as stat10, writeFile as writeFile37 } from "fs/promises";
97062
+ import { glob as fsGlob, readFile as readFile58, rename as rename3, stat as stat10, writeFile as writeFile38 } from "fs/promises";
96995
97063
  import { dirname as dirname2, extname as extname3, join as join5, resolve as resolve4 } from "path";
96996
97064
  var REMOTE_RE = /^https?:\/\//i;
96997
97065
  var GLOB_RE = /[*?[\]{}]/;
@@ -97047,7 +97115,7 @@ async function atomicWrite(targetPath, data) {
97047
97115
  const absolute = resolve4(targetPath);
97048
97116
  const dir = dirname2(absolute);
97049
97117
  const tmp = join5(dir, `.baker-image-${randomBytes(8).toString("hex")}.tmp`);
97050
- await writeFile37(tmp, data);
97118
+ await writeFile38(tmp, data);
97051
97119
  await rename3(tmp, absolute);
97052
97120
  }
97053
97121
 
@@ -101620,7 +101688,7 @@ function describeCounts(findings) {
101620
101688
  }
101621
101689
 
101622
101690
  // src/commands/landing/snapshot.ts
101623
- import { mkdir as mkdir26, rename as rename4, writeFile as writeFile38 } from "fs/promises";
101691
+ import { mkdir as mkdir26, rename as rename4, writeFile as writeFile39 } from "fs/promises";
101624
101692
  import path69 from "path";
101625
101693
  var CRITIC_VERSION = "5";
101626
101694
  function critiqueCacheDir(projectRoot) {
@@ -101633,7 +101701,7 @@ async function writeCritiqueSnapshot(projectRoot, snapshot) {
101633
101701
  await mkdir26(critiqueCacheDir(projectRoot), { recursive: true });
101634
101702
  const dest = snapshotPath(projectRoot, snapshot.slug);
101635
101703
  const tmp = `${dest}.tmp`;
101636
- await writeFile38(tmp, `${JSON.stringify(snapshot, null, 2)}
101704
+ await writeFile39(tmp, `${JSON.stringify(snapshot, null, 2)}
101637
101705
  `, "utf8");
101638
101706
  await rename4(tmp, dest);
101639
101707
  }
@@ -101788,7 +101856,7 @@ var critiqueCommand2 = defineCommand220({
101788
101856
  const hints2 = [
101789
101857
  blocked.length > 0 ? `Block-tier tells are held at publish \u2014 fix them (${blocked.join(", ")}). Warn/advisory are guidance. Scores are 0\u20131 (higher is better).` : "No block-tier tells. Warn/advisory findings are guidance, not gates. Re-run after edits so the publish gate stays fresh.",
101790
101858
  ...copyUnchecked.length > 0 ? [
101791
- `The copy of ${copyUnchecked.join(", ")} could not be read this time (concessions, rivals named, buzzwords, AI copy tells are unchecked, so none is reported). Re-run the critique before finishing.`
101859
+ `The copy of ${copyUnchecked.join(", ")} could not be checked just now (concessions to a competitor, rivals named, buzzwords and AI copy tells are unchecked, so none is reported), and this critique was not recorded for publishing. Run \`baker landing critique ${copyUnchecked.join(" ")}\` again before publishing.`
101792
101860
  ] : [],
101793
101861
  ...snapshotFailures.length > 0 ? [
101794
101862
  `Could not record the critique snapshot for ${snapshotFailures.join(", ")} (.cache/ not writable?) \u2014 the publish quality gate will still report ${snapshotFailures.length === 1 ? "this landing" : "these landings"} as un-critiqued.`
@@ -101816,6 +101884,7 @@ async function critiqueOne(projectRoot, slug, brand, competitors, ask) {
101816
101884
  computeLegacyLandingSourceSha(path71.resolve(projectRoot, "src", "pages", slug))
101817
101885
  ]);
101818
101886
  const report = await critiqueLanding({ slug, sources, brand, competitors, ask });
101887
+ if (!report.copyChecked) return { slug, report, snapshotFailed: false };
101819
101888
  let snapshotFailed = false;
101820
101889
  try {
101821
101890
  await writeCritiqueSnapshot(projectRoot, {
@@ -102152,7 +102221,7 @@ var addCommand = defineCommand222({
102152
102221
  });
102153
102222
 
102154
102223
  // src/commands/landing/inspiration/code.ts
102155
- import { mkdir as mkdir27, writeFile as writeFile39 } from "fs/promises";
102224
+ import { mkdir as mkdir27, writeFile as writeFile40 } from "fs/promises";
102156
102225
  import path72 from "path";
102157
102226
  import { defineCommand as defineCommand223 } from "citty";
102158
102227
  registerSchema({
@@ -102179,7 +102248,7 @@ var codeCommand = defineCommand223({
102179
102248
  const dir = path72.join(process.cwd(), ".baker", "inspiration", id);
102180
102249
  await mkdir27(dir, { recursive: true });
102181
102250
  const file = path72.join(dir, "section.html");
102182
- await writeFile39(file, data.html);
102251
+ await writeFile40(file, data.html);
102183
102252
  const hints2 = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
102184
102253
  const fidelity = fidelityHint(data.fidelity);
102185
102254
  if (fidelity) hints2.push(fidelity);
@@ -102647,7 +102716,7 @@ function classifyCaptureFailure(error) {
102647
102716
  }
102648
102717
 
102649
102718
  // src/engine/landing-library/run.ts
102650
- import { mkdir as mkdir28, writeFile as writeFile41 } from "fs/promises";
102719
+ import { mkdir as mkdir28, writeFile as writeFile42 } from "fs/promises";
102651
102720
  import path74 from "path";
102652
102721
 
102653
102722
  // ../proxy/src/preflight.ts
@@ -104065,11 +104134,11 @@ async function renderBundleToPng(browser, html, viewportWidth, options = {}) {
104065
104134
  }
104066
104135
 
104067
104136
  // src/engine/landing-library/report.ts
104068
- import { writeFile as writeFile40 } from "fs/promises";
104137
+ import { writeFile as writeFile41 } from "fs/promises";
104069
104138
  import path73 from "path";
104070
104139
  async function writeCaptureReport(manifest, outDir) {
104071
104140
  const file = path73.join(outDir, "report.html");
104072
- await writeFile40(file, renderReport(manifest));
104141
+ await writeFile41(file, renderReport(manifest));
104073
104142
  return file;
104074
104143
  }
104075
104144
  function escapeHtml3(value2) {
@@ -104212,13 +104281,13 @@ async function reproducePage(args8) {
104212
104281
  const { browser, page, outDir, pageUrl, livePageShot } = args8;
104213
104282
  const built = await buildSectionBundle(page, "body", pageUrl).catch(() => null);
104214
104283
  if (!built) return { bundle: null, fidelity: null };
104215
- await writeFile41(path74.join(outDir, "page.html"), built.html);
104284
+ await writeFile42(path74.join(outDir, "page.html"), built.html);
104216
104285
  const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width, {
104217
104286
  wholePage: true,
104218
104287
  timeoutMs: 6e4
104219
104288
  });
104220
104289
  if (!rendered || !livePageShot) return { bundle: "page.html", fidelity: null };
104221
- await writeFile41(path74.join(outDir, "page-rendered.png"), rendered);
104290
+ await writeFile42(path74.join(outDir, "page-rendered.png"), rendered);
104222
104291
  const { score, note } = await scoreFidelity(livePageShot, rendered);
104223
104292
  return { bundle: "page.html", fidelity: score, ...note ? { fidelityNote: note } : {} };
104224
104293
  }
@@ -104227,17 +104296,17 @@ async function captureOneSection(args8) {
104227
104296
  const dir = path74.join(sectionsDir, String(candidate.index).padStart(2, "0"));
104228
104297
  await mkdir28(dir, { recursive: true });
104229
104298
  const desktop = await captureSection(page, candidate);
104230
- if (desktop) await writeFile41(path74.join(dir, "desktop.png"), desktop);
104299
+ if (desktop) await writeFile42(path74.join(dir, "desktop.png"), desktop);
104231
104300
  const visualHash = desktop ? await perceptualHash(desktop) : null;
104232
104301
  const motion = await collectMotion(page, candidate.selector);
104233
104302
  const built = withCode ? await buildSectionBundle(page, candidate.selector, pageUrl) : null;
104234
104303
  let fidelity = null;
104235
104304
  let fidelityNote;
104236
104305
  if (built) {
104237
- await writeFile41(path74.join(dir, "section.html"), built.html);
104306
+ await writeFile42(path74.join(dir, "section.html"), built.html);
104238
104307
  const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width);
104239
104308
  if (rendered && desktop) {
104240
- await writeFile41(path74.join(dir, "section-rendered.png"), rendered);
104309
+ await writeFile42(path74.join(dir, "section-rendered.png"), rendered);
104241
104310
  const result = await scoreFidelity(desktop, rendered);
104242
104311
  fidelity = result.score;
104243
104312
  fidelityNote = result.note;
@@ -104267,7 +104336,7 @@ async function captureMobileShots(args8) {
104267
104336
  const shot = await captureSectionOnMobile(mobile.page, section2);
104268
104337
  if (!shot) continue;
104269
104338
  const file = path74.join(sectionsDir, String(section2.index).padStart(2, "0"), "mobile.png");
104270
- await writeFile41(file, shot);
104339
+ await writeFile42(file, shot);
104271
104340
  section2.mobileShot = path74.relative(outDir, file);
104272
104341
  }
104273
104342
  } finally {
@@ -104285,7 +104354,7 @@ async function captureMotionTakes(args8) {
104285
104354
  if (!take) return;
104286
104355
  const dir = path74.join(sectionsDir, String(section2.index).padStart(2, "0"));
104287
104356
  const file = path74.join(dir, "motion-filmstrip.png");
104288
- await writeFile41(file, take.filmstrip);
104357
+ await writeFile42(file, take.filmstrip);
104289
104358
  section2.motionFilmstrip = path74.relative(outDir, file);
104290
104359
  log(` [${section2.index}] ${section2.motion.summary}`);
104291
104360
  };
@@ -104343,7 +104412,7 @@ async function captureAlternateViews(args8) {
104343
104412
  async function reproduceWholePage(args8) {
104344
104413
  const { browser, page, outDir, pageUrl, withCode, log } = args8;
104345
104414
  const fullPage = await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
104346
- if (fullPage) await writeFile41(path74.join(outDir, "full-page.png"), fullPage);
104415
+ if (fullPage) await writeFile42(path74.join(outDir, "full-page.png"), fullPage);
104347
104416
  if (!withCode) return { bundle: null, fidelity: null };
104348
104417
  const reproduction = await reproducePage({ browser, page, outDir, pageUrl, livePageShot: fullPage });
104349
104418
  log(`page reproduction: ${reproduction.fidelity === null ? "unavailable" : reproduction.fidelity.toFixed(2)}`);
@@ -104476,7 +104545,7 @@ async function scrapeLanding(options) {
104476
104545
  security: prepared.security,
104477
104546
  captureTier: tier
104478
104547
  };
104479
- await writeFile41(path74.join(options.outDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
104548
+ await writeFile42(path74.join(options.outDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
104480
104549
  `);
104481
104550
  if (options.report !== false) {
104482
104551
  const reportPath = await writeCaptureReport(manifest, options.outDir);
@@ -104690,7 +104759,7 @@ import path77 from "path";
104690
104759
  import { defineCommand as defineCommand228 } from "citty";
104691
104760
 
104692
104761
  // src/commands/landing/inspiration/shot.ts
104693
- import { mkdir as mkdir29, writeFile as writeFile42 } from "fs/promises";
104762
+ import { mkdir as mkdir29, writeFile as writeFile43 } from "fs/promises";
104694
104763
  import path76 from "path";
104695
104764
  import sharp9 from "sharp";
104696
104765
  var READABLE_SHOT = {
@@ -104718,7 +104787,7 @@ async function downloadReadableShot(url, file) {
104718
104787
  if (!response.ok) return null;
104719
104788
  const shot = await toReadableShot(Buffer.from(await response.arrayBuffer()));
104720
104789
  await mkdir29(path76.dirname(file), { recursive: true });
104721
- await writeFile42(file, shot);
104790
+ await writeFile43(file, shot);
104722
104791
  return path76.relative(process.cwd(), file);
104723
104792
  } catch {
104724
104793
  return null;
@@ -105191,7 +105260,7 @@ Full guide: __tooling__/docs/tools/baker/landing.md`
105191
105260
 
105192
105261
  // src/commands/landing/variant.ts
105193
105262
  import { randomUUID as randomUUID4 } from "crypto";
105194
- import { mkdir as mkdir30, readdir as readdir26, readFile as readFile61, stat as stat13, writeFile as writeFile43 } from "fs/promises";
105263
+ import { mkdir as mkdir30, readdir as readdir26, readFile as readFile61, stat as stat13, writeFile as writeFile44 } from "fs/promises";
105195
105264
  import path79 from "path";
105196
105265
  import { defineCommand as defineCommand232 } from "citty";
105197
105266
  registerSchema({
@@ -105380,7 +105449,7 @@ async function writeVariant(opts) {
105380
105449
  const written = [];
105381
105450
  const indexText = await readFile61(path79.join(controlDir, "index.astro"), "utf8");
105382
105451
  await mkdir30(variantDir, { recursive: true });
105383
- await writeFile43(
105452
+ await writeFile44(
105384
105453
  path79.join(variantDir, "index.astro"),
105385
105454
  repointFile(indexText, { fromDir: controlDir, toDir: variantDir, controlDir, variantDir, forkedTargets }),
105386
105455
  "utf8"
@@ -105391,7 +105460,7 @@ async function writeVariant(opts) {
105391
105460
  const toFile = path79.join(variantDir, "_components", fork);
105392
105461
  const text4 = await readFile61(fromFile, "utf8");
105393
105462
  await mkdir30(path79.dirname(toFile), { recursive: true });
105394
- await writeFile43(
105463
+ await writeFile44(
105395
105464
  toFile,
105396
105465
  repointFile(text4, {
105397
105466
  fromDir: path79.dirname(fromFile),
@@ -105414,11 +105483,11 @@ async function writeVariant(opts) {
105414
105483
  ...opts.because ? { because: opts.because } : {},
105415
105484
  ...opts.change ? { change: opts.change } : {}
105416
105485
  });
105417
- await writeFile43(path79.join(variantDir, "_definition.md"), definition, "utf8");
105486
+ await writeFile44(path79.join(variantDir, "_definition.md"), definition, "utf8");
105418
105487
  written.push(`src/pages/${variantSlug2}/_definition.md`);
105419
105488
  }
105420
105489
  await mkdir30(path79.join(variantDir, "_images"), { recursive: true });
105421
- await writeFile43(path79.join(variantDir, "_images", ".gitkeep"), "", "utf8");
105490
+ await writeFile44(path79.join(variantDir, "_images", ".gitkeep"), "", "utf8");
105422
105491
  return written;
105423
105492
  }
105424
105493
  var variantCommand = defineCommand232({