@koda-sl/baker-cli 0.271.0-dev.1f1c09c80 → 0.272.1-dev.1f1c09c80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3669,6 +3669,9 @@ paid for by the time it runs — and the findings come back as `review` and `hin
3669
3669
  - **Across the reel:** whether the cast stays the same people, whether a brand mark is
3670
3670
  visible anywhere, and whether the last frames close the ad or simply stop.
3671
3671
 
3672
+ Each per-frame finding names the **scene file** that produced that second, so a defect is
3673
+ an edit rather than a hunt — and re-running re-renders only the scenes you changed.
3674
+
3672
3675
  It needs `GEMINI_API_KEY` (the Runtime receives it automatically). Without one the run
3673
3676
  says the video was **not** reviewed rather than reporting it clean, and a frame that
3674
3677
  could not be read is reported as unchecked for the same reason.
package/dist/cli.js CHANGED
@@ -30100,6 +30100,9 @@ async function syncSceneNodeParams(canvas, canvasPath, log) {
30100
30100
  try {
30101
30101
  rebuildRaw = await readFile7(path9.join(creativeDir, REBUILD_FILE), "utf8");
30102
30102
  } catch {
30103
+ log(
30104
+ `[scenes] this creative has scene files but no ${REBUILD_FILE}, so edits to scenes/*.json are NOT reaching the render \u2014 the prompts inlined in the canvas are what renders. Re-scaffold the creative to restore the link, or edit the canvas node prompts directly.`
30105
+ );
30103
30106
  return "not_applicable";
30104
30107
  }
30105
30108
  const { elements, opts } = JSON.parse(rebuildRaw);
@@ -30531,10 +30534,10 @@ function runDirsToPrune(entries, keep, currentRunId) {
30531
30534
  return runs.slice(0, Math.max(0, runs.length - keep));
30532
30535
  }
30533
30536
  async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
30534
- const { readdir: readdir11 } = await import("fs/promises");
30537
+ const { readdir: readdir12 } = await import("fs/promises");
30535
30538
  let entries;
30536
30539
  try {
30537
- entries = await readdir11(outputsDir);
30540
+ entries = await readdir12(outputsDir);
30538
30541
  } catch {
30539
30542
  return;
30540
30543
  }
@@ -30827,7 +30830,7 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
30827
30830
 
30828
30831
  // src/engine/nodes/local/lib/render-review.ts
30829
30832
  import { execFile as execFile2 } from "child_process";
30830
- import { mkdtemp, readFile as readFile11, rm as rm5 } from "fs/promises";
30833
+ import { mkdtemp, readdir as readdir6, readFile as readFile11, rm as rm5 } from "fs/promises";
30831
30834
  import { tmpdir } from "os";
30832
30835
  import path15 from "path";
30833
30836
  import { promisify as promisify2 } from "util";
@@ -30835,6 +30838,9 @@ import { z as z28 } from "zod";
30835
30838
  var RENDER_REVIEW_MODEL = "gemini-pro-latest";
30836
30839
  var RENDER_REVIEW_FRAMES = 12;
30837
30840
  var RENDER_REVIEW_WIDTH = 1024;
30841
+ function sceneAt(windows, atS) {
30842
+ return windows.find((w) => atS >= w.startS && atS < w.endS)?.file;
30843
+ }
30838
30844
  function reviewFrameTimes(durationS, frames) {
30839
30845
  const n = Math.max(2, frames);
30840
30846
  const last = Math.max(0, durationS - 0.15);
@@ -30937,19 +30943,23 @@ function collapseRepeats(frames) {
30937
30943
  }
30938
30944
  return [...seen.values()];
30939
30945
  }
30940
- function classifyFrameFindings(frames) {
30946
+ function classifyFrameFindings(frames, windows = []) {
30941
30947
  const seen = collapseRepeats(frames);
30942
30948
  const anatomy = seen.filter((f) => ANATOMY.test(f.what));
30943
30949
  const rest = seen.filter((f) => !ANATOMY.test(f.what));
30944
- const findings = rest.map((f) => ({
30945
- severity: "blocking",
30946
- what: `At ${f.at.map((s) => `${s}s`).join(", ")}: ${f.what}`
30947
- }));
30950
+ const findings = rest.map((f) => {
30951
+ const scene = sceneAt(windows, f.at[0]);
30952
+ return {
30953
+ severity: "blocking",
30954
+ what: `At ${f.at.map((s) => `${s}s`).join(", ")}${scene ? ` (${scene})` : ""}: ${f.what}`,
30955
+ ...scene ? { scene } : {}
30956
+ };
30957
+ });
30948
30958
  if (anatomy.length > 0) {
30949
30959
  const at = [...new Set(anatomy.flatMap((f) => f.at))].sort((a, b) => a - b);
30950
30960
  findings.push({
30951
30961
  severity: "blocking",
30952
- what: `Malformed hands or limbs in ${at.length} of ${frames.length} sampled frames (${at.map((s) => `${s}s`).join(", ")}): ${anatomy.map((f) => f.what).join(" ")} Re-render those beats \u2014 keep hands out of frame, or away from the object being held.`
30962
+ what: `Malformed hands or limbs in ${at.length} of ${frames.length} sampled frames (${at.map((s) => `${s}s${sceneAt(windows, s) ? ` \u2192 ${sceneAt(windows, s)}` : ""}`).join(", ")}): ${anatomy.map((f) => f.what).join(" ")} Re-render those beats \u2014 keep hands out of frame, or away from the object being held.`
30953
30963
  });
30954
30964
  }
30955
30965
  return findings;
@@ -31074,7 +31084,7 @@ async function reviewRenderedVideo(opts) {
31074
31084
  }));
31075
31085
  const unread = perFrame.filter((f) => f.json === null).length;
31076
31086
  if (unread === perFrame.length && reel === null) return null;
31077
- const findings = [...classifyFrameFindings(perFrame), ...classifyReelFindings(reel)];
31087
+ const findings = [...classifyFrameFindings(perFrame, opts.windows ?? []), ...classifyReelFindings(reel)];
31078
31088
  if (unread > 0) {
31079
31089
  findings.push({
31080
31090
  severity: "warning",
@@ -31086,6 +31096,24 @@ async function reviewRenderedVideo(opts) {
31086
31096
  await rm5(dir, { recursive: true, force: true });
31087
31097
  }
31088
31098
  }
31099
+ async function sceneWindowsBeside(canvasPath) {
31100
+ const dir = path15.join(path15.dirname(canvasPath), "scenes");
31101
+ const files = await readdir6(dir).catch(() => null);
31102
+ if (!files) return [];
31103
+ const windows = [];
31104
+ for (const name of files.filter((f) => f.endsWith(".json")).sort()) {
31105
+ const raw = await readFile11(path15.join(dir, name), "utf-8").catch(() => null);
31106
+ if (!raw) continue;
31107
+ try {
31108
+ const scene = JSON.parse(raw);
31109
+ if (typeof scene.start_s === "number" && typeof scene.end_s === "number") {
31110
+ windows.push({ file: `scenes/${name}`, startS: scene.start_s, endS: scene.end_s });
31111
+ }
31112
+ } catch {
31113
+ }
31114
+ }
31115
+ return windows;
31116
+ }
31089
31117
 
31090
31118
  // src/commands/canvas/run.ts
31091
31119
  var runCommand = defineCommand100({
@@ -31373,10 +31401,13 @@ ${describeRewrites(healed.rewrites)}
31373
31401
  `));
31374
31402
  }
31375
31403
  const reviewable = videoPathFromOutput(result.output);
31376
- const review = reviewable ? await reviewRenderedVideo({ video: reviewable }) : null;
31404
+ const review = reviewable ? await reviewRenderedVideo({ video: reviewable, windows: await sceneWindowsBeside(filePath) }) : null;
31377
31405
  const hints = reviewable ? review === null ? [RENDER_REVIEW_UNAVAILABLE] : review.length === 0 ? ["Reviewed the finished video frame by frame \u2014 nothing to report."] : [
31378
31406
  `Reviewed the finished video and found ${review.length} thing${review.length === 1 ? "" : "s"} to fix. Show the user what is wrong before you show them the ad.`,
31379
- ...review.map((f) => `${f.severity === "blocking" ? "MUST FIX" : "SHOULD FIX"} \u2014 ${f.what}`)
31407
+ ...review.map((f) => `${f.severity === "blocking" ? "MUST FIX" : "SHOULD FIX"} \u2014 ${f.what}`),
31408
+ ...review.some((f) => f.scene) ? [
31409
+ `Fix them, then re-run: edit the \`show\` of the scene files named above \u2014 a shot with a person handling equipment is where limbs and objects go wrong, so widen it, move the hands out of frame, or drop the person from that beat. Re-running re-renders ONLY the scenes you edited; everything else comes from the cache (this run: ${result.stats?.cached_nodes ?? 0} of ${result.stats?.total_nodes ?? 0} nodes were cached).`
31410
+ ] : []
31380
31411
  ] : [];
31381
31412
  process.stdout.write(
31382
31413
  `${JSON.stringify(
@@ -31921,7 +31952,7 @@ function isValidScaffoldSlug(slug) {
31921
31952
  }
31922
31953
 
31923
31954
  // src/commands/canvas/sync-definition.ts
31924
- import { readdir as readdir6, readFile as readFile13, stat as stat3 } from "fs/promises";
31955
+ import { readdir as readdir7, readFile as readFile13, stat as stat3 } from "fs/promises";
31925
31956
  import path20 from "path";
31926
31957
  import { defineCommand as defineCommand102 } from "citty";
31927
31958
 
@@ -32027,7 +32058,7 @@ async function resolveCanvasPath(inputPath) {
32027
32058
  }
32028
32059
  let entries;
32029
32060
  try {
32030
- entries = await readdir6(dir);
32061
+ entries = await readdir7(dir);
32031
32062
  } catch {
32032
32063
  return null;
32033
32064
  }
@@ -32692,7 +32723,7 @@ function adSpecToBlueprint(spec) {
32692
32723
  const market = marketFor(spec);
32693
32724
  const place = market ? ` Set in ${market}: the architecture, streets and styling are ${market}'s.` : "";
32694
32725
  const currency = market ? CURRENCY_BY_MARKET[market.toLowerCase()] : void 0;
32695
- const physics = " Everything obeys real-world physics: paper, card and screens are OPAQUE with nothing showing through from behind, every object is at believable real-world scale next to the people handling it, and every object has its real-world form and construction \u2014 a phone has ONE screen and it is on the front. NO readable text or numbers anywhere in frame \u2014 phone screens, documents and signage stay illegible or out of focus, because any figure the model invents will contradict the script." + (currency ? ` If a currency is unavoidably visible it is ${currency}.` : "");
32726
+ const physics = " Everything obeys real-world physics: paper, card and screens are OPAQUE with nothing showing through from behind, every object is at believable real-world scale next to the people handling it, and every object has its real-world form and construction \u2014 a phone has ONE screen and it is on the front. NO readable text or numbers anywhere in frame \u2014 phone screens, documents and signage stay illegible or out of focus, because any figure the model invents will contradict the script. Hands are kept simple: no close-up of fingers manipulating small parts, no hand gripping the edge of an object, and each person has exactly TWO arms and TWO legs, all attached and all visible or all out of frame. Anyone working does so the way the trade actually does it: nobody stands or kneels on the equipment being installed, nothing is fitted overhanging an edge or floating unsupported, and every part rests on the structure that would really carry it." + (currency ? ` If a currency is unavoidably visible it is ${currency}.` : "");
32696
32727
  const closesOnCard = endCardWanted(spec);
32697
32728
  let clock = 0;
32698
32729
  const scenes = spec.beats.map((beat, i) => {
@@ -32877,7 +32908,7 @@ function injectAdBrandOverlay(indexHtml, overlayHtml) {
32877
32908
  }
32878
32909
 
32879
32910
  // src/engine/scaffold/ad-brand-source.ts
32880
- import { readdir as readdir7, readFile as readFile15 } from "fs/promises";
32911
+ import { readdir as readdir8, readFile as readFile15 } from "fs/promises";
32881
32912
  import path22 from "path";
32882
32913
  var BRAND_DIR = "src/brand";
32883
32914
  function paletteFromBrandDoc(markdown) {
@@ -32917,7 +32948,7 @@ async function readBrandFromWorkspace(root = ".") {
32917
32948
  if (palette.length > 0) found.palette = palette;
32918
32949
  }
32919
32950
  const dir = path22.join(root, BRAND_DIR, "logos");
32920
- const files = await readdir7(dir).catch(() => null);
32951
+ const files = await readdir8(dir).catch(() => null);
32921
32952
  if (files) {
32922
32953
  const mark = pickBrandMark(files);
32923
32954
  if (mark) found.logo = path22.join(BRAND_DIR, "logos", mark);
@@ -33111,6 +33142,12 @@ var scaffoldAdCommand = defineCommand104({
33111
33142
  const html = await readFile16(indexPath, "utf-8");
33112
33143
  await writeFile9(indexPath, injectAdBrandOverlay(html, overlayHtml), "utf-8");
33113
33144
  }
33145
+ await writeFile9(
33146
+ path24.join(outDir, REBUILD_FILE),
33147
+ `${JSON.stringify({ elements: adSpecCastElements(spec.data, avatar), opts }, null, 2)}
33148
+ `,
33149
+ "utf8"
33150
+ );
33114
33151
  await writeFile9(outPath, `${JSON.stringify(canvas, null, 2)}
33115
33152
  `, "utf-8");
33116
33153
  writeJson({
@@ -33199,7 +33236,7 @@ function routeVideoModel(input) {
33199
33236
 
33200
33237
  // src/engine/nodes/local/lib/sceneDetect.ts
33201
33238
  import { execFile as execFile3 } from "child_process";
33202
- import { mkdtemp as mkdtemp2, readdir as readdir8, readFile as readFile17, rm as rm6 } from "fs/promises";
33239
+ import { mkdtemp as mkdtemp2, readdir as readdir9, readFile as readFile17, rm as rm6 } from "fs/promises";
33203
33240
  import { tmpdir as tmpdir2 } from "os";
33204
33241
  import { join as join2 } from "path";
33205
33242
  import { promisify as promisify3 } from "util";
@@ -33273,7 +33310,7 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
33273
33310
  ],
33274
33311
  { encoding: "utf-8", maxBuffer: 32 * 1024 * 1024, timeout: timeoutMs }
33275
33312
  );
33276
- const csvName = (await readdir8(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
33313
+ const csvName = (await readdir9(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
33277
33314
  if (!csvName) return [];
33278
33315
  return parsePySceneDetectCsvCuts(await readFile17(join2(outDir, csvName), "utf-8"));
33279
33316
  } finally {
@@ -42332,7 +42369,7 @@ Full guide: __tooling__/docs/tools/baker/images.md`
42332
42369
  import { defineCommand as defineCommand167 } from "citty";
42333
42370
 
42334
42371
  // src/commands/landing/critique.ts
42335
- import { readdir as readdir10, stat as stat6 } from "fs/promises";
42372
+ import { readdir as readdir11, stat as stat6 } from "fs/promises";
42336
42373
  import path31 from "path";
42337
42374
  import { defineCommand as defineCommand157 } from "citty";
42338
42375
 
@@ -43319,7 +43356,7 @@ async function writeCritiqueSnapshot(projectRoot, snapshot) {
43319
43356
  }
43320
43357
 
43321
43358
  // src/commands/landing/source-version.ts
43322
- import { readdir as readdir9, readFile as readFile24, stat as stat5 } from "fs/promises";
43359
+ import { readdir as readdir10, readFile as readFile24, stat as stat5 } from "fs/promises";
43323
43360
  import path30 from "path";
43324
43361
  async function landingSourceRelPaths(landingDir) {
43325
43362
  const rel = [];
@@ -43360,7 +43397,7 @@ async function isFile(p) {
43360
43397
  async function walkAstro(dir) {
43361
43398
  let entries;
43362
43399
  try {
43363
- entries = await readdir9(dir, { withFileTypes: true });
43400
+ entries = await readdir10(dir, { withFileTypes: true });
43364
43401
  } catch {
43365
43402
  return [];
43366
43403
  }
@@ -43486,7 +43523,7 @@ async function critiqueOne(projectRoot, slug, brand) {
43486
43523
  }
43487
43524
  async function listLandingSlugs(projectRoot) {
43488
43525
  try {
43489
- const entries = await readdir10(path31.join(projectRoot, "src", "pages"), { withFileTypes: true });
43526
+ const entries = await readdir11(path31.join(projectRoot, "src", "pages"), { withFileTypes: true });
43490
43527
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
43491
43528
  } catch {
43492
43529
  return [];