@koda-sl/baker-cli 0.265.2-dev.1f1c09c80 → 0.266.0-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
@@ -3641,9 +3641,11 @@ because a video model garbles a wordmark every time:
3641
3641
  becomes a flat brand colour plate (rendered by ffmpeg, not generated), with the mark and
3642
3642
  the call to action over it. `end_card: false` keeps the footage; `end_card: { cta }` sets
3643
3643
  the words on the button, which otherwise default to the last line of the script.
3644
- - **`cast`** — one description of the person the ad is about. The engine builds a reference
3645
- sheet from it and grounds every beat they appear in on that one identity. A beat sets
3646
- `cast: false` to opt out (a fitter on a roof, a product close-up).
3644
+ - **`cast.avatar`** — the handle of a cast Avatar (`baker avatars list`). The ad grounds
3645
+ every beat they appear in on that avatar's identity sheet and copies its subject
3646
+ description verbatim, so the ad shows the same face as the rest of the company's work.
3647
+ This is the whole journey: `baker avatars create` → `cast.avatar` in the spec → render.
3648
+ - **`cast.description`** — a person described in words, when there is no avatar.
3647
3649
 
3648
3650
  Each of these also removes a generation: the closing beat costs no image and no clip.
3649
3651
 
package/dist/cli.js CHANGED
@@ -27314,6 +27314,12 @@ var RecurringElement = z27.object({
27314
27314
  // pink shirt and believer in a white shirt). Each look gets its own reference
27315
27315
  // slot, but the face/identity must stay identical across them.
27316
27316
  same_as: z27.string().nullable().optional(),
27317
+ /**
27318
+ * A picture of this element that already exists — a cast Avatar's identity sheet,
27319
+ * a product photo, a real location shot. Given, the slot ingests it; absent, the
27320
+ * slot emits a TODO for somebody to drop one by hand.
27321
+ */
27322
+ source_image: z27.string().optional(),
27317
27323
  // Scenes the element appears in. Either a bare list of scene indices (both
27318
27324
  // edges) or per-{scene,edge} entries. Both forms are accepted and merged.
27319
27325
  scenes: z27.array(z27.number()).optional(),
@@ -29596,10 +29602,16 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
29596
29602
  const slots = buildElementSlots(elements);
29597
29603
  extendPresenceByPromptMentions(slots, blueprint);
29598
29604
  slots.forEach((slot, i) => {
29605
+ const element = elements[i];
29606
+ const supplied = typeof element?.source_image === "string" ? element.source_image.trim() : "";
29599
29607
  nodes.push({
29600
29608
  id: slot.id,
29601
29609
  type: "ingest",
29602
- params: { source: "path", path: todoPath(elements[i], slot.label), expect: "image" }
29610
+ params: {
29611
+ source: "path",
29612
+ path: supplied || todoPath(element, slot.label),
29613
+ expect: "image"
29614
+ }
29603
29615
  });
29604
29616
  });
29605
29617
  buildElementSheets(slots, nodes);
@@ -32483,8 +32495,20 @@ var AdSpec = z30.object({
32483
32495
  * collage rather than a story.
32484
32496
  */
32485
32497
  cast: z30.object({
32498
+ /**
32499
+ * A cast Avatar's handle — `baker avatars list` shows who there is.
32500
+ *
32501
+ * The right way to put a person in an ad. The avatar already has a settled
32502
+ * identity and a rendered identity sheet, so the ad grounds on the same face the
32503
+ * rest of the company's work uses, and its subject description is copied verbatim
32504
+ * rather than re-written per spec — re-phrasing it per render is the other reason
32505
+ * a face drifts across a set.
32506
+ */
32507
+ avatar: z30.string().min(1).optional(),
32486
32508
  /** Who they are, plainly: age, build, hair, wardrobe, the register of the ad. */
32487
- description: z30.string().min(1)
32509
+ description: z30.string().min(1).optional()
32510
+ }).refine((c) => Boolean(c.avatar || c.description), {
32511
+ message: "give `avatar` (a cast avatar's handle) or `description` \u2014 a cast with neither grounds on nothing"
32488
32512
  }).optional(),
32489
32513
  /**
32490
32514
  * A bed, described in words. Omitted leaves the ad dry — the engine only wires
@@ -32565,13 +32589,14 @@ function endCardCta(spec) {
32565
32589
  const declared = spec.end_card === false ? void 0 : spec.end_card?.cta;
32566
32590
  return declared?.trim() || (spec.beats[spec.beats.length - 1]?.say ?? "").trim() || null;
32567
32591
  }
32568
- function adSpecCastElements(spec) {
32569
- const description = spec.cast?.description?.trim();
32592
+ function adSpecCastElements(spec, avatar) {
32593
+ const description = (avatar?.subjectDescription ?? spec.cast?.description)?.trim();
32570
32594
  if (!description) return [];
32571
32595
  const lastIndex = spec.beats.length - 1;
32572
32596
  const scenes = spec.beats.map((beat, i) => ({ beat, i })).filter(({ beat, i }) => beat.cast !== false && !(endCardWanted(spec) && i === lastIndex)).map(({ i }) => i);
32573
32597
  if (scenes.length === 0) return [];
32574
- return [{ type: "person", label: "customer", description, scenes }];
32598
+ const sheet = avatar?.sheetUrl?.trim();
32599
+ return [{ type: "person", label: "customer", description, scenes, ...sheet ? { source_image: sheet } : {} }];
32575
32600
  }
32576
32601
  function adSpecToBlueprint(spec) {
32577
32602
  const total = spec.beats.length;
@@ -32824,6 +32849,26 @@ var scaffoldAdCommand = defineCommand104({
32824
32849
  process.exit(1);
32825
32850
  return;
32826
32851
  }
32852
+ const handle = spec.data.cast?.avatar?.trim();
32853
+ let avatar = null;
32854
+ let avatarError = null;
32855
+ if (handle) {
32856
+ try {
32857
+ avatar = await readAvatars(
32858
+ "/api/avatars/get",
32859
+ { handle }
32860
+ );
32861
+ if (avatar?.status && avatar.status !== "ready") {
32862
+ avatarError = `Avatar \`${handle}\` is ${avatar.status}, not ready \u2014 only a ready avatar can be cast. Check \`baker avatars get ${handle}\`.`;
32863
+ avatar = null;
32864
+ } else if (!avatar?.sheetUrl) {
32865
+ avatarError = `Avatar \`${handle}\` has no identity sheet yet, so the ad cannot ground on their face.`;
32866
+ avatar = null;
32867
+ }
32868
+ } catch (e) {
32869
+ avatarError = `Could not read avatar \`${handle}\`: ${e instanceof Error ? e.message : String(e)}. \`baker avatars list\` shows who there is.`;
32870
+ }
32871
+ }
32827
32872
  const blueprint = adSpecToBlueprint(spec.data);
32828
32873
  const slug = args.slug ?? path23.basename(specPath).replace(/\.[^.]+$/, "");
32829
32874
  const outPath = args.out ?? (args.slug ? path23.join("src/creatives", slug, `${slug}.canvas.json`) : path23.join(path23.dirname(specPath), `${slug}.canvas.json`));
@@ -32853,7 +32898,7 @@ var scaffoldAdCommand = defineCommand104({
32853
32898
  // 8 is the composition's own maximum and clears every beat measured here.
32854
32899
  captionWordsPerGroup: 8
32855
32900
  };
32856
- const canvas = scaffoldVideoCanvas(blueprint, adSpecCastElements(spec.data), opts);
32901
+ const canvas = scaffoldVideoCanvas(blueprint, adSpecCastElements(spec.data, avatar), opts);
32857
32902
  const renderNode3 = canvas.nodes.find((n) => n.type === "hyperframe_render");
32858
32903
  const renderedDir = renderNode3 ? path23.join(outDir, String(renderNode3.params?.composition ?? "")) : null;
32859
32904
  let staged = null;
@@ -32890,6 +32935,13 @@ var scaffoldAdCommand = defineCommand104({
32890
32935
  ...logoPath && !staged ? [`Could not read the logo at \`${logoPath}\` \u2014 the ad has no mark on screen. Give the path as it appears in src/brand/BRAND.md, relative to the workspace root.`] : [],
32891
32936
  ...staged ? ["The brand mark is drawn by the composition \u2014 top-left throughout, and large on the closing card. It is never generated as a picture, so it cannot come back garbled."] : [],
32892
32937
  ...endCardWanted(spec.data) ? ["The last beat closes on a flat brand plate with the call to action over it, instead of one more photograph."] : [],
32938
+ ...avatarError ? [avatarError] : [],
32939
+ ...avatar ? [
32940
+ `Every beat the customer appears in is grounded on \`${handle}\`'s identity sheet, and their subject description was copied into the frames verbatim \u2014 so it is the same face throughout and the same face as the rest of this company's work.`
32941
+ ] : [],
32942
+ ...spec.data.cast && !avatar ? [
32943
+ "This cast has a description but no avatar, so the canvas asks you to drop a real source photo before it can run. `baker avatars list` \u2014 casting one and passing `cast.avatar` fills that slot for you and keeps the face consistent across every piece of work."
32944
+ ] : [],
32893
32945
  "The caption cards are your `say` lines verbatim \u2014 read them back before running; that is the copy the viewer sees.",
32894
32946
  "Every clip renders with nobody speaking unless a beat set `on_camera`. If a beat needs a presenter talking, set it there rather than describing it in `show`.",
32895
32947
  ...spec.data.brand?.palette?.length && spec.data.brand?.logo ? [] : [