@koda-sl/baker-cli 0.430.0-dev.8c14009f3 → 0.434.0-dev.42b55fa66

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
@@ -198,7 +198,7 @@ import {
198
198
  withoutCastName,
199
199
  wordCount,
200
200
  ytDlpBlockSignal
201
- } from "./chunk-GTHLEQHU.js";
201
+ } from "./chunk-MMTPMFFH.js";
202
202
  import {
203
203
  NAME_CONTAINS_ARG,
204
204
  csvOrJson,
@@ -65294,7 +65294,7 @@ var catalogCommand = defineCommand134({
65294
65294
  });
65295
65295
 
65296
65296
  // src/commands/canvas/critique.ts
65297
- import path14 from "path";
65297
+ import path15 from "path";
65298
65298
  import { defineCommand as defineCommand135 } from "citty";
65299
65299
 
65300
65300
  // src/engine/scaffold/lib/shoot-modes.ts
@@ -66050,8 +66050,8 @@ function critiqueCanvas(canvas) {
66050
66050
  }
66051
66051
 
66052
66052
  // src/commands/canvas/prepare-canvas.ts
66053
- import { readdir as readdir6, readFile as readFile11, writeFile as writeFile6 } from "fs/promises";
66054
- import path13 from "path";
66053
+ import { readdir as readdir7, readFile as readFile12, writeFile as writeFile6 } from "fs/promises";
66054
+ import path14 from "path";
66055
66055
 
66056
66056
  // src/commands/canvas/normalize-paths.ts
66057
66057
  import { existsSync as existsSync4, realpathSync } from "fs";
@@ -66845,6 +66845,10 @@ function projectAdaptCopy(nodes, spec) {
66845
66845
  }
66846
66846
  return changed;
66847
66847
  }
66848
+ function variantChecks(nodes, spec) {
66849
+ const checks = staticChecksOf(spec);
66850
+ return checks ? { ...checks, logo: checks.logo && nodes.some((n) => n.tags?.role === BRAND_LOGO_ROLE) } : null;
66851
+ }
66848
66852
  function projectJudges(nodes, spec, brief2) {
66849
66853
  const criteria = judgeBrief(brief2, {
66850
66854
  ...spec.brand?.name ? { brand: spec.brand.name } : {},
@@ -66852,7 +66856,7 @@ function projectJudges(nodes, spec, brief2) {
66852
66856
  mechanisms: spec.mechanism.map((m) => m.device)
66853
66857
  });
66854
66858
  let changed = 0;
66855
- const checks = staticChecksOf(spec);
66859
+ const checks = variantChecks(nodes, spec);
66856
66860
  for (const prefilter of nodes.filter((n) => n.tags?.role === VARIANT_PREFILTER_ROLE)) {
66857
66861
  const before = JSON.stringify([prefilter.params, prefilter.inputs]);
66858
66862
  const params = { ...prefilter.params, ...checks ? { checks } : {} };
@@ -67619,8 +67623,15 @@ function figureSourced(match, knowledge2) {
67619
67623
  const [, currency, price, number = "", unit = ""] = match;
67620
67624
  if (currency && price) return figureForms(price.replace(/[.,]$/, "")).some((form) => knowledge2.includes(form));
67621
67625
  const forms = figureForms(number.replace(/[.,]$/, "")).map((f) => f.replace(/[.,]/g, "[.,]?"));
67622
- const unitPattern = unit === "%" ? "%" : /^[x×]$/i.test(unit) ? "[x\xD7]" : unit.slice(0, 3).toLowerCase();
67623
- return forms.some((form) => new RegExp(`(^|[^\\d])${form}[\\s-]*${unitPattern}`, "iu").test(knowledge2));
67626
+ if (unit === "%" || /^[x×]$/i.test(unit)) {
67627
+ const sign = unit === "%" ? "%" : "[x\xD7]";
67628
+ return forms.some((form) => new RegExp(`(^|[^\\d])${form}\\s?${sign}`, "iu").test(knowledge2));
67629
+ }
67630
+ const stem = unit.slice(0, 3).toLowerCase();
67631
+ return forms.some((form) => {
67632
+ const number2 = `(?:^|[^\\d.,])${form}(?![\\d])`;
67633
+ return new RegExp(`${number2}[^.!?\\n]{0,60}?${stem}|${stem}[^.!?\\n]{0,60}?${number2}`, "iu").test(knowledge2);
67634
+ });
67624
67635
  }
67625
67636
  function unsourcedFigureLines(spec, knowledge2) {
67626
67637
  return spec.copy.flatMap(
@@ -70972,6 +70983,414 @@ function toPosix(p) {
70972
70983
  return p.split(path12.sep).join("/");
70973
70984
  }
70974
70985
 
70986
+ // src/commands/creatives/ideas-model.ts
70987
+ import { parse as parseYaml2 } from "yaml";
70988
+ var IDEA_ANGLES = [
70989
+ "pain_relief",
70990
+ "aspiration",
70991
+ "status",
70992
+ "fear",
70993
+ "FOMO",
70994
+ "identity",
70995
+ "contrarian",
70996
+ "education",
70997
+ "novelty",
70998
+ "authority",
70999
+ "community",
71000
+ "transformation",
71001
+ "convenience",
71002
+ "save_money",
71003
+ "save_time",
71004
+ "save_effort",
71005
+ "named_enemy",
71006
+ "objection_crusher",
71007
+ "villain_democratization",
71008
+ "commiseration",
71009
+ "recognition_mirror",
71010
+ "flipped_idiom"
71011
+ ];
71012
+ var IDEA_VIDEO_FORMATS = [
71013
+ "video_ugc",
71014
+ "video_founder",
71015
+ "video_testimonial",
71016
+ "video_demo",
71017
+ "video_screen_recording",
71018
+ "video_explainer",
71019
+ "video_motion_graphics"
71020
+ ];
71021
+ var IDEA_FORMATS = [...staticFormatPatternSchema.options, ...IDEA_VIDEO_FORMATS];
71022
+ var AWARENESS_STAGES = ["unaware", "problem_aware", "solution_aware", "product_aware", "most_aware"];
71023
+ var EVIDENCE_LEVELS = ["observed", "interpreted", "hypothesized", "generated"];
71024
+ var IDEA_STATUSES = ["idea", "built", "testing", "winner", "failed", "inconclusive", "retired"];
71025
+ var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
71026
+ function frontmatterOf(markdown) {
71027
+ const block2 = FRONTMATTER.exec(markdown)?.[1];
71028
+ if (block2 === void 0) return null;
71029
+ try {
71030
+ const parsed = parseYaml2(block2);
71031
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
71032
+ } catch {
71033
+ return null;
71034
+ }
71035
+ }
71036
+ function text2(value2) {
71037
+ if (typeof value2 === "string") return value2.trim() || void 0;
71038
+ if (typeof value2 === "number") return String(value2);
71039
+ return void 0;
71040
+ }
71041
+ function oneOf(values, value2) {
71042
+ return typeof value2 === "string" && values.includes(value2) ? value2 : void 0;
71043
+ }
71044
+ var STATISTIC = /(?:\d+(?:[.,]\d+)?\s?%|\b(?:a|one)\s+(?:quarter|third|half|fifth)|\bhalf|\b\d+\s+(?:in|out of)\s+\d+)\s+(?:of\s+)?(?:[\p{L}-]+\s+){0,2}(?:people|homeowners|customers|users|owners|respondents|consumers|households|adults|buyers|shoppers|businesses|companies)\b/iu;
71045
+ var CHECKABLE_SOURCE = /^(?:https?:\/\/\S+|[a-z_]+:[\w-]+)$/i;
71046
+ function evidenceStatus(stated, quote, source) {
71047
+ if (stated !== "observed") return stated;
71048
+ return STATISTIC.test(quote) || !CHECKABLE_SOURCE.test(source) ? "interpreted" : "observed";
71049
+ }
71050
+ function parseInsight(id, markdown) {
71051
+ const fm = frontmatterOf(markdown);
71052
+ const quote = text2(fm?.quote);
71053
+ const source = text2(fm?.source);
71054
+ if (!fm || !quote || !source) return null;
71055
+ const force = text2(fm.force);
71056
+ const persona = text2(fm.persona);
71057
+ const sourceKind = text2(fm.sourceKind);
71058
+ return {
71059
+ id,
71060
+ type: text2(fm.type) ?? "other",
71061
+ quote,
71062
+ source,
71063
+ status: evidenceStatus(oneOf(EVIDENCE_LEVELS, fm.status) ?? "observed", quote, source),
71064
+ ...sourceKind ? { sourceKind } : {},
71065
+ ...force ? { force } : {},
71066
+ ...persona ? { persona } : {}
71067
+ };
71068
+ }
71069
+ function evidenceList(value2) {
71070
+ if (Array.isArray(value2)) return value2.flatMap((e) => text2(e) ? [String(text2(e))] : []);
71071
+ const single = text2(value2);
71072
+ return single ? [single] : [];
71073
+ }
71074
+ function fieldProblems(f) {
71075
+ const problems = [];
71076
+ if (!f.who) problems.push("who is missing: the persona it is for");
71077
+ if (!f.message)
71078
+ problems.push(
71079
+ "message is missing: the one promise a stranger should get, what is offered and what changes for them"
71080
+ );
71081
+ if (!f.why) problems.push("why is missing: the brief finding it answers");
71082
+ if (!f.awareness) problems.push(`awareness must be one of ${AWARENESS_STAGES.join(", ")}`);
71083
+ if (!(f.angle && IDEA_ANGLES.includes(f.angle)))
71084
+ problems.push(`angle ${f.angle ? `"${f.angle}" ` : ""}must be one of ${IDEA_ANGLES.join(", ")}`);
71085
+ if (!(f.format && IDEA_FORMATS.includes(f.format)))
71086
+ problems.push(`format ${f.format ? `"${f.format}" ` : ""}must be one of ${IDEA_FORMATS.join(", ")}`);
71087
+ return problems;
71088
+ }
71089
+ function parseIdea(id, markdown) {
71090
+ const fm = frontmatterOf(markdown) ?? {};
71091
+ const f = {
71092
+ who: text2(fm.who),
71093
+ message: text2(fm.message),
71094
+ why: text2(fm.why),
71095
+ angle: text2(fm.angle),
71096
+ format: text2(fm.format),
71097
+ awareness: oneOf(AWARENESS_STAGES, fm.awareness)
71098
+ };
71099
+ const problems = fieldProblems(f);
71100
+ const { who, message, why, angle, format, awareness } = f;
71101
+ if (problems.length > 0 || !who || !message || !why || !awareness || !angle || !format) return { problems };
71102
+ return {
71103
+ idea: {
71104
+ id,
71105
+ who,
71106
+ message,
71107
+ evidence: evidenceList(fm.evidence),
71108
+ awareness,
71109
+ angle,
71110
+ format,
71111
+ job: fm.job === "closer" ? "closer" : "opener",
71112
+ why,
71113
+ status: oneOf(IDEA_STATUSES, fm.status) ?? "idea"
71114
+ },
71115
+ problems
71116
+ };
71117
+ }
71118
+ var EVIDENCE_WEIGHT = {
71119
+ observed: 3,
71120
+ interpreted: 2,
71121
+ hypothesized: 1,
71122
+ generated: 0,
71123
+ explore: 0
71124
+ };
71125
+ var LIVE_STATUSES = ["built", "testing", "winner"];
71126
+ function claimsOf(message) {
71127
+ return [...message.matchAll(/\d[\d.,]*\s?(?:%|€|\$|£|x\b|k\b)?/gi)].map((m) => m[0].trim());
71128
+ }
71129
+ function figureOf(claim) {
71130
+ return claim.replace(/[^\d]/g, "");
71131
+ }
71132
+ function figuresIn(text3) {
71133
+ return new Set([...text3.matchAll(/\d(?:[\d.,]*\d)?/g)].map((m) => figureOf(m[0])));
71134
+ }
71135
+ function normalize(value2) {
71136
+ return value2.toLowerCase().replace(/\s+/g, " ").trim();
71137
+ }
71138
+ function sentencesOf(message) {
71139
+ return message.split(/[.!?]+(?:\s|$)/).filter((s) => s.trim().length > 0).length;
71140
+ }
71141
+ var FILLER = /* @__PURE__ */ new Set([
71142
+ "that",
71143
+ "this",
71144
+ "with",
71145
+ "your",
71146
+ "from",
71147
+ "they",
71148
+ "them",
71149
+ "what",
71150
+ "when",
71151
+ "have",
71152
+ "will",
71153
+ "into",
71154
+ "than",
71155
+ "more",
71156
+ "just",
71157
+ "para",
71158
+ "como",
71159
+ "pero",
71160
+ "sobre"
71161
+ ]);
71162
+ var SAME_MESSAGE = 0.6;
71163
+ function messageWords(message) {
71164
+ return new Set(
71165
+ message.toLowerCase().split(/[^\p{L}]+/u).filter((w) => w.length >= 4 && !FILLER.has(w)).map((w) => w.replace(/s$/, ""))
71166
+ );
71167
+ }
71168
+ function sameMessage(a, b) {
71169
+ const wa = messageWords(a);
71170
+ const wb = messageWords(b);
71171
+ const shared = [...wa].filter((w) => wb.has(w)).length;
71172
+ const all = (/* @__PURE__ */ new Set([...wa, ...wb])).size;
71173
+ return normalize(a) === normalize(b) || all > 0 && shared / all >= SAME_MESSAGE;
71174
+ }
71175
+ function repeatedFailure(idea, failed) {
71176
+ const lost = failed.find(
71177
+ (f) => f.id !== idea.id && (sameMessage(f.message, idea.message) || f.angle === idea.angle && f.format === idea.format)
71178
+ );
71179
+ return lost ? [`says what "${lost.id}" said, and that idea failed in the account: change the message, not just the picture`] : [];
71180
+ }
71181
+ function backingOf(idea, insights, problems) {
71182
+ const backing = idea.evidence.flatMap((id) => {
71183
+ const insight = insights.get(id);
71184
+ if (!insight)
71185
+ problems.push(`evidence "${id}" is not in src/content/insights/: name an insight on file or leave it out`);
71186
+ return insight ? [insight] : [];
71187
+ });
71188
+ return [...backing].sort((a, b) => EVIDENCE_WEIGHT[b.status] - EVIDENCE_WEIGHT[a.status])[0];
71189
+ }
71190
+ function unsourcedClaims(text3, corpusFigures) {
71191
+ return claimsOf(text3).filter((claim) => {
71192
+ const figure = figureOf(claim);
71193
+ return figure !== "" && !corpusFigures.has(figure);
71194
+ });
71195
+ }
71196
+ function claimProblems(message, corpusFigures) {
71197
+ return unsourcedClaims(message, corpusFigures).map(
71198
+ (claim) => `"${claim}" is in no customer quote, COMPANY.md or Knowledge entry: source it or drop it`
71199
+ );
71200
+ }
71201
+ var MARKET_SOURCES = ["reddit", "forum", "web"];
71202
+ var TESTIMONIAL_FORMATS = ["review_screenshot", "testimonial_quote", "video_testimonial"];
71203
+ function fromMarket(insight) {
71204
+ return insight?.sourceKind !== void 0 && MARKET_SOURCES.includes(insight.sourceKind);
71205
+ }
71206
+ function sourcesClaims(insight) {
71207
+ return insight.status === "observed" && !fromMarket(insight);
71208
+ }
71209
+ function borrowedProof(idea, insights) {
71210
+ return idea.evidence.find((id) => {
71211
+ const insight = insights.get(id);
71212
+ return fromMarket(insight) && insight?.type === "proof";
71213
+ });
71214
+ }
71215
+ function fakeReviewProblem(idea, insights) {
71216
+ if (!TESTIMONIAL_FORMATS.includes(idea.format)) return [];
71217
+ const cited = idea.evidence.flatMap((id) => insights.get(id) ?? []);
71218
+ if (cited.length === 0 || !cited.every(fromMarket)) return [];
71219
+ return [
71220
+ `${idea.format} needs a quote from our own customer (a review, testimonial, call or survey); every one cited was said by someone in the market. Pick another format, or cite our own customer`
71221
+ ];
71222
+ }
71223
+ function warningsOf(idea, evidence, personas, insights) {
71224
+ const warnings = [];
71225
+ const borrowed = borrowedProof(idea, insights);
71226
+ if (borrowed) {
71227
+ warnings.push(
71228
+ `"${borrowed}" was said by someone in the market, not a customer of ours: use their words for the problem, never show it as our review, testimonial or result`
71229
+ );
71230
+ }
71231
+ if (sentencesOf(idea.message) > 1 || idea.message.split(/\s+/).length > 25) {
71232
+ warnings.push("the message says more than one thing: keep one promise, the rest goes to the ad text");
71233
+ }
71234
+ if (personas.length > 0 && !personas.includes(idea.who)) {
71235
+ warnings.push(`who "${idea.who}" is not a persona in the Knowledge (${personas.join(", ")})`);
71236
+ }
71237
+ if (evidence === "explore") {
71238
+ warnings.push("no customer evidence: an explore bet. `baker research customers` may find some");
71239
+ }
71240
+ return warnings;
71241
+ }
71242
+ function checkOne(idea, ctx) {
71243
+ const problems = [];
71244
+ const best = backingOf(idea, ctx.insights, problems);
71245
+ const evidence = best?.status ?? "explore";
71246
+ problems.push(...claimProblems(idea.message, ctx.corpusFigures), ...fakeReviewProblem(idea, ctx.insights));
71247
+ const duplicate = ctx.duplicateOf.get(idea.id);
71248
+ if (duplicate) {
71249
+ problems.push(
71250
+ `same angle and format, or the same message, as "${duplicate}": to the platform it is the same ad. Change the angle or the format`
71251
+ );
71252
+ }
71253
+ const gaps = [];
71254
+ if (!ctx.covered.has(idea.awareness)) gaps.push(`no live ad speaks to ${idea.awareness} people`);
71255
+ if (idea.job === "closer" && ctx.closersLive === 0) gaps.push("no live ad closes warm people");
71256
+ return {
71257
+ id: idea.id,
71258
+ status: idea.status,
71259
+ message: idea.message,
71260
+ evidence,
71261
+ ...best ? { quote: best.quote } : {},
71262
+ awareness: idea.awareness,
71263
+ angle: idea.angle,
71264
+ format: idea.format,
71265
+ problems,
71266
+ warnings: [...warningsOf(idea, evidence, ctx.personas, ctx.insights), ...repeatedFailure(idea, ctx.failed)],
71267
+ gaps,
71268
+ score: EVIDENCE_WEIGHT[evidence] + gaps.length - (problems.length > 0 ? 10 : 0)
71269
+ };
71270
+ }
71271
+ function duplicatesOf(ideas, insights) {
71272
+ const active = ideas.filter((i) => i.status !== "retired" && i.status !== "failed");
71273
+ const strength = (idea) => Math.max(0, ...idea.evidence.map((id) => EVIDENCE_WEIGHT[insights.get(id)?.status ?? "explore"]));
71274
+ const position = new Map(ideas.map((idea, index) => [idea.id, index]));
71275
+ const order = [...active].sort(
71276
+ (a, b) => Number(LIVE_STATUSES.includes(b.status)) - Number(LIVE_STATUSES.includes(a.status)) || strength(b) - strength(a) || (position.get(a.id) ?? 0) - (position.get(b.id) ?? 0)
71277
+ );
71278
+ const seenPairs = /* @__PURE__ */ new Map();
71279
+ const seenMessages = /* @__PURE__ */ new Map();
71280
+ const duplicateOf = /* @__PURE__ */ new Map();
71281
+ for (const idea of order) {
71282
+ const pair = `${idea.angle}\xD7${idea.format}`;
71283
+ const message = normalize(idea.message);
71284
+ const earlier = seenPairs.get(pair) ?? seenMessages.get(message);
71285
+ if (earlier) duplicateOf.set(idea.id, earlier);
71286
+ if (!seenPairs.has(pair)) seenPairs.set(pair, idea.id);
71287
+ if (!seenMessages.has(message)) seenMessages.set(message, idea.id);
71288
+ }
71289
+ return duplicateOf;
71290
+ }
71291
+ function checkIdeas(input) {
71292
+ const insights = new Map(input.insights.map((insight) => [insight.id, insight]));
71293
+ const ownQuotes = input.insights.filter(sourcesClaims).map((i) => i.quote);
71294
+ const corpusFigures = figuresIn(`${input.knowledge}
71295
+ ${ownQuotes.join("\n")}`);
71296
+ const covered = new Set(input.coveredAwareness);
71297
+ const duplicateOf = duplicatesOf(input.ideas, insights);
71298
+ const checks = input.ideas.map(
71299
+ (idea) => checkOne(idea, {
71300
+ insights,
71301
+ corpusFigures,
71302
+ covered,
71303
+ duplicateOf,
71304
+ personas: input.personas,
71305
+ closersLive: input.closersLive,
71306
+ failed: input.ideas.filter((i) => i.status === "failed")
71307
+ })
71308
+ );
71309
+ const toBuild = (c) => c.status === "idea";
71310
+ return checks.sort(
71311
+ (a, b) => Number(toBuild(b)) - Number(toBuild(a)) || b.score - a.score || a.id.localeCompare(b.id)
71312
+ );
71313
+ }
71314
+
71315
+ // src/commands/creatives/ideas-workspace.ts
71316
+ import { readdir as readdir6, readFile as readFile10 } from "fs/promises";
71317
+ import path13 from "path";
71318
+ var IDEAS_DIR = path13.join("src", "creatives", "_ideas");
71319
+ var INSIGHTS_DIR = path13.join("src", "content", "insights");
71320
+ async function markdownFiles(dir, recursive = false) {
71321
+ const entries = await readdir6(dir, { recursive }).catch(() => []);
71322
+ return entries.filter((f) => f.endsWith(".md")).sort();
71323
+ }
71324
+ var idOf2 = (file) => file.replace(/\\/g, "/").replace(/\.md$/, "");
71325
+ async function readInsights(root) {
71326
+ const dir = path13.join(root, INSIGHTS_DIR);
71327
+ const insights = [];
71328
+ for (const file of await markdownFiles(dir, true)) {
71329
+ const insight = parseInsight(idOf2(file), await readFile10(path13.join(dir, file), "utf8").catch(() => ""));
71330
+ if (insight) insights.push(insight);
71331
+ }
71332
+ return insights;
71333
+ }
71334
+ async function readIdeas(root) {
71335
+ const dir = path13.join(root, IDEAS_DIR);
71336
+ const ideas = [];
71337
+ const unreadable = [];
71338
+ for (const file of await markdownFiles(dir)) {
71339
+ const id = idOf2(file);
71340
+ const parsed = parseIdea(id, await readFile10(path13.join(dir, file), "utf8").catch(() => ""));
71341
+ if (parsed.idea) ideas.push(parsed.idea);
71342
+ else unreadable.push({ id, problems: parsed.problems });
71343
+ }
71344
+ return { ideas, unreadable };
71345
+ }
71346
+ async function readPersonaIds(root) {
71347
+ return (await markdownFiles(path13.join(root, "src", "content", "personas"), true)).map(idOf2);
71348
+ }
71349
+ var MAX_KNOWLEDGE_CHARS = 2e5;
71350
+ async function readKnowledgeCorpus(root) {
71351
+ const parts = [];
71352
+ const company = await readFile10(path13.join(root, "COMPANY.md"), "utf8").catch(() => "");
71353
+ if (company) parts.push(company);
71354
+ const content = path13.join(root, "src", "content");
71355
+ const insights = path13.relative(content, path13.join(root, INSIGHTS_DIR));
71356
+ const files = (await markdownFiles(content, true)).filter((f) => !f.replace(/\\/g, "/").startsWith(`${insights}/`));
71357
+ for (const file of files) {
71358
+ parts.push(await readFile10(path13.join(content, file), "utf8").catch(() => ""));
71359
+ if (parts.join("").length > MAX_KNOWLEDGE_CHARS) break;
71360
+ }
71361
+ return parts.join("\n").slice(0, MAX_KNOWLEDGE_CHARS);
71362
+ }
71363
+ async function readCreativeRecords(root) {
71364
+ const dir = path13.join(root, "src", "creatives");
71365
+ const entries = await readdir6(dir, { withFileTypes: true }).catch(() => []);
71366
+ const records = [];
71367
+ for (const entry of entries) {
71368
+ if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
71369
+ const raw = await readFile10(path13.join(dir, entry.name, "_definition.md"), "utf8").catch(() => null);
71370
+ if (raw === null) continue;
71371
+ const fm = frontmatterOf(raw) ?? {};
71372
+ const pick2 = (key) => typeof fm[key] === "string" && fm[key] ? { [key]: fm[key] } : {};
71373
+ records.push({
71374
+ slug: entry.name,
71375
+ ...pick2("status"),
71376
+ ...pick2("idea"),
71377
+ ...pick2("awareness"),
71378
+ ...pick2("angle"),
71379
+ ...pick2("kind")
71380
+ });
71381
+ }
71382
+ return records;
71383
+ }
71384
+ var SHOWN_STATUSES = /* @__PURE__ */ new Set(["rendered", "published"]);
71385
+ function coverageOf(ideas, creatives) {
71386
+ const live = ideas.filter((i) => i.status === "built" || i.status === "testing" || i.status === "winner");
71387
+ const awareness = new Set(live.map((i) => i.awareness));
71388
+ for (const creative of creatives) {
71389
+ if (creative.awareness && SHOWN_STATUSES.has(creative.status ?? "")) awareness.add(creative.awareness);
71390
+ }
71391
+ return { coveredAwareness: [...awareness].sort(), closersLive: live.filter((i) => i.job === "closer").length };
71392
+ }
71393
+
70975
71394
  // src/engine/scaffold/static-upgrade.ts
70976
71395
  var TAKE_ID = /^ad(?:_[a-c])?$/;
70977
71396
  var RELAY_ID = /^ad_(\d+)x(\d+)$/;
@@ -71185,7 +71604,7 @@ function upgradedDescription(text3, old, plan) {
71185
71604
  }
71186
71605
 
71187
71606
  // src/commands/canvas/style-projection.ts
71188
- import { readFile as readFile10, writeFile as writeFile5 } from "fs/promises";
71607
+ import { readFile as readFile11, writeFile as writeFile5 } from "fs/promises";
71189
71608
  function findBlueprintProjection(canvas) {
71190
71609
  if (!canvas || typeof canvas !== "object") return null;
71191
71610
  const nodes = canvas.nodes;
@@ -71213,8 +71632,8 @@ function renderStyleProjectionFromValue(blueprint) {
71213
71632
  async function syncStyleProjection(canvas, log) {
71214
71633
  const pair = findBlueprintProjection(canvas);
71215
71634
  if (!pair) return "not_applicable";
71216
- const rendered = renderStyleProjection(await readFile10(pair.promptPath, "utf8"));
71217
- const current = await readFile10(pair.stylePath, "utf8").catch(() => null);
71635
+ const rendered = renderStyleProjection(await readFile11(pair.promptPath, "utf8"));
71636
+ const current = await readFile11(pair.stylePath, "utf8").catch(() => null);
71218
71637
  if (current === rendered) return "up_to_date";
71219
71638
  await writeFile5(pair.stylePath, rendered, "utf8");
71220
71639
  log(
@@ -71262,10 +71681,13 @@ async function staticSpecGate(canvas, canvasDir) {
71262
71681
  return null;
71263
71682
  }
71264
71683
  async function readRecords(root) {
71265
- const parts = [await readFile11(path13.join(root, "COMPANY.md"), "utf8").catch(() => "")];
71266
- const content = path13.join(root, "src", "content");
71267
- const files = (await readdir6(content, { recursive: true }).catch(() => [])).filter((f) => /\.(md|mdx|json|ya?ml)$/.test(f));
71268
- for (const file of files.slice(0, 400)) parts.push(await readFile11(path13.join(content, file), "utf8").catch(() => ""));
71684
+ const parts = [await readFile12(path14.join(root, "COMPANY.md"), "utf8").catch(() => "")];
71685
+ const content = path14.join(root, "src", "content");
71686
+ const files = (await readdir7(content, { recursive: true }).catch(() => [])).filter(
71687
+ (f) => /\.(md|mdx|json|ya?ml)$/.test(f) && !f.replace(/\\/g, "/").startsWith("insights/")
71688
+ );
71689
+ for (const file of files.slice(0, 400)) parts.push(await readFile12(path14.join(content, file), "utf8").catch(() => ""));
71690
+ parts.push(...(await readInsights(root)).filter(sourcesClaims).map((insight) => insight.quote));
71269
71691
  return parts.join("\n");
71270
71692
  }
71271
71693
  var CanvasPrepareError = class extends Error {
@@ -71288,14 +71710,14 @@ var CanvasPrepareError = class extends Error {
71288
71710
  }
71289
71711
  };
71290
71712
  async function prepareCanvas(canvasPath, opts = {}) {
71291
- const filePath = path13.resolve(canvasPath);
71292
- const canvasDir = path13.dirname(filePath);
71713
+ const filePath = path14.resolve(canvasPath);
71714
+ const canvasDir = path14.dirname(filePath);
71293
71715
  const notes = [];
71294
71716
  const log = (line) => {
71295
71717
  notes.push(line);
71296
71718
  opts.log?.(line);
71297
71719
  };
71298
- const raw = await readFile11(filePath, "utf8");
71720
+ const raw = await readFile12(filePath, "utf8");
71299
71721
  let parsed;
71300
71722
  try {
71301
71723
  parsed = JSON.parse(raw);
@@ -71334,7 +71756,7 @@ async function prepareCanvas(canvasPath, opts = {}) {
71334
71756
  if (logo === "regenerated" || pictures === "regenerated") staticCopy = "regenerated";
71335
71757
  } catch (e) {
71336
71758
  throw new CanvasPrepareError("style_projection", `prompt.json is not readable or not a valid ad: ${errorMessage(e)}`, {
71337
- fix: `Fix ${path13.join(canvasDir, "prompt.json")} so it parses as JSON (and, for a static ad, matches the ad spec the message names), then re-run.`
71759
+ fix: `Fix ${path14.join(canvasDir, "prompt.json")} so it parses as JSON (and, for a static ad, matches the ad spec the message names), then re-run.`
71338
71760
  });
71339
71761
  }
71340
71762
  const gate = opts.gate && isStaticBriefCanvas(canvas) ? await staticSpecGate(canvas, canvasDir) : null;
@@ -71346,7 +71768,7 @@ async function prepareCanvas(canvasPath, opts = {}) {
71346
71768
  throw new CanvasPrepareError(
71347
71769
  "scene_projection",
71348
71770
  `a scenes/*.json file is not readable/valid or its rebuild failed: ${errorMessage(e)}`,
71349
- { fix: `Fix the scene files in ${path13.join(canvasDir, "scenes")} so each parses as JSON, then re-run.` }
71771
+ { fix: `Fix the scene files in ${path14.join(canvasDir, "scenes")} so each parses as JSON, then re-run.` }
71350
71772
  );
71351
71773
  }
71352
71774
  const projected = withFilePaths(canvas, onDisk, canvasDir);
@@ -71382,7 +71804,7 @@ async function prepareCanvas(canvasPath, opts = {}) {
71382
71804
  };
71383
71805
  }
71384
71806
  function healInMemory(filePath, canvas) {
71385
- const canvasDir = path13.dirname(filePath);
71807
+ const canvasDir = path14.dirname(filePath);
71386
71808
  const workspaceRoot = findWorkspaceRoot(canvasDir);
71387
71809
  if (!workspaceRoot) return { canvas, rewrites: [], text: null };
71388
71810
  return { ...normalizeCanvasPaths(canvas, canvasDir, workspaceRoot), text: null };
@@ -71403,10 +71825,10 @@ function withFilePaths(projected, onDisk, canvasDir) {
71403
71825
  if (!key || !node.params || typeof node.params[key] !== "string") continue;
71404
71826
  const value2 = node.params[key];
71405
71827
  const original = byId.get(node.id)?.params?.[key];
71406
- if (typeof original === "string" && path13.resolve(canvasDir, original) === path13.resolve(canvasDir, value2)) {
71828
+ if (typeof original === "string" && path14.resolve(canvasDir, original) === path14.resolve(canvasDir, value2)) {
71407
71829
  node.params[key] = original;
71408
- } else if (path13.isAbsolute(value2)) {
71409
- const rel = path13.relative(canvasDir, value2).split(path13.sep).join("/");
71830
+ } else if (path14.isAbsolute(value2)) {
71831
+ const rel = path14.relative(canvasDir, value2).split(path14.sep).join("/");
71410
71832
  node.params[key] = rel.startsWith(".") ? rel : `./${rel}`;
71411
71833
  }
71412
71834
  }
@@ -71430,7 +71852,7 @@ var critiqueCommand = defineCommand135({
71430
71852
  },
71431
71853
  args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
71432
71854
  async run({ args: args8 }) {
71433
- const filePath = path14.resolve(String(args8.file));
71855
+ const filePath = path15.resolve(String(args8.file));
71434
71856
  let prepared;
71435
71857
  try {
71436
71858
  prepared = await prepareCanvas(filePath, { log: (line) => process.stderr.write(`${line}
@@ -71458,8 +71880,8 @@ var critiqueCommand = defineCommand135({
71458
71880
 
71459
71881
  // src/commands/canvas/inspect.ts
71460
71882
  import { execFile } from "child_process";
71461
- import { readdir as readdir7, readFile as readFile12, stat as stat2 } from "fs/promises";
71462
- import path15 from "path";
71883
+ import { readdir as readdir8, readFile as readFile13, stat as stat2 } from "fs/promises";
71884
+ import path16 from "path";
71463
71885
  import { promisify } from "util";
71464
71886
  import { defineCommand as defineCommand136 } from "citty";
71465
71887
 
@@ -71491,12 +71913,12 @@ var inspectCommand = defineCommand136({
71491
71913
  },
71492
71914
  async run({ args: args8 }) {
71493
71915
  const cwd = process.cwd();
71494
- const outputsDir = args8["outputs-dir"] ? path15.resolve(String(args8["outputs-dir"])) : path15.join(findWorkspaceRoot(cwd) ?? cwd, "canvas");
71916
+ const outputsDir = args8["outputs-dir"] ? path16.resolve(String(args8["outputs-dir"])) : path16.join(findWorkspaceRoot(cwd) ?? cwd, "canvas");
71495
71917
  const runArg = String(args8.run);
71496
71918
  const runDir = await localRunDir(runArg, outputsDir);
71497
71919
  if (!runDir) {
71498
- if (path15.isAbsolute(runArg)) fail3("not_found", `${runArg} is not a run directory`);
71499
- await inspectFromHistory(runArg, path15.join(outputsDir, runArg));
71920
+ if (path16.isAbsolute(runArg)) fail3("not_found", `${runArg} is not a run directory`);
71921
+ await inspectFromHistory(runArg, path16.join(outputsDir, runArg));
71500
71922
  return;
71501
71923
  }
71502
71924
  const manifest = await loadManifest(runDir);
@@ -71509,7 +71931,7 @@ var inspectCommand = defineCommand136({
71509
71931
  const summary = {
71510
71932
  ok: true,
71511
71933
  source: "local",
71512
- run_id: manifest.run_id ?? path15.basename(runDir),
71934
+ run_id: manifest.run_id ?? path16.basename(runDir),
71513
71935
  run_dir: runDir,
71514
71936
  stats: manifest.stats ?? null,
71515
71937
  output: manifest.output ?? null,
@@ -71522,7 +71944,7 @@ var inspectCommand = defineCommand136({
71522
71944
  }
71523
71945
  });
71524
71946
  async function localRunDir(run3, outputsDir) {
71525
- const candidate = path15.isAbsolute(run3) ? run3 : path15.join(outputsDir, run3);
71947
+ const candidate = path16.isAbsolute(run3) ? run3 : path16.join(outputsDir, run3);
71526
71948
  const s = await stat2(candidate).catch(() => null);
71527
71949
  return s?.isDirectory() ? candidate : null;
71528
71950
  }
@@ -71577,9 +71999,9 @@ function fail3(code, message) {
71577
71999
  process.exit(2);
71578
72000
  }
71579
72001
  async function loadManifest(runDir) {
71580
- const manifestPath = path15.join(runDir, "manifest.json");
72002
+ const manifestPath = path16.join(runDir, "manifest.json");
71581
72003
  try {
71582
- const raw = await readFile12(manifestPath, "utf-8");
72004
+ const raw = await readFile13(manifestPath, "utf-8");
71583
72005
  return JSON.parse(raw);
71584
72006
  } catch {
71585
72007
  return {};
@@ -71587,9 +72009,9 @@ async function loadManifest(runDir) {
71587
72009
  }
71588
72010
  async function listRunFiles(runDir) {
71589
72011
  const out = [];
71590
- const names = await readdir7(runDir);
72012
+ const names = await readdir8(runDir);
71591
72013
  for (const name of names) {
71592
- const abs = path15.join(runDir, name);
72014
+ const abs = path16.join(runDir, name);
71593
72015
  const s = await stat2(abs).catch(() => null);
71594
72016
  if (!s?.isFile()) continue;
71595
72017
  out.push({ name, path: abs, size: s.size });
@@ -71637,18 +72059,18 @@ async function probeDuration(filePath) {
71637
72059
  import { defineCommand as defineCommand140 } from "citty";
71638
72060
 
71639
72061
  // src/commands/canvas/run.ts
71640
- import path28 from "path";
72062
+ import path29 from "path";
71641
72063
  import { defineCommand as defineCommand139 } from "citty";
71642
72064
 
71643
72065
  // src/commands/canvas/definition-status.ts
71644
- import { readFile as readFile13, writeFile as writeFile7 } from "fs/promises";
71645
- import path16 from "path";
72066
+ import { readFile as readFile14, writeFile as writeFile7 } from "fs/promises";
72067
+ import path17 from "path";
71646
72068
  import { parseDocument } from "yaml";
71647
72069
  var ORDER = { draft: 0, rendered: 1, published: 2 };
71648
72070
  async function advanceDefinitionStatus(definitionPath, to) {
71649
72071
  let raw;
71650
72072
  try {
71651
- raw = await readFile13(definitionPath, "utf8");
72073
+ raw = await readFile14(definitionPath, "utf8");
71652
72074
  } catch {
71653
72075
  return "missing";
71654
72076
  }
@@ -71671,12 +72093,12 @@ ${nextFrontmatter}---${raw.slice(match[0].length)}`, "utf8");
71671
72093
  return "updated";
71672
72094
  }
71673
72095
  function definitionPathFor(workspaceRoot, slug) {
71674
- return path16.join(workspaceRoot, "src", "creatives", slug, "_definition.md");
72096
+ return path17.join(workspaceRoot, "src", "creatives", slug, "_definition.md");
71675
72097
  }
71676
72098
 
71677
72099
  // src/commands/canvas/output-ratio.ts
71678
- import { readFile as readFile14 } from "fs/promises";
71679
- import path17 from "path";
72100
+ import { readFile as readFile15 } from "fs/promises";
72101
+ import path18 from "path";
71680
72102
  function definitionFormats2(markdown) {
71681
72103
  const front = markdown.match(/^---\n([\s\S]*?)\n---/)?.[1];
71682
72104
  if (!front) return [];
@@ -71751,21 +72173,21 @@ function outputRatioWarnings(declared, produced) {
71751
72173
  });
71752
72174
  }
71753
72175
  async function outputRatioWarningsFor(canvas, canvasPath) {
71754
- const definition = await readFile14(path17.join(path17.dirname(canvasPath), "_definition.md"), "utf8").catch(() => null);
72176
+ const definition = await readFile15(path18.join(path18.dirname(canvasPath), "_definition.md"), "utf8").catch(() => null);
71755
72177
  if (!definition) return [];
71756
72178
  return outputRatioWarnings(definitionFormats2(definition), await producedRatios(canvas));
71757
72179
  }
71758
72180
 
71759
72181
  // src/commands/canvas/render-state.ts
71760
- import { mkdir as mkdir3, readdir as readdir8, readFile as readFile15, rm as rm2, writeFile as writeFile8 } from "fs/promises";
71761
- import path18 from "path";
72182
+ import { mkdir as mkdir3, readdir as readdir9, readFile as readFile16, rm as rm2, writeFile as writeFile8 } from "fs/promises";
72183
+ import path19 from "path";
71762
72184
  var RENDER_STATE_DIR = ".rendered";
71763
72185
  function statePath(canvasRoot, slug) {
71764
- return path18.join(canvasRoot, RENDER_STATE_DIR, `${slug}.json`);
72186
+ return path19.join(canvasRoot, RENDER_STATE_DIR, `${slug}.json`);
71765
72187
  }
71766
72188
  async function readRenderState(canvasRoot, slug) {
71767
72189
  try {
71768
- const parsed = JSON.parse(await readFile15(statePath(canvasRoot, slug), "utf8"));
72190
+ const parsed = JSON.parse(await readFile16(statePath(canvasRoot, slug), "utf8"));
71769
72191
  return parsed && typeof parsed === "object" ? parsed : {};
71770
72192
  } catch {
71771
72193
  return {};
@@ -71774,7 +72196,7 @@ async function readRenderState(canvasRoot, slug) {
71774
72196
  async function updateRenderState(canvasRoot, slug, patch) {
71775
72197
  try {
71776
72198
  const next = { ...await readRenderState(canvasRoot, slug), ...patch };
71777
- await mkdir3(path18.join(canvasRoot, RENDER_STATE_DIR), { recursive: true });
72199
+ await mkdir3(path19.join(canvasRoot, RENDER_STATE_DIR), { recursive: true });
71778
72200
  await writeFile8(statePath(canvasRoot, slug), `${JSON.stringify(next, null, 2)}
71779
72201
  `, "utf8");
71780
72202
  } catch {
@@ -71805,11 +72227,11 @@ function isRenderStale(state, currentSourceSha) {
71805
72227
  return state.attempted?.sourceSha !== currentSourceSha;
71806
72228
  }
71807
72229
  async function staleCreatives(workspaceRoot) {
71808
- const creativesDir = path18.join(workspaceRoot, "src", "creatives");
71809
- const canvasRoot = path18.join(workspaceRoot, "canvas");
72230
+ const creativesDir = path19.join(workspaceRoot, "src", "creatives");
72231
+ const canvasRoot = path19.join(workspaceRoot, "canvas");
71810
72232
  let slugs;
71811
72233
  try {
71812
- slugs = (await readdir8(creativesDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
72234
+ slugs = (await readdir9(creativesDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
71813
72235
  } catch {
71814
72236
  return [];
71815
72237
  }
@@ -71817,7 +72239,7 @@ async function staleCreatives(workspaceRoot) {
71817
72239
  for (const slug of slugs.sort()) {
71818
72240
  const state = await readRenderState(canvasRoot, slug);
71819
72241
  if (!state.rendered) continue;
71820
- const canvasPath = path18.join(creativesDir, slug, `${slug}.canvas.json`);
72242
+ const canvasPath = path19.join(creativesDir, slug, `${slug}.canvas.json`);
71821
72243
  let prepared;
71822
72244
  try {
71823
72245
  prepared = await prepareCanvas(canvasPath, { write: false });
@@ -71829,7 +72251,7 @@ async function staleCreatives(workspaceRoot) {
71829
72251
  if (!current || !isRenderStale(state, current)) continue;
71830
72252
  stale.push({
71831
72253
  slug,
71832
- canvasPath: path18.relative(workspaceRoot, canvasPath).split(path18.sep).join("/"),
72254
+ canvasPath: path19.relative(workspaceRoot, canvasPath).split(path19.sep).join("/"),
71833
72255
  renderedRunId: state.rendered.runId,
71834
72256
  sourceSha: current
71835
72257
  });
@@ -71837,7 +72259,7 @@ async function staleCreatives(workspaceRoot) {
71837
72259
  return stale;
71838
72260
  }
71839
72261
  async function clearLegacyDirtyMarker(canvasRoot, slug) {
71840
- await rm2(path18.join(canvasRoot, ".dirty", `${slug}.json`), { force: true }).catch(() => void 0);
72262
+ await rm2(path19.join(canvasRoot, ".dirty", `${slug}.json`), { force: true }).catch(() => void 0);
71841
72263
  }
71842
72264
 
71843
72265
  // src/commands/canvas/node-preview.ts
@@ -71893,13 +72315,13 @@ ${body}` : header || body || compactJson(record);
71893
72315
  }
71894
72316
 
71895
72317
  // src/commands/canvas/run-record.ts
71896
- import path19 from "path";
72318
+ import path20 from "path";
71897
72319
  var MAX_RUN_NODES = 200;
71898
72320
  var MAX_OUTPUTS_PER_NODE = 10;
71899
72321
  var MAX_FINAL_OUTPUTS = 10;
71900
72322
  var MAX_CREATIVE_SLUG_LENGTH = 100;
71901
72323
  function creativeSlugFromCanvasPath(filePath) {
71902
- const normalized2 = filePath.split(path19.sep).join("/");
72324
+ const normalized2 = filePath.split(path20.sep).join("/");
71903
72325
  const match = normalized2.match(/(?:^|\/)src\/creatives\/([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\//);
71904
72326
  const slug = match?.[1] ?? null;
71905
72327
  return slug && slug.length <= MAX_CREATIVE_SLUG_LENGTH ? slug : null;
@@ -72207,7 +72629,7 @@ var RunRecordPoster = class {
72207
72629
 
72208
72630
  // src/commands/canvas/run-retention.ts
72209
72631
  import { rm as rm3 } from "fs/promises";
72210
- import path20 from "path";
72632
+ import path21 from "path";
72211
72633
  function runDirsToPrune(entries, keep, currentRunId) {
72212
72634
  const runs = entries.filter((e) => /^r_[0-9A-Za-z]+$/.test(e) && e !== currentRunId).sort();
72213
72635
  if (keep <= 0) return runs;
@@ -72224,7 +72646,7 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
72224
72646
  const toPrune = runDirsToPrune(entries, keep, currentRunId);
72225
72647
  if (toPrune.length === 0) return;
72226
72648
  for (const dir of toPrune) {
72227
- await rm3(path20.join(outputsDir, dir), { recursive: true, force: true }).catch(
72649
+ await rm3(path21.join(outputsDir, dir), { recursive: true, force: true }).catch(
72228
72650
  (e) => log(`[prune ] could not remove ${dir}: ${e.message}`)
72229
72651
  );
72230
72652
  }
@@ -72233,15 +72655,15 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
72233
72655
 
72234
72656
  // src/commands/canvas/run-resume.ts
72235
72657
  import { randomUUID } from "crypto";
72236
- import { mkdir as mkdir4, readFile as readFile16, rm as rm4, writeFile as writeFile9 } from "fs/promises";
72237
- import path21 from "path";
72658
+ import { mkdir as mkdir4, readFile as readFile17, rm as rm4, writeFile as writeFile9 } from "fs/promises";
72659
+ import path22 from "path";
72238
72660
  function markerKey(canvasPath) {
72239
72661
  const slug = creativeSlugFromCanvasPath(canvasPath);
72240
- const identity = slug ?? path21.relative(process.cwd(), path21.resolve(canvasPath));
72662
+ const identity = slug ?? path22.relative(process.cwd(), path22.resolve(canvasPath));
72241
72663
  return sha256Hex(Buffer.from(identity)).slice(0, 32);
72242
72664
  }
72243
72665
  function markerFile(outputsDir, canvasPath) {
72244
- return path21.join(outputsDir, ".inflight", `${markerKey(canvasPath)}.json`);
72666
+ return path22.join(outputsDir, ".inflight", `${markerKey(canvasPath)}.json`);
72245
72667
  }
72246
72668
  function classifyRemoteRun(run3) {
72247
72669
  if (run3.interrupted === true || run3.status === "awaiting_approval") return "adopt";
@@ -72294,7 +72716,7 @@ async function resolveRunId(opts) {
72294
72716
  }
72295
72717
  async function readMarker(outputsDir, canvasPath) {
72296
72718
  try {
72297
- const parsed = JSON.parse(await readFile16(markerFile(outputsDir, canvasPath), "utf8"));
72719
+ const parsed = JSON.parse(await readFile17(markerFile(outputsDir, canvasPath), "utf8"));
72298
72720
  if (typeof parsed.runId !== "string" || parsed.runId.length === 0) return null;
72299
72721
  const num4 = (value2) => typeof value2 === "number" && Number.isFinite(value2) ? value2 : void 0;
72300
72722
  return {
@@ -72319,14 +72741,14 @@ async function markRunInFlight(outputsDir, canvasPath, runId, sourceSha) {
72319
72741
  runId,
72320
72742
  pid: process.pid,
72321
72743
  token,
72322
- canvasPath: path21.resolve(canvasPath),
72744
+ canvasPath: path22.resolve(canvasPath),
72323
72745
  startedAt,
72324
72746
  heartbeatAt,
72325
72747
  ...sourceSha ? { sourceSha } : {}
72326
72748
  })
72327
72749
  );
72328
72750
  try {
72329
- await mkdir4(path21.dirname(file), { recursive: true });
72751
+ await mkdir4(path22.dirname(file), { recursive: true });
72330
72752
  await write(startedAt);
72331
72753
  } catch {
72332
72754
  }
@@ -72349,8 +72771,8 @@ async function clearRunMarker(outputsDir, canvasPath, token) {
72349
72771
 
72350
72772
  // src/commands/canvas/run-snapshot.ts
72351
72773
  import { existsSync as existsSync5 } from "fs";
72352
- import { mkdir as mkdir5, readdir as readdir9, readFile as readFile17, stat as stat3, writeFile as writeFile10 } from "fs/promises";
72353
- import path22 from "path";
72774
+ import { mkdir as mkdir5, readdir as readdir10, readFile as readFile18, stat as stat3, writeFile as writeFile10 } from "fs/promises";
72775
+ import path23 from "path";
72354
72776
  var SNAPSHOT_SCHEMA = "baker-canvas-snapshot/1";
72355
72777
  var MAX_SNAPSHOT_FILE_BYTES = 32 * 1024 * 1024;
72356
72778
  var EXT_TO_MIME = {
@@ -72377,15 +72799,15 @@ var EXT_TO_MIME = {
72377
72799
  woff2: "font/woff2"
72378
72800
  };
72379
72801
  function mimeForFile(filePath) {
72380
- const ext = path22.extname(filePath).slice(1).toLowerCase();
72802
+ const ext = path23.extname(filePath).slice(1).toLowerCase();
72381
72803
  return EXT_TO_MIME[ext] ?? "application/octet-stream";
72382
72804
  }
72383
72805
  function toPosix2(p) {
72384
- return p.split(path22.sep).join("/");
72806
+ return p.split(path23.sep).join("/");
72385
72807
  }
72386
72808
  function isInside2(dir, target2) {
72387
- const rel = path22.relative(dir, target2);
72388
- return !rel.startsWith("..") && !path22.isAbsolute(rel);
72809
+ const rel = path23.relative(dir, target2);
72810
+ return !rel.startsWith("..") && !path23.isAbsolute(rel);
72389
72811
  }
72390
72812
  function localPathRefsFromCanvas(parsed) {
72391
72813
  const nodes = parsed?.nodes;
@@ -72408,20 +72830,20 @@ var FINGERPRINTED_FILES = ["prompt.json", "_definition.md"];
72408
72830
  async function sourceRefsToSnapshot(canvasDir, parsed) {
72409
72831
  const refs = new Set(localPathRefsFromCanvas(parsed));
72410
72832
  for (const sceneFile of await listSceneFiles(canvasDir)) {
72411
- refs.add(toPosix2(path22.relative(canvasDir, sceneFile)));
72833
+ refs.add(toPosix2(path23.relative(canvasDir, sceneFile)));
72412
72834
  }
72413
72835
  refs.add(REBUILD_FILE);
72414
72836
  for (const fingerprinted of FINGERPRINTED_FILES) {
72415
- if (existsSync5(path22.join(canvasDir, fingerprinted))) refs.add(fingerprinted);
72837
+ if (existsSync5(path23.join(canvasDir, fingerprinted))) refs.add(fingerprinted);
72416
72838
  }
72417
72839
  return [...refs];
72418
72840
  }
72419
72841
  async function uploadRunSnapshot(client, opts) {
72420
72842
  try {
72421
- const canvasDir = path22.dirname(opts.canvasPath);
72843
+ const canvasDir = path23.dirname(opts.canvasPath);
72422
72844
  const canvasBytes = Buffer.from(opts.raw);
72423
72845
  const canvasEntry = {
72424
- path: path22.basename(opts.canvasPath),
72846
+ path: path23.basename(opts.canvasPath),
72425
72847
  bytes: canvasBytes,
72426
72848
  sha256: sha256Hex(canvasBytes),
72427
72849
  mime: "application/json"
@@ -72429,10 +72851,10 @@ async function uploadRunSnapshot(client, opts) {
72429
72851
  const files = [];
72430
72852
  const skipped = [];
72431
72853
  for (const refPath of await sourceRefsToSnapshot(canvasDir, opts.parsed)) {
72432
- const abs = path22.isAbsolute(refPath) ? refPath : path22.resolve(canvasDir, refPath);
72854
+ const abs = path23.isAbsolute(refPath) ? refPath : path23.resolve(canvasDir, refPath);
72433
72855
  if (!isInside2(canvasDir, abs)) {
72434
72856
  skipped.push({
72435
- path: toPosix2(path22.relative(canvasDir, abs)),
72857
+ path: toPosix2(path23.relative(canvasDir, abs)),
72436
72858
  reason: "outside the creative folder \u2014 read from the workspace on rerun"
72437
72859
  });
72438
72860
  continue;
@@ -72441,18 +72863,18 @@ async function uploadRunSnapshot(client, opts) {
72441
72863
  try {
72442
72864
  st = await stat3(abs);
72443
72865
  } catch {
72444
- skipped.push({ path: toPosix2(path22.relative(canvasDir, abs)), reason: "missing" });
72866
+ skipped.push({ path: toPosix2(path23.relative(canvasDir, abs)), reason: "missing" });
72445
72867
  continue;
72446
72868
  }
72447
72869
  const fileList = st.isDirectory() ? await listFilesRecursive(abs) : [abs];
72448
72870
  for (const file of fileList) {
72449
- const rel = toPosix2(path22.relative(canvasDir, file));
72871
+ const rel = toPosix2(path23.relative(canvasDir, file));
72450
72872
  const size = (await stat3(file)).size;
72451
72873
  if (size > MAX_SNAPSHOT_FILE_BYTES) {
72452
72874
  skipped.push({ path: rel, reason: `too large (${size} bytes)` });
72453
72875
  continue;
72454
72876
  }
72455
- const bytes = await readFile17(file);
72877
+ const bytes = await readFile18(file);
72456
72878
  files.push({ path: rel, bytes, sha256: sha256Hex(bytes), mime: mimeForFile(file) });
72457
72879
  }
72458
72880
  }
@@ -72508,8 +72930,8 @@ async function putContentAddressed(client, bytes, mime, signal) {
72508
72930
  return { sha256: sha2562, url: publicUrl };
72509
72931
  }
72510
72932
  async function listFilesRecursive(dir) {
72511
- const entries = await readdir9(dir, { recursive: true, withFileTypes: true });
72512
- return entries.filter((d) => d.isFile()).map((d) => path22.join(d.parentPath, d.name));
72933
+ const entries = await readdir10(dir, { recursive: true, withFileTypes: true });
72934
+ return entries.filter((d) => d.isFile()).map((d) => path23.join(d.parentPath, d.name));
72513
72935
  }
72514
72936
  var SnapshotConflictError = class extends Error {
72515
72937
  conflicts;
@@ -72521,19 +72943,19 @@ var SnapshotConflictError = class extends Error {
72521
72943
  };
72522
72944
  async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
72523
72945
  const entries = [manifest.canvas, ...manifest.files];
72524
- const resolvedTarget = path22.resolve(targetDir);
72946
+ const resolvedTarget = path23.resolve(targetDir);
72525
72947
  const planned = [];
72526
72948
  const conflicts = [];
72527
72949
  const upToDate = [];
72528
72950
  for (const entry of entries) {
72529
- if (path22.isAbsolute(entry.path) || entry.path.split("/").includes("..")) {
72951
+ if (path23.isAbsolute(entry.path) || entry.path.split("/").includes("..")) {
72530
72952
  throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
72531
72953
  }
72532
- const target2 = path22.resolve(resolvedTarget, entry.path);
72533
- if (target2 !== resolvedTarget && !target2.startsWith(resolvedTarget + path22.sep)) {
72954
+ const target2 = path23.resolve(resolvedTarget, entry.path);
72955
+ if (target2 !== resolvedTarget && !target2.startsWith(resolvedTarget + path23.sep)) {
72534
72956
  throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
72535
72957
  }
72536
- const existing = await readFile17(target2).catch(() => null);
72958
+ const existing = await readFile18(target2).catch(() => null);
72537
72959
  if (existing) {
72538
72960
  if (sha256Hex(existing) === entry.sha256) {
72539
72961
  upToDate.push(entry.path);
@@ -72555,11 +72977,11 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
72555
72977
  if (sha256Hex(bytes) !== entry.sha256) {
72556
72978
  throw new Error(`snapshot download for ${entry.path} does not match its recorded sha256`);
72557
72979
  }
72558
- await mkdir5(path22.dirname(target2), { recursive: true });
72980
+ await mkdir5(path23.dirname(target2), { recursive: true });
72559
72981
  await writeFile10(target2, bytes);
72560
72982
  restored.push(entry.path);
72561
72983
  }
72562
- return { canvasPath: path22.resolve(resolvedTarget, manifest.canvas.path), restored, upToDate };
72984
+ return { canvasPath: path23.resolve(resolvedTarget, manifest.canvas.path), restored, upToDate };
72563
72985
  }
72564
72986
 
72565
72987
  // src/commands/canvas/static-ad-canvas.ts
@@ -72886,7 +73308,7 @@ async function fetchBrandLogo(domain) {
72886
73308
 
72887
73309
  // src/commands/canvas/slug-restore.ts
72888
73310
  import { existsSync as existsSync6 } from "fs";
72889
- import path23 from "path";
73311
+ import path24 from "path";
72890
73312
  var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
72891
73313
  var SlugRestoreError = class extends Error {
72892
73314
  code;
@@ -72902,8 +73324,8 @@ async function canvasForSlug(slug, opts) {
72902
73324
  if (!SLUG_PATTERN.test(slug)) {
72903
73325
  throw new SlugRestoreError("invalid_slug", `"${slug}" is not a valid creative slug`);
72904
73326
  }
72905
- const targetDir = path23.join(opts.workspaceRoot, "src", "creatives", slug);
72906
- const local = path23.join(targetDir, `${slug}.canvas.json`);
73327
+ const targetDir = path24.join(opts.workspaceRoot, "src", "creatives", slug);
73328
+ const local = path24.join(targetDir, `${slug}.canvas.json`);
72907
73329
  if (!opts.forceRemote && existsSync6(local)) return { canvasPath: local };
72908
73330
  const client = opts.client();
72909
73331
  if (!client) {
@@ -73348,11 +73770,11 @@ function withDeliveredSizes(canvas, last) {
73348
73770
  }
73349
73771
 
73350
73772
  // src/commands/canvas/static-fix.ts
73351
- import { readFile as readFile18 } from "fs/promises";
73352
- import path24 from "path";
73773
+ import { readFile as readFile19 } from "fs/promises";
73774
+ import path25 from "path";
73353
73775
  async function previousAdImage(canvasRoot, runId, fetchRecord = fetchRunRecord, roles = {}) {
73354
73776
  try {
73355
- const manifest = JSON.parse(await readFile18(path24.join(canvasRoot, runId, "manifest.json"), "utf8"));
73777
+ const manifest = JSON.parse(await readFile19(path25.join(canvasRoot, runId, "manifest.json"), "utf8"));
73356
73778
  const byNode = Object.fromEntries(
73357
73779
  Object.entries(manifest.outputs_by_node ?? {}).map(([id, out]) => [id, { ...out, ...roles[id] ? { role: roles[id] } : {} }])
73358
73780
  );
@@ -73368,7 +73790,7 @@ async function previousAdImage(canvasRoot, runId, fetchRecord = fetchRunRecord,
73368
73790
  }
73369
73791
  async function previousDelivered(canvasRoot, runId, finalNode, labels, opts = {}) {
73370
73792
  try {
73371
- const manifest = JSON.parse(await readFile18(path24.join(canvasRoot, runId, "manifest.json"), "utf8"));
73793
+ const manifest = JSON.parse(await readFile19(path25.join(canvasRoot, runId, "manifest.json"), "utf8"));
73372
73794
  const images = manifest.outputs_by_node?.[finalNode]?.images ?? [];
73373
73795
  return images.flatMap((image, i) => {
73374
73796
  const local = typeof image.path === "string" ? image.path : null;
@@ -73414,7 +73836,7 @@ function correctedUrl(byNode) {
73414
73836
  }
73415
73837
  async function correctedPicture(canvasRoot, runId, fetchRecord = fetchRunRecord) {
73416
73838
  try {
73417
- const manifest = JSON.parse(await readFile18(path24.join(canvasRoot, runId, "manifest.json"), "utf8"));
73839
+ const manifest = JSON.parse(await readFile19(path25.join(canvasRoot, runId, "manifest.json"), "utf8"));
73418
73840
  if (manifest.outputs_by_node) return correctedUrl(manifest.outputs_by_node);
73419
73841
  } catch {
73420
73842
  }
@@ -73446,7 +73868,7 @@ async function variantImage(canvasRoot, runId, sha2562, fetchRecord = fetchRunRe
73446
73868
  const sha = sha2562.trim().toLowerCase();
73447
73869
  if (!SHA_PREFIX.test(sha)) return null;
73448
73870
  try {
73449
- const manifest = JSON.parse(await readFile18(path24.join(canvasRoot, runId, "manifest.json"), "utf8"));
73871
+ const manifest = JSON.parse(await readFile19(path25.join(canvasRoot, runId, "manifest.json"), "utf8"));
73450
73872
  for (const [id, out] of Object.entries(manifest.outputs_by_node ?? {})) {
73451
73873
  if (roles[id] !== STATIC_VARIANTS_ROLE) continue;
73452
73874
  const url = matchVariant(out.images ?? [], sha);
@@ -73460,7 +73882,7 @@ async function variantImage(canvasRoot, runId, sha2562, fetchRecord = fetchRunRe
73460
73882
  }
73461
73883
 
73462
73884
  // src/commands/canvas/static-run-review.ts
73463
- import { readFile as readFile22 } from "fs/promises";
73885
+ import { readFile as readFile23 } from "fs/promises";
73464
73886
 
73465
73887
  // src/commands/canvas/glance-check.ts
73466
73888
  import sharp2 from "sharp";
@@ -73468,16 +73890,16 @@ import { z as z65 } from "zod";
73468
73890
 
73469
73891
  // src/commands/canvas/viewer-check.ts
73470
73892
  import { createHash as createHash4 } from "crypto";
73471
- import { mkdir as mkdir8, readdir as readdir11, readFile as readFile21, stat as stat4, writeFile as writeFile13 } from "fs/promises";
73472
- import path27 from "path";
73893
+ import { mkdir as mkdir8, readdir as readdir12, readFile as readFile22, stat as stat4, writeFile as writeFile13 } from "fs/promises";
73894
+ import path28 from "path";
73473
73895
  import { defineCommand as defineCommand138 } from "citty";
73474
- import { parse as parseYaml2 } from "yaml";
73896
+ import { parse as parseYaml3 } from "yaml";
73475
73897
  import { z as z64 } from "zod";
73476
73898
 
73477
73899
  // src/commands/canvas/unbilled-spend.ts
73478
73900
  import { randomUUID as randomUUID2 } from "crypto";
73479
- import { mkdir as mkdir6, readdir as readdir10, readFile as readFile19, rename, unlink, writeFile as writeFile11 } from "fs/promises";
73480
- import path25 from "path";
73901
+ import { mkdir as mkdir6, readdir as readdir11, readFile as readFile20, rename, unlink, writeFile as writeFile11 } from "fs/promises";
73902
+ import path26 from "path";
73481
73903
  import { z as z62 } from "zod";
73482
73904
  var DIR = "unbilled-spend";
73483
73905
  var Held = z62.array(
@@ -73485,22 +73907,22 @@ var Held = z62.array(
73485
73907
  );
73486
73908
  async function holdUnbilledSpend(cacheDir, usage) {
73487
73909
  if (usage.length === 0) return;
73488
- const dir = path25.join(cacheDir, DIR);
73910
+ const dir = path26.join(cacheDir, DIR);
73489
73911
  await mkdir6(dir, { recursive: true });
73490
- await writeFile11(path25.join(dir, `${Date.now()}-${randomUUID2()}.json`), JSON.stringify(usage), "utf8");
73912
+ await writeFile11(path26.join(dir, `${Date.now()}-${randomUUID2()}.json`), JSON.stringify(usage), "utf8");
73491
73913
  }
73492
73914
  async function takeUnbilledSpend(cacheDir) {
73493
- const dir = path25.join(cacheDir, DIR);
73494
- const files = await readdir10(dir).catch(() => []);
73915
+ const dir = path26.join(cacheDir, DIR);
73916
+ const files = await readdir11(dir).catch(() => []);
73495
73917
  const byModel = /* @__PURE__ */ new Map();
73496
73918
  for (const file of files.filter((f) => f.endsWith(".json"))) {
73497
- const claimed = path25.join(dir, `${file}.${randomUUID2()}.taken`);
73919
+ const claimed = path26.join(dir, `${file}.${randomUUID2()}.taken`);
73498
73920
  try {
73499
- await rename(path25.join(dir, file), claimed);
73921
+ await rename(path26.join(dir, file), claimed);
73500
73922
  } catch {
73501
73923
  continue;
73502
73924
  }
73503
- const parsed = Held.safeParse(await readFile19(claimed, "utf8").then(JSON.parse, () => null).catch(() => null));
73925
+ const parsed = Held.safeParse(await readFile20(claimed, "utf8").then(JSON.parse, () => null).catch(() => null));
73504
73926
  await unlink(claimed).catch(() => {
73505
73927
  });
73506
73928
  for (const call of parsed.success ? parsed.data : []) {
@@ -73543,8 +73965,8 @@ function holdWhenSettled(cacheDir, spendOf) {
73543
73965
 
73544
73966
  // src/commands/canvas/casting-check.ts
73545
73967
  import { createHash as createHash3 } from "crypto";
73546
- import { mkdir as mkdir7, readFile as readFile20, writeFile as writeFile12 } from "fs/promises";
73547
- import path26 from "path";
73968
+ import { mkdir as mkdir7, readFile as readFile21, writeFile as writeFile12 } from "fs/promises";
73969
+ import path27 from "path";
73548
73970
  import { z as z63 } from "zod";
73549
73971
  var CastingAnswerSchema = z63.object({
73550
73972
  passes: z63.boolean(),
@@ -73576,15 +73998,15 @@ function parseOrNull(raw) {
73576
73998
  }
73577
73999
  async function castingCheck(input, opts) {
73578
74000
  if (input.people.length === 0) return { status: "skipped" };
73579
- const file = path26.join(opts.cacheDir, "casting-check", `${cacheKey(input, opts.model)}.json`);
73580
- const cachedRaw = await readFile20(file, "utf8").catch(() => null);
74001
+ const file = path27.join(opts.cacheDir, "casting-check", `${cacheKey(input, opts.model)}.json`);
74002
+ const cachedRaw = await readFile21(file, "utf8").catch(() => null);
73581
74003
  const cachedParse = cachedRaw ? CastingAnswerSchema.safeParse(parseOrNull(cachedRaw)) : null;
73582
74004
  if (cachedParse?.success) return { status: cachedParse.data.passes ? "passed" : "failed", answer: cachedParse.data, cached: true };
73583
74005
  const prompt = buildCastingPrompt(input);
73584
74006
  for (let attempt = 0; attempt < 2; attempt++) {
73585
74007
  const parsed = CastingAnswerSchema.safeParse(await opts.ask(prompt));
73586
74008
  if (!parsed.success) continue;
73587
- await mkdir7(path26.dirname(file), { recursive: true });
74009
+ await mkdir7(path27.dirname(file), { recursive: true });
73588
74010
  await writeFile12(file, `${JSON.stringify(parsed.data, null, 2)}
73589
74011
  `, "utf8");
73590
74012
  return { status: parsed.data.passes ? "passed" : "failed", answer: parsed.data, cached: false };
@@ -73723,14 +74145,14 @@ function viewerCheckCacheKey(input, model) {
73723
74145
  }
73724
74146
  async function cachedAnswer(file) {
73725
74147
  try {
73726
- const parsed = ViewerAnswerSchema.safeParse(JSON.parse(await readFile21(file, "utf8")));
74148
+ const parsed = ViewerAnswerSchema.safeParse(JSON.parse(await readFile22(file, "utf8")));
73727
74149
  return parsed.success ? parsed.data : null;
73728
74150
  } catch {
73729
74151
  return null;
73730
74152
  }
73731
74153
  }
73732
74154
  async function checkViewer(input, opts) {
73733
- const file = path27.join(opts.cacheDir, "viewer-check", `${viewerCheckCacheKey(input, opts.model)}.json`);
74155
+ const file = path28.join(opts.cacheDir, "viewer-check", `${viewerCheckCacheKey(input, opts.model)}.json`);
73734
74156
  let answer = await cachedAnswer(file);
73735
74157
  const cached = answer !== null;
73736
74158
  const prompt = buildViewerPrompt(input);
@@ -73740,7 +74162,7 @@ async function checkViewer(input, opts) {
73740
74162
  }
73741
74163
  if (answer === null) return { status: "unavailable", input };
73742
74164
  if (!cached) {
73743
- await mkdir8(path27.dirname(file), { recursive: true });
74165
+ await mkdir8(path28.dirname(file), { recursive: true });
73744
74166
  await writeFile13(file, `${JSON.stringify(answer, null, 2)}
73745
74167
  `, "utf8");
73746
74168
  }
@@ -73867,19 +74289,19 @@ var Frontmatter = z64.object({
73867
74289
  awareness: z64.enum(AWARENESS_VALUES).optional().catch(void 0),
73868
74290
  angle: z64.string().optional()
73869
74291
  });
73870
- function frontmatterOf(raw) {
74292
+ function frontmatterOf2(raw) {
73871
74293
  const block2 = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
73872
74294
  if (!block2) return {};
73873
74295
  try {
73874
- const parsed = Frontmatter.safeParse(parseYaml2(block2));
74296
+ const parsed = Frontmatter.safeParse(parseYaml3(block2));
73875
74297
  return parsed.success ? parsed.data : {};
73876
74298
  } catch {
73877
74299
  return {};
73878
74300
  }
73879
74301
  }
73880
74302
  async function readCreativeForViewerCheck(creativeDir) {
73881
- const spec = readAdSpec(JSON.parse(await readFile21(path27.join(creativeDir, "prompt.json"), "utf8")));
73882
- const definition = frontmatterOf(await readFile21(path27.join(creativeDir, "_definition.md"), "utf8").catch(() => ""));
74303
+ const spec = readAdSpec(JSON.parse(await readFile22(path28.join(creativeDir, "prompt.json"), "utf8")));
74304
+ const definition = frontmatterOf2(await readFile22(path28.join(creativeDir, "_definition.md"), "utf8").catch(() => ""));
73883
74305
  const offer = spec.message?.trim() || definition.angle?.trim();
73884
74306
  const persona = spec.viewer?.who.trim() || definition.persona?.trim();
73885
74307
  const brand = spec.brand?.name?.trim();
@@ -73908,26 +74330,26 @@ function castPeople(spec) {
73908
74330
  return people.length > 0 ? { people } : {};
73909
74331
  }
73910
74332
  async function readPersonas(workspaceRoot) {
73911
- const dir = path27.join(workspaceRoot, "src", "content", "personas");
74333
+ const dir = path28.join(workspaceRoot, "src", "content", "personas");
73912
74334
  let files;
73913
74335
  try {
73914
- files = (await readdir11(dir, { recursive: true })).filter((f) => f.endsWith(".md"));
74336
+ files = (await readdir12(dir, { recursive: true })).filter((f) => f.endsWith(".md"));
73915
74337
  } catch {
73916
74338
  return [];
73917
74339
  }
73918
74340
  return await Promise.all(
73919
74341
  files.map(async (file) => {
73920
- const raw = await readFile21(path27.join(dir, file), "utf8");
74342
+ const raw = await readFile22(path28.join(dir, file), "utf8");
73921
74343
  const block2 = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
73922
74344
  let segment;
73923
74345
  try {
73924
- segment = block2 ? parseYaml2(block2)?.segment : void 0;
74346
+ segment = block2 ? parseYaml3(block2)?.segment : void 0;
73925
74347
  } catch {
73926
74348
  segment = void 0;
73927
74349
  }
73928
74350
  const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim();
73929
74351
  return {
73930
- slug: path27.basename(file, ".md"),
74352
+ slug: path28.basename(file, ".md"),
73931
74353
  ...title ? { title } : {},
73932
74354
  ...typeof segment === "string" && segment.trim() ? { segment: segment.trim() } : {}
73933
74355
  };
@@ -73966,8 +74388,8 @@ function liveViewerLookups(workspaceRoot) {
73966
74388
  return {
73967
74389
  adSetViewer: metaAdSetViewer,
73968
74390
  personas: () => readPersonas(workspaceRoot),
73969
- companyMarket: async () => marketFromCompanyDoc(await readFile21(path27.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null)),
73970
- company: async () => (await readFile21(path27.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => "")).slice(0, 800)
74391
+ companyMarket: async () => marketFromCompanyDoc(await readFile22(path28.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null)),
74392
+ company: async () => (await readFile22(path28.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => "")).slice(0, 800)
73971
74393
  };
73972
74394
  }
73973
74395
  function gatewayViewerModel(model, onSpend) {
@@ -74038,11 +74460,11 @@ var ARGS5 = {
74038
74460
  };
74039
74461
  registerSchema({ command: "canvas.viewer-check", description: DESCRIPTION2, args: ARGS5 });
74040
74462
  async function creativeDirOf(arg, workspaceRoot) {
74041
- const direct = path27.resolve(arg);
74463
+ const direct = path28.resolve(arg);
74042
74464
  try {
74043
- return (await stat4(direct)).isDirectory() ? direct : path27.dirname(direct);
74465
+ return (await stat4(direct)).isDirectory() ? direct : path28.dirname(direct);
74044
74466
  } catch {
74045
- return path27.join(workspaceRoot, "src", "creatives", arg);
74467
+ return path28.join(workspaceRoot, "src", "creatives", arg);
74046
74468
  }
74047
74469
  }
74048
74470
  var viewerCheckCommand = defineCommand138({
@@ -74052,7 +74474,7 @@ var viewerCheckCommand = defineCommand138({
74052
74474
  const cwd = process.cwd();
74053
74475
  const workspaceRoot = findWorkspaceRoot(cwd) ?? cwd;
74054
74476
  const creativeDir = await creativeDirOf(String(args8.slug), workspaceRoot);
74055
- const slug = path27.basename(creativeDir);
74477
+ const slug = path28.basename(creativeDir);
74056
74478
  let creative;
74057
74479
  try {
74058
74480
  creative = await readCreativeForViewerCheck(creativeDir);
@@ -74069,7 +74491,7 @@ var viewerCheckCommand = defineCommand138({
74069
74491
  }
74070
74492
  const resolved = await resolveViewer(creative.sources, liveViewerLookups(workspaceRoot));
74071
74493
  const input = { ...creative.ad, viewer: resolved.viewer };
74072
- const cacheDir = path27.join(workspaceRoot, "canvas", ".cache");
74494
+ const cacheDir = path28.join(workspaceRoot, "canvas", ".cache");
74073
74495
  const spent = [];
74074
74496
  const outcome2 = await checkViewer(input, {
74075
74497
  cacheDir,
@@ -74300,7 +74722,7 @@ function runCostHint(stats, kind) {
74300
74722
  async function textOf(ref2) {
74301
74723
  const r = ref2;
74302
74724
  try {
74303
- if (typeof r?.path === "string") return await readFile22(r.path, "utf8");
74725
+ if (typeof r?.path === "string") return await readFile23(r.path, "utf8");
74304
74726
  if (typeof r?.url === "string") {
74305
74727
  const res = await fetch(r.url);
74306
74728
  return res.ok ? await res.text() : null;
@@ -74367,7 +74789,7 @@ async function reviewStaticRun(opts) {
74367
74789
  let native;
74368
74790
  let palette = [];
74369
74791
  try {
74370
- const spec = readAdSpec(JSON.parse(await readFile22(targets.promptPath, "utf8")));
74792
+ const spec = readAdSpec(JSON.parse(await readFile23(targets.promptPath, "utf8")));
74371
74793
  copy = spec.copy.map((l) => l.text);
74372
74794
  lines = spec.copy.map((l) => ({ text: l.text, lettering: l.lettering, ...l.role ? { role: l.role } : {} }));
74373
74795
  if (spec.native) {
@@ -74388,7 +74810,7 @@ async function reviewStaticRun(opts) {
74388
74810
  ...allowedSceneWords(spec),
74389
74811
  ...spec.native ? nativeChrome(spec.native, spec.viewer?.market?.language) : []
74390
74812
  ];
74391
- expectLogo = spec.logo && !spec.native;
74813
+ expectLogo = spec.logo && !spec.native && staticRunShape(opts.parsed).logo;
74392
74814
  concept = conceptSummary(spec);
74393
74815
  uncast = spec.native ? [] : uncastPeople(spec);
74394
74816
  } catch {
@@ -74543,7 +74965,7 @@ function variantHints(judge, prefilter) {
74543
74965
  }
74544
74966
 
74545
74967
  // src/commands/canvas/static-spec-repair.ts
74546
- import { readFile as readFile23, writeFile as writeFile14 } from "fs/promises";
74968
+ import { readFile as readFile24, writeFile as writeFile14 } from "fs/promises";
74547
74969
  import sharp3 from "sharp";
74548
74970
  import { z as z66 } from "zod";
74549
74971
  var Answer = z66.object({
@@ -74602,7 +75024,7 @@ function buildSpecRepairPrompt(spec, findings, schema) {
74602
75024
  var REPAIR_TIMEOUT_MS = 15e4;
74603
75025
  var SHOWN_WIDTH = 768;
74604
75026
  async function repairSpec(opts) {
74605
- const text3 = await readFile23(opts.promptPath, "utf8").catch(() => null);
75027
+ const text3 = await readFile24(opts.promptPath, "utf8").catch(() => null);
74606
75028
  if (text3 === null) return null;
74607
75029
  let before;
74608
75030
  try {
@@ -74741,7 +75163,7 @@ function failPreflight3(code, message, fix) {
74741
75163
  process.exit(2);
74742
75164
  }
74743
75165
  async function canvasFileFor(opts) {
74744
- if (opts.file) return { canvasPath: path28.resolve(opts.file) };
75166
+ if (opts.file) return { canvasPath: path29.resolve(opts.file) };
74745
75167
  if (!opts.slug) failPreflight3("missing_canvas", "Pass the canvas file, or --slug <slug> to run a creative by name.");
74746
75168
  const cwd = process.cwd();
74747
75169
  try {
@@ -74763,14 +75185,14 @@ async function executeCanvasRun(opts) {
74763
75185
  const creativeSlug = creativeSlugFromCanvasPath(filePath) ?? void 0;
74764
75186
  if (creativeSlug) await clearLegacyDirtyMarker(canvasRoot, creativeSlug);
74765
75187
  await wireProofBadges(
74766
- path28.join(path28.dirname(filePath), "prompt.json"),
75188
+ path29.join(path29.dirname(filePath), "prompt.json"),
74767
75189
  fetchBrandLogo,
74768
75190
  (line) => process.stderr.write(`${line}
74769
75191
  `)
74770
75192
  );
74771
75193
  if ((opts.autoFixRound ?? 0) === 0) {
74772
75194
  await wireNamedPeople(
74773
- path28.join(path28.dirname(filePath), "prompt.json"),
75195
+ path29.join(path29.dirname(filePath), "prompt.json"),
74774
75196
  avatarOfNamedPerson,
74775
75197
  (line) => process.stderr.write(`${line}
74776
75198
  `)
@@ -74962,7 +75384,7 @@ ${describeRewrites(prepared.rewrites)}
74962
75384
  process.stdout.write(`[extend] placing ${opts.extendSizes.join(", ")} clear of what covers or cuts it, its margins painted
74963
75385
  `);
74964
75386
  }
74965
- const cacheDir = opts.cacheDir ?? path28.join(canvasRoot, ".cache");
75387
+ const cacheDir = opts.cacheDir ?? path29.join(canvasRoot, ".cache");
74966
75388
  const engine = createEngineFromEnv({
74967
75389
  cacheDir,
74968
75390
  outputsDir: opts.outputsDir ?? canvasRoot,
@@ -74985,7 +75407,7 @@ ${describeRewrites(prepared.rewrites)}
74985
75407
  const canvasSha = sha256Hex(Buffer.from(canvasText));
74986
75408
  const sourceSha = prepared.sourceSha;
74987
75409
  const client = opts.record === false ? null : buildBackendClient();
74988
- const outputsDir = opts.outputsDir ? path28.resolve(opts.outputsDir) : canvasRoot;
75410
+ const outputsDir = opts.outputsDir ? path29.resolve(opts.outputsDir) : canvasRoot;
74989
75411
  const { runId, resumed, source, sameSource, concurrentRunId, rendering } = await resolveRunId({
74990
75412
  explicitRunId: opts.runId,
74991
75413
  adoptedRunId: adoptRunId,
@@ -75015,7 +75437,7 @@ ${describeRewrites(prepared.rewrites)}
75015
75437
  }
75016
75438
  const viewerUsage = [];
75017
75439
  const viewerCheck = creativeSlug && isStaticAdCanvas(parsed) ? await viewerCheckBeforeRender({
75018
- creativeDir: path28.dirname(filePath),
75440
+ creativeDir: path29.dirname(filePath),
75019
75441
  cacheDir,
75020
75442
  slug: creativeSlug,
75021
75443
  skip: viewerCheckSkip({
@@ -75026,7 +75448,7 @@ ${describeRewrites(prepared.rewrites)}
75026
75448
  resumed: resumed && sameSource === true,
75027
75449
  asked: opts.skipViewerCheck === true
75028
75450
  }),
75029
- lookups: liveViewerLookups(path28.dirname(canvasRoot)),
75451
+ lookups: liveViewerLookups(path29.dirname(canvasRoot)),
75030
75452
  model: VIEWER_CHECK_MODEL,
75031
75453
  ask: gatewayViewerModel(VIEWER_CHECK_MODEL, (usage) => viewerUsage.push(usage))
75032
75454
  }) : null;
@@ -75075,7 +75497,7 @@ ${describeRewrites(prepared.rewrites)}
75075
75497
  if (snapshot && creativeSlug) await recordSnapshot(canvasRoot, creativeSlug, snapshot);
75076
75498
  const recordMeta = {
75077
75499
  creativeSlug,
75078
- canvasPath: path28.relative(path28.dirname(canvasRoot), filePath) || void 0,
75500
+ canvasPath: path29.relative(path29.dirname(canvasRoot), filePath) || void 0,
75079
75501
  canvasSha,
75080
75502
  // The fingerprint the dashboard compares against the current source to flag
75081
75503
  // "edited since last render": the same `computeSourceSha` the edit-sync posts,
@@ -75163,7 +75585,7 @@ ${describeRewrites(prepared.rewrites)}
75163
75585
  brief: opts.brief?.trim() || void 0,
75164
75586
  onSpend: (usage) => reviewUsage.push(usage)
75165
75587
  }) : null;
75166
- const brandKit = reviewable ? null : await readStaticBrandKit(await findBrandRoot(path28.dirname(filePath)) ?? process.cwd());
75588
+ const brandKit = reviewable ? null : await readStaticBrandKit(await findBrandRoot(path29.dirname(filePath)) ?? process.cwd());
75167
75589
  const runShape = {
75168
75590
  fix: fixInstruction !== void 0,
75169
75591
  useVariant: opts.useVariant !== void 0,
@@ -75216,7 +75638,7 @@ ${describeRewrites(prepared.rewrites)}
75216
75638
  ] : [];
75217
75639
  const findings = staticReview ? staticReview.review : review;
75218
75640
  const ready = staticReview ? staticReview.verdict === "ready" || staticReview.verdict === "ready_with_notes" : review !== null && !review.some((f) => f.severity === "blocking");
75219
- const definitionStatus = ready && creativeSlug ? await advanceDefinitionStatus(path28.join(path28.dirname(filePath), "_definition.md"), "rendered") : null;
75641
+ const definitionStatus = ready && creativeSlug ? await advanceDefinitionStatus(path29.join(path29.dirname(filePath), "_definition.md"), "rendered") : null;
75220
75642
  if (poster) {
75221
75643
  await flushBilled(
75222
75644
  poster,
@@ -75448,14 +75870,14 @@ var rerunCommand = defineCommand140({
75448
75870
  });
75449
75871
 
75450
75872
  // src/commands/canvas/scaffold-static-ad.ts
75451
- import { mkdir as mkdir11, readFile as readFile27, writeFile as writeFile17 } from "fs/promises";
75452
- import path34 from "path";
75873
+ import { mkdir as mkdir11, readFile as readFile28, writeFile as writeFile17 } from "fs/promises";
75874
+ import path35 from "path";
75453
75875
  import { defineCommand as defineCommand143 } from "citty";
75454
75876
 
75455
75877
  // src/commands/canvas/creative-limit.ts
75456
75878
  import { existsSync as existsSync7 } from "fs";
75457
- import { mkdir as mkdir9, readdir as readdir12, writeFile as writeFile15 } from "fs/promises";
75458
- import path29 from "path";
75879
+ import { mkdir as mkdir9, readdir as readdir13, writeFile as writeFile15 } from "fs/promises";
75880
+ import path30 from "path";
75459
75881
  var NEW_CREATIVES_PER_CHAT = 10;
75460
75882
  function claimNewCreative(made, slug) {
75461
75883
  if (made.includes(slug)) return { allowed: true, count: made.length };
@@ -75463,10 +75885,10 @@ function claimNewCreative(made, slug) {
75463
75885
  return { allowed: true, count: made.length + 1 };
75464
75886
  }
75465
75887
  function chatDir(canvasRoot, chatId) {
75466
- return path29.join(canvasRoot, ".creative-limit", (chatId ?? "no-chat").replace(/[^A-Za-z0-9_-]/g, "_"));
75888
+ return path30.join(canvasRoot, ".creative-limit", (chatId ?? "no-chat").replace(/[^A-Za-z0-9_-]/g, "_"));
75467
75889
  }
75468
75890
  async function madeInChat(dir) {
75469
- return readdir12(dir).catch(() => []);
75891
+ return readdir13(dir).catch(() => []);
75470
75892
  }
75471
75893
  async function claimNewCreativeInWorkspace(canvasRoot, slug, chatId, definitionPath, exists3 = existsSync7) {
75472
75894
  const dir = chatDir(canvasRoot, chatId);
@@ -75476,7 +75898,7 @@ async function claimNewCreativeInWorkspace(canvasRoot, slug, chatId, definitionP
75476
75898
  if (claim.allowed && !made.includes(slug)) {
75477
75899
  try {
75478
75900
  await mkdir9(dir, { recursive: true });
75479
- await writeFile15(path29.join(dir, slug), "", { flag: "wx" });
75901
+ await writeFile15(path30.join(dir, slug), "", { flag: "wx" });
75480
75902
  } catch {
75481
75903
  }
75482
75904
  }
@@ -75497,7 +75919,7 @@ async function enforceNewCreativeLimit(slug, canvasPath) {
75497
75919
  canvasOutputsRoot(canvasPath),
75498
75920
  slug,
75499
75921
  optionalChatId() ?? null,
75500
- path29.join(path29.dirname(path29.resolve(canvasPath)), "_definition.md")
75922
+ path30.join(path30.dirname(path30.resolve(canvasPath)), "_definition.md")
75501
75923
  );
75502
75924
  if (claim.allowed) return;
75503
75925
  process.stderr.write(`${JSON.stringify(creativeLimitRefusal(slug), null, 2)}
@@ -75506,22 +75928,22 @@ async function enforceNewCreativeLimit(slug, canvasPath) {
75506
75928
  }
75507
75929
 
75508
75930
  // src/commands/canvas/scaffold-common.ts
75509
- import { access, readFile as readFile24 } from "fs/promises";
75931
+ import { access, readFile as readFile25 } from "fs/promises";
75510
75932
 
75511
75933
  // src/commands/canvas/composition-path.ts
75512
75934
  import { existsSync as existsSync8 } from "fs";
75513
- import path30 from "path";
75935
+ import path31 from "path";
75514
75936
  function resolveShippedCanvasDir(name, startDir, exists3 = existsSync8, maxDepth = 8) {
75515
- const rel = path30.join("canvas", name);
75937
+ const rel = path31.join("canvas", name);
75516
75938
  let dir = startDir;
75517
75939
  for (let i = 0; i < maxDepth; i++) {
75518
- const candidate = path30.join(dir, rel);
75519
- if (exists3(path30.join(candidate, "meta.json"))) return candidate;
75520
- const parent = path30.dirname(dir);
75940
+ const candidate = path31.join(dir, rel);
75941
+ if (exists3(path31.join(candidate, "meta.json"))) return candidate;
75942
+ const parent = path31.dirname(dir);
75521
75943
  if (parent === dir) break;
75522
75944
  dir = parent;
75523
75945
  }
75524
- return path30.resolve(startDir, "../../../", rel);
75946
+ return path31.resolve(startDir, "../../../", rel);
75525
75947
  }
75526
75948
 
75527
75949
  // src/commands/canvas/scaffold-common.ts
@@ -75538,7 +75960,7 @@ function resolveModel(kind, requested, fallback) {
75538
75960
  }
75539
75961
  async function loadAssetText(ref2, label) {
75540
75962
  const r = ref2;
75541
- if (typeof r?.path === "string") return readFile24(r.path, "utf8");
75963
+ if (typeof r?.path === "string") return readFile25(r.path, "utf8");
75542
75964
  if (typeof r?.url === "string") {
75543
75965
  const res = await fetch(r.url);
75544
75966
  if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
@@ -75632,7 +76054,7 @@ function parseDefinitionSourceFields(raw) {
75632
76054
  }
75633
76055
  return fields;
75634
76056
  }
75635
- function oneOf(values, value2) {
76057
+ function oneOf2(values, value2) {
75636
76058
  return values.find((allowed) => allowed === value2);
75637
76059
  }
75638
76060
  function parseDefinitionStrategyFields(raw) {
@@ -75646,10 +76068,10 @@ function parseDefinitionStrategyFields(raw) {
75646
76068
  };
75647
76069
  const ideaSlug = text3("idea");
75648
76070
  const message = text3("message");
75649
- const evidence = oneOf(EVIDENCE_VALUES, text3("evidence"));
76071
+ const evidence = oneOf2(EVIDENCE_VALUES, text3("evidence"));
75650
76072
  const angle = text3("angle");
75651
76073
  const persona = text3("persona");
75652
- const awareness = oneOf(AWARENESS_VALUES, text3("awareness"));
76074
+ const awareness = oneOf2(AWARENESS_VALUES, text3("awareness"));
75653
76075
  const hypothesis = text3("hypothesis");
75654
76076
  return {
75655
76077
  ...ideaSlug ? { ideaSlug } : {},
@@ -75686,16 +76108,16 @@ ${doc.toString()}---${raw.slice(match[0].length)}`;
75686
76108
  }
75687
76109
 
75688
76110
  // src/commands/canvas/scaffold-static-ad-paths.ts
75689
- import path31 from "path";
76111
+ import path32 from "path";
75690
76112
  function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd(), slug) {
75691
76113
  const file = rawFile.trim();
75692
76114
  const imageIsUrl = /^https?:\/\//i.test(file);
75693
- const imageSource = imageIsUrl ? file : path31.resolve(cwd, file);
75694
- const outPath = out ? path31.resolve(cwd, out) : slug ? path31.join(cwd, "src", "creatives", slug, `${slug}.canvas.json`) : imageIsUrl ? path31.join(cwd, "static-ad.canvas.json") : path31.join(path31.dirname(imageSource), "static-ad.canvas.json");
75695
- const blueprintPath2 = path31.join(path31.dirname(outPath), "prompt.json");
75696
- const creativeDir = slug ? path31.dirname(outPath) : null;
75697
- const definitionPath = creativeDir ? path31.join(creativeDir, "_definition.md") : null;
75698
- const referencesDir = creativeDir ? path31.join(creativeDir, "references") : null;
76115
+ const imageSource = imageIsUrl ? file : path32.resolve(cwd, file);
76116
+ const outPath = out ? path32.resolve(cwd, out) : slug ? path32.join(cwd, "src", "creatives", slug, `${slug}.canvas.json`) : imageIsUrl ? path32.join(cwd, "static-ad.canvas.json") : path32.join(path32.dirname(imageSource), "static-ad.canvas.json");
76117
+ const blueprintPath2 = path32.join(path32.dirname(outPath), "prompt.json");
76118
+ const creativeDir = slug ? path32.dirname(outPath) : null;
76119
+ const definitionPath = creativeDir ? path32.join(creativeDir, "_definition.md") : null;
76120
+ const referencesDir = creativeDir ? path32.join(creativeDir, "references") : null;
75699
76121
  return { imageIsUrl, imageSource, outPath, blueprintPath: blueprintPath2, creativeDir, definitionPath, referencesDir };
75700
76122
  }
75701
76123
  var SCAFFOLD_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
@@ -75705,8 +76127,8 @@ function isValidScaffoldSlug(slug) {
75705
76127
  }
75706
76128
 
75707
76129
  // src/commands/canvas/sync-definition.ts
75708
- import { readdir as readdir13, readFile as readFile25, stat as stat5 } from "fs/promises";
75709
- import path32 from "path";
76130
+ import { readdir as readdir14, readFile as readFile26, stat as stat5 } from "fs/promises";
76131
+ import path33 from "path";
75710
76132
  import { defineCommand as defineCommand141 } from "citty";
75711
76133
 
75712
76134
  // src/commands/canvas/definition-graph.ts
@@ -75763,14 +76185,14 @@ function buildDefinitionSyncPayload(input, graph, chatId) {
75763
76185
  }
75764
76186
  async function readDefinitionSourceFields(canvasPath) {
75765
76187
  try {
75766
- return parseDefinitionSourceFields(await readFile25(path32.join(path32.dirname(canvasPath), "_definition.md"), "utf8"));
76188
+ return parseDefinitionSourceFields(await readFile26(path33.join(path33.dirname(canvasPath), "_definition.md"), "utf8"));
75767
76189
  } catch {
75768
76190
  return {};
75769
76191
  }
75770
76192
  }
75771
76193
  async function readDefinitionStrategyFields(canvasPath) {
75772
76194
  try {
75773
- return parseDefinitionStrategyFields(await readFile25(path32.join(path32.dirname(canvasPath), "_definition.md"), "utf8"));
76195
+ return parseDefinitionStrategyFields(await readFile26(path33.join(path33.dirname(canvasPath), "_definition.md"), "utf8"));
75774
76196
  } catch {
75775
76197
  return {};
75776
76198
  }
@@ -75793,26 +76215,26 @@ async function syncCreativeDefinitionBestEffort(input) {
75793
76215
  }
75794
76216
  }
75795
76217
  async function resolveCanvasPath(inputPath) {
75796
- const resolved = path32.resolve(inputPath);
76218
+ const resolved = path33.resolve(inputPath);
75797
76219
  let dir = resolved;
75798
76220
  try {
75799
76221
  if ((await stat5(resolved)).isFile()) {
75800
76222
  if (resolved.endsWith(".canvas.json")) return resolved;
75801
- dir = path32.dirname(resolved);
76223
+ dir = path33.dirname(resolved);
75802
76224
  }
75803
76225
  } catch {
75804
- dir = resolved.endsWith(".canvas.json") ? path32.dirname(resolved) : resolved;
76226
+ dir = resolved.endsWith(".canvas.json") ? path33.dirname(resolved) : resolved;
75805
76227
  }
75806
76228
  let entries;
75807
76229
  try {
75808
- entries = await readdir13(dir);
76230
+ entries = await readdir14(dir);
75809
76231
  } catch {
75810
76232
  return null;
75811
76233
  }
75812
76234
  const canvases = entries.filter((name) => name.endsWith(".canvas.json"));
75813
76235
  const slug = creativeSlugFromCanvasPath(`${dir}/x/`);
75814
76236
  const chosen = (slug ? canvases.find((name) => name === `${slug}.canvas.json`) : void 0) ?? canvases[0];
75815
- return chosen ? path32.join(dir, chosen) : null;
76237
+ return chosen ? path33.join(dir, chosen) : null;
75816
76238
  }
75817
76239
  var syncDefinitionCommand = defineCommand141({
75818
76240
  meta: {
@@ -76082,8 +76504,8 @@ function mergeReferenceIntoBlueprint(blueprint, reference) {
76082
76504
  }
76083
76505
 
76084
76506
  // src/commands/winning-ads/fit.ts
76085
- import { mkdir as mkdir10, readdir as readdir14, readFile as readFile26, writeFile as writeFile16 } from "fs/promises";
76086
- import path33 from "path";
76507
+ import { mkdir as mkdir10, readdir as readdir15, readFile as readFile27, writeFile as writeFile16 } from "fs/promises";
76508
+ import path34 from "path";
76087
76509
  import { defineCommand as defineCommand142 } from "citty";
76088
76510
  import sharp4 from "sharp";
76089
76511
  import { z as z67 } from "zod";
@@ -76168,23 +76590,23 @@ var MAX_DOC_CHARS = 1500;
76168
76590
  var MAX_ENTRY_CHARS = 400;
76169
76591
  var KNOWLEDGE_COLLECTIONS = ["offers", "products", "personas"];
76170
76592
  function readText(file) {
76171
- return readFile26(file, "utf8").catch(() => null);
76593
+ return readFile27(file, "utf8").catch(() => null);
76172
76594
  }
76173
76595
  async function readKnowledge(root) {
76174
76596
  const parts = [];
76175
- const company = await readText(path33.join(root, "COMPANY.md"));
76597
+ const company = await readText(path34.join(root, "COMPANY.md"));
76176
76598
  if (company) parts.push(`COMPANY.md:
76177
76599
  ${company.slice(0, MAX_DOC_CHARS)}`);
76178
- const brand = await readText(path33.join(root, "src", "brand", "BRAND.md"));
76600
+ const brand = await readText(path34.join(root, "src", "brand", "BRAND.md"));
76179
76601
  if (brand) parts.push(`BRAND.md (start):
76180
76602
  ${brand.slice(0, MAX_DOC_CHARS)}`);
76181
76603
  for (const collection of KNOWLEDGE_COLLECTIONS) {
76182
- const dir = path33.join(root, "src", "content", collection);
76183
- const files = (await readdir14(dir, { recursive: true }).catch(() => [])).filter(
76604
+ const dir = path34.join(root, "src", "content", collection);
76605
+ const files = (await readdir15(dir, { recursive: true }).catch(() => [])).filter(
76184
76606
  (f) => f.endsWith(".md")
76185
76607
  );
76186
76608
  for (const file of files.sort().slice(0, 12)) {
76187
- const text3 = await readText(path33.join(dir, file));
76609
+ const text3 = await readText(path34.join(dir, file));
76188
76610
  if (text3) parts.push(`${collection}/${file}:
76189
76611
  ${text3.slice(0, MAX_ENTRY_CHARS)}`);
76190
76612
  }
@@ -76216,7 +76638,7 @@ async function readReviews() {
76216
76638
  }
76217
76639
  }
76218
76640
  function fitCachePath(root, platform, adId) {
76219
- return path33.join(root, "canvas", ".cache", "fit", `${platform}-${adId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
76641
+ return path34.join(root, "canvas", ".cache", "fit", `${platform}-${adId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
76220
76642
  }
76221
76643
  async function readCachedFit(root, platform, adId) {
76222
76644
  const raw = await readText(fitCachePath(root, platform, adId));
@@ -76271,7 +76693,7 @@ async function fitOnce(root, platform, adId, ask) {
76271
76693
  const fit = await ask();
76272
76694
  if (!fit) return null;
76273
76695
  const file = fitCachePath(root, platform, adId);
76274
- await mkdir10(path33.dirname(file), { recursive: true });
76696
+ await mkdir10(path34.dirname(file), { recursive: true });
76275
76697
  await writeFile16(file, `${JSON.stringify(fit, null, 2)}
76276
76698
  `, "utf8");
76277
76699
  return fit;
@@ -76310,7 +76732,7 @@ async function fitReference(root, platform, adId) {
76310
76732
  const prompt = buildFitPrompt({ library: libraryRead(ad), knowledge: knowledge2 });
76311
76733
  const spent = [];
76312
76734
  const answer = await askFit(jpeg, prompt, (u) => spent.push(u));
76313
- await holdUnbilledSpend(path33.join(root, "canvas", ".cache"), spent);
76735
+ await holdUnbilledSpend(path34.join(root, "canvas", ".cache"), spent);
76314
76736
  return answer;
76315
76737
  });
76316
76738
  if (fit) return { ok: true, fit };
@@ -76363,7 +76785,7 @@ var fitCommand = defineCommand142({
76363
76785
 
76364
76786
  // src/commands/canvas/scaffold-static-ad.ts
76365
76787
  function toCanvasRelative2(canvasDir, file) {
76366
- const rel = path34.relative(canvasDir, file).split(path34.sep).join("/");
76788
+ const rel = path35.relative(canvasDir, file).split(path35.sep).join("/");
76367
76789
  return rel.startsWith(".") ? rel : `./${rel}`;
76368
76790
  }
76369
76791
  async function uploadSourceAsReference(source, isUrl, client) {
@@ -76375,7 +76797,7 @@ async function uploadSourceAsReference(source, isUrl, client) {
76375
76797
  throw new Error(`failed to download source image (${e instanceof Error ? e.message : String(e)})`);
76376
76798
  }
76377
76799
  } else {
76378
- bytes = await readFile27(source);
76800
+ bytes = await readFile28(source);
76379
76801
  }
76380
76802
  const safe = await toModelSafeImage(bytes);
76381
76803
  const sha2562 = sha256Hex(safe.bytes);
@@ -76655,25 +77077,25 @@ async function lookupReference(args8) {
76655
77077
  }
76656
77078
  async function scaffoldFromSpec(args8, slug) {
76657
77079
  if (!slug) return fail4("missing_slug", "--spec needs --slug: an ad started from a spec is a creative.");
76658
- const specPath = path34.resolve(process.cwd(), String(args8.spec));
77080
+ const specPath = path35.resolve(process.cwd(), String(args8.spec));
76659
77081
  let started;
76660
77082
  try {
76661
- started = readAdSpec(JSON.parse(await readFile27(specPath, "utf8")));
77083
+ started = readAdSpec(JSON.parse(await readFile28(specPath, "utf8")));
76662
77084
  } catch (e) {
76663
77085
  return fail4("invalid_spec", `${specPath} is not an ad spec: ${e instanceof Error ? e.message : String(e)}`);
76664
77086
  }
76665
- const outPath = path34.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`);
76666
- const dir = path34.dirname(outPath);
76667
- const blueprintPath2 = path34.join(dir, "prompt.json");
76668
- const definitionPath = path34.join(dir, "_definition.md");
77087
+ const outPath = path35.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`);
77088
+ const dir = path35.dirname(outPath);
77089
+ const blueprintPath2 = path35.join(dir, "prompt.json");
77090
+ const definitionPath = path35.join(dir, "_definition.md");
76669
77091
  await enforceNewCreativeLimit(slug, outPath);
76670
- const existingDefinition = await fileExists(definitionPath) ? await readFile27(definitionPath, "utf8") : null;
77092
+ const existingDefinition = await fileExists(definitionPath) ? await readFile28(definitionPath, "utf8") : null;
76671
77093
  const who = await scaffoldViewer(args8, existingDefinition, liveViewerLookups(process.cwd()).adSetViewer);
76672
77094
  if (!who.ok) return fail4("invalid_viewer", who.message);
76673
77095
  await mkdir11(dir, { recursive: true });
76674
77096
  const workspaceRoot = await findBrandRoot(dir) ?? process.cwd();
76675
77097
  const kit = await readStaticBrandKit(workspaceRoot);
76676
- const market = who.viewer?.market ?? marketFromCompanyDoc(await readFile27(path34.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null));
77098
+ const market = who.viewer?.market ?? marketFromCompanyDoc(await readFile28(path35.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null));
76677
77099
  const brand = brandBlock(kit);
76678
77100
  const platform = resolvePlatform(args8.platform ? String(args8.platform) : void 0);
76679
77101
  const placementsResult = resolvePlacements(args8.placements ? String(args8.placements) : void 0, platform);
@@ -76820,14 +77242,14 @@ var scaffoldStaticAdCommand = defineCommand143({
76820
77242
  }
76821
77243
  const { imageIsUrl, imageSource, outPath, blueprintPath: blueprintPath2, definitionPath, referencesDir } = resolveScaffoldStaticAdPaths(sourceFile, args8.out ? String(args8.out) : void 0, process.cwd(), slug);
76822
77244
  if (slug) await enforceNewCreativeLimit(slug, outPath);
76823
- const existingDefinition = definitionPath && await fileExists(definitionPath) ? await readFile27(definitionPath, "utf8") : null;
77245
+ const existingDefinition = definitionPath && await fileExists(definitionPath) ? await readFile28(definitionPath, "utf8") : null;
76824
77246
  const who = await scaffoldViewer(args8, existingDefinition, liveViewerLookups(process.cwd()).adSetViewer);
76825
77247
  if (!who.ok) return fail4("invalid_viewer", who.message);
76826
77248
  const fitRoot = findWorkspaceRoot(process.cwd()) ?? process.cwd();
76827
77249
  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;
76828
77250
  const fitProblems = fit ? fitHints(fit, reference?.ad_id ?? "").filter((h) => h.startsWith("Pick another reference")) : [];
76829
77251
  if (fitProblems.length > 0) return fail4("concept_does_not_fit", fitProblems.join(" "));
76830
- await mkdir11(path34.dirname(outPath), { recursive: true });
77252
+ await mkdir11(path35.dirname(outPath), { recursive: true });
76831
77253
  let models;
76832
77254
  try {
76833
77255
  models = resolveModels(args8);
@@ -76861,10 +77283,10 @@ var scaffoldStaticAdCommand = defineCommand143({
76861
77283
  const persona = existingDefinition?.match(/^persona:\s*"?([^"\n]+)"?\s*$/m)?.[1];
76862
77284
  const elements = castAvatarsOnStaticElements(vision.elements, cast.avatars, persona);
76863
77285
  const uncast = staticAvatarsNotCast(elements, cast.handles);
76864
- const canvasDir = path34.dirname(outPath);
77286
+ const canvasDir = path35.dirname(outPath);
76865
77287
  const workspaceRoot = await findBrandRoot(canvasDir) ?? process.cwd();
76866
77288
  const kit = await readStaticBrandKit(workspaceRoot);
76867
- const market = who.viewer?.market ?? marketFromCompanyDoc(await readFile27(path34.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null));
77289
+ const market = who.viewer?.market ?? marketFromCompanyDoc(await readFile28(path35.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null));
76868
77290
  const brand = brandBlock(kit);
76869
77291
  let canvasImagePath = imageSource;
76870
77292
  let canvasImageIsUrl = imageIsUrl;
@@ -76928,7 +77350,7 @@ var scaffoldStaticAdCommand = defineCommand143({
76928
77350
  } catch (e) {
76929
77351
  return fail4("scaffold", e instanceof Error ? e.message : String(e));
76930
77352
  }
76931
- const validation = await validateScaffoldedStaticAd(canvas, path34.dirname(outPath));
77353
+ const validation = await validateScaffoldedStaticAd(canvas, path35.dirname(outPath));
76932
77354
  if (validation.blocking.length > 0) {
76933
77355
  process.stderr.write(
76934
77356
  `${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.blocking } }, null, 2)}
@@ -77025,7 +77447,7 @@ var scaffoldStaticAdCommand = defineCommand143({
77025
77447
  run_estimated_credits: validation.estimatedCredits
77026
77448
  },
77027
77449
  checklist: {
77028
- edit_prompt: `Edit ${path34.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." : ""}`,
77450
+ edit_prompt: `Edit ${path35.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." : ""}`,
77029
77451
  mechanism: report.winning_mechanisms,
77030
77452
  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.",
77031
77453
  assets_to_supply: report.elements,
@@ -77087,8 +77509,8 @@ function clientLogoMissNote(decision, domain) {
77087
77509
  }
77088
77510
 
77089
77511
  // src/commands/canvas/scaffold-ad.ts
77090
- import { copyFile, cp, mkdir as mkdir12, readFile as readFile29, stat as stat6, writeFile as writeFile19 } from "fs/promises";
77091
- import path36 from "path";
77512
+ import { copyFile, cp, mkdir as mkdir12, readFile as readFile30, stat as stat6, writeFile as writeFile19 } from "fs/promises";
77513
+ import path37 from "path";
77092
77514
  import { defineCommand as defineCommand144 } from "citty";
77093
77515
 
77094
77516
  // src/engine/scaffold/ad-spec.ts
@@ -77802,8 +78224,8 @@ function injectAdBrandOverlay(indexHtml, overlayHtml, style = {}) {
77802
78224
  }
77803
78225
 
77804
78226
  // src/commands/canvas/composition-dims.ts
77805
- import { readFile as readFile28, writeFile as writeFile18 } from "fs/promises";
77806
- import path35 from "path";
78227
+ import { readFile as readFile29, writeFile as writeFile18 } from "fs/promises";
78228
+ import path36 from "path";
77807
78229
  function patchCompositionMeta(metaJson, dims) {
77808
78230
  try {
77809
78231
  const meta = JSON.parse(metaJson);
@@ -77818,11 +78240,11 @@ function patchCompositionHtml(html, dims) {
77818
78240
  return html.replace(/(<meta\s+name="viewport"\s+content="width=)\d+(,\s*height=)\d+(")/i, `$1${dims.w}$2${dims.h}$3`).replace(/(width:\s*)\d+(px;\s*height:\s*)\d+(px;)/i, `$1${dims.w}$2${dims.h}$3`).replace(/(data-width=")\d+(")/i, `$1${dims.w}$2`).replace(/(data-height=")\d+(")/i, `$1${dims.h}$2`);
77819
78241
  }
77820
78242
  async function stampCompositionDims(compositionDir, dims) {
77821
- const metaPath = path35.join(compositionDir, "meta.json");
77822
- const rawMeta = await readFile28(metaPath, "utf8");
78243
+ const metaPath = path36.join(compositionDir, "meta.json");
78244
+ const rawMeta = await readFile29(metaPath, "utf8");
77823
78245
  await writeFile18(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
77824
- const htmlPath = path35.join(compositionDir, "index.html");
77825
- const rawHtml = await readFile28(htmlPath, "utf8");
78246
+ const htmlPath = path36.join(compositionDir, "index.html");
78247
+ const rawHtml = await readFile29(htmlPath, "utf8");
77826
78248
  await writeFile18(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
77827
78249
  }
77828
78250
 
@@ -77905,7 +78327,7 @@ var scaffoldAdCommand = defineCommand144({
77905
78327
  const specPath = args8.spec;
77906
78328
  let parsed;
77907
78329
  try {
77908
- parsed = JSON.parse(await readFile29(specPath, "utf-8"));
78330
+ parsed = JSON.parse(await readFile30(specPath, "utf-8"));
77909
78331
  } catch (e) {
77910
78332
  writeJson({
77911
78333
  ok: false,
@@ -78066,7 +78488,7 @@ var scaffoldAdCommand = defineCommand144({
78066
78488
  ] : [];
78067
78489
  const markPath = spec.data.brand?.logo?.trim();
78068
78490
  if (!noBrand && markPath?.toLowerCase().endsWith(".svg")) {
78069
- const svg = await readFile29(markPath, "utf-8").catch(() => null);
78491
+ const svg = await readFile30(markPath, "utf-8").catch(() => null);
78070
78492
  if (svg === null) {
78071
78493
  writeJson({
78072
78494
  ok: false,
@@ -78118,8 +78540,8 @@ var scaffoldAdCommand = defineCommand144({
78118
78540
  videoModel: chosenVideoModel,
78119
78541
  ...avatarVoice ? { avatarVoice } : {}
78120
78542
  });
78121
- const slug = args8.slug ?? path36.basename(specPath).replace(/\.[^.]+$/, "");
78122
- const outPath = args8.out ?? (args8.slug ? path36.join("src/creatives", slug, `${slug}.canvas.json`) : path36.join(path36.dirname(specPath), `${slug}.canvas.json`));
78543
+ const slug = args8.slug ?? path37.basename(specPath).replace(/\.[^.]+$/, "");
78544
+ const outPath = args8.out ?? (args8.slug ? path37.join("src/creatives", slug, `${slug}.canvas.json`) : path37.join(path37.dirname(specPath), `${slug}.canvas.json`));
78123
78545
  if (args8.slug) await enforceNewCreativeLimit(slug, outPath);
78124
78546
  let outAr;
78125
78547
  try {
@@ -78137,28 +78559,28 @@ var scaffoldAdCommand = defineCommand144({
78137
78559
  return;
78138
78560
  }
78139
78561
  const outDims = canvasDims(outAr);
78140
- const outDir = path36.dirname(outPath);
78562
+ const outDir = path37.dirname(outPath);
78141
78563
  await mkdir12(outDir, { recursive: true });
78142
- const compositionDest = path36.join(outDir, "video-overlay-composition");
78143
- const captionsDest = path36.join(outDir, "tiktok-captions-composition");
78564
+ const compositionDest = path37.join(outDir, "video-overlay-composition");
78565
+ const captionsDest = path37.join(outDir, "tiktok-captions-composition");
78144
78566
  await cp(SHIPPED_OVERLAY_COMPOSITION_DIR, compositionDest, { recursive: true });
78145
78567
  await cp(SHIPPED_CAPTIONS_DIR, captionsDest, { recursive: true });
78146
78568
  await stampCompositionDims(compositionDest, outDims);
78147
78569
  await stampCompositionDims(captionsDest, outDims);
78148
- const captionsIndex = path36.join(captionsDest, "index.html");
78149
- await writeFile19(captionsIndex, injectAdCaptionPlacement(await readFile29(captionsIndex, "utf-8"), outAr), "utf-8");
78570
+ const captionsIndex = path37.join(captionsDest, "index.html");
78571
+ await writeFile19(captionsIndex, injectAdCaptionPlacement(await readFile30(captionsIndex, "utf-8"), outAr), "utf-8");
78150
78572
  const face = await stageBrandDisplayFace(outDir, captionsDest);
78151
78573
  if (face) {
78152
- await writeFile19(captionsIndex, injectAdDisplayFace(await readFile29(captionsIndex, "utf-8"), face.file), "utf-8");
78574
+ await writeFile19(captionsIndex, injectAdDisplayFace(await readFile30(captionsIndex, "utf-8"), face.file), "utf-8");
78153
78575
  }
78154
- const blueprintPath2 = path36.join(outDir, "prompt.json");
78155
- const blueprintStylePath = path36.join(outDir, "prompt.style.json");
78576
+ const blueprintPath2 = path37.join(outDir, "prompt.json");
78577
+ const blueprintStylePath = path37.join(outDir, "prompt.style.json");
78156
78578
  await writeSceneFiles(outDir, blueprint);
78157
78579
  await writeFile19(blueprintStylePath, renderStyleProjectionFromValue(blueprint), "utf8");
78158
- await writeFile19(path36.join(outDir, AD_SPEC_FILE), `${JSON.stringify(specArtifact, null, 2)}
78580
+ await writeFile19(path37.join(outDir, AD_SPEC_FILE), `${JSON.stringify(specArtifact, null, 2)}
78159
78581
  `, "utf8");
78160
78582
  const logoPath = noBrand ? void 0 : spec.data.brand?.logo?.trim();
78161
- const transcriptPath = path36.join(outDir, "transcript.json");
78583
+ const transcriptPath = path37.join(outDir, "transcript.json");
78162
78584
  if (spec.data.voiceover === false) {
78163
78585
  await writeFile19(transcriptPath, `${JSON.stringify(staticTranscript(spec.data, chosenVideoModel), null, 2)}
78164
78586
  `, "utf-8");
@@ -78170,10 +78592,10 @@ var scaffoldAdCommand = defineCommand144({
78170
78592
  // that stops the whole canvas. The same sheet model every other scaffold uses.
78171
78593
  sheetModel: ACTOR_SHEET_MODEL,
78172
78594
  videoModel: chosenVideoModel,
78173
- overlayCompositionPath: path36.relative(outDir, compositionDest),
78174
- captionsCompositionPath: path36.relative(outDir, captionsDest),
78175
- blueprintPath: path36.relative(outDir, blueprintPath2),
78176
- blueprintStylePath: path36.relative(outDir, blueprintStylePath),
78595
+ overlayCompositionPath: path37.relative(outDir, compositionDest),
78596
+ captionsCompositionPath: path37.relative(outDir, captionsDest),
78597
+ blueprintPath: path37.relative(outDir, blueprintPath2),
78598
+ blueprintStylePath: path37.relative(outDir, blueprintStylePath),
78177
78599
  aspect: spec.data.format.aspect_ratio,
78178
78600
  resolution: spec.data.format.resolution,
78179
78601
  // A beat is one clause and one card, so the cap has to clear a whole clause.
@@ -78212,17 +78634,17 @@ var scaffoldAdCommand = defineCommand144({
78212
78634
  // the stock voice this exists to avoid. It is to convert the LATER extracts onto the
78213
78635
  // first beat of the same speaker, which keeps a voice the clip really produced.
78214
78636
  ...avatar ? { keepRenderedVoice: true } : {},
78215
- ...spec.data.voiceover === false ? { staticTranscriptPath: path36.relative(outDir, transcriptPath) } : {}
78637
+ ...spec.data.voiceover === false ? { staticTranscriptPath: path37.relative(outDir, transcriptPath) } : {}
78216
78638
  };
78217
78639
  const canvas = scaffoldVideoCanvas(blueprint, adSpecCastElements(spec.data, avatar), opts);
78218
78640
  const renderNode5 = canvas.nodes.find((n) => n.type === "hyperframe_render");
78219
- const renderedDir = renderNode5 ? path36.join(outDir, String(renderNode5.params?.composition ?? "")) : null;
78641
+ const renderedDir = renderNode5 ? path37.join(outDir, String(renderNode5.params?.composition ?? "")) : null;
78220
78642
  let staged = null;
78221
78643
  let markIsLight = false;
78222
78644
  if (logoPath && renderedDir) {
78223
- const ext = path36.extname(logoPath) || ".png";
78645
+ const ext = path37.extname(logoPath) || ".png";
78224
78646
  try {
78225
- await copyFile(logoPath, path36.join(renderedDir, `brand-mark${ext}`));
78647
+ await copyFile(logoPath, path37.join(renderedDir, `brand-mark${ext}`));
78226
78648
  staged = { file: `brand-mark${ext}`, alt: spec.data.brand?.name ?? "brand" };
78227
78649
  markIsLight = await markInkIsLight(logoPath);
78228
78650
  } catch {
@@ -78241,12 +78663,12 @@ var scaffoldAdCommand = defineCommand144({
78241
78663
  plateIsDark: plateIsDark(spec.data.brand?.palette)
78242
78664
  }) : "";
78243
78665
  if (overlayHtml && renderedDir) {
78244
- const indexPath = path36.join(renderedDir, "index.html");
78245
- const html = await readFile29(indexPath, "utf-8");
78666
+ const indexPath = path37.join(renderedDir, "index.html");
78667
+ const html = await readFile30(indexPath, "utf-8");
78246
78668
  await writeFile19(indexPath, injectAdBrandOverlay(html, overlayHtml, { aspect: outAr, ...face ? { face: AD_DISPLAY_FAMILY } : {} }), "utf-8");
78247
78669
  }
78248
78670
  await writeFile19(
78249
- path36.join(outDir, REBUILD_FILE),
78671
+ path37.join(outDir, REBUILD_FILE),
78250
78672
  `${JSON.stringify({ elements: adSpecCastElements(spec.data, avatar), opts }, null, 2)}
78251
78673
  `,
78252
78674
  "utf8"
@@ -78276,7 +78698,7 @@ var scaffoldAdCommand = defineCommand144({
78276
78698
  return;
78277
78699
  }
78278
78700
  if (args8.slug) {
78279
- const definitionPath = path36.join(outDir, "_definition.md");
78701
+ const definitionPath = path37.join(outDir, "_definition.md");
78280
78702
  const definitionExists = await stat6(definitionPath).then(() => true, () => false);
78281
78703
  if (!definitionExists) {
78282
78704
  await writeFile19(
@@ -78394,9 +78816,9 @@ async function fetchClientLogo() {
78394
78816
  if (!response.ok) return { ok: false, reason: clientLogoMissNote({ use: false, reason: "no_hits" }, domain) };
78395
78817
  const bytes = Buffer.from(await response.arrayBuffer());
78396
78818
  const extension = /\.(svg|png|jpg|jpeg|webp)(?:\?|$)/i.exec(decision.url)?.[1]?.toLowerCase() ?? "svg";
78397
- const dir = path36.join(BRAND_DIR, "logos");
78819
+ const dir = path37.join(BRAND_DIR, "logos");
78398
78820
  await mkdir12(dir, { recursive: true });
78399
- const file = path36.join(dir, `brand-mark.${extension}`);
78821
+ const file = path37.join(dir, `brand-mark.${extension}`);
78400
78822
  await writeFile19(file, bytes);
78401
78823
  return { ok: true, path: file };
78402
78824
  } catch {
@@ -78415,14 +78837,14 @@ async function stageBrandDisplayFace(outDir, compositionDir) {
78415
78837
  if (!face) return null;
78416
78838
  try {
78417
78839
  if ("path" in face.slot) {
78418
- const file2 = `brand-face${path36.extname(face.slot.path).toLowerCase()}`;
78419
- await copyFile(face.slot.path, path36.join(compositionDir, file2));
78420
- return { file: file2, label: `the font file \`${path36.relative(root, face.slot.path)}\`` };
78840
+ const file2 = `brand-face${path37.extname(face.slot.path).toLowerCase()}`;
78841
+ await copyFile(face.slot.path, path37.join(compositionDir, file2));
78842
+ return { file: file2, label: `the font file \`${path37.relative(root, face.slot.path)}\`` };
78421
78843
  }
78422
78844
  const { buffer } = await fetchExternalBytes(face.slot.url, { maxBytes: MAX_FONT_BYTES });
78423
78845
  const extension = /\.(woff2|woff|ttf|otf)(?:\?|$)/i.exec(face.slot.url)?.[1]?.toLowerCase() ?? "woff2";
78424
78846
  const file = `brand-face.${extension}`;
78425
- await writeFile19(path36.join(compositionDir, file), buffer);
78847
+ await writeFile19(path37.join(compositionDir, file), buffer);
78426
78848
  return { file, label: `${face.family ?? "the brand's typeface"}, loaded as a web font` };
78427
78849
  } catch {
78428
78850
  return null;
@@ -78430,9 +78852,9 @@ async function stageBrandDisplayFace(outDir, compositionDir) {
78430
78852
  }
78431
78853
 
78432
78854
  // src/commands/canvas/scaffold-video.ts
78433
- import { cp as cp2, mkdir as mkdir13, readFile as readFile32, rm as rm6, writeFile as writeFile20 } from "fs/promises";
78855
+ import { cp as cp2, mkdir as mkdir13, readFile as readFile33, rm as rm6, writeFile as writeFile20 } from "fs/promises";
78434
78856
  import { tmpdir as tmpdir2 } from "os";
78435
- import path38 from "path";
78857
+ import path39 from "path";
78436
78858
  import { defineCommand as defineCommand145 } from "citty";
78437
78859
 
78438
78860
  // src/engine/scaffold/lib/model-router.ts
@@ -78472,7 +78894,7 @@ function routeVideoModel(input) {
78472
78894
 
78473
78895
  // src/engine/nodes/local/lib/sceneDetect.ts
78474
78896
  import { execFile as execFile2 } from "child_process";
78475
- import { mkdtemp, readdir as readdir15, readFile as readFile30, rm as rm5 } from "fs/promises";
78897
+ import { mkdtemp, readdir as readdir16, readFile as readFile31, rm as rm5 } from "fs/promises";
78476
78898
  import { tmpdir } from "os";
78477
78899
  import { join as join3 } from "path";
78478
78900
  import { promisify as promisify2 } from "util";
@@ -78546,9 +78968,9 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
78546
78968
  ],
78547
78969
  { encoding: "utf-8", maxBuffer: 32 * 1024 * 1024, timeout: timeoutMs }
78548
78970
  );
78549
- const csvName = (await readdir15(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
78971
+ const csvName = (await readdir16(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
78550
78972
  if (!csvName) return [];
78551
- return parsePySceneDetectCsvCuts(await readFile30(join3(outDir, csvName), "utf-8"));
78973
+ return parsePySceneDetectCsvCuts(await readFile31(join3(outDir, csvName), "utf-8"));
78552
78974
  } finally {
78553
78975
  await rm5(outDir, { recursive: true, force: true });
78554
78976
  }
@@ -78572,8 +78994,8 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
78572
78994
  }
78573
78995
 
78574
78996
  // src/commands/canvas/gitignore.ts
78575
- import { appendFile, readFile as readFile31 } from "fs/promises";
78576
- import path37 from "path";
78997
+ import { appendFile, readFile as readFile32 } from "fs/promises";
78998
+ import path38 from "path";
78577
78999
  function missingGitignoreEntries(existing, entries) {
78578
79000
  const present2 = new Set(
78579
79001
  existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
@@ -78581,10 +79003,10 @@ function missingGitignoreEntries(existing, entries) {
78581
79003
  return entries.filter((e) => !present2.has(e.trim().replace(/\/+$/, "")));
78582
79004
  }
78583
79005
  async function ensureGitignore(dir, entries) {
78584
- const file = path37.join(dir, ".gitignore");
79006
+ const file = path38.join(dir, ".gitignore");
78585
79007
  let existing;
78586
79008
  try {
78587
- existing = await readFile31(file, "utf8");
79009
+ existing = await readFile32(file, "utf8");
78588
79010
  } catch {
78589
79011
  return;
78590
79012
  }
@@ -78626,7 +79048,7 @@ async function loadTranscriptBestEffort(ref2) {
78626
79048
  async function stageCaptions(outDir, transcript) {
78627
79049
  const text3 = transcript?.trim();
78628
79050
  if (!text3 || text3 === "[]") return {};
78629
- const compositionPath = path38.join(outDir, "tiktok-captions-composition");
79051
+ const compositionPath = path39.join(outDir, "tiktok-captions-composition");
78630
79052
  await cp2(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });
78631
79053
  return { compositionPath };
78632
79054
  }
@@ -78662,7 +79084,7 @@ var VIDEO_EXT_BY_MIME = {
78662
79084
  "video/x-matroska": ".mkv"
78663
79085
  };
78664
79086
  function referenceVideoExt(url, contentType) {
78665
- const fromPath = path38.extname(new URL(url).pathname).toLowerCase();
79087
+ const fromPath = path39.extname(new URL(url).pathname).toLowerCase();
78666
79088
  if (fromPath && fromPath.length <= 5) return fromPath;
78667
79089
  const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase();
78668
79090
  return mime && VIDEO_EXT_BY_MIME[mime] || ".mp4";
@@ -78688,7 +79110,7 @@ function framesModeReport(canvas, requested) {
78688
79110
  return { frames_mode: mode, ...refused ? { frames_reuse_refused: refused } : {} };
78689
79111
  }
78690
79112
  async function materializeReferenceVideo(fileArg2) {
78691
- if (!/^https?:\/\//i.test(fileArg2)) return path38.resolve(fileArg2);
79113
+ if (!/^https?:\/\//i.test(fileArg2)) return path39.resolve(fileArg2);
78692
79114
  let bytes;
78693
79115
  let contentType;
78694
79116
  try {
@@ -78700,7 +79122,7 @@ async function materializeReferenceVideo(fileArg2) {
78700
79122
  throw new Error(`failed to download reference video: ${e instanceof Error ? e.message : String(e)}`);
78701
79123
  }
78702
79124
  if (bytes.length === 0) throw new Error("reference video download was empty");
78703
- const dest = path38.join(
79125
+ const dest = path39.join(
78704
79126
  tmpdir2(),
78705
79127
  `baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg2, contentType)}`
78706
79128
  );
@@ -78927,12 +79349,12 @@ var scaffoldVideoCommand = defineCommand145({
78927
79349
  } catch (e) {
78928
79350
  return fail4("download", e instanceof Error ? e.message : String(e));
78929
79351
  }
78930
- const base = path38.basename(videoPath, path38.extname(videoPath));
78931
- const outPath = args8.out ? path38.resolve(String(args8.out)) : slug ? path38.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`) : path38.join(path38.dirname(videoPath), `${base}.video.canvas.json`);
79352
+ const base = path39.basename(videoPath, path39.extname(videoPath));
79353
+ const outPath = args8.out ? path39.resolve(String(args8.out)) : slug ? path39.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`) : path39.join(path39.dirname(videoPath), `${base}.video.canvas.json`);
78932
79354
  if (slug) await enforceNewCreativeLimit(slug, outPath);
78933
- const outDir = path38.dirname(outPath);
78934
- const blueprintPath2 = path38.join(outDir, "prompt.json");
78935
- const blueprintStylePath = path38.join(outDir, "prompt.style.json");
79355
+ const outDir = path39.dirname(outPath);
79356
+ const blueprintPath2 = path39.join(outDir, "prompt.json");
79357
+ const blueprintStylePath = path39.join(outDir, "prompt.style.json");
78936
79358
  const frames = args8.frames === "reuse" ? "reuse" : "generate";
78937
79359
  const maxScenes = args8["max-scenes"] ? Number(args8["max-scenes"]) : void 0;
78938
79360
  if (Number.isFinite(maxScenes)) {
@@ -79001,12 +79423,12 @@ var scaffoldVideoCommand = defineCommand145({
79001
79423
  `
79002
79424
  );
79003
79425
  }
79004
- const compositionDest = path38.join(outDir, "video-overlay-composition");
79426
+ const compositionDest = path39.join(outDir, "video-overlay-composition");
79005
79427
  await cp2(SHIPPED_OVERLAY_COMPOSITION_DIR, compositionDest, { recursive: true });
79006
79428
  await stampCompositionDims(compositionDest, outDims);
79007
- const indexPath = path38.join(compositionDest, "index.html");
79429
+ const indexPath = path39.join(compositionDest, "index.html");
79008
79430
  const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript), videoModel });
79009
- const indexHtml = await readFile32(indexPath, "utf8");
79431
+ const indexHtml = await readFile33(indexPath, "utf8");
79010
79432
  const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
79011
79433
  if (injected === indexHtml && overlayHtml.trim()) {
79012
79434
  fail4(
@@ -79020,10 +79442,10 @@ var scaffoldVideoCommand = defineCommand145({
79020
79442
  const opts = {
79021
79443
  imageModel,
79022
79444
  videoModel,
79023
- overlayCompositionPath: path38.relative(outDir, compositionDest),
79024
- captionsCompositionPath: captions.compositionPath ? path38.relative(outDir, captions.compositionPath) : void 0,
79025
- blueprintPath: path38.relative(outDir, blueprintPath2),
79026
- blueprintStylePath: path38.relative(outDir, blueprintStylePath),
79445
+ overlayCompositionPath: path39.relative(outDir, compositionDest),
79446
+ captionsCompositionPath: captions.compositionPath ? path39.relative(outDir, captions.compositionPath) : void 0,
79447
+ blueprintPath: path39.relative(outDir, blueprintPath2),
79448
+ blueprintStylePath: path39.relative(outDir, blueprintStylePath),
79027
79449
  frames,
79028
79450
  ambient: Boolean(args8.ambient),
79029
79451
  seamDedup: resolveSeamDedup(args8["seam-dedup"]),
@@ -79051,7 +79473,7 @@ var scaffoldVideoCommand = defineCommand145({
79051
79473
  await writeFile20(outPath, `${JSON.stringify(canvas, null, 2)}
79052
79474
  `, "utf8");
79053
79475
  await writeFile20(
79054
- path38.join(outDir, REBUILD_FILE),
79476
+ path39.join(outDir, REBUILD_FILE),
79055
79477
  `${JSON.stringify({ elements, opts }, null, 2)}
79056
79478
  `,
79057
79479
  "utf8"
@@ -79078,7 +79500,7 @@ var scaffoldVideoCommand = defineCommand145({
79078
79500
  await ensureGitignore(process.cwd(), ["canvas/", ".context/"]);
79079
79501
  const sourceRef = videoSourceReference(blueprint, fileArg2);
79080
79502
  if (slug) {
79081
- const definitionPath = path38.join(outDir, "_definition.md");
79503
+ const definitionPath = path39.join(outDir, "_definition.md");
79082
79504
  if (!await fileExists(definitionPath)) {
79083
79505
  await writeFile20(
79084
79506
  definitionPath,
@@ -79134,7 +79556,7 @@ var scaffoldVideoCommand = defineCommand145({
79134
79556
  graph: canvas.metadata?.video?.graph_stats
79135
79557
  },
79136
79558
  checklist: {
79137
- edit_prompt: `The blueprint is split so you edit ONE small file at a time \u2014 never a giant one. Per-scene content (a scene's dialogue, action, frame prompts, overlays) lives in \`scenes/sNN.json\` \u2014 edit the single scene you want to change. Global cast/palette/brand/copy lives in \`${path38.basename(blueprintPath2)}\`. \`baker canvas validate\`/\`run\` re-assemble the blueprint and re-flow every edited scene back into the render (and regenerate ${path38.basename(blueprintStylePath)}, the projection each frame's target_blueprint reads) \u2014 so your scene edits reach the render automatically. Never hand-edit the inlined node prompts in the canvas or the derived ${path38.basename(blueprintStylePath)}; both are regenerated.`,
79559
+ edit_prompt: `The blueprint is split so you edit ONE small file at a time \u2014 never a giant one. Per-scene content (a scene's dialogue, action, frame prompts, overlays) lives in \`scenes/sNN.json\` \u2014 edit the single scene you want to change. Global cast/palette/brand/copy lives in \`${path39.basename(blueprintPath2)}\`. \`baker canvas validate\`/\`run\` re-assemble the blueprint and re-flow every edited scene back into the render (and regenerate ${path39.basename(blueprintStylePath)}, the projection each frame's target_blueprint reads) \u2014 so your scene edits reach the render automatically. Never hand-edit the inlined node prompts in the canvas or the derived ${path39.basename(blueprintStylePath)}; both are regenerated.`,
79138
79560
  recurring_elements_to_supply: report.elements,
79139
79561
  voices_to_confirm: report.dialogue.map((d) => ({
79140
79562
  scene: d.scene,
@@ -79171,8 +79593,8 @@ var scaffoldVideoCommand = defineCommand145({
79171
79593
  });
79172
79594
 
79173
79595
  // src/commands/canvas/set-prompt.ts
79174
- import { readFile as readFile33, writeFile as writeFile21 } from "fs/promises";
79175
- import path39 from "path";
79596
+ import { readFile as readFile34, writeFile as writeFile21 } from "fs/promises";
79597
+ import path40 from "path";
79176
79598
  import { defineCommand as defineCommand146 } from "citty";
79177
79599
  function setNodePrompt(canvas, nodeId, text3) {
79178
79600
  const nodes = canvas?.nodes;
@@ -79201,8 +79623,8 @@ var SetPromptError = class extends Error {
79201
79623
  }
79202
79624
  };
79203
79625
  async function setPromptInCanvasFile(canvasPath, nodeId, text3) {
79204
- const filePath = path39.resolve(canvasPath);
79205
- const original = await readFile33(filePath, "utf8");
79626
+ const filePath = path40.resolve(canvasPath);
79627
+ const original = await readFile34(filePath, "utf8");
79206
79628
  let canvas;
79207
79629
  try {
79208
79630
  canvas = JSON.parse(original);
@@ -79224,7 +79646,7 @@ async function setPromptInCanvasFile(canvasPath, nodeId, text3) {
79224
79646
  "projected_prompt",
79225
79647
  `The prompt of "${nodeId}" is compiled from prompt.json on every validate and run, so setting it in the canvas would be overwritten.`,
79226
79648
  {
79227
- fix: `Edit the ad in ${path39.join(path39.dirname(filePath), "prompt.json")} (its scene, copy, references or direction), then run \`baker canvas validate\` to see the compiled prompt on the node.`
79649
+ fix: `Edit the ad in ${path40.join(path40.dirname(filePath), "prompt.json")} (its scene, copy, references or direction), then run \`baker canvas validate\` to see the compiled prompt on the node.`
79228
79650
  }
79229
79651
  );
79230
79652
  }
@@ -79278,7 +79700,7 @@ var setPromptCommand = defineCommand146({
79278
79700
  },
79279
79701
  async run({ args: args8 }) {
79280
79702
  let text3;
79281
- if (args8["text-file"]) text3 = await readFile33(path39.resolve(String(args8["text-file"])), "utf8");
79703
+ if (args8["text-file"]) text3 = await readFile34(path40.resolve(String(args8["text-file"])), "utf8");
79282
79704
  else if (args8.text !== void 0) text3 = String(args8.text);
79283
79705
  else {
79284
79706
  process.stderr.write(
@@ -79335,7 +79757,7 @@ var staleCommand = defineCommand147({
79335
79757
  });
79336
79758
 
79337
79759
  // src/commands/canvas/validate.ts
79338
- import path40 from "path";
79760
+ import path41 from "path";
79339
79761
  import { defineCommand as defineCommand148 } from "citty";
79340
79762
  var validateCommand = defineCommand148({
79341
79763
  meta: {
@@ -79344,7 +79766,7 @@ var validateCommand = defineCommand148({
79344
79766
  },
79345
79767
  args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
79346
79768
  async run({ args: args8 }) {
79347
- const filePath = path40.resolve(String(args8.file));
79769
+ const filePath = path41.resolve(String(args8.file));
79348
79770
  let prepared;
79349
79771
  try {
79350
79772
  prepared = await prepareCanvas(filePath, { gate: true, log: (line) => process.stderr.write(`${line}
@@ -80302,326 +80724,6 @@ var searchCommand = defineCommand152({
80302
80724
  }
80303
80725
  });
80304
80726
 
80305
- // src/commands/creatives/ideas-model.ts
80306
- import { parse as parseYaml3 } from "yaml";
80307
- var IDEA_ANGLES = [
80308
- "pain_relief",
80309
- "aspiration",
80310
- "status",
80311
- "fear",
80312
- "FOMO",
80313
- "identity",
80314
- "contrarian",
80315
- "education",
80316
- "novelty",
80317
- "authority",
80318
- "community",
80319
- "transformation",
80320
- "convenience",
80321
- "save_money",
80322
- "save_time",
80323
- "save_effort",
80324
- "named_enemy",
80325
- "objection_crusher",
80326
- "villain_democratization",
80327
- "commiseration",
80328
- "recognition_mirror",
80329
- "flipped_idiom"
80330
- ];
80331
- var IDEA_VIDEO_FORMATS = [
80332
- "video_ugc",
80333
- "video_founder",
80334
- "video_testimonial",
80335
- "video_demo",
80336
- "video_screen_recording",
80337
- "video_explainer",
80338
- "video_motion_graphics"
80339
- ];
80340
- var IDEA_FORMATS = [...staticFormatPatternSchema.options, ...IDEA_VIDEO_FORMATS];
80341
- var AWARENESS_STAGES = ["unaware", "problem_aware", "solution_aware", "product_aware", "most_aware"];
80342
- var EVIDENCE_LEVELS = ["observed", "interpreted", "hypothesized", "generated"];
80343
- var IDEA_STATUSES = ["idea", "built", "testing", "winner", "failed", "inconclusive", "retired"];
80344
- var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
80345
- function frontmatterOf2(markdown) {
80346
- const block2 = FRONTMATTER.exec(markdown)?.[1];
80347
- if (block2 === void 0) return null;
80348
- try {
80349
- const parsed = parseYaml3(block2);
80350
- return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
80351
- } catch {
80352
- return null;
80353
- }
80354
- }
80355
- function text2(value2) {
80356
- if (typeof value2 === "string") return value2.trim() || void 0;
80357
- if (typeof value2 === "number") return String(value2);
80358
- return void 0;
80359
- }
80360
- function oneOf2(values, value2) {
80361
- return typeof value2 === "string" && values.includes(value2) ? value2 : void 0;
80362
- }
80363
- function parseInsight(id, markdown) {
80364
- const fm = frontmatterOf2(markdown);
80365
- const quote = text2(fm?.quote);
80366
- const source = text2(fm?.source);
80367
- if (!fm || !quote || !source) return null;
80368
- const force = text2(fm.force);
80369
- const persona = text2(fm.persona);
80370
- const sourceKind = text2(fm.sourceKind);
80371
- return {
80372
- id,
80373
- type: text2(fm.type) ?? "other",
80374
- quote,
80375
- source,
80376
- status: oneOf2(EVIDENCE_LEVELS, fm.status) ?? "observed",
80377
- ...sourceKind ? { sourceKind } : {},
80378
- ...force ? { force } : {},
80379
- ...persona ? { persona } : {}
80380
- };
80381
- }
80382
- function evidenceList(value2) {
80383
- if (Array.isArray(value2)) return value2.flatMap((e) => text2(e) ? [String(text2(e))] : []);
80384
- const single = text2(value2);
80385
- return single ? [single] : [];
80386
- }
80387
- function fieldProblems(f) {
80388
- const problems = [];
80389
- if (!f.who) problems.push("who is missing: the persona it is for");
80390
- if (!f.message)
80391
- problems.push(
80392
- "message is missing: the one promise a stranger should get, what is offered and what changes for them"
80393
- );
80394
- if (!f.why) problems.push("why is missing: the brief finding it answers");
80395
- if (!f.awareness) problems.push(`awareness must be one of ${AWARENESS_STAGES.join(", ")}`);
80396
- if (!(f.angle && IDEA_ANGLES.includes(f.angle)))
80397
- problems.push(`angle ${f.angle ? `"${f.angle}" ` : ""}must be one of ${IDEA_ANGLES.join(", ")}`);
80398
- if (!(f.format && IDEA_FORMATS.includes(f.format)))
80399
- problems.push(`format ${f.format ? `"${f.format}" ` : ""}must be one of ${IDEA_FORMATS.join(", ")}`);
80400
- return problems;
80401
- }
80402
- function parseIdea(id, markdown) {
80403
- const fm = frontmatterOf2(markdown) ?? {};
80404
- const f = {
80405
- who: text2(fm.who),
80406
- message: text2(fm.message),
80407
- why: text2(fm.why),
80408
- angle: text2(fm.angle),
80409
- format: text2(fm.format),
80410
- awareness: oneOf2(AWARENESS_STAGES, fm.awareness)
80411
- };
80412
- const problems = fieldProblems(f);
80413
- const { who, message, why, angle, format, awareness } = f;
80414
- if (problems.length > 0 || !who || !message || !why || !awareness || !angle || !format) return { problems };
80415
- return {
80416
- idea: {
80417
- id,
80418
- who,
80419
- message,
80420
- evidence: evidenceList(fm.evidence),
80421
- awareness,
80422
- angle,
80423
- format,
80424
- job: fm.job === "closer" ? "closer" : "opener",
80425
- why,
80426
- status: oneOf2(IDEA_STATUSES, fm.status) ?? "idea"
80427
- },
80428
- problems
80429
- };
80430
- }
80431
- var EVIDENCE_WEIGHT = {
80432
- observed: 3,
80433
- interpreted: 2,
80434
- hypothesized: 1,
80435
- generated: 0,
80436
- explore: 0
80437
- };
80438
- var LIVE_STATUSES = ["built", "testing", "winner"];
80439
- function claimsOf(message) {
80440
- return [...message.matchAll(/\d[\d.,]*\s?(?:%|€|\$|£|x\b|k\b)?/gi)].map((m) => m[0].trim());
80441
- }
80442
- function figureOf(claim) {
80443
- return claim.replace(/[^\d]/g, "");
80444
- }
80445
- function figuresIn(text3) {
80446
- return new Set([...text3.matchAll(/\d(?:[\d.,]*\d)?/g)].map((m) => figureOf(m[0])));
80447
- }
80448
- function normalize(value2) {
80449
- return value2.toLowerCase().replace(/\s+/g, " ").trim();
80450
- }
80451
- function sentencesOf(message) {
80452
- return message.split(/[.!?]+(?:\s|$)/).filter((s) => s.trim().length > 0).length;
80453
- }
80454
- var FILLER = /* @__PURE__ */ new Set([
80455
- "that",
80456
- "this",
80457
- "with",
80458
- "your",
80459
- "from",
80460
- "they",
80461
- "them",
80462
- "what",
80463
- "when",
80464
- "have",
80465
- "will",
80466
- "into",
80467
- "than",
80468
- "more",
80469
- "just",
80470
- "para",
80471
- "como",
80472
- "pero",
80473
- "sobre"
80474
- ]);
80475
- var SAME_MESSAGE = 0.6;
80476
- function messageWords(message) {
80477
- return new Set(
80478
- message.toLowerCase().split(/[^\p{L}]+/u).filter((w) => w.length >= 4 && !FILLER.has(w)).map((w) => w.replace(/s$/, ""))
80479
- );
80480
- }
80481
- function sameMessage(a, b) {
80482
- const wa = messageWords(a);
80483
- const wb = messageWords(b);
80484
- const shared = [...wa].filter((w) => wb.has(w)).length;
80485
- const all = (/* @__PURE__ */ new Set([...wa, ...wb])).size;
80486
- return normalize(a) === normalize(b) || all > 0 && shared / all >= SAME_MESSAGE;
80487
- }
80488
- function repeatedFailure(idea, failed) {
80489
- const lost = failed.find(
80490
- (f) => f.id !== idea.id && (sameMessage(f.message, idea.message) || f.angle === idea.angle && f.format === idea.format)
80491
- );
80492
- return lost ? [`says what "${lost.id}" said, and that idea failed in the account: change the message, not just the picture`] : [];
80493
- }
80494
- function backingOf(idea, insights, problems) {
80495
- const backing = idea.evidence.flatMap((id) => {
80496
- const insight = insights.get(id);
80497
- if (!insight)
80498
- problems.push(`evidence "${id}" is not in src/content/insights/: name an insight on file or leave it out`);
80499
- return insight ? [insight] : [];
80500
- });
80501
- return [...backing].sort((a, b) => EVIDENCE_WEIGHT[b.status] - EVIDENCE_WEIGHT[a.status])[0];
80502
- }
80503
- function unsourcedClaims(text3, corpusFigures) {
80504
- return claimsOf(text3).filter((claim) => {
80505
- const figure = figureOf(claim);
80506
- return figure !== "" && !corpusFigures.has(figure);
80507
- });
80508
- }
80509
- function claimProblems(message, corpusFigures) {
80510
- return unsourcedClaims(message, corpusFigures).map(
80511
- (claim) => `"${claim}" is in no customer quote, COMPANY.md or Knowledge entry: source it or drop it`
80512
- );
80513
- }
80514
- var MARKET_SOURCES = ["reddit", "forum", "web"];
80515
- var TESTIMONIAL_FORMATS = ["review_screenshot", "testimonial_quote", "video_testimonial"];
80516
- function fromMarket(insight) {
80517
- return insight?.sourceKind !== void 0 && MARKET_SOURCES.includes(insight.sourceKind);
80518
- }
80519
- function borrowedProof(idea, insights) {
80520
- return idea.evidence.find((id) => {
80521
- const insight = insights.get(id);
80522
- return fromMarket(insight) && insight?.type === "proof";
80523
- });
80524
- }
80525
- function fakeReviewProblem(idea, insights) {
80526
- if (!TESTIMONIAL_FORMATS.includes(idea.format)) return [];
80527
- const cited = idea.evidence.flatMap((id) => insights.get(id) ?? []);
80528
- if (cited.length === 0 || !cited.every(fromMarket)) return [];
80529
- return [
80530
- `${idea.format} needs a quote from our own customer (a review, testimonial, call or survey); every one cited was said by someone in the market. Pick another format, or cite our own customer`
80531
- ];
80532
- }
80533
- function warningsOf(idea, evidence, personas, insights) {
80534
- const warnings = [];
80535
- const borrowed = borrowedProof(idea, insights);
80536
- if (borrowed) {
80537
- warnings.push(
80538
- `"${borrowed}" was said by someone in the market, not a customer of ours: use their words for the problem, never show it as our review, testimonial or result`
80539
- );
80540
- }
80541
- if (sentencesOf(idea.message) > 1 || idea.message.split(/\s+/).length > 25) {
80542
- warnings.push("the message says more than one thing: keep one promise, the rest goes to the ad text");
80543
- }
80544
- if (personas.length > 0 && !personas.includes(idea.who)) {
80545
- warnings.push(`who "${idea.who}" is not a persona in the Knowledge (${personas.join(", ")})`);
80546
- }
80547
- if (evidence === "explore") {
80548
- warnings.push("no customer evidence: an explore bet. `baker research customers` may find some");
80549
- }
80550
- return warnings;
80551
- }
80552
- function checkOne(idea, ctx) {
80553
- const problems = [];
80554
- const best = backingOf(idea, ctx.insights, problems);
80555
- const evidence = best?.status ?? "explore";
80556
- problems.push(...claimProblems(idea.message, ctx.corpusFigures), ...fakeReviewProblem(idea, ctx.insights));
80557
- const duplicate = ctx.duplicateOf.get(idea.id);
80558
- if (duplicate) {
80559
- problems.push(
80560
- `same angle and format, or the same message, as "${duplicate}": to the platform it is the same ad. Change the angle or the format`
80561
- );
80562
- }
80563
- const gaps = [];
80564
- if (!ctx.covered.has(idea.awareness)) gaps.push(`no live ad speaks to ${idea.awareness} people`);
80565
- if (idea.job === "closer" && ctx.closersLive === 0) gaps.push("no live ad closes warm people");
80566
- return {
80567
- id: idea.id,
80568
- status: idea.status,
80569
- message: idea.message,
80570
- evidence,
80571
- ...best ? { quote: best.quote } : {},
80572
- awareness: idea.awareness,
80573
- angle: idea.angle,
80574
- format: idea.format,
80575
- problems,
80576
- warnings: [...warningsOf(idea, evidence, ctx.personas, ctx.insights), ...repeatedFailure(idea, ctx.failed)],
80577
- gaps,
80578
- score: EVIDENCE_WEIGHT[evidence] + gaps.length - (problems.length > 0 ? 10 : 0)
80579
- };
80580
- }
80581
- function duplicatesOf(ideas, insights) {
80582
- const active = ideas.filter((i) => i.status !== "retired" && i.status !== "failed");
80583
- const strength = (idea) => Math.max(0, ...idea.evidence.map((id) => EVIDENCE_WEIGHT[insights.get(id)?.status ?? "explore"]));
80584
- const position = new Map(ideas.map((idea, index) => [idea.id, index]));
80585
- const order = [...active].sort(
80586
- (a, b) => Number(LIVE_STATUSES.includes(b.status)) - Number(LIVE_STATUSES.includes(a.status)) || strength(b) - strength(a) || (position.get(a.id) ?? 0) - (position.get(b.id) ?? 0)
80587
- );
80588
- const seenPairs = /* @__PURE__ */ new Map();
80589
- const seenMessages = /* @__PURE__ */ new Map();
80590
- const duplicateOf = /* @__PURE__ */ new Map();
80591
- for (const idea of order) {
80592
- const pair = `${idea.angle}\xD7${idea.format}`;
80593
- const message = normalize(idea.message);
80594
- const earlier = seenPairs.get(pair) ?? seenMessages.get(message);
80595
- if (earlier) duplicateOf.set(idea.id, earlier);
80596
- if (!seenPairs.has(pair)) seenPairs.set(pair, idea.id);
80597
- if (!seenMessages.has(message)) seenMessages.set(message, idea.id);
80598
- }
80599
- return duplicateOf;
80600
- }
80601
- function checkIdeas(input) {
80602
- const insights = new Map(input.insights.map((insight) => [insight.id, insight]));
80603
- const ownQuotes = input.insights.filter((i) => !fromMarket(i)).map((i) => i.quote);
80604
- const corpusFigures = figuresIn(`${input.knowledge}
80605
- ${ownQuotes.join("\n")}`);
80606
- const covered = new Set(input.coveredAwareness);
80607
- const duplicateOf = duplicatesOf(input.ideas, insights);
80608
- const checks = input.ideas.map(
80609
- (idea) => checkOne(idea, {
80610
- insights,
80611
- corpusFigures,
80612
- covered,
80613
- duplicateOf,
80614
- personas: input.personas,
80615
- closersLive: input.closersLive,
80616
- failed: input.ideas.filter((i) => i.status === "failed")
80617
- })
80618
- );
80619
- const toBuild = (c) => c.status === "idea";
80620
- return checks.sort(
80621
- (a, b) => Number(toBuild(b)) - Number(toBuild(a)) || b.score - a.score || a.id.localeCompare(b.id)
80622
- );
80623
- }
80624
-
80625
80727
  // src/commands/creatives/brief-model.ts
80626
80728
  var MIN_IMPRESSIONS2 = 1e3;
80627
80729
  var LOW_CTR = 1.5;
@@ -80731,85 +80833,6 @@ function marketMap(ads, source) {
80731
80833
 
80732
80834
  // src/commands/creatives/ideas.ts
80733
80835
  import { defineCommand as defineCommand153 } from "citty";
80734
-
80735
- // src/commands/creatives/ideas-workspace.ts
80736
- import { readdir as readdir16, readFile as readFile34 } from "fs/promises";
80737
- import path41 from "path";
80738
- var IDEAS_DIR = path41.join("src", "creatives", "_ideas");
80739
- var INSIGHTS_DIR = path41.join("src", "content", "insights");
80740
- async function markdownFiles(dir, recursive = false) {
80741
- const entries = await readdir16(dir, { recursive }).catch(() => []);
80742
- return entries.filter((f) => f.endsWith(".md")).sort();
80743
- }
80744
- var idOf2 = (file) => file.replace(/\\/g, "/").replace(/\.md$/, "");
80745
- async function readInsights(root) {
80746
- const dir = path41.join(root, INSIGHTS_DIR);
80747
- const insights = [];
80748
- for (const file of await markdownFiles(dir, true)) {
80749
- const insight = parseInsight(idOf2(file), await readFile34(path41.join(dir, file), "utf8").catch(() => ""));
80750
- if (insight) insights.push(insight);
80751
- }
80752
- return insights;
80753
- }
80754
- async function readIdeas(root) {
80755
- const dir = path41.join(root, IDEAS_DIR);
80756
- const ideas = [];
80757
- const unreadable = [];
80758
- for (const file of await markdownFiles(dir)) {
80759
- const id = idOf2(file);
80760
- const parsed = parseIdea(id, await readFile34(path41.join(dir, file), "utf8").catch(() => ""));
80761
- if (parsed.idea) ideas.push(parsed.idea);
80762
- else unreadable.push({ id, problems: parsed.problems });
80763
- }
80764
- return { ideas, unreadable };
80765
- }
80766
- async function readPersonaIds(root) {
80767
- return (await markdownFiles(path41.join(root, "src", "content", "personas"), true)).map(idOf2);
80768
- }
80769
- var MAX_KNOWLEDGE_CHARS = 2e5;
80770
- async function readKnowledgeCorpus(root) {
80771
- const parts = [];
80772
- const company = await readFile34(path41.join(root, "COMPANY.md"), "utf8").catch(() => "");
80773
- if (company) parts.push(company);
80774
- const content = path41.join(root, "src", "content");
80775
- for (const file of await markdownFiles(content, true)) {
80776
- parts.push(await readFile34(path41.join(content, file), "utf8").catch(() => ""));
80777
- if (parts.join("").length > MAX_KNOWLEDGE_CHARS) break;
80778
- }
80779
- return parts.join("\n").slice(0, MAX_KNOWLEDGE_CHARS);
80780
- }
80781
- async function readCreativeRecords(root) {
80782
- const dir = path41.join(root, "src", "creatives");
80783
- const entries = await readdir16(dir, { withFileTypes: true }).catch(() => []);
80784
- const records = [];
80785
- for (const entry of entries) {
80786
- if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
80787
- const raw = await readFile34(path41.join(dir, entry.name, "_definition.md"), "utf8").catch(() => null);
80788
- if (raw === null) continue;
80789
- const fm = frontmatterOf2(raw) ?? {};
80790
- const pick2 = (key) => typeof fm[key] === "string" && fm[key] ? { [key]: fm[key] } : {};
80791
- records.push({
80792
- slug: entry.name,
80793
- ...pick2("status"),
80794
- ...pick2("idea"),
80795
- ...pick2("awareness"),
80796
- ...pick2("angle"),
80797
- ...pick2("kind")
80798
- });
80799
- }
80800
- return records;
80801
- }
80802
- var SHOWN_STATUSES = /* @__PURE__ */ new Set(["rendered", "published"]);
80803
- function coverageOf(ideas, creatives) {
80804
- const live = ideas.filter((i) => i.status === "built" || i.status === "testing" || i.status === "winner");
80805
- const awareness = new Set(live.map((i) => i.awareness));
80806
- for (const creative of creatives) {
80807
- if (creative.awareness && SHOWN_STATUSES.has(creative.status ?? "")) awareness.add(creative.awareness);
80808
- }
80809
- return { coveredAwareness: [...awareness].sort(), closersLive: live.filter((i) => i.job === "closer").length };
80810
- }
80811
-
80812
- // src/commands/creatives/ideas.ts
80813
80836
  var BUILD_NEXT = 3;
80814
80837
  var DESCRIPTION4 = `Check and rank the ad ideas in src/creatives/_ideas/: is each backed by something a customer said (src/content/insights/), does it claim only what the Knowledge holds, is it a different ad from the others, does it fill a gap the live ads leave. Free and local.
80815
80838
 
@@ -80847,7 +80870,11 @@ function ideasHints(report) {
80847
80870
  hints2.push("No idea cites an insight: put the insight ids that back each one in its `evidence`.");
80848
80871
  }
80849
80872
  const next = buildNext(report.ideas);
80850
- if (next.length > 0) hints2.push(`Build: ${next.map((id) => `\`baker creatives make ${id}\``).join(", ")}.`);
80873
+ if (next.length === 1) hints2.push(`Build: \`baker creatives make ${next[0]}\`.`);
80874
+ if (next.length > 1)
80875
+ hints2.push(
80876
+ `Build ${next.join(", ")} at once: one \`creative-producer\` per idea, all spawned in one message, each given its idea id. Never build them one after another: each render takes about five minutes.`
80877
+ );
80851
80878
  return hints2;
80852
80879
  }
80853
80880
  function buildNext(ideas, count2 = BUILD_NEXT) {
@@ -81957,7 +81984,7 @@ async function recordIdea(root, slug, idea, evidence) {
81957
81984
  }
81958
81985
  async function draftedClaims(root, prompt) {
81959
81986
  const [knowledge2, insights] = await Promise.all([readKnowledgeCorpus(root), readInsights(root)]);
81960
- const own = insights.filter((i) => !MARKET_SOURCES.includes(i.sourceKind ?? "")).map((i) => i.quote);
81987
+ const own = insights.filter(sourcesClaims).map((i) => i.quote);
81961
81988
  try {
81962
81989
  return unsourcedCopyLines(prompt, `${knowledge2}
81963
81990
  ${own.join("\n")}`);