@koda-sl/baker-cli 0.263.0-dev.ac71c8123 → 0.265.1-dev.1f1c09c80
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 +34 -0
- package/canvas/tiktok-captions-composition/index.html +6 -2
- package/dist/{chunk-GXRUZ4LP.js → chunk-2IUN3S3E.js} +2 -2
- package/dist/{chunk-ECPNYXOA.js → chunk-P2PEABMQ.js} +3 -3
- package/dist/{chunk-2VM2J6MB.js → chunk-RHEQTHT7.js} +4 -4
- package/dist/{chunk-ZK5S7JV2.js → chunk-SZJWEOW2.js} +3 -3
- package/dist/{chunk-POYXIDOD.js → chunk-WFWU3CHS.js} +6 -2
- package/dist/{chunk-POYXIDOD.js.map → chunk-WFWU3CHS.js.map} +1 -1
- package/dist/{chunk-TBEVU7XF.js → chunk-XAUTT2Q6.js} +3 -3
- package/dist/cli.js +763 -324
- package/dist/cli.js.map +1 -1
- package/dist/client-KOUPT7HU.js +15 -0
- package/dist/engine/index.js +3 -3
- package/dist/{env-HZNFGXAR.js → env-C7YXL6C6.js} +4 -2
- package/dist/{output-MWMAXN4M.js → output-ALQGBDPQ.js} +5 -5
- package/dist/{shared-NFLXWGHK.js → shared-UKJH3PS7.js} +6 -6
- package/package.json +1 -1
- package/dist/client-7QIDRQXG.js +0 -15
- /package/dist/{chunk-GXRUZ4LP.js.map → chunk-2IUN3S3E.js.map} +0 -0
- /package/dist/{chunk-ECPNYXOA.js.map → chunk-P2PEABMQ.js.map} +0 -0
- /package/dist/{chunk-2VM2J6MB.js.map → chunk-RHEQTHT7.js.map} +0 -0
- /package/dist/{chunk-ZK5S7JV2.js.map → chunk-SZJWEOW2.js.map} +0 -0
- /package/dist/{chunk-TBEVU7XF.js.map → chunk-XAUTT2Q6.js.map} +0 -0
- /package/dist/{client-7QIDRQXG.js.map → client-KOUPT7HU.js.map} +0 -0
- /package/dist/{env-HZNFGXAR.js.map → env-C7YXL6C6.js.map} +0 -0
- /package/dist/{output-MWMAXN4M.js.map → output-ALQGBDPQ.js.map} +0 -0
- /package/dist/{shared-NFLXWGHK.js.map → shared-UKJH3PS7.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
ulid,
|
|
59
59
|
validateCanvasDeep,
|
|
60
60
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-SZJWEOW2.js";
|
|
62
62
|
import {
|
|
63
63
|
csvOrJson,
|
|
64
64
|
daysAgoIso,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
resolveAccountIdArg,
|
|
68
68
|
resolveEffectiveStatus,
|
|
69
69
|
todayIso
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-RHEQTHT7.js";
|
|
71
71
|
import {
|
|
72
72
|
buildQueryCacheKey,
|
|
73
73
|
cacheGet,
|
|
@@ -83,29 +83,31 @@ import {
|
|
|
83
83
|
writeAdsJson,
|
|
84
84
|
writeAdsOutput,
|
|
85
85
|
writeJsonEnvelope
|
|
86
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-XAUTT2Q6.js";
|
|
87
87
|
import {
|
|
88
88
|
ApiError,
|
|
89
89
|
apiGet,
|
|
90
90
|
apiPost,
|
|
91
91
|
validateConvexId
|
|
92
|
-
} from "./chunk-
|
|
92
|
+
} from "./chunk-P2PEABMQ.js";
|
|
93
93
|
import {
|
|
94
94
|
installStreamTaps,
|
|
95
95
|
logInvocation
|
|
96
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-2IUN3S3E.js";
|
|
97
97
|
import {
|
|
98
98
|
captureBudgetMs,
|
|
99
99
|
captureProxyCredentials,
|
|
100
100
|
captureVimeoCookie,
|
|
101
|
+
frameVisionKey,
|
|
101
102
|
getEnv,
|
|
103
|
+
hasFrameVisionKey,
|
|
102
104
|
isProxyFailure,
|
|
103
105
|
plannedRoutes,
|
|
104
106
|
refuseNonPublicUrl,
|
|
105
107
|
requireChatId,
|
|
106
108
|
resolveChatId,
|
|
107
109
|
shouldEscalate
|
|
108
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-WFWU3CHS.js";
|
|
109
111
|
|
|
110
112
|
// src/cli.ts
|
|
111
113
|
import { defineCommand as defineCommand228, runMain } from "citty";
|
|
@@ -9784,11 +9786,11 @@ function rawTextEntries(value) {
|
|
|
9784
9786
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
9785
9787
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
9786
9788
|
}
|
|
9787
|
-
function rawFileEntries(
|
|
9788
|
-
if (typeof
|
|
9789
|
+
function rawFileEntries(path40) {
|
|
9790
|
+
if (typeof path40 !== "string" || path40.length === 0) {
|
|
9789
9791
|
return [];
|
|
9790
9792
|
}
|
|
9791
|
-
return readFileSync2(
|
|
9793
|
+
return readFileSync2(path40, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
9792
9794
|
}
|
|
9793
9795
|
function keywordEntries(args) {
|
|
9794
9796
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -9811,19 +9813,19 @@ function keywordEntries(args) {
|
|
|
9811
9813
|
}
|
|
9812
9814
|
return entries;
|
|
9813
9815
|
}
|
|
9814
|
-
function loadJsonFileArg(
|
|
9815
|
-
if (typeof
|
|
9816
|
+
function loadJsonFileArg(path40) {
|
|
9817
|
+
if (typeof path40 !== "string" || path40.length === 0) {
|
|
9816
9818
|
return {};
|
|
9817
9819
|
}
|
|
9818
9820
|
try {
|
|
9819
|
-
const parsed = JSON.parse(readFileSync2(
|
|
9821
|
+
const parsed = JSON.parse(readFileSync2(path40, "utf8"));
|
|
9820
9822
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9821
|
-
failWriteValidation(`${
|
|
9823
|
+
failWriteValidation(`${path40} must contain a JSON object`);
|
|
9822
9824
|
}
|
|
9823
9825
|
return parsed;
|
|
9824
9826
|
} catch (err) {
|
|
9825
9827
|
if (err instanceof SyntaxError) {
|
|
9826
|
-
failWriteValidation(`${
|
|
9828
|
+
failWriteValidation(`${path40} is not valid JSON: ${err.message}`);
|
|
9827
9829
|
}
|
|
9828
9830
|
throw err;
|
|
9829
9831
|
}
|
|
@@ -9953,10 +9955,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
|
|
|
9953
9955
|
async function stageTarget(kind, customerId, target, hints) {
|
|
9954
9956
|
await stageGoogleOp({ kind, customerId, target }, hints);
|
|
9955
9957
|
}
|
|
9956
|
-
async function draftAction(
|
|
9958
|
+
async function draftAction(path40, body, chat) {
|
|
9957
9959
|
try {
|
|
9958
9960
|
const chatId = resolveChatId(chat);
|
|
9959
|
-
const response = await apiPost(
|
|
9961
|
+
const response = await apiPost(path40, { chatId, ...body });
|
|
9960
9962
|
writeJsonEnvelope(response);
|
|
9961
9963
|
} catch (err) {
|
|
9962
9964
|
handleGoogleError(err);
|
|
@@ -15170,19 +15172,19 @@ function failWriteValidation2(message) {
|
|
|
15170
15172
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
15171
15173
|
process.exit(1);
|
|
15172
15174
|
}
|
|
15173
|
-
function loadJsonFileArg2(
|
|
15174
|
-
if (typeof
|
|
15175
|
+
function loadJsonFileArg2(path40) {
|
|
15176
|
+
if (typeof path40 !== "string" || path40.length === 0) {
|
|
15175
15177
|
return {};
|
|
15176
15178
|
}
|
|
15177
15179
|
try {
|
|
15178
|
-
const parsed = JSON.parse(readFileSync4(
|
|
15180
|
+
const parsed = JSON.parse(readFileSync4(path40, "utf8"));
|
|
15179
15181
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15180
|
-
failWriteValidation2(`${
|
|
15182
|
+
failWriteValidation2(`${path40} must contain a JSON object`);
|
|
15181
15183
|
}
|
|
15182
15184
|
return parsed;
|
|
15183
15185
|
} catch (err) {
|
|
15184
15186
|
if (err instanceof SyntaxError) {
|
|
15185
|
-
failWriteValidation2(`${
|
|
15187
|
+
failWriteValidation2(`${path40} is not valid JSON: ${err.message}`);
|
|
15186
15188
|
}
|
|
15187
15189
|
throw err;
|
|
15188
15190
|
}
|
|
@@ -15267,15 +15269,15 @@ function parseLocaleFlag(value) {
|
|
|
15267
15269
|
}
|
|
15268
15270
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
15269
15271
|
}
|
|
15270
|
-
function loadTargetingFileArg(
|
|
15271
|
-
if (typeof
|
|
15272
|
+
function loadTargetingFileArg(path40) {
|
|
15273
|
+
if (typeof path40 !== "string" || path40.length === 0) {
|
|
15272
15274
|
return void 0;
|
|
15273
15275
|
}
|
|
15274
|
-
const parsed = loadJsonFileArg2(
|
|
15276
|
+
const parsed = loadJsonFileArg2(path40);
|
|
15275
15277
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
15276
15278
|
if (!criteria.include) {
|
|
15277
15279
|
failWriteValidation2(
|
|
15278
|
-
`${
|
|
15280
|
+
`${path40} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
15279
15281
|
);
|
|
15280
15282
|
}
|
|
15281
15283
|
return criteria;
|
|
@@ -15310,14 +15312,14 @@ function parseCsvLine(line) {
|
|
|
15310
15312
|
cells.push(current);
|
|
15311
15313
|
return cells.map((cell2) => cell2.trim());
|
|
15312
15314
|
}
|
|
15313
|
-
function parseListFileArg(
|
|
15314
|
-
if (typeof
|
|
15315
|
+
function parseListFileArg(path40, maxRows) {
|
|
15316
|
+
if (typeof path40 !== "string" || path40.length === 0) {
|
|
15315
15317
|
return void 0;
|
|
15316
15318
|
}
|
|
15317
|
-
const raw = readFileSync4(
|
|
15319
|
+
const raw = readFileSync4(path40, "utf8");
|
|
15318
15320
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
15319
15321
|
if (lines.length < 2) {
|
|
15320
|
-
failWriteValidation2(`${
|
|
15322
|
+
failWriteValidation2(`${path40} needs a header row and at least one data row`);
|
|
15321
15323
|
}
|
|
15322
15324
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
15323
15325
|
const rows = [];
|
|
@@ -15336,7 +15338,7 @@ function parseListFileArg(path39, maxRows) {
|
|
|
15336
15338
|
}
|
|
15337
15339
|
}
|
|
15338
15340
|
if (rows.length > maxRows) {
|
|
15339
|
-
failWriteValidation2(`${
|
|
15341
|
+
failWriteValidation2(`${path40} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
15340
15342
|
}
|
|
15341
15343
|
return { columns, rows };
|
|
15342
15344
|
}
|
|
@@ -15432,11 +15434,11 @@ function readPositionals(args) {
|
|
|
15432
15434
|
function splitIdList(raw) {
|
|
15433
15435
|
return raw.split(",").map((id) => id.trim()).filter(Boolean);
|
|
15434
15436
|
}
|
|
15435
|
-
function idsFileEntries(
|
|
15436
|
-
if (typeof
|
|
15437
|
+
function idsFileEntries(path40) {
|
|
15438
|
+
if (typeof path40 !== "string" || path40.length === 0) {
|
|
15437
15439
|
return [];
|
|
15438
15440
|
}
|
|
15439
|
-
return readFileSync4(
|
|
15441
|
+
return readFileSync4(path40, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
|
|
15440
15442
|
}
|
|
15441
15443
|
function requireTargets(args, entity) {
|
|
15442
15444
|
const positionals = readPositionals(args);
|
|
@@ -18057,9 +18059,9 @@ function compactRow(row) {
|
|
|
18057
18059
|
...destination.postUrn ? { postUrn: destination.postUrn } : {}
|
|
18058
18060
|
};
|
|
18059
18061
|
}
|
|
18060
|
-
function readPath(row,
|
|
18062
|
+
function readPath(row, path40) {
|
|
18061
18063
|
let current = row;
|
|
18062
|
-
for (const segment of
|
|
18064
|
+
for (const segment of path40.split(".")) {
|
|
18063
18065
|
const record = asRecord2(current);
|
|
18064
18066
|
if (!record) return void 0;
|
|
18065
18067
|
current = record[segment];
|
|
@@ -18069,10 +18071,10 @@ function readPath(row, path39) {
|
|
|
18069
18071
|
function projectFields(rows, paths) {
|
|
18070
18072
|
return rows.map((row) => {
|
|
18071
18073
|
const projected = {};
|
|
18072
|
-
for (const
|
|
18073
|
-
const value = readPath(row,
|
|
18074
|
+
for (const path40 of paths) {
|
|
18075
|
+
const value = readPath(row, path40);
|
|
18074
18076
|
if (value !== void 0) {
|
|
18075
|
-
projected[
|
|
18077
|
+
projected[path40] = value;
|
|
18076
18078
|
}
|
|
18077
18079
|
}
|
|
18078
18080
|
return projected;
|
|
@@ -19372,11 +19374,11 @@ var updateStatusSchema = z25.enum(UPDATE_STATUSES);
|
|
|
19372
19374
|
function currencyMinimums2(currencyCode) {
|
|
19373
19375
|
return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
|
|
19374
19376
|
}
|
|
19375
|
-
function validateDailyBudgetFloor(money, ctx,
|
|
19377
|
+
function validateDailyBudgetFloor(money, ctx, path40) {
|
|
19376
19378
|
if (money?.currencyCode) {
|
|
19377
19379
|
const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
|
|
19378
19380
|
if (Number(money.amount) < min) {
|
|
19379
|
-
ctx.addIssue({ code: "custom", path:
|
|
19381
|
+
ctx.addIssue({ code: "custom", path: path40, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
19380
19382
|
}
|
|
19381
19383
|
}
|
|
19382
19384
|
}
|
|
@@ -20045,19 +20047,19 @@ function failWriteValidation3(message) {
|
|
|
20045
20047
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
20046
20048
|
process.exit(1);
|
|
20047
20049
|
}
|
|
20048
|
-
function loadJsonFileArg3(
|
|
20049
|
-
if (typeof
|
|
20050
|
+
function loadJsonFileArg3(path40) {
|
|
20051
|
+
if (typeof path40 !== "string" || path40.length === 0) {
|
|
20050
20052
|
return {};
|
|
20051
20053
|
}
|
|
20052
20054
|
try {
|
|
20053
|
-
const parsed = JSON.parse(readFileSync8(
|
|
20055
|
+
const parsed = JSON.parse(readFileSync8(path40, "utf8"));
|
|
20054
20056
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
20055
|
-
failWriteValidation3(`${
|
|
20057
|
+
failWriteValidation3(`${path40} must contain a JSON object`);
|
|
20056
20058
|
}
|
|
20057
20059
|
return parsed;
|
|
20058
20060
|
} catch (err) {
|
|
20059
20061
|
if (err instanceof SyntaxError) {
|
|
20060
|
-
failWriteValidation3(`${
|
|
20062
|
+
failWriteValidation3(`${path40} is not valid JSON: ${err.message}`);
|
|
20061
20063
|
}
|
|
20062
20064
|
throw err;
|
|
20063
20065
|
}
|
|
@@ -20610,10 +20612,10 @@ function duplicateCommand2(entity, label) {
|
|
|
20610
20612
|
replace: { type: "boolean", description: "Pause the original once the copy publishes" }
|
|
20611
20613
|
},
|
|
20612
20614
|
run: async ({ args }) => {
|
|
20613
|
-
const { apiPost: apiPost2 } = await import("./client-
|
|
20614
|
-
const { requireChatId: requireChatId2 } = await import("./env-
|
|
20615
|
-
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-
|
|
20616
|
-
const { handleMetaError: handleMetaError2 } = await import("./shared-
|
|
20615
|
+
const { apiPost: apiPost2 } = await import("./client-KOUPT7HU.js");
|
|
20616
|
+
const { requireChatId: requireChatId2 } = await import("./env-C7YXL6C6.js");
|
|
20617
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-ALQGBDPQ.js");
|
|
20618
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-UKJH3PS7.js");
|
|
20617
20619
|
try {
|
|
20618
20620
|
const accountId = bareAccountId2(args);
|
|
20619
20621
|
const chatId = requireChatId2();
|
|
@@ -24666,11 +24668,11 @@ function unwrap(response) {
|
|
|
24666
24668
|
}
|
|
24667
24669
|
return response.data;
|
|
24668
24670
|
}
|
|
24669
|
-
async function readAvatars(
|
|
24670
|
-
return unwrap(await apiGet(
|
|
24671
|
+
async function readAvatars(path40, params) {
|
|
24672
|
+
return unwrap(await apiGet(path40, params));
|
|
24671
24673
|
}
|
|
24672
|
-
async function writeAvatars(
|
|
24673
|
-
return unwrap(await apiPost(
|
|
24674
|
+
async function writeAvatars(path40, body) {
|
|
24675
|
+
return unwrap(await apiPost(path40, body));
|
|
24674
24676
|
}
|
|
24675
24677
|
|
|
24676
24678
|
// src/commands/avatars/create.ts
|
|
@@ -25514,12 +25516,12 @@ function missingFontFiles(urls, available) {
|
|
|
25514
25516
|
function planFontAdoption(sources, families) {
|
|
25515
25517
|
const wanted = new Map(families.map((family) => [normalizeFamily(family), family]));
|
|
25516
25518
|
const byFamily = /* @__PURE__ */ new Map();
|
|
25517
|
-
for (const { path:
|
|
25518
|
-
const dir = posix.dirname(
|
|
25519
|
+
for (const { path: path40, source } of sources) {
|
|
25520
|
+
const dir = posix.dirname(path40);
|
|
25519
25521
|
for (const face of declaredFontFaces(source)) {
|
|
25520
25522
|
if (!wanted.has(face.family)) continue;
|
|
25521
25523
|
const perFile = byFamily.get(face.family) ?? /* @__PURE__ */ new Map();
|
|
25522
|
-
perFile.set(
|
|
25524
|
+
perFile.set(path40, [...perFile.get(path40) ?? [], rebaseFontFaceSrc(face.block, dir)]);
|
|
25523
25525
|
byFamily.set(face.family, perFile);
|
|
25524
25526
|
}
|
|
25525
25527
|
}
|
|
@@ -26531,12 +26533,12 @@ async function probeDuration(filePath) {
|
|
|
26531
26533
|
}
|
|
26532
26534
|
|
|
26533
26535
|
// src/commands/canvas/rerun.ts
|
|
26534
|
-
import
|
|
26536
|
+
import path17 from "path";
|
|
26535
26537
|
import { defineCommand as defineCommand101 } from "citty";
|
|
26536
26538
|
|
|
26537
26539
|
// src/commands/canvas/run.ts
|
|
26538
|
-
import { readFile as
|
|
26539
|
-
import
|
|
26540
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
26541
|
+
import path16 from "path";
|
|
26540
26542
|
import { defineCommand as defineCommand100 } from "citty";
|
|
26541
26543
|
|
|
26542
26544
|
// src/commands/canvas/normalize-paths.ts
|
|
@@ -29568,13 +29570,19 @@ function emitCaptionChain(blueprint, tracks, clock, opts, videoRef, nodes) {
|
|
|
29568
29570
|
const overlaidNode = nodes.find((n) => n.id === "overlaid");
|
|
29569
29571
|
if (overlaidNode) {
|
|
29570
29572
|
overlaidNode.inputs = { ...overlaidNode.inputs, transcript: "$ref:captions_transcribe.transcript" };
|
|
29573
|
+
if (opts.captionWordsPerGroup) {
|
|
29574
|
+
overlaidNode.params = { ...overlaidNode.params, words_per_group: opts.captionWordsPerGroup };
|
|
29575
|
+
}
|
|
29571
29576
|
return { videoRef };
|
|
29572
29577
|
}
|
|
29573
29578
|
nodes.push({
|
|
29574
29579
|
id: "captions",
|
|
29575
29580
|
type: "hyperframe_render",
|
|
29576
29581
|
inputs: { background: videoRef, transcript: "$ref:captions_transcribe.transcript" },
|
|
29577
|
-
params: {
|
|
29582
|
+
params: {
|
|
29583
|
+
composition: opts.captionsCompositionPath,
|
|
29584
|
+
...opts.captionWordsPerGroup ? { words_per_group: opts.captionWordsPerGroup } : {}
|
|
29585
|
+
}
|
|
29578
29586
|
});
|
|
29579
29587
|
return { videoRef: "$ref:captions.video", videoNode: "captions" };
|
|
29580
29588
|
}
|
|
@@ -30787,6 +30795,258 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
|
|
|
30787
30795
|
return { canvasPath: path14.resolve(resolvedTarget, manifest.canvas.path), restored, upToDate };
|
|
30788
30796
|
}
|
|
30789
30797
|
|
|
30798
|
+
// src/engine/nodes/local/lib/render-review.ts
|
|
30799
|
+
import { execFile as execFile2 } from "child_process";
|
|
30800
|
+
import { mkdtemp, readFile as readFile11, rm as rm5 } from "fs/promises";
|
|
30801
|
+
import { tmpdir } from "os";
|
|
30802
|
+
import path15 from "path";
|
|
30803
|
+
import { promisify as promisify2 } from "util";
|
|
30804
|
+
import { z as z28 } from "zod";
|
|
30805
|
+
var RENDER_REVIEW_MODEL = "gemini-pro-latest";
|
|
30806
|
+
var RENDER_REVIEW_FRAMES = 12;
|
|
30807
|
+
var RENDER_REVIEW_WIDTH = 1024;
|
|
30808
|
+
function reviewFrameTimes(durationS, frames) {
|
|
30809
|
+
const n = Math.max(2, frames);
|
|
30810
|
+
const last = Math.max(0, durationS - 0.15);
|
|
30811
|
+
const step = last / (n - 1);
|
|
30812
|
+
return Array.from({ length: n }, (_, i) => Math.round(i * step * 100) / 100);
|
|
30813
|
+
}
|
|
30814
|
+
function buildFrameGrabArgs(video, atS, out) {
|
|
30815
|
+
return [
|
|
30816
|
+
"-v",
|
|
30817
|
+
"error",
|
|
30818
|
+
"-ss",
|
|
30819
|
+
String(atS),
|
|
30820
|
+
"-i",
|
|
30821
|
+
video,
|
|
30822
|
+
"-frames:v",
|
|
30823
|
+
"1",
|
|
30824
|
+
"-vf",
|
|
30825
|
+
`scale=${RENDER_REVIEW_WIDTH}:-2`,
|
|
30826
|
+
out,
|
|
30827
|
+
"-y"
|
|
30828
|
+
];
|
|
30829
|
+
}
|
|
30830
|
+
var FrameReview = z28.object({
|
|
30831
|
+
/** Something in shot that could not happen, or is being done wrong. */
|
|
30832
|
+
impossible: z28.array(z28.string()).default([]),
|
|
30833
|
+
/** Objects present in frame that belong to no part of the scene. */
|
|
30834
|
+
strays: z28.array(z28.string()).default([])
|
|
30835
|
+
});
|
|
30836
|
+
var ReelReview = z28.object({
|
|
30837
|
+
/** Whether the people the script treats as one person stay one person. */
|
|
30838
|
+
cast_is_consistent: z28.boolean().default(true),
|
|
30839
|
+
cast_note: z28.string().default(""),
|
|
30840
|
+
/** Whether the advertiser's mark is visible anywhere. */
|
|
30841
|
+
brand_mark_visible: z28.boolean().default(false),
|
|
30842
|
+
/** Whether the last frames close the ad rather than trailing off. */
|
|
30843
|
+
ends_on_a_close: z28.boolean().default(false),
|
|
30844
|
+
ending_note: z28.string().default("")
|
|
30845
|
+
});
|
|
30846
|
+
function buildFrameReviewPrompt(opts) {
|
|
30847
|
+
const subject = opts.subject ? ` The advertiser is ${opts.subject}.` : "";
|
|
30848
|
+
return `This is ONE frame from a video advertisement.${subject}
|
|
30849
|
+
|
|
30850
|
+
Answer only about what is visible in THIS frame. Do not judge whether the ad is good, and do not comment on style, mood or lighting.
|
|
30851
|
+
|
|
30852
|
+
1. IMPOSSIBLE OR WRONG. Is anything shown that could not physically happen, or that shows the job being done incorrectly? Judge the PROCEDURE, not only the physics: equipment resting where it would never rest, a component fitted to nothing, someone handling a part in a way that would damage it, an object at the wrong scale beside a person, a body with a limb missing or duplicated.
|
|
30853
|
+
2. STRAYS. Is any object present that belongs to no part of the scene \u2014 floating, unattached, or cut off from whatever should hold it?
|
|
30854
|
+
|
|
30855
|
+
Report only what you can actually see. An empty list is the right answer for a clean frame. Keep each item under twelve words.
|
|
30856
|
+
|
|
30857
|
+
Reply with only this JSON: {"impossible":[],"strays":[]}`;
|
|
30858
|
+
}
|
|
30859
|
+
function buildReelReviewPrompt(opts) {
|
|
30860
|
+
const subject = opts.subject ? ` The advertiser is ${opts.subject}.` : "";
|
|
30861
|
+
const script = opts.script ? `
|
|
30862
|
+
|
|
30863
|
+
The voiceover says, in order:
|
|
30864
|
+
${opts.script}` : "";
|
|
30865
|
+
return `These frames are sampled in order from one video advertisement.${subject}` + script + `
|
|
30866
|
+
|
|
30867
|
+
Answer only about what is visible.
|
|
30868
|
+
|
|
30869
|
+
1. CAST. Do the people stay the same people? If the script speaks about one customer and the frames show different faces in that role, say so and count them.
|
|
30870
|
+
2. BRAND. Is the advertiser's logo or wordmark visible in ANY frame? Answer yes only if you can actually see a mark \u2014 a colour is not a mark.
|
|
30871
|
+
3. ENDING. Do the final frames close the advertisement \u2014 a call to action, a mark, a card \u2014 or does the picture simply carry on and stop?
|
|
30872
|
+
|
|
30873
|
+
Reply with only this JSON:
|
|
30874
|
+
{"cast_is_consistent":true,"cast_note":"","brand_mark_visible":false,"ends_on_a_close":false,"ending_note":""}`;
|
|
30875
|
+
}
|
|
30876
|
+
function parseReviewJson(raw) {
|
|
30877
|
+
const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
30878
|
+
const body = (fenced?.[1] ?? raw).trim();
|
|
30879
|
+
const start = body.indexOf("{");
|
|
30880
|
+
const end = body.lastIndexOf("}");
|
|
30881
|
+
if (start === -1 || end === -1 || end < start) return null;
|
|
30882
|
+
try {
|
|
30883
|
+
return JSON.parse(body.slice(start, end + 1));
|
|
30884
|
+
} catch {
|
|
30885
|
+
return null;
|
|
30886
|
+
}
|
|
30887
|
+
}
|
|
30888
|
+
var normalise = (s) => s.toLowerCase().replace(/[^a-z0-9 ]/g, " ").replace(/\s+/g, " ").trim();
|
|
30889
|
+
var ANATOMY = /\b(finger|fingers|hand|hands|thumb|limb|leg|legs|arm|arms|foot|feet|anatomy)\b/i;
|
|
30890
|
+
function collapseRepeats(frames) {
|
|
30891
|
+
const seen = /* @__PURE__ */ new Map();
|
|
30892
|
+
for (const frame of frames) {
|
|
30893
|
+
const parsed = FrameReview.safeParse(frame.json);
|
|
30894
|
+
if (!parsed.success) continue;
|
|
30895
|
+
for (const item of [...parsed.data.impossible, ...parsed.data.strays]) {
|
|
30896
|
+
const text2 = item.trim();
|
|
30897
|
+
const key = normalise(text2);
|
|
30898
|
+
if (!key) continue;
|
|
30899
|
+
const hit = seen.get(key);
|
|
30900
|
+
if (hit) hit.at.push(frame.at);
|
|
30901
|
+
else seen.set(key, { what: text2, at: [frame.at] });
|
|
30902
|
+
}
|
|
30903
|
+
}
|
|
30904
|
+
return [...seen.values()];
|
|
30905
|
+
}
|
|
30906
|
+
function classifyFrameFindings(frames) {
|
|
30907
|
+
const seen = collapseRepeats(frames);
|
|
30908
|
+
const anatomy = seen.filter((f) => ANATOMY.test(f.what));
|
|
30909
|
+
const rest = seen.filter((f) => !ANATOMY.test(f.what));
|
|
30910
|
+
const findings = rest.map((f) => ({
|
|
30911
|
+
severity: "blocking",
|
|
30912
|
+
what: `At ${f.at.map((s) => `${s}s`).join(", ")}: ${f.what}`
|
|
30913
|
+
}));
|
|
30914
|
+
if (anatomy.length > 0) {
|
|
30915
|
+
const at = [...new Set(anatomy.flatMap((f) => f.at))].sort((a, b) => a - b);
|
|
30916
|
+
findings.push({
|
|
30917
|
+
severity: "blocking",
|
|
30918
|
+
what: `Malformed hands or limbs in ${at.length} of ${frames.length} sampled frames (${at.map((s) => `${s}s`).join(", ")}): ${anatomy.map((f) => f.what).join(" ")} Re-render those beats \u2014 keep hands out of frame, or away from the object being held.`
|
|
30919
|
+
});
|
|
30920
|
+
}
|
|
30921
|
+
return findings;
|
|
30922
|
+
}
|
|
30923
|
+
function classifyReelFindings(json) {
|
|
30924
|
+
const parsed = ReelReview.safeParse(json);
|
|
30925
|
+
if (!parsed.success) return [];
|
|
30926
|
+
const r = parsed.data;
|
|
30927
|
+
const findings = [];
|
|
30928
|
+
if (!r.cast_is_consistent) {
|
|
30929
|
+
findings.push({
|
|
30930
|
+
severity: "blocking",
|
|
30931
|
+
what: `The cast changes between shots${r.cast_note.trim() ? `: ${r.cast_note.trim()}` : ""}. One customer played by several faces reads as stock footage. Ground the frames on one identity (\`baker avatars\`) or write the beats so the subject is never the same person twice.`
|
|
30932
|
+
});
|
|
30933
|
+
}
|
|
30934
|
+
if (!r.brand_mark_visible) {
|
|
30935
|
+
findings.push({
|
|
30936
|
+
severity: "warning",
|
|
30937
|
+
what: "No brand mark is visible in any frame. `baker canvas scaffold-ad` draws one from `brand.logo` (the repo path in src/brand/BRAND.md) \u2014 top-left throughout and large on the closing card \u2014 so a mark missing here means the spec carried no logo, or the path did not resolve and the scaffold said so."
|
|
30938
|
+
});
|
|
30939
|
+
}
|
|
30940
|
+
if (!r.ends_on_a_close) {
|
|
30941
|
+
findings.push({
|
|
30942
|
+
severity: "warning",
|
|
30943
|
+
what: `The video does not close${r.ending_note.trim() ? `: ${r.ending_note.trim()}` : ""} \u2014 it runs on and stops. Give the last beat a call to action, and keep its shot short enough that the picture ends with the read.`
|
|
30944
|
+
});
|
|
30945
|
+
}
|
|
30946
|
+
return findings;
|
|
30947
|
+
}
|
|
30948
|
+
function videoPathFromOutput(output) {
|
|
30949
|
+
const refs = Array.isArray(output) ? output : [output];
|
|
30950
|
+
let found = null;
|
|
30951
|
+
for (const ref of refs) {
|
|
30952
|
+
const r = ref;
|
|
30953
|
+
if (!r || typeof r !== "object") continue;
|
|
30954
|
+
const isVideo = r.kind === "video" || typeof r.mime === "string" && r.mime.startsWith("video/");
|
|
30955
|
+
if (isVideo && typeof r.path === "string" && r.path) found = r.path;
|
|
30956
|
+
}
|
|
30957
|
+
return found;
|
|
30958
|
+
}
|
|
30959
|
+
var RENDER_REVIEW_UNAVAILABLE = "The render was NOT reviewed \u2014 no frame-vision key in this environment. Watch it yourself before sending it on.";
|
|
30960
|
+
var REVIEW_CONCURRENCY = 3;
|
|
30961
|
+
async function mapWithLimit(items, limit, fn) {
|
|
30962
|
+
const out = new Array(items.length);
|
|
30963
|
+
let next = 0;
|
|
30964
|
+
await Promise.all(
|
|
30965
|
+
Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
30966
|
+
for (let i = next++; i < items.length; i = next++) {
|
|
30967
|
+
out[i] = await fn(items[i]);
|
|
30968
|
+
}
|
|
30969
|
+
})
|
|
30970
|
+
);
|
|
30971
|
+
return out;
|
|
30972
|
+
}
|
|
30973
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
30974
|
+
async function videoDuration(video) {
|
|
30975
|
+
try {
|
|
30976
|
+
const { stdout } = await execFileAsync2(
|
|
30977
|
+
"ffprobe",
|
|
30978
|
+
["-v", "error", "-show_entries", "format=duration", "-of", "default=nw=1:nk=1", video],
|
|
30979
|
+
{ timeout: 2e4 }
|
|
30980
|
+
);
|
|
30981
|
+
const seconds = Number.parseFloat(stdout.trim());
|
|
30982
|
+
return Number.isFinite(seconds) && seconds > 0 ? seconds : null;
|
|
30983
|
+
} catch {
|
|
30984
|
+
return null;
|
|
30985
|
+
}
|
|
30986
|
+
}
|
|
30987
|
+
async function ask(images, prompt) {
|
|
30988
|
+
const key = frameVisionKey();
|
|
30989
|
+
if (!key) return null;
|
|
30990
|
+
const parts = [{ text: prompt }];
|
|
30991
|
+
for (const b64 of images) parts.push({ inline_data: { mime_type: "image/jpeg", data: b64 } });
|
|
30992
|
+
try {
|
|
30993
|
+
const response = await fetch(
|
|
30994
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${RENDER_REVIEW_MODEL}:generateContent`,
|
|
30995
|
+
{
|
|
30996
|
+
method: "POST",
|
|
30997
|
+
headers: { "content-type": "application/json", "x-goog-api-key": key },
|
|
30998
|
+
body: JSON.stringify({ contents: [{ parts }] }),
|
|
30999
|
+
signal: AbortSignal.timeout(18e4)
|
|
31000
|
+
}
|
|
31001
|
+
);
|
|
31002
|
+
if (!response.ok) return null;
|
|
31003
|
+
const body = await response.json();
|
|
31004
|
+
const text2 = body.candidates?.[0]?.content?.parts?.map((p) => p.text ?? "").join("");
|
|
31005
|
+
return text2 ? parseReviewJson(text2) : null;
|
|
31006
|
+
} catch {
|
|
31007
|
+
return null;
|
|
31008
|
+
}
|
|
31009
|
+
}
|
|
31010
|
+
async function reviewRenderedVideo(opts) {
|
|
31011
|
+
if (!hasFrameVisionKey()) return null;
|
|
31012
|
+
const duration = await videoDuration(opts.video);
|
|
31013
|
+
if (duration === null) return null;
|
|
31014
|
+
const dir = await mkdtemp(path15.join(tmpdir(), "baker-review-"));
|
|
31015
|
+
try {
|
|
31016
|
+
const grabbed = [];
|
|
31017
|
+
for (const [i, at] of reviewFrameTimes(duration, RENDER_REVIEW_FRAMES).entries()) {
|
|
31018
|
+
const out = path15.join(dir, `f${String(i).padStart(2, "0")}.jpg`);
|
|
31019
|
+
try {
|
|
31020
|
+
await execFileAsync2("ffmpeg", buildFrameGrabArgs(opts.video, at, out), { timeout: 3e4 });
|
|
31021
|
+
grabbed.push({ at, image: await readFile11(out, { encoding: "base64" }) });
|
|
31022
|
+
} catch {
|
|
31023
|
+
}
|
|
31024
|
+
}
|
|
31025
|
+
if (grabbed.length === 0) return null;
|
|
31026
|
+
const reel = await ask(
|
|
31027
|
+
grabbed.map((f) => f.image),
|
|
31028
|
+
buildReelReviewPrompt({ subject: opts.subject, script: opts.script })
|
|
31029
|
+
);
|
|
31030
|
+
const framePrompt = buildFrameReviewPrompt({ subject: opts.subject });
|
|
31031
|
+
const perFrame = await mapWithLimit(grabbed, REVIEW_CONCURRENCY, async (f) => ({
|
|
31032
|
+
at: f.at,
|
|
31033
|
+
json: await ask([f.image], framePrompt)
|
|
31034
|
+
}));
|
|
31035
|
+
const unread = perFrame.filter((f) => f.json === null).length;
|
|
31036
|
+
if (unread === perFrame.length && reel === null) return null;
|
|
31037
|
+
const findings = [...classifyFrameFindings(perFrame), ...classifyReelFindings(reel)];
|
|
31038
|
+
if (unread > 0) {
|
|
31039
|
+
findings.push({
|
|
31040
|
+
severity: "warning",
|
|
31041
|
+
what: `${unread} of ${perFrame.length} frames could not be reviewed \u2014 that part of the video is unchecked.`
|
|
31042
|
+
});
|
|
31043
|
+
}
|
|
31044
|
+
return findings;
|
|
31045
|
+
} finally {
|
|
31046
|
+
await rm5(dir, { recursive: true, force: true });
|
|
31047
|
+
}
|
|
31048
|
+
}
|
|
31049
|
+
|
|
30790
31050
|
// src/commands/canvas/run.ts
|
|
30791
31051
|
var runCommand = defineCommand100({
|
|
30792
31052
|
meta: { name: "run", description: "Validate and execute a canvas JSON file." },
|
|
@@ -30864,8 +31124,8 @@ function resolveMaxCredits(...candidates) {
|
|
|
30864
31124
|
return void 0;
|
|
30865
31125
|
}
|
|
30866
31126
|
async function executeCanvasRun(opts) {
|
|
30867
|
-
const filePath =
|
|
30868
|
-
const raw = await
|
|
31127
|
+
const filePath = path16.resolve(opts.file);
|
|
31128
|
+
const raw = await readFile12(filePath, "utf8");
|
|
30869
31129
|
let parsed;
|
|
30870
31130
|
try {
|
|
30871
31131
|
parsed = JSON.parse(raw);
|
|
@@ -30887,7 +31147,7 @@ ${describeRewrites(healed.rewrites)}
|
|
|
30887
31147
|
`
|
|
30888
31148
|
);
|
|
30889
31149
|
}
|
|
30890
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
31150
|
+
parsed = resolveRelativeCanvasPaths(parsed, path16.dirname(filePath));
|
|
30891
31151
|
try {
|
|
30892
31152
|
await syncStyleProjection(parsed, (line) => process.stderr.write(`${line}
|
|
30893
31153
|
`));
|
|
@@ -30993,7 +31253,7 @@ ${describeRewrites(healed.rewrites)}
|
|
|
30993
31253
|
const canvasSha = sha256Hex(Buffer.from(canvasText));
|
|
30994
31254
|
const creativeSlug = creativeSlugFromCanvasPath(filePath) ?? void 0;
|
|
30995
31255
|
const client = opts.record === false ? null : buildBackendClient();
|
|
30996
|
-
const outputsDir = opts.outputsDir ?
|
|
31256
|
+
const outputsDir = opts.outputsDir ? path16.resolve(opts.outputsDir) : path16.resolve("canvas");
|
|
30997
31257
|
const { runId, resumed, source, concurrentRunId } = await resolveRunId({
|
|
30998
31258
|
explicitRunId: opts.runId,
|
|
30999
31259
|
fresh: opts.fresh === true,
|
|
@@ -31027,7 +31287,7 @@ ${describeRewrites(healed.rewrites)}
|
|
|
31027
31287
|
const canvasSnapshotUrl = client && creativeSlug ? await uploadRunSnapshot(client, { canvasPath: filePath, raw: canvasText, creativeSlug, parsed }) ?? void 0 : void 0;
|
|
31028
31288
|
const recordMeta = {
|
|
31029
31289
|
creativeSlug,
|
|
31030
|
-
canvasPath:
|
|
31290
|
+
canvasPath: path16.relative(process.cwd(), filePath) || void 0,
|
|
31031
31291
|
canvasSha,
|
|
31032
31292
|
// The fingerprint the dashboard compares against the current source to flag
|
|
31033
31293
|
// "edited since last render". Computed from the on-disk canvas.json +
|
|
@@ -31072,6 +31332,12 @@ ${describeRewrites(healed.rewrites)}
|
|
|
31072
31332
|
await pruneOldRuns(outputsDir, keepRuns, result.run_id, (line) => process.stdout.write(`${line}
|
|
31073
31333
|
`));
|
|
31074
31334
|
}
|
|
31335
|
+
const reviewable = videoPathFromOutput(result.output);
|
|
31336
|
+
const review = reviewable ? await reviewRenderedVideo({ video: reviewable }) : null;
|
|
31337
|
+
const hints = reviewable ? review === null ? [RENDER_REVIEW_UNAVAILABLE] : review.length === 0 ? ["Reviewed the finished video frame by frame \u2014 nothing to report."] : [
|
|
31338
|
+
`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.`,
|
|
31339
|
+
...review.map((f) => `${f.severity === "blocking" ? "MUST FIX" : "SHOULD FIX"} \u2014 ${f.what}`)
|
|
31340
|
+
] : [];
|
|
31075
31341
|
process.stdout.write(
|
|
31076
31342
|
`${JSON.stringify(
|
|
31077
31343
|
{
|
|
@@ -31079,7 +31345,9 @@ ${describeRewrites(healed.rewrites)}
|
|
|
31079
31345
|
run_id: result.run_id,
|
|
31080
31346
|
outputs_dir: result.outputs_dir,
|
|
31081
31347
|
output: result.output,
|
|
31082
|
-
stats: result.stats
|
|
31348
|
+
stats: result.stats,
|
|
31349
|
+
...review ? { review } : {},
|
|
31350
|
+
...hints.length ? { hints } : {}
|
|
31083
31351
|
},
|
|
31084
31352
|
null,
|
|
31085
31353
|
2
|
|
@@ -31214,7 +31482,7 @@ var rerunCommand = defineCommand101({
|
|
|
31214
31482
|
if (latest.canvasSha && manifest.canvasSha !== latest.canvasSha) {
|
|
31215
31483
|
fail2("snapshot_mismatch", `snapshot manifest for run ${latest.runId} does not match its recorded canvas sha`);
|
|
31216
31484
|
}
|
|
31217
|
-
const targetDir =
|
|
31485
|
+
const targetDir = path17.resolve("src", "creatives", slug);
|
|
31218
31486
|
let restoredCanvasPath;
|
|
31219
31487
|
try {
|
|
31220
31488
|
const restore = await restoreRunSnapshot(manifest, targetDir, { force: args["force-remote"] === true });
|
|
@@ -31263,12 +31531,12 @@ async function fetchManifest(url) {
|
|
|
31263
31531
|
}
|
|
31264
31532
|
|
|
31265
31533
|
// src/commands/canvas/scaffold-static-ad.ts
|
|
31266
|
-
import { access, mkdir as mkdir6, readFile as
|
|
31267
|
-
import
|
|
31534
|
+
import { access, mkdir as mkdir6, readFile as readFile14, writeFile as writeFile8 } from "fs/promises";
|
|
31535
|
+
import path21 from "path";
|
|
31268
31536
|
import { defineCommand as defineCommand103 } from "citty";
|
|
31269
31537
|
|
|
31270
31538
|
// src/engine/scaffold/staticAd.ts
|
|
31271
|
-
import { z as
|
|
31539
|
+
import { z as z29 } from "zod";
|
|
31272
31540
|
var GEN_ASPECT_RATIOS = /* @__PURE__ */ new Set(["1:1", "4:5", "9:16", "16:9", "4:3", "3:4", "2:3", "3:2", "21:9"]);
|
|
31273
31541
|
var DEFAULT_ASPECT_RATIO = "9:16";
|
|
31274
31542
|
var SHEET_SUBJECT_TYPE2 = {
|
|
@@ -31280,24 +31548,24 @@ var ACTOR_SHEET_IMAGE_SIZE = "4K";
|
|
|
31280
31548
|
var ADAPT_MODEL = "google/gemini-3-pro-image-preview";
|
|
31281
31549
|
var ADAPT_IMAGE_SIZE = "2K";
|
|
31282
31550
|
var ADAPT_GUIDANCE = "Keep the headline, logo, CTA, and hero subject fully visible in every ratio. Reproduce every text string verbatim \u2014 no dropped, added, or altered characters \u2014 and preserve the exact brand-color treatment (e.g. a black\u2192red word pivot), never flattening it.";
|
|
31283
|
-
var Blueprint =
|
|
31284
|
-
meta:
|
|
31285
|
-
text_content:
|
|
31551
|
+
var Blueprint = z29.object({
|
|
31552
|
+
meta: z29.object({ estimated_aspect_ratio: z29.string().optional() }).loose().optional(),
|
|
31553
|
+
text_content: z29.array(z29.object({ text: z29.string().optional() }).loose()).optional()
|
|
31286
31554
|
}).loose();
|
|
31287
|
-
var ElementLocator =
|
|
31288
|
-
collection:
|
|
31289
|
-
index:
|
|
31555
|
+
var ElementLocator = z29.object({
|
|
31556
|
+
collection: z29.enum(["subjects", "people", "brands_logos"]),
|
|
31557
|
+
index: z29.number().int().nonnegative()
|
|
31290
31558
|
}).loose();
|
|
31291
|
-
var MainElement =
|
|
31559
|
+
var MainElement = z29.object({
|
|
31292
31560
|
// logo | product | person | animal | badge | other
|
|
31293
|
-
type:
|
|
31294
|
-
label:
|
|
31295
|
-
description:
|
|
31296
|
-
expression:
|
|
31297
|
-
reason:
|
|
31561
|
+
type: z29.string(),
|
|
31562
|
+
label: z29.string().optional(),
|
|
31563
|
+
description: z29.string().optional(),
|
|
31564
|
+
expression: z29.string().nullable().optional(),
|
|
31565
|
+
reason: z29.string().optional(),
|
|
31298
31566
|
locator: ElementLocator.optional()
|
|
31299
31567
|
}).loose();
|
|
31300
|
-
var MainElements =
|
|
31568
|
+
var MainElements = z29.array(MainElement);
|
|
31301
31569
|
function sanitizeId2(raw, fallback) {
|
|
31302
31570
|
const id = raw.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
31303
31571
|
return /^[a-z]/.test(id) ? id : `${fallback}_${id}`.replace(/_+$/g, "") || fallback;
|
|
@@ -31546,7 +31814,7 @@ function staticAdReport(input, elementsInput, opts) {
|
|
|
31546
31814
|
}
|
|
31547
31815
|
|
|
31548
31816
|
// src/commands/canvas/creative-definition.ts
|
|
31549
|
-
import
|
|
31817
|
+
import path18 from "path";
|
|
31550
31818
|
var PLATFORM_VALUES = ["meta", "google", "linkedin", "tiktok", "youtube", "x", "other"];
|
|
31551
31819
|
var FORMAT_VALUES = ["1:1", "4:5", "9:16", "16:9", "1.91:1"];
|
|
31552
31820
|
function titleFromSlug(slug) {
|
|
@@ -31594,16 +31862,16 @@ function buildCreativeDefinition(input) {
|
|
|
31594
31862
|
}
|
|
31595
31863
|
|
|
31596
31864
|
// src/commands/canvas/scaffold-static-ad-paths.ts
|
|
31597
|
-
import
|
|
31865
|
+
import path19 from "path";
|
|
31598
31866
|
function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd(), slug) {
|
|
31599
31867
|
const file = rawFile.trim();
|
|
31600
31868
|
const imageIsUrl = /^https?:\/\//i.test(file);
|
|
31601
|
-
const imageSource = imageIsUrl ? file :
|
|
31602
|
-
const outPath = out ?
|
|
31603
|
-
const blueprintPath =
|
|
31604
|
-
const creativeDir = slug ?
|
|
31605
|
-
const definitionPath = creativeDir ?
|
|
31606
|
-
const referencesDir = creativeDir ?
|
|
31869
|
+
const imageSource = imageIsUrl ? file : path19.resolve(cwd, file);
|
|
31870
|
+
const outPath = out ? path19.resolve(cwd, out) : slug ? path19.join(cwd, "src", "creatives", slug, `${slug}.canvas.json`) : imageIsUrl ? path19.join(cwd, "static-ad.canvas.json") : path19.join(path19.dirname(imageSource), "static-ad.canvas.json");
|
|
31871
|
+
const blueprintPath = path19.join(path19.dirname(outPath), "prompt.json");
|
|
31872
|
+
const creativeDir = slug ? path19.dirname(outPath) : null;
|
|
31873
|
+
const definitionPath = creativeDir ? path19.join(creativeDir, "_definition.md") : null;
|
|
31874
|
+
const referencesDir = creativeDir ? path19.join(creativeDir, "references") : null;
|
|
31607
31875
|
return { imageIsUrl, imageSource, outPath, blueprintPath, creativeDir, definitionPath, referencesDir };
|
|
31608
31876
|
}
|
|
31609
31877
|
var SCAFFOLD_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
@@ -31613,8 +31881,8 @@ function isValidScaffoldSlug(slug) {
|
|
|
31613
31881
|
}
|
|
31614
31882
|
|
|
31615
31883
|
// src/commands/canvas/sync-definition.ts
|
|
31616
|
-
import { readdir as readdir6, readFile as
|
|
31617
|
-
import
|
|
31884
|
+
import { readdir as readdir6, readFile as readFile13, stat as stat3 } from "fs/promises";
|
|
31885
|
+
import path20 from "path";
|
|
31618
31886
|
import { defineCommand as defineCommand102 } from "citty";
|
|
31619
31887
|
|
|
31620
31888
|
// src/commands/canvas/definition-graph.ts
|
|
@@ -31707,15 +31975,15 @@ async function syncCreativeDefinitionBestEffort(input) {
|
|
|
31707
31975
|
}
|
|
31708
31976
|
}
|
|
31709
31977
|
async function resolveCanvasPath(inputPath) {
|
|
31710
|
-
const resolved =
|
|
31978
|
+
const resolved = path20.resolve(inputPath);
|
|
31711
31979
|
let dir = resolved;
|
|
31712
31980
|
try {
|
|
31713
31981
|
if ((await stat3(resolved)).isFile()) {
|
|
31714
31982
|
if (resolved.endsWith(".canvas.json")) return resolved;
|
|
31715
|
-
dir =
|
|
31983
|
+
dir = path20.dirname(resolved);
|
|
31716
31984
|
}
|
|
31717
31985
|
} catch {
|
|
31718
|
-
dir = resolved.endsWith(".canvas.json") ?
|
|
31986
|
+
dir = resolved.endsWith(".canvas.json") ? path20.dirname(resolved) : resolved;
|
|
31719
31987
|
}
|
|
31720
31988
|
let entries;
|
|
31721
31989
|
try {
|
|
@@ -31726,7 +31994,7 @@ async function resolveCanvasPath(inputPath) {
|
|
|
31726
31994
|
const canvases = entries.filter((name) => name.endsWith(".canvas.json"));
|
|
31727
31995
|
const slug = creativeSlugFromCanvasPath(`${dir}/x/`);
|
|
31728
31996
|
const chosen = (slug ? canvases.find((name) => name === `${slug}.canvas.json`) : void 0) ?? canvases[0];
|
|
31729
|
-
return chosen ?
|
|
31997
|
+
return chosen ? path20.join(dir, chosen) : null;
|
|
31730
31998
|
}
|
|
31731
31999
|
var syncDefinitionCommand = defineCommand102({
|
|
31732
32000
|
meta: {
|
|
@@ -31749,7 +32017,7 @@ var syncDefinitionCommand = defineCommand102({
|
|
|
31749
32017
|
if (!slug) return;
|
|
31750
32018
|
let canvas;
|
|
31751
32019
|
try {
|
|
31752
|
-
canvas = JSON.parse(await
|
|
32020
|
+
canvas = JSON.parse(await readFile13(canvasPath, "utf8"));
|
|
31753
32021
|
} catch {
|
|
31754
32022
|
return;
|
|
31755
32023
|
}
|
|
@@ -31776,7 +32044,7 @@ async function uploadSourceAsReference(source, isUrl, client) {
|
|
|
31776
32044
|
throw new Error(`failed to download source image (${e instanceof Error ? e.message : String(e)})`);
|
|
31777
32045
|
}
|
|
31778
32046
|
} else {
|
|
31779
|
-
bytes = await
|
|
32047
|
+
bytes = await readFile14(source);
|
|
31780
32048
|
}
|
|
31781
32049
|
const safe = await toModelSafeImage(bytes);
|
|
31782
32050
|
const sha256 = sha256Hex(safe.bytes);
|
|
@@ -31831,7 +32099,7 @@ DROP background extras, decorative props, generic scenery, and anything small or
|
|
|
31831
32099
|
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.`;
|
|
31832
32100
|
async function loadAssetText(ref, label) {
|
|
31833
32101
|
const r = ref;
|
|
31834
|
-
if (typeof r?.path === "string") return
|
|
32102
|
+
if (typeof r?.path === "string") return readFile14(r.path, "utf8");
|
|
31835
32103
|
if (typeof r?.url === "string") {
|
|
31836
32104
|
const res = await fetch(r.url);
|
|
31837
32105
|
if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
|
|
@@ -32007,7 +32275,7 @@ var scaffoldStaticAdCommand = defineCommand103({
|
|
|
32007
32275
|
process.cwd(),
|
|
32008
32276
|
slug
|
|
32009
32277
|
);
|
|
32010
|
-
await mkdir6(
|
|
32278
|
+
await mkdir6(path21.dirname(outPath), { recursive: true });
|
|
32011
32279
|
const { describeModel, selectModel, layoutModel, genModel } = resolveModels(args);
|
|
32012
32280
|
let durableSourceUrl;
|
|
32013
32281
|
if (referencesDir) {
|
|
@@ -32127,7 +32395,7 @@ var scaffoldStaticAdCommand = defineCommand103({
|
|
|
32127
32395
|
run_estimated_credits: validation.estimatedCredits
|
|
32128
32396
|
},
|
|
32129
32397
|
checklist: {
|
|
32130
|
-
edit_prompt: `Edit ${
|
|
32398
|
+
edit_prompt: `Edit ${path21.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.`,
|
|
32131
32399
|
assets_to_supply: report.elements,
|
|
32132
32400
|
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)",
|
|
32133
32401
|
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)",
|
|
@@ -32144,49 +32412,56 @@ var scaffoldStaticAdCommand = defineCommand103({
|
|
|
32144
32412
|
});
|
|
32145
32413
|
|
|
32146
32414
|
// src/commands/canvas/scaffold-ad.ts
|
|
32147
|
-
import { cp, mkdir as mkdir7, readFile as
|
|
32148
|
-
import
|
|
32415
|
+
import { copyFile, cp, mkdir as mkdir7, readFile as readFile15, writeFile as writeFile9 } from "fs/promises";
|
|
32416
|
+
import path23 from "path";
|
|
32149
32417
|
import { defineCommand as defineCommand104 } from "citty";
|
|
32150
32418
|
|
|
32151
32419
|
// src/engine/scaffold/ad-spec.ts
|
|
32152
|
-
import { z as
|
|
32420
|
+
import { z as z30 } from "zod";
|
|
32153
32421
|
var oneClause = (line) => (line.match(/[.!?](\s|$)/g) ?? []).length <= 1;
|
|
32154
|
-
var Beat =
|
|
32422
|
+
var Beat = z30.object({
|
|
32155
32423
|
/**
|
|
32156
32424
|
* The line the voice says in this beat — ONE clause, ending in its own
|
|
32157
32425
|
* punctuation. It becomes the caption card verbatim.
|
|
32158
32426
|
*/
|
|
32159
|
-
say:
|
|
32427
|
+
say: z30.string().min(1).refine(oneClause, "a beat is one clause: split a second sentence into its own beat"),
|
|
32160
32428
|
/** What is on screen while it is said, as a shot brief for the video model. */
|
|
32161
|
-
show:
|
|
32429
|
+
show: z30.string().min(1),
|
|
32162
32430
|
/**
|
|
32163
32431
|
* Set only when this beat's subject is talking to camera. Off by default, and
|
|
32164
32432
|
* the default is load-bearing: a person visibly speaking under a voice that is
|
|
32165
32433
|
* not theirs reads as bad dubbing, which is what sank the first cut of this.
|
|
32166
32434
|
*/
|
|
32167
|
-
on_camera:
|
|
32435
|
+
on_camera: z30.boolean().optional(),
|
|
32436
|
+
/**
|
|
32437
|
+
* Whether the ad's recurring person is in this shot. Defaults to true whenever the
|
|
32438
|
+
* spec declares a `cast`, because an ad is usually about one person and defaulting
|
|
32439
|
+
* the other way is how continuity gets forgotten. Set false for a beat they are not
|
|
32440
|
+
* in — a fitter on a roof, a product close-up.
|
|
32441
|
+
*/
|
|
32442
|
+
cast: z30.boolean().optional()
|
|
32168
32443
|
});
|
|
32169
|
-
var AdSpec =
|
|
32170
|
-
format:
|
|
32171
|
-
aspect_ratio:
|
|
32172
|
-
resolution:
|
|
32444
|
+
var AdSpec = z30.object({
|
|
32445
|
+
format: z30.object({
|
|
32446
|
+
aspect_ratio: z30.string().default("9:16"),
|
|
32447
|
+
resolution: z30.string().default("720p")
|
|
32173
32448
|
}).default({ aspect_ratio: "9:16", resolution: "720p" }),
|
|
32174
|
-
brand:
|
|
32175
|
-
name:
|
|
32449
|
+
brand: z30.object({
|
|
32450
|
+
name: z30.string().optional(),
|
|
32176
32451
|
/** Figures to lift typographically — the one gold number every ad has. */
|
|
32177
|
-
highlight:
|
|
32452
|
+
highlight: z30.array(z30.string()).optional(),
|
|
32178
32453
|
/**
|
|
32179
32454
|
* Brand hex colours, most important first. They dress the brand plate and
|
|
32180
32455
|
* the type — never the scenes: pushing hex into a shot brief makes the
|
|
32181
32456
|
* model paint the whole frame that colour, which reads as a filter.
|
|
32182
32457
|
*/
|
|
32183
|
-
palette:
|
|
32458
|
+
palette: z30.array(z30.string()).optional(),
|
|
32184
32459
|
/** Repo path to the brand mark, so the CTA carries it instead of a name in a font. */
|
|
32185
|
-
logo:
|
|
32460
|
+
logo: z30.string().optional()
|
|
32186
32461
|
}).optional(),
|
|
32187
|
-
voice:
|
|
32188
|
-
description:
|
|
32189
|
-
language:
|
|
32462
|
+
voice: z30.object({
|
|
32463
|
+
description: z30.string().min(1),
|
|
32464
|
+
language: z30.string().optional()
|
|
32190
32465
|
}).optional(),
|
|
32191
32466
|
/**
|
|
32192
32467
|
* Where the ad is set, as a place a model can picture: "Spain", "Mexico",
|
|
@@ -32197,7 +32472,20 @@ var AdSpec = z29.object({
|
|
|
32197
32472
|
* houses — sash windows, slate roofs, English suburbs — in every single frame,
|
|
32198
32473
|
* from briefs that were otherwise perfectly good.
|
|
32199
32474
|
*/
|
|
32200
|
-
market:
|
|
32475
|
+
market: z30.string().min(1).optional(),
|
|
32476
|
+
/**
|
|
32477
|
+
* The one person the ad is about.
|
|
32478
|
+
*
|
|
32479
|
+
* Declared once and grounded on every beat they appear in, because the engine already
|
|
32480
|
+
* builds a reference sheet from this and tells each frame to render THE SAME
|
|
32481
|
+
* individual. Without it each beat invents its own face: a 28-second ad came back
|
|
32482
|
+
* with five different men playing one customer, which reads as a stock-footage
|
|
32483
|
+
* collage rather than a story.
|
|
32484
|
+
*/
|
|
32485
|
+
cast: z30.object({
|
|
32486
|
+
/** Who they are, plainly: age, build, hair, wardrobe, the register of the ad. */
|
|
32487
|
+
description: z30.string().min(1)
|
|
32488
|
+
}).optional(),
|
|
32201
32489
|
/**
|
|
32202
32490
|
* A bed, described in words. Omitted leaves the ad dry — the engine only wires
|
|
32203
32491
|
* music when asked, and silence is a legitimate choice.
|
|
@@ -32206,8 +32494,27 @@ var AdSpec = z29.object({
|
|
|
32206
32494
|
* scroll-stopper and has to read clean. That is the engine's rule, not this
|
|
32207
32495
|
* one, and it is why the prompt is all this needs to carry.
|
|
32208
32496
|
*/
|
|
32209
|
-
music:
|
|
32210
|
-
|
|
32497
|
+
music: z30.string().min(1).optional(),
|
|
32498
|
+
/**
|
|
32499
|
+
* How the ad closes.
|
|
32500
|
+
*
|
|
32501
|
+
* On by default whenever the spec carries a brand, because an ad that stops instead
|
|
32502
|
+
* of closing is what shipped: the last caption fell four seconds before the last
|
|
32503
|
+
* frame and the picture simply ran out on a man shuffling paper. The closing beat's
|
|
32504
|
+
* PICTURE becomes a flat brand plate — the engine already refuses to generate one as
|
|
32505
|
+
* a photograph, since a video model garbles a wordmark — and the mark and the call to
|
|
32506
|
+
* action are drawn over it by the composition.
|
|
32507
|
+
*
|
|
32508
|
+
* `false` opts out, for an ad that genuinely ends on its footage.
|
|
32509
|
+
*/
|
|
32510
|
+
end_card: z30.union([
|
|
32511
|
+
z30.literal(false),
|
|
32512
|
+
z30.object({
|
|
32513
|
+
/** The words on the button. Defaults to the last beat's line. */
|
|
32514
|
+
cta: z30.string().min(1).optional()
|
|
32515
|
+
})
|
|
32516
|
+
]).optional(),
|
|
32517
|
+
beats: z30.array(Beat).min(2)
|
|
32211
32518
|
});
|
|
32212
32519
|
function narrativeRole(index, total) {
|
|
32213
32520
|
if (index === 0) return "hook";
|
|
@@ -32248,14 +32555,34 @@ function beatSeconds(line) {
|
|
|
32248
32555
|
const allowed = [...SEEDANCE_DURATIONS].sort((a, b) => a - b);
|
|
32249
32556
|
return allowed.find((d) => d >= spoken) ?? allowed[allowed.length - 1];
|
|
32250
32557
|
}
|
|
32558
|
+
var BRAND_PLATE = "A solid flat brand colour plate \u2014 an end card. No people, no objects, no scenery, no text of any kind.";
|
|
32559
|
+
function endCardWanted(spec) {
|
|
32560
|
+
if (spec.end_card === false) return false;
|
|
32561
|
+
return Boolean(spec.brand?.name || spec.brand?.logo);
|
|
32562
|
+
}
|
|
32563
|
+
function endCardCta(spec) {
|
|
32564
|
+
if (!endCardWanted(spec)) return null;
|
|
32565
|
+
const declared = spec.end_card === false ? void 0 : spec.end_card?.cta;
|
|
32566
|
+
return declared?.trim() || (spec.beats[spec.beats.length - 1]?.say ?? "").trim() || null;
|
|
32567
|
+
}
|
|
32568
|
+
function adSpecCastElements(spec) {
|
|
32569
|
+
const description = spec.cast?.description?.trim();
|
|
32570
|
+
if (!description) return [];
|
|
32571
|
+
const lastIndex = spec.beats.length - 1;
|
|
32572
|
+
const scenes = spec.beats.map((beat, i) => ({ beat, i })).filter(({ beat, i }) => beat.cast !== false && !(endCardWanted(spec) && i === lastIndex)).map(({ i }) => i);
|
|
32573
|
+
if (scenes.length === 0) return [];
|
|
32574
|
+
return [{ type: "person", label: "customer", description, scenes }];
|
|
32575
|
+
}
|
|
32251
32576
|
function adSpecToBlueprint(spec) {
|
|
32252
32577
|
const total = spec.beats.length;
|
|
32253
32578
|
const market = marketFor(spec);
|
|
32254
32579
|
const place = market ? ` Set in ${market}: the architecture, streets and styling are ${market}'s.` : "";
|
|
32255
32580
|
const currency = market ? CURRENCY_BY_MARKET[market.toLowerCase()] : void 0;
|
|
32256
32581
|
const physics = " Everything obeys real-world physics: paper, card and screens are OPAQUE with nothing showing through from behind, every object is at believable real-world scale next to the people handling it, and every object has its real-world form and construction \u2014 a phone has ONE screen and it is on the front. NO readable text or numbers anywhere in frame \u2014 phone screens, documents and signage stay illegible or out of focus, because any figure the model invents will contradict the script." + (currency ? ` If a currency is unavoidably visible it is ${currency}.` : "");
|
|
32582
|
+
const closesOnCard = endCardWanted(spec);
|
|
32257
32583
|
let clock = 0;
|
|
32258
32584
|
const scenes = spec.beats.map((beat, i) => {
|
|
32585
|
+
const isClosingCard = closesOnCard && i === total - 1;
|
|
32259
32586
|
const duration = beatSeconds(beat.say);
|
|
32260
32587
|
const start = clock;
|
|
32261
32588
|
clock += duration;
|
|
@@ -32263,7 +32590,7 @@ function adSpecToBlueprint(spec) {
|
|
|
32263
32590
|
start_s: Math.round(start * 100) / 100,
|
|
32264
32591
|
end_s: Math.round(clock * 100) / 100,
|
|
32265
32592
|
duration_s: duration,
|
|
32266
|
-
summary: beat.say,
|
|
32593
|
+
summary: isClosingCard ? `${BRAND_PLATE} ${beat.say}` : beat.say,
|
|
32267
32594
|
narrative_role: narrativeRole(i, total),
|
|
32268
32595
|
// The read is built from per-scene dialogue, not from the global
|
|
32269
32596
|
// transcript: emitting only the transcript scaffolds a SILENT ad. And
|
|
@@ -32279,12 +32606,12 @@ function adSpecToBlueprint(spec) {
|
|
|
32279
32606
|
...spec.voice?.description ? { voice_description: spec.voice.description } : {}
|
|
32280
32607
|
}
|
|
32281
32608
|
],
|
|
32282
|
-
motion_prompt: beat.on_camera ? `${beat.show}${place}${physics}` : (
|
|
32609
|
+
motion_prompt: isClosingCard ? BRAND_PLATE : beat.on_camera ? `${beat.show}${place}${physics}` : (
|
|
32283
32610
|
// The subject must not be forming words: the voice belongs to the
|
|
32284
32611
|
// narration, and a mouth moving under it reads as dubbing.
|
|
32285
32612
|
`${beat.show}${place}${physics} Nobody in frame is speaking \u2014 mouths closed, no dialogue.`
|
|
32286
32613
|
),
|
|
32287
|
-
start_frame_prompt: `${beat.show}${place}${physics}`
|
|
32614
|
+
start_frame_prompt: isClosingCard ? BRAND_PLATE : `${beat.show}${place}${physics}`
|
|
32288
32615
|
};
|
|
32289
32616
|
});
|
|
32290
32617
|
return {
|
|
@@ -32310,20 +32637,91 @@ function adSpecToBlueprint(spec) {
|
|
|
32310
32637
|
};
|
|
32311
32638
|
}
|
|
32312
32639
|
|
|
32640
|
+
// src/engine/scaffold/ad-brand-overlay.ts
|
|
32641
|
+
var escapeHtml2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
32642
|
+
function firstHex(palette) {
|
|
32643
|
+
const hex = (palette ?? []).find((h) => /^#?[0-9a-fA-F]{6}$/.test(h.trim()));
|
|
32644
|
+
if (!hex) return null;
|
|
32645
|
+
const trimmed = hex.trim();
|
|
32646
|
+
return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
|
|
32647
|
+
}
|
|
32648
|
+
function buildAdBrandOverlay(opts) {
|
|
32649
|
+
const { logo, cta, cardAtS, cardDurationS, totalS, accent } = opts;
|
|
32650
|
+
if (!logo && !cta?.trim()) return "";
|
|
32651
|
+
const parts = [
|
|
32652
|
+
"<!-- Brand layer, emitted by `baker canvas scaffold-ad`. The mark and the closing",
|
|
32653
|
+
" card are drawn HERE, by the composition \u2014 never generated as a picture, because",
|
|
32654
|
+
" a video model garbles a wordmark every time. Restyle freely; keep the `clip`",
|
|
32655
|
+
" class and the data-start/data-dur attributes, which is how the runtime times them. -->"
|
|
32656
|
+
];
|
|
32657
|
+
if (logo) {
|
|
32658
|
+
parts.push(
|
|
32659
|
+
`<img class="clip brand-mark" src="${escapeHtml2(logo.file)}" alt="${escapeHtml2(logo.alt)}" data-start="0" data-dur="${totalS}" />`
|
|
32660
|
+
);
|
|
32661
|
+
}
|
|
32662
|
+
const cardBits = [];
|
|
32663
|
+
if (logo) {
|
|
32664
|
+
cardBits.push(`<img class="endcard-logo" src="${escapeHtml2(logo.file)}" alt="${escapeHtml2(logo.alt)}" />`);
|
|
32665
|
+
}
|
|
32666
|
+
if (cta?.trim()) {
|
|
32667
|
+
const style = accent ? ` style="background:${escapeHtml2(accent)}"` : "";
|
|
32668
|
+
cardBits.push(`<div class="endcard-cta"${style}>${escapeHtml2(cta.trim())}</div>`);
|
|
32669
|
+
}
|
|
32670
|
+
parts.push(
|
|
32671
|
+
`<div class="clip endcard" data-start="${cardAtS}" data-dur="${cardDurationS}" data-role="cta">`,
|
|
32672
|
+
...cardBits.map((b) => ` ${b}`),
|
|
32673
|
+
"</div>"
|
|
32674
|
+
);
|
|
32675
|
+
return parts.join("\n");
|
|
32676
|
+
}
|
|
32677
|
+
function adBrandOverlayCss() {
|
|
32678
|
+
return [
|
|
32679
|
+
" /* --- brand layer (baker canvas scaffold-ad) --- */",
|
|
32680
|
+
" /* Positioned by this block alone. The composition that actually renders an ad",
|
|
32681
|
+
" is the caption track, which has no .pos-* helpers \u2014 relying on them put the",
|
|
32682
|
+
" brand layer in a file nothing opens and shipped an empty green end plate. */",
|
|
32683
|
+
" .brand-mark { position: absolute; top: 90px; left: 56px; width: 220px; height: auto;",
|
|
32684
|
+
" opacity: 0.92; filter: drop-shadow(0 2px 8px rgba(0,0,0,0.45)); }",
|
|
32685
|
+
" .endcard { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);",
|
|
32686
|
+
" display: flex; flex-direction: column; align-items: center; gap: 56px;",
|
|
32687
|
+
" width: 100%; max-width: 1080px; padding: 0 60px; text-align: center; }",
|
|
32688
|
+
" .endcard-logo { width: 620px; max-width: 78%; height: auto; }",
|
|
32689
|
+
" /* Capped and wrapping, not sized to its content: measured in a real browser,",
|
|
32690
|
+
" `GET YOUR FREE QUOTE` renders 948px wide against a 1080px canvas, so the next",
|
|
32691
|
+
" slightly longer call to action runs off the frame. Two lines beat an edge. */",
|
|
32692
|
+
" /* Declares its own family: rendered without one it inherited the document",
|
|
32693
|
+
" default and the call to action came out in Times, beside sans-serif captions. */",
|
|
32694
|
+
" .endcard-cta { font-family: 'BrandFont', 'Arial Black', 'Helvetica Neue', Arial, sans-serif;",
|
|
32695
|
+
" font-size: 64px; font-weight: 900; text-transform: uppercase;",
|
|
32696
|
+
" letter-spacing: 0.01em; color: #fff; padding: 28px 58px; border-radius: 56px;",
|
|
32697
|
+
" max-width: 900px; line-height: 1.15; background: rgba(0,0,0,0.82); }"
|
|
32698
|
+
].join("\n");
|
|
32699
|
+
}
|
|
32700
|
+
function injectAdBrandOverlay(indexHtml, overlayHtml) {
|
|
32701
|
+
if (!overlayHtml.trim()) return indexHtml;
|
|
32702
|
+
const withCss = indexHtml.replace(" </style>", `${adBrandOverlayCss()}
|
|
32703
|
+
</style>`);
|
|
32704
|
+
if (withCss.includes("<!--OVERLAYS-->")) return withCss.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
32705
|
+
const rootClose = withCss.lastIndexOf("</div>");
|
|
32706
|
+
if (rootClose === -1) return withCss;
|
|
32707
|
+
return `${withCss.slice(0, rootClose)}${overlayHtml}
|
|
32708
|
+
${withCss.slice(rootClose)}`;
|
|
32709
|
+
}
|
|
32710
|
+
|
|
32313
32711
|
// src/commands/canvas/composition-path.ts
|
|
32314
32712
|
import { existsSync as existsSync4 } from "fs";
|
|
32315
|
-
import
|
|
32713
|
+
import path22 from "path";
|
|
32316
32714
|
function resolveShippedCanvasDir(name, startDir, exists = existsSync4, maxDepth = 8) {
|
|
32317
|
-
const rel =
|
|
32715
|
+
const rel = path22.join("canvas", name);
|
|
32318
32716
|
let dir = startDir;
|
|
32319
32717
|
for (let i = 0; i < maxDepth; i++) {
|
|
32320
|
-
const candidate =
|
|
32321
|
-
if (exists(
|
|
32322
|
-
const parent =
|
|
32718
|
+
const candidate = path22.join(dir, rel);
|
|
32719
|
+
if (exists(path22.join(candidate, "meta.json"))) return candidate;
|
|
32720
|
+
const parent = path22.dirname(dir);
|
|
32323
32721
|
if (parent === dir) break;
|
|
32324
32722
|
dir = parent;
|
|
32325
32723
|
}
|
|
32326
|
-
return
|
|
32724
|
+
return path22.resolve(startDir, "../../../", rel);
|
|
32327
32725
|
}
|
|
32328
32726
|
|
|
32329
32727
|
// src/commands/canvas/scaffold-ad.ts
|
|
@@ -32369,7 +32767,7 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
32369
32767
|
const specPath = args.spec;
|
|
32370
32768
|
let parsed;
|
|
32371
32769
|
try {
|
|
32372
|
-
parsed = JSON.parse(await
|
|
32770
|
+
parsed = JSON.parse(await readFile15(specPath, "utf-8"));
|
|
32373
32771
|
} catch (e) {
|
|
32374
32772
|
writeJson({
|
|
32375
32773
|
ok: false,
|
|
@@ -32396,29 +32794,60 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
32396
32794
|
return;
|
|
32397
32795
|
}
|
|
32398
32796
|
const blueprint = adSpecToBlueprint(spec.data);
|
|
32399
|
-
const slug = args.slug ??
|
|
32400
|
-
const outPath = args.out ?? (args.slug ?
|
|
32401
|
-
const outDir =
|
|
32797
|
+
const slug = args.slug ?? path23.basename(specPath).replace(/\.[^.]+$/, "");
|
|
32798
|
+
const outPath = args.out ?? (args.slug ? path23.join("src/creatives", slug, `${slug}.canvas.json`) : path23.join(path23.dirname(specPath), `${slug}.canvas.json`));
|
|
32799
|
+
const outDir = path23.dirname(outPath);
|
|
32402
32800
|
await mkdir7(outDir, { recursive: true });
|
|
32403
|
-
const compositionDest =
|
|
32404
|
-
const captionsDest =
|
|
32801
|
+
const compositionDest = path23.join(outDir, "video-overlay-composition");
|
|
32802
|
+
const captionsDest = path23.join(outDir, "tiktok-captions-composition");
|
|
32405
32803
|
await cp(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
32406
32804
|
await cp(SHIPPED_CAPTIONS_DIR, captionsDest, { recursive: true });
|
|
32407
|
-
const blueprintPath =
|
|
32408
|
-
const blueprintStylePath =
|
|
32805
|
+
const blueprintPath = path23.join(outDir, "prompt.json");
|
|
32806
|
+
const blueprintStylePath = path23.join(outDir, "prompt.style.json");
|
|
32409
32807
|
await writeSceneFiles(outDir, blueprint);
|
|
32410
32808
|
await writeFile9(blueprintStylePath, renderStyleProjectionFromValue(blueprint), "utf8");
|
|
32809
|
+
const logoPath = spec.data.brand?.logo?.trim();
|
|
32411
32810
|
const opts = {
|
|
32412
32811
|
imageModel: AD_IMAGE_MODEL,
|
|
32413
32812
|
videoModel: DEFAULT_VIDEO_GENERATE_MODEL,
|
|
32414
|
-
overlayCompositionPath:
|
|
32415
|
-
captionsCompositionPath:
|
|
32416
|
-
blueprintPath:
|
|
32417
|
-
blueprintStylePath:
|
|
32813
|
+
overlayCompositionPath: path23.relative(outDir, compositionDest),
|
|
32814
|
+
captionsCompositionPath: path23.relative(outDir, captionsDest),
|
|
32815
|
+
blueprintPath: path23.relative(outDir, blueprintPath),
|
|
32816
|
+
blueprintStylePath: path23.relative(outDir, blueprintStylePath),
|
|
32418
32817
|
aspect: spec.data.format.aspect_ratio,
|
|
32419
|
-
resolution: spec.data.format.resolution
|
|
32818
|
+
resolution: spec.data.format.resolution,
|
|
32819
|
+
// A beat is one clause and one card, so the cap has to clear a whole clause.
|
|
32820
|
+
// The composition's default of 3 is TikTok karaoke styling; applied to an ad
|
|
32821
|
+
// it overrode the clause rule on every line and rendered "IS STANDARD. WE".
|
|
32822
|
+
// 8 is the composition's own maximum and clears every beat measured here.
|
|
32823
|
+
captionWordsPerGroup: 8
|
|
32420
32824
|
};
|
|
32421
|
-
const canvas = scaffoldVideoCanvas(blueprint,
|
|
32825
|
+
const canvas = scaffoldVideoCanvas(blueprint, adSpecCastElements(spec.data), opts);
|
|
32826
|
+
const renderNode3 = canvas.nodes.find((n) => n.type === "hyperframe_render");
|
|
32827
|
+
const renderedDir = renderNode3 ? path23.join(outDir, String(renderNode3.params?.composition ?? "")) : null;
|
|
32828
|
+
let staged = null;
|
|
32829
|
+
if (logoPath && renderedDir) {
|
|
32830
|
+
const ext = path23.extname(logoPath) || ".png";
|
|
32831
|
+
try {
|
|
32832
|
+
await copyFile(logoPath, path23.join(renderedDir, `brand-mark${ext}`));
|
|
32833
|
+
staged = { file: `brand-mark${ext}`, alt: spec.data.brand?.name ?? "brand" };
|
|
32834
|
+
} catch {
|
|
32835
|
+
}
|
|
32836
|
+
}
|
|
32837
|
+
const closing = blueprint.scenes[blueprint.scenes.length - 1];
|
|
32838
|
+
const overlayHtml = endCardWanted(spec.data) && closing && renderedDir ? buildAdBrandOverlay({
|
|
32839
|
+
logo: staged,
|
|
32840
|
+
cta: endCardCta(spec.data),
|
|
32841
|
+
cardAtS: closing.start_s,
|
|
32842
|
+
cardDurationS: closing.duration_s,
|
|
32843
|
+
totalS: blueprint.estimated_duration_s,
|
|
32844
|
+
accent: firstHex(spec.data.brand?.palette)
|
|
32845
|
+
}) : "";
|
|
32846
|
+
if (overlayHtml && renderedDir) {
|
|
32847
|
+
const indexPath = path23.join(renderedDir, "index.html");
|
|
32848
|
+
const html = await readFile15(indexPath, "utf-8");
|
|
32849
|
+
await writeFile9(indexPath, injectAdBrandOverlay(html, overlayHtml), "utf-8");
|
|
32850
|
+
}
|
|
32422
32851
|
await writeFile9(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
32423
32852
|
`, "utf-8");
|
|
32424
32853
|
writeJson({
|
|
@@ -32427,18 +32856,28 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
32427
32856
|
hints: [
|
|
32428
32857
|
`Wrote ${spec.data.beats.length} beats to ${outPath}. Run it with \`baker canvas run ${outPath}\`.`,
|
|
32429
32858
|
`These beats run about ${blueprint.estimated_duration_s}s. If the ask was longer, add beats \u2014 the length is the sum of the lines, so a 25s ad needs roughly 25s of script and cannot be stretched by holding shots.`,
|
|
32859
|
+
...logoPath && !staged ? [`Could not read the logo at \`${logoPath}\` \u2014 the ad has no mark on screen. Give the path as it appears in src/brand/BRAND.md, relative to the workspace root.`] : [],
|
|
32860
|
+
...staged ? ["The brand mark is drawn by the composition \u2014 top-left throughout, and large on the closing card. It is never generated as a picture, so it cannot come back garbled."] : [],
|
|
32861
|
+
...endCardWanted(spec.data) ? ["The last beat closes on a flat brand plate with the call to action over it, instead of one more photograph."] : [],
|
|
32430
32862
|
"The caption cards are your `say` lines verbatim \u2014 read them back before running; that is the copy the viewer sees.",
|
|
32431
32863
|
"Every clip renders with nobody speaking unless a beat set `on_camera`. If a beat needs a presenter talking, set it there rather than describing it in `show`.",
|
|
32432
|
-
|
|
32864
|
+
...spec.data.brand?.palette?.length && spec.data.brand?.logo ? [] : [
|
|
32865
|
+
// Keyed on what is MISSING, not on whether `brand` exists at all. The
|
|
32866
|
+
// previous wording fired only when the spec carried no brand — so a spec
|
|
32867
|
+
// holding `brand.name` and nothing else sailed through and rendered an
|
|
32868
|
+
// ad with no palette and no mark anywhere in 28 seconds.
|
|
32869
|
+
`This spec has no ${!spec.data.brand?.palette?.length ? "`brand.palette`" : ""}${!spec.data.brand?.palette?.length && !spec.data.brand?.logo ? " and no " : ""}${!spec.data.brand?.logo ? "`brand.logo`" : ""}. Read src/brand/BRAND.md and re-run with them \u2014 a name alone leaves the ad in someone else's clothes.`
|
|
32870
|
+
],
|
|
32871
|
+
"`brand.palette` dresses the caption type and the closing button; `brand.logo` is the repo path to the mark, and it is DRAWN by the composition \u2014 top-left throughout and large on the closing card \u2014 never generated as a picture, so it cannot come back garbled. Give it whenever the client has one."
|
|
32433
32872
|
]
|
|
32434
32873
|
});
|
|
32435
32874
|
}
|
|
32436
32875
|
});
|
|
32437
32876
|
|
|
32438
32877
|
// src/commands/canvas/scaffold-video.ts
|
|
32439
|
-
import { access as access2, cp as cp2, mkdir as mkdir8, readFile as
|
|
32440
|
-
import { tmpdir as
|
|
32441
|
-
import
|
|
32878
|
+
import { access as access2, cp as cp2, mkdir as mkdir8, readFile as readFile18, rm as rm7, writeFile as writeFile10 } from "fs/promises";
|
|
32879
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
32880
|
+
import path25 from "path";
|
|
32442
32881
|
import { defineCommand as defineCommand105 } from "citty";
|
|
32443
32882
|
|
|
32444
32883
|
// src/engine/scaffold/lib/model-router.ts
|
|
@@ -32477,12 +32916,12 @@ function routeVideoModel(input) {
|
|
|
32477
32916
|
}
|
|
32478
32917
|
|
|
32479
32918
|
// src/engine/nodes/local/lib/sceneDetect.ts
|
|
32480
|
-
import { execFile as
|
|
32481
|
-
import { mkdtemp, readdir as readdir7, readFile as
|
|
32482
|
-
import { tmpdir } from "os";
|
|
32919
|
+
import { execFile as execFile3 } from "child_process";
|
|
32920
|
+
import { mkdtemp as mkdtemp2, readdir as readdir7, readFile as readFile16, rm as rm6 } from "fs/promises";
|
|
32921
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
32483
32922
|
import { join as join2 } from "path";
|
|
32484
|
-
import { promisify as
|
|
32485
|
-
var
|
|
32923
|
+
import { promisify as promisify3 } from "util";
|
|
32924
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
32486
32925
|
var PYSCENEDETECT_THRESHOLD = 18;
|
|
32487
32926
|
var PYSCENEDETECT_MIN_SCENE_LEN_S = 0.25;
|
|
32488
32927
|
var PYSCENEDETECT_RECHECK_THRESHOLD = 27;
|
|
@@ -32533,9 +32972,9 @@ function parsePySceneDetectCsvCuts(csv) {
|
|
|
32533
32972
|
return [...new Set(cuts)].sort((a, b) => a - b);
|
|
32534
32973
|
}
|
|
32535
32974
|
async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs) {
|
|
32536
|
-
const outDir = await
|
|
32975
|
+
const outDir = await mkdtemp2(join2(tmpdir2(), "baker-scenedetect-"));
|
|
32537
32976
|
try {
|
|
32538
|
-
await
|
|
32977
|
+
await execFileAsync3(
|
|
32539
32978
|
"scenedetect",
|
|
32540
32979
|
[
|
|
32541
32980
|
"--input",
|
|
@@ -32554,9 +32993,9 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
|
|
|
32554
32993
|
);
|
|
32555
32994
|
const csvName = (await readdir7(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
|
|
32556
32995
|
if (!csvName) return [];
|
|
32557
|
-
return parsePySceneDetectCsvCuts(await
|
|
32996
|
+
return parsePySceneDetectCsvCuts(await readFile16(join2(outDir, csvName), "utf-8"));
|
|
32558
32997
|
} finally {
|
|
32559
|
-
await
|
|
32998
|
+
await rm6(outDir, { recursive: true, force: true });
|
|
32560
32999
|
}
|
|
32561
33000
|
}
|
|
32562
33001
|
async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
@@ -32578,8 +33017,8 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
|
32578
33017
|
}
|
|
32579
33018
|
|
|
32580
33019
|
// src/commands/canvas/gitignore.ts
|
|
32581
|
-
import { appendFile, readFile as
|
|
32582
|
-
import
|
|
33020
|
+
import { appendFile, readFile as readFile17 } from "fs/promises";
|
|
33021
|
+
import path24 from "path";
|
|
32583
33022
|
function missingGitignoreEntries(existing, entries) {
|
|
32584
33023
|
const present2 = new Set(
|
|
32585
33024
|
existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
@@ -32587,10 +33026,10 @@ function missingGitignoreEntries(existing, entries) {
|
|
|
32587
33026
|
return entries.filter((e) => !present2.has(e.trim().replace(/\/+$/, "")));
|
|
32588
33027
|
}
|
|
32589
33028
|
async function ensureGitignore(dir, entries) {
|
|
32590
|
-
const file =
|
|
33029
|
+
const file = path24.join(dir, ".gitignore");
|
|
32591
33030
|
let existing;
|
|
32592
33031
|
try {
|
|
32593
|
-
existing = await
|
|
33032
|
+
existing = await readFile17(file, "utf8");
|
|
32594
33033
|
} catch {
|
|
32595
33034
|
return;
|
|
32596
33035
|
}
|
|
@@ -32629,7 +33068,7 @@ ONE PERSON, MULTIPLE LOOKS: if a single individual plays MULTIPLE personas or wa
|
|
|
32629
33068
|
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.`;
|
|
32630
33069
|
async function loadAssetText2(ref, label) {
|
|
32631
33070
|
const r = ref;
|
|
32632
|
-
if (typeof r?.path === "string") return
|
|
33071
|
+
if (typeof r?.path === "string") return readFile18(r.path, "utf8");
|
|
32633
33072
|
if (typeof r?.url === "string") {
|
|
32634
33073
|
const res = await fetch(r.url);
|
|
32635
33074
|
if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
|
|
@@ -32648,7 +33087,7 @@ async function loadTranscriptBestEffort(ref) {
|
|
|
32648
33087
|
async function stageCaptions(outDir, transcript) {
|
|
32649
33088
|
const text2 = transcript?.trim();
|
|
32650
33089
|
if (!text2 || text2 === "[]") return {};
|
|
32651
|
-
const compositionPath =
|
|
33090
|
+
const compositionPath = path25.join(outDir, "tiktok-captions-composition");
|
|
32652
33091
|
await cp2(SHIPPED_CAPTIONS_DIR2, compositionPath, { recursive: true });
|
|
32653
33092
|
return { compositionPath };
|
|
32654
33093
|
}
|
|
@@ -32666,11 +33105,11 @@ function patchCompositionHtml(html, dims) {
|
|
|
32666
33105
|
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`);
|
|
32667
33106
|
}
|
|
32668
33107
|
async function stampCompositionDims(compositionDir, dims) {
|
|
32669
|
-
const metaPath =
|
|
32670
|
-
const rawMeta = await
|
|
33108
|
+
const metaPath = path25.join(compositionDir, "meta.json");
|
|
33109
|
+
const rawMeta = await readFile18(metaPath, "utf8");
|
|
32671
33110
|
await writeFile10(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
|
|
32672
|
-
const htmlPath =
|
|
32673
|
-
const rawHtml = await
|
|
33111
|
+
const htmlPath = path25.join(compositionDir, "index.html");
|
|
33112
|
+
const rawHtml = await readFile18(htmlPath, "utf8");
|
|
32674
33113
|
await writeFile10(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
|
|
32675
33114
|
}
|
|
32676
33115
|
function parseElements2(raw) {
|
|
@@ -32718,7 +33157,7 @@ var VIDEO_EXT_BY_MIME = {
|
|
|
32718
33157
|
"video/x-matroska": ".mkv"
|
|
32719
33158
|
};
|
|
32720
33159
|
function referenceVideoExt(url, contentType) {
|
|
32721
|
-
const fromPath =
|
|
33160
|
+
const fromPath = path25.extname(new URL(url).pathname).toLowerCase();
|
|
32722
33161
|
if (fromPath && fromPath.length <= 5) return fromPath;
|
|
32723
33162
|
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase();
|
|
32724
33163
|
return mime && VIDEO_EXT_BY_MIME[mime] || ".mp4";
|
|
@@ -32744,7 +33183,7 @@ function videoDefinitionDescription(blueprint) {
|
|
|
32744
33183
|
return typeof product === "string" && product.trim() ? product.trim() : void 0;
|
|
32745
33184
|
}
|
|
32746
33185
|
async function materializeReferenceVideo(fileArg2) {
|
|
32747
|
-
if (!/^https?:\/\//i.test(fileArg2)) return
|
|
33186
|
+
if (!/^https?:\/\//i.test(fileArg2)) return path25.resolve(fileArg2);
|
|
32748
33187
|
let bytes;
|
|
32749
33188
|
let contentType;
|
|
32750
33189
|
try {
|
|
@@ -32756,8 +33195,8 @@ async function materializeReferenceVideo(fileArg2) {
|
|
|
32756
33195
|
throw new Error(`failed to download reference video: ${e instanceof Error ? e.message : String(e)}`);
|
|
32757
33196
|
}
|
|
32758
33197
|
if (bytes.length === 0) throw new Error("reference video download was empty");
|
|
32759
|
-
const dest =
|
|
32760
|
-
|
|
33198
|
+
const dest = path25.join(
|
|
33199
|
+
tmpdir3(),
|
|
32761
33200
|
`baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg2, contentType)}`
|
|
32762
33201
|
);
|
|
32763
33202
|
await writeFile10(dest, bytes);
|
|
@@ -32976,11 +33415,11 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
32976
33415
|
} catch (e) {
|
|
32977
33416
|
return fail4("download", e instanceof Error ? e.message : String(e));
|
|
32978
33417
|
}
|
|
32979
|
-
const base =
|
|
32980
|
-
const outPath = args.out ?
|
|
32981
|
-
const outDir =
|
|
32982
|
-
const blueprintPath =
|
|
32983
|
-
const blueprintStylePath =
|
|
33418
|
+
const base = path25.basename(videoPath, path25.extname(videoPath));
|
|
33419
|
+
const outPath = args.out ? path25.resolve(String(args.out)) : slug ? path25.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`) : path25.join(path25.dirname(videoPath), `${base}.video.canvas.json`);
|
|
33420
|
+
const outDir = path25.dirname(outPath);
|
|
33421
|
+
const blueprintPath = path25.join(outDir, "prompt.json");
|
|
33422
|
+
const blueprintStylePath = path25.join(outDir, "prompt.style.json");
|
|
32984
33423
|
const frames = args.frames === "reuse" ? "reuse" : "generate";
|
|
32985
33424
|
const maxScenes = args["max-scenes"] ? Number(args["max-scenes"]) : void 0;
|
|
32986
33425
|
if (Number.isFinite(maxScenes)) {
|
|
@@ -33025,12 +33464,12 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33025
33464
|
`
|
|
33026
33465
|
);
|
|
33027
33466
|
}
|
|
33028
|
-
const compositionDest =
|
|
33467
|
+
const compositionDest = path25.join(outDir, "video-overlay-composition");
|
|
33029
33468
|
await cp2(SHIPPED_COMPOSITION_DIR2, compositionDest, { recursive: true });
|
|
33030
33469
|
await stampCompositionDims(compositionDest, outDims);
|
|
33031
|
-
const indexPath =
|
|
33470
|
+
const indexPath = path25.join(compositionDest, "index.html");
|
|
33032
33471
|
const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript) });
|
|
33033
|
-
const indexHtml = await
|
|
33472
|
+
const indexHtml = await readFile18(indexPath, "utf8");
|
|
33034
33473
|
const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
33035
33474
|
if (injected === indexHtml && overlayHtml.trim()) {
|
|
33036
33475
|
fail4(
|
|
@@ -33044,10 +33483,10 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33044
33483
|
const opts = {
|
|
33045
33484
|
imageModel,
|
|
33046
33485
|
videoModel,
|
|
33047
|
-
overlayCompositionPath:
|
|
33048
|
-
captionsCompositionPath: captions.compositionPath ?
|
|
33049
|
-
blueprintPath:
|
|
33050
|
-
blueprintStylePath:
|
|
33486
|
+
overlayCompositionPath: path25.relative(outDir, compositionDest),
|
|
33487
|
+
captionsCompositionPath: captions.compositionPath ? path25.relative(outDir, captions.compositionPath) : void 0,
|
|
33488
|
+
blueprintPath: path25.relative(outDir, blueprintPath),
|
|
33489
|
+
blueprintStylePath: path25.relative(outDir, blueprintStylePath),
|
|
33051
33490
|
frames,
|
|
33052
33491
|
ambient: Boolean(args.ambient),
|
|
33053
33492
|
seamDedup: resolveSeamDedup(args["seam-dedup"]),
|
|
@@ -33063,7 +33502,7 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33063
33502
|
return fail4("scaffold", e instanceof Error ? e.message : String(e));
|
|
33064
33503
|
}
|
|
33065
33504
|
if (captions.compositionPath && !canvas.nodes.some((n) => n.id === "captions")) {
|
|
33066
|
-
await
|
|
33505
|
+
await rm7(captions.compositionPath, { recursive: true, force: true });
|
|
33067
33506
|
}
|
|
33068
33507
|
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(canvas, outDir), defaultRegistry());
|
|
33069
33508
|
if (!validation.ok) {
|
|
@@ -33075,7 +33514,7 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33075
33514
|
await writeFile10(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
33076
33515
|
`, "utf8");
|
|
33077
33516
|
await writeFile10(
|
|
33078
|
-
|
|
33517
|
+
path25.join(outDir, REBUILD_FILE),
|
|
33079
33518
|
`${JSON.stringify({ elements, opts }, null, 2)}
|
|
33080
33519
|
`,
|
|
33081
33520
|
"utf8"
|
|
@@ -33100,7 +33539,7 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33100
33539
|
await ensureGitignore(process.cwd(), ["canvas/", ".context/"]);
|
|
33101
33540
|
const sourceRef = videoSourceReference(blueprint, fileArg2);
|
|
33102
33541
|
if (slug) {
|
|
33103
|
-
const definitionPath =
|
|
33542
|
+
const definitionPath = path25.join(outDir, "_definition.md");
|
|
33104
33543
|
if (!await fileExists2(definitionPath)) {
|
|
33105
33544
|
await writeFile10(
|
|
33106
33545
|
definitionPath,
|
|
@@ -33155,7 +33594,7 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33155
33594
|
graph: canvas.metadata?.video?.graph_stats
|
|
33156
33595
|
},
|
|
33157
33596
|
checklist: {
|
|
33158
|
-
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 \`${
|
|
33597
|
+
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 \`${path25.basename(blueprintPath)}\`. \`baker canvas validate\`/\`run\` re-assemble the blueprint and re-flow every edited scene back into the render (and regenerate ${path25.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 ${path25.basename(blueprintStylePath)}; both are regenerated.`,
|
|
33159
33598
|
recurring_elements_to_supply: report.elements,
|
|
33160
33599
|
voices_to_confirm: report.dialogue.map((d) => ({
|
|
33161
33600
|
scene: d.scene,
|
|
@@ -33192,8 +33631,8 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33192
33631
|
});
|
|
33193
33632
|
|
|
33194
33633
|
// src/commands/canvas/set-prompt.ts
|
|
33195
|
-
import { readFile as
|
|
33196
|
-
import
|
|
33634
|
+
import { readFile as readFile19, writeFile as writeFile11 } from "fs/promises";
|
|
33635
|
+
import path26 from "path";
|
|
33197
33636
|
import { defineCommand as defineCommand106 } from "citty";
|
|
33198
33637
|
function setNodePrompt(canvas, nodeId, text2) {
|
|
33199
33638
|
const nodes = canvas?.nodes;
|
|
@@ -33221,17 +33660,17 @@ var setPromptCommand = defineCommand106({
|
|
|
33221
33660
|
"text-file": { type: "string", description: "Read the new prompt from a UTF-8 file (preserves accents/newlines)" }
|
|
33222
33661
|
},
|
|
33223
33662
|
async run({ args }) {
|
|
33224
|
-
const filePath =
|
|
33663
|
+
const filePath = path26.resolve(String(args.file));
|
|
33225
33664
|
let canvas;
|
|
33226
33665
|
try {
|
|
33227
|
-
canvas = JSON.parse(await
|
|
33666
|
+
canvas = JSON.parse(await readFile19(filePath, "utf8"));
|
|
33228
33667
|
} catch (e) {
|
|
33229
33668
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "parse", message: String(e) } }, null, 2)}
|
|
33230
33669
|
`);
|
|
33231
33670
|
process.exit(2);
|
|
33232
33671
|
}
|
|
33233
33672
|
let text2;
|
|
33234
|
-
if (args["text-file"]) text2 = await
|
|
33673
|
+
if (args["text-file"]) text2 = await readFile19(path26.resolve(String(args["text-file"])), "utf8");
|
|
33235
33674
|
else if (args.text !== void 0) text2 = String(args.text);
|
|
33236
33675
|
else {
|
|
33237
33676
|
process.stderr.write(
|
|
@@ -33252,7 +33691,7 @@ var setPromptCommand = defineCommand106({
|
|
|
33252
33691
|
process.exit(2);
|
|
33253
33692
|
return;
|
|
33254
33693
|
}
|
|
33255
|
-
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated,
|
|
33694
|
+
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated, path26.dirname(filePath)), defaultRegistry());
|
|
33256
33695
|
if (!validation.ok) {
|
|
33257
33696
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.issues } }, null, 2)}
|
|
33258
33697
|
`);
|
|
@@ -33267,8 +33706,8 @@ var setPromptCommand = defineCommand106({
|
|
|
33267
33706
|
});
|
|
33268
33707
|
|
|
33269
33708
|
// src/commands/canvas/validate.ts
|
|
33270
|
-
import { readFile as
|
|
33271
|
-
import
|
|
33709
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
33710
|
+
import path27 from "path";
|
|
33272
33711
|
import { defineCommand as defineCommand107 } from "citty";
|
|
33273
33712
|
var validateCommand = defineCommand107({
|
|
33274
33713
|
meta: {
|
|
@@ -33277,8 +33716,8 @@ var validateCommand = defineCommand107({
|
|
|
33277
33716
|
},
|
|
33278
33717
|
args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
|
|
33279
33718
|
async run({ args }) {
|
|
33280
|
-
const filePath =
|
|
33281
|
-
const raw = await
|
|
33719
|
+
const filePath = path27.resolve(String(args.file));
|
|
33720
|
+
const raw = await readFile20(filePath, "utf8");
|
|
33282
33721
|
let parsed;
|
|
33283
33722
|
try {
|
|
33284
33723
|
parsed = JSON.parse(raw);
|
|
@@ -33290,7 +33729,7 @@ var validateCommand = defineCommand107({
|
|
|
33290
33729
|
}
|
|
33291
33730
|
const healed = await healAbsoluteCanvasPaths(filePath, parsed);
|
|
33292
33731
|
parsed = healed.canvas;
|
|
33293
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
33732
|
+
parsed = resolveRelativeCanvasPaths(parsed, path27.dirname(filePath));
|
|
33294
33733
|
let styleProjection = "not_applicable";
|
|
33295
33734
|
try {
|
|
33296
33735
|
styleProjection = await syncStyleProjection(parsed, (line) => process.stderr.write(`${line}
|
|
@@ -33716,7 +34155,7 @@ import { defineCommand as defineCommand112 } from "citty";
|
|
|
33716
34155
|
import { defineCommand as defineCommand111 } from "citty";
|
|
33717
34156
|
|
|
33718
34157
|
// src/commands/images/api.ts
|
|
33719
|
-
import { readFile as
|
|
34158
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
33720
34159
|
import { basename, extname } from "path";
|
|
33721
34160
|
var imageProcessingTimeoutMs = 18e4;
|
|
33722
34161
|
var imageReadyPollIntervalMs = 2e3;
|
|
@@ -33730,7 +34169,7 @@ var mimeMap = {
|
|
|
33730
34169
|
".avif": "image/avif"
|
|
33731
34170
|
};
|
|
33732
34171
|
var defaultImageApiDeps = {
|
|
33733
|
-
readFile:
|
|
34172
|
+
readFile: readFile21,
|
|
33734
34173
|
post: apiPost,
|
|
33735
34174
|
get: apiGet,
|
|
33736
34175
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
|
|
@@ -33992,12 +34431,12 @@ function collectSideEffects(tree) {
|
|
|
33992
34431
|
);
|
|
33993
34432
|
}
|
|
33994
34433
|
function readFlowTree(slug) {
|
|
33995
|
-
const
|
|
33996
|
-
if (!existsSync5(
|
|
34434
|
+
const path40 = join3(flowsDir(), slug, "_data.json");
|
|
34435
|
+
if (!existsSync5(path40)) {
|
|
33997
34436
|
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
33998
34437
|
}
|
|
33999
34438
|
try {
|
|
34000
|
-
return JSON.parse(readFileSync9(
|
|
34439
|
+
return JSON.parse(readFileSync9(path40, "utf-8"));
|
|
34001
34440
|
} catch (error) {
|
|
34002
34441
|
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
34003
34442
|
}
|
|
@@ -34389,10 +34828,10 @@ function parseValueExpression(raw) {
|
|
|
34389
34828
|
return parts.map(parsePart);
|
|
34390
34829
|
}
|
|
34391
34830
|
function trackingFieldIds() {
|
|
34392
|
-
const
|
|
34393
|
-
if (!existsSync6(
|
|
34831
|
+
const path40 = join4(flowsDir(), "..", "tracking.ts");
|
|
34832
|
+
if (!existsSync6(path40)) return null;
|
|
34394
34833
|
try {
|
|
34395
|
-
const source = readFileSync10(
|
|
34834
|
+
const source = readFileSync10(path40, "utf-8");
|
|
34396
34835
|
const block2 = source.match(/TRACKING_FIELD_IDS\s*=\s*\[([\s\S]*?)\]\s*as const/)?.[1];
|
|
34397
34836
|
if (!block2) return null;
|
|
34398
34837
|
const ids = [...block2.matchAll(/"(tracking\.[a-z0-9_]+)"/g)].map((match) => match[1]);
|
|
@@ -34944,13 +35383,13 @@ function specsFromFile(parsed) {
|
|
|
34944
35383
|
return `${destField}${type}=${entry?.value ?? ""}`;
|
|
34945
35384
|
});
|
|
34946
35385
|
}
|
|
34947
|
-
function readSpecFile(
|
|
35386
|
+
function readSpecFile(path40) {
|
|
34948
35387
|
let raw;
|
|
34949
35388
|
try {
|
|
34950
|
-
raw =
|
|
35389
|
+
raw = path40 === "-" ? readFileSync11(0, "utf-8") : readFileSync11(path40, "utf-8");
|
|
34951
35390
|
} catch (error) {
|
|
34952
35391
|
refuse(
|
|
34953
|
-
`Could not read ${
|
|
35392
|
+
`Could not read ${path40 === "-" ? "the mapping from stdin" : `"${path40}"`}: ${error instanceof Error ? error.message : String(error)}`
|
|
34954
35393
|
);
|
|
34955
35394
|
}
|
|
34956
35395
|
let parsed;
|
|
@@ -34958,7 +35397,7 @@ function readSpecFile(path39) {
|
|
|
34958
35397
|
parsed = JSON.parse(raw);
|
|
34959
35398
|
} catch (error) {
|
|
34960
35399
|
refuse(
|
|
34961
|
-
`${
|
|
35400
|
+
`${path40 === "-" ? "stdin" : `"${path40}"`} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
34962
35401
|
'Expected { "map": { "<destField>": "<value>", \u2026 } }'
|
|
34963
35402
|
);
|
|
34964
35403
|
}
|
|
@@ -35212,18 +35651,18 @@ var ARRAY_FIELDS = [
|
|
|
35212
35651
|
"tagIds"
|
|
35213
35652
|
];
|
|
35214
35653
|
var ARRAY_OWNERS = ["", "body"];
|
|
35215
|
-
function dropUnsetOptionals(sideEffect,
|
|
35654
|
+
function dropUnsetOptionals(sideEffect, path40) {
|
|
35216
35655
|
return OPTIONAL_STRINGS.flatMap((key) => {
|
|
35217
35656
|
if (!(key in sideEffect) || sideEffect[key] !== null && sideEffect[key] !== "") return [];
|
|
35218
35657
|
delete sideEffect[key];
|
|
35219
|
-
return [{ path:
|
|
35658
|
+
return [{ path: path40, change: `dropped \`${key}\` (an optional string is absent, never null)` }];
|
|
35220
35659
|
});
|
|
35221
35660
|
}
|
|
35222
|
-
function fillNulledArrays(target, prefix,
|
|
35661
|
+
function fillNulledArrays(target, prefix, path40) {
|
|
35223
35662
|
return ARRAY_FIELDS.flatMap((key) => {
|
|
35224
35663
|
if (!(key in target) || target[key] !== null) return [];
|
|
35225
35664
|
target[key] = [];
|
|
35226
|
-
return [{ path:
|
|
35665
|
+
return [{ path: path40, change: `\`${prefix}${key}: null\` \u2192 \`[]\`` }];
|
|
35227
35666
|
});
|
|
35228
35667
|
}
|
|
35229
35668
|
function sideEffectsOf(node) {
|
|
@@ -35233,13 +35672,13 @@ function sideEffectsOf(node) {
|
|
|
35233
35672
|
);
|
|
35234
35673
|
}
|
|
35235
35674
|
function normalizeSideEffect(sideEffect, where) {
|
|
35236
|
-
const
|
|
35675
|
+
const path40 = `${where} \u2192 ${String(sideEffect.id ?? "side effect")}`;
|
|
35237
35676
|
const arrays = ARRAY_OWNERS.flatMap((owner) => {
|
|
35238
35677
|
const target = owner ? sideEffect[owner] : sideEffect;
|
|
35239
35678
|
if (!target || typeof target !== "object") return [];
|
|
35240
|
-
return fillNulledArrays(target, owner ? `${owner}.` : "",
|
|
35679
|
+
return fillNulledArrays(target, owner ? `${owner}.` : "", path40);
|
|
35241
35680
|
});
|
|
35242
|
-
return [...dropUnsetOptionals(sideEffect,
|
|
35681
|
+
return [...dropUnsetOptionals(sideEffect, path40), ...arrays];
|
|
35243
35682
|
}
|
|
35244
35683
|
function normalizeFlowTree(tree) {
|
|
35245
35684
|
const changes = [];
|
|
@@ -35777,10 +36216,10 @@ async function stageOps(ops) {
|
|
|
35777
36216
|
handleError2(err);
|
|
35778
36217
|
}
|
|
35779
36218
|
}
|
|
35780
|
-
async function draftAction2(
|
|
36219
|
+
async function draftAction2(path40, body, chat) {
|
|
35781
36220
|
const chatId = resolveChatId(chat);
|
|
35782
36221
|
try {
|
|
35783
|
-
const data = await apiPost(
|
|
36222
|
+
const data = await apiPost(path40, { chatId, ...body });
|
|
35784
36223
|
writeJsonEnvelope({ ok: true, data });
|
|
35785
36224
|
return data;
|
|
35786
36225
|
} catch (err) {
|
|
@@ -38177,7 +38616,7 @@ function cropSprite(input, region) {
|
|
|
38177
38616
|
|
|
38178
38617
|
// src/lib/image/io.ts
|
|
38179
38618
|
import { randomBytes } from "crypto";
|
|
38180
|
-
import { glob as fsGlob, readFile as
|
|
38619
|
+
import { glob as fsGlob, readFile as readFile22, rename, stat as stat4, writeFile as writeFile12 } from "fs/promises";
|
|
38181
38620
|
import { dirname as dirname2, extname as extname2, join as join5, resolve as resolve4 } from "path";
|
|
38182
38621
|
var REMOTE_RE = /^https?:\/\//i;
|
|
38183
38622
|
var GLOB_RE = /[*?[\]{}]/;
|
|
@@ -38210,11 +38649,11 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
38210
38649
|
const { buffer } = await fetchExternalBytes(pathOrUrl, { maxBytes: MAX_REMOTE_IMAGE_BYTES });
|
|
38211
38650
|
return buffer;
|
|
38212
38651
|
}
|
|
38213
|
-
return
|
|
38652
|
+
return readFile22(pathOrUrl);
|
|
38214
38653
|
}
|
|
38215
|
-
async function isDirectory(
|
|
38654
|
+
async function isDirectory(path40) {
|
|
38216
38655
|
try {
|
|
38217
|
-
const s = await stat4(
|
|
38656
|
+
const s = await stat4(path40);
|
|
38218
38657
|
return s.isDirectory();
|
|
38219
38658
|
} catch {
|
|
38220
38659
|
return false;
|
|
@@ -38516,13 +38955,13 @@ function resolveDownloadPath({ baseName, extension, out, outIsDirectory: outIsDi
|
|
|
38516
38955
|
}
|
|
38517
38956
|
function disambiguate(paths) {
|
|
38518
38957
|
const taken = /* @__PURE__ */ new Set();
|
|
38519
|
-
return paths.map((
|
|
38520
|
-
if (!taken.has(
|
|
38521
|
-
taken.add(
|
|
38522
|
-
return
|
|
38958
|
+
return paths.map((path40) => {
|
|
38959
|
+
if (!taken.has(path40)) {
|
|
38960
|
+
taken.add(path40);
|
|
38961
|
+
return path40;
|
|
38523
38962
|
}
|
|
38524
|
-
const ext = extname3(
|
|
38525
|
-
const stem =
|
|
38963
|
+
const ext = extname3(path40);
|
|
38964
|
+
const stem = path40.slice(0, path40.length - ext.length);
|
|
38526
38965
|
let n = 2;
|
|
38527
38966
|
while (taken.has(`${stem}-${n}${ext}`)) n += 1;
|
|
38528
38967
|
const unique = `${stem}-${n}${ext}`;
|
|
@@ -38649,10 +39088,10 @@ async function runDownloads(plan) {
|
|
|
38649
39088
|
const paths = disambiguate(fetched.map((item) => item.path));
|
|
38650
39089
|
const downloaded = [];
|
|
38651
39090
|
for (const [index, item] of fetched.entries()) {
|
|
38652
|
-
const
|
|
39091
|
+
const path40 = paths[index] ?? item.path;
|
|
38653
39092
|
try {
|
|
38654
|
-
await atomicWrite(
|
|
38655
|
-
downloaded.push({ input: item.input, output:
|
|
39093
|
+
await atomicWrite(path40, item.buffer);
|
|
39094
|
+
downloaded.push({ input: item.input, output: path40, bytes: item.buffer.length, contentType: item.contentType });
|
|
38656
39095
|
} catch (err) {
|
|
38657
39096
|
failed.push({ input: item.input, error: failureMessage(err, "Write failed") });
|
|
38658
39097
|
}
|
|
@@ -41612,7 +42051,7 @@ import { defineCommand as defineCommand167 } from "citty";
|
|
|
41612
42051
|
|
|
41613
42052
|
// src/commands/landing/critique.ts
|
|
41614
42053
|
import { readdir as readdir9, stat as stat6 } from "fs/promises";
|
|
41615
|
-
import
|
|
42054
|
+
import path30 from "path";
|
|
41616
42055
|
import { defineCommand as defineCommand157 } from "citty";
|
|
41617
42056
|
|
|
41618
42057
|
// src/engine/landing/lib/constants.ts
|
|
@@ -42580,13 +43019,13 @@ function describeCounts(findings) {
|
|
|
42580
43019
|
|
|
42581
43020
|
// src/commands/landing/snapshot.ts
|
|
42582
43021
|
import { mkdir as mkdir9, rename as rename2, writeFile as writeFile13 } from "fs/promises";
|
|
42583
|
-
import
|
|
43022
|
+
import path28 from "path";
|
|
42584
43023
|
var CRITIC_VERSION = "2";
|
|
42585
43024
|
function critiqueCacheDir(projectRoot) {
|
|
42586
|
-
return
|
|
43025
|
+
return path28.join(projectRoot, ".cache", "landing-critique");
|
|
42587
43026
|
}
|
|
42588
43027
|
function snapshotPath(projectRoot, slug) {
|
|
42589
|
-
return
|
|
43028
|
+
return path28.join(critiqueCacheDir(projectRoot), `${slug}.json`);
|
|
42590
43029
|
}
|
|
42591
43030
|
async function writeCritiqueSnapshot(projectRoot, snapshot) {
|
|
42592
43031
|
await mkdir9(critiqueCacheDir(projectRoot), { recursive: true });
|
|
@@ -42598,21 +43037,21 @@ async function writeCritiqueSnapshot(projectRoot, snapshot) {
|
|
|
42598
43037
|
}
|
|
42599
43038
|
|
|
42600
43039
|
// src/commands/landing/source-version.ts
|
|
42601
|
-
import { readdir as readdir8, readFile as
|
|
42602
|
-
import
|
|
43040
|
+
import { readdir as readdir8, readFile as readFile23, stat as stat5 } from "fs/promises";
|
|
43041
|
+
import path29 from "path";
|
|
42603
43042
|
async function landingSourceRelPaths(landingDir) {
|
|
42604
43043
|
const rel = [];
|
|
42605
|
-
if (await isFile(
|
|
42606
|
-
const componentsDir =
|
|
43044
|
+
if (await isFile(path29.join(landingDir, "index.astro"))) rel.push("index.astro");
|
|
43045
|
+
const componentsDir = path29.join(landingDir, "_components");
|
|
42607
43046
|
for (const abs of await walkAstro(componentsDir)) {
|
|
42608
|
-
rel.push(
|
|
43047
|
+
rel.push(path29.relative(landingDir, abs).split(path29.sep).join("/"));
|
|
42609
43048
|
}
|
|
42610
43049
|
return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
42611
43050
|
}
|
|
42612
43051
|
async function readLandingSources(landingDir) {
|
|
42613
43052
|
const rel = await landingSourceRelPaths(landingDir);
|
|
42614
43053
|
const out = [];
|
|
42615
|
-
for (const r of rel) out.push({ path: r, text: await
|
|
43054
|
+
for (const r of rel) out.push({ path: r, text: await readFile23(path29.join(landingDir, r), "utf8") });
|
|
42616
43055
|
return out;
|
|
42617
43056
|
}
|
|
42618
43057
|
async function computeLandingSourceSha(landingDir) {
|
|
@@ -42621,7 +43060,7 @@ async function computeLandingSourceSha(landingDir) {
|
|
|
42621
43060
|
for (const r of rel) {
|
|
42622
43061
|
let bytes;
|
|
42623
43062
|
try {
|
|
42624
|
-
bytes = await
|
|
43063
|
+
bytes = await readFile23(path29.join(landingDir, r));
|
|
42625
43064
|
} catch {
|
|
42626
43065
|
bytes = Buffer.alloc(0);
|
|
42627
43066
|
}
|
|
@@ -42645,7 +43084,7 @@ async function walkAstro(dir) {
|
|
|
42645
43084
|
}
|
|
42646
43085
|
const out = [];
|
|
42647
43086
|
for (const entry of entries) {
|
|
42648
|
-
const abs =
|
|
43087
|
+
const abs = path29.join(dir, entry.name);
|
|
42649
43088
|
if (entry.isDirectory()) out.push(...await walkAstro(abs));
|
|
42650
43089
|
else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
|
|
42651
43090
|
}
|
|
@@ -42706,7 +43145,7 @@ var critiqueCommand2 = defineCommand157({
|
|
|
42706
43145
|
{ availableSlugs: await listLandingSlugs(projectRoot) }
|
|
42707
43146
|
);
|
|
42708
43147
|
}
|
|
42709
|
-
if (!await isDir(
|
|
43148
|
+
if (!await isDir(path30.resolve(projectRoot, "src", "pages", slug))) {
|
|
42710
43149
|
fail5("NOT_FOUND", `No landing at src/pages/${slug}/`, {
|
|
42711
43150
|
availableSlugs: await listLandingSlugs(projectRoot)
|
|
42712
43151
|
});
|
|
@@ -42745,7 +43184,7 @@ var critiqueCommand2 = defineCommand157({
|
|
|
42745
43184
|
}
|
|
42746
43185
|
});
|
|
42747
43186
|
async function critiqueOne(projectRoot, slug, brand) {
|
|
42748
|
-
const landingDir =
|
|
43187
|
+
const landingDir = path30.resolve(projectRoot, "src", "pages", slug);
|
|
42749
43188
|
const [sources, sourceSha] = await Promise.all([readLandingSources(landingDir), computeLandingSourceSha(landingDir)]);
|
|
42750
43189
|
const report = critiqueLanding({ slug, sources, brand });
|
|
42751
43190
|
let snapshotFailed = false;
|
|
@@ -42765,7 +43204,7 @@ async function critiqueOne(projectRoot, slug, brand) {
|
|
|
42765
43204
|
}
|
|
42766
43205
|
async function listLandingSlugs(projectRoot) {
|
|
42767
43206
|
try {
|
|
42768
|
-
const entries = await readdir9(
|
|
43207
|
+
const entries = await readdir9(path30.join(projectRoot, "src", "pages"), { withFileTypes: true });
|
|
42769
43208
|
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
42770
43209
|
} catch {
|
|
42771
43210
|
return [];
|
|
@@ -42917,7 +43356,7 @@ var addCommand = defineCommand158({
|
|
|
42917
43356
|
|
|
42918
43357
|
// src/commands/landing/inspiration/code.ts
|
|
42919
43358
|
import { mkdir as mkdir10, writeFile as writeFile14 } from "fs/promises";
|
|
42920
|
-
import
|
|
43359
|
+
import path31 from "path";
|
|
42921
43360
|
import { defineCommand as defineCommand159 } from "citty";
|
|
42922
43361
|
registerSchema({
|
|
42923
43362
|
command: "landing.inspiration.code",
|
|
@@ -42940,9 +43379,9 @@ var codeCommand = defineCommand159({
|
|
|
42940
43379
|
try {
|
|
42941
43380
|
const id = args.id;
|
|
42942
43381
|
const data = await apiGet("/api/landing-inspiration/section-code", { id });
|
|
42943
|
-
const dir =
|
|
43382
|
+
const dir = path31.join(process.cwd(), ".baker", "inspiration", id);
|
|
42944
43383
|
await mkdir10(dir, { recursive: true });
|
|
42945
|
-
const file =
|
|
43384
|
+
const file = path31.join(dir, "section.html");
|
|
42946
43385
|
await writeFile14(file, data.html);
|
|
42947
43386
|
const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
|
|
42948
43387
|
const fidelity = fidelityHint(data.fidelity);
|
|
@@ -42952,7 +43391,7 @@ var codeCommand = defineCommand159({
|
|
|
42952
43391
|
ok: true,
|
|
42953
43392
|
data: {
|
|
42954
43393
|
id,
|
|
42955
|
-
file:
|
|
43394
|
+
file: path31.relative(process.cwd(), file),
|
|
42956
43395
|
bytes: data.html.length,
|
|
42957
43396
|
fidelity: data.fidelity,
|
|
42958
43397
|
reproduction_notes: data.reproductionNotes,
|
|
@@ -43384,7 +43823,7 @@ function classifyCaptureFailure(error) {
|
|
|
43384
43823
|
|
|
43385
43824
|
// src/engine/landing-library/run.ts
|
|
43386
43825
|
import { mkdir as mkdir11, writeFile as writeFile16 } from "fs/promises";
|
|
43387
|
-
import
|
|
43826
|
+
import path33 from "path";
|
|
43388
43827
|
|
|
43389
43828
|
// ../proxy/src/preflight.ts
|
|
43390
43829
|
import http from "http";
|
|
@@ -43614,7 +44053,7 @@ ${used.inheritedSeed}
|
|
|
43614
44053
|
<head>
|
|
43615
44054
|
<meta charset="utf-8">
|
|
43616
44055
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
43617
|
-
<title>Section from ${
|
|
44056
|
+
<title>Section from ${escapeHtml3(extracted.host)}</title>
|
|
43618
44057
|
<style>
|
|
43619
44058
|
${css}
|
|
43620
44059
|
</style>
|
|
@@ -43626,7 +44065,7 @@ ${wrapInLayoutContext(extracted.html, extracted.layoutContext)}
|
|
|
43626
44065
|
`;
|
|
43627
44066
|
return { html, css, assetUrls: extracted.assetUrls, stats: used.stats };
|
|
43628
44067
|
}
|
|
43629
|
-
function
|
|
44068
|
+
function escapeHtml3(value) {
|
|
43630
44069
|
return value.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c] ?? c);
|
|
43631
44070
|
}
|
|
43632
44071
|
var SECTION_ROOT_ATTRIBUTE = "data-baker-section-root";
|
|
@@ -44800,13 +45239,13 @@ async function renderBundleToPng(browser, html, viewportWidth, options = {}) {
|
|
|
44800
45239
|
|
|
44801
45240
|
// src/engine/landing-library/report.ts
|
|
44802
45241
|
import { writeFile as writeFile15 } from "fs/promises";
|
|
44803
|
-
import
|
|
45242
|
+
import path32 from "path";
|
|
44804
45243
|
async function writeCaptureReport(manifest, outDir) {
|
|
44805
|
-
const file =
|
|
45244
|
+
const file = path32.join(outDir, "report.html");
|
|
44806
45245
|
await writeFile15(file, renderReport(manifest));
|
|
44807
45246
|
return file;
|
|
44808
45247
|
}
|
|
44809
|
-
function
|
|
45248
|
+
function escapeHtml4(value) {
|
|
44810
45249
|
return value.replace(
|
|
44811
45250
|
/[&<>"']/g,
|
|
44812
45251
|
(character) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[character] ?? character
|
|
@@ -44831,18 +45270,18 @@ function renderSection3(section) {
|
|
|
44831
45270
|
const shot = (label, src, note) => {
|
|
44832
45271
|
if (!src) return `<figure class="shot empty"><figcaption>${label} \u2014 none</figcaption></figure>`;
|
|
44833
45272
|
return `<figure class="shot">
|
|
44834
|
-
<figcaption>${label}${note ? ` <span class="note">${
|
|
44835
|
-
<a href="${
|
|
45273
|
+
<figcaption>${label}${note ? ` <span class="note">${escapeHtml4(note)}</span>` : ""}</figcaption>
|
|
45274
|
+
<a href="${escapeHtml4(src)}" target="_blank" rel="noopener"><img src="${escapeHtml4(src)}" alt="" loading="lazy"></a>
|
|
44836
45275
|
</figure>`;
|
|
44837
45276
|
};
|
|
44838
45277
|
return `<section class="card">
|
|
44839
45278
|
<header>
|
|
44840
45279
|
<h2><span class="index">${String(section.index).padStart(2, "0")}</span> ${section.rect.width}\xD7${section.rect.height}</h2>
|
|
44841
45280
|
<span class="badge ${tone}">fidelity ${formatFidelity(section.fidelity)}</span>
|
|
44842
|
-
${section.fidelityNote ? `<span class="badge warn">${
|
|
44843
|
-
${section.motion.hasMotion ? `<span class="badge motion">${
|
|
45281
|
+
${section.fidelityNote ? `<span class="badge warn">${escapeHtml4(section.fidelityNote)}</span>` : ""}
|
|
45282
|
+
${section.motion.hasMotion ? `<span class="badge motion">${escapeHtml4(section.motion.summary)}</span>` : ""}
|
|
44844
45283
|
</header>
|
|
44845
|
-
<p class="preview">${
|
|
45284
|
+
<p class="preview">${escapeHtml4(section.textPreview.slice(0, 220)) || "<em>no text</em>"}</p>
|
|
44846
45285
|
<div class="shots">
|
|
44847
45286
|
${shot("Live", section.desktopShot)}
|
|
44848
45287
|
${shot("Reproduction", rendered, "rendered from the extracted bundle")}
|
|
@@ -44850,8 +45289,8 @@ function renderSection3(section) {
|
|
|
44850
45289
|
</div>
|
|
44851
45290
|
${section.motionFilmstrip ? `<div class="filmstrip">${shot("Motion \u2014 six frames, left to right", section.motionFilmstrip)}</div>` : ""}
|
|
44852
45291
|
<footer>
|
|
44853
|
-
<code>${
|
|
44854
|
-
${section.bundle ? `<a href="${
|
|
45292
|
+
<code>${escapeHtml4(section.selector)}</code>
|
|
45293
|
+
${section.bundle ? `<a href="${escapeHtml4(section.bundle)}" target="_blank" rel="noopener">open the standalone bundle \u2192</a>` : ""}
|
|
44855
45294
|
</footer>
|
|
44856
45295
|
</section>`;
|
|
44857
45296
|
}
|
|
@@ -44863,7 +45302,7 @@ function renderReport(manifest) {
|
|
|
44863
45302
|
<head>
|
|
44864
45303
|
<meta charset="utf-8">
|
|
44865
45304
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
44866
|
-
<title>Capture report \u2014 ${
|
|
45305
|
+
<title>Capture report \u2014 ${escapeHtml4(manifest.title || manifest.url)}</title>
|
|
44867
45306
|
<style>
|
|
44868
45307
|
:root { color-scheme: light dark; --line: #e5e7eb; --muted: #6b7280; --bg: #fafafa; --card: #fff; }
|
|
44869
45308
|
@media (prefers-color-scheme: dark) {
|
|
@@ -44905,8 +45344,8 @@ function renderReport(manifest) {
|
|
|
44905
45344
|
</style>
|
|
44906
45345
|
</head>
|
|
44907
45346
|
<body>
|
|
44908
|
-
<h1>${
|
|
44909
|
-
<p class="sub"><a href="${
|
|
45347
|
+
<h1>${escapeHtml4(manifest.title || "Capture report")}</h1>
|
|
45348
|
+
<p class="sub"><a href="${escapeHtml4(manifest.finalUrl)}" target="_blank" rel="noopener">${escapeHtml4(manifest.finalUrl)}</a> \xB7 captured ${escapeHtml4(manifest.capturedAt)}</p>
|
|
44910
45349
|
|
|
44911
45350
|
<div class="stats">
|
|
44912
45351
|
<div class="stat"><b>${manifest.sections.length}</b><span>sections</span></div>
|
|
@@ -44981,32 +45420,32 @@ async function reproducePage(args) {
|
|
|
44981
45420
|
const { browser, page, outDir, pageUrl, livePageShot } = args;
|
|
44982
45421
|
const built = await buildSectionBundle(page, "body", pageUrl).catch(() => null);
|
|
44983
45422
|
if (!built) return { bundle: null, fidelity: null };
|
|
44984
|
-
await writeFile16(
|
|
45423
|
+
await writeFile16(path33.join(outDir, "page.html"), built.html);
|
|
44985
45424
|
const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width, {
|
|
44986
45425
|
wholePage: true,
|
|
44987
45426
|
timeoutMs: 6e4
|
|
44988
45427
|
});
|
|
44989
45428
|
if (!rendered || !livePageShot) return { bundle: "page.html", fidelity: null };
|
|
44990
|
-
await writeFile16(
|
|
45429
|
+
await writeFile16(path33.join(outDir, "page-rendered.png"), rendered);
|
|
44991
45430
|
const { score, note } = await scoreFidelity(livePageShot, rendered);
|
|
44992
45431
|
return { bundle: "page.html", fidelity: score, ...note ? { fidelityNote: note } : {} };
|
|
44993
45432
|
}
|
|
44994
45433
|
async function captureOneSection(args) {
|
|
44995
45434
|
const { browser, page, candidate, sectionsDir, outDir, pageUrl, withCode } = args;
|
|
44996
|
-
const dir =
|
|
45435
|
+
const dir = path33.join(sectionsDir, String(candidate.index).padStart(2, "0"));
|
|
44997
45436
|
await mkdir11(dir, { recursive: true });
|
|
44998
45437
|
const desktop = await captureSection(page, candidate);
|
|
44999
|
-
if (desktop) await writeFile16(
|
|
45438
|
+
if (desktop) await writeFile16(path33.join(dir, "desktop.png"), desktop);
|
|
45000
45439
|
const visualHash = desktop ? await perceptualHash(desktop) : null;
|
|
45001
45440
|
const motion = await collectMotion(page, candidate.selector);
|
|
45002
45441
|
const built = withCode ? await buildSectionBundle(page, candidate.selector, pageUrl) : null;
|
|
45003
45442
|
let fidelity = null;
|
|
45004
45443
|
let fidelityNote;
|
|
45005
45444
|
if (built) {
|
|
45006
|
-
await writeFile16(
|
|
45445
|
+
await writeFile16(path33.join(dir, "section.html"), built.html);
|
|
45007
45446
|
const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width);
|
|
45008
45447
|
if (rendered && desktop) {
|
|
45009
|
-
await writeFile16(
|
|
45448
|
+
await writeFile16(path33.join(dir, "section-rendered.png"), rendered);
|
|
45010
45449
|
const result = await scoreFidelity(desktop, rendered);
|
|
45011
45450
|
fidelity = result.score;
|
|
45012
45451
|
fidelityNote = result.note;
|
|
@@ -45014,9 +45453,9 @@ async function captureOneSection(args) {
|
|
|
45014
45453
|
}
|
|
45015
45454
|
return {
|
|
45016
45455
|
...candidate,
|
|
45017
|
-
desktopShot: desktop ?
|
|
45456
|
+
desktopShot: desktop ? path33.relative(outDir, path33.join(dir, "desktop.png")) : null,
|
|
45018
45457
|
mobileShot: null,
|
|
45019
|
-
bundle: built ?
|
|
45458
|
+
bundle: built ? path33.relative(outDir, path33.join(dir, "section.html")) : null,
|
|
45020
45459
|
fidelity,
|
|
45021
45460
|
...fidelityNote ? { fidelityNote } : {},
|
|
45022
45461
|
...built ? { cssStats: built.stats } : {},
|
|
@@ -45035,9 +45474,9 @@ async function captureMobileShots(args) {
|
|
|
45035
45474
|
for (const section of sections) {
|
|
45036
45475
|
const shot = await captureSectionOnMobile(mobile.page, section);
|
|
45037
45476
|
if (!shot) continue;
|
|
45038
|
-
const file =
|
|
45477
|
+
const file = path33.join(sectionsDir, String(section.index).padStart(2, "0"), "mobile.png");
|
|
45039
45478
|
await writeFile16(file, shot);
|
|
45040
|
-
section.mobileShot =
|
|
45479
|
+
section.mobileShot = path33.relative(outDir, file);
|
|
45041
45480
|
}
|
|
45042
45481
|
} finally {
|
|
45043
45482
|
await mobile.context.close();
|
|
@@ -45052,10 +45491,10 @@ async function captureMotionTakes(args) {
|
|
|
45052
45491
|
const filmOne = async (section) => {
|
|
45053
45492
|
const take = await captureMotionTake(browser, pageUrl, section.selector).catch(() => null);
|
|
45054
45493
|
if (!take) return;
|
|
45055
|
-
const dir =
|
|
45056
|
-
const file =
|
|
45494
|
+
const dir = path33.join(sectionsDir, String(section.index).padStart(2, "0"));
|
|
45495
|
+
const file = path33.join(dir, "motion-filmstrip.png");
|
|
45057
45496
|
await writeFile16(file, take.filmstrip);
|
|
45058
|
-
section.motionFilmstrip =
|
|
45497
|
+
section.motionFilmstrip = path33.relative(outDir, file);
|
|
45059
45498
|
log(` [${section.index}] ${section.motion.summary}`);
|
|
45060
45499
|
};
|
|
45061
45500
|
const queue = [...moving];
|
|
@@ -45112,7 +45551,7 @@ async function captureAlternateViews(args) {
|
|
|
45112
45551
|
async function reproduceWholePage(args) {
|
|
45113
45552
|
const { browser, page, outDir, pageUrl, withCode, log } = args;
|
|
45114
45553
|
const fullPage = await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
|
|
45115
|
-
if (fullPage) await writeFile16(
|
|
45554
|
+
if (fullPage) await writeFile16(path33.join(outDir, "full-page.png"), fullPage);
|
|
45116
45555
|
if (!withCode) return { bundle: null, fidelity: null };
|
|
45117
45556
|
const reproduction = await reproducePage({ browser, page, outDir, pageUrl, livePageShot: fullPage });
|
|
45118
45557
|
log(`page reproduction: ${reproduction.fidelity === null ? "unavailable" : reproduction.fidelity.toFixed(2)}`);
|
|
@@ -45183,7 +45622,7 @@ async function openViaLadder(args) {
|
|
|
45183
45622
|
async function scrapeLanding(options) {
|
|
45184
45623
|
const timeoutMs = options.timeoutMs ?? 45e3;
|
|
45185
45624
|
const log = options.onProgress ?? (() => void 0);
|
|
45186
|
-
const sectionsDir =
|
|
45625
|
+
const sectionsDir = path33.join(options.outDir, "sections");
|
|
45187
45626
|
const nonPublic = refuseNonPublicUrl(options.url);
|
|
45188
45627
|
if (nonPublic) {
|
|
45189
45628
|
throw new BlockedPageError({
|
|
@@ -45245,7 +45684,7 @@ async function scrapeLanding(options) {
|
|
|
45245
45684
|
security: prepared.security,
|
|
45246
45685
|
captureTier: tier
|
|
45247
45686
|
};
|
|
45248
|
-
await writeFile16(
|
|
45687
|
+
await writeFile16(path33.join(options.outDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
45249
45688
|
`);
|
|
45250
45689
|
if (options.report !== false) {
|
|
45251
45690
|
const reportPath = await writeCaptureReport(manifest, options.outDir);
|
|
@@ -45260,28 +45699,28 @@ async function scrapeLanding(options) {
|
|
|
45260
45699
|
|
|
45261
45700
|
// src/commands/landing/inspiration/captureOut.ts
|
|
45262
45701
|
import { existsSync as existsSync9 } from "fs";
|
|
45263
|
-
import
|
|
45702
|
+
import path34 from "path";
|
|
45264
45703
|
var SCRATCH_DIR = ".baker";
|
|
45265
45704
|
function isWithin(parent, target) {
|
|
45266
|
-
const relative =
|
|
45267
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
45705
|
+
const relative = path34.relative(parent, target);
|
|
45706
|
+
return relative === "" || !relative.startsWith("..") && !path34.isAbsolute(relative);
|
|
45268
45707
|
}
|
|
45269
45708
|
function findRepoRoot(from) {
|
|
45270
|
-
let dir =
|
|
45709
|
+
let dir = path34.resolve(from);
|
|
45271
45710
|
for (; ; ) {
|
|
45272
|
-
if (existsSync9(
|
|
45273
|
-
const parent =
|
|
45711
|
+
if (existsSync9(path34.join(dir, ".git"))) return dir;
|
|
45712
|
+
const parent = path34.dirname(dir);
|
|
45274
45713
|
if (parent === dir) return null;
|
|
45275
45714
|
dir = parent;
|
|
45276
45715
|
}
|
|
45277
45716
|
}
|
|
45278
45717
|
function checkCaptureOut(out, options) {
|
|
45279
45718
|
const { cwd, repoRoot } = options;
|
|
45280
|
-
const resolved =
|
|
45719
|
+
const resolved = path34.resolve(cwd, out);
|
|
45281
45720
|
if (repoRoot === null || !isWithin(repoRoot, resolved)) return { ok: true };
|
|
45282
|
-
const scratch =
|
|
45721
|
+
const scratch = path34.join(repoRoot, SCRATCH_DIR);
|
|
45283
45722
|
if (isWithin(scratch, resolved)) return { ok: true };
|
|
45284
|
-
const suggestion =
|
|
45723
|
+
const suggestion = path34.posix.join(SCRATCH_DIR, "teardowns", path34.basename(resolved) || "capture");
|
|
45285
45724
|
return {
|
|
45286
45725
|
ok: false,
|
|
45287
45726
|
error: {
|
|
@@ -45455,12 +45894,12 @@ var scrapeCommand = defineCommand162({
|
|
|
45455
45894
|
});
|
|
45456
45895
|
|
|
45457
45896
|
// src/commands/landing/inspiration/search.ts
|
|
45458
|
-
import
|
|
45897
|
+
import path36 from "path";
|
|
45459
45898
|
import { defineCommand as defineCommand163 } from "citty";
|
|
45460
45899
|
|
|
45461
45900
|
// src/commands/landing/inspiration/shot.ts
|
|
45462
45901
|
import { mkdir as mkdir12, writeFile as writeFile17 } from "fs/promises";
|
|
45463
|
-
import
|
|
45902
|
+
import path35 from "path";
|
|
45464
45903
|
import sharp6 from "sharp";
|
|
45465
45904
|
var READABLE_SHOT = {
|
|
45466
45905
|
maxWidth: 1440,
|
|
@@ -45486,9 +45925,9 @@ async function downloadReadableShot(url, file) {
|
|
|
45486
45925
|
const response = await fetch(url);
|
|
45487
45926
|
if (!response.ok) return null;
|
|
45488
45927
|
const shot = await toReadableShot(Buffer.from(await response.arrayBuffer()));
|
|
45489
|
-
await mkdir12(
|
|
45928
|
+
await mkdir12(path35.dirname(file), { recursive: true });
|
|
45490
45929
|
await writeFile17(file, shot);
|
|
45491
|
-
return
|
|
45930
|
+
return path35.relative(process.cwd(), file);
|
|
45492
45931
|
} catch {
|
|
45493
45932
|
return null;
|
|
45494
45933
|
}
|
|
@@ -45580,13 +46019,13 @@ function buildSearchBody(args) {
|
|
|
45580
46019
|
return body;
|
|
45581
46020
|
}
|
|
45582
46021
|
async function downloadShots(results) {
|
|
45583
|
-
const dir =
|
|
46022
|
+
const dir = path36.join(process.cwd(), ".baker", "inspiration");
|
|
45584
46023
|
const saved = /* @__PURE__ */ new Map();
|
|
45585
46024
|
await Promise.all(
|
|
45586
46025
|
results.map(async (result) => {
|
|
45587
46026
|
const file = await downloadReadableShot(
|
|
45588
46027
|
result.desktopShotUrl,
|
|
45589
|
-
|
|
46028
|
+
path36.join(dir, `${result.id}.${READABLE_SHOT.extension}`)
|
|
45590
46029
|
);
|
|
45591
46030
|
if (file) saved.set(result.id, file);
|
|
45592
46031
|
})
|
|
@@ -45814,7 +46253,7 @@ var sequencesCommand = defineCommand164({
|
|
|
45814
46253
|
});
|
|
45815
46254
|
|
|
45816
46255
|
// src/commands/landing/inspiration/view.ts
|
|
45817
|
-
import
|
|
46256
|
+
import path37 from "path";
|
|
45818
46257
|
import { defineCommand as defineCommand165 } from "citty";
|
|
45819
46258
|
registerSchema({
|
|
45820
46259
|
command: "landing.inspiration.view",
|
|
@@ -45847,12 +46286,12 @@ var viewCommand2 = defineCommand165({
|
|
|
45847
46286
|
const id = args.id;
|
|
45848
46287
|
const data = await apiGet("/api/landing-inspiration/section", { id });
|
|
45849
46288
|
const section = data.section;
|
|
45850
|
-
const dir =
|
|
46289
|
+
const dir = path37.join(process.cwd(), ".baker", "inspiration", id);
|
|
45851
46290
|
const ext = READABLE_SHOT.extension;
|
|
45852
46291
|
const [desktop, mobile, filmstrip] = await Promise.all([
|
|
45853
|
-
downloadReadableShot(section.desktopShotUrl,
|
|
45854
|
-
downloadReadableShot(section.mobileShotUrl,
|
|
45855
|
-
downloadReadableShot(section.motionFilmstripUrl,
|
|
46292
|
+
downloadReadableShot(section.desktopShotUrl, path37.join(dir, `desktop.${ext}`)),
|
|
46293
|
+
downloadReadableShot(section.mobileShotUrl, path37.join(dir, `mobile.${ext}`)),
|
|
46294
|
+
downloadReadableShot(section.motionFilmstripUrl, path37.join(dir, `motion-filmstrip.${ext}`))
|
|
45856
46295
|
]);
|
|
45857
46296
|
const full = args.full;
|
|
45858
46297
|
const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
|
|
@@ -47701,8 +48140,8 @@ var listCommand15 = defineCommand184({
|
|
|
47701
48140
|
});
|
|
47702
48141
|
|
|
47703
48142
|
// src/commands/scheduled-actions/templates.ts
|
|
47704
|
-
import { readFile as
|
|
47705
|
-
import
|
|
48143
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
48144
|
+
import path38 from "path";
|
|
47706
48145
|
import { defineCommand as defineCommand185 } from "citty";
|
|
47707
48146
|
registerSchema({
|
|
47708
48147
|
command: "scheduled-actions.templates",
|
|
@@ -47805,7 +48244,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
|
|
|
47805
48244
|
}
|
|
47806
48245
|
if (save.length > 0) {
|
|
47807
48246
|
const briefFile = flag("brief-file");
|
|
47808
|
-
const brief = briefFile.length > 0 ? await
|
|
48247
|
+
const brief = briefFile.length > 0 ? await readFile24(path38.resolve(briefFile), "utf8") : flag("brief");
|
|
47809
48248
|
if (brief.trim().length === 0) {
|
|
47810
48249
|
failValidation4("--brief-file (preferred) or --brief is required: the brief is the recipe.");
|
|
47811
48250
|
}
|
|
@@ -48276,7 +48715,7 @@ function parseImageRefs(spec) {
|
|
|
48276
48715
|
}
|
|
48277
48716
|
var defaultDeps = {
|
|
48278
48717
|
ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
|
|
48279
|
-
upload: (
|
|
48718
|
+
upload: (path40) => uploadLocalImage({ file: path40, contentType: detectImageContentType(path40), source: "uploaded" })
|
|
48280
48719
|
};
|
|
48281
48720
|
async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
48282
48721
|
const refs = parseImageRefs(spec);
|
|
@@ -48296,10 +48735,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
|
48296
48735
|
}
|
|
48297
48736
|
return { imageIds, added };
|
|
48298
48737
|
}
|
|
48299
|
-
function uploadFailure(
|
|
48738
|
+
function uploadFailure(path40) {
|
|
48300
48739
|
return (error) => {
|
|
48301
48740
|
if (error instanceof ApiError) throw error;
|
|
48302
|
-
throw new ApiError("VALIDATION_ERROR", `Could not read "${
|
|
48741
|
+
throw new ApiError("VALIDATION_ERROR", `Could not read "${path40}" as an image.`);
|
|
48303
48742
|
};
|
|
48304
48743
|
}
|
|
48305
48744
|
|
|
@@ -49371,10 +49810,10 @@ async function stageOp4(op) {
|
|
|
49371
49810
|
handleError5(err);
|
|
49372
49811
|
}
|
|
49373
49812
|
}
|
|
49374
|
-
async function draftAction3(
|
|
49813
|
+
async function draftAction3(path40, body, chat) {
|
|
49375
49814
|
const chatId = resolveChatId(chat);
|
|
49376
49815
|
try {
|
|
49377
|
-
const data = await apiPost(
|
|
49816
|
+
const data = await apiPost(path40, { chatId, ...body });
|
|
49378
49817
|
writeJsonEnvelope({ ok: true, data });
|
|
49379
49818
|
return data;
|
|
49380
49819
|
} catch (err) {
|
|
@@ -50479,9 +50918,9 @@ var groupCommand2 = defineCommand210({
|
|
|
50479
50918
|
});
|
|
50480
50919
|
|
|
50481
50920
|
// src/commands/videos/ingest.ts
|
|
50482
|
-
import { mkdtemp as
|
|
50483
|
-
import { tmpdir as
|
|
50484
|
-
import
|
|
50921
|
+
import { mkdtemp as mkdtemp3, rm as rm8, stat as stat7 } from "fs/promises";
|
|
50922
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
50923
|
+
import path39 from "path";
|
|
50485
50924
|
import { defineCommand as defineCommand211 } from "citty";
|
|
50486
50925
|
|
|
50487
50926
|
// src/lib/streamUpload.ts
|
|
@@ -50832,7 +51271,7 @@ function ingestUrl(args) {
|
|
|
50832
51271
|
}
|
|
50833
51272
|
async function downloadThenIngest(args, country) {
|
|
50834
51273
|
const vimeoCookie = captureVimeoCookie();
|
|
50835
|
-
const workDir = await
|
|
51274
|
+
const workDir = await mkdtemp3(path39.join(tmpdir4(), "videos-ingest-"));
|
|
50836
51275
|
try {
|
|
50837
51276
|
const probe = await probeYtDlp({ url: args.url, country, vimeoCookie, cookieDir: workDir });
|
|
50838
51277
|
if (isAudioOnly(probe.info)) {
|
|
@@ -50889,7 +51328,7 @@ async function downloadThenIngest(args, country) {
|
|
|
50889
51328
|
...reducedQuality ? { reducedQuality: true } : {}
|
|
50890
51329
|
};
|
|
50891
51330
|
} finally {
|
|
50892
|
-
await
|
|
51331
|
+
await rm8(workDir, { recursive: true, force: true }).catch(() => {
|
|
50893
51332
|
});
|
|
50894
51333
|
}
|
|
50895
51334
|
}
|
|
@@ -50968,7 +51407,7 @@ var searchCommand4 = defineCommand212({
|
|
|
50968
51407
|
var tagsCommand6 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
50969
51408
|
|
|
50970
51409
|
// src/commands/videos/upload.ts
|
|
50971
|
-
import { readFile as
|
|
51410
|
+
import { readFile as readFile25, stat as stat8 } from "fs/promises";
|
|
50972
51411
|
import { basename as basename3, extname as extname4 } from "path";
|
|
50973
51412
|
import { defineCommand as defineCommand213 } from "citty";
|
|
50974
51413
|
var MIME_MAP = {
|
|
@@ -51065,7 +51504,7 @@ var uploadCommand2 = defineCommand213({
|
|
|
51065
51504
|
originalFilename,
|
|
51066
51505
|
descriptionContext
|
|
51067
51506
|
});
|
|
51068
|
-
const fileBuffer = await
|
|
51507
|
+
const fileBuffer = await readFile25(filePath);
|
|
51069
51508
|
const uploadResponse = await fetch(uploadUrl, {
|
|
51070
51509
|
method: "PUT",
|
|
51071
51510
|
headers: { "Content-Type": contentType },
|
|
@@ -52452,7 +52891,7 @@ function unknownFlagEnvelope(unknown, commandPath, suggestion) {
|
|
|
52452
52891
|
};
|
|
52453
52892
|
}
|
|
52454
52893
|
function commandPathOf(root, argv) {
|
|
52455
|
-
const
|
|
52894
|
+
const path40 = [];
|
|
52456
52895
|
let command = root;
|
|
52457
52896
|
for (const token of argv) {
|
|
52458
52897
|
if (token === "--" || token.startsWith("-")) {
|
|
@@ -52463,10 +52902,10 @@ function commandPathOf(root, argv) {
|
|
|
52463
52902
|
if (next === void 0 || typeof next !== "object") {
|
|
52464
52903
|
break;
|
|
52465
52904
|
}
|
|
52466
|
-
|
|
52905
|
+
path40.push(token);
|
|
52467
52906
|
command = next;
|
|
52468
52907
|
}
|
|
52469
|
-
return
|
|
52908
|
+
return path40.join(" ");
|
|
52470
52909
|
}
|
|
52471
52910
|
function refuseUnknownFlags(root, argv) {
|
|
52472
52911
|
const unknown = findUnknownFlags(root, argv);
|