@koda-sl/baker-cli 0.306.6 → 0.306.8
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/cli.js +95 -25
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3736,6 +3736,8 @@ baker studio animate "she keeps walking, camera stays with her, then she stops a
|
|
|
3736
3736
|
|
|
3737
3737
|
**A rendered clip is not usable anywhere until it is kept** — `keep` is what ingests it into the video library. Unkept takes are never ingested, which is what makes a rejected batch cheap.
|
|
3738
3738
|
|
|
3739
|
+
**One take of a presenter is an asset; an ad is not.** From the second clip in a conversation that carries a scripted line, `animate` warns that an ad is being assembled by hand and names `baker canvas scaffold-ad` instead. Separate takes have no cut between them, no continuous read, no captions and no end card, so three takes of a presenter are three clips and never one ad. It is a hint and never a refusal — splitting one long read into takes you then cut together is a legitimate shape, and building on a previous take (`--from-clip` / `--from-video`) is not flagged at all.
|
|
3740
|
+
|
|
3739
3741
|
### `baker studio get` / `list` / `keep` / `skills`
|
|
3740
3742
|
|
|
3741
3743
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -22665,6 +22665,20 @@ var lookalikeSpecSchema = z30.object({
|
|
|
22665
22665
|
ratio: z30.number().min(0.01).max(0.2).optional(),
|
|
22666
22666
|
country: z30.string().length(2).optional()
|
|
22667
22667
|
});
|
|
22668
|
+
function valuesOfKey(node, key) {
|
|
22669
|
+
if (Array.isArray(node)) return node.flatMap((entry) => valuesOfKey(entry, key));
|
|
22670
|
+
if (!node || typeof node !== "object") return [];
|
|
22671
|
+
const record = node;
|
|
22672
|
+
return [
|
|
22673
|
+
...key in record ? [record[key]] : [],
|
|
22674
|
+
...Object.values(record).flatMap((value) => valuesOfKey(value, key))
|
|
22675
|
+
];
|
|
22676
|
+
}
|
|
22677
|
+
function mixedEventSourceTypes(rule) {
|
|
22678
|
+
const types = valuesOfKey(rule, "event_sources").flatMap((sources) => Array.isArray(sources) ? sources : []).map((source) => source?.type).filter((type) => typeof type === "string");
|
|
22679
|
+
const distinct = [...new Set(types)].sort();
|
|
22680
|
+
return distinct.length > 1 ? distinct : [];
|
|
22681
|
+
}
|
|
22668
22682
|
var customAudienceCreateSchema = z30.object({
|
|
22669
22683
|
name: z30.string().min(1).max(META_LIMITS.audience.nameMax),
|
|
22670
22684
|
subtype: z30.enum(CUSTOM_AUDIENCE_SUBTYPES),
|
|
@@ -22681,6 +22695,21 @@ var customAudienceCreateSchema = z30.object({
|
|
|
22681
22695
|
if ((p.subtype === "WEBSITE" || p.subtype === "ENGAGEMENT") && !p.rule) {
|
|
22682
22696
|
ctx.addIssue({ code: "custom", path: ["rule"], message: `${p.subtype} audiences need a rule (use --file)` });
|
|
22683
22697
|
}
|
|
22698
|
+
if (p.rule && p.retention_days !== void 0) {
|
|
22699
|
+
ctx.addIssue({
|
|
22700
|
+
code: "custom",
|
|
22701
|
+
path: ["retention_days"],
|
|
22702
|
+
message: "a rule carries its own window in retention_seconds, so drop retention_days \u2014 Meta takes it only on a customer-file audience, and sending both leaves the review showing a window the audience will not have"
|
|
22703
|
+
});
|
|
22704
|
+
}
|
|
22705
|
+
const mixed = mixedEventSourceTypes(p.rule);
|
|
22706
|
+
if (mixed.length > 0) {
|
|
22707
|
+
ctx.addIssue({
|
|
22708
|
+
code: "custom",
|
|
22709
|
+
path: ["rule"],
|
|
22710
|
+
message: `one audience covers one kind of source, but this rule mixes ${mixed.join(" and ")} \u2014 split it into one audience per source`
|
|
22711
|
+
});
|
|
22712
|
+
}
|
|
22684
22713
|
});
|
|
22685
22714
|
var customAudienceUpdateSchema = z30.object({
|
|
22686
22715
|
name: z30.string().min(1).max(META_LIMITS.audience.nameMax).optional(),
|
|
@@ -23421,6 +23450,7 @@ var audiencesCreateCommand2 = defineCommand57({
|
|
|
23421
23450
|
name: "create",
|
|
23422
23451
|
description: `Stage a new custom audience shell. ${STAGED_NOTE2}
|
|
23423
23452
|
LOOKALIKE/WEBSITE/ENGAGEMENT specifics go via --file (lookalike_spec / rule). Member-list upload is not staged in v1.
|
|
23453
|
+
A WEBSITE/ENGAGEMENT rule carries its own window in retention_seconds, so --retention-days is refused next to one, and one audience covers one kind of event source (a Page and an Instagram profile are two audiences).
|
|
23424
23454
|
Example: baker ads meta audiences create --name "Site visitors 30d" --subtype WEBSITE --file rule.json`
|
|
23425
23455
|
},
|
|
23426
23456
|
args: {
|
|
@@ -23428,7 +23458,10 @@ Example: baker ads meta audiences create --name "Site visitors 30d" --subtype WE
|
|
|
23428
23458
|
name: { type: "string", description: "Audience name" },
|
|
23429
23459
|
subtype: { type: "string", description: "CUSTOM|WEBSITE|ENGAGEMENT|LOOKALIKE" },
|
|
23430
23460
|
description: { type: "string", description: "Description" },
|
|
23431
|
-
"retention-days": {
|
|
23461
|
+
"retention-days": {
|
|
23462
|
+
type: "string",
|
|
23463
|
+
description: "Retention window in days \u2014 customer-file audiences only; a rule carries its own retention_seconds"
|
|
23464
|
+
},
|
|
23432
23465
|
file: { type: "string", description: "JSON file with lookalike_spec / rule / full payload" }
|
|
23433
23466
|
},
|
|
23434
23467
|
run: async ({ args }) => {
|
|
@@ -58456,6 +58489,43 @@ import { defineCommand as defineCommand209 } from "citty";
|
|
|
58456
58489
|
// src/commands/studio/animate.ts
|
|
58457
58490
|
import { defineCommand as defineCommand201 } from "citty";
|
|
58458
58491
|
|
|
58492
|
+
// src/commands/studio/spokenPace.ts
|
|
58493
|
+
var WORDS_PER_SECOND3 = SPOKEN_WORDS_PER_SECOND;
|
|
58494
|
+
var spokenWords = spokenWordsIn;
|
|
58495
|
+
function wordsThatFit(seconds) {
|
|
58496
|
+
return Math.floor(seconds * WORDS_PER_SECOND3);
|
|
58497
|
+
}
|
|
58498
|
+
function spokenPaceHint(prompt, seconds, model) {
|
|
58499
|
+
const words2 = spokenWords(prompt);
|
|
58500
|
+
if (words2 === 0) return null;
|
|
58501
|
+
const split = model ? splitScriptFor(words2, model) : null;
|
|
58502
|
+
if (split) {
|
|
58503
|
+
return `The line is ${words2} words \u2014 about ${Math.ceil(words2 / WORDS_PER_SECOND3)}s of speech \u2014 and ${model} renders at most ${split.maxSeconds}s (~${Math.floor(split.maxSeconds * WORDS_PER_SECOND3)} words). It will not refuse and it will not cut: it will RUSH, swallowing syllables. Split the script into ${split.clips} takes of roughly ${split.wordsPerClip} words each, rendered separately and cut together \u2014 break it where the thought breaks, not mid-sentence. Or move to a model that renders longer (bytedance/seedance-2.5 goes to 30s).`;
|
|
58504
|
+
}
|
|
58505
|
+
if (!seconds) return null;
|
|
58506
|
+
const fits = wordsThatFit(seconds);
|
|
58507
|
+
if (words2 <= fits) return null;
|
|
58508
|
+
const rate = (words2 / seconds).toFixed(1);
|
|
58509
|
+
return `The line is ${words2} words and this clip is ${seconds}s \u2014 that is ${rate} words a second against a natural ${WORDS_PER_SECOND3}. The model will not run over, it will RUSH: syllables swallowed, endings clipped, and the take unusable however good the picture. Cut the line to about ${fits} words, or give it the length it needs (${Math.ceil(words2 / WORDS_PER_SECOND3)}s). Do not re-render the same line at the same length and hope.`;
|
|
58510
|
+
}
|
|
58511
|
+
|
|
58512
|
+
// src/commands/studio/adByHand.ts
|
|
58513
|
+
function priorScriptedTakes(batches) {
|
|
58514
|
+
return batches.filter(
|
|
58515
|
+
(batch) => batch.kind === "video" && spokenWords(batch.prompt) > 0 && batch.variants.some((variant) => variant.status !== "failed")
|
|
58516
|
+
).length;
|
|
58517
|
+
}
|
|
58518
|
+
function ordinal(n) {
|
|
58519
|
+
const suffix = n % 100 >= 11 && n % 100 <= 13 ? "th" : ["th", "st", "nd", "rd"][n % 10] ?? "th";
|
|
58520
|
+
return `${n}${suffix}`;
|
|
58521
|
+
}
|
|
58522
|
+
function adByHandHint(input) {
|
|
58523
|
+
if (!input.thisTakeIsScripted || input.buildsOnAClip) return null;
|
|
58524
|
+
if (input.priorScriptedTakes < 1) return null;
|
|
58525
|
+
const which = ordinal(input.priorScriptedTakes + 1);
|
|
58526
|
+
return `This is the ${which} clip with a scripted line in this conversation. Separate takes stay separate \u2014 nothing cuts them together, the audio stops and restarts at every join, and there are no captions and no closing card \u2014 so ${input.priorScriptedTakes + 1} takes of a presenter are ${input.priorScriptedTakes + 1} clips and never one ad. If these are the beats of one ad, stop rendering them one at a time: write them as beats (one line the voice says plus one shot on screen while it is said) and \`baker canvas scaffold-ad\` emits the whole ad \u2014 one canvas, one render, continuous read, synced captions and a brand end card. If they really are consecutive parts of ONE long read you will cut together yourself, carry on.`;
|
|
58527
|
+
}
|
|
58528
|
+
|
|
58459
58529
|
// src/commands/studio/batch.ts
|
|
58460
58530
|
function projectBatch(generation, full) {
|
|
58461
58531
|
const compact3 = {
|
|
@@ -58670,26 +58740,6 @@ function uploadFailure(path48) {
|
|
|
58670
58740
|
};
|
|
58671
58741
|
}
|
|
58672
58742
|
|
|
58673
|
-
// src/commands/studio/spokenPace.ts
|
|
58674
|
-
var WORDS_PER_SECOND3 = SPOKEN_WORDS_PER_SECOND;
|
|
58675
|
-
var spokenWords = spokenWordsIn;
|
|
58676
|
-
function wordsThatFit(seconds) {
|
|
58677
|
-
return Math.floor(seconds * WORDS_PER_SECOND3);
|
|
58678
|
-
}
|
|
58679
|
-
function spokenPaceHint(prompt, seconds, model) {
|
|
58680
|
-
const words2 = spokenWords(prompt);
|
|
58681
|
-
if (words2 === 0) return null;
|
|
58682
|
-
const split = model ? splitScriptFor(words2, model) : null;
|
|
58683
|
-
if (split) {
|
|
58684
|
-
return `The line is ${words2} words \u2014 about ${Math.ceil(words2 / WORDS_PER_SECOND3)}s of speech \u2014 and ${model} renders at most ${split.maxSeconds}s (~${Math.floor(split.maxSeconds * WORDS_PER_SECOND3)} words). It will not refuse and it will not cut: it will RUSH, swallowing syllables. Split the script into ${split.clips} takes of roughly ${split.wordsPerClip} words each, rendered separately and cut together \u2014 break it where the thought breaks, not mid-sentence. Or move to a model that renders longer (bytedance/seedance-2.5 goes to 30s).`;
|
|
58685
|
-
}
|
|
58686
|
-
if (!seconds) return null;
|
|
58687
|
-
const fits = wordsThatFit(seconds);
|
|
58688
|
-
if (words2 <= fits) return null;
|
|
58689
|
-
const rate = (words2 / seconds).toFixed(1);
|
|
58690
|
-
return `The line is ${words2} words and this clip is ${seconds}s \u2014 that is ${rate} words a second against a natural ${WORDS_PER_SECOND3}. The model will not run over, it will RUSH: syllables swallowed, endings clipped, and the take unusable however good the picture. Cut the line to about ${fits} words, or give it the length it needs (${Math.ceil(words2 / WORDS_PER_SECOND3)}s). Do not re-render the same line at the same length and hope.`;
|
|
58691
|
-
}
|
|
58692
|
-
|
|
58693
58743
|
// src/commands/studio/animate.ts
|
|
58694
58744
|
var MODEL_LIST = VIDEO_MODEL_IDS;
|
|
58695
58745
|
var DEFAULT_MAX_WAIT_MS = 6e5;
|
|
@@ -58931,6 +58981,25 @@ function costHintsFor(body) {
|
|
|
58931
58981
|
}
|
|
58932
58982
|
return hints2;
|
|
58933
58983
|
}
|
|
58984
|
+
var PRIOR_TAKE_WINDOW = 20;
|
|
58985
|
+
async function adByHandHintFor(body) {
|
|
58986
|
+
const thisTakeIsScripted = spokenWords(body.prompt) > 0;
|
|
58987
|
+
const buildsOnAClip = (body.clipRefs?.length ?? 0) + (body.videoIds?.length ?? 0) > 0;
|
|
58988
|
+
if (!thisTakeIsScripted || buildsOnAClip) return null;
|
|
58989
|
+
try {
|
|
58990
|
+
const { generations } = await apiGet("/api/studio/generations", {
|
|
58991
|
+
kind: "video",
|
|
58992
|
+
limit: String(PRIOR_TAKE_WINDOW)
|
|
58993
|
+
});
|
|
58994
|
+
return adByHandHint({
|
|
58995
|
+
priorScriptedTakes: priorScriptedTakes(generations),
|
|
58996
|
+
thisTakeIsScripted,
|
|
58997
|
+
buildsOnAClip
|
|
58998
|
+
});
|
|
58999
|
+
} catch {
|
|
59000
|
+
return null;
|
|
59001
|
+
}
|
|
59002
|
+
}
|
|
58934
59003
|
var animateCommand = defineCommand201({
|
|
58935
59004
|
meta: {
|
|
58936
59005
|
name: "animate",
|
|
@@ -59020,7 +59089,8 @@ var animateCommand = defineCommand201({
|
|
|
59020
59089
|
}
|
|
59021
59090
|
const { imageIds } = args.image ? await resolveLibraryImageIds(args.image, clipImageLimitFor(args)) : { imageIds: [] };
|
|
59022
59091
|
const body = buildAnimateBody(args, prompt, imageIds);
|
|
59023
|
-
const
|
|
59092
|
+
const assembling = await adByHandHintFor(body);
|
|
59093
|
+
const preflightHints = [...assembling ? [assembling] : [], ...costHintsFor(body)];
|
|
59024
59094
|
const start = await apiPost("/api/studio/animate", body);
|
|
59025
59095
|
const maxWaitMs = resolveMaxWaitMs(args.wait, args["max-wait"], DEFAULT_MAX_WAIT_MS);
|
|
59026
59096
|
if (maxWaitMs === 0) {
|
|
@@ -59029,7 +59099,7 @@ var animateCommand = defineCommand201({
|
|
|
59029
59099
|
data: start,
|
|
59030
59100
|
hints: [
|
|
59031
59101
|
`Rendering ${start.count} take(s). Read them with \`baker studio get ${start.generationId}\`, then keep the one you want.`,
|
|
59032
|
-
...
|
|
59102
|
+
...preflightHints
|
|
59033
59103
|
]
|
|
59034
59104
|
});
|
|
59035
59105
|
return;
|
|
@@ -59039,14 +59109,14 @@ var animateCommand = defineCommand201({
|
|
|
59039
59109
|
writeJson({
|
|
59040
59110
|
ok: true,
|
|
59041
59111
|
data: { ...start, status: "running" },
|
|
59042
|
-
hints: [waitingHint(outcome, start.generationId, maxWaitMs), ...
|
|
59112
|
+
hints: [waitingHint(outcome, start.generationId, maxWaitMs), ...preflightHints]
|
|
59043
59113
|
});
|
|
59044
59114
|
return;
|
|
59045
59115
|
}
|
|
59046
59116
|
writeJson({
|
|
59047
59117
|
ok: true,
|
|
59048
59118
|
data: projectBatch(outcome.generation, args.full === true),
|
|
59049
|
-
hints: [...batchHints(outcome.generation), ...
|
|
59119
|
+
hints: [...batchHints(outcome.generation), ...preflightHints]
|
|
59050
59120
|
});
|
|
59051
59121
|
} catch (error) {
|
|
59052
59122
|
failStudioCommand(error, { withFix: true });
|