@koda-sl/baker-cli 0.270.3-dev.e350bbc93 → 0.270.4-dev.719d8b6f3
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
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
ulid,
|
|
60
60
|
validateCanvasDeep,
|
|
61
61
|
ytDlpBlockSignal
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-Y2GYULGB.js";
|
|
63
63
|
import {
|
|
64
64
|
csvOrJson,
|
|
65
65
|
daysAgoIso,
|
|
@@ -26263,7 +26263,11 @@ function thinProfileHint(profile, handle) {
|
|
|
26263
26263
|
}
|
|
26264
26264
|
function creationHints(created, profile) {
|
|
26265
26265
|
const thin = thinProfileHint(profile, created.handle);
|
|
26266
|
+
const casting = created.likeness === "synthetic" ? [
|
|
26267
|
+
`The user chooses this face, and it only happens if you ask: call \`request_avatar_casting { avatarHandle: "${created.handle}" }\` now. It shows them real portraits and blocks until they pick \u2014 never stand in a question of your own, which can only offer them words. The sheet building right now is fused from a face nobody has chosen.`
|
|
26268
|
+
] : [];
|
|
26266
26269
|
const hints2 = [
|
|
26270
|
+
...casting,
|
|
26267
26271
|
...thin ? [thin] : [],
|
|
26268
26272
|
...castingHints({ handle: created.handle, status: created.status }),
|
|
26269
26273
|
`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.`
|
|
@@ -32789,6 +32793,26 @@ function classifyWatchDefects(json, windows) {
|
|
|
32789
32793
|
};
|
|
32790
32794
|
});
|
|
32791
32795
|
}
|
|
32796
|
+
var FidelityReview = z29.object({
|
|
32797
|
+
delivers_the_ask: z29.boolean(),
|
|
32798
|
+
missing: z29.array(z29.string()).default([]),
|
|
32799
|
+
contradicted: z29.array(z29.string()).default([])
|
|
32800
|
+
});
|
|
32801
|
+
function buildFidelityPrompt(brief) {
|
|
32802
|
+
return `Someone asked for an advertisement in these words:
|
|
32803
|
+
|
|
32804
|
+
"""${brief.trim()}"""
|
|
32805
|
+
|
|
32806
|
+
Watch and LISTEN to the video, then answer only this: does it deliver what they asked for? Judge the request as it was written \u2014 not what would make a good ad, and not what you would have made. Two lists: what they asked for that is NOT in the video, and what the video does that CONTRADICTS what they asked for. Quote their own words in each entry so the answer can be checked. Empty lists when it delivers.
|
|
32807
|
+
Answer JSON only: {"delivers_the_ask":true|false,"missing":["..."],"contradicted":["..."]}`;
|
|
32808
|
+
}
|
|
32809
|
+
function classifyFidelityFindings(json) {
|
|
32810
|
+
const parsed = FidelityReview.safeParse(json);
|
|
32811
|
+
if (!parsed.success) return [];
|
|
32812
|
+
const r = parsed.data;
|
|
32813
|
+
if (r.delivers_the_ask) return [];
|
|
32814
|
+
return [...r.missing, ...r.contradicted].map((entry) => entry.trim()).filter(Boolean).map((what) => ({ severity: "blocking", what: `Not what was asked for: ${what}.` }));
|
|
32815
|
+
}
|
|
32792
32816
|
function classifyWatchFindings(json) {
|
|
32793
32817
|
const parsed = WatchReview.safeParse(json);
|
|
32794
32818
|
if (!parsed.success) return [];
|
|
@@ -33005,11 +33029,13 @@ async function reviewRenderedVideo(opts) {
|
|
|
33005
33029
|
const unread = perFrame.filter((f) => f.json === null).length;
|
|
33006
33030
|
if (unread === perFrame.length && reel === null) return null;
|
|
33007
33031
|
const watch = await askVideo(opts.video, buildWatchReviewPrompt({ market: opts.market, script: opts.script }));
|
|
33032
|
+
const fidelity = opts.brief?.trim() ? await askVideo(opts.video, buildFidelityPrompt(opts.brief)) : null;
|
|
33008
33033
|
const findings = [
|
|
33009
33034
|
...classifyFrameFindings(perFrame, opts.windows ?? []),
|
|
33010
33035
|
...classifyReelFindings(reel),
|
|
33011
33036
|
...classifyWatchFindings(watch),
|
|
33012
33037
|
...classifyWatchDefects(watch, opts.windows ?? []),
|
|
33038
|
+
...classifyFidelityFindings(fidelity),
|
|
33013
33039
|
// Measured, not judged: a model looking at frames cannot hear the mix.
|
|
33014
33040
|
...await measureAudio(opts.video)
|
|
33015
33041
|
];
|
|
@@ -33048,6 +33074,14 @@ var runCommand = defineCommand102({
|
|
|
33048
33074
|
meta: { name: "run", description: "Validate and execute a canvas JSON file." },
|
|
33049
33075
|
args: {
|
|
33050
33076
|
file: { type: "positional", required: true, description: "Path to canvas JSON" },
|
|
33077
|
+
until: {
|
|
33078
|
+
type: "string",
|
|
33079
|
+
description: "Stop before the expensive half and leave the run resumable. `frames` settles the pictures and stops on the doorstep of the clips, so the look can be checked for the price of a few images instead of a whole ad. Continue with `baker canvas run <file> --run-id <the same id>` \u2014 everything already done is cached and is not paid for twice."
|
|
33080
|
+
},
|
|
33081
|
+
brief: {
|
|
33082
|
+
type: "string",
|
|
33083
|
+
description: "What the person asked for, IN THEIR OWN WORDS \u2014 paste their message, do not summarise it. The review compares the finished video against this and reports what they asked for that is missing or contradicted. Without it the review can only say whether the video is well made, not whether it is the one they wanted: every other check grades the render against the spec, and a spec that misread the request agrees with itself."
|
|
33084
|
+
},
|
|
33051
33085
|
"cache-dir": { type: "string", description: "Cache root (default ./canvas/.cache)" },
|
|
33052
33086
|
"outputs-dir": { type: "string", description: "Per-run outputs root (default ./canvas)" },
|
|
33053
33087
|
"run-id": { type: "string", description: "Override run id (also resumes that run, re-attaching its in-flight jobs)" },
|
|
@@ -33097,6 +33131,8 @@ var runCommand = defineCommand102({
|
|
|
33097
33131
|
outputsDir: args["outputs-dir"] ? String(args["outputs-dir"]) : void 0,
|
|
33098
33132
|
runId: args["run-id"] ? String(args["run-id"]) : void 0,
|
|
33099
33133
|
fresh: args.fresh === true,
|
|
33134
|
+
brief: args.brief ? String(args.brief) : void 0,
|
|
33135
|
+
until: args.until ? String(args.until) : void 0,
|
|
33100
33136
|
cachePolicy: args["cache-policy"] ? String(args["cache-policy"]) : void 0,
|
|
33101
33137
|
regenerate: args.regenerate !== void 0 ? String(args.regenerate) : void 0,
|
|
33102
33138
|
// --concurrency wins; --parallel is the discoverable alias for the same bound.
|
|
@@ -33119,6 +33155,10 @@ function resolveMaxCredits(...candidates) {
|
|
|
33119
33155
|
}
|
|
33120
33156
|
return void 0;
|
|
33121
33157
|
}
|
|
33158
|
+
function stopBeforeKinds(until) {
|
|
33159
|
+
if (until?.trim() === "frames") return /* @__PURE__ */ new Set(["video_generate"]);
|
|
33160
|
+
return null;
|
|
33161
|
+
}
|
|
33122
33162
|
async function executeCanvasRun(opts) {
|
|
33123
33163
|
const filePath = path16.resolve(opts.file);
|
|
33124
33164
|
const raw = await readFile12(filePath, "utf8");
|
|
@@ -33307,6 +33347,8 @@ ${describeRewrites(healed.rewrites)}
|
|
|
33307
33347
|
if (progress && poster) {
|
|
33308
33348
|
poster.startKeepalive(() => progress.hasPlan() ? progress.snapshot() : null);
|
|
33309
33349
|
}
|
|
33350
|
+
const frameUrls = [];
|
|
33351
|
+
const isFrameNode = (id) => parsed.nodes?.find((n) => n.id === id)?.type === "image_generate";
|
|
33310
33352
|
try {
|
|
33311
33353
|
const policy = opts.cachePolicy ?? "read_write";
|
|
33312
33354
|
const result = await engine.run(parsed, {
|
|
@@ -33314,12 +33356,24 @@ ${describeRewrites(healed.rewrites)}
|
|
|
33314
33356
|
signal: abort.signal,
|
|
33315
33357
|
cache_policy: policy,
|
|
33316
33358
|
max_credits: opts.maxCredits,
|
|
33359
|
+
...stopBeforeKinds(opts.until) ? { stop_before_kinds: stopBeforeKinds(opts.until) } : {},
|
|
33317
33360
|
concurrency: resolveConcurrency(opts.concurrency, process.env.BAKER_CANVAS_CONCURRENCY),
|
|
33318
33361
|
regenerate,
|
|
33319
|
-
|
|
33320
|
-
|
|
33321
|
-
|
|
33322
|
-
|
|
33362
|
+
// Always set, because the frame collector has to run whether or not this run is
|
|
33363
|
+
// reporting progress to a backend — without it a checkpoint stops with nothing to
|
|
33364
|
+
// show, which is the only thing a checkpoint is for.
|
|
33365
|
+
onProgress: (event) => {
|
|
33366
|
+
if (event.kind === "node_settled" && isFrameNode(event.run.node_id)) {
|
|
33367
|
+
for (const out of Object.values(event.outputs ?? {})) {
|
|
33368
|
+
const url = out?.url;
|
|
33369
|
+
if (url) frameUrls.push(url);
|
|
33370
|
+
}
|
|
33371
|
+
}
|
|
33372
|
+
if (progress && poster) {
|
|
33373
|
+
progress.apply(event);
|
|
33374
|
+
if (progress.hasPlan()) poster.enqueue(progress.snapshot());
|
|
33375
|
+
}
|
|
33376
|
+
}
|
|
33323
33377
|
});
|
|
33324
33378
|
await clearRunMarker(outputsDir, filePath);
|
|
33325
33379
|
const keepRuns = opts.keepRuns;
|
|
@@ -33328,7 +33382,11 @@ ${describeRewrites(healed.rewrites)}
|
|
|
33328
33382
|
`));
|
|
33329
33383
|
}
|
|
33330
33384
|
const reviewable = videoPathFromOutput(result.output);
|
|
33331
|
-
const review = reviewable ? await reviewRenderedVideo({
|
|
33385
|
+
const review = reviewable ? await reviewRenderedVideo({
|
|
33386
|
+
video: reviewable,
|
|
33387
|
+
windows: await sceneWindowsBeside(filePath),
|
|
33388
|
+
brief: opts.brief?.trim() || void 0
|
|
33389
|
+
}) : null;
|
|
33332
33390
|
const hints2 = reviewable ? review === null ? [RENDER_REVIEW_UNAVAILABLE] : review.length === 0 ? ["Reviewed the finished video frame by frame \u2014 nothing to report."] : [
|
|
33333
33391
|
`Reviewed the finished video and found ${review.length} thing${review.length === 1 ? "" : "s"} to fix. Show the user what is wrong before you show them the ad.`,
|
|
33334
33392
|
...review.map((f) => `${f.severity === "blocking" ? "MUST FIX" : "SHOULD FIX"} \u2014 ${f.what}`),
|
|
@@ -33369,6 +33427,27 @@ ${describeRewrites(healed.rewrites)}
|
|
|
33369
33427
|
`);
|
|
33370
33428
|
process.exit(abortedBy === "SIGTERM" ? 143 : 130);
|
|
33371
33429
|
}
|
|
33430
|
+
if (e instanceof RunAbortedError && e.reason === "checkpoint") {
|
|
33431
|
+
await clearRunMarker(outputsDir, filePath);
|
|
33432
|
+
const frames = frameUrls;
|
|
33433
|
+
process.stdout.write(
|
|
33434
|
+
`${JSON.stringify(
|
|
33435
|
+
{
|
|
33436
|
+
ok: true,
|
|
33437
|
+
data: { stopped: "frames", runId, frames },
|
|
33438
|
+
hints: [
|
|
33439
|
+
`${e.message}`,
|
|
33440
|
+
frames.length > 0 ? `Show these ${frames.length} frames and ask whether to carry on, BEFORE the clips are paid for. They are the ad's look, at a fraction of its price.` : "No frame outputs were captured \u2014 carry on rather than blocking on nothing.",
|
|
33441
|
+
`Carry on with: baker canvas run ${filePath} --run-id ${runId}`
|
|
33442
|
+
]
|
|
33443
|
+
},
|
|
33444
|
+
null,
|
|
33445
|
+
2
|
|
33446
|
+
)}
|
|
33447
|
+
`
|
|
33448
|
+
);
|
|
33449
|
+
process.exit(0);
|
|
33450
|
+
}
|
|
33372
33451
|
if (e instanceof RunAbortedError && e.reason === "cost_cap") {
|
|
33373
33452
|
await clearRunMarker(outputsDir, filePath);
|
|
33374
33453
|
if (poster) await poster.flush(failedPayload(e.message));
|
|
@@ -35470,7 +35549,11 @@ var scaffoldAdCommand = defineCommand106({
|
|
|
35470
35549
|
ok: true,
|
|
35471
35550
|
data: { canvas: outPath, beats: spec.data.beats.length, slug },
|
|
35472
35551
|
hints: [
|
|
35473
|
-
`
|
|
35552
|
+
// `--until frames` on anything carrying a face or a mark, because that is where
|
|
35553
|
+
// getting it wrong is expensive: the pictures cost a fraction of the clips, and
|
|
35554
|
+
// every defect anyone has caught in a finished ad was visible in them. The run
|
|
35555
|
+
// stops, the frames are shown, and continuing pays for none of them twice.
|
|
35556
|
+
handle || staged ? `Wrote ${spec.data.beats.length} beats to ${outPath}. Run it with \`baker canvas run ${outPath} --until frames --brief "<what they asked for, their words>"\`, SHOW the frames it returns and ask whether to carry on \u2014 then continue with the \`--run-id\` it gives you. This ad carries ${handle ? "a person's face" : "the client's mark"}, and a wrong look costs a whole ad to discover after the clips.` : `Wrote ${spec.data.beats.length} beats to ${outPath}. Run it with \`baker canvas run ${outPath} --brief "<what they asked for, their words>"\`.`,
|
|
35474
35557
|
...autoCast ? [
|
|
35475
35558
|
`Cast \`${autoCast}\` \u2014 the spec described this person in words, and they are one of this company's avatars. Every shot now grounds on their identity sheet and they speak in their own pinned voice. Write \`"cast": { "avatar": "` + autoCast + '" }` yourself next time; a written description is for someone the company does not have.'
|
|
35476
35559
|
] : [],
|