@koda-sl/baker-cli 0.131.0-dev.cad5e0bc7 → 0.132.0-dev.cad5e0bc7
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 +7 -5
- package/dist/cli.js +398 -203
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4926,11 +4926,11 @@ function rawTextEntries(value) {
|
|
|
4926
4926
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
4927
4927
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
4928
4928
|
}
|
|
4929
|
-
function rawFileEntries(
|
|
4930
|
-
if (typeof
|
|
4929
|
+
function rawFileEntries(path23) {
|
|
4930
|
+
if (typeof path23 !== "string" || path23.length === 0) {
|
|
4931
4931
|
return [];
|
|
4932
4932
|
}
|
|
4933
|
-
return readFileSync2(
|
|
4933
|
+
return readFileSync2(path23, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
4934
4934
|
}
|
|
4935
4935
|
function keywordEntries(args) {
|
|
4936
4936
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -4953,19 +4953,19 @@ function keywordEntries(args) {
|
|
|
4953
4953
|
}
|
|
4954
4954
|
return entries;
|
|
4955
4955
|
}
|
|
4956
|
-
function loadJsonFileArg(
|
|
4957
|
-
if (typeof
|
|
4956
|
+
function loadJsonFileArg(path23) {
|
|
4957
|
+
if (typeof path23 !== "string" || path23.length === 0) {
|
|
4958
4958
|
return {};
|
|
4959
4959
|
}
|
|
4960
4960
|
try {
|
|
4961
|
-
const parsed = JSON.parse(readFileSync2(
|
|
4961
|
+
const parsed = JSON.parse(readFileSync2(path23, "utf8"));
|
|
4962
4962
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4963
|
-
failWriteValidation(`${
|
|
4963
|
+
failWriteValidation(`${path23} must contain a JSON object`);
|
|
4964
4964
|
}
|
|
4965
4965
|
return parsed;
|
|
4966
4966
|
} catch (err) {
|
|
4967
4967
|
if (err instanceof SyntaxError) {
|
|
4968
|
-
failWriteValidation(`${
|
|
4968
|
+
failWriteValidation(`${path23} is not valid JSON: ${err.message}`);
|
|
4969
4969
|
}
|
|
4970
4970
|
throw err;
|
|
4971
4971
|
}
|
|
@@ -5076,10 +5076,10 @@ async function stageUpdate(kind, customerId, target, payload) {
|
|
|
5076
5076
|
async function stageTarget(kind, customerId, target) {
|
|
5077
5077
|
await stageGoogleOp({ kind, customerId, target });
|
|
5078
5078
|
}
|
|
5079
|
-
async function draftAction(
|
|
5079
|
+
async function draftAction(path23, body) {
|
|
5080
5080
|
try {
|
|
5081
5081
|
const chatId = requireChatId();
|
|
5082
|
-
const response = await apiPost(
|
|
5082
|
+
const response = await apiPost(path23, { chatId, ...body });
|
|
5083
5083
|
writeJsonEnvelope(response);
|
|
5084
5084
|
} catch (err) {
|
|
5085
5085
|
handleGoogleError(err);
|
|
@@ -8834,19 +8834,19 @@ function failWriteValidation2(message) {
|
|
|
8834
8834
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
8835
8835
|
process.exit(1);
|
|
8836
8836
|
}
|
|
8837
|
-
function loadJsonFileArg2(
|
|
8838
|
-
if (typeof
|
|
8837
|
+
function loadJsonFileArg2(path23) {
|
|
8838
|
+
if (typeof path23 !== "string" || path23.length === 0) {
|
|
8839
8839
|
return {};
|
|
8840
8840
|
}
|
|
8841
8841
|
try {
|
|
8842
|
-
const parsed = JSON.parse(readFileSync6(
|
|
8842
|
+
const parsed = JSON.parse(readFileSync6(path23, "utf8"));
|
|
8843
8843
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8844
|
-
failWriteValidation2(`${
|
|
8844
|
+
failWriteValidation2(`${path23} must contain a JSON object`);
|
|
8845
8845
|
}
|
|
8846
8846
|
return parsed;
|
|
8847
8847
|
} catch (err) {
|
|
8848
8848
|
if (err instanceof SyntaxError) {
|
|
8849
|
-
failWriteValidation2(`${
|
|
8849
|
+
failWriteValidation2(`${path23} is not valid JSON: ${err.message}`);
|
|
8850
8850
|
}
|
|
8851
8851
|
throw err;
|
|
8852
8852
|
}
|
|
@@ -8931,15 +8931,15 @@ function parseLocaleFlag(value) {
|
|
|
8931
8931
|
}
|
|
8932
8932
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
8933
8933
|
}
|
|
8934
|
-
function loadTargetingFileArg(
|
|
8935
|
-
if (typeof
|
|
8934
|
+
function loadTargetingFileArg(path23) {
|
|
8935
|
+
if (typeof path23 !== "string" || path23.length === 0) {
|
|
8936
8936
|
return void 0;
|
|
8937
8937
|
}
|
|
8938
|
-
const parsed = loadJsonFileArg2(
|
|
8938
|
+
const parsed = loadJsonFileArg2(path23);
|
|
8939
8939
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
8940
8940
|
if (!criteria.include) {
|
|
8941
8941
|
failWriteValidation2(
|
|
8942
|
-
`${
|
|
8942
|
+
`${path23} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
8943
8943
|
);
|
|
8944
8944
|
}
|
|
8945
8945
|
return criteria;
|
|
@@ -8974,14 +8974,14 @@ function parseCsvLine(line) {
|
|
|
8974
8974
|
cells.push(current);
|
|
8975
8975
|
return cells.map((cell) => cell.trim());
|
|
8976
8976
|
}
|
|
8977
|
-
function parseListFileArg(
|
|
8978
|
-
if (typeof
|
|
8977
|
+
function parseListFileArg(path23, maxRows) {
|
|
8978
|
+
if (typeof path23 !== "string" || path23.length === 0) {
|
|
8979
8979
|
return void 0;
|
|
8980
8980
|
}
|
|
8981
|
-
const raw = readFileSync6(
|
|
8981
|
+
const raw = readFileSync6(path23, "utf8");
|
|
8982
8982
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
8983
8983
|
if (lines.length < 2) {
|
|
8984
|
-
failWriteValidation2(`${
|
|
8984
|
+
failWriteValidation2(`${path23} needs a header row and at least one data row`);
|
|
8985
8985
|
}
|
|
8986
8986
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
8987
8987
|
const rows = [];
|
|
@@ -9000,7 +9000,7 @@ function parseListFileArg(path21, maxRows) {
|
|
|
9000
9000
|
}
|
|
9001
9001
|
}
|
|
9002
9002
|
if (rows.length > maxRows) {
|
|
9003
|
-
failWriteValidation2(`${
|
|
9003
|
+
failWriteValidation2(`${path23} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
9004
9004
|
}
|
|
9005
9005
|
return { columns, rows };
|
|
9006
9006
|
}
|
|
@@ -11083,11 +11083,11 @@ var updateStatusSchema = z11.enum(UPDATE_STATUSES);
|
|
|
11083
11083
|
function currencyMinimums2(currencyCode) {
|
|
11084
11084
|
return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
|
|
11085
11085
|
}
|
|
11086
|
-
function validateDailyBudgetFloor(money, ctx,
|
|
11086
|
+
function validateDailyBudgetFloor(money, ctx, path23) {
|
|
11087
11087
|
if (money?.currencyCode) {
|
|
11088
11088
|
const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
|
|
11089
11089
|
if (Number(money.amount) < min) {
|
|
11090
|
-
ctx.addIssue({ code: "custom", path:
|
|
11090
|
+
ctx.addIssue({ code: "custom", path: path23, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
11091
11091
|
}
|
|
11092
11092
|
}
|
|
11093
11093
|
}
|
|
@@ -11575,19 +11575,19 @@ function failWriteValidation3(message) {
|
|
|
11575
11575
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
11576
11576
|
process.exit(1);
|
|
11577
11577
|
}
|
|
11578
|
-
function loadJsonFileArg3(
|
|
11579
|
-
if (typeof
|
|
11578
|
+
function loadJsonFileArg3(path23) {
|
|
11579
|
+
if (typeof path23 !== "string" || path23.length === 0) {
|
|
11580
11580
|
return {};
|
|
11581
11581
|
}
|
|
11582
11582
|
try {
|
|
11583
|
-
const parsed = JSON.parse(readFileSync8(
|
|
11583
|
+
const parsed = JSON.parse(readFileSync8(path23, "utf8"));
|
|
11584
11584
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11585
|
-
failWriteValidation3(`${
|
|
11585
|
+
failWriteValidation3(`${path23} must contain a JSON object`);
|
|
11586
11586
|
}
|
|
11587
11587
|
return parsed;
|
|
11588
11588
|
} catch (err) {
|
|
11589
11589
|
if (err instanceof SyntaxError) {
|
|
11590
|
-
failWriteValidation3(`${
|
|
11590
|
+
failWriteValidation3(`${path23} is not valid JSON: ${err.message}`);
|
|
11591
11591
|
}
|
|
11592
11592
|
throw err;
|
|
11593
11593
|
}
|
|
@@ -14809,12 +14809,12 @@ async function probeDuration(filePath) {
|
|
|
14809
14809
|
}
|
|
14810
14810
|
|
|
14811
14811
|
// src/commands/canvas/rerun.ts
|
|
14812
|
-
import
|
|
14812
|
+
import path13 from "path";
|
|
14813
14813
|
import { defineCommand as defineCommand90 } from "citty";
|
|
14814
14814
|
|
|
14815
14815
|
// src/commands/canvas/run.ts
|
|
14816
|
-
import { readFile as
|
|
14817
|
-
import
|
|
14816
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
14817
|
+
import path12 from "path";
|
|
14818
14818
|
import { defineCommand as defineCommand89 } from "citty";
|
|
14819
14819
|
|
|
14820
14820
|
// src/commands/canvas/placeholders.ts
|
|
@@ -14859,34 +14859,110 @@ function isResolvableRelative(value) {
|
|
|
14859
14859
|
}
|
|
14860
14860
|
|
|
14861
14861
|
// src/commands/canvas/source-version.ts
|
|
14862
|
-
import { readFile as
|
|
14862
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
14863
|
+
import path5 from "path";
|
|
14864
|
+
|
|
14865
|
+
// src/commands/canvas/scene-files.ts
|
|
14866
|
+
import { mkdir, readFile as readFile3, readdir as readdir2, rm, writeFile } from "fs/promises";
|
|
14863
14867
|
import path4 from "path";
|
|
14868
|
+
var SCENES_DIR = "scenes";
|
|
14869
|
+
var GLOBAL_PROMPT_FILE = "prompt.json";
|
|
14870
|
+
var REBUILD_FILE = "prompt.rebuild.json";
|
|
14871
|
+
function sceneFileName(index, total) {
|
|
14872
|
+
const width = Math.max(2, String(Math.max(0, total - 1)).length);
|
|
14873
|
+
return `s${String(index).padStart(width, "0")}.json`;
|
|
14874
|
+
}
|
|
14875
|
+
function splitBlueprint(blueprint) {
|
|
14876
|
+
if (!blueprint || typeof blueprint !== "object") {
|
|
14877
|
+
return { global: {}, scenes: [] };
|
|
14878
|
+
}
|
|
14879
|
+
const { scenes, ...global } = blueprint;
|
|
14880
|
+
return { global, scenes: Array.isArray(scenes) ? scenes : [] };
|
|
14881
|
+
}
|
|
14882
|
+
async function writeSceneFiles(outDir, blueprint) {
|
|
14883
|
+
const { global, scenes } = splitBlueprint(blueprint);
|
|
14884
|
+
await writeFile(path4.join(outDir, GLOBAL_PROMPT_FILE), `${JSON.stringify(global, null, 2)}
|
|
14885
|
+
`, "utf8");
|
|
14886
|
+
const scenesDir = path4.join(outDir, SCENES_DIR);
|
|
14887
|
+
await mkdir(scenesDir, { recursive: true });
|
|
14888
|
+
const written = /* @__PURE__ */ new Set();
|
|
14889
|
+
for (let i = 0; i < scenes.length; i++) {
|
|
14890
|
+
const name = sceneFileName(i, scenes.length);
|
|
14891
|
+
written.add(name);
|
|
14892
|
+
await writeFile(path4.join(scenesDir, name), `${JSON.stringify(scenes[i], null, 2)}
|
|
14893
|
+
`, "utf8");
|
|
14894
|
+
}
|
|
14895
|
+
for (const name of await listSceneFileNames(scenesDir)) {
|
|
14896
|
+
if (!written.has(name)) await rm(path4.join(scenesDir, name), { force: true });
|
|
14897
|
+
}
|
|
14898
|
+
}
|
|
14899
|
+
async function listSceneFileNames(scenesDir) {
|
|
14900
|
+
let entries;
|
|
14901
|
+
try {
|
|
14902
|
+
entries = await readdir2(scenesDir);
|
|
14903
|
+
} catch {
|
|
14904
|
+
return [];
|
|
14905
|
+
}
|
|
14906
|
+
return entries.filter((n) => /^s\d+\.json$/.test(n)).sort(bySceneIndex);
|
|
14907
|
+
}
|
|
14908
|
+
async function listSceneFiles(creativeDir) {
|
|
14909
|
+
const scenesDir = path4.join(creativeDir, SCENES_DIR);
|
|
14910
|
+
return (await listSceneFileNames(scenesDir)).map((n) => path4.join(scenesDir, n));
|
|
14911
|
+
}
|
|
14912
|
+
async function reassembleBlueprint(creativeDir) {
|
|
14913
|
+
const files = await listSceneFiles(creativeDir);
|
|
14914
|
+
if (files.length === 0) return null;
|
|
14915
|
+
const globalRaw = await readFile3(path4.join(creativeDir, GLOBAL_PROMPT_FILE), "utf8");
|
|
14916
|
+
const global = JSON.parse(globalRaw);
|
|
14917
|
+
const scenes = [];
|
|
14918
|
+
for (const file of files) {
|
|
14919
|
+
scenes.push(JSON.parse(await readFile3(file, "utf8")));
|
|
14920
|
+
}
|
|
14921
|
+
return { ...global, scenes };
|
|
14922
|
+
}
|
|
14923
|
+
function bySceneIndex(a, b) {
|
|
14924
|
+
const ai = Number(a.match(/^s(\d+)\.json$/)?.[1] ?? 0);
|
|
14925
|
+
const bi = Number(b.match(/^s(\d+)\.json$/)?.[1] ?? 0);
|
|
14926
|
+
return ai - bi;
|
|
14927
|
+
}
|
|
14928
|
+
|
|
14929
|
+
// src/commands/canvas/source-version.ts
|
|
14864
14930
|
async function computeSourceSha(canvasPath) {
|
|
14865
14931
|
let canvasBytes;
|
|
14866
14932
|
try {
|
|
14867
|
-
canvasBytes = await
|
|
14933
|
+
canvasBytes = await readFile4(canvasPath);
|
|
14868
14934
|
} catch {
|
|
14869
14935
|
return void 0;
|
|
14870
14936
|
}
|
|
14871
|
-
const
|
|
14937
|
+
const canvasDir = path5.dirname(canvasPath);
|
|
14938
|
+
const promptPath = path5.join(canvasDir, "prompt.json");
|
|
14872
14939
|
let promptBytes;
|
|
14873
14940
|
try {
|
|
14874
|
-
promptBytes = await
|
|
14941
|
+
promptBytes = await readFile4(promptPath);
|
|
14875
14942
|
} catch {
|
|
14876
14943
|
promptBytes = Buffer.alloc(0);
|
|
14877
14944
|
}
|
|
14878
|
-
|
|
14879
|
-
Buffer.
|
|
14880
|
-
|
|
14881
|
-
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14945
|
+
const parts = [
|
|
14946
|
+
Buffer.from(`${canvasBytes.length}\0`),
|
|
14947
|
+
canvasBytes,
|
|
14948
|
+
Buffer.from(`${promptBytes.length}\0`),
|
|
14949
|
+
promptBytes
|
|
14950
|
+
];
|
|
14951
|
+
for (const sceneFile of await listSceneFiles(canvasDir)) {
|
|
14952
|
+
let sceneBytes;
|
|
14953
|
+
try {
|
|
14954
|
+
sceneBytes = await readFile4(sceneFile);
|
|
14955
|
+
} catch {
|
|
14956
|
+
sceneBytes = Buffer.alloc(0);
|
|
14957
|
+
}
|
|
14958
|
+
parts.push(Buffer.from(`${sceneBytes.length}\0`), sceneBytes);
|
|
14959
|
+
}
|
|
14960
|
+
return sha256Hex(Buffer.concat(parts));
|
|
14886
14961
|
}
|
|
14887
14962
|
|
|
14888
|
-
// src/commands/canvas/
|
|
14889
|
-
import { readFile as
|
|
14963
|
+
// src/commands/canvas/scene-projection.ts
|
|
14964
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
14965
|
+
import path6 from "path";
|
|
14890
14966
|
|
|
14891
14967
|
// src/engine/scaffold/video.ts
|
|
14892
14968
|
import { toCardinal as nwAr } from "n2words/ar-SA";
|
|
@@ -16902,6 +16978,14 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
16902
16978
|
});
|
|
16903
16979
|
registerMergedPhraseSlices(phrase, clipRef, clipStart, genDur, Boolean(chained), env, out);
|
|
16904
16980
|
}
|
|
16981
|
+
function sumSceneDurations(env, first, last) {
|
|
16982
|
+
let total = 0;
|
|
16983
|
+
for (let s = first; s <= last; s++) {
|
|
16984
|
+
const sc = env.blueprint.scenes[s];
|
|
16985
|
+
if (sc) total += sceneDurationS(sc);
|
|
16986
|
+
}
|
|
16987
|
+
return total;
|
|
16988
|
+
}
|
|
16905
16989
|
function registerMergedPhraseSlices(phrase, clipRef, clipStart, genDur, chained, env, out) {
|
|
16906
16990
|
const shown = [...phrase.shownScenes].sort((a, b) => a - b);
|
|
16907
16991
|
let r = 0;
|
|
@@ -16915,13 +16999,12 @@ function registerMergedPhraseSlices(phrase, clipRef, clipStart, genDur, chained,
|
|
|
16915
16999
|
if (!firstSc) continue;
|
|
16916
17000
|
const firstStart = firstSc.start_s ?? clipStart;
|
|
16917
17001
|
const rawOffset = firstStart - clipStart;
|
|
16918
|
-
const runEnd = env.blueprint.scenes[last]?.end_s ?? firstStart + sceneDurationS(firstSc);
|
|
16919
17002
|
out.sceneSlice.set(first, {
|
|
16920
17003
|
clipRef,
|
|
16921
17004
|
// Snap a sub-frame offset (line-start vs scene-start drift) to 0 so a run that tiles
|
|
16922
17005
|
// the clip hits the whole-clip fast path instead of a needless re-encode + tiny shift.
|
|
16923
17006
|
offset: rawOffset < 0.05 ? 0 : rawOffset,
|
|
16924
|
-
len: Math.max(0.5,
|
|
17007
|
+
len: Math.max(0.5, sumSceneDurations(env, first, last)),
|
|
16925
17008
|
clipDur: genDur,
|
|
16926
17009
|
...firstRegistered && chained ? { continuesFrame: true } : {}
|
|
16927
17010
|
});
|
|
@@ -18114,7 +18197,75 @@ function videoReport(input, elementsInput) {
|
|
|
18114
18197
|
};
|
|
18115
18198
|
}
|
|
18116
18199
|
|
|
18200
|
+
// src/commands/canvas/scene-projection.ts
|
|
18201
|
+
var PROMPT_NODE_TYPES = /* @__PURE__ */ new Set(["image_generate", "video_generate"]);
|
|
18202
|
+
function promptNodes(canvas) {
|
|
18203
|
+
const out = /* @__PURE__ */ new Map();
|
|
18204
|
+
const nodes = canvas?.nodes;
|
|
18205
|
+
if (!Array.isArray(nodes)) return out;
|
|
18206
|
+
for (const n of nodes) {
|
|
18207
|
+
if (typeof n?.id === "string" && typeof n?.type === "string" && PROMPT_NODE_TYPES.has(n.type)) {
|
|
18208
|
+
out.set(n.id, n);
|
|
18209
|
+
}
|
|
18210
|
+
}
|
|
18211
|
+
return out;
|
|
18212
|
+
}
|
|
18213
|
+
function nonPromptParamsDiverge(live = {}, rebuilt = {}) {
|
|
18214
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(live), ...Object.keys(rebuilt)]);
|
|
18215
|
+
keys.delete("prompt");
|
|
18216
|
+
for (const k of keys) {
|
|
18217
|
+
if (JSON.stringify(live[k]) !== JSON.stringify(rebuilt[k])) return true;
|
|
18218
|
+
}
|
|
18219
|
+
return false;
|
|
18220
|
+
}
|
|
18221
|
+
async function syncSceneNodeParams(canvas, canvasPath, log) {
|
|
18222
|
+
const creativeDir = path6.dirname(canvasPath);
|
|
18223
|
+
const blueprint = await reassembleBlueprint(creativeDir);
|
|
18224
|
+
if (!blueprint) return "not_applicable";
|
|
18225
|
+
let rebuildRaw;
|
|
18226
|
+
try {
|
|
18227
|
+
rebuildRaw = await readFile5(path6.join(creativeDir, REBUILD_FILE), "utf8");
|
|
18228
|
+
} catch {
|
|
18229
|
+
return "not_applicable";
|
|
18230
|
+
}
|
|
18231
|
+
const { elements, opts } = JSON.parse(rebuildRaw);
|
|
18232
|
+
const rebuilt = scaffoldVideoCanvas(blueprint, elements, opts);
|
|
18233
|
+
const rebuiltNodes = promptNodes(rebuilt);
|
|
18234
|
+
const liveNodes = promptNodes(canvas);
|
|
18235
|
+
const missingInLive = [...rebuiltNodes.keys()].filter((id) => !liveNodes.has(id));
|
|
18236
|
+
const missingInRebuilt = [...liveNodes.keys()].filter((id) => !rebuiltNodes.has(id));
|
|
18237
|
+
if (missingInLive.length > 0 || missingInRebuilt.length > 0) {
|
|
18238
|
+
log(
|
|
18239
|
+
"[scenes] scene structure changed since scaffold (a scene's route, timing, or count differs) \u2014 re-run `baker canvas scaffold-video` to rebuild the graph. Prompt-only edits were still applied where the nodes still match."
|
|
18240
|
+
);
|
|
18241
|
+
}
|
|
18242
|
+
let changed = 0;
|
|
18243
|
+
let structural = false;
|
|
18244
|
+
for (const [id, rebuiltNode] of rebuiltNodes) {
|
|
18245
|
+
const live = liveNodes.get(id);
|
|
18246
|
+
if (!live) continue;
|
|
18247
|
+
const liveParams = live.params ??= {};
|
|
18248
|
+
const rebuiltParams = rebuiltNode.params ?? {};
|
|
18249
|
+
if (nonPromptParamsDiverge(liveParams, rebuiltParams)) structural = true;
|
|
18250
|
+
if (typeof rebuiltParams.prompt === "string" && liveParams.prompt !== rebuiltParams.prompt) {
|
|
18251
|
+
liveParams.prompt = rebuiltParams.prompt;
|
|
18252
|
+
changed++;
|
|
18253
|
+
}
|
|
18254
|
+
}
|
|
18255
|
+
if (structural) {
|
|
18256
|
+
log(
|
|
18257
|
+
"[scenes] a scene edit changed clip timing or audio (not just prompt text) \u2014 the graph's timing nodes were NOT updated; re-run `baker canvas scaffold-video` so the spine and voice tracks match."
|
|
18258
|
+
);
|
|
18259
|
+
}
|
|
18260
|
+
if (changed === 0) return "up_to_date";
|
|
18261
|
+
log(
|
|
18262
|
+
`[scenes] re-flowed ${changed} scene prompt(s) from scenes/*.json into the graph \u2014 the affected frames/clips will re-bill on the next run`
|
|
18263
|
+
);
|
|
18264
|
+
return "regenerated";
|
|
18265
|
+
}
|
|
18266
|
+
|
|
18117
18267
|
// src/commands/canvas/style-projection.ts
|
|
18268
|
+
import { readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
18118
18269
|
function findBlueprintProjection(canvas) {
|
|
18119
18270
|
if (!canvas || typeof canvas !== "object") return null;
|
|
18120
18271
|
const nodes = canvas.nodes;
|
|
@@ -18142,10 +18293,10 @@ function renderStyleProjectionFromValue(blueprint) {
|
|
|
18142
18293
|
async function syncStyleProjection(canvas, log) {
|
|
18143
18294
|
const pair = findBlueprintProjection(canvas);
|
|
18144
18295
|
if (!pair) return "not_applicable";
|
|
18145
|
-
const rendered = renderStyleProjection(await
|
|
18146
|
-
const current = await
|
|
18296
|
+
const rendered = renderStyleProjection(await readFile6(pair.promptPath, "utf8"));
|
|
18297
|
+
const current = await readFile6(pair.stylePath, "utf8").catch(() => null);
|
|
18147
18298
|
if (current === rendered) return "up_to_date";
|
|
18148
|
-
await
|
|
18299
|
+
await writeFile2(pair.stylePath, rendered, "utf8");
|
|
18149
18300
|
log(
|
|
18150
18301
|
"[style] prompt.style.json regenerated from prompt.json \u2014 every frame's shared ad spec changed; affected image frames will re-bill on the next run"
|
|
18151
18302
|
);
|
|
@@ -18205,13 +18356,13 @@ ${body}` : header || body || compactJson(record);
|
|
|
18205
18356
|
}
|
|
18206
18357
|
|
|
18207
18358
|
// src/commands/canvas/run-record.ts
|
|
18208
|
-
import
|
|
18359
|
+
import path7 from "path";
|
|
18209
18360
|
var MAX_RUN_NODES = 200;
|
|
18210
18361
|
var MAX_OUTPUTS_PER_NODE = 10;
|
|
18211
18362
|
var MAX_FINAL_OUTPUTS = 10;
|
|
18212
18363
|
var MAX_CREATIVE_SLUG_LENGTH = 100;
|
|
18213
18364
|
function creativeSlugFromCanvasPath(filePath) {
|
|
18214
|
-
const normalized = filePath.split(
|
|
18365
|
+
const normalized = filePath.split(path7.sep).join("/");
|
|
18215
18366
|
const match = normalized.match(/(?:^|\/)src\/creatives\/([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\//);
|
|
18216
18367
|
const slug = match?.[1] ?? null;
|
|
18217
18368
|
return slug && slug.length <= MAX_CREATIVE_SLUG_LENGTH ? slug : null;
|
|
@@ -18495,25 +18646,25 @@ var RunRecordPoster = class {
|
|
|
18495
18646
|
};
|
|
18496
18647
|
|
|
18497
18648
|
// src/commands/canvas/run-retention.ts
|
|
18498
|
-
import { rm } from "fs/promises";
|
|
18499
|
-
import
|
|
18649
|
+
import { rm as rm2 } from "fs/promises";
|
|
18650
|
+
import path8 from "path";
|
|
18500
18651
|
function runDirsToPrune(entries, keep, currentRunId) {
|
|
18501
18652
|
const runs = entries.filter((e) => /^r_[0-9A-Za-z]+$/.test(e) && e !== currentRunId).sort();
|
|
18502
18653
|
if (keep <= 0) return runs;
|
|
18503
18654
|
return runs.slice(0, Math.max(0, runs.length - keep));
|
|
18504
18655
|
}
|
|
18505
18656
|
async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
|
|
18506
|
-
const { readdir:
|
|
18657
|
+
const { readdir: readdir7 } = await import("fs/promises");
|
|
18507
18658
|
let entries;
|
|
18508
18659
|
try {
|
|
18509
|
-
entries = await
|
|
18660
|
+
entries = await readdir7(outputsDir);
|
|
18510
18661
|
} catch {
|
|
18511
18662
|
return;
|
|
18512
18663
|
}
|
|
18513
18664
|
const toPrune = runDirsToPrune(entries, keep, currentRunId);
|
|
18514
18665
|
if (toPrune.length === 0) return;
|
|
18515
18666
|
for (const dir of toPrune) {
|
|
18516
|
-
await
|
|
18667
|
+
await rm2(path8.join(outputsDir, dir), { recursive: true, force: true }).catch(
|
|
18517
18668
|
(e) => log(`[prune ] could not remove ${dir}: ${e.message}`)
|
|
18518
18669
|
);
|
|
18519
18670
|
}
|
|
@@ -18521,34 +18672,34 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
|
|
|
18521
18672
|
}
|
|
18522
18673
|
|
|
18523
18674
|
// src/commands/canvas/dirty-marker.ts
|
|
18524
|
-
import { mkdir, readdir as
|
|
18525
|
-
import
|
|
18675
|
+
import { mkdir as mkdir2, readdir as readdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
18676
|
+
import path9 from "path";
|
|
18526
18677
|
function creativeDirtyDir(base) {
|
|
18527
|
-
return base ??
|
|
18678
|
+
return base ?? path9.resolve("canvas", ".dirty");
|
|
18528
18679
|
}
|
|
18529
18680
|
function dirtyMarkerFile(slug, base) {
|
|
18530
|
-
return
|
|
18681
|
+
return path9.join(creativeDirtyDir(base), `${slug}.json`);
|
|
18531
18682
|
}
|
|
18532
18683
|
async function clearCreativeDirty(slug, base) {
|
|
18533
18684
|
try {
|
|
18534
|
-
await
|
|
18685
|
+
await rm3(dirtyMarkerFile(slug, base), { force: true });
|
|
18535
18686
|
} catch {
|
|
18536
18687
|
}
|
|
18537
18688
|
}
|
|
18538
18689
|
|
|
18539
18690
|
// src/commands/canvas/run-resume.ts
|
|
18540
|
-
import { mkdir as
|
|
18541
|
-
import
|
|
18691
|
+
import { mkdir as mkdir3, readFile as readFile7, rm as rm4, writeFile as writeFile4 } from "fs/promises";
|
|
18692
|
+
import path10 from "path";
|
|
18542
18693
|
function markerKey(canvasPath) {
|
|
18543
18694
|
const slug = creativeSlugFromCanvasPath(canvasPath);
|
|
18544
|
-
const identity2 = slug ??
|
|
18695
|
+
const identity2 = slug ?? path10.relative(process.cwd(), path10.resolve(canvasPath));
|
|
18545
18696
|
return sha256Hex(Buffer.from(identity2)).slice(0, 32);
|
|
18546
18697
|
}
|
|
18547
18698
|
function legacyMarkerKey(canvasPath) {
|
|
18548
|
-
return sha256Hex(Buffer.from(
|
|
18699
|
+
return sha256Hex(Buffer.from(path10.resolve(canvasPath))).slice(0, 32);
|
|
18549
18700
|
}
|
|
18550
18701
|
function markerFile(outputsDir, key) {
|
|
18551
|
-
return
|
|
18702
|
+
return path10.join(outputsDir, ".inflight", `${key}.json`);
|
|
18552
18703
|
}
|
|
18553
18704
|
var REMOTE_ADOPT_STALE_MS = 12e4;
|
|
18554
18705
|
function classifyRemoteRun(run, now) {
|
|
@@ -18584,7 +18735,7 @@ async function resolveRunId(opts) {
|
|
|
18584
18735
|
async function readMarkerRunId(outputsDir, canvasPath) {
|
|
18585
18736
|
for (const key of [markerKey(canvasPath), legacyMarkerKey(canvasPath)]) {
|
|
18586
18737
|
try {
|
|
18587
|
-
const raw = await
|
|
18738
|
+
const raw = await readFile7(markerFile(outputsDir, key), "utf8");
|
|
18588
18739
|
const parsed = JSON.parse(raw);
|
|
18589
18740
|
if (typeof parsed.runId === "string" && parsed.runId.length > 0) return parsed.runId;
|
|
18590
18741
|
} catch {
|
|
@@ -18595,23 +18746,23 @@ async function readMarkerRunId(outputsDir, canvasPath) {
|
|
|
18595
18746
|
async function markRunInFlight(outputsDir, canvasPath, runId) {
|
|
18596
18747
|
try {
|
|
18597
18748
|
const file = markerFile(outputsDir, markerKey(canvasPath));
|
|
18598
|
-
await
|
|
18599
|
-
await
|
|
18749
|
+
await mkdir3(path10.dirname(file), { recursive: true });
|
|
18750
|
+
await writeFile4(file, JSON.stringify({ runId, canvasPath: path10.resolve(canvasPath), startedAt: Date.now() }));
|
|
18600
18751
|
} catch {
|
|
18601
18752
|
}
|
|
18602
18753
|
}
|
|
18603
18754
|
async function clearRunMarker(outputsDir, canvasPath) {
|
|
18604
18755
|
for (const key of [markerKey(canvasPath), legacyMarkerKey(canvasPath)]) {
|
|
18605
18756
|
try {
|
|
18606
|
-
await
|
|
18757
|
+
await rm4(markerFile(outputsDir, key), { force: true });
|
|
18607
18758
|
} catch {
|
|
18608
18759
|
}
|
|
18609
18760
|
}
|
|
18610
18761
|
}
|
|
18611
18762
|
|
|
18612
18763
|
// src/commands/canvas/run-snapshot.ts
|
|
18613
|
-
import { mkdir as
|
|
18614
|
-
import
|
|
18764
|
+
import { mkdir as mkdir4, readdir as readdir4, readFile as readFile8, stat as stat2, writeFile as writeFile5 } from "fs/promises";
|
|
18765
|
+
import path11 from "path";
|
|
18615
18766
|
var SNAPSHOT_SCHEMA = "baker-canvas-snapshot/1";
|
|
18616
18767
|
var MAX_SNAPSHOT_FILE_BYTES = 32 * 1024 * 1024;
|
|
18617
18768
|
var EXT_TO_MIME = {
|
|
@@ -18638,11 +18789,11 @@ var EXT_TO_MIME = {
|
|
|
18638
18789
|
woff2: "font/woff2"
|
|
18639
18790
|
};
|
|
18640
18791
|
function mimeForFile(filePath) {
|
|
18641
|
-
const ext =
|
|
18792
|
+
const ext = path11.extname(filePath).slice(1).toLowerCase();
|
|
18642
18793
|
return EXT_TO_MIME[ext] ?? "application/octet-stream";
|
|
18643
18794
|
}
|
|
18644
18795
|
function toPosix(p) {
|
|
18645
|
-
return p.split(
|
|
18796
|
+
return p.split(path11.sep).join("/");
|
|
18646
18797
|
}
|
|
18647
18798
|
function localPathRefsFromCanvas(parsed) {
|
|
18648
18799
|
const nodes = parsed?.nodes;
|
|
@@ -18664,32 +18815,40 @@ function localPathRefsFromCanvas(parsed) {
|
|
|
18664
18815
|
function isSnapshotablePath(value) {
|
|
18665
18816
|
return typeof value === "string" && value.length > 0 && !value.includes("[TODO");
|
|
18666
18817
|
}
|
|
18818
|
+
async function sourceRefsToSnapshot(canvasDir, parsed) {
|
|
18819
|
+
const refs = new Set(localPathRefsFromCanvas(parsed));
|
|
18820
|
+
for (const sceneFile of await listSceneFiles(canvasDir)) {
|
|
18821
|
+
refs.add(toPosix(path11.relative(canvasDir, sceneFile)));
|
|
18822
|
+
}
|
|
18823
|
+
refs.add(REBUILD_FILE);
|
|
18824
|
+
return [...refs];
|
|
18825
|
+
}
|
|
18667
18826
|
async function uploadRunSnapshot(client, opts) {
|
|
18668
18827
|
try {
|
|
18669
|
-
const canvasDir =
|
|
18828
|
+
const canvasDir = path11.dirname(opts.canvasPath);
|
|
18670
18829
|
const put = (bytes, mime) => putContentAddressed(client, bytes, mime, opts.signal);
|
|
18671
18830
|
const canvasBytes = Buffer.from(opts.raw);
|
|
18672
18831
|
const canvasUpload = await put(canvasBytes, "application/json");
|
|
18673
18832
|
const files = [];
|
|
18674
18833
|
const skipped = [];
|
|
18675
|
-
for (const refPath of
|
|
18676
|
-
const abs =
|
|
18834
|
+
for (const refPath of await sourceRefsToSnapshot(canvasDir, opts.parsed)) {
|
|
18835
|
+
const abs = path11.isAbsolute(refPath) ? refPath : path11.resolve(canvasDir, refPath);
|
|
18677
18836
|
let st;
|
|
18678
18837
|
try {
|
|
18679
18838
|
st = await stat2(abs);
|
|
18680
18839
|
} catch {
|
|
18681
|
-
skipped.push({ path: toPosix(
|
|
18840
|
+
skipped.push({ path: toPosix(path11.relative(canvasDir, abs)), reason: "missing" });
|
|
18682
18841
|
continue;
|
|
18683
18842
|
}
|
|
18684
18843
|
const fileList = st.isDirectory() ? await listFilesRecursive(abs) : [abs];
|
|
18685
18844
|
for (const file of fileList) {
|
|
18686
|
-
const rel = toPosix(
|
|
18845
|
+
const rel = toPosix(path11.relative(canvasDir, file));
|
|
18687
18846
|
const size = (await stat2(file)).size;
|
|
18688
18847
|
if (size > MAX_SNAPSHOT_FILE_BYTES) {
|
|
18689
18848
|
skipped.push({ path: rel, reason: `too large (${size} bytes)` });
|
|
18690
18849
|
continue;
|
|
18691
18850
|
}
|
|
18692
|
-
const bytes = await
|
|
18851
|
+
const bytes = await readFile8(file);
|
|
18693
18852
|
const upload = await put(bytes, mimeForFile(file));
|
|
18694
18853
|
files.push({ path: rel, sha256: upload.sha256, url: upload.url });
|
|
18695
18854
|
}
|
|
@@ -18698,7 +18857,7 @@ async function uploadRunSnapshot(client, opts) {
|
|
|
18698
18857
|
schema: SNAPSHOT_SCHEMA,
|
|
18699
18858
|
creativeSlug: opts.creativeSlug,
|
|
18700
18859
|
canvasSha: canvasUpload.sha256,
|
|
18701
|
-
canvas: { path:
|
|
18860
|
+
canvas: { path: path11.basename(opts.canvasPath), sha256: canvasUpload.sha256, url: canvasUpload.url },
|
|
18702
18861
|
files,
|
|
18703
18862
|
skipped: skipped.length > 0 ? skipped : void 0
|
|
18704
18863
|
};
|
|
@@ -18724,8 +18883,8 @@ async function putContentAddressed(client, bytes, mime, signal) {
|
|
|
18724
18883
|
return { sha256, url: publicUrl };
|
|
18725
18884
|
}
|
|
18726
18885
|
async function listFilesRecursive(dir) {
|
|
18727
|
-
const entries = await
|
|
18728
|
-
return entries.filter((d) => d.isFile()).map((d) =>
|
|
18886
|
+
const entries = await readdir4(dir, { recursive: true, withFileTypes: true });
|
|
18887
|
+
return entries.filter((d) => d.isFile()).map((d) => path11.join(d.parentPath, d.name));
|
|
18729
18888
|
}
|
|
18730
18889
|
var SnapshotConflictError = class extends Error {
|
|
18731
18890
|
conflicts;
|
|
@@ -18737,19 +18896,19 @@ var SnapshotConflictError = class extends Error {
|
|
|
18737
18896
|
};
|
|
18738
18897
|
async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
|
|
18739
18898
|
const entries = [manifest.canvas, ...manifest.files];
|
|
18740
|
-
const resolvedTarget =
|
|
18899
|
+
const resolvedTarget = path11.resolve(targetDir);
|
|
18741
18900
|
const planned = [];
|
|
18742
18901
|
const conflicts = [];
|
|
18743
18902
|
const upToDate = [];
|
|
18744
18903
|
for (const entry of entries) {
|
|
18745
|
-
if (
|
|
18904
|
+
if (path11.isAbsolute(entry.path) || entry.path.split("/").includes("..")) {
|
|
18746
18905
|
throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
|
|
18747
18906
|
}
|
|
18748
|
-
const target =
|
|
18749
|
-
if (target !== resolvedTarget && !target.startsWith(resolvedTarget +
|
|
18907
|
+
const target = path11.resolve(resolvedTarget, entry.path);
|
|
18908
|
+
if (target !== resolvedTarget && !target.startsWith(resolvedTarget + path11.sep)) {
|
|
18750
18909
|
throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
|
|
18751
18910
|
}
|
|
18752
|
-
const existing = await
|
|
18911
|
+
const existing = await readFile8(target).catch(() => null);
|
|
18753
18912
|
if (existing) {
|
|
18754
18913
|
if (sha256Hex(existing) === entry.sha256) {
|
|
18755
18914
|
upToDate.push(entry.path);
|
|
@@ -18771,11 +18930,11 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
|
|
|
18771
18930
|
if (sha256Hex(bytes) !== entry.sha256) {
|
|
18772
18931
|
throw new Error(`snapshot download for ${entry.path} does not match its recorded sha256`);
|
|
18773
18932
|
}
|
|
18774
|
-
await
|
|
18775
|
-
await
|
|
18933
|
+
await mkdir4(path11.dirname(target), { recursive: true });
|
|
18934
|
+
await writeFile5(target, bytes);
|
|
18776
18935
|
restored.push(entry.path);
|
|
18777
18936
|
}
|
|
18778
|
-
return { canvasPath:
|
|
18937
|
+
return { canvasPath: path11.resolve(resolvedTarget, manifest.canvas.path), restored, upToDate };
|
|
18779
18938
|
}
|
|
18780
18939
|
|
|
18781
18940
|
// src/commands/canvas/run.ts
|
|
@@ -18855,8 +19014,8 @@ function resolveMaxCredits(...candidates) {
|
|
|
18855
19014
|
return void 0;
|
|
18856
19015
|
}
|
|
18857
19016
|
async function executeCanvasRun(opts) {
|
|
18858
|
-
const filePath =
|
|
18859
|
-
const raw = await
|
|
19017
|
+
const filePath = path12.resolve(opts.file);
|
|
19018
|
+
const raw = await readFile9(filePath, "utf8");
|
|
18860
19019
|
let parsed;
|
|
18861
19020
|
try {
|
|
18862
19021
|
parsed = JSON.parse(raw);
|
|
@@ -18868,7 +19027,7 @@ async function executeCanvasRun(opts) {
|
|
|
18868
19027
|
}
|
|
18869
19028
|
const attemptedSlug = creativeSlugFromCanvasPath(filePath);
|
|
18870
19029
|
if (attemptedSlug) await clearCreativeDirty(attemptedSlug);
|
|
18871
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
19030
|
+
parsed = resolveRelativeCanvasPaths(parsed, path12.dirname(filePath));
|
|
18872
19031
|
try {
|
|
18873
19032
|
await syncStyleProjection(parsed, (line) => process.stderr.write(`${line}
|
|
18874
19033
|
`));
|
|
@@ -18883,6 +19042,24 @@ async function executeCanvasRun(opts) {
|
|
|
18883
19042
|
null,
|
|
18884
19043
|
2
|
|
18885
19044
|
)}
|
|
19045
|
+
`
|
|
19046
|
+
);
|
|
19047
|
+
process.exit(2);
|
|
19048
|
+
}
|
|
19049
|
+
try {
|
|
19050
|
+
await syncSceneNodeParams(parsed, filePath, (line) => process.stderr.write(`${line}
|
|
19051
|
+
`));
|
|
19052
|
+
} catch (e) {
|
|
19053
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
19054
|
+
process.stderr.write(
|
|
19055
|
+
`${JSON.stringify(
|
|
19056
|
+
{
|
|
19057
|
+
ok: false,
|
|
19058
|
+
error: { code: "scene_projection", message: `a scenes/*.json file is not readable/valid or its rebuild failed: ${msg}` }
|
|
19059
|
+
},
|
|
19060
|
+
null,
|
|
19061
|
+
2
|
|
19062
|
+
)}
|
|
18886
19063
|
`
|
|
18887
19064
|
);
|
|
18888
19065
|
process.exit(2);
|
|
@@ -18952,7 +19129,7 @@ async function executeCanvasRun(opts) {
|
|
|
18952
19129
|
const canvasSha = sha256Hex(Buffer.from(raw));
|
|
18953
19130
|
const creativeSlug = creativeSlugFromCanvasPath(filePath) ?? void 0;
|
|
18954
19131
|
const client = opts.record === false ? null : buildBackendClient();
|
|
18955
|
-
const outputsDir = opts.outputsDir ?
|
|
19132
|
+
const outputsDir = opts.outputsDir ? path12.resolve(opts.outputsDir) : path12.resolve("canvas");
|
|
18956
19133
|
const { runId, resumed, source, concurrentRunId } = await resolveRunId({
|
|
18957
19134
|
explicitRunId: opts.runId,
|
|
18958
19135
|
fresh: opts.fresh === true,
|
|
@@ -18986,7 +19163,7 @@ async function executeCanvasRun(opts) {
|
|
|
18986
19163
|
const canvasSnapshotUrl = client && creativeSlug ? await uploadRunSnapshot(client, { canvasPath: filePath, raw, creativeSlug, parsed }) ?? void 0 : void 0;
|
|
18987
19164
|
const recordMeta = {
|
|
18988
19165
|
creativeSlug,
|
|
18989
|
-
canvasPath:
|
|
19166
|
+
canvasPath: path12.relative(process.cwd(), filePath) || void 0,
|
|
18990
19167
|
canvasSha,
|
|
18991
19168
|
// The fingerprint the dashboard compares against the current source to flag
|
|
18992
19169
|
// "edited since last render". Computed from the on-disk canvas.json +
|
|
@@ -19173,7 +19350,7 @@ var rerunCommand = defineCommand90({
|
|
|
19173
19350
|
if (latest.canvasSha && manifest.canvasSha !== latest.canvasSha) {
|
|
19174
19351
|
fail("snapshot_mismatch", `snapshot manifest for run ${latest.runId} does not match its recorded canvas sha`);
|
|
19175
19352
|
}
|
|
19176
|
-
const targetDir =
|
|
19353
|
+
const targetDir = path13.resolve("src", "creatives", slug);
|
|
19177
19354
|
let restoredCanvasPath;
|
|
19178
19355
|
try {
|
|
19179
19356
|
const restore = await restoreRunSnapshot(manifest, targetDir, { force: args["force-remote"] === true });
|
|
@@ -19222,8 +19399,8 @@ async function fetchManifest(url) {
|
|
|
19222
19399
|
}
|
|
19223
19400
|
|
|
19224
19401
|
// src/commands/canvas/scaffold-static-ad.ts
|
|
19225
|
-
import { access, mkdir as
|
|
19226
|
-
import
|
|
19402
|
+
import { access, mkdir as mkdir5, readFile as readFile11, writeFile as writeFile6 } from "fs/promises";
|
|
19403
|
+
import path17 from "path";
|
|
19227
19404
|
import { defineCommand as defineCommand92 } from "citty";
|
|
19228
19405
|
|
|
19229
19406
|
// src/engine/scaffold/staticAd.ts
|
|
@@ -19467,7 +19644,7 @@ function staticAdReport(input, elementsInput, opts) {
|
|
|
19467
19644
|
}
|
|
19468
19645
|
|
|
19469
19646
|
// src/commands/canvas/creative-definition.ts
|
|
19470
|
-
import
|
|
19647
|
+
import path14 from "path";
|
|
19471
19648
|
var PLATFORM_VALUES = ["meta", "google", "linkedin", "tiktok", "youtube", "x", "other"];
|
|
19472
19649
|
var FORMAT_VALUES = ["1:1", "4:5", "9:16", "16:9", "1.91:1"];
|
|
19473
19650
|
function titleFromSlug(slug) {
|
|
@@ -19509,16 +19686,16 @@ function buildCreativeDefinition(input) {
|
|
|
19509
19686
|
}
|
|
19510
19687
|
|
|
19511
19688
|
// src/commands/canvas/scaffold-static-ad-paths.ts
|
|
19512
|
-
import
|
|
19689
|
+
import path15 from "path";
|
|
19513
19690
|
function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd(), slug) {
|
|
19514
19691
|
const file = rawFile.trim();
|
|
19515
19692
|
const imageIsUrl = /^https?:\/\//i.test(file);
|
|
19516
|
-
const imageSource = imageIsUrl ? file :
|
|
19517
|
-
const outPath = out ?
|
|
19518
|
-
const blueprintPath =
|
|
19519
|
-
const creativeDir = slug ?
|
|
19520
|
-
const definitionPath = creativeDir ?
|
|
19521
|
-
const referencesDir = creativeDir ?
|
|
19693
|
+
const imageSource = imageIsUrl ? file : path15.resolve(cwd, file);
|
|
19694
|
+
const outPath = out ? path15.resolve(cwd, out) : slug ? path15.join(cwd, "src", "creatives", slug, `${slug}.canvas.json`) : imageIsUrl ? path15.join(cwd, "static-ad.canvas.json") : path15.join(path15.dirname(imageSource), "static-ad.canvas.json");
|
|
19695
|
+
const blueprintPath = path15.join(path15.dirname(outPath), "prompt.json");
|
|
19696
|
+
const creativeDir = slug ? path15.dirname(outPath) : null;
|
|
19697
|
+
const definitionPath = creativeDir ? path15.join(creativeDir, "_definition.md") : null;
|
|
19698
|
+
const referencesDir = creativeDir ? path15.join(creativeDir, "references") : null;
|
|
19522
19699
|
return { imageIsUrl, imageSource, outPath, blueprintPath, creativeDir, definitionPath, referencesDir };
|
|
19523
19700
|
}
|
|
19524
19701
|
var SCAFFOLD_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
@@ -19528,8 +19705,8 @@ function isValidScaffoldSlug(slug) {
|
|
|
19528
19705
|
}
|
|
19529
19706
|
|
|
19530
19707
|
// src/commands/canvas/sync-definition.ts
|
|
19531
|
-
import { readdir as
|
|
19532
|
-
import
|
|
19708
|
+
import { readdir as readdir5, readFile as readFile10, stat as stat3 } from "fs/promises";
|
|
19709
|
+
import path16 from "path";
|
|
19533
19710
|
import { defineCommand as defineCommand91 } from "citty";
|
|
19534
19711
|
|
|
19535
19712
|
// src/commands/canvas/definition-graph.ts
|
|
@@ -19622,26 +19799,26 @@ async function syncCreativeDefinitionBestEffort(input) {
|
|
|
19622
19799
|
}
|
|
19623
19800
|
}
|
|
19624
19801
|
async function resolveCanvasPath(inputPath) {
|
|
19625
|
-
const resolved =
|
|
19802
|
+
const resolved = path16.resolve(inputPath);
|
|
19626
19803
|
let dir = resolved;
|
|
19627
19804
|
try {
|
|
19628
19805
|
if ((await stat3(resolved)).isFile()) {
|
|
19629
19806
|
if (resolved.endsWith(".canvas.json")) return resolved;
|
|
19630
|
-
dir =
|
|
19807
|
+
dir = path16.dirname(resolved);
|
|
19631
19808
|
}
|
|
19632
19809
|
} catch {
|
|
19633
|
-
dir = resolved.endsWith(".canvas.json") ?
|
|
19810
|
+
dir = resolved.endsWith(".canvas.json") ? path16.dirname(resolved) : resolved;
|
|
19634
19811
|
}
|
|
19635
19812
|
let entries;
|
|
19636
19813
|
try {
|
|
19637
|
-
entries = await
|
|
19814
|
+
entries = await readdir5(dir);
|
|
19638
19815
|
} catch {
|
|
19639
19816
|
return null;
|
|
19640
19817
|
}
|
|
19641
19818
|
const canvases = entries.filter((name) => name.endsWith(".canvas.json"));
|
|
19642
19819
|
const slug = creativeSlugFromCanvasPath(`${dir}/x/`);
|
|
19643
19820
|
const chosen = (slug ? canvases.find((name) => name === `${slug}.canvas.json`) : void 0) ?? canvases[0];
|
|
19644
|
-
return chosen ?
|
|
19821
|
+
return chosen ? path16.join(dir, chosen) : null;
|
|
19645
19822
|
}
|
|
19646
19823
|
var syncDefinitionCommand = defineCommand91({
|
|
19647
19824
|
meta: {
|
|
@@ -19664,7 +19841,7 @@ var syncDefinitionCommand = defineCommand91({
|
|
|
19664
19841
|
if (!slug) return;
|
|
19665
19842
|
let canvas;
|
|
19666
19843
|
try {
|
|
19667
|
-
canvas = JSON.parse(await
|
|
19844
|
+
canvas = JSON.parse(await readFile10(canvasPath, "utf8"));
|
|
19668
19845
|
} catch {
|
|
19669
19846
|
return;
|
|
19670
19847
|
}
|
|
@@ -19689,7 +19866,7 @@ async function uploadSourceAsReference(source, isUrl, client) {
|
|
|
19689
19866
|
if (!res.ok) throw new Error(`failed to download source image (${res.status})`);
|
|
19690
19867
|
bytes = Buffer.from(await res.arrayBuffer());
|
|
19691
19868
|
} else {
|
|
19692
|
-
bytes = await
|
|
19869
|
+
bytes = await readFile11(source);
|
|
19693
19870
|
}
|
|
19694
19871
|
const safe = await toModelSafeImage(bytes);
|
|
19695
19872
|
const sha256 = sha256Hex(safe.bytes);
|
|
@@ -19744,7 +19921,7 @@ DROP background extras, decorative props, generic scenery, and anything small or
|
|
|
19744
19921
|
For each kept element return: { "type": one of logo|product|person|animal|badge, "label": a short UPPER_SNAKE_CASE name (e.g. LOGO, PRODUCT, HERO_DOG, TRUSTPILOT), "description": a concrete reusable description to source/shoot the real asset (include the exact expression for a living subject, and its castable attributes \u2014 breed/species for an animal, apparent age band, apparent origin/ethnicity, and wardrobe/setting for a person \u2014 so it can be recast to fit OUR audience/market), "expression": the facial expression for a living subject or null, "reason": why it is identity-critical, "locator": the blueprint entry this element came from as { "collection": one of "subjects" | "people" | "brands_logos", "index": its 0-based position in that array } (people -> people; logos/badges -> brands_logos; products/animals/objects -> subjects). Output ONLY the JSON object.`;
|
|
19745
19922
|
async function loadAssetText(ref, label) {
|
|
19746
19923
|
const r = ref;
|
|
19747
|
-
if (typeof r?.path === "string") return
|
|
19924
|
+
if (typeof r?.path === "string") return readFile11(r.path, "utf8");
|
|
19748
19925
|
if (typeof r?.url === "string") {
|
|
19749
19926
|
const res = await fetch(r.url);
|
|
19750
19927
|
if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
|
|
@@ -19897,7 +20074,7 @@ var scaffoldStaticAdCommand = defineCommand92({
|
|
|
19897
20074
|
process.cwd(),
|
|
19898
20075
|
slug
|
|
19899
20076
|
);
|
|
19900
|
-
await
|
|
20077
|
+
await mkdir5(path17.dirname(outPath), { recursive: true });
|
|
19901
20078
|
const { describeModel, selectModel, layoutModel, genModel } = resolveModels(args);
|
|
19902
20079
|
let durableSourceUrl;
|
|
19903
20080
|
if (referencesDir) {
|
|
@@ -19923,7 +20100,7 @@ var scaffoldStaticAdCommand = defineCommand92({
|
|
|
19923
20100
|
if (layout && annotated && typeof annotated === "object") {
|
|
19924
20101
|
annotated.layout = layout;
|
|
19925
20102
|
}
|
|
19926
|
-
await
|
|
20103
|
+
await writeFile6(blueprintPath, `${JSON.stringify(annotated, null, 2)}
|
|
19927
20104
|
`, "utf8");
|
|
19928
20105
|
let canvasImagePath = imageSource;
|
|
19929
20106
|
let canvasImageIsUrl = imageIsUrl;
|
|
@@ -19960,10 +20137,10 @@ var scaffoldStaticAdCommand = defineCommand92({
|
|
|
19960
20137
|
);
|
|
19961
20138
|
process.exit(2);
|
|
19962
20139
|
}
|
|
19963
|
-
await
|
|
20140
|
+
await writeFile6(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
19964
20141
|
`, "utf8");
|
|
19965
20142
|
if (definitionPath && !await fileExists(definitionPath)) {
|
|
19966
|
-
await
|
|
20143
|
+
await writeFile6(
|
|
19967
20144
|
definitionPath,
|
|
19968
20145
|
buildCreativeDefinition({
|
|
19969
20146
|
title: args.title ? String(args.title) : titleFromSlug(slug ?? ""),
|
|
@@ -20007,7 +20184,7 @@ var scaffoldStaticAdCommand = defineCommand92({
|
|
|
20007
20184
|
run_estimated_credits: validation.estimatedCredits
|
|
20008
20185
|
},
|
|
20009
20186
|
checklist: {
|
|
20010
|
-
edit_prompt: `Edit ${
|
|
20187
|
+
edit_prompt: `Edit ${path17.basename(blueprintPath)} \u2014 it is the blueprint generated from your image; rewrite it into the ad you want (palette, copy, claims, subjects). It feeds the generator directly.`,
|
|
20011
20188
|
assets_to_supply: report.elements,
|
|
20012
20189
|
font_slot: report.includes_font ? "Drop a brand font at the [TODO] brandfont path (the describe pass recorded the ad's typefaces under `fonts` in prompt.json \u2014 match those). The font is wired into the render as a TYPE SPECIMEN reference so generated text takes the brand letterforms. Delete the brandfont + type_ref nodes to skip it." : "skipped (--skip-font)",
|
|
20013
20190
|
actor_sheets: report.actor_sheets.length > 0 ? `Each living hero (${report.actor_sheets.join(", ")}) is fused into a generated multi-view reference sheet (image_reference_sheet) that the render grounds on \u2014 so drop ONE clean photo at that hero's ingest and the sheet builds the consistent turnaround. Pass --skip-actor-sheets to ground on the lone photo instead.` : "none (no person/animal heroes detected, or --skip-actor-sheets)",
|
|
@@ -20023,9 +20200,9 @@ var scaffoldStaticAdCommand = defineCommand92({
|
|
|
20023
20200
|
});
|
|
20024
20201
|
|
|
20025
20202
|
// src/commands/canvas/scaffold-video.ts
|
|
20026
|
-
import { access as access2, cp, mkdir as
|
|
20203
|
+
import { access as access2, cp, mkdir as mkdir6, readFile as readFile14, rm as rm6, writeFile as writeFile7 } from "fs/promises";
|
|
20027
20204
|
import { tmpdir as tmpdir2 } from "os";
|
|
20028
|
-
import
|
|
20205
|
+
import path20 from "path";
|
|
20029
20206
|
import { defineCommand as defineCommand93 } from "citty";
|
|
20030
20207
|
|
|
20031
20208
|
// src/engine/scaffold/lib/model-router.ts
|
|
@@ -20064,7 +20241,7 @@ function routeVideoModel(input) {
|
|
|
20064
20241
|
|
|
20065
20242
|
// src/engine/nodes/local/lib/sceneDetect.ts
|
|
20066
20243
|
import { execFile as execFile2 } from "child_process";
|
|
20067
|
-
import { mkdtemp, readdir as
|
|
20244
|
+
import { mkdtemp, readdir as readdir6, readFile as readFile12, rm as rm5 } from "fs/promises";
|
|
20068
20245
|
import { tmpdir } from "os";
|
|
20069
20246
|
import { join as join2 } from "path";
|
|
20070
20247
|
import { promisify as promisify2 } from "util";
|
|
@@ -20138,11 +20315,11 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
|
|
|
20138
20315
|
],
|
|
20139
20316
|
{ encoding: "utf-8", maxBuffer: 32 * 1024 * 1024, timeout: timeoutMs }
|
|
20140
20317
|
);
|
|
20141
|
-
const csvName = (await
|
|
20318
|
+
const csvName = (await readdir6(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
|
|
20142
20319
|
if (!csvName) return [];
|
|
20143
|
-
return parsePySceneDetectCsvCuts(await
|
|
20320
|
+
return parsePySceneDetectCsvCuts(await readFile12(join2(outDir, csvName), "utf-8"));
|
|
20144
20321
|
} finally {
|
|
20145
|
-
await
|
|
20322
|
+
await rm5(outDir, { recursive: true, force: true });
|
|
20146
20323
|
}
|
|
20147
20324
|
}
|
|
20148
20325
|
async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
@@ -20165,23 +20342,23 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
|
20165
20342
|
|
|
20166
20343
|
// src/commands/canvas/composition-path.ts
|
|
20167
20344
|
import { existsSync as existsSync3 } from "fs";
|
|
20168
|
-
import
|
|
20345
|
+
import path18 from "path";
|
|
20169
20346
|
function resolveShippedCanvasDir(name, startDir, exists = existsSync3, maxDepth = 8) {
|
|
20170
|
-
const rel =
|
|
20347
|
+
const rel = path18.join("canvas", name);
|
|
20171
20348
|
let dir = startDir;
|
|
20172
20349
|
for (let i = 0; i < maxDepth; i++) {
|
|
20173
|
-
const candidate =
|
|
20174
|
-
if (exists(
|
|
20175
|
-
const parent =
|
|
20350
|
+
const candidate = path18.join(dir, rel);
|
|
20351
|
+
if (exists(path18.join(candidate, "meta.json"))) return candidate;
|
|
20352
|
+
const parent = path18.dirname(dir);
|
|
20176
20353
|
if (parent === dir) break;
|
|
20177
20354
|
dir = parent;
|
|
20178
20355
|
}
|
|
20179
|
-
return
|
|
20356
|
+
return path18.resolve(startDir, "../../../", rel);
|
|
20180
20357
|
}
|
|
20181
20358
|
|
|
20182
20359
|
// src/commands/canvas/gitignore.ts
|
|
20183
|
-
import { appendFile, readFile as
|
|
20184
|
-
import
|
|
20360
|
+
import { appendFile, readFile as readFile13 } from "fs/promises";
|
|
20361
|
+
import path19 from "path";
|
|
20185
20362
|
function missingGitignoreEntries(existing, entries) {
|
|
20186
20363
|
const present = new Set(
|
|
20187
20364
|
existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
@@ -20189,10 +20366,10 @@ function missingGitignoreEntries(existing, entries) {
|
|
|
20189
20366
|
return entries.filter((e) => !present.has(e.trim().replace(/\/+$/, "")));
|
|
20190
20367
|
}
|
|
20191
20368
|
async function ensureGitignore(dir, entries) {
|
|
20192
|
-
const file =
|
|
20369
|
+
const file = path19.join(dir, ".gitignore");
|
|
20193
20370
|
let existing;
|
|
20194
20371
|
try {
|
|
20195
|
-
existing = await
|
|
20372
|
+
existing = await readFile13(file, "utf8");
|
|
20196
20373
|
} catch {
|
|
20197
20374
|
return;
|
|
20198
20375
|
}
|
|
@@ -20231,7 +20408,7 @@ ONE PERSON, MULTIPLE LOOKS: if a single individual plays MULTIPLE personas or wa
|
|
|
20231
20408
|
For each kept element return: { "type": one of person|animal|product|logo|badge|location, "label": a short UPPER_SNAKE_CASE name (e.g. HERO, CREATOR_SKEPTIC, INSURANCE_CARD, LOGO), "description": a concrete reusable description to source/shoot the real asset \u2014 for a person/animal give a NEUTRAL castable role (e.g. "hero pet-owner, woman in her 30s" or "a small beagle"), NOT the original individual's literal face/identity: we RECAST with a FRESH person/animal, so never tell the agent to reuse the original. "expression": a living subject's typical expression or null, "cast_id": the global.cast id if it maps to one else null, "same_as": the label of another element this is the SAME individual as (different wardrobe/persona) else null, "scenes": the 0-based indices of ONLY the scenes where the element is ACTUALLY VISIBLE ON SCREEN \u2014 judged from that scene's start_frame_prompt / end_frame_prompt subjects and its action_detail, NOT from who is merely speaking. A narrator heard over b-roll is NOT present in that b-roll scene; a dog-running cutaway does NOT contain the couch creator just because she talks across it. Do NOT pad the list \u2014 an element wrongly listed in a scene makes the reproduction render the wrong subject there (e.g. the creator appearing in a pure-dog b-roll). When in doubt, leave a scene OUT. Output ONLY the JSON object.`;
|
|
20232
20409
|
async function loadAssetText2(ref, label) {
|
|
20233
20410
|
const r = ref;
|
|
20234
|
-
if (typeof r?.path === "string") return
|
|
20411
|
+
if (typeof r?.path === "string") return readFile14(r.path, "utf8");
|
|
20235
20412
|
if (typeof r?.url === "string") {
|
|
20236
20413
|
const res = await fetch(r.url);
|
|
20237
20414
|
if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
|
|
@@ -20250,7 +20427,7 @@ async function loadTranscriptBestEffort(ref) {
|
|
|
20250
20427
|
async function stageCaptions(outDir, transcript) {
|
|
20251
20428
|
const text = transcript?.trim();
|
|
20252
20429
|
if (!text || text === "[]") return {};
|
|
20253
|
-
const compositionPath =
|
|
20430
|
+
const compositionPath = path20.join(outDir, "tiktok-captions-composition");
|
|
20254
20431
|
await cp(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });
|
|
20255
20432
|
return { compositionPath };
|
|
20256
20433
|
}
|
|
@@ -20268,12 +20445,12 @@ function patchCompositionHtml(html, dims) {
|
|
|
20268
20445
|
return html.replace(/(<meta\s+name="viewport"\s+content="width=)\d+(,\s*height=)\d+(")/i, `$1${dims.w}$2${dims.h}$3`).replace(/(width:\s*)\d+(px;\s*height:\s*)\d+(px;)/i, `$1${dims.w}$2${dims.h}$3`).replace(/(data-width=")\d+(")/i, `$1${dims.w}$2`).replace(/(data-height=")\d+(")/i, `$1${dims.h}$2`);
|
|
20269
20446
|
}
|
|
20270
20447
|
async function stampCompositionDims(compositionDir, dims) {
|
|
20271
|
-
const metaPath =
|
|
20272
|
-
const rawMeta = await
|
|
20273
|
-
await
|
|
20274
|
-
const htmlPath =
|
|
20275
|
-
const rawHtml = await
|
|
20276
|
-
await
|
|
20448
|
+
const metaPath = path20.join(compositionDir, "meta.json");
|
|
20449
|
+
const rawMeta = await readFile14(metaPath, "utf8");
|
|
20450
|
+
await writeFile7(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
|
|
20451
|
+
const htmlPath = path20.join(compositionDir, "index.html");
|
|
20452
|
+
const rawHtml = await readFile14(htmlPath, "utf8");
|
|
20453
|
+
await writeFile7(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
|
|
20277
20454
|
}
|
|
20278
20455
|
function parseElements2(raw) {
|
|
20279
20456
|
const parsed = JSON.parse(raw);
|
|
@@ -20320,7 +20497,7 @@ var VIDEO_EXT_BY_MIME = {
|
|
|
20320
20497
|
"video/x-matroska": ".mkv"
|
|
20321
20498
|
};
|
|
20322
20499
|
function referenceVideoExt(url, contentType) {
|
|
20323
|
-
const fromPath =
|
|
20500
|
+
const fromPath = path20.extname(new URL(url).pathname).toLowerCase();
|
|
20324
20501
|
if (fromPath && fromPath.length <= 5) return fromPath;
|
|
20325
20502
|
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase();
|
|
20326
20503
|
return mime && VIDEO_EXT_BY_MIME[mime] || ".mp4";
|
|
@@ -20346,7 +20523,7 @@ function videoDefinitionDescription(blueprint) {
|
|
|
20346
20523
|
return typeof product === "string" && product.trim() ? product.trim() : void 0;
|
|
20347
20524
|
}
|
|
20348
20525
|
async function materializeReferenceVideo(fileArg2) {
|
|
20349
|
-
if (!/^https?:\/\//i.test(fileArg2)) return
|
|
20526
|
+
if (!/^https?:\/\//i.test(fileArg2)) return path20.resolve(fileArg2);
|
|
20350
20527
|
let res;
|
|
20351
20528
|
try {
|
|
20352
20529
|
res = await fetch(fileArg2);
|
|
@@ -20356,11 +20533,11 @@ async function materializeReferenceVideo(fileArg2) {
|
|
|
20356
20533
|
if (!res.ok) throw new Error(`failed to download reference video (${res.status} ${res.statusText})`);
|
|
20357
20534
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
20358
20535
|
if (bytes.length === 0) throw new Error("reference video download was empty");
|
|
20359
|
-
const dest =
|
|
20536
|
+
const dest = path20.join(
|
|
20360
20537
|
tmpdir2(),
|
|
20361
20538
|
`baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg2, res.headers.get("content-type"))}`
|
|
20362
20539
|
);
|
|
20363
|
-
await
|
|
20540
|
+
await writeFile7(dest, bytes);
|
|
20364
20541
|
return dest;
|
|
20365
20542
|
}
|
|
20366
20543
|
function resolveSeamDedup(raw) {
|
|
@@ -20483,7 +20660,7 @@ async function runAnalysisPasses(deconstructCanvas, selectModel) {
|
|
|
20483
20660
|
var scaffoldVideoCommand = defineCommand93({
|
|
20484
20661
|
meta: {
|
|
20485
20662
|
name: "scaffold-video",
|
|
20486
|
-
description: "Turn a reference video into a runnable reproduction canvas in one command. Runs billed passes \u2014 video_deconstruct (the full scene-by-scene blueprint + transcript, baked to prompt.json
|
|
20663
|
+
description: "Turn a reference video into a runnable reproduction canvas in one command. Runs billed passes \u2014 video_deconstruct (the full scene-by-scene blueprint + transcript, baked to a split, editable blueprint: a global prompt.json plus one small scenes/sNN.json per scene) and an AI selection of the video's RECURRING identity elements (person/animal/product/logo) \u2014 then scaffolds a pipeline where every scene boundary is a static-ad-grade frame (the blueprint as target_blueprint, a reference legend, the real frame as anchor) and each recurring element gets ONE shared [TODO] ingest slot wired into every frame it appears in. The clips feed Seedance an ultra-detailed motion brief (action, camera, dialogue, transcript). Edit a scene's scenes/sNN.json (or prompt.json for global cast/palette/brand), drop the real source images, then `baker canvas run`."
|
|
20487
20664
|
},
|
|
20488
20665
|
args: {
|
|
20489
20666
|
file: {
|
|
@@ -20572,11 +20749,11 @@ var scaffoldVideoCommand = defineCommand93({
|
|
|
20572
20749
|
} catch (e) {
|
|
20573
20750
|
return fail3("download", e instanceof Error ? e.message : String(e));
|
|
20574
20751
|
}
|
|
20575
|
-
const base =
|
|
20576
|
-
const outPath = args.out ?
|
|
20577
|
-
const outDir =
|
|
20578
|
-
const blueprintPath =
|
|
20579
|
-
const blueprintStylePath =
|
|
20752
|
+
const base = path20.basename(videoPath, path20.extname(videoPath));
|
|
20753
|
+
const outPath = args.out ? path20.resolve(String(args.out)) : slug ? path20.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`) : path20.join(path20.dirname(videoPath), `${base}.video.canvas.json`);
|
|
20754
|
+
const outDir = path20.dirname(outPath);
|
|
20755
|
+
const blueprintPath = path20.join(outDir, "prompt.json");
|
|
20756
|
+
const blueprintStylePath = path20.join(outDir, "prompt.style.json");
|
|
20580
20757
|
const frames = args.frames === "reuse" ? "reuse" : "generate";
|
|
20581
20758
|
const maxScenes = args["max-scenes"] ? Number(args["max-scenes"]) : void 0;
|
|
20582
20759
|
if (Number.isFinite(maxScenes)) {
|
|
@@ -20595,11 +20772,10 @@ var scaffoldVideoCommand = defineCommand93({
|
|
|
20595
20772
|
shotCuts
|
|
20596
20773
|
});
|
|
20597
20774
|
const { blueprint, elements, transcript, creditsSpent } = await runAnalysisPasses(deconstructCanvas, selectModel);
|
|
20598
|
-
await
|
|
20775
|
+
await mkdir6(outDir, { recursive: true });
|
|
20599
20776
|
const annotated = annotateBlueprintWithElements(blueprint, elements);
|
|
20600
|
-
await
|
|
20601
|
-
|
|
20602
|
-
await writeFile6(blueprintStylePath, renderStyleProjectionFromValue(annotated), "utf8");
|
|
20777
|
+
await writeSceneFiles(outDir, annotated);
|
|
20778
|
+
await writeFile7(blueprintStylePath, renderStyleProjectionFromValue(annotated), "utf8");
|
|
20603
20779
|
let aspect;
|
|
20604
20780
|
try {
|
|
20605
20781
|
aspect = resolveAspect(
|
|
@@ -20617,12 +20793,12 @@ var scaffoldVideoCommand = defineCommand93({
|
|
|
20617
20793
|
`
|
|
20618
20794
|
);
|
|
20619
20795
|
}
|
|
20620
|
-
const compositionDest =
|
|
20796
|
+
const compositionDest = path20.join(outDir, "video-overlay-composition");
|
|
20621
20797
|
await cp(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
20622
20798
|
await stampCompositionDims(compositionDest, outDims);
|
|
20623
|
-
const indexPath =
|
|
20799
|
+
const indexPath = path20.join(compositionDest, "index.html");
|
|
20624
20800
|
const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript) });
|
|
20625
|
-
const indexHtml = await
|
|
20801
|
+
const indexHtml = await readFile14(indexPath, "utf8");
|
|
20626
20802
|
const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
20627
20803
|
if (injected === indexHtml && overlayHtml.trim()) {
|
|
20628
20804
|
fail3(
|
|
@@ -20630,16 +20806,16 @@ var scaffoldVideoCommand = defineCommand93({
|
|
|
20630
20806
|
`video-overlay-composition/index.html is missing the <!--OVERLAYS--> marker \u2014 cannot inject the overlay layer`
|
|
20631
20807
|
);
|
|
20632
20808
|
}
|
|
20633
|
-
await
|
|
20809
|
+
await writeFile7(indexPath, injected, "utf8");
|
|
20634
20810
|
const captions = await stageCaptions(outDir, transcript);
|
|
20635
20811
|
if (captions.compositionPath) await stampCompositionDims(captions.compositionPath, outDims);
|
|
20636
20812
|
const opts = {
|
|
20637
20813
|
imageModel,
|
|
20638
20814
|
videoModel,
|
|
20639
|
-
overlayCompositionPath:
|
|
20640
|
-
captionsCompositionPath: captions.compositionPath ?
|
|
20641
|
-
blueprintPath:
|
|
20642
|
-
blueprintStylePath:
|
|
20815
|
+
overlayCompositionPath: path20.relative(outDir, compositionDest),
|
|
20816
|
+
captionsCompositionPath: captions.compositionPath ? path20.relative(outDir, captions.compositionPath) : void 0,
|
|
20817
|
+
blueprintPath: path20.relative(outDir, blueprintPath),
|
|
20818
|
+
blueprintStylePath: path20.relative(outDir, blueprintStylePath),
|
|
20643
20819
|
frames,
|
|
20644
20820
|
ambient: Boolean(args.ambient),
|
|
20645
20821
|
seamDedup: resolveSeamDedup(args["seam-dedup"]),
|
|
@@ -20655,7 +20831,7 @@ var scaffoldVideoCommand = defineCommand93({
|
|
|
20655
20831
|
return fail3("scaffold", e instanceof Error ? e.message : String(e));
|
|
20656
20832
|
}
|
|
20657
20833
|
if (captions.compositionPath && !canvas.nodes.some((n) => n.id === "captions")) {
|
|
20658
|
-
await
|
|
20834
|
+
await rm6(captions.compositionPath, { recursive: true, force: true });
|
|
20659
20835
|
}
|
|
20660
20836
|
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(canvas, outDir), defaultRegistry());
|
|
20661
20837
|
if (!validation.ok) {
|
|
@@ -20664,8 +20840,14 @@ var scaffoldVideoCommand = defineCommand93({
|
|
|
20664
20840
|
todo.blocking_validation_issues = validation.issues;
|
|
20665
20841
|
meta.todo = todo;
|
|
20666
20842
|
}
|
|
20667
|
-
await
|
|
20843
|
+
await writeFile7(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
20668
20844
|
`, "utf8");
|
|
20845
|
+
await writeFile7(
|
|
20846
|
+
path20.join(outDir, REBUILD_FILE),
|
|
20847
|
+
`${JSON.stringify({ elements, opts }, null, 2)}
|
|
20848
|
+
`,
|
|
20849
|
+
"utf8"
|
|
20850
|
+
);
|
|
20669
20851
|
if (!validation.ok) {
|
|
20670
20852
|
process.stderr.write(
|
|
20671
20853
|
`${JSON.stringify(
|
|
@@ -20686,9 +20868,9 @@ var scaffoldVideoCommand = defineCommand93({
|
|
|
20686
20868
|
await ensureGitignore(process.cwd(), ["canvas/", ".context/"]);
|
|
20687
20869
|
const sourceRef = videoSourceReference(blueprint, fileArg2);
|
|
20688
20870
|
if (slug) {
|
|
20689
|
-
const definitionPath =
|
|
20871
|
+
const definitionPath = path20.join(outDir, "_definition.md");
|
|
20690
20872
|
if (!await fileExists2(definitionPath)) {
|
|
20691
|
-
await
|
|
20873
|
+
await writeFile7(
|
|
20692
20874
|
definitionPath,
|
|
20693
20875
|
buildCreativeDefinition({
|
|
20694
20876
|
title: titleFromSlug(slug),
|
|
@@ -20741,7 +20923,7 @@ var scaffoldVideoCommand = defineCommand93({
|
|
|
20741
20923
|
graph: canvas.metadata?.video?.graph_stats
|
|
20742
20924
|
},
|
|
20743
20925
|
checklist: {
|
|
20744
|
-
edit_prompt: `
|
|
20926
|
+
edit_prompt: `The blueprint is split so you edit ONE small file at a time \u2014 never a giant one. Per-scene content (a scene's dialogue, action, frame prompts, overlays) lives in \`scenes/sNN.json\` \u2014 edit the single scene you want to change. Global cast/palette/brand/copy lives in \`${path20.basename(blueprintPath)}\`. \`baker canvas validate\`/\`run\` re-assemble the blueprint and re-flow every edited scene back into the render (and regenerate ${path20.basename(blueprintStylePath)}, the projection each frame's target_blueprint reads) \u2014 so your scene edits reach the render automatically. Never hand-edit the inlined node prompts in the canvas or the derived ${path20.basename(blueprintStylePath)}; both are regenerated.`,
|
|
20745
20927
|
recurring_elements_to_supply: report.elements,
|
|
20746
20928
|
voices_to_confirm: report.dialogue.map((d) => ({
|
|
20747
20929
|
scene: d.scene,
|
|
@@ -20778,8 +20960,8 @@ var scaffoldVideoCommand = defineCommand93({
|
|
|
20778
20960
|
});
|
|
20779
20961
|
|
|
20780
20962
|
// src/commands/canvas/set-prompt.ts
|
|
20781
|
-
import { readFile as
|
|
20782
|
-
import
|
|
20963
|
+
import { readFile as readFile15, writeFile as writeFile8 } from "fs/promises";
|
|
20964
|
+
import path21 from "path";
|
|
20783
20965
|
import { defineCommand as defineCommand94 } from "citty";
|
|
20784
20966
|
function setNodePrompt(canvas, nodeId, text) {
|
|
20785
20967
|
const nodes = canvas?.nodes;
|
|
@@ -20807,17 +20989,17 @@ var setPromptCommand = defineCommand94({
|
|
|
20807
20989
|
"text-file": { type: "string", description: "Read the new prompt from a UTF-8 file (preserves accents/newlines)" }
|
|
20808
20990
|
},
|
|
20809
20991
|
async run({ args }) {
|
|
20810
|
-
const filePath =
|
|
20992
|
+
const filePath = path21.resolve(String(args.file));
|
|
20811
20993
|
let canvas;
|
|
20812
20994
|
try {
|
|
20813
|
-
canvas = JSON.parse(await
|
|
20995
|
+
canvas = JSON.parse(await readFile15(filePath, "utf8"));
|
|
20814
20996
|
} catch (e) {
|
|
20815
20997
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "parse", message: String(e) } }, null, 2)}
|
|
20816
20998
|
`);
|
|
20817
20999
|
process.exit(2);
|
|
20818
21000
|
}
|
|
20819
21001
|
let text;
|
|
20820
|
-
if (args["text-file"]) text = await
|
|
21002
|
+
if (args["text-file"]) text = await readFile15(path21.resolve(String(args["text-file"])), "utf8");
|
|
20821
21003
|
else if (args.text !== void 0) text = String(args.text);
|
|
20822
21004
|
else {
|
|
20823
21005
|
process.stderr.write(
|
|
@@ -20838,14 +21020,14 @@ var setPromptCommand = defineCommand94({
|
|
|
20838
21020
|
process.exit(2);
|
|
20839
21021
|
return;
|
|
20840
21022
|
}
|
|
20841
|
-
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated,
|
|
21023
|
+
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated, path21.dirname(filePath)), defaultRegistry());
|
|
20842
21024
|
if (!validation.ok) {
|
|
20843
21025
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.issues } }, null, 2)}
|
|
20844
21026
|
`);
|
|
20845
21027
|
process.exit(2);
|
|
20846
21028
|
return;
|
|
20847
21029
|
}
|
|
20848
|
-
await
|
|
21030
|
+
await writeFile8(filePath, `${JSON.stringify(updated, null, 2)}
|
|
20849
21031
|
`, "utf8");
|
|
20850
21032
|
process.stdout.write(`${JSON.stringify({ ok: true, node: String(args.node), bytes: text.length }, null, 2)}
|
|
20851
21033
|
`);
|
|
@@ -20853,8 +21035,8 @@ var setPromptCommand = defineCommand94({
|
|
|
20853
21035
|
});
|
|
20854
21036
|
|
|
20855
21037
|
// src/commands/canvas/validate.ts
|
|
20856
|
-
import { readFile as
|
|
20857
|
-
import
|
|
21038
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
21039
|
+
import path22 from "path";
|
|
20858
21040
|
import { defineCommand as defineCommand95 } from "citty";
|
|
20859
21041
|
var validateCommand = defineCommand95({
|
|
20860
21042
|
meta: {
|
|
@@ -20863,8 +21045,8 @@ var validateCommand = defineCommand95({
|
|
|
20863
21045
|
},
|
|
20864
21046
|
args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
|
|
20865
21047
|
async run({ args }) {
|
|
20866
|
-
const filePath =
|
|
20867
|
-
const raw = await
|
|
21048
|
+
const filePath = path22.resolve(String(args.file));
|
|
21049
|
+
const raw = await readFile16(filePath, "utf8");
|
|
20868
21050
|
let parsed;
|
|
20869
21051
|
try {
|
|
20870
21052
|
parsed = JSON.parse(raw);
|
|
@@ -20874,7 +21056,7 @@ var validateCommand = defineCommand95({
|
|
|
20874
21056
|
`);
|
|
20875
21057
|
process.exit(2);
|
|
20876
21058
|
}
|
|
20877
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
21059
|
+
parsed = resolveRelativeCanvasPaths(parsed, path22.dirname(filePath));
|
|
20878
21060
|
let styleProjection = "not_applicable";
|
|
20879
21061
|
try {
|
|
20880
21062
|
styleProjection = await syncStyleProjection(parsed, (line) => process.stderr.write(`${line}
|
|
@@ -20883,6 +21065,18 @@ var validateCommand = defineCommand95({
|
|
|
20883
21065
|
const msg = e instanceof Error ? e.message : String(e);
|
|
20884
21066
|
process.stderr.write(
|
|
20885
21067
|
`${JSON.stringify({ ok: false, error: { code: "style_projection", message: `prompt.json is not readable/valid JSON: ${msg}` } }, null, 2)}
|
|
21068
|
+
`
|
|
21069
|
+
);
|
|
21070
|
+
process.exit(2);
|
|
21071
|
+
}
|
|
21072
|
+
let sceneProjection = "not_applicable";
|
|
21073
|
+
try {
|
|
21074
|
+
sceneProjection = await syncSceneNodeParams(parsed, filePath, (line) => process.stderr.write(`${line}
|
|
21075
|
+
`));
|
|
21076
|
+
} catch (e) {
|
|
21077
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
21078
|
+
process.stderr.write(
|
|
21079
|
+
`${JSON.stringify({ ok: false, error: { code: "scene_projection", message: `a scenes/*.json file is not readable/valid or its rebuild failed: ${msg}` } }, null, 2)}
|
|
20886
21080
|
`
|
|
20887
21081
|
);
|
|
20888
21082
|
process.exit(2);
|
|
@@ -20902,6 +21096,7 @@ var validateCommand = defineCommand95({
|
|
|
20902
21096
|
estimated_credits: result.estimatedCredits,
|
|
20903
21097
|
cost_preview: result.perNodeCredits ?? [],
|
|
20904
21098
|
style_projection: styleProjection,
|
|
21099
|
+
scene_projection: sceneProjection,
|
|
20905
21100
|
warnings: result.warnings ?? []
|
|
20906
21101
|
},
|
|
20907
21102
|
null,
|
|
@@ -20952,7 +21147,7 @@ import { defineCommand as defineCommand98 } from "citty";
|
|
|
20952
21147
|
import { defineCommand as defineCommand97 } from "citty";
|
|
20953
21148
|
|
|
20954
21149
|
// src/commands/images/api.ts
|
|
20955
|
-
import { readFile as
|
|
21150
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
20956
21151
|
import { extname } from "path";
|
|
20957
21152
|
var imageProcessingTimeoutMs = 18e4;
|
|
20958
21153
|
var imageReadyPollIntervalMs = 2e3;
|
|
@@ -20966,7 +21161,7 @@ var mimeMap = {
|
|
|
20966
21161
|
".avif": "image/avif"
|
|
20967
21162
|
};
|
|
20968
21163
|
var defaultImageApiDeps = {
|
|
20969
|
-
readFile:
|
|
21164
|
+
readFile: readFile17,
|
|
20970
21165
|
post: apiPost,
|
|
20971
21166
|
get: apiGet,
|
|
20972
21167
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
|
|
@@ -21200,12 +21395,12 @@ function listFlowSlugs() {
|
|
|
21200
21395
|
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
|
|
21201
21396
|
}
|
|
21202
21397
|
function readFlowTree(slug) {
|
|
21203
|
-
const
|
|
21204
|
-
if (!existsSync4(
|
|
21398
|
+
const path23 = join3(flowsDir(), slug, "_data.json");
|
|
21399
|
+
if (!existsSync4(path23)) {
|
|
21205
21400
|
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
21206
21401
|
}
|
|
21207
21402
|
try {
|
|
21208
|
-
return JSON.parse(readFileSync9(
|
|
21403
|
+
return JSON.parse(readFileSync9(path23, "utf-8"));
|
|
21209
21404
|
} catch (error) {
|
|
21210
21405
|
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
21211
21406
|
}
|
|
@@ -22213,7 +22408,7 @@ function cropSprite(input, region) {
|
|
|
22213
22408
|
|
|
22214
22409
|
// src/lib/image/io.ts
|
|
22215
22410
|
import { randomBytes } from "crypto";
|
|
22216
|
-
import { glob as fsGlob, readFile as
|
|
22411
|
+
import { glob as fsGlob, readFile as readFile18, rename, stat as stat4, writeFile as writeFile9 } from "fs/promises";
|
|
22217
22412
|
import { dirname as dirname2, extname as extname2, join as join4, resolve as resolve4 } from "path";
|
|
22218
22413
|
var REMOTE_RE = /^https?:\/\//i;
|
|
22219
22414
|
var GLOB_RE = /[*?[\]{}]/;
|
|
@@ -22249,11 +22444,11 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
22249
22444
|
}
|
|
22250
22445
|
return Buffer.from(await response.arrayBuffer());
|
|
22251
22446
|
}
|
|
22252
|
-
return
|
|
22447
|
+
return readFile18(pathOrUrl);
|
|
22253
22448
|
}
|
|
22254
|
-
async function isDirectory(
|
|
22449
|
+
async function isDirectory(path23) {
|
|
22255
22450
|
try {
|
|
22256
|
-
const s = await stat4(
|
|
22451
|
+
const s = await stat4(path23);
|
|
22257
22452
|
return s.isDirectory();
|
|
22258
22453
|
} catch {
|
|
22259
22454
|
return false;
|
|
@@ -22272,7 +22467,7 @@ async function atomicWrite(targetPath, data) {
|
|
|
22272
22467
|
const absolute = resolve4(targetPath);
|
|
22273
22468
|
const dir = dirname2(absolute);
|
|
22274
22469
|
const tmp = join4(dir, `.baker-image-${randomBytes(8).toString("hex")}.tmp`);
|
|
22275
|
-
await
|
|
22470
|
+
await writeFile9(tmp, data);
|
|
22276
22471
|
await rename(tmp, absolute);
|
|
22277
22472
|
}
|
|
22278
22473
|
|
|
@@ -22615,7 +22810,7 @@ var findCommand = defineCommand112({
|
|
|
22615
22810
|
});
|
|
22616
22811
|
|
|
22617
22812
|
// src/commands/images/generate.ts
|
|
22618
|
-
import { readFile as
|
|
22813
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
22619
22814
|
import { defineCommand as defineCommand113 } from "citty";
|
|
22620
22815
|
import sharp2 from "sharp";
|
|
22621
22816
|
var GENERATE_TIMEOUT_MS = 18e4;
|
|
@@ -22705,7 +22900,7 @@ async function resolveReferences(spec) {
|
|
|
22705
22900
|
}
|
|
22706
22901
|
let raw;
|
|
22707
22902
|
try {
|
|
22708
|
-
raw = await
|
|
22903
|
+
raw = await readFile19(entry);
|
|
22709
22904
|
} catch {
|
|
22710
22905
|
throw new ApiError("VALIDATION_ERROR", `Reference file not found: ${entry}`);
|
|
22711
22906
|
}
|
|
@@ -26864,7 +27059,7 @@ var searchCommand3 = defineCommand159({
|
|
|
26864
27059
|
var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
26865
27060
|
|
|
26866
27061
|
// src/commands/videos/upload.ts
|
|
26867
|
-
import { readFile as
|
|
27062
|
+
import { readFile as readFile20, stat as stat5 } from "fs/promises";
|
|
26868
27063
|
import { extname as extname3 } from "path";
|
|
26869
27064
|
import { defineCommand as defineCommand160 } from "citty";
|
|
26870
27065
|
var MIME_MAP = {
|
|
@@ -26929,7 +27124,7 @@ var uploadCommand2 = defineCommand160({
|
|
|
26929
27124
|
return;
|
|
26930
27125
|
}
|
|
26931
27126
|
const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
|
|
26932
|
-
const fileBuffer = await
|
|
27127
|
+
const fileBuffer = await readFile20(filePath);
|
|
26933
27128
|
const uploadResponse = await fetch(uploadUrl, {
|
|
26934
27129
|
method: "PUT",
|
|
26935
27130
|
headers: { "Content-Type": contentType },
|