@koda-sl/baker-cli 0.185.2 → 0.186.0

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/dist/cli.js CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  isPersistedAssetRef,
30
30
  looksLikeHttpUrl,
31
31
  maxInputReferences,
32
+ maxInputSlot,
32
33
  nearestClipDuration,
33
34
  nearestSupportedAspectRatio,
34
35
  nearestSupportedImageSize,
@@ -46,7 +47,7 @@ import {
46
47
  toModelSafeImage,
47
48
  ulid,
48
49
  validateCanvasDeep
49
- } from "./chunk-3TXZKSAR.js";
50
+ } from "./chunk-OWDTSPY4.js";
50
51
  import {
51
52
  csvOrJson,
52
53
  daysAgoIso,
@@ -3487,9 +3488,10 @@ var imagesIngestResponseSchema = z11.object({
3487
3488
  import { z as z12 } from "zod";
3488
3489
  var studioGenerationStatusSchema = z12.enum(["running", "completed", "partial", "failed"]);
3489
3490
  var studioVariantStatusSchema = z12.enum(["pending", "ready", "failed"]);
3490
- var studioClipModeSchema = z12.enum(["first_frame", "references"]);
3491
+ var studioClipModeSchema = z12.enum(["first_frame", "references", "text"]);
3491
3492
  var videoGenerateModelSchema = z12.enum([
3492
3493
  "bytedance/seedance-2.0",
3494
+ "bytedance/seedance-2.5",
3493
3495
  "google/gemini-omni-flash",
3494
3496
  "google/veo-3.1",
3495
3497
  "google/veo-3.1-fast",
@@ -3504,7 +3506,9 @@ var MAX_STUDIO_IMAGES = 8;
3504
3506
  var MAX_STUDIO_CLIPS = 3;
3505
3507
  var MAX_STUDIO_PROMPT_CHARS = 32e3;
3506
3508
  var MAX_STUDIO_IMAGE_REFERENCES = 16;
3507
- var MAX_STUDIO_CLIP_IMAGES = 9;
3509
+ var MAX_STUDIO_CLIP_IMAGES = 30;
3510
+ var MAX_STUDIO_CLIP_REFS = 10;
3511
+ var MAX_STUDIO_CLIP_SECONDS = 30;
3508
3512
  var aspectRatioSchema = z12.string().regex(/^[1-9]\d*:[1-9]\d*$/, "Write the shape as width:height, like 1:1, 4:5 or 16:9");
3509
3513
  var seedSchema = z12.coerce.number().int().min(0).max(4294967295);
3510
3514
  var studioGenerateRequestSchema = z12.object({
@@ -3537,15 +3541,39 @@ var studioGenerateRequestSchema = z12.object({
3537
3541
  backgroundRgbColor: rgbTriple.optional()
3538
3542
  });
3539
3543
  var studioAnimateRequestSchema = z12.object({
3540
- /** What MOVES. The look is already fixed by the still this starts from. */
3544
+ /**
3545
+ * What MOVES, when a still fixes the look. With no still it is the whole shot,
3546
+ * because the clip is then generated from the prompt alone.
3547
+ */
3541
3548
  prompt: z12.string().min(1).max(MAX_STUDIO_PROMPT_CHARS),
3542
- imageIds: z12.array(z12.string()).min(1).max(MAX_STUDIO_CLIP_IMAGES),
3549
+ /**
3550
+ * The still(s) the clip is built from. MAY BE EMPTY — every curated video model
3551
+ * also does text-to-video, so a prompt on its own is a complete request. The
3552
+ * CLI still requires one unless the caller asks for text-to-video explicitly,
3553
+ * because "I forgot the image" and "I meant no image" look identical here.
3554
+ */
3555
+ imageIds: z12.array(z12.string()).max(MAX_STUDIO_CLIP_IMAGES),
3556
+ /**
3557
+ * Clips this render builds on — extend, edit, or carry the motion of. Addressed
3558
+ * by the batch and slot they were rendered in, because a studio take is a plain
3559
+ * public file the provider can fetch while a library video is a Mux asset that
3560
+ * is not. Seedance 2.5 only; the backend refuses them on any other model.
3561
+ */
3562
+ clipRefs: z12.array(z12.object({ generationId: z12.string(), slot: z12.coerce.number().int().min(0) })).max(MAX_STUDIO_CLIP_REFS).optional(),
3563
+ /**
3564
+ * The same thing from the VIDEO LIBRARY — footage the company uploaded or
3565
+ * synced rather than rendered here. Ids, not URLs: a library video is a Mux
3566
+ * asset with no downloadable file until one is encoded for it, which the
3567
+ * backend does on first use.
3568
+ */
3569
+ videoIds: z12.array(z12.string()).max(MAX_STUDIO_CLIP_REFS).optional(),
3543
3570
  clipMode: studioClipModeSchema.optional(),
3544
3571
  model: videoGenerateModelSchema.optional(),
3545
3572
  aspectRatio: aspectRatioSchema.optional(),
3546
- // 15s is the longest clip any curated model renders; which lengths the chosen
3547
- // one actually offers is a per-model enum settled server-side.
3548
- duration: z12.coerce.number().int().min(1).max(15).optional(),
3573
+ // 30s is the longest clip any curated model renders (Seedance 2.5); which
3574
+ // lengths the chosen one actually offers is a per-model enum settled
3575
+ // server-side, and most of the roster stops well short of this.
3576
+ duration: z12.coerce.number().int().min(1).max(MAX_STUDIO_CLIP_SECONDS).optional(),
3549
3577
  resolution: z12.string().optional(),
3550
3578
  count: z12.coerce.number().int().min(1).max(MAX_STUDIO_CLIPS).optional(),
3551
3579
  generateAudio: z12.boolean().optional(),
@@ -3554,7 +3582,9 @@ var studioAnimateRequestSchema = z12.object({
3554
3582
  personGeneration: z12.enum(["allow_all", "allow_adult"]).optional(),
3555
3583
  enhancePrompt: z12.boolean().optional(),
3556
3584
  conditioningScale: z12.coerce.number().optional(),
3557
- cfgScale: z12.coerce.number().min(0).max(1).optional()
3585
+ cfgScale: z12.coerce.number().min(0).max(1).optional(),
3586
+ /** Seedance 2.5 only. Left unset the lane sends `false` — see `resolveClipRequest`. */
3587
+ watermark: z12.boolean().optional()
3558
3588
  });
3559
3589
  var studioVariantSchema = z12.object({
3560
3590
  slot: z12.number(),
@@ -3592,7 +3622,8 @@ var studioClipSettingsSchema = z12.object({
3592
3622
  personGeneration: z12.string().optional(),
3593
3623
  enhancePrompt: z12.boolean().optional(),
3594
3624
  conditioningScale: z12.number().optional(),
3595
- cfgScale: z12.number().optional()
3625
+ cfgScale: z12.number().optional(),
3626
+ watermark: z12.boolean().optional()
3596
3627
  });
3597
3628
  var studioGenerationSchema = z12.object({
3598
3629
  generationId: z12.string(),
@@ -34862,23 +34893,33 @@ var MODEL_LIST = VIDEO_MODEL_IDS;
34862
34893
  var DEFAULT_MAX_WAIT_MS = 6e5;
34863
34894
  registerSchema({
34864
34895
  command: "studio.animate",
34865
- description: "Turn a still into a clip. The look is already fixed by the image, so the prompt describes MOVEMENT \u2014 camera, subject, timing. --image takes library image ids, local file paths and/or image URLs (paths and URLs are added to the media library first). A rendered clip is NOT in the video library until `baker studio keep` approves it.",
34896
+ description: "Render a clip. With --image the look is already fixed, so the prompt describes MOVEMENT \u2014 camera, subject, timing; with --from text there is no image and the prompt is the whole shot. --image takes library image ids, local file paths and/or image URLs (paths and URLs are added to the media library first). A rendered clip is NOT in the video library until `baker studio keep` approves it.",
34866
34897
  args: {
34867
34898
  prompt: {
34868
34899
  type: "string",
34869
- description: "What MOVES \u2014 camera, subject, timing. Kling reads 2,500 characters and Seedance 4,000; over the cap the request is refused rather than trimmed.",
34900
+ description: "What MOVES \u2014 camera, subject, timing. Kling reads 2,500 characters and Seedance 2.0 4,000; over the cap the request is refused rather than trimmed. Seedance 2.5 publishes no cap, which is why it is the one to reach for when the brief is a whole multi-shot sequence.",
34870
34901
  required: true
34871
34902
  },
34872
34903
  image: {
34873
34904
  type: "string",
34874
- description: "Comma-separated source images: library image ids, local file paths, and/or image URLs. 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.",
34875
- required: true
34905
+ 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.",
34906
+ required: false
34876
34907
  },
34877
34908
  from: {
34878
34909
  type: "string",
34879
- description: "frame (default) animates a single image as the opening frame; references uses several images as visual guidance instead. Mutually exclusive at every provider, and reference mode is Seedance/Gemini Omni only \u2014 Veo and Kling are frame-only.",
34910
+ description: "What the clip is built FROM. frame (default) animates a single image as the opening frame; references uses several assets as visual guidance; text renders the prompt with no image at all. frame and references are mutually exclusive at every provider, and reference mode is Seedance/Gemini Omni only \u2014 Veo and Kling are frame-only. Every model does text.",
34880
34911
  required: false,
34881
- enum: ["frame", "references"]
34912
+ enum: ["frame", "references", "text"]
34913
+ },
34914
+ "from-clip": {
34915
+ type: "string",
34916
+ description: "Comma-separated Studio takes to BUILD ON, each written batchId:slot (e.g. j57abc\u2026:0) \u2014 this is how you extend a clip, edit one, or carry its motion into a new shot. Seedance 2.5 only, up to 10 source clips in total, and requires --from references.",
34917
+ required: false
34918
+ },
34919
+ "from-video": {
34920
+ type: "string",
34921
+ description: "The same, but from the VIDEO LIBRARY: comma-separated video ids (see `baker videos library`) to build on. Use this for footage the client uploaded or synced rather than something the Studio rendered. Counts against the same 10 source clips as --from-clip. The first use of a given video spends a minute encoding a reusable copy and is refused with a 'try again shortly' message; every use after that is instant.",
34922
+ required: false
34882
34923
  },
34883
34924
  model: {
34884
34925
  type: "string",
@@ -34896,7 +34937,7 @@ registerSchema({
34896
34937
  // Left unset this SNAPS onto the model's set rather than failing: the lane
34897
34938
  // default is 5s and Veo renders only 4/6/8, so an unstated length becomes 4
34898
34939
  // there. An explicit length the model cannot render is refused by name.
34899
- description: `Clip length in seconds \u2014 per-model. Unset it and the lane default (5s) snaps onto what the model renders. Across the roster: ${CLIP_DURATIONS.join(", ")}`,
34940
+ description: `Clip length in seconds \u2014 per-model. Unset it and the lane default (5s) snaps onto what the model renders. Past 15s only bytedance/seedance-2.5 will do it, out to 30. Across the roster: ${CLIP_DURATIONS.join(", ")}`,
34900
34941
  required: false
34901
34942
  },
34902
34943
  quality: {
@@ -34949,6 +34990,11 @@ registerSchema({
34949
34990
  description: "How closely the clip follows the prompt, 0-1 (Kling only; higher = closer to the prompt)",
34950
34991
  required: false
34951
34992
  },
34993
+ watermark: {
34994
+ type: "boolean",
34995
+ description: "Burn the provider's watermark into the picture. Seedance 2.5 only, and OFF by default because it cannot be removed after the render \u2014 pass it only if a client actually wants the mark.",
34996
+ required: false
34997
+ },
34952
34998
  wait: {
34953
34999
  type: "string",
34954
35000
  description: "Pass false to get the batch id back immediately instead of waiting for the takes",
@@ -34975,8 +35021,31 @@ function clipKnobs(args) {
34975
35021
  if (typeof args["enhance-prompt"] === "boolean") knobs.enhancePrompt = args["enhance-prompt"];
34976
35022
  knobs.conditioningScale = num2(args["conditioning-scale"]);
34977
35023
  knobs.cfgScale = num2(args["cfg-scale"]);
35024
+ if (args.watermark === true) knobs.watermark = true;
34978
35025
  return knobs;
34979
35026
  }
35027
+ function parseVideoIds(value) {
35028
+ if (typeof value !== "string") return void 0;
35029
+ const ids = value.split(",").map((part) => part.trim()).filter(Boolean);
35030
+ return ids.length > 0 ? ids : void 0;
35031
+ }
35032
+ function parseClipRefs(value) {
35033
+ if (typeof value !== "string" || value.trim() === "") return void 0;
35034
+ const refs = [];
35035
+ for (const entry of value.split(",").map((part) => part.trim())) {
35036
+ if (!entry) continue;
35037
+ const separator = entry.lastIndexOf(":");
35038
+ const generationId = separator === -1 ? "" : entry.slice(0, separator);
35039
+ const slot = Number(entry.slice(separator + 1));
35040
+ if (!generationId || !Number.isInteger(slot) || slot < 0) {
35041
+ throw new Error(
35042
+ `--from-clip takes batchId:slot pairs \u2014 "${entry}" is not one. Read a batch with \`baker studio get <id>\` to see its slots.`
35043
+ );
35044
+ }
35045
+ refs.push({ generationId, slot });
35046
+ }
35047
+ return refs.length > 0 ? refs : void 0;
35048
+ }
34980
35049
  function clipImageLimitFor(args) {
34981
35050
  const model = args.model ?? DEFAULT_VIDEO_GENERATE_MODEL;
34982
35051
  const label = MODEL_REGISTRY.video_generate[model]?.label ?? model;
@@ -34999,9 +35068,18 @@ function clipImageLimitFor(args) {
34999
35068
  refuse: (count) => `${label} takes up to ${max} reference images \u2014 you passed ${count}. Drop the weakest ones.`
35000
35069
  };
35001
35070
  }
35071
+ function clipModeFor(from) {
35072
+ if (from === "references") return "references";
35073
+ if (from === "text") return "text";
35074
+ return "first_frame";
35075
+ }
35002
35076
  function buildAnimateBody(args, prompt, imageIds) {
35003
35077
  const body = { prompt, imageIds, ...clipKnobs(args) };
35004
- if (args.from) body.clipMode = args.from === "references" ? "references" : "first_frame";
35078
+ const clipRefs = parseClipRefs(args["from-clip"]);
35079
+ if (clipRefs) body.clipRefs = clipRefs;
35080
+ const videoIds = parseVideoIds(args["from-video"]);
35081
+ if (videoIds) body.videoIds = videoIds;
35082
+ if (args.from) body.clipMode = clipModeFor(args.from);
35005
35083
  if (args.model) body.model = args.model;
35006
35084
  if (args["aspect-ratio"]) body.aspectRatio = args["aspect-ratio"];
35007
35085
  body.duration = num2(args.duration);
@@ -35021,7 +35099,12 @@ function costHintsFor(body) {
35021
35099
  // ceiling against an actual 5s — exactly double.
35022
35100
  duration: body.duration ?? nearestClipDuration(model, DEFAULT_CLIP_DURATION_S),
35023
35101
  resolution: body.resolution,
35024
- generateAudio: body.generateAudio
35102
+ generateAudio: body.generateAudio,
35103
+ buildsOnSourceClip: (body.clipRefs?.length ?? 0) + (body.videoIds?.length ?? 0) > 0,
35104
+ // Reference images might carry a face, and a face sends the render to the
35105
+ // dearer provider. Scoped to reference mode because that is the only shape
35106
+ // that gets re-routed — a frame-started clip never does.
35107
+ mayRouteToFal: body.clipMode === "references" && body.imageIds.length > 0
35025
35108
  });
35026
35109
  const total = perTake * count;
35027
35110
  const hints = [
@@ -35037,7 +35120,7 @@ function costHintsFor(body) {
35037
35120
  var animateCommand = defineCommand163({
35038
35121
  meta: {
35039
35122
  name: "animate",
35040
- description: "Turn a still into a clip. The image fixes the look, so the prompt describes MOVEMENT \u2014 what the camera does, what the subject does, in what order.\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\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"
35123
+ 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"
35041
35124
  },
35042
35125
  args: {
35043
35126
  prompt: { type: "positional", description: "What MOVES", required: false },
@@ -35046,7 +35129,17 @@ var animateCommand = defineCommand163({
35046
35129
  description: "Comma-separated library image ids, local file paths, and/or image URLs",
35047
35130
  required: false
35048
35131
  },
35049
- from: { type: "string", description: "frame (default) | references", required: false },
35132
+ from: { type: "string", description: "frame (default) | references | text", required: false },
35133
+ "from-clip": {
35134
+ type: "string",
35135
+ description: "Studio takes to build on, batchId:slot (Seedance 2.5, needs --from references)",
35136
+ required: false
35137
+ },
35138
+ "from-video": {
35139
+ type: "string",
35140
+ description: "Library video ids to build on (Seedance 2.5, needs --from references)",
35141
+ required: false
35142
+ },
35050
35143
  model: {
35051
35144
  type: "string",
35052
35145
  description: `Model id (default ${DEFAULT_VIDEO_GENERATE_MODEL})`,
@@ -35079,6 +35172,11 @@ var animateCommand = defineCommand163({
35079
35172
  },
35080
35173
  "conditioning-scale": { type: "string", description: "Frame adherence", required: false },
35081
35174
  "cfg-scale": { type: "string", description: "Prompt adherence 0-1", required: false },
35175
+ watermark: {
35176
+ type: "boolean",
35177
+ description: "Burn in the provider watermark (Seedance 2.5, off by default)",
35178
+ required: false
35179
+ },
35082
35180
  wait: { type: "string", description: "false to return the batch id immediately", required: false },
35083
35181
  "max-wait": { type: "string", description: "Max ms to wait (default 600000, 0 = no wait)", required: false },
35084
35182
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
@@ -35090,17 +35188,18 @@ var animateCommand = defineCommand163({
35090
35188
  writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "A prompt is required" } });
35091
35189
  process.exit(1);
35092
35190
  }
35093
- if (!args.image) {
35191
+ if (!args.image && args.from !== "text") {
35094
35192
  writeJson({
35095
35193
  ok: false,
35096
35194
  error: {
35097
35195
  code: "VALIDATION_ERROR",
35098
- message: "--image is required: a clip animates a still, so it needs the image to start from"
35196
+ message: "--image is required unless you pass --from text: a clip normally animates a still, so it needs the image to start from",
35197
+ fix: "Pass --image <id|path|url>, or --from text to render from the prompt alone."
35099
35198
  }
35100
35199
  });
35101
35200
  process.exit(1);
35102
35201
  }
35103
- const { imageIds } = await resolveLibraryImageIds(args.image, clipImageLimitFor(args));
35202
+ const { imageIds } = args.image ? await resolveLibraryImageIds(args.image, clipImageLimitFor(args)) : { imageIds: [] };
35104
35203
  const body = buildAnimateBody(args, prompt, imageIds);
35105
35204
  const costHints = costHintsFor(body);
35106
35205
  const start = await apiPost("/api/studio/animate", body);
@@ -35619,6 +35718,7 @@ function imageCard(model) {
35619
35718
  };
35620
35719
  }
35621
35720
  function videoCard(model) {
35721
+ const sourceClips = maxInputSlot("video_generate", model, "video");
35622
35722
  return {
35623
35723
  id: model,
35624
35724
  label: MODEL_REGISTRY.video_generate[model]?.label,
@@ -35630,6 +35730,11 @@ function videoCard(model) {
35630
35730
  // Only meaningful in reference mode; a first-frame clip always takes exactly
35631
35731
  // one image, whatever the model's reference slot allows.
35632
35732
  referenceMode: supportsReferenceToVideo(model) ? `up to ${maxInputReferences("video_generate", model) ?? "several"} reference images` : "first frame only \u2014 this model cannot take reference images",
35733
+ // Stated on EVERY card, not just the one model that has it: "can this build
35734
+ // on a clip I already made" is the question that decides whether extend and
35735
+ // edit are possible at all, and a field that appears only when the answer is
35736
+ // yes reads as "unknown" everywhere else.
35737
+ buildsOnClips: sourceClips ? `up to ${sourceClips} source clips \u2014 extend, edit, or carry their motion (pass with --from-clip)` : "no \u2014 this model cannot build on an existing clip",
35633
35738
  knobs: knobsFor("video_generate", model)
35634
35739
  };
35635
35740
  }
@@ -35679,7 +35784,7 @@ Examples:
35679
35784
  data: cards,
35680
35785
  meta: { count: cards.length },
35681
35786
  hints: [
35682
- kind === "image" ? "Shapes snap to the nearest the model renders; every other setting is refused if the model does not have it." : "A clip starting from one exact frame uses ONE image. Only Seedance and Gemini Omni take several as references."
35787
+ kind === "image" ? "Shapes snap to the nearest the model renders; every other setting is refused if the model does not have it." : "A clip starting from one exact frame uses ONE image. Only Seedance and Gemini Omni take several as references, and only Seedance 2.5 goes past 15s or builds on an existing clip."
35683
35788
  ]
35684
35789
  });
35685
35790
  }