@koda-sl/baker-cli 0.250.0 → 0.250.2-dev.e7a1a227e
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 +2 -0
- package/dist/{chunk-SJPRTSGY.js → chunk-VLNO5AUC.js} +4 -4
- package/dist/chunk-VLNO5AUC.js.map +1 -0
- package/dist/cli.js +44 -80
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-SJPRTSGY.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
ulid,
|
|
59
59
|
validateCanvasDeep,
|
|
60
60
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-VLNO5AUC.js";
|
|
62
62
|
import {
|
|
63
63
|
csvOrJson,
|
|
64
64
|
daysAgoIso,
|
|
@@ -7411,7 +7411,11 @@ registerSchema({
|
|
|
7411
7411
|
command: "actions.status",
|
|
7412
7412
|
description: "Resolve one or more Work Action refs by real action ID or temp_* ref in a single batch call. When BAKER_CHAT_ID is set, a temp_* ref still staged in THIS chat resolves to status 'draft' (not 'not_found') \u2014 staged ops only become published actions on chat publish.",
|
|
7413
7413
|
args: {
|
|
7414
|
-
|
|
7414
|
+
ref: {
|
|
7415
|
+
type: "positional",
|
|
7416
|
+
description: "One or more action refs: real action IDs or temp_* refs (space-separated)",
|
|
7417
|
+
required: true
|
|
7418
|
+
}
|
|
7415
7419
|
}
|
|
7416
7420
|
});
|
|
7417
7421
|
var statusCommand = defineCommand11({
|
|
@@ -19688,15 +19692,7 @@ var descriptionSchema = z25.string().min(1).max(META_LIMITS.creative.description
|
|
|
19688
19692
|
var callToActionSchema = z25.object({
|
|
19689
19693
|
type: z25.enum(CTA_TYPES2),
|
|
19690
19694
|
/** Overrides the base link for the CTA button; defaults to the ad's link. */
|
|
19691
|
-
link: httpsUrlSchema3.optional()
|
|
19692
|
-
/**
|
|
19693
|
-
* The instant form the button opens — Meta's `call_to_action.value.lead_gen_form_id`.
|
|
19694
|
-
* This is the ONLY place an instant form is named: the ad set says leads open
|
|
19695
|
-
* on the ad (`destination_type: ON_AD`), and the creative says which form.
|
|
19696
|
-
* Without it Meta refuses every ad in that ad set with subcode 3390001,
|
|
19697
|
-
* "Choose or create an instant form for your leads campaign".
|
|
19698
|
-
*/
|
|
19699
|
-
lead_gen_form_id: z25.string().regex(NUMERIC_ID_REGEX3).optional()
|
|
19695
|
+
link: httpsUrlSchema3.optional()
|
|
19700
19696
|
});
|
|
19701
19697
|
var creativeEnhancementsSchema = z25.object({
|
|
19702
19698
|
standardEnhancements: z25.enum(ENROLL_STATUSES).optional(),
|
|
@@ -19794,19 +19790,6 @@ var carouselCreativeSchema2 = z25.object({
|
|
|
19794
19790
|
link: httpsUrlSchema3.optional(),
|
|
19795
19791
|
call_to_action: callToActionSchema.optional(),
|
|
19796
19792
|
cards: z25.array(carouselCardSchema).min(META_LIMITS.creative.carouselCardsMin).max(META_LIMITS.creative.carouselCardsMax)
|
|
19797
|
-
}).superRefine((p, ctx) => {
|
|
19798
|
-
const forms = new Set(
|
|
19799
|
-
[p.call_to_action?.lead_gen_form_id, ...p.cards.map((card) => card.call_to_action?.lead_gen_form_id)].filter(
|
|
19800
|
-
Boolean
|
|
19801
|
-
)
|
|
19802
|
-
);
|
|
19803
|
-
if (forms.size > 1) {
|
|
19804
|
-
ctx.addIssue({
|
|
19805
|
-
code: "custom",
|
|
19806
|
-
path: ["cards"],
|
|
19807
|
-
message: `every card of a carousel must open the SAME instant form \u2014 this one names ${[...forms].join(" and ")}`
|
|
19808
|
-
});
|
|
19809
|
-
}
|
|
19810
19793
|
});
|
|
19811
19794
|
var dynamicImageSchema = z25.object({ ...imageMediaFields }).refine((p) => countImageRefs(p) === 1, "each dynamic image needs exactly one reference");
|
|
19812
19795
|
var dynamicVideoSchema = z25.object({
|
|
@@ -20313,10 +20296,7 @@ var adSetWriteArgs = {
|
|
|
20313
20296
|
"daily-budget": { type: "string", description: "Daily budget (ABO; omit if the campaign uses CBO)" },
|
|
20314
20297
|
"lifetime-budget": { type: "string", description: "Lifetime budget (needs --end)" },
|
|
20315
20298
|
"bid-amount": { type: "string", description: "Bid amount (required for COST_CAP / bid-cap strategies)" },
|
|
20316
|
-
"bid-strategy": {
|
|
20317
|
-
type: "string",
|
|
20318
|
-
description: "LOWEST_COST_WITHOUT_CAP (auto-bid) | LOWEST_COST_WITH_BID_CAP | COST_CAP \u2014 needs --bid-amount. Omit and Baker sets auto-bid; never leave it to Meta, whose default is a bid cap that then fails without an amount."
|
|
20319
|
-
},
|
|
20299
|
+
"bid-strategy": { type: "string", description: "Bid strategy override" },
|
|
20320
20300
|
currency: { type: "string", description: "3-letter currency (defaults to the account currency)" },
|
|
20321
20301
|
start: { type: "string", description: "Start time" },
|
|
20322
20302
|
end: { type: "string", description: "End time" },
|
|
@@ -20383,7 +20363,7 @@ function creativePayloadFromFlags(args) {
|
|
|
20383
20363
|
headline: args.headline,
|
|
20384
20364
|
description: args.description,
|
|
20385
20365
|
caption: args.caption,
|
|
20386
|
-
call_to_action:
|
|
20366
|
+
call_to_action: args.cta ? { type: upper(args.cta) } : void 0,
|
|
20387
20367
|
imageHash: args["image-hash"],
|
|
20388
20368
|
imageRef: args["image-ref"],
|
|
20389
20369
|
videoId: args["video-id"],
|
|
@@ -20393,26 +20373,6 @@ function creativePayloadFromFlags(args) {
|
|
|
20393
20373
|
enhancements
|
|
20394
20374
|
};
|
|
20395
20375
|
}
|
|
20396
|
-
function ctaPatchFromFlags(args) {
|
|
20397
|
-
const patch = {
|
|
20398
|
-
...args.cta ? { type: String(upper(args.cta)) } : {},
|
|
20399
|
-
...args["lead-form"] === void 0 ? {} : { lead_gen_form_id: String(args["lead-form"]) }
|
|
20400
|
-
};
|
|
20401
|
-
return Object.keys(patch).length > 0 ? patch : void 0;
|
|
20402
|
-
}
|
|
20403
|
-
function creativeCta(args) {
|
|
20404
|
-
const patch = ctaPatchFromFlags(args);
|
|
20405
|
-
return patch ? { type: "SIGN_UP", ...patch } : void 0;
|
|
20406
|
-
}
|
|
20407
|
-
function creativeContentPatch2(file, args) {
|
|
20408
|
-
const cta = ctaPatchFromFlags(args);
|
|
20409
|
-
if (!cta) {
|
|
20410
|
-
return void 0;
|
|
20411
|
-
}
|
|
20412
|
-
const fileContent = file.content ?? {};
|
|
20413
|
-
const fileCta = fileContent.call_to_action ?? {};
|
|
20414
|
-
return { ...fileContent, call_to_action: { ...fileCta, ...cta } };
|
|
20415
|
-
}
|
|
20416
20376
|
function parseEnroll(value) {
|
|
20417
20377
|
const raw = String(value).toLowerCase();
|
|
20418
20378
|
if (raw === "on" || raw === "true" || raw === "opt_in") return "OPT_IN";
|
|
@@ -20439,10 +20399,6 @@ Example: baker ads meta creatives create --page 555 --message "Save now" --link
|
|
|
20439
20399
|
description: { type: "string", description: "Description" },
|
|
20440
20400
|
caption: { type: "string", description: "Display URL / caption" },
|
|
20441
20401
|
cta: { type: "string", description: "Call-to-action type (SHOP_NOW|LEARN_MORE|SIGN_UP|\u2026)" },
|
|
20442
|
-
"lead-form": {
|
|
20443
|
-
type: "string",
|
|
20444
|
-
description: "Instant form id the button opens \u2014 REQUIRED for an ad set that collects leads on the ad. List them with `baker ads meta lead-forms`"
|
|
20445
|
-
},
|
|
20446
20402
|
"image-hash": { type: "string", description: "Meta ad-image hash (already uploaded)" },
|
|
20447
20403
|
"image-ref": { type: "string", description: "meta_temp_* ref of a staged media upload" },
|
|
20448
20404
|
"video-id": { type: "string", description: "Meta video id (already uploaded)" },
|
|
@@ -20476,11 +20432,6 @@ Amending a staged (meta_temp_*) creative merges fields into the create. Example:
|
|
|
20476
20432
|
...accountArgs2,
|
|
20477
20433
|
name: { type: "string", description: "New name" },
|
|
20478
20434
|
status: { type: "string", description: "ACTIVE|PAUSED|ARCHIVED" },
|
|
20479
|
-
cta: { type: "string", description: "Call-to-action type (staged creatives only)" },
|
|
20480
|
-
"lead-form": {
|
|
20481
|
-
type: "string",
|
|
20482
|
-
description: "Instant form id the button opens (staged creatives only) \u2014 `baker ads meta lead-forms` lists them"
|
|
20483
|
-
},
|
|
20484
20435
|
file: {
|
|
20485
20436
|
type: "string",
|
|
20486
20437
|
description: "JSON file with fields to change (content only merges into a staged creative)"
|
|
@@ -20488,15 +20439,7 @@ Amending a staged (meta_temp_*) creative merges fields into the create. Example:
|
|
|
20488
20439
|
},
|
|
20489
20440
|
run: async ({ args }) => {
|
|
20490
20441
|
const accountId = bareAccountId2(args);
|
|
20491
|
-
const
|
|
20492
|
-
const payload = mergePayload3(file, {
|
|
20493
|
-
name: args.name,
|
|
20494
|
-
status: upper(args.status),
|
|
20495
|
-
// A creative's content is immutable on Meta, so this only ever lands on a
|
|
20496
|
-
// creative still staged in this chat — which is exactly the fix path when
|
|
20497
|
-
// staging an ad refuses it for naming no instant form.
|
|
20498
|
-
content: creativeContentPatch2(file, args)
|
|
20499
|
-
});
|
|
20442
|
+
const payload = mergePayload3(loadJsonFileArg3(args.file), { name: args.name, status: upper(args.status) });
|
|
20500
20443
|
await stageOp2({
|
|
20501
20444
|
kind: "adCreative.update",
|
|
20502
20445
|
accountId,
|
|
@@ -23686,7 +23629,7 @@ var ANALYTICS_PRESET_INFO = [
|
|
|
23686
23629
|
},
|
|
23687
23630
|
{
|
|
23688
23631
|
name: "flow",
|
|
23689
|
-
description: "One Form in depth: conversions counted at the node that produced them, step-to-step paths for a branching Form, the per-step table, every trigger raised, and each marked conversion by name. Read convertedSessions rather than submits \u2014 a Form whose scheduling widget sits mid-flow never emits a submit and reports zero on every other report. An empty `conversions` array means nobody has marked what this Form is for, so the numbers rest on a built-in guess; mark it in the Forms builder or with `conversions: [{ triggerId }]` on the node.
|
|
23632
|
+
description: "One Form in depth: conversions counted at the node that produced them, step-to-step paths for a branching Form, the per-step table, every trigger raised, and each marked conversion by name. Read convertedSessions rather than submits \u2014 a Form whose scheduling widget sits mid-flow never emits a submit and reports zero on every other report. An empty `conversions` array means nobody has marked what this Form is for, so the numbers rest on a built-in guess; mark it in the Forms builder or with `conversions: [{ triggerId }]` on the node.",
|
|
23690
23633
|
playbook: "flow-builder \u2014 fix the branch or the step that loses people"
|
|
23691
23634
|
},
|
|
23692
23635
|
{
|
|
@@ -24165,7 +24108,7 @@ var funnelCommand = presetCommand({
|
|
|
24165
24108
|
var flowCommand = presetCommand({
|
|
24166
24109
|
name: "flow",
|
|
24167
24110
|
preset: "flow",
|
|
24168
|
-
description: "One Form in depth: how many people it converted, where they went between steps, the per-step table, and every trigger it raised. Every count is DISTINCT VISITS, so quote shares against `funnels[].visits` \u2014 the visits that opened the Form \u2014 and never against `starts`, which is only the visits that touched it. The gap between the two is people who read the Form and left, and on most Forms it is the largest loss there is. Read `flowSummary.convertedSessions`, not `submits` \u2014 a Form that books a call on a scheduling node in the MIDDLE of the flow never emits a submit, so it reports zero submits and every one of its real bookings as conversions. `flowPaths` is the flow as edges between steps, bracketed by __flow_start__, __flow_converted__ and __flow_exit__, which is how a branching Form is read at all: a per-step table cannot say that of the people who left step two, forty went to the booking branch and ninety went nowhere.
|
|
24111
|
+
description: "One Form in depth: how many people it converted, where they went between steps, the per-step table, and every trigger it raised. Every count is DISTINCT VISITS, so quote shares against `funnels[].visits` \u2014 the visits that opened the Form \u2014 and never against `starts`, which is only the visits that touched it. The gap between the two is people who read the Form and left, and on most Forms it is the largest loss there is. Read `flowSummary.convertedSessions`, not `submits` \u2014 a Form that books a call on a scheduling node in the MIDDLE of the flow never emits a submit, so it reports zero submits and every one of its real bookings as conversions. `flowPaths` is the flow as edges between steps, bracketed by __flow_start__, __flow_converted__ and __flow_exit__, which is how a branching Form is read at all: a per-step table cannot say that of the people who left step two, forty went to the booking branch and ninety went nowhere.",
|
|
24169
24112
|
extraArgs: { flow: { type: "string", description: "Form slug (default: every Form)", required: false } },
|
|
24170
24113
|
resolve: (args) => ({ preset: "flow", flowSlug: args.flow ? String(args.flow) : void 0 })
|
|
24171
24114
|
});
|
|
@@ -24507,7 +24450,8 @@ import { defineCommand as defineCommand94 } from "citty";
|
|
|
24507
24450
|
import { defineCommand as defineCommand89 } from "citty";
|
|
24508
24451
|
|
|
24509
24452
|
// src/commands/avatars/casting.ts
|
|
24510
|
-
var
|
|
24453
|
+
var CAST_FLAG_RULE = "Cast with `--avatar <handle>` on `baker studio generate` and `baker studio animate`. Do NOT hand-roll it by pasting `subjectDescription` into the prompt or by passing the sheet through `--reference` \u2014 both render something that merely resembles them. `--avatar` is also the ONLY thing that carries their pinned VOICE and how they speak into a clip; without it the video model invents a different voice, and no later step can put theirs back.";
|
|
24454
|
+
var VERBATIM_RULE = "When you do write the description yourself \u2014 a canvas node, a landing image, anywhere `--avatar` does not exist \u2014 copy `subjectDescription` VERBATIM, word for word. Re-phrasing it per generation is the other reason a face drifts across a set.";
|
|
24511
24455
|
var VIDEO_ROUTING = "In video, a photoreal presenter renders on `google/veo-3.1` (or `google/veo-3.1-fast`), never `bytedance/seedance-2.0` \u2014 it refuses photoreal human faces, AI-generated ones included. Scaffolding a video creative: `baker canvas scaffold-video \u2026 --real-face`.";
|
|
24512
24456
|
function castingHints(avatar) {
|
|
24513
24457
|
if (avatar.status === "generating") {
|
|
@@ -24531,7 +24475,8 @@ function castingHints(avatar) {
|
|
|
24531
24475
|
];
|
|
24532
24476
|
}
|
|
24533
24477
|
return [
|
|
24534
|
-
`Ready to cast
|
|
24478
|
+
`Ready to cast: \`baker studio generate "<your scene>" --avatar ${avatar.handle}\` for a still, \`baker studio animate "<the motion, and the line they say>" --avatar ${avatar.handle}\` for a clip.`,
|
|
24479
|
+
CAST_FLAG_RULE,
|
|
24535
24480
|
VERBATIM_RULE,
|
|
24536
24481
|
VIDEO_ROUTING
|
|
24537
24482
|
];
|
|
@@ -24571,10 +24516,19 @@ function rosterHints(roster, statusFilter) {
|
|
|
24571
24516
|
}
|
|
24572
24517
|
return hints;
|
|
24573
24518
|
}
|
|
24574
|
-
function
|
|
24519
|
+
function thinProfileHint(profile, handle) {
|
|
24520
|
+
const missing = ["motion", "persona", "speech", "wardrobe", "setting"].filter(
|
|
24521
|
+
(field) => !profile?.[field]?.trim()
|
|
24522
|
+
);
|
|
24523
|
+
if (missing.length === 0) return null;
|
|
24524
|
+
return `MISSING ${missing.map((field) => `--${field}`).join(" ")}. A bare subject description holds the face still and nothing else, so clips of @${handle} come out stiff and generic \u2014 measured, filling these in nearly doubled the movement in the same brief. The user almost certainly told you how this person talks and carries themselves; put it in: \`baker avatars update ${handle} ${missing.map((field) => `--${field} "\u2026"`).join(" ")}\`.`;
|
|
24525
|
+
}
|
|
24526
|
+
function creationHints(created, profile) {
|
|
24527
|
+
const thin = thinProfileHint(profile, created.handle);
|
|
24575
24528
|
const hints = [
|
|
24529
|
+
...thin ? [thin] : [],
|
|
24576
24530
|
...castingHints({ handle: created.handle, status: created.status }),
|
|
24577
|
-
`Once it is ready,
|
|
24531
|
+
`Once it is ready, cast it with \`--avatar ${created.handle}\` on \`baker studio generate\` / \`baker studio animate\` \u2014 that flag is what grounds the render on its identity sheet and, for a clip, what makes it speak in this avatar's own voice.`
|
|
24578
24532
|
];
|
|
24579
24533
|
if (created.likeness === "licensed") {
|
|
24580
24534
|
hints.push(
|
|
@@ -24844,7 +24798,7 @@ var createCommand2 = defineCommand89({
|
|
|
24844
24798
|
...chatIdFromEnv() ? { chatId: chatIdFromEnv() } : {}
|
|
24845
24799
|
};
|
|
24846
24800
|
const data = await writeAvatars("/api/avatars", body);
|
|
24847
|
-
writeJson({ ok: true, data, hints: creationHints(data) });
|
|
24801
|
+
writeJson({ ok: true, data, hints: creationHints(data, body.profile) });
|
|
24848
24802
|
} catch (err) {
|
|
24849
24803
|
failAvatarApi(err, handle);
|
|
24850
24804
|
}
|
|
@@ -29976,7 +29930,7 @@ function buildVideoTodo(report, overlayCount, floatingCount, opts, blueprint) {
|
|
|
29976
29930
|
]
|
|
29977
29931
|
},
|
|
29978
29932
|
transitions: "Scene-to-scene cuts the deconstruct flagged as fade/whip/zoom/dissolve/swipe are reproduced as an ffmpeg xfade at the boundary (everything else stays a hard cut). The overlap is consumed from extra generated footage, so the picture stays exactly on the audio timeline. To change a transition, edit the scene's `transition_out.type` in prompt.json and re-scaffold, or hand-edit the `spine` node's ffmpeg args. For a richer HERO cut (whip-pan, glitch, light-leak, gravitational-lens\u2026), the overlay layer can run a Hyperframes shader/CSS transition instead \u2014 see references/hyperframes/blueprints-and-transitions.md (pick 2\u20133 transition types total; the motion IS the handoff).",
|
|
29979
|
-
overlay_capabilities: 'The overlay layer (video-overlay-composition/index.html) is a REAL Hyperframes composition, not a plain text layer \u2014 our hyperframe_render node runs the genuine `npx hyperframes` renderer. So it can do far more than fade/slide/pop: (1) PULL a ready-made block \u2014 `npx hyperframes catalog --type block` then `npx hyperframes add <id>` into the composition dir, and nest it with a <div data-composition-src="compositions/<id>.html" data-start data-track-index data-width data-height> clip (~
|
|
29933
|
+
overlay_capabilities: 'The overlay layer (video-overlay-composition/index.html) is a REAL Hyperframes composition, not a plain text layer \u2014 our hyperframe_render node runs the genuine `npx hyperframes` renderer. So it can do far more than fade/slide/pop: (1) PULL a ready-made block \u2014 `npx hyperframes catalog --type block` then `npx hyperframes add <id>` into the composition dir, and nest it with a <div data-composition-src="compositions/<id>.html" data-start data-track-index data-width data-height> clip (~154 blocks: lower-thirds, social-proof cards, stat counters, charts, code, logo stings); (2) animate with the named GSAP motion-rule vocabulary (kinetic-beat-slam, svg-path-draw, counting-dynamic-scale, multi-phase-camera\u2026); (3) frame a talking head as a video-call/PIP/split. The whole engine + how to build new effects is documented in references/hyperframes/ (start at README.md \u2192 catalog.md, motion-rules.md). Reach for this whenever the reference\'s graphics are richer than plain text.',
|
|
29980
29934
|
caption_system: "Sound-off feed = burned-in captions carry the message. The deconstruct produced a word-level transcript \u2014 drive a real caption track off it (karaoke highlight \u2192 themed \u2192 kinetic), not hand-typed text. Floor: `npx hyperframes add caption-highlight` (TikTok karaoke); escalate by content register (caption-kinetic-slam for hype, caption-editorial-emphasis for premium, caption-neon-glow for AI/crypto). Group on meaning, sync within 80ms. Full ladder + transcript contract: references/hyperframes/captions-and-audio.md.",
|
|
29981
29935
|
talking_head_overlay: "For a scene where a presenter shows something (screen-share, app demo, product, a 'video call' look), frame the talking-head clip in the overlay composition: presenter full-bleed \u2192 shrinks to a rounded corner PIP pill while a content card takes the stage (or split/stack/overlay-glass-card). The pattern, the #video-wrap\u2192corner-pill tween, the glass call card, and the corner-coord presets are in references/hyperframes/talking-head-and-overlays.md. A ready exemplar ships as the `video-call-composition` move \u2014 copy it next to your canvas, drop brand fonts in the dir, point a `hyperframe_render` at it with the presenter clip as its `background`.",
|
|
29982
29936
|
text_overlays: {
|
|
@@ -48080,7 +48034,7 @@ registerSchema({
|
|
|
48080
48034
|
},
|
|
48081
48035
|
image: {
|
|
48082
48036
|
type: "string",
|
|
48083
|
-
description: "Comma-separated source images: library image ids, local file paths, and/or image URLs. Required unless --from text. With the default --from frame this must be exactly ONE image (the opening frame) \u2014 several are refused rather than silently reduced to the first. With --from references, up to 9 on Seedance 2.0 and 30 on Seedance 2.5.",
|
|
48037
|
+
description: "Comma-separated source images: library image ids, local file paths, and/or image URLs. Required unless --from text or --avatar (the avatar is the source). With the default --from frame this must be exactly ONE image (the opening frame) \u2014 several are refused rather than silently reduced to the first. With --from references, up to 9 on Seedance 2.0 and 30 on Seedance 2.5.",
|
|
48084
48038
|
required: false
|
|
48085
48039
|
},
|
|
48086
48040
|
from: {
|
|
@@ -48299,10 +48253,15 @@ function costHintsFor(body) {
|
|
|
48299
48253
|
var animateCommand = defineCommand189({
|
|
48300
48254
|
meta: {
|
|
48301
48255
|
name: "animate",
|
|
48302
|
-
description: "Render a clip. With an image the look is already fixed, so the prompt describes MOVEMENT \u2014 what the camera does, what the subject does, in what order. With --from text there is no image and the prompt is the whole shot.\n\nA rendered clip is NOT usable anywhere until you keep it: `baker studio keep <id> --slot N` is what puts it in the video library. Takes nobody keeps are never ingested, which is what makes a rejected batch cheap.\n\nFor anything longer than 15 seconds, or to build on footage that already exists, use --model bytedance/seedance-2.5: it renders 4-30s and is the only model that reads an existing clip or an existing soundtrack.\n\nExamples:\n baker studio animate 'slow push in, model turns to camera and smiles' --image j57abc123def456ghi789\n baker studio animate 'handheld drift right, steam rising from the cup' --image './out/hero.png' --duration 6 --quality 1080p\n baker studio animate 'product rotates once on a turntable' --image j57abc\u2026,j57def\u2026 --from references\n baker studio animate 'she keeps walking, camera stays with her, then she stops and looks up' --image j57abc\u2026 --from references --from-clip j57batch\u2026:0 --model bytedance/seedance-2.5 --duration 20\n baker studio animate 'hold on the product, then a slow push-in' --from references --from-video j57vid\u2026 --model bytedance/seedance-2.5\n baker studio animate 'slow drone pull-back over a solar farm at golden hour, no people' --from text --model bytedance/seedance-2.5 --duration 12"
|
|
48256
|
+
description: "Render a clip. With an image the look is already fixed, so the prompt describes MOVEMENT \u2014 what the camera does, what the subject does, in what order. With --from text there is no image and the prompt is the whole shot.\n\nA rendered clip is NOT usable anywhere until you keep it: `baker studio keep <id> --slot N` is what puts it in the video library. Takes nobody keeps are never ingested, which is what makes a rejected batch cheap.\n\nFor anything longer than 15 seconds, or to build on footage that already exists, use --model bytedance/seedance-2.5: it renders 4-30s and is the only model that reads an existing clip or an existing soundtrack.\n\nExamples:\n baker studio animate 'slow push in, model turns to camera and smiles' --image j57abc123def456ghi789\n baker studio animate 'she looks to camera and says: \"Hola, soy Elena\"' --avatar elena --quality 720p --aspect-ratio 9:16\n baker studio animate 'handheld drift right, steam rising from the cup' --image './out/hero.png' --duration 6 --quality 1080p\n baker studio animate 'product rotates once on a turntable' --image j57abc\u2026,j57def\u2026 --from references\n baker studio animate 'she keeps walking, camera stays with her, then she stops and looks up' --image j57abc\u2026 --from references --from-clip j57batch\u2026:0 --model bytedance/seedance-2.5 --duration 20\n baker studio animate 'hold on the product, then a slow push-in' --from references --from-video j57vid\u2026 --model bytedance/seedance-2.5\n baker studio animate 'slow drone pull-back over a solar farm at golden hour, no people' --from text --model bytedance/seedance-2.5 --duration 12"
|
|
48303
48257
|
},
|
|
48304
48258
|
args: {
|
|
48305
48259
|
prompt: { type: "positional", description: "What MOVES", required: false },
|
|
48260
|
+
avatar: {
|
|
48261
|
+
type: "string",
|
|
48262
|
+
description: "Cast an avatar by short name \u2014 grounds the clip on their identity sheet AND gives it their voice",
|
|
48263
|
+
required: false
|
|
48264
|
+
},
|
|
48306
48265
|
image: {
|
|
48307
48266
|
type: "string",
|
|
48308
48267
|
description: "Comma-separated library image ids, local file paths, and/or image URLs",
|
|
@@ -48367,13 +48326,13 @@ var animateCommand = defineCommand189({
|
|
|
48367
48326
|
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "A prompt is required" } });
|
|
48368
48327
|
process.exit(1);
|
|
48369
48328
|
}
|
|
48370
|
-
if (!args.image && args.from !== "text") {
|
|
48329
|
+
if (!args.image && !args.avatar && args.from !== "text") {
|
|
48371
48330
|
writeJson({
|
|
48372
48331
|
ok: false,
|
|
48373
48332
|
error: {
|
|
48374
48333
|
code: "VALIDATION_ERROR",
|
|
48375
|
-
message: "--image is required unless you pass --from text: a clip normally animates a still, so it needs the image to start from",
|
|
48376
|
-
fix: "Pass --image <id|path|url>, or --from text to render from the prompt alone."
|
|
48334
|
+
message: "--image is required unless you pass --from text or --avatar: a clip normally animates a still, so it needs the image to start from",
|
|
48335
|
+
fix: "Pass --image <id|path|url>, --avatar <handle> to build the shot around one of your avatars, or --from text to render from the prompt alone."
|
|
48377
48336
|
}
|
|
48378
48337
|
});
|
|
48379
48338
|
process.exit(1);
|
|
@@ -48557,6 +48516,11 @@ var generateCommand = defineCommand190({
|
|
|
48557
48516
|
description: "Start here to make an image. Renders 1-8 takes of one brief, ingests each into the media library as it lands, and shows the batch in the dashboard Studio next to the ones the client ran.\n\nModel choice: google/gemini-3.1-flash-image-preview (default \u2014 fast, best at editing a reference and at extreme ratios), google/gemini-3-pro-image-preview (highest fidelity, slower), openai/gpt-image-2 (photoreal and the cleanest in-image text \u2014 no --image-size, no 4:5 / 5:4), recraft/recraft-v4.1-pro-vector (vector/flat marks with palette control).\n\n--reference is the biggest quality lever there is: a real logo, product shot, Pinterest pin or sandbox screenshot beats any amount of adjectives.\n\nExamples:\n baker studio generate 'matte black bottle on wet marble, hard studio light, 35mm' --aspect-ratio 3:2 --count 3\n baker studio generate 'this bottle on a sunlit kitchen counter' --reference './src/brand/product.png,https://\u2026/kitchen.jpg'\n baker studio generate 'founder-style selfie, kitchen background, natural light' --skill ugc-selfie-hook\n baker studio generate 'flat geometric mascot, brand palette' --model recraft/recraft-v4.1-pro-vector --rgb-colors '[[10,10,10],[255,80,0]]'"
|
|
48558
48517
|
},
|
|
48559
48518
|
args: {
|
|
48519
|
+
avatar: {
|
|
48520
|
+
type: "string",
|
|
48521
|
+
description: "Cast an avatar by short name \u2014 grounds the image on their identity sheet",
|
|
48522
|
+
required: false
|
|
48523
|
+
},
|
|
48560
48524
|
prompt: { type: "positional", description: "What to generate", required: false },
|
|
48561
48525
|
model: { type: "string", description: "Model id (default google/gemini-3.1-flash-image-preview)", required: false },
|
|
48562
48526
|
"aspect-ratio": { type: "string", description: "Aspect ratio (default 1:1)", required: false },
|