@koda-sl/baker-cli 0.382.0-dev.a410b0488 → 0.383.0-dev.2caa8a15e

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
@@ -892,7 +892,7 @@ baker ads meta insights --object act_123 --level adset \
892
892
  baker ads meta activities --account-id act_123 --days 7
893
893
 
894
894
  # "Which Page should this ad run from?"
895
- baker ads meta pages # Facebook Pages the user manages (tasks shows which can ADVERTISE)
895
+ baker ads meta pages # Facebook Pages the selected ad accounts can promote
896
896
 
897
897
  # "What is the lead form collecting, and how many came in?"
898
898
  baker ads meta lead-forms --page-id 100635183117758 # instant forms on a Page + lead counts
@@ -5474,7 +5474,7 @@ Before the deconstruct it runs a **local shot-cut pass** on the source file with
5474
5474
 
5475
5475
  A shot longer than the video model's per-clip ceiling (Seedance's 15s, passed as `video_deconstruct`'s `max_clip_s`) is split into equal **continuation sub-scenes** that share their splice boundary exactly — so a long shot is reproduced in **full** (no truncation) and joins seamlessly. Each sub-scene carries `continues_previous`.
5476
5476
 
5477
- It then scaffolds the full pipeline like an **editing timeline**: each clip gets a **static-ad-grade start AND end keyframe** (`image_generate`, each with its **own `params.prompt`** regenerated from that scene's `scenes/sNN.json` — edit the scene file to change that frame, never the node prompt by hand; the slim `prompt.style.json` wired as the **shared `target_blueprint`** style reference, plus a per-element reference legend). Each keyframe is **fully recast** to the dropped `el_*` reference images. The original extracted frame is kept LAST as a **pure composition anchor** (framing / camera angle / shot size / pose) only in an ad with **nobody in it** — no deconstructed cast and no recurring person. In any ad with people, no generated frame takes a source frame as a reference: an image model copies the face of whoever is in its references, whatever the prompt says, and which scenes a person is in is itself a model's guess — so framing is carried by the frame prompt instead. **People are cast from company avatars** (`--avatar <handle>`): each recurring person grounds on that avatar's identity sheet, used as-is (no sheet is built from it); a person with no avatar gets a `[TODO]` asking for one, never for a found photo. Both keyframes feed `video_generate` (`first_frame`+`last_frame`, so Seedance interpolates real in-shot motion; ultra-detailed motion brief; duration snapped to the nearest allowed clip length). Every keyframe grounds **only on its own extracted frame + `el_*` slots** — no reference to any other generated frame — so all images render **in parallel** (no cascade). Source-frame URLs are **deduped** (each ingested once). `--frames reuse` wires the real source frame straight in.
5477
+ It then scaffolds the full pipeline like an **editing timeline**: each clip gets a **static-ad-grade start AND end keyframe** (`image_generate`, each with its **own `params.prompt`** regenerated from that scene's `scenes/sNN.json` — edit the scene file to change that frame, never the node prompt by hand; the slim `prompt.style.json` wired as the **shared `target_blueprint`** style reference, plus a per-element reference legend). Each keyframe is **fully recast** to the dropped `el_*` reference images. The original extracted frame is kept LAST as a **pure composition anchor** (framing / camera angle / shot size / pose) only in an ad with **nobody in it** — no deconstructed cast and no recurring person. In any ad with people, no generated frame takes a source frame as a reference: an image model copies the face of whoever is in its references, whatever the prompt says, and which scenes a person is in is itself a model's guess — so framing is carried by the frame prompt instead. **People are cast from company avatars** (`--avatar <handle>`): each recurring person grounds on that avatar's identity sheet, used as-is (no sheet is built from it); a person with no avatar gets a `[TODO]` asking for one, never for a found photo. Both keyframes feed `video_generate` (`first_frame`+`last_frame`, so Seedance interpolates real in-shot motion; ultra-detailed motion brief; duration snapped to the nearest allowed clip length). Every keyframe grounds **only on its own extracted frame + `el_*` slots** — no reference to any other generated frame — so all images render **in parallel** (no cascade). Source-frame URLs are **deduped** (each ingested once). `--frames reuse` wires the real source frame straight in — only on a video with nobody in it: when the deconstruct found a cast or a recurring person, reuse is refused (`frames_reuse_refused`) and every frame is generated, so the source's actors never reach the ad.
5478
5478
 
5479
5479
  **Composited scenes (split-screen / picture-in-picture / keyed presenter).** Real ads aren't always one full-frame shot — a frame can be **persistently divided** (b-roll on top, a presenter talking on the bottom) or **layer a presenter** over background footage (boxed in a corner, or green-screen keyed). The deconstruct now reports this per scene as `scene.composition` (`layout: split_screen | pip | keyed_overlay`, with one `region` per stream — each its own clean-plate frame + motion brief, the talking-head region flagged `is_presenter`). The scaffold reproduces a composited scene by building **one clip per region** (`s<i>_r0_*`, `s<i>_r1_*`, …) and compositing them with ffmpeg: a split-screen `vstack`/`hstack` (stack direction read from the region **panels**, so a top/bottom split always stacks vertically), or a picture-in-picture `overlay` of the presenter inset at its corner. A **keyed** presenter is first cut to transparency by `video_background_remove` (`s<i>_key`), then overlaid. The presenter region carries the native lip-synced voice; b-roll/render panels stay silent. To change a layout, edit `composition` in that scene's `scenes/sNN.json` and re-scaffold, or hand-edit the `s<i>_composite` ffmpeg args. Plain full-frame scenes (the default) are unaffected.
5480
5480
 
@@ -5528,7 +5528,7 @@ baker canvas run ./reference-ad.video.canvas.json
5528
5528
  |---|---|---|
5529
5529
  | `--out <path>` | `<video-dir>/<name>.video.canvas.json` | Where to write the canvas (composition is copied alongside). |
5530
5530
  | `--slug <slug>` | — | Creative slug (lowercase kebab): writes the canvas to `src/creatives/<slug>/<slug>.canvas.json` — the repo convention that attaches every run to the creative's dashboard generation history. `--out` wins over `--slug`. |
5531
- | `--frames <mode>` | `generate` | `generate` emits ONE recast keyframe per scene (the original frame is dropped so the dropped `el_*` assets drive identity); `reuse` wires the real extracted first+last frames straight into the clips (faithful, cheaper, no recast). |
5531
+ | `--frames <mode>` | `generate` | `generate` emits ONE recast keyframe per scene (the original frame is dropped so the dropped `el_*` assets drive identity); `reuse` wires the real extracted first+last frames straight into the clips (faithful, cheaper, no recast) — only on a video with nobody in it; on one showing people it is refused and every frame is generated. |
5532
5532
  | `--ambient` | off | Give silent **b-roll** scenes native diegetic ambient (Seedance `generate_audio`), mixed deep under the music bed. Talking scenes already carry voice; check levels don't muddy the mix before keeping it. |
5533
5533
  | `--seam-dedup <mode>` | `head` | How to dedup the boundary frame two clips SHARE when a long shot is split for length (the second clip's first frame IS the first clip's last frame, so a plain concat freezes on it for a frame). `head` drops the second clip's first frame, `tail` drops the first clip's last frame, `off` keeps both. Only touches shared-frame continuation joins — a hard cut between two shots shares no frame. |
5534
5534
  | `--max-scenes <n>` | all source scenes | **Cost lever that reduces fidelity** — caps the deconstruct, MERGING away every scene beyond the cap (fewer cuts, lost beats). Prints a warning when set; omit it to reproduce every scene. |
package/dist/cli.js CHANGED
@@ -299,6 +299,7 @@ var chatActionDraftOpViewSchema = z.object({
299
299
  targetActionId: z.string().optional(),
300
300
  tempId: z.string().optional(),
301
301
  tags: z.array(z.string()).optional(),
302
+ priority: actionPrioritySchema.nullable().optional(),
302
303
  reason: z.string().optional(),
303
304
  note: z.string().optional(),
304
305
  name: z.string().optional(),
@@ -28397,19 +28398,10 @@ var metaMediaCommand = defineCommand68({
28397
28398
 
28398
28399
  // src/commands/ads/meta/pages.ts
28399
28400
  import { defineCommand as defineCommand69 } from "citty";
28400
- function advertisablePages(data) {
28401
- return data.filter((p) => Array.isArray(p.tasks) && p.tasks.includes("ADVERTISE"));
28402
- }
28403
28401
  function pageSelectionHints(data) {
28404
- const advertisable = advertisablePages(data);
28405
- if (advertisable.length > 1) {
28402
+ if (data.length > 1) {
28406
28403
  return [
28407
- "MULTIPLE Pages can run ads \u2014 do NOT guess. Confirm with the user which Page an ad should run from before passing --page to `creatives create`; a wrong Page publishes the ad under the wrong brand identity and staging will not catch it."
28408
- ];
28409
- }
28410
- if (data.length > 0 && advertisable.length === 0) {
28411
- return [
28412
- "NO Page can run ads \u2014 none of the connected user's Pages carry the ADVERTISE task. Ask the user to grant Advertise access to the right Page before creating an ad; a readable-but-non-advertising Page still passes staging and would fail or run under the wrong identity."
28404
+ "MULTIPLE Pages can run ads from the selected Meta ad accounts \u2014 do NOT guess. Confirm with the user which Page an ad should run from before passing --page to `creatives create`; a wrong Page publishes the ad under the wrong brand identity and staging will not catch it."
28413
28405
  ];
28414
28406
  }
28415
28407
  return [];
@@ -28417,7 +28409,7 @@ function pageSelectionHints(data) {
28417
28409
  var pagesCommand = defineCommand69({
28418
28410
  meta: {
28419
28411
  name: "pages",
28420
- description: `List the Facebook Pages the connected user manages. Every Meta ad publishes from a Page, so use this to identify the Page an ad should run from. The 'tasks' field shows what the user can do on each Page (ADVERTISE means it can back ads). If exactly one Page can ADVERTISE, use it; if more than one can, confirm with the user which Page to use; if none can, ask the user to grant Advertise access \u2014 a wrong or non-advertising Page publishes under the wrong brand identity.
28412
+ description: `List the Facebook Pages that the selected Meta ad accounts can promote. Every Meta ad publishes from a Page, so use this to identify the Page an ad should run from. If more than one Page is listed, confirm with the user which Page to use \u2014 a wrong Page publishes under the wrong brand identity.
28421
28413
 
28422
28414
  Examples:
28423
28415
  baker ads meta pages`
@@ -28678,7 +28670,7 @@ Discovery:
28678
28670
  baker ads meta accounts \u2014 accounts in this company's connected scope
28679
28671
  baker ads meta accounts --include-all \u2014 every account the token can see
28680
28672
  baker ads meta businesses \u2014 Business Manager IDs
28681
- baker ads meta pages \u2014 Facebook Pages the user manages (an ad's identity)
28673
+ baker ads meta pages \u2014 Facebook Pages the selected ad accounts can promote (an ad's identity)
28682
28674
  baker ads meta lead-forms --page-id 100635183117758 \u2014 instant (lead) forms on a Page, with lead counts
28683
28675
  baker ads meta leads --form-id 987654321 \u2014 the leads one form collected (contact details masked)
28684
28676
 
@@ -40790,10 +40782,18 @@ function dropUncastVoices(nodes) {
40790
40782
  if (node && node.type === "voice_select" && !referenced.has(node.id)) nodes.splice(i, 1);
40791
40783
  }
40792
40784
  }
40793
- function scaffoldVideoCanvas(input, elementsInput, opts) {
40785
+ function framesReuseRefusal(blueprint, elements, requested) {
40786
+ if (requested !== "reuse") return void 0;
40787
+ const showsPeople = (blueprint.global?.cast ?? []).length > 0 || elements.some((e) => e.type.toLowerCase() === "person");
40788
+ if (!showsPeople) return void 0;
40789
+ return "--frames reuse was not applied: this video shows people, and reusing its frames would put the source's actors into our ad. Every frame is generated instead, recast to the el_* references. Reuse applies only to a video with nobody in it.";
40790
+ }
40791
+ function scaffoldVideoCanvas(input, elementsInput, requested) {
40794
40792
  const blueprint = VideoBlueprint.parse(input);
40795
40793
  injectHookPhysicality(blueprint);
40796
40794
  const elements = RecurringElements.parse(elementsInput);
40795
+ const reuseRefused = framesReuseRefusal(blueprint, elements, requested.frames);
40796
+ const opts = reuseRefused ? { ...requested, frames: "generate" } : requested;
40797
40797
  const clock = spineClockFor(blueprint);
40798
40798
  const nodes = [];
40799
40799
  emitBlueprintIngests(opts, nodes);
@@ -40896,6 +40896,7 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
40896
40896
  }
40897
40897
  const todo = {
40898
40898
  ...buildVideoTodo(videoReport(input, elementsInput), overlays.length, floating.length, opts, blueprint),
40899
+ ...reuseRefused ? { frames_reuse_refused: reuseRefused } : {},
40899
40900
  ...aspectRemapTodo(resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel))),
40900
40901
  model_constraints: buildModelConstraints()
40901
40902
  };
@@ -42021,12 +42022,6 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
42021
42022
  return { canvasPath: path14.resolve(resolvedTarget, manifest.canvas.path), restored, upToDate };
42022
42023
  }
42023
42024
 
42024
- // src/commands/canvas/unchanged-run.ts
42025
- function unchangedRunHint(runId, stats) {
42026
- if (stats.total_nodes === 0 || stats.cached_nodes < stats.total_nodes) return null;
42027
- return `Nothing changed since the last render: every step came from the cache, so this run is no new version. To read what a render produced, use \`baker canvas inspect ${runId}\` instead of re-running; re-run a canvas only after editing it or the files it reads.`;
42028
- }
42029
-
42030
42025
  // src/engine/nodes/local/lib/render-review.ts
42031
42026
  import { execFile as execFile2 } from "child_process";
42032
42027
  import { mkdtemp, readdir as readdir6, readFile as readFile11, rm as rm5 } from "fs/promises";
@@ -42910,8 +42905,6 @@ ${describeRewrites(healed.rewrites)}
42910
42905
  `Anything about the mark or the closing card is a composition fix, not a re-render: both are drawn as HTML by the caption composition, so editing it and re-running re-renders only that pass \u2014 the clips are cached and no generation is paid for again. 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).`
42911
42906
  ] : []
42912
42907
  ] : [];
42913
- const unchanged = result.stats ? unchangedRunHint(result.run_id, result.stats) : null;
42914
- if (unchanged) hints2.push(unchanged);
42915
42908
  if (poster) {
42916
42909
  await poster.flush(
42917
42910
  // `review` is null only when nobody looked (no key, no ffmpeg, unreadable video);
@@ -45830,6 +45823,12 @@ function videoDefinitionDescription(blueprint) {
45830
45823
  const product = g.branding?.product;
45831
45824
  return typeof product === "string" && product.trim() ? product.trim() : void 0;
45832
45825
  }
45826
+ function framesModeReport(canvas, requested) {
45827
+ const todo = canvas.metadata?.todo;
45828
+ const mode = typeof todo?.frames_mode === "string" ? todo.frames_mode : requested;
45829
+ const refused = typeof todo?.frames_reuse_refused === "string" ? todo.frames_reuse_refused : void 0;
45830
+ return { frames_mode: mode, ...refused ? { frames_reuse_refused: refused } : {} };
45831
+ }
45833
45832
  async function materializeReferenceVideo(fileArg2) {
45834
45833
  if (!/^https?:\/\//i.test(fileArg2)) return path26.resolve(fileArg2);
45835
45834
  let bytes;
@@ -45987,7 +45986,10 @@ var scaffoldVideoCommand = defineCommand116({
45987
45986
  type: "string",
45988
45987
  description: "Creative slug \u2014 writes the canvas to src/creatives/<slug>/<slug>.canvas.json (repo convention)"
45989
45988
  },
45990
- frames: { type: "string", description: '"generate" (default, anchored regen) or "reuse" (wire real frames in)' },
45989
+ frames: {
45990
+ type: "string",
45991
+ description: `"generate" (default, every frame regenerated for our ad) or "reuse" (wire the source's real frames in). Reuse applies only to a video with nobody in it \u2014 on a video showing people it is refused and every frame is generated, so the source's actors never reach our ad.`
45992
+ },
45991
45993
  ambient: {
45992
45994
  type: "boolean",
45993
45995
  description: "Give silent b-roll scenes native diegetic ambient mixed deep under the music bed (off by default)"
@@ -46190,6 +46192,7 @@ var scaffoldVideoCommand = defineCommand116({
46190
46192
  error: { code: "validation", issues: validation.issues },
46191
46193
  canvas_path: outPath,
46192
46194
  prompt_path: blueprintPath,
46195
+ ...framesModeReport(canvas, frames),
46193
46196
  note: "The canvas WAS written despite the blocking issue(s) \u2014 fix them in place, then `baker canvas validate` before running.",
46194
46197
  ...uncastHint
46195
46198
  },
@@ -46241,7 +46244,7 @@ var scaffoldVideoCommand = defineCommand116({
46241
46244
  composition_dir: compositionDest,
46242
46245
  output: canvas.output,
46243
46246
  ...uncastHint,
46244
- frames_mode: frames,
46247
+ ...framesModeReport(canvas, frames),
46245
46248
  models: {
46246
46249
  deconstruct: deconstructModel,
46247
46250
  select: selectModel,