@koda-sl/baker-cli 0.180.0 → 0.181.1
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 +55 -3
- package/dist/{chunk-P2T3IZRE.js → chunk-RWHEFQXI.js} +172 -20
- package/dist/chunk-RWHEFQXI.js.map +1 -0
- package/dist/cli.js +57 -23
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-P2T3IZRE.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
isPersistedAssetRef,
|
|
26
26
|
looksLikeHttpUrl,
|
|
27
27
|
nearestSupportedAspectRatio,
|
|
28
|
+
nearestSupportedImageSize,
|
|
28
29
|
parseRefExpr,
|
|
29
30
|
platformFormats,
|
|
30
31
|
requireCredentialsFromEnv,
|
|
@@ -32,11 +33,12 @@ import {
|
|
|
32
33
|
sha256Hex,
|
|
33
34
|
spineInputFlags,
|
|
34
35
|
spineInputOps,
|
|
36
|
+
supportsLastFrame,
|
|
35
37
|
supportsParam,
|
|
36
38
|
toModelSafeImage,
|
|
37
39
|
ulid,
|
|
38
40
|
validateCanvasDeep
|
|
39
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-RWHEFQXI.js";
|
|
40
42
|
import {
|
|
41
43
|
csvOrJson,
|
|
42
44
|
daysAgoIso,
|
|
@@ -3412,6 +3414,7 @@ var imageGenerateModelSchema = z11.enum([
|
|
|
3412
3414
|
// Legacy — see the registry entry; kept so pre-switch canvases still run.
|
|
3413
3415
|
"openai/gpt-5.4-image-2",
|
|
3414
3416
|
"google/gemini-3.1-flash-image-preview",
|
|
3417
|
+
"google/gemini-3.1-flash-lite-image",
|
|
3415
3418
|
"google/gemini-3-pro-image-preview",
|
|
3416
3419
|
"recraft/recraft-v4.1-pro-vector"
|
|
3417
3420
|
]);
|
|
@@ -19770,6 +19773,20 @@ function videoResolutionParam(videoModel, resolution) {
|
|
|
19770
19773
|
if (!VIDEO_MODELS_WITH_RESOLUTION.has(videoModel)) return {};
|
|
19771
19774
|
return { resolution: resolution ?? DEFAULT_VIDEO_RESOLUTION };
|
|
19772
19775
|
}
|
|
19776
|
+
var VIDEO_MODELS_WITH_DURATION = new Set(
|
|
19777
|
+
Object.entries(MODEL_REGISTRY.video_generate).filter(([, spec]) => "duration" in spec.params).map(([id]) => id)
|
|
19778
|
+
);
|
|
19779
|
+
var VIDEO_MODELS_WITH_AUDIO_TOGGLE = new Set(
|
|
19780
|
+
Object.entries(MODEL_REGISTRY.video_generate).filter(([, spec]) => "generate_audio" in spec.params).map(([id]) => id)
|
|
19781
|
+
);
|
|
19782
|
+
function videoDurationParam(videoModel, duration) {
|
|
19783
|
+
if (!VIDEO_MODELS_WITH_DURATION.has(videoModel)) return {};
|
|
19784
|
+
return { duration };
|
|
19785
|
+
}
|
|
19786
|
+
function videoAudioParam(videoModel, generateAudio) {
|
|
19787
|
+
if (!VIDEO_MODELS_WITH_AUDIO_TOGGLE.has(videoModel)) return {};
|
|
19788
|
+
return { generate_audio: generateAudio };
|
|
19789
|
+
}
|
|
19773
19790
|
var WORDS_PER_SECOND = 2.5;
|
|
19774
19791
|
function wordCount(text) {
|
|
19775
19792
|
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
@@ -20374,6 +20391,7 @@ function extendPresenceByPromptMentions(slots, blueprint) {
|
|
|
20374
20391
|
});
|
|
20375
20392
|
}
|
|
20376
20393
|
var ACTOR_SHEET_MODEL = "google/gemini-3-pro-image-preview";
|
|
20394
|
+
var KEYFRAME_IMAGE_SIZE = "2K";
|
|
20377
20395
|
var SHEET_SUBJECT_TYPE = {
|
|
20378
20396
|
person: "person",
|
|
20379
20397
|
animal: "character",
|
|
@@ -20563,8 +20581,11 @@ function buildFrameRef(edge, url, framePrompt, present2, ctx, nodes) {
|
|
|
20563
20581
|
const genParams = {
|
|
20564
20582
|
model: ctx.imageModel,
|
|
20565
20583
|
// gpt-image-2 derives pixel dimensions from the ratio and has no size knob,
|
|
20566
|
-
// so asking for 2K there is an `unknown_param` at validate.
|
|
20567
|
-
|
|
20584
|
+
// so asking for 2K there is an `unknown_param` at validate. Models that DO take
|
|
20585
|
+
// one still differ in how far up they go — Nano Banana 2 Lite renders 1K only —
|
|
20586
|
+
// so snap to the best tier the chosen model actually offers rather than pinning
|
|
20587
|
+
// a literal that a cheaper model would reject outright.
|
|
20588
|
+
...supportsParam("image_generate", ctx.imageModel, "image_size") ? { image_size: nearestSupportedImageSize("image_generate", ctx.imageModel, KEYFRAME_IMAGE_SIZE) } : {},
|
|
20568
20589
|
// Per-model image defaults (gpt-image: quality=high — OpenRouter forwards it; we do
|
|
20569
20590
|
// NOT send input_fidelity, which gpt-image-2 forces high automatically).
|
|
20570
20591
|
...imageProfile?.paramDefaults ?? {},
|
|
@@ -20854,11 +20875,11 @@ function emitSceneClip(i, scene, present2, mode, nativeTurn, ambientBroll, frame
|
|
|
20854
20875
|
opts.uiRouted,
|
|
20855
20876
|
opts.videoModel
|
|
20856
20877
|
),
|
|
20857
|
-
|
|
20878
|
+
...videoDurationParam(opts.videoModel, lengths.genDur),
|
|
20858
20879
|
...videoResolutionParam(opts.videoModel, opts.resolution),
|
|
20859
|
-
// Native talking scene →
|
|
20880
|
+
// Native talking scene → the model generates the spoken audio + lip-sync; an opt-in
|
|
20860
20881
|
// ambient b-roll beat generates diegetic ambient only; otherwise the clip is silent.
|
|
20861
|
-
|
|
20882
|
+
...videoAudioParam(opts.videoModel, Boolean(nativeTurn) || ambientBroll),
|
|
20862
20883
|
// Intent-keyed "astonishing default" overrides — merged LAST so a hero/reveal
|
|
20863
20884
|
// beat can claim the 1080p ceiling over the aspect-derived resolution.
|
|
20864
20885
|
...clipParamRecipe(profile, clipIntentOf(scene, i))
|
|
@@ -20867,7 +20888,13 @@ function emitSceneClip(i, scene, present2, mode, nativeTurn, ambientBroll, frame
|
|
|
20867
20888
|
nodes.push({
|
|
20868
20889
|
id: `s${i}${tag}_clip`,
|
|
20869
20890
|
type: "video_generate",
|
|
20870
|
-
|
|
20891
|
+
// An end frame can exist purely as the NEXT scene's splice frame, so having one
|
|
20892
|
+
// is not permission to wire it — a model that takes a single conditioning image
|
|
20893
|
+
// silently drops it.
|
|
20894
|
+
inputs: {
|
|
20895
|
+
first_frame: frames.first,
|
|
20896
|
+
...frames.last && supportsLastFrame(opts.videoModel) ? { last_frame: frames.last } : {}
|
|
20897
|
+
},
|
|
20871
20898
|
params: clipParams
|
|
20872
20899
|
});
|
|
20873
20900
|
const base = `$ref:s${i}${tag}_clip.video`;
|
|
@@ -21010,7 +21037,7 @@ function buildCompositeScene(layout, regions, comp, scene, i, present2, mode, na
|
|
|
21010
21037
|
const startPrompt = region.frame_prompt ?? scene.start_frame_prompt;
|
|
21011
21038
|
const endPrompt = region.frame_prompt ?? scene.end_frame_prompt;
|
|
21012
21039
|
const first = buildFrameRef("start", void 0, startPrompt, regionSlots, ctx, nodes);
|
|
21013
|
-
const last = buildFrameRef("end", void 0, endPrompt, regionSlots, ctx, nodes);
|
|
21040
|
+
const last = supportsLastFrame(opts.videoModel) ? buildFrameRef("end", void 0, endPrompt, regionSlots, ctx, nodes) : void 0;
|
|
21014
21041
|
const regionNative = isPresenter ? nativeTurn : void 0;
|
|
21015
21042
|
const regionScene = {
|
|
21016
21043
|
...scene,
|
|
@@ -21578,14 +21605,16 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
21578
21605
|
);
|
|
21579
21606
|
const lastShown = phrase.shownScenes[phrase.shownScenes.length - 1] ?? anchor;
|
|
21580
21607
|
const lastScene = env.blueprint.scenes[lastShown] ?? anchorScene;
|
|
21581
|
-
const
|
|
21608
|
+
const modelTakesLastFrame = supportsLastFrame(env.opts.videoModel);
|
|
21609
|
+
const nextSplicesFromEnd = Boolean(env.blueprint.scenes[lastShown + 1]?.continues_previous);
|
|
21610
|
+
const last = modelTakesLastFrame || nextSplicesFromEnd ? buildFrameRef(
|
|
21582
21611
|
"end",
|
|
21583
21612
|
lastScene.end_frame_asset?.url,
|
|
21584
21613
|
lastScene.end_frame_prompt,
|
|
21585
21614
|
slotsForFrame(env.slots, lastShown, "end"),
|
|
21586
21615
|
ctx,
|
|
21587
21616
|
nodes
|
|
21588
|
-
);
|
|
21617
|
+
) : void 0;
|
|
21589
21618
|
const clipStart = phrase.shownScenes.reduce(
|
|
21590
21619
|
(m, s) => Math.min(m, env.blueprint.scenes[s]?.start_s ?? phrase.start_s),
|
|
21591
21620
|
phrase.start_s
|
|
@@ -21608,19 +21637,19 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
21608
21637
|
env.uiRouted.has(anchor),
|
|
21609
21638
|
env.opts.videoModel
|
|
21610
21639
|
),
|
|
21611
|
-
|
|
21640
|
+
...videoDurationParam(env.opts.videoModel, genDur),
|
|
21612
21641
|
...videoResolutionParam(env.opts.videoModel, env.opts.resolution),
|
|
21613
|
-
|
|
21642
|
+
...videoAudioParam(env.opts.videoModel, true),
|
|
21614
21643
|
...clipParamRecipe(clipProfile, clipIntentOf(anchorScene, anchor))
|
|
21615
21644
|
};
|
|
21616
21645
|
clipParams.aspect_ratio = env.genAr;
|
|
21617
21646
|
nodes.push({
|
|
21618
21647
|
id: `s${anchor}_clip`,
|
|
21619
21648
|
type: "video_generate",
|
|
21620
|
-
inputs: { first_frame: first, last_frame: last },
|
|
21649
|
+
inputs: { first_frame: first, ...last && modelTakesLastFrame ? { last_frame: last } : {} },
|
|
21621
21650
|
params: clipParams
|
|
21622
21651
|
});
|
|
21623
|
-
out.phraseEnd = { ref: last, scene: lastShown };
|
|
21652
|
+
out.phraseEnd = last ? { ref: last, scene: lastShown } : void 0;
|
|
21624
21653
|
const clipRef = `$ref:s${anchor}_clip.video`;
|
|
21625
21654
|
const speechOffset = Math.max(0, phrase.start_s - clipStart);
|
|
21626
21655
|
const extractLen = Math.min(Math.max(0.5, phrase.end_s - phrase.start_s), Math.max(0.5, genDur - speechOffset));
|
|
@@ -21828,14 +21857,15 @@ function brollKeyframes(scene, i, env, ctx, lengths, prevEndFrame, nodes) {
|
|
|
21828
21857
|
nodes
|
|
21829
21858
|
);
|
|
21830
21859
|
const nextContinues = Boolean(env.blueprint.scenes[i + 1]?.continues_previous);
|
|
21831
|
-
const
|
|
21860
|
+
const guidesMotion = supportsLastFrame(env.opts.videoModel) && lengths.trimTarget > 4;
|
|
21861
|
+
const last = guidesMotion || nextContinues ? buildFrameRef(
|
|
21832
21862
|
"end",
|
|
21833
21863
|
scene.end_frame_asset?.url,
|
|
21834
21864
|
scene.end_frame_prompt,
|
|
21835
21865
|
slotsForFrame(env.slots, i, "end"),
|
|
21836
21866
|
ctx,
|
|
21837
21867
|
nodes
|
|
21838
|
-
);
|
|
21868
|
+
) : void 0;
|
|
21839
21869
|
return { first, last, sharesPrevFrame };
|
|
21840
21870
|
}
|
|
21841
21871
|
function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
@@ -22685,7 +22715,7 @@ var VIDEO_GUIDE = [
|
|
|
22685
22715
|
"1. Edit each frame's prompt IN PLACE. Every `s<i>_start` keyframe node has its OWN self-contained `params.prompt` (the FRAME DESCRIPTION) \u2014 editing one changes only that frame. Rewrite the cast, product, claims, palette into the ad you want. The frame is RECAST to the el_* reference images you drop (the source ad's people are never reused), so describe pose/action/framing here and let the references carry identity.",
|
|
22686
22716
|
"1b. STORYBOARD FIRST \u2014 align the look on the cheap stills before clips bill. Each scene's keyframe IS your storyboard; `metadata.video.motion_board` lays out each scene's frame, time window, spoken line, and the graphics scheduled in it. Lock the keyframes + check each graphic lands on its spoken beat, THEN run the clips (images are cheap, videos aren't; the cache re-bills only what you change). See references/video-flow.md.",
|
|
22687
22717
|
"2. Drop ONE real source image at each `el_*` ingest `[TODO]` path. Each recurring element (person/product/logo) is reused across every frame it appears in, so the same identity stays consistent. `baker canvas run` REFUSES to start until every `[TODO]` slot holds a real source \u2014 so this is mandatory, not optional.",
|
|
22688
|
-
"3. Talking heads are NATIVE: a scene with one on-camera speaker is voiced by
|
|
22718
|
+
"3. Talking heads are NATIVE: a scene with one on-camera speaker is voiced by the video model itself (the line goes in the clip's prompt; models with a `generate_audio` toggle get it set, and the default model always renders audio), so lips and voice are generated together \u2014 no separate tts, no post-hoc lip-sync. Edit the line in the scene's `s<i>_clip` prompt to re-author the words TRUE for your brand. Off-camera narration scenes still use a sequenced `tts` per turn.",
|
|
22689
22719
|
"4. Voice consistency: every native talking clip's audio is re-voiced to ONE brand voice via `audio_voice_convert` (timing preserved \u2192 lips stay matched). Confirm the `voice_select` casting (one per speaker) \u2014 its `voice_id` is the brand voice; set its gender/language so the voice matches the creator.",
|
|
22690
22720
|
"5. Overlays are REAL HTML you paint. Open `video-overlay-composition/index.html`: the reference's overlays are seeded inside `#overlay-root` as plain elements (text + a `.pos-*` class + `data-start`/`data-dur`). Restyle the CSS freely \u2014 build lower-thirds, a ticker, whatever the look needs \u2014 and replace a logo placeholder with a real `<img>` you source (`baker images icon/sticker/gif/logo`) and drop in that dir. The runtime only shows/hides by timestamp; it makes no styling decisions. Drop `brand-bold.otf` / `brand-regular.otf` there for on-brand type.",
|
|
22691
22721
|
"6. `baker canvas validate` (proves native-audio + timing for free) then `baker canvas run` (generates many billed image/video/audio assets \u2014 not free).",
|
|
@@ -22762,9 +22792,9 @@ function buildVideoTodo(report, overlayCount, floatingCount, opts, blueprint) {
|
|
|
22762
22792
|
voice_description: d.voice_description,
|
|
22763
22793
|
line: d.line
|
|
22764
22794
|
})),
|
|
22765
|
-
talking_head_note: "SHOT-NATIVE: a presenter shot is ONE
|
|
22795
|
+
talking_head_note: "SHOT-NATIVE: a presenter shot is ONE video clip (its line quoted in s<anchor>_clip's prompt, plus generate_audio on the models that expose it) so lips+voice are generated together \u2014 no tts, no veed-lipsync. Two adjacent presenter shots at a hard cut are SEPARATE clips; a cutaway phrase (the presenter on camera, cut to b-roll, back on camera) stays one clip whose on-camera windows are cut inside the spine's per-input chains (no extra nodes). Edit the line in the s<anchor>_clip prompt to re-author it. A pure-voiceover phrase (speaker never shown) is one ElevenLabs tts read instead.",
|
|
22766
22796
|
voice_note: "ONE voice per person: a single voice_select is reused across all that person's shots (on-camera AND off \u2014 the deconstruct's `voiceover` label folds into the sole presenter). Every presenter clip's native audio is extracted and re-voiced to that brand voice through a SINGLE merged audio_voice_convert per speaker (<voice>_conv, eleven_multilingual_sts_v2, timing preserved so lips stay matched) \u2014 so timbre stays consistent across the separate shot clips. Set voice_select.voice_id's gender/language to match the creator.",
|
|
22767
|
-
native_timing: "Clips separate at COMPLETE BREAKS between shots, but the VOICE stays continuous where it should: a voiceover narration is ONE read across the b-roll it plays over, and a cutaway leaves the presenter's read continuous under the insert. Each clip is generated long enough for its estimated speech. `metadata.video.talking_scenes` carries each shot's scene_s vs est_speech_s. CAVEAT: a b-roll cutaway INSIDE a phrase lands at an approximate (proportional) time \u2014
|
|
22797
|
+
native_timing: "Clips separate at COMPLETE BREAKS between shots, but the VOICE stays continuous where it should: a voiceover narration is ONE read across the b-roll it plays over, and a cutaway leaves the presenter's read continuous under the insert. Each clip is generated long enough for its estimated speech. `metadata.video.talking_scenes` carries each shot's scene_s vs est_speech_s. CAVEAT: a b-roll cutaway INSIDE a phrase lands at an approximate (proportional) time \u2014 no video model exposes word timing \u2014 so if a cutaway is off its beat, nudge the scene boundary (it's a starting point). NOTE: a native clip's voice is extracted for the SPOKEN window only (`s<i>_voextract`'s `-t`) \u2014 when the scene runs longer than the line, the picture tail is deliberately silent (extracting the full clip would put post-line breathing/babble on the voice bus); trim the scene or extend the line if the dead air reads wrong.",
|
|
22768
22798
|
craft: {
|
|
22769
22799
|
note: "Production-craft principles that raise every clip's realism. Full rationale: references/video-craft.md (production craft); references/script-craft.md + meta-ads-playbook for the hook/message layer.",
|
|
22770
22800
|
principles: [
|
|
@@ -24967,6 +24997,7 @@ var SEEDANCE = "bytedance/seedance-2.0";
|
|
|
24967
24997
|
var VEO = "google/veo-3.1";
|
|
24968
24998
|
var VEO_FAST = "google/veo-3.1-fast";
|
|
24969
24999
|
var KLING = "kwaivgi/kling-v3.0-pro";
|
|
25000
|
+
var GEMINI_OMNI = "google/gemini-omni-flash";
|
|
24970
25001
|
function routeVideoModel(input) {
|
|
24971
25002
|
const budget = input.budget ?? "standard";
|
|
24972
25003
|
const signals = [];
|
|
@@ -24979,8 +25010,8 @@ function routeVideoModel(input) {
|
|
|
24979
25010
|
if (input.needsIdentity && !input.hasRealFace) {
|
|
24980
25011
|
signals.push({ model: SEEDANCE, weight: 300, because: "identity/product consistency \u2192 Seedance workhorse" });
|
|
24981
25012
|
}
|
|
24982
|
-
signals.push({ model:
|
|
24983
|
-
const scores = { [SEEDANCE]: 0, [VEO]: 0, [VEO_FAST]: 0, [KLING]: 0 };
|
|
25013
|
+
signals.push({ model: GEMINI_OMNI, weight: 100, because: "default \u2014 native audio in one call" });
|
|
25014
|
+
const scores = { [SEEDANCE]: 0, [VEO]: 0, [VEO_FAST]: 0, [KLING]: 0, [GEMINI_OMNI]: 0 };
|
|
24984
25015
|
let top = signals[0];
|
|
24985
25016
|
for (const s of signals) {
|
|
24986
25017
|
scores[s.model] = (scores[s.model] ?? 0) + s.weight;
|
|
@@ -35116,8 +35147,11 @@ Then stage changes (nothing is sent to Tag Manager until publish):
|
|
|
35116
35147
|
baker tag-manager tag create --json '{"name":"GA4 Quote","type":"gaawe","firingTriggerId":["gtm_temp_trigger_..."]}'
|
|
35117
35148
|
baker tag-manager draft list \u2014 review everything staged
|
|
35118
35149
|
|
|
35119
|
-
On publish, staged changes are written into
|
|
35120
|
-
|
|
35150
|
+
On publish, staged changes are written into the container and closed as a new Tag Manager
|
|
35151
|
+
version, and a clean run PUBLISHES that version \u2014 so the changes go live on the user's site.
|
|
35152
|
+
Say they go live when the user publishes the chat; never that they are live already, and never
|
|
35153
|
+
that someone still has to approve them inside Tag Manager. (If the publish call itself fails the
|
|
35154
|
+
version stands unpublished, and \`draft list\` says so and what is left to do.)
|
|
35121
35155
|
|
|
35122
35156
|
This owns the CONTENTS of the container. Installing the container snippet on the site is a
|
|
35123
35157
|
different job \u2014 that is \`baker tags\` with the googleTagManager tag.
|