@koda-sl/baker-cli 0.263.0-dev.ac71c8123 → 0.265.0-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 +751 -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,80 @@ 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="ov clip pos-top-left 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="ov clip pos-center 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
|
+
" .brand-mark { width: 220px; height: auto; opacity: 0.92;",
|
|
32681
|
+
" filter: drop-shadow(0 2px 8px rgba(0,0,0,0.45)); }",
|
|
32682
|
+
" .endcard { display: flex; flex-direction: column; align-items: center; gap: 56px;",
|
|
32683
|
+
" width: 100%; max-width: 1080px; padding: 0 60px; text-align: center; }",
|
|
32684
|
+
" .endcard-logo { width: 620px; max-width: 78%; height: auto; }",
|
|
32685
|
+
" /* Capped and wrapping, not sized to its content: measured in a real browser,",
|
|
32686
|
+
" `GET YOUR FREE QUOTE` renders 948px wide against a 1080px canvas, so the next",
|
|
32687
|
+
" slightly longer call to action runs off the frame. Two lines beat an edge. */",
|
|
32688
|
+
" .endcard-cta { font-size: 64px; font-weight: 900; text-transform: uppercase;",
|
|
32689
|
+
" letter-spacing: 0.01em; color: #fff; padding: 28px 58px; border-radius: 56px;",
|
|
32690
|
+
" max-width: 900px; line-height: 1.15; background: rgba(0,0,0,0.82); }"
|
|
32691
|
+
].join("\n");
|
|
32692
|
+
}
|
|
32693
|
+
function injectAdBrandOverlay(indexHtml, overlayHtml) {
|
|
32694
|
+
if (!overlayHtml.trim()) return indexHtml;
|
|
32695
|
+
const withCss = indexHtml.replace(" </style>", `${adBrandOverlayCss()}
|
|
32696
|
+
</style>`);
|
|
32697
|
+
return withCss.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
32698
|
+
}
|
|
32699
|
+
|
|
32313
32700
|
// src/commands/canvas/composition-path.ts
|
|
32314
32701
|
import { existsSync as existsSync4 } from "fs";
|
|
32315
|
-
import
|
|
32702
|
+
import path22 from "path";
|
|
32316
32703
|
function resolveShippedCanvasDir(name, startDir, exists = existsSync4, maxDepth = 8) {
|
|
32317
|
-
const rel =
|
|
32704
|
+
const rel = path22.join("canvas", name);
|
|
32318
32705
|
let dir = startDir;
|
|
32319
32706
|
for (let i = 0; i < maxDepth; i++) {
|
|
32320
|
-
const candidate =
|
|
32321
|
-
if (exists(
|
|
32322
|
-
const parent =
|
|
32707
|
+
const candidate = path22.join(dir, rel);
|
|
32708
|
+
if (exists(path22.join(candidate, "meta.json"))) return candidate;
|
|
32709
|
+
const parent = path22.dirname(dir);
|
|
32323
32710
|
if (parent === dir) break;
|
|
32324
32711
|
dir = parent;
|
|
32325
32712
|
}
|
|
32326
|
-
return
|
|
32713
|
+
return path22.resolve(startDir, "../../../", rel);
|
|
32327
32714
|
}
|
|
32328
32715
|
|
|
32329
32716
|
// src/commands/canvas/scaffold-ad.ts
|
|
@@ -32369,7 +32756,7 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
32369
32756
|
const specPath = args.spec;
|
|
32370
32757
|
let parsed;
|
|
32371
32758
|
try {
|
|
32372
|
-
parsed = JSON.parse(await
|
|
32759
|
+
parsed = JSON.parse(await readFile15(specPath, "utf-8"));
|
|
32373
32760
|
} catch (e) {
|
|
32374
32761
|
writeJson({
|
|
32375
32762
|
ok: false,
|
|
@@ -32396,29 +32783,59 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
32396
32783
|
return;
|
|
32397
32784
|
}
|
|
32398
32785
|
const blueprint = adSpecToBlueprint(spec.data);
|
|
32399
|
-
const slug = args.slug ??
|
|
32400
|
-
const outPath = args.out ?? (args.slug ?
|
|
32401
|
-
const outDir =
|
|
32786
|
+
const slug = args.slug ?? path23.basename(specPath).replace(/\.[^.]+$/, "");
|
|
32787
|
+
const outPath = args.out ?? (args.slug ? path23.join("src/creatives", slug, `${slug}.canvas.json`) : path23.join(path23.dirname(specPath), `${slug}.canvas.json`));
|
|
32788
|
+
const outDir = path23.dirname(outPath);
|
|
32402
32789
|
await mkdir7(outDir, { recursive: true });
|
|
32403
|
-
const compositionDest =
|
|
32404
|
-
const captionsDest =
|
|
32790
|
+
const compositionDest = path23.join(outDir, "video-overlay-composition");
|
|
32791
|
+
const captionsDest = path23.join(outDir, "tiktok-captions-composition");
|
|
32405
32792
|
await cp(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
32406
32793
|
await cp(SHIPPED_CAPTIONS_DIR, captionsDest, { recursive: true });
|
|
32407
|
-
const blueprintPath =
|
|
32408
|
-
const blueprintStylePath =
|
|
32794
|
+
const blueprintPath = path23.join(outDir, "prompt.json");
|
|
32795
|
+
const blueprintStylePath = path23.join(outDir, "prompt.style.json");
|
|
32409
32796
|
await writeSceneFiles(outDir, blueprint);
|
|
32410
32797
|
await writeFile9(blueprintStylePath, renderStyleProjectionFromValue(blueprint), "utf8");
|
|
32798
|
+
const logoPath = spec.data.brand?.logo?.trim();
|
|
32799
|
+
let staged = null;
|
|
32800
|
+
if (logoPath) {
|
|
32801
|
+
const ext = path23.extname(logoPath) || ".png";
|
|
32802
|
+
const dest = path23.join(compositionDest, `brand-mark${ext}`);
|
|
32803
|
+
try {
|
|
32804
|
+
await copyFile(logoPath, dest);
|
|
32805
|
+
staged = { file: `brand-mark${ext}`, alt: spec.data.brand?.name ?? "brand" };
|
|
32806
|
+
} catch {
|
|
32807
|
+
}
|
|
32808
|
+
}
|
|
32809
|
+
const closing = blueprint.scenes[blueprint.scenes.length - 1];
|
|
32810
|
+
const overlayHtml = endCardWanted(spec.data) && closing ? buildAdBrandOverlay({
|
|
32811
|
+
logo: staged,
|
|
32812
|
+
cta: endCardCta(spec.data),
|
|
32813
|
+
cardAtS: closing.start_s,
|
|
32814
|
+
cardDurationS: closing.duration_s,
|
|
32815
|
+
totalS: blueprint.estimated_duration_s,
|
|
32816
|
+
accent: firstHex(spec.data.brand?.palette)
|
|
32817
|
+
}) : "";
|
|
32818
|
+
if (overlayHtml) {
|
|
32819
|
+
const indexPath = path23.join(compositionDest, "index.html");
|
|
32820
|
+
const html = await readFile15(indexPath, "utf-8");
|
|
32821
|
+
await writeFile9(indexPath, injectAdBrandOverlay(html, overlayHtml), "utf-8");
|
|
32822
|
+
}
|
|
32411
32823
|
const opts = {
|
|
32412
32824
|
imageModel: AD_IMAGE_MODEL,
|
|
32413
32825
|
videoModel: DEFAULT_VIDEO_GENERATE_MODEL,
|
|
32414
|
-
overlayCompositionPath:
|
|
32415
|
-
captionsCompositionPath:
|
|
32416
|
-
blueprintPath:
|
|
32417
|
-
blueprintStylePath:
|
|
32826
|
+
overlayCompositionPath: path23.relative(outDir, compositionDest),
|
|
32827
|
+
captionsCompositionPath: path23.relative(outDir, captionsDest),
|
|
32828
|
+
blueprintPath: path23.relative(outDir, blueprintPath),
|
|
32829
|
+
blueprintStylePath: path23.relative(outDir, blueprintStylePath),
|
|
32418
32830
|
aspect: spec.data.format.aspect_ratio,
|
|
32419
|
-
resolution: spec.data.format.resolution
|
|
32831
|
+
resolution: spec.data.format.resolution,
|
|
32832
|
+
// A beat is one clause and one card, so the cap has to clear a whole clause.
|
|
32833
|
+
// The composition's default of 3 is TikTok karaoke styling; applied to an ad
|
|
32834
|
+
// it overrode the clause rule on every line and rendered "IS STANDARD. WE".
|
|
32835
|
+
// 8 is the composition's own maximum and clears every beat measured here.
|
|
32836
|
+
captionWordsPerGroup: 8
|
|
32420
32837
|
};
|
|
32421
|
-
const canvas = scaffoldVideoCanvas(blueprint,
|
|
32838
|
+
const canvas = scaffoldVideoCanvas(blueprint, adSpecCastElements(spec.data), opts);
|
|
32422
32839
|
await writeFile9(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
32423
32840
|
`, "utf-8");
|
|
32424
32841
|
writeJson({
|
|
@@ -32427,18 +32844,28 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
32427
32844
|
hints: [
|
|
32428
32845
|
`Wrote ${spec.data.beats.length} beats to ${outPath}. Run it with \`baker canvas run ${outPath}\`.`,
|
|
32429
32846
|
`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.`,
|
|
32847
|
+
...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.`] : [],
|
|
32848
|
+
...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."] : [],
|
|
32849
|
+
...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
32850
|
"The caption cards are your `say` lines verbatim \u2014 read them back before running; that is the copy the viewer sees.",
|
|
32431
32851
|
"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
|
-
|
|
32852
|
+
...spec.data.brand?.palette?.length && spec.data.brand?.logo ? [] : [
|
|
32853
|
+
// Keyed on what is MISSING, not on whether `brand` exists at all. The
|
|
32854
|
+
// previous wording fired only when the spec carried no brand — so a spec
|
|
32855
|
+
// holding `brand.name` and nothing else sailed through and rendered an
|
|
32856
|
+
// ad with no palette and no mark anywhere in 28 seconds.
|
|
32857
|
+
`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.`
|
|
32858
|
+
],
|
|
32859
|
+
"`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
32860
|
]
|
|
32434
32861
|
});
|
|
32435
32862
|
}
|
|
32436
32863
|
});
|
|
32437
32864
|
|
|
32438
32865
|
// 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
|
|
32866
|
+
import { access as access2, cp as cp2, mkdir as mkdir8, readFile as readFile18, rm as rm7, writeFile as writeFile10 } from "fs/promises";
|
|
32867
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
32868
|
+
import path25 from "path";
|
|
32442
32869
|
import { defineCommand as defineCommand105 } from "citty";
|
|
32443
32870
|
|
|
32444
32871
|
// src/engine/scaffold/lib/model-router.ts
|
|
@@ -32477,12 +32904,12 @@ function routeVideoModel(input) {
|
|
|
32477
32904
|
}
|
|
32478
32905
|
|
|
32479
32906
|
// src/engine/nodes/local/lib/sceneDetect.ts
|
|
32480
|
-
import { execFile as
|
|
32481
|
-
import { mkdtemp, readdir as readdir7, readFile as
|
|
32482
|
-
import { tmpdir } from "os";
|
|
32907
|
+
import { execFile as execFile3 } from "child_process";
|
|
32908
|
+
import { mkdtemp as mkdtemp2, readdir as readdir7, readFile as readFile16, rm as rm6 } from "fs/promises";
|
|
32909
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
32483
32910
|
import { join as join2 } from "path";
|
|
32484
|
-
import { promisify as
|
|
32485
|
-
var
|
|
32911
|
+
import { promisify as promisify3 } from "util";
|
|
32912
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
32486
32913
|
var PYSCENEDETECT_THRESHOLD = 18;
|
|
32487
32914
|
var PYSCENEDETECT_MIN_SCENE_LEN_S = 0.25;
|
|
32488
32915
|
var PYSCENEDETECT_RECHECK_THRESHOLD = 27;
|
|
@@ -32533,9 +32960,9 @@ function parsePySceneDetectCsvCuts(csv) {
|
|
|
32533
32960
|
return [...new Set(cuts)].sort((a, b) => a - b);
|
|
32534
32961
|
}
|
|
32535
32962
|
async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs) {
|
|
32536
|
-
const outDir = await
|
|
32963
|
+
const outDir = await mkdtemp2(join2(tmpdir2(), "baker-scenedetect-"));
|
|
32537
32964
|
try {
|
|
32538
|
-
await
|
|
32965
|
+
await execFileAsync3(
|
|
32539
32966
|
"scenedetect",
|
|
32540
32967
|
[
|
|
32541
32968
|
"--input",
|
|
@@ -32554,9 +32981,9 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
|
|
|
32554
32981
|
);
|
|
32555
32982
|
const csvName = (await readdir7(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
|
|
32556
32983
|
if (!csvName) return [];
|
|
32557
|
-
return parsePySceneDetectCsvCuts(await
|
|
32984
|
+
return parsePySceneDetectCsvCuts(await readFile16(join2(outDir, csvName), "utf-8"));
|
|
32558
32985
|
} finally {
|
|
32559
|
-
await
|
|
32986
|
+
await rm6(outDir, { recursive: true, force: true });
|
|
32560
32987
|
}
|
|
32561
32988
|
}
|
|
32562
32989
|
async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
@@ -32578,8 +33005,8 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
|
32578
33005
|
}
|
|
32579
33006
|
|
|
32580
33007
|
// src/commands/canvas/gitignore.ts
|
|
32581
|
-
import { appendFile, readFile as
|
|
32582
|
-
import
|
|
33008
|
+
import { appendFile, readFile as readFile17 } from "fs/promises";
|
|
33009
|
+
import path24 from "path";
|
|
32583
33010
|
function missingGitignoreEntries(existing, entries) {
|
|
32584
33011
|
const present2 = new Set(
|
|
32585
33012
|
existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
@@ -32587,10 +33014,10 @@ function missingGitignoreEntries(existing, entries) {
|
|
|
32587
33014
|
return entries.filter((e) => !present2.has(e.trim().replace(/\/+$/, "")));
|
|
32588
33015
|
}
|
|
32589
33016
|
async function ensureGitignore(dir, entries) {
|
|
32590
|
-
const file =
|
|
33017
|
+
const file = path24.join(dir, ".gitignore");
|
|
32591
33018
|
let existing;
|
|
32592
33019
|
try {
|
|
32593
|
-
existing = await
|
|
33020
|
+
existing = await readFile17(file, "utf8");
|
|
32594
33021
|
} catch {
|
|
32595
33022
|
return;
|
|
32596
33023
|
}
|
|
@@ -32629,7 +33056,7 @@ ONE PERSON, MULTIPLE LOOKS: if a single individual plays MULTIPLE personas or wa
|
|
|
32629
33056
|
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
33057
|
async function loadAssetText2(ref, label) {
|
|
32631
33058
|
const r = ref;
|
|
32632
|
-
if (typeof r?.path === "string") return
|
|
33059
|
+
if (typeof r?.path === "string") return readFile18(r.path, "utf8");
|
|
32633
33060
|
if (typeof r?.url === "string") {
|
|
32634
33061
|
const res = await fetch(r.url);
|
|
32635
33062
|
if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
|
|
@@ -32648,7 +33075,7 @@ async function loadTranscriptBestEffort(ref) {
|
|
|
32648
33075
|
async function stageCaptions(outDir, transcript) {
|
|
32649
33076
|
const text2 = transcript?.trim();
|
|
32650
33077
|
if (!text2 || text2 === "[]") return {};
|
|
32651
|
-
const compositionPath =
|
|
33078
|
+
const compositionPath = path25.join(outDir, "tiktok-captions-composition");
|
|
32652
33079
|
await cp2(SHIPPED_CAPTIONS_DIR2, compositionPath, { recursive: true });
|
|
32653
33080
|
return { compositionPath };
|
|
32654
33081
|
}
|
|
@@ -32666,11 +33093,11 @@ function patchCompositionHtml(html, dims) {
|
|
|
32666
33093
|
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
33094
|
}
|
|
32668
33095
|
async function stampCompositionDims(compositionDir, dims) {
|
|
32669
|
-
const metaPath =
|
|
32670
|
-
const rawMeta = await
|
|
33096
|
+
const metaPath = path25.join(compositionDir, "meta.json");
|
|
33097
|
+
const rawMeta = await readFile18(metaPath, "utf8");
|
|
32671
33098
|
await writeFile10(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
|
|
32672
|
-
const htmlPath =
|
|
32673
|
-
const rawHtml = await
|
|
33099
|
+
const htmlPath = path25.join(compositionDir, "index.html");
|
|
33100
|
+
const rawHtml = await readFile18(htmlPath, "utf8");
|
|
32674
33101
|
await writeFile10(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
|
|
32675
33102
|
}
|
|
32676
33103
|
function parseElements2(raw) {
|
|
@@ -32718,7 +33145,7 @@ var VIDEO_EXT_BY_MIME = {
|
|
|
32718
33145
|
"video/x-matroska": ".mkv"
|
|
32719
33146
|
};
|
|
32720
33147
|
function referenceVideoExt(url, contentType) {
|
|
32721
|
-
const fromPath =
|
|
33148
|
+
const fromPath = path25.extname(new URL(url).pathname).toLowerCase();
|
|
32722
33149
|
if (fromPath && fromPath.length <= 5) return fromPath;
|
|
32723
33150
|
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase();
|
|
32724
33151
|
return mime && VIDEO_EXT_BY_MIME[mime] || ".mp4";
|
|
@@ -32744,7 +33171,7 @@ function videoDefinitionDescription(blueprint) {
|
|
|
32744
33171
|
return typeof product === "string" && product.trim() ? product.trim() : void 0;
|
|
32745
33172
|
}
|
|
32746
33173
|
async function materializeReferenceVideo(fileArg2) {
|
|
32747
|
-
if (!/^https?:\/\//i.test(fileArg2)) return
|
|
33174
|
+
if (!/^https?:\/\//i.test(fileArg2)) return path25.resolve(fileArg2);
|
|
32748
33175
|
let bytes;
|
|
32749
33176
|
let contentType;
|
|
32750
33177
|
try {
|
|
@@ -32756,8 +33183,8 @@ async function materializeReferenceVideo(fileArg2) {
|
|
|
32756
33183
|
throw new Error(`failed to download reference video: ${e instanceof Error ? e.message : String(e)}`);
|
|
32757
33184
|
}
|
|
32758
33185
|
if (bytes.length === 0) throw new Error("reference video download was empty");
|
|
32759
|
-
const dest =
|
|
32760
|
-
|
|
33186
|
+
const dest = path25.join(
|
|
33187
|
+
tmpdir3(),
|
|
32761
33188
|
`baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg2, contentType)}`
|
|
32762
33189
|
);
|
|
32763
33190
|
await writeFile10(dest, bytes);
|
|
@@ -32976,11 +33403,11 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
32976
33403
|
} catch (e) {
|
|
32977
33404
|
return fail4("download", e instanceof Error ? e.message : String(e));
|
|
32978
33405
|
}
|
|
32979
|
-
const base =
|
|
32980
|
-
const outPath = args.out ?
|
|
32981
|
-
const outDir =
|
|
32982
|
-
const blueprintPath =
|
|
32983
|
-
const blueprintStylePath =
|
|
33406
|
+
const base = path25.basename(videoPath, path25.extname(videoPath));
|
|
33407
|
+
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`);
|
|
33408
|
+
const outDir = path25.dirname(outPath);
|
|
33409
|
+
const blueprintPath = path25.join(outDir, "prompt.json");
|
|
33410
|
+
const blueprintStylePath = path25.join(outDir, "prompt.style.json");
|
|
32984
33411
|
const frames = args.frames === "reuse" ? "reuse" : "generate";
|
|
32985
33412
|
const maxScenes = args["max-scenes"] ? Number(args["max-scenes"]) : void 0;
|
|
32986
33413
|
if (Number.isFinite(maxScenes)) {
|
|
@@ -33025,12 +33452,12 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33025
33452
|
`
|
|
33026
33453
|
);
|
|
33027
33454
|
}
|
|
33028
|
-
const compositionDest =
|
|
33455
|
+
const compositionDest = path25.join(outDir, "video-overlay-composition");
|
|
33029
33456
|
await cp2(SHIPPED_COMPOSITION_DIR2, compositionDest, { recursive: true });
|
|
33030
33457
|
await stampCompositionDims(compositionDest, outDims);
|
|
33031
|
-
const indexPath =
|
|
33458
|
+
const indexPath = path25.join(compositionDest, "index.html");
|
|
33032
33459
|
const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript) });
|
|
33033
|
-
const indexHtml = await
|
|
33460
|
+
const indexHtml = await readFile18(indexPath, "utf8");
|
|
33034
33461
|
const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
33035
33462
|
if (injected === indexHtml && overlayHtml.trim()) {
|
|
33036
33463
|
fail4(
|
|
@@ -33044,10 +33471,10 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33044
33471
|
const opts = {
|
|
33045
33472
|
imageModel,
|
|
33046
33473
|
videoModel,
|
|
33047
|
-
overlayCompositionPath:
|
|
33048
|
-
captionsCompositionPath: captions.compositionPath ?
|
|
33049
|
-
blueprintPath:
|
|
33050
|
-
blueprintStylePath:
|
|
33474
|
+
overlayCompositionPath: path25.relative(outDir, compositionDest),
|
|
33475
|
+
captionsCompositionPath: captions.compositionPath ? path25.relative(outDir, captions.compositionPath) : void 0,
|
|
33476
|
+
blueprintPath: path25.relative(outDir, blueprintPath),
|
|
33477
|
+
blueprintStylePath: path25.relative(outDir, blueprintStylePath),
|
|
33051
33478
|
frames,
|
|
33052
33479
|
ambient: Boolean(args.ambient),
|
|
33053
33480
|
seamDedup: resolveSeamDedup(args["seam-dedup"]),
|
|
@@ -33063,7 +33490,7 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33063
33490
|
return fail4("scaffold", e instanceof Error ? e.message : String(e));
|
|
33064
33491
|
}
|
|
33065
33492
|
if (captions.compositionPath && !canvas.nodes.some((n) => n.id === "captions")) {
|
|
33066
|
-
await
|
|
33493
|
+
await rm7(captions.compositionPath, { recursive: true, force: true });
|
|
33067
33494
|
}
|
|
33068
33495
|
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(canvas, outDir), defaultRegistry());
|
|
33069
33496
|
if (!validation.ok) {
|
|
@@ -33075,7 +33502,7 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33075
33502
|
await writeFile10(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
33076
33503
|
`, "utf8");
|
|
33077
33504
|
await writeFile10(
|
|
33078
|
-
|
|
33505
|
+
path25.join(outDir, REBUILD_FILE),
|
|
33079
33506
|
`${JSON.stringify({ elements, opts }, null, 2)}
|
|
33080
33507
|
`,
|
|
33081
33508
|
"utf8"
|
|
@@ -33100,7 +33527,7 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33100
33527
|
await ensureGitignore(process.cwd(), ["canvas/", ".context/"]);
|
|
33101
33528
|
const sourceRef = videoSourceReference(blueprint, fileArg2);
|
|
33102
33529
|
if (slug) {
|
|
33103
|
-
const definitionPath =
|
|
33530
|
+
const definitionPath = path25.join(outDir, "_definition.md");
|
|
33104
33531
|
if (!await fileExists2(definitionPath)) {
|
|
33105
33532
|
await writeFile10(
|
|
33106
33533
|
definitionPath,
|
|
@@ -33155,7 +33582,7 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33155
33582
|
graph: canvas.metadata?.video?.graph_stats
|
|
33156
33583
|
},
|
|
33157
33584
|
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 \`${
|
|
33585
|
+
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
33586
|
recurring_elements_to_supply: report.elements,
|
|
33160
33587
|
voices_to_confirm: report.dialogue.map((d) => ({
|
|
33161
33588
|
scene: d.scene,
|
|
@@ -33192,8 +33619,8 @@ var scaffoldVideoCommand = defineCommand105({
|
|
|
33192
33619
|
});
|
|
33193
33620
|
|
|
33194
33621
|
// src/commands/canvas/set-prompt.ts
|
|
33195
|
-
import { readFile as
|
|
33196
|
-
import
|
|
33622
|
+
import { readFile as readFile19, writeFile as writeFile11 } from "fs/promises";
|
|
33623
|
+
import path26 from "path";
|
|
33197
33624
|
import { defineCommand as defineCommand106 } from "citty";
|
|
33198
33625
|
function setNodePrompt(canvas, nodeId, text2) {
|
|
33199
33626
|
const nodes = canvas?.nodes;
|
|
@@ -33221,17 +33648,17 @@ var setPromptCommand = defineCommand106({
|
|
|
33221
33648
|
"text-file": { type: "string", description: "Read the new prompt from a UTF-8 file (preserves accents/newlines)" }
|
|
33222
33649
|
},
|
|
33223
33650
|
async run({ args }) {
|
|
33224
|
-
const filePath =
|
|
33651
|
+
const filePath = path26.resolve(String(args.file));
|
|
33225
33652
|
let canvas;
|
|
33226
33653
|
try {
|
|
33227
|
-
canvas = JSON.parse(await
|
|
33654
|
+
canvas = JSON.parse(await readFile19(filePath, "utf8"));
|
|
33228
33655
|
} catch (e) {
|
|
33229
33656
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "parse", message: String(e) } }, null, 2)}
|
|
33230
33657
|
`);
|
|
33231
33658
|
process.exit(2);
|
|
33232
33659
|
}
|
|
33233
33660
|
let text2;
|
|
33234
|
-
if (args["text-file"]) text2 = await
|
|
33661
|
+
if (args["text-file"]) text2 = await readFile19(path26.resolve(String(args["text-file"])), "utf8");
|
|
33235
33662
|
else if (args.text !== void 0) text2 = String(args.text);
|
|
33236
33663
|
else {
|
|
33237
33664
|
process.stderr.write(
|
|
@@ -33252,7 +33679,7 @@ var setPromptCommand = defineCommand106({
|
|
|
33252
33679
|
process.exit(2);
|
|
33253
33680
|
return;
|
|
33254
33681
|
}
|
|
33255
|
-
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated,
|
|
33682
|
+
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated, path26.dirname(filePath)), defaultRegistry());
|
|
33256
33683
|
if (!validation.ok) {
|
|
33257
33684
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.issues } }, null, 2)}
|
|
33258
33685
|
`);
|
|
@@ -33267,8 +33694,8 @@ var setPromptCommand = defineCommand106({
|
|
|
33267
33694
|
});
|
|
33268
33695
|
|
|
33269
33696
|
// src/commands/canvas/validate.ts
|
|
33270
|
-
import { readFile as
|
|
33271
|
-
import
|
|
33697
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
33698
|
+
import path27 from "path";
|
|
33272
33699
|
import { defineCommand as defineCommand107 } from "citty";
|
|
33273
33700
|
var validateCommand = defineCommand107({
|
|
33274
33701
|
meta: {
|
|
@@ -33277,8 +33704,8 @@ var validateCommand = defineCommand107({
|
|
|
33277
33704
|
},
|
|
33278
33705
|
args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
|
|
33279
33706
|
async run({ args }) {
|
|
33280
|
-
const filePath =
|
|
33281
|
-
const raw = await
|
|
33707
|
+
const filePath = path27.resolve(String(args.file));
|
|
33708
|
+
const raw = await readFile20(filePath, "utf8");
|
|
33282
33709
|
let parsed;
|
|
33283
33710
|
try {
|
|
33284
33711
|
parsed = JSON.parse(raw);
|
|
@@ -33290,7 +33717,7 @@ var validateCommand = defineCommand107({
|
|
|
33290
33717
|
}
|
|
33291
33718
|
const healed = await healAbsoluteCanvasPaths(filePath, parsed);
|
|
33292
33719
|
parsed = healed.canvas;
|
|
33293
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
33720
|
+
parsed = resolveRelativeCanvasPaths(parsed, path27.dirname(filePath));
|
|
33294
33721
|
let styleProjection = "not_applicable";
|
|
33295
33722
|
try {
|
|
33296
33723
|
styleProjection = await syncStyleProjection(parsed, (line) => process.stderr.write(`${line}
|
|
@@ -33716,7 +34143,7 @@ import { defineCommand as defineCommand112 } from "citty";
|
|
|
33716
34143
|
import { defineCommand as defineCommand111 } from "citty";
|
|
33717
34144
|
|
|
33718
34145
|
// src/commands/images/api.ts
|
|
33719
|
-
import { readFile as
|
|
34146
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
33720
34147
|
import { basename, extname } from "path";
|
|
33721
34148
|
var imageProcessingTimeoutMs = 18e4;
|
|
33722
34149
|
var imageReadyPollIntervalMs = 2e3;
|
|
@@ -33730,7 +34157,7 @@ var mimeMap = {
|
|
|
33730
34157
|
".avif": "image/avif"
|
|
33731
34158
|
};
|
|
33732
34159
|
var defaultImageApiDeps = {
|
|
33733
|
-
readFile:
|
|
34160
|
+
readFile: readFile21,
|
|
33734
34161
|
post: apiPost,
|
|
33735
34162
|
get: apiGet,
|
|
33736
34163
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
|
|
@@ -33992,12 +34419,12 @@ function collectSideEffects(tree) {
|
|
|
33992
34419
|
);
|
|
33993
34420
|
}
|
|
33994
34421
|
function readFlowTree(slug) {
|
|
33995
|
-
const
|
|
33996
|
-
if (!existsSync5(
|
|
34422
|
+
const path40 = join3(flowsDir(), slug, "_data.json");
|
|
34423
|
+
if (!existsSync5(path40)) {
|
|
33997
34424
|
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
33998
34425
|
}
|
|
33999
34426
|
try {
|
|
34000
|
-
return JSON.parse(readFileSync9(
|
|
34427
|
+
return JSON.parse(readFileSync9(path40, "utf-8"));
|
|
34001
34428
|
} catch (error) {
|
|
34002
34429
|
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
34003
34430
|
}
|
|
@@ -34389,10 +34816,10 @@ function parseValueExpression(raw) {
|
|
|
34389
34816
|
return parts.map(parsePart);
|
|
34390
34817
|
}
|
|
34391
34818
|
function trackingFieldIds() {
|
|
34392
|
-
const
|
|
34393
|
-
if (!existsSync6(
|
|
34819
|
+
const path40 = join4(flowsDir(), "..", "tracking.ts");
|
|
34820
|
+
if (!existsSync6(path40)) return null;
|
|
34394
34821
|
try {
|
|
34395
|
-
const source = readFileSync10(
|
|
34822
|
+
const source = readFileSync10(path40, "utf-8");
|
|
34396
34823
|
const block2 = source.match(/TRACKING_FIELD_IDS\s*=\s*\[([\s\S]*?)\]\s*as const/)?.[1];
|
|
34397
34824
|
if (!block2) return null;
|
|
34398
34825
|
const ids = [...block2.matchAll(/"(tracking\.[a-z0-9_]+)"/g)].map((match) => match[1]);
|
|
@@ -34944,13 +35371,13 @@ function specsFromFile(parsed) {
|
|
|
34944
35371
|
return `${destField}${type}=${entry?.value ?? ""}`;
|
|
34945
35372
|
});
|
|
34946
35373
|
}
|
|
34947
|
-
function readSpecFile(
|
|
35374
|
+
function readSpecFile(path40) {
|
|
34948
35375
|
let raw;
|
|
34949
35376
|
try {
|
|
34950
|
-
raw =
|
|
35377
|
+
raw = path40 === "-" ? readFileSync11(0, "utf-8") : readFileSync11(path40, "utf-8");
|
|
34951
35378
|
} catch (error) {
|
|
34952
35379
|
refuse(
|
|
34953
|
-
`Could not read ${
|
|
35380
|
+
`Could not read ${path40 === "-" ? "the mapping from stdin" : `"${path40}"`}: ${error instanceof Error ? error.message : String(error)}`
|
|
34954
35381
|
);
|
|
34955
35382
|
}
|
|
34956
35383
|
let parsed;
|
|
@@ -34958,7 +35385,7 @@ function readSpecFile(path39) {
|
|
|
34958
35385
|
parsed = JSON.parse(raw);
|
|
34959
35386
|
} catch (error) {
|
|
34960
35387
|
refuse(
|
|
34961
|
-
`${
|
|
35388
|
+
`${path40 === "-" ? "stdin" : `"${path40}"`} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
34962
35389
|
'Expected { "map": { "<destField>": "<value>", \u2026 } }'
|
|
34963
35390
|
);
|
|
34964
35391
|
}
|
|
@@ -35212,18 +35639,18 @@ var ARRAY_FIELDS = [
|
|
|
35212
35639
|
"tagIds"
|
|
35213
35640
|
];
|
|
35214
35641
|
var ARRAY_OWNERS = ["", "body"];
|
|
35215
|
-
function dropUnsetOptionals(sideEffect,
|
|
35642
|
+
function dropUnsetOptionals(sideEffect, path40) {
|
|
35216
35643
|
return OPTIONAL_STRINGS.flatMap((key) => {
|
|
35217
35644
|
if (!(key in sideEffect) || sideEffect[key] !== null && sideEffect[key] !== "") return [];
|
|
35218
35645
|
delete sideEffect[key];
|
|
35219
|
-
return [{ path:
|
|
35646
|
+
return [{ path: path40, change: `dropped \`${key}\` (an optional string is absent, never null)` }];
|
|
35220
35647
|
});
|
|
35221
35648
|
}
|
|
35222
|
-
function fillNulledArrays(target, prefix,
|
|
35649
|
+
function fillNulledArrays(target, prefix, path40) {
|
|
35223
35650
|
return ARRAY_FIELDS.flatMap((key) => {
|
|
35224
35651
|
if (!(key in target) || target[key] !== null) return [];
|
|
35225
35652
|
target[key] = [];
|
|
35226
|
-
return [{ path:
|
|
35653
|
+
return [{ path: path40, change: `\`${prefix}${key}: null\` \u2192 \`[]\`` }];
|
|
35227
35654
|
});
|
|
35228
35655
|
}
|
|
35229
35656
|
function sideEffectsOf(node) {
|
|
@@ -35233,13 +35660,13 @@ function sideEffectsOf(node) {
|
|
|
35233
35660
|
);
|
|
35234
35661
|
}
|
|
35235
35662
|
function normalizeSideEffect(sideEffect, where) {
|
|
35236
|
-
const
|
|
35663
|
+
const path40 = `${where} \u2192 ${String(sideEffect.id ?? "side effect")}`;
|
|
35237
35664
|
const arrays = ARRAY_OWNERS.flatMap((owner) => {
|
|
35238
35665
|
const target = owner ? sideEffect[owner] : sideEffect;
|
|
35239
35666
|
if (!target || typeof target !== "object") return [];
|
|
35240
|
-
return fillNulledArrays(target, owner ? `${owner}.` : "",
|
|
35667
|
+
return fillNulledArrays(target, owner ? `${owner}.` : "", path40);
|
|
35241
35668
|
});
|
|
35242
|
-
return [...dropUnsetOptionals(sideEffect,
|
|
35669
|
+
return [...dropUnsetOptionals(sideEffect, path40), ...arrays];
|
|
35243
35670
|
}
|
|
35244
35671
|
function normalizeFlowTree(tree) {
|
|
35245
35672
|
const changes = [];
|
|
@@ -35777,10 +36204,10 @@ async function stageOps(ops) {
|
|
|
35777
36204
|
handleError2(err);
|
|
35778
36205
|
}
|
|
35779
36206
|
}
|
|
35780
|
-
async function draftAction2(
|
|
36207
|
+
async function draftAction2(path40, body, chat) {
|
|
35781
36208
|
const chatId = resolveChatId(chat);
|
|
35782
36209
|
try {
|
|
35783
|
-
const data = await apiPost(
|
|
36210
|
+
const data = await apiPost(path40, { chatId, ...body });
|
|
35784
36211
|
writeJsonEnvelope({ ok: true, data });
|
|
35785
36212
|
return data;
|
|
35786
36213
|
} catch (err) {
|
|
@@ -38177,7 +38604,7 @@ function cropSprite(input, region) {
|
|
|
38177
38604
|
|
|
38178
38605
|
// src/lib/image/io.ts
|
|
38179
38606
|
import { randomBytes } from "crypto";
|
|
38180
|
-
import { glob as fsGlob, readFile as
|
|
38607
|
+
import { glob as fsGlob, readFile as readFile22, rename, stat as stat4, writeFile as writeFile12 } from "fs/promises";
|
|
38181
38608
|
import { dirname as dirname2, extname as extname2, join as join5, resolve as resolve4 } from "path";
|
|
38182
38609
|
var REMOTE_RE = /^https?:\/\//i;
|
|
38183
38610
|
var GLOB_RE = /[*?[\]{}]/;
|
|
@@ -38210,11 +38637,11 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
38210
38637
|
const { buffer } = await fetchExternalBytes(pathOrUrl, { maxBytes: MAX_REMOTE_IMAGE_BYTES });
|
|
38211
38638
|
return buffer;
|
|
38212
38639
|
}
|
|
38213
|
-
return
|
|
38640
|
+
return readFile22(pathOrUrl);
|
|
38214
38641
|
}
|
|
38215
|
-
async function isDirectory(
|
|
38642
|
+
async function isDirectory(path40) {
|
|
38216
38643
|
try {
|
|
38217
|
-
const s = await stat4(
|
|
38644
|
+
const s = await stat4(path40);
|
|
38218
38645
|
return s.isDirectory();
|
|
38219
38646
|
} catch {
|
|
38220
38647
|
return false;
|
|
@@ -38516,13 +38943,13 @@ function resolveDownloadPath({ baseName, extension, out, outIsDirectory: outIsDi
|
|
|
38516
38943
|
}
|
|
38517
38944
|
function disambiguate(paths) {
|
|
38518
38945
|
const taken = /* @__PURE__ */ new Set();
|
|
38519
|
-
return paths.map((
|
|
38520
|
-
if (!taken.has(
|
|
38521
|
-
taken.add(
|
|
38522
|
-
return
|
|
38946
|
+
return paths.map((path40) => {
|
|
38947
|
+
if (!taken.has(path40)) {
|
|
38948
|
+
taken.add(path40);
|
|
38949
|
+
return path40;
|
|
38523
38950
|
}
|
|
38524
|
-
const ext = extname3(
|
|
38525
|
-
const stem =
|
|
38951
|
+
const ext = extname3(path40);
|
|
38952
|
+
const stem = path40.slice(0, path40.length - ext.length);
|
|
38526
38953
|
let n = 2;
|
|
38527
38954
|
while (taken.has(`${stem}-${n}${ext}`)) n += 1;
|
|
38528
38955
|
const unique = `${stem}-${n}${ext}`;
|
|
@@ -38649,10 +39076,10 @@ async function runDownloads(plan) {
|
|
|
38649
39076
|
const paths = disambiguate(fetched.map((item) => item.path));
|
|
38650
39077
|
const downloaded = [];
|
|
38651
39078
|
for (const [index, item] of fetched.entries()) {
|
|
38652
|
-
const
|
|
39079
|
+
const path40 = paths[index] ?? item.path;
|
|
38653
39080
|
try {
|
|
38654
|
-
await atomicWrite(
|
|
38655
|
-
downloaded.push({ input: item.input, output:
|
|
39081
|
+
await atomicWrite(path40, item.buffer);
|
|
39082
|
+
downloaded.push({ input: item.input, output: path40, bytes: item.buffer.length, contentType: item.contentType });
|
|
38656
39083
|
} catch (err) {
|
|
38657
39084
|
failed.push({ input: item.input, error: failureMessage(err, "Write failed") });
|
|
38658
39085
|
}
|
|
@@ -41612,7 +42039,7 @@ import { defineCommand as defineCommand167 } from "citty";
|
|
|
41612
42039
|
|
|
41613
42040
|
// src/commands/landing/critique.ts
|
|
41614
42041
|
import { readdir as readdir9, stat as stat6 } from "fs/promises";
|
|
41615
|
-
import
|
|
42042
|
+
import path30 from "path";
|
|
41616
42043
|
import { defineCommand as defineCommand157 } from "citty";
|
|
41617
42044
|
|
|
41618
42045
|
// src/engine/landing/lib/constants.ts
|
|
@@ -42580,13 +43007,13 @@ function describeCounts(findings) {
|
|
|
42580
43007
|
|
|
42581
43008
|
// src/commands/landing/snapshot.ts
|
|
42582
43009
|
import { mkdir as mkdir9, rename as rename2, writeFile as writeFile13 } from "fs/promises";
|
|
42583
|
-
import
|
|
43010
|
+
import path28 from "path";
|
|
42584
43011
|
var CRITIC_VERSION = "2";
|
|
42585
43012
|
function critiqueCacheDir(projectRoot) {
|
|
42586
|
-
return
|
|
43013
|
+
return path28.join(projectRoot, ".cache", "landing-critique");
|
|
42587
43014
|
}
|
|
42588
43015
|
function snapshotPath(projectRoot, slug) {
|
|
42589
|
-
return
|
|
43016
|
+
return path28.join(critiqueCacheDir(projectRoot), `${slug}.json`);
|
|
42590
43017
|
}
|
|
42591
43018
|
async function writeCritiqueSnapshot(projectRoot, snapshot) {
|
|
42592
43019
|
await mkdir9(critiqueCacheDir(projectRoot), { recursive: true });
|
|
@@ -42598,21 +43025,21 @@ async function writeCritiqueSnapshot(projectRoot, snapshot) {
|
|
|
42598
43025
|
}
|
|
42599
43026
|
|
|
42600
43027
|
// src/commands/landing/source-version.ts
|
|
42601
|
-
import { readdir as readdir8, readFile as
|
|
42602
|
-
import
|
|
43028
|
+
import { readdir as readdir8, readFile as readFile23, stat as stat5 } from "fs/promises";
|
|
43029
|
+
import path29 from "path";
|
|
42603
43030
|
async function landingSourceRelPaths(landingDir) {
|
|
42604
43031
|
const rel = [];
|
|
42605
|
-
if (await isFile(
|
|
42606
|
-
const componentsDir =
|
|
43032
|
+
if (await isFile(path29.join(landingDir, "index.astro"))) rel.push("index.astro");
|
|
43033
|
+
const componentsDir = path29.join(landingDir, "_components");
|
|
42607
43034
|
for (const abs of await walkAstro(componentsDir)) {
|
|
42608
|
-
rel.push(
|
|
43035
|
+
rel.push(path29.relative(landingDir, abs).split(path29.sep).join("/"));
|
|
42609
43036
|
}
|
|
42610
43037
|
return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
42611
43038
|
}
|
|
42612
43039
|
async function readLandingSources(landingDir) {
|
|
42613
43040
|
const rel = await landingSourceRelPaths(landingDir);
|
|
42614
43041
|
const out = [];
|
|
42615
|
-
for (const r of rel) out.push({ path: r, text: await
|
|
43042
|
+
for (const r of rel) out.push({ path: r, text: await readFile23(path29.join(landingDir, r), "utf8") });
|
|
42616
43043
|
return out;
|
|
42617
43044
|
}
|
|
42618
43045
|
async function computeLandingSourceSha(landingDir) {
|
|
@@ -42621,7 +43048,7 @@ async function computeLandingSourceSha(landingDir) {
|
|
|
42621
43048
|
for (const r of rel) {
|
|
42622
43049
|
let bytes;
|
|
42623
43050
|
try {
|
|
42624
|
-
bytes = await
|
|
43051
|
+
bytes = await readFile23(path29.join(landingDir, r));
|
|
42625
43052
|
} catch {
|
|
42626
43053
|
bytes = Buffer.alloc(0);
|
|
42627
43054
|
}
|
|
@@ -42645,7 +43072,7 @@ async function walkAstro(dir) {
|
|
|
42645
43072
|
}
|
|
42646
43073
|
const out = [];
|
|
42647
43074
|
for (const entry of entries) {
|
|
42648
|
-
const abs =
|
|
43075
|
+
const abs = path29.join(dir, entry.name);
|
|
42649
43076
|
if (entry.isDirectory()) out.push(...await walkAstro(abs));
|
|
42650
43077
|
else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
|
|
42651
43078
|
}
|
|
@@ -42706,7 +43133,7 @@ var critiqueCommand2 = defineCommand157({
|
|
|
42706
43133
|
{ availableSlugs: await listLandingSlugs(projectRoot) }
|
|
42707
43134
|
);
|
|
42708
43135
|
}
|
|
42709
|
-
if (!await isDir(
|
|
43136
|
+
if (!await isDir(path30.resolve(projectRoot, "src", "pages", slug))) {
|
|
42710
43137
|
fail5("NOT_FOUND", `No landing at src/pages/${slug}/`, {
|
|
42711
43138
|
availableSlugs: await listLandingSlugs(projectRoot)
|
|
42712
43139
|
});
|
|
@@ -42745,7 +43172,7 @@ var critiqueCommand2 = defineCommand157({
|
|
|
42745
43172
|
}
|
|
42746
43173
|
});
|
|
42747
43174
|
async function critiqueOne(projectRoot, slug, brand) {
|
|
42748
|
-
const landingDir =
|
|
43175
|
+
const landingDir = path30.resolve(projectRoot, "src", "pages", slug);
|
|
42749
43176
|
const [sources, sourceSha] = await Promise.all([readLandingSources(landingDir), computeLandingSourceSha(landingDir)]);
|
|
42750
43177
|
const report = critiqueLanding({ slug, sources, brand });
|
|
42751
43178
|
let snapshotFailed = false;
|
|
@@ -42765,7 +43192,7 @@ async function critiqueOne(projectRoot, slug, brand) {
|
|
|
42765
43192
|
}
|
|
42766
43193
|
async function listLandingSlugs(projectRoot) {
|
|
42767
43194
|
try {
|
|
42768
|
-
const entries = await readdir9(
|
|
43195
|
+
const entries = await readdir9(path30.join(projectRoot, "src", "pages"), { withFileTypes: true });
|
|
42769
43196
|
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
42770
43197
|
} catch {
|
|
42771
43198
|
return [];
|
|
@@ -42917,7 +43344,7 @@ var addCommand = defineCommand158({
|
|
|
42917
43344
|
|
|
42918
43345
|
// src/commands/landing/inspiration/code.ts
|
|
42919
43346
|
import { mkdir as mkdir10, writeFile as writeFile14 } from "fs/promises";
|
|
42920
|
-
import
|
|
43347
|
+
import path31 from "path";
|
|
42921
43348
|
import { defineCommand as defineCommand159 } from "citty";
|
|
42922
43349
|
registerSchema({
|
|
42923
43350
|
command: "landing.inspiration.code",
|
|
@@ -42940,9 +43367,9 @@ var codeCommand = defineCommand159({
|
|
|
42940
43367
|
try {
|
|
42941
43368
|
const id = args.id;
|
|
42942
43369
|
const data = await apiGet("/api/landing-inspiration/section-code", { id });
|
|
42943
|
-
const dir =
|
|
43370
|
+
const dir = path31.join(process.cwd(), ".baker", "inspiration", id);
|
|
42944
43371
|
await mkdir10(dir, { recursive: true });
|
|
42945
|
-
const file =
|
|
43372
|
+
const file = path31.join(dir, "section.html");
|
|
42946
43373
|
await writeFile14(file, data.html);
|
|
42947
43374
|
const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
|
|
42948
43375
|
const fidelity = fidelityHint(data.fidelity);
|
|
@@ -42952,7 +43379,7 @@ var codeCommand = defineCommand159({
|
|
|
42952
43379
|
ok: true,
|
|
42953
43380
|
data: {
|
|
42954
43381
|
id,
|
|
42955
|
-
file:
|
|
43382
|
+
file: path31.relative(process.cwd(), file),
|
|
42956
43383
|
bytes: data.html.length,
|
|
42957
43384
|
fidelity: data.fidelity,
|
|
42958
43385
|
reproduction_notes: data.reproductionNotes,
|
|
@@ -43384,7 +43811,7 @@ function classifyCaptureFailure(error) {
|
|
|
43384
43811
|
|
|
43385
43812
|
// src/engine/landing-library/run.ts
|
|
43386
43813
|
import { mkdir as mkdir11, writeFile as writeFile16 } from "fs/promises";
|
|
43387
|
-
import
|
|
43814
|
+
import path33 from "path";
|
|
43388
43815
|
|
|
43389
43816
|
// ../proxy/src/preflight.ts
|
|
43390
43817
|
import http from "http";
|
|
@@ -43614,7 +44041,7 @@ ${used.inheritedSeed}
|
|
|
43614
44041
|
<head>
|
|
43615
44042
|
<meta charset="utf-8">
|
|
43616
44043
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
43617
|
-
<title>Section from ${
|
|
44044
|
+
<title>Section from ${escapeHtml3(extracted.host)}</title>
|
|
43618
44045
|
<style>
|
|
43619
44046
|
${css}
|
|
43620
44047
|
</style>
|
|
@@ -43626,7 +44053,7 @@ ${wrapInLayoutContext(extracted.html, extracted.layoutContext)}
|
|
|
43626
44053
|
`;
|
|
43627
44054
|
return { html, css, assetUrls: extracted.assetUrls, stats: used.stats };
|
|
43628
44055
|
}
|
|
43629
|
-
function
|
|
44056
|
+
function escapeHtml3(value) {
|
|
43630
44057
|
return value.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c] ?? c);
|
|
43631
44058
|
}
|
|
43632
44059
|
var SECTION_ROOT_ATTRIBUTE = "data-baker-section-root";
|
|
@@ -44800,13 +45227,13 @@ async function renderBundleToPng(browser, html, viewportWidth, options = {}) {
|
|
|
44800
45227
|
|
|
44801
45228
|
// src/engine/landing-library/report.ts
|
|
44802
45229
|
import { writeFile as writeFile15 } from "fs/promises";
|
|
44803
|
-
import
|
|
45230
|
+
import path32 from "path";
|
|
44804
45231
|
async function writeCaptureReport(manifest, outDir) {
|
|
44805
|
-
const file =
|
|
45232
|
+
const file = path32.join(outDir, "report.html");
|
|
44806
45233
|
await writeFile15(file, renderReport(manifest));
|
|
44807
45234
|
return file;
|
|
44808
45235
|
}
|
|
44809
|
-
function
|
|
45236
|
+
function escapeHtml4(value) {
|
|
44810
45237
|
return value.replace(
|
|
44811
45238
|
/[&<>"']/g,
|
|
44812
45239
|
(character) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[character] ?? character
|
|
@@ -44831,18 +45258,18 @@ function renderSection3(section) {
|
|
|
44831
45258
|
const shot = (label, src, note) => {
|
|
44832
45259
|
if (!src) return `<figure class="shot empty"><figcaption>${label} \u2014 none</figcaption></figure>`;
|
|
44833
45260
|
return `<figure class="shot">
|
|
44834
|
-
<figcaption>${label}${note ? ` <span class="note">${
|
|
44835
|
-
<a href="${
|
|
45261
|
+
<figcaption>${label}${note ? ` <span class="note">${escapeHtml4(note)}</span>` : ""}</figcaption>
|
|
45262
|
+
<a href="${escapeHtml4(src)}" target="_blank" rel="noopener"><img src="${escapeHtml4(src)}" alt="" loading="lazy"></a>
|
|
44836
45263
|
</figure>`;
|
|
44837
45264
|
};
|
|
44838
45265
|
return `<section class="card">
|
|
44839
45266
|
<header>
|
|
44840
45267
|
<h2><span class="index">${String(section.index).padStart(2, "0")}</span> ${section.rect.width}\xD7${section.rect.height}</h2>
|
|
44841
45268
|
<span class="badge ${tone}">fidelity ${formatFidelity(section.fidelity)}</span>
|
|
44842
|
-
${section.fidelityNote ? `<span class="badge warn">${
|
|
44843
|
-
${section.motion.hasMotion ? `<span class="badge motion">${
|
|
45269
|
+
${section.fidelityNote ? `<span class="badge warn">${escapeHtml4(section.fidelityNote)}</span>` : ""}
|
|
45270
|
+
${section.motion.hasMotion ? `<span class="badge motion">${escapeHtml4(section.motion.summary)}</span>` : ""}
|
|
44844
45271
|
</header>
|
|
44845
|
-
<p class="preview">${
|
|
45272
|
+
<p class="preview">${escapeHtml4(section.textPreview.slice(0, 220)) || "<em>no text</em>"}</p>
|
|
44846
45273
|
<div class="shots">
|
|
44847
45274
|
${shot("Live", section.desktopShot)}
|
|
44848
45275
|
${shot("Reproduction", rendered, "rendered from the extracted bundle")}
|
|
@@ -44850,8 +45277,8 @@ function renderSection3(section) {
|
|
|
44850
45277
|
</div>
|
|
44851
45278
|
${section.motionFilmstrip ? `<div class="filmstrip">${shot("Motion \u2014 six frames, left to right", section.motionFilmstrip)}</div>` : ""}
|
|
44852
45279
|
<footer>
|
|
44853
|
-
<code>${
|
|
44854
|
-
${section.bundle ? `<a href="${
|
|
45280
|
+
<code>${escapeHtml4(section.selector)}</code>
|
|
45281
|
+
${section.bundle ? `<a href="${escapeHtml4(section.bundle)}" target="_blank" rel="noopener">open the standalone bundle \u2192</a>` : ""}
|
|
44855
45282
|
</footer>
|
|
44856
45283
|
</section>`;
|
|
44857
45284
|
}
|
|
@@ -44863,7 +45290,7 @@ function renderReport(manifest) {
|
|
|
44863
45290
|
<head>
|
|
44864
45291
|
<meta charset="utf-8">
|
|
44865
45292
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
44866
|
-
<title>Capture report \u2014 ${
|
|
45293
|
+
<title>Capture report \u2014 ${escapeHtml4(manifest.title || manifest.url)}</title>
|
|
44867
45294
|
<style>
|
|
44868
45295
|
:root { color-scheme: light dark; --line: #e5e7eb; --muted: #6b7280; --bg: #fafafa; --card: #fff; }
|
|
44869
45296
|
@media (prefers-color-scheme: dark) {
|
|
@@ -44905,8 +45332,8 @@ function renderReport(manifest) {
|
|
|
44905
45332
|
</style>
|
|
44906
45333
|
</head>
|
|
44907
45334
|
<body>
|
|
44908
|
-
<h1>${
|
|
44909
|
-
<p class="sub"><a href="${
|
|
45335
|
+
<h1>${escapeHtml4(manifest.title || "Capture report")}</h1>
|
|
45336
|
+
<p class="sub"><a href="${escapeHtml4(manifest.finalUrl)}" target="_blank" rel="noopener">${escapeHtml4(manifest.finalUrl)}</a> \xB7 captured ${escapeHtml4(manifest.capturedAt)}</p>
|
|
44910
45337
|
|
|
44911
45338
|
<div class="stats">
|
|
44912
45339
|
<div class="stat"><b>${manifest.sections.length}</b><span>sections</span></div>
|
|
@@ -44981,32 +45408,32 @@ async function reproducePage(args) {
|
|
|
44981
45408
|
const { browser, page, outDir, pageUrl, livePageShot } = args;
|
|
44982
45409
|
const built = await buildSectionBundle(page, "body", pageUrl).catch(() => null);
|
|
44983
45410
|
if (!built) return { bundle: null, fidelity: null };
|
|
44984
|
-
await writeFile16(
|
|
45411
|
+
await writeFile16(path33.join(outDir, "page.html"), built.html);
|
|
44985
45412
|
const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width, {
|
|
44986
45413
|
wholePage: true,
|
|
44987
45414
|
timeoutMs: 6e4
|
|
44988
45415
|
});
|
|
44989
45416
|
if (!rendered || !livePageShot) return { bundle: "page.html", fidelity: null };
|
|
44990
|
-
await writeFile16(
|
|
45417
|
+
await writeFile16(path33.join(outDir, "page-rendered.png"), rendered);
|
|
44991
45418
|
const { score, note } = await scoreFidelity(livePageShot, rendered);
|
|
44992
45419
|
return { bundle: "page.html", fidelity: score, ...note ? { fidelityNote: note } : {} };
|
|
44993
45420
|
}
|
|
44994
45421
|
async function captureOneSection(args) {
|
|
44995
45422
|
const { browser, page, candidate, sectionsDir, outDir, pageUrl, withCode } = args;
|
|
44996
|
-
const dir =
|
|
45423
|
+
const dir = path33.join(sectionsDir, String(candidate.index).padStart(2, "0"));
|
|
44997
45424
|
await mkdir11(dir, { recursive: true });
|
|
44998
45425
|
const desktop = await captureSection(page, candidate);
|
|
44999
|
-
if (desktop) await writeFile16(
|
|
45426
|
+
if (desktop) await writeFile16(path33.join(dir, "desktop.png"), desktop);
|
|
45000
45427
|
const visualHash = desktop ? await perceptualHash(desktop) : null;
|
|
45001
45428
|
const motion = await collectMotion(page, candidate.selector);
|
|
45002
45429
|
const built = withCode ? await buildSectionBundle(page, candidate.selector, pageUrl) : null;
|
|
45003
45430
|
let fidelity = null;
|
|
45004
45431
|
let fidelityNote;
|
|
45005
45432
|
if (built) {
|
|
45006
|
-
await writeFile16(
|
|
45433
|
+
await writeFile16(path33.join(dir, "section.html"), built.html);
|
|
45007
45434
|
const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width);
|
|
45008
45435
|
if (rendered && desktop) {
|
|
45009
|
-
await writeFile16(
|
|
45436
|
+
await writeFile16(path33.join(dir, "section-rendered.png"), rendered);
|
|
45010
45437
|
const result = await scoreFidelity(desktop, rendered);
|
|
45011
45438
|
fidelity = result.score;
|
|
45012
45439
|
fidelityNote = result.note;
|
|
@@ -45014,9 +45441,9 @@ async function captureOneSection(args) {
|
|
|
45014
45441
|
}
|
|
45015
45442
|
return {
|
|
45016
45443
|
...candidate,
|
|
45017
|
-
desktopShot: desktop ?
|
|
45444
|
+
desktopShot: desktop ? path33.relative(outDir, path33.join(dir, "desktop.png")) : null,
|
|
45018
45445
|
mobileShot: null,
|
|
45019
|
-
bundle: built ?
|
|
45446
|
+
bundle: built ? path33.relative(outDir, path33.join(dir, "section.html")) : null,
|
|
45020
45447
|
fidelity,
|
|
45021
45448
|
...fidelityNote ? { fidelityNote } : {},
|
|
45022
45449
|
...built ? { cssStats: built.stats } : {},
|
|
@@ -45035,9 +45462,9 @@ async function captureMobileShots(args) {
|
|
|
45035
45462
|
for (const section of sections) {
|
|
45036
45463
|
const shot = await captureSectionOnMobile(mobile.page, section);
|
|
45037
45464
|
if (!shot) continue;
|
|
45038
|
-
const file =
|
|
45465
|
+
const file = path33.join(sectionsDir, String(section.index).padStart(2, "0"), "mobile.png");
|
|
45039
45466
|
await writeFile16(file, shot);
|
|
45040
|
-
section.mobileShot =
|
|
45467
|
+
section.mobileShot = path33.relative(outDir, file);
|
|
45041
45468
|
}
|
|
45042
45469
|
} finally {
|
|
45043
45470
|
await mobile.context.close();
|
|
@@ -45052,10 +45479,10 @@ async function captureMotionTakes(args) {
|
|
|
45052
45479
|
const filmOne = async (section) => {
|
|
45053
45480
|
const take = await captureMotionTake(browser, pageUrl, section.selector).catch(() => null);
|
|
45054
45481
|
if (!take) return;
|
|
45055
|
-
const dir =
|
|
45056
|
-
const file =
|
|
45482
|
+
const dir = path33.join(sectionsDir, String(section.index).padStart(2, "0"));
|
|
45483
|
+
const file = path33.join(dir, "motion-filmstrip.png");
|
|
45057
45484
|
await writeFile16(file, take.filmstrip);
|
|
45058
|
-
section.motionFilmstrip =
|
|
45485
|
+
section.motionFilmstrip = path33.relative(outDir, file);
|
|
45059
45486
|
log(` [${section.index}] ${section.motion.summary}`);
|
|
45060
45487
|
};
|
|
45061
45488
|
const queue = [...moving];
|
|
@@ -45112,7 +45539,7 @@ async function captureAlternateViews(args) {
|
|
|
45112
45539
|
async function reproduceWholePage(args) {
|
|
45113
45540
|
const { browser, page, outDir, pageUrl, withCode, log } = args;
|
|
45114
45541
|
const fullPage = await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
|
|
45115
|
-
if (fullPage) await writeFile16(
|
|
45542
|
+
if (fullPage) await writeFile16(path33.join(outDir, "full-page.png"), fullPage);
|
|
45116
45543
|
if (!withCode) return { bundle: null, fidelity: null };
|
|
45117
45544
|
const reproduction = await reproducePage({ browser, page, outDir, pageUrl, livePageShot: fullPage });
|
|
45118
45545
|
log(`page reproduction: ${reproduction.fidelity === null ? "unavailable" : reproduction.fidelity.toFixed(2)}`);
|
|
@@ -45183,7 +45610,7 @@ async function openViaLadder(args) {
|
|
|
45183
45610
|
async function scrapeLanding(options) {
|
|
45184
45611
|
const timeoutMs = options.timeoutMs ?? 45e3;
|
|
45185
45612
|
const log = options.onProgress ?? (() => void 0);
|
|
45186
|
-
const sectionsDir =
|
|
45613
|
+
const sectionsDir = path33.join(options.outDir, "sections");
|
|
45187
45614
|
const nonPublic = refuseNonPublicUrl(options.url);
|
|
45188
45615
|
if (nonPublic) {
|
|
45189
45616
|
throw new BlockedPageError({
|
|
@@ -45245,7 +45672,7 @@ async function scrapeLanding(options) {
|
|
|
45245
45672
|
security: prepared.security,
|
|
45246
45673
|
captureTier: tier
|
|
45247
45674
|
};
|
|
45248
|
-
await writeFile16(
|
|
45675
|
+
await writeFile16(path33.join(options.outDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
45249
45676
|
`);
|
|
45250
45677
|
if (options.report !== false) {
|
|
45251
45678
|
const reportPath = await writeCaptureReport(manifest, options.outDir);
|
|
@@ -45260,28 +45687,28 @@ async function scrapeLanding(options) {
|
|
|
45260
45687
|
|
|
45261
45688
|
// src/commands/landing/inspiration/captureOut.ts
|
|
45262
45689
|
import { existsSync as existsSync9 } from "fs";
|
|
45263
|
-
import
|
|
45690
|
+
import path34 from "path";
|
|
45264
45691
|
var SCRATCH_DIR = ".baker";
|
|
45265
45692
|
function isWithin(parent, target) {
|
|
45266
|
-
const relative =
|
|
45267
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
45693
|
+
const relative = path34.relative(parent, target);
|
|
45694
|
+
return relative === "" || !relative.startsWith("..") && !path34.isAbsolute(relative);
|
|
45268
45695
|
}
|
|
45269
45696
|
function findRepoRoot(from) {
|
|
45270
|
-
let dir =
|
|
45697
|
+
let dir = path34.resolve(from);
|
|
45271
45698
|
for (; ; ) {
|
|
45272
|
-
if (existsSync9(
|
|
45273
|
-
const parent =
|
|
45699
|
+
if (existsSync9(path34.join(dir, ".git"))) return dir;
|
|
45700
|
+
const parent = path34.dirname(dir);
|
|
45274
45701
|
if (parent === dir) return null;
|
|
45275
45702
|
dir = parent;
|
|
45276
45703
|
}
|
|
45277
45704
|
}
|
|
45278
45705
|
function checkCaptureOut(out, options) {
|
|
45279
45706
|
const { cwd, repoRoot } = options;
|
|
45280
|
-
const resolved =
|
|
45707
|
+
const resolved = path34.resolve(cwd, out);
|
|
45281
45708
|
if (repoRoot === null || !isWithin(repoRoot, resolved)) return { ok: true };
|
|
45282
|
-
const scratch =
|
|
45709
|
+
const scratch = path34.join(repoRoot, SCRATCH_DIR);
|
|
45283
45710
|
if (isWithin(scratch, resolved)) return { ok: true };
|
|
45284
|
-
const suggestion =
|
|
45711
|
+
const suggestion = path34.posix.join(SCRATCH_DIR, "teardowns", path34.basename(resolved) || "capture");
|
|
45285
45712
|
return {
|
|
45286
45713
|
ok: false,
|
|
45287
45714
|
error: {
|
|
@@ -45455,12 +45882,12 @@ var scrapeCommand = defineCommand162({
|
|
|
45455
45882
|
});
|
|
45456
45883
|
|
|
45457
45884
|
// src/commands/landing/inspiration/search.ts
|
|
45458
|
-
import
|
|
45885
|
+
import path36 from "path";
|
|
45459
45886
|
import { defineCommand as defineCommand163 } from "citty";
|
|
45460
45887
|
|
|
45461
45888
|
// src/commands/landing/inspiration/shot.ts
|
|
45462
45889
|
import { mkdir as mkdir12, writeFile as writeFile17 } from "fs/promises";
|
|
45463
|
-
import
|
|
45890
|
+
import path35 from "path";
|
|
45464
45891
|
import sharp6 from "sharp";
|
|
45465
45892
|
var READABLE_SHOT = {
|
|
45466
45893
|
maxWidth: 1440,
|
|
@@ -45486,9 +45913,9 @@ async function downloadReadableShot(url, file) {
|
|
|
45486
45913
|
const response = await fetch(url);
|
|
45487
45914
|
if (!response.ok) return null;
|
|
45488
45915
|
const shot = await toReadableShot(Buffer.from(await response.arrayBuffer()));
|
|
45489
|
-
await mkdir12(
|
|
45916
|
+
await mkdir12(path35.dirname(file), { recursive: true });
|
|
45490
45917
|
await writeFile17(file, shot);
|
|
45491
|
-
return
|
|
45918
|
+
return path35.relative(process.cwd(), file);
|
|
45492
45919
|
} catch {
|
|
45493
45920
|
return null;
|
|
45494
45921
|
}
|
|
@@ -45580,13 +46007,13 @@ function buildSearchBody(args) {
|
|
|
45580
46007
|
return body;
|
|
45581
46008
|
}
|
|
45582
46009
|
async function downloadShots(results) {
|
|
45583
|
-
const dir =
|
|
46010
|
+
const dir = path36.join(process.cwd(), ".baker", "inspiration");
|
|
45584
46011
|
const saved = /* @__PURE__ */ new Map();
|
|
45585
46012
|
await Promise.all(
|
|
45586
46013
|
results.map(async (result) => {
|
|
45587
46014
|
const file = await downloadReadableShot(
|
|
45588
46015
|
result.desktopShotUrl,
|
|
45589
|
-
|
|
46016
|
+
path36.join(dir, `${result.id}.${READABLE_SHOT.extension}`)
|
|
45590
46017
|
);
|
|
45591
46018
|
if (file) saved.set(result.id, file);
|
|
45592
46019
|
})
|
|
@@ -45814,7 +46241,7 @@ var sequencesCommand = defineCommand164({
|
|
|
45814
46241
|
});
|
|
45815
46242
|
|
|
45816
46243
|
// src/commands/landing/inspiration/view.ts
|
|
45817
|
-
import
|
|
46244
|
+
import path37 from "path";
|
|
45818
46245
|
import { defineCommand as defineCommand165 } from "citty";
|
|
45819
46246
|
registerSchema({
|
|
45820
46247
|
command: "landing.inspiration.view",
|
|
@@ -45847,12 +46274,12 @@ var viewCommand2 = defineCommand165({
|
|
|
45847
46274
|
const id = args.id;
|
|
45848
46275
|
const data = await apiGet("/api/landing-inspiration/section", { id });
|
|
45849
46276
|
const section = data.section;
|
|
45850
|
-
const dir =
|
|
46277
|
+
const dir = path37.join(process.cwd(), ".baker", "inspiration", id);
|
|
45851
46278
|
const ext = READABLE_SHOT.extension;
|
|
45852
46279
|
const [desktop, mobile, filmstrip] = await Promise.all([
|
|
45853
|
-
downloadReadableShot(section.desktopShotUrl,
|
|
45854
|
-
downloadReadableShot(section.mobileShotUrl,
|
|
45855
|
-
downloadReadableShot(section.motionFilmstripUrl,
|
|
46280
|
+
downloadReadableShot(section.desktopShotUrl, path37.join(dir, `desktop.${ext}`)),
|
|
46281
|
+
downloadReadableShot(section.mobileShotUrl, path37.join(dir, `mobile.${ext}`)),
|
|
46282
|
+
downloadReadableShot(section.motionFilmstripUrl, path37.join(dir, `motion-filmstrip.${ext}`))
|
|
45856
46283
|
]);
|
|
45857
46284
|
const full = args.full;
|
|
45858
46285
|
const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
|
|
@@ -47701,8 +48128,8 @@ var listCommand15 = defineCommand184({
|
|
|
47701
48128
|
});
|
|
47702
48129
|
|
|
47703
48130
|
// src/commands/scheduled-actions/templates.ts
|
|
47704
|
-
import { readFile as
|
|
47705
|
-
import
|
|
48131
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
48132
|
+
import path38 from "path";
|
|
47706
48133
|
import { defineCommand as defineCommand185 } from "citty";
|
|
47707
48134
|
registerSchema({
|
|
47708
48135
|
command: "scheduled-actions.templates",
|
|
@@ -47805,7 +48232,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
|
|
|
47805
48232
|
}
|
|
47806
48233
|
if (save.length > 0) {
|
|
47807
48234
|
const briefFile = flag("brief-file");
|
|
47808
|
-
const brief = briefFile.length > 0 ? await
|
|
48235
|
+
const brief = briefFile.length > 0 ? await readFile24(path38.resolve(briefFile), "utf8") : flag("brief");
|
|
47809
48236
|
if (brief.trim().length === 0) {
|
|
47810
48237
|
failValidation4("--brief-file (preferred) or --brief is required: the brief is the recipe.");
|
|
47811
48238
|
}
|
|
@@ -48276,7 +48703,7 @@ function parseImageRefs(spec) {
|
|
|
48276
48703
|
}
|
|
48277
48704
|
var defaultDeps = {
|
|
48278
48705
|
ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
|
|
48279
|
-
upload: (
|
|
48706
|
+
upload: (path40) => uploadLocalImage({ file: path40, contentType: detectImageContentType(path40), source: "uploaded" })
|
|
48280
48707
|
};
|
|
48281
48708
|
async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
48282
48709
|
const refs = parseImageRefs(spec);
|
|
@@ -48296,10 +48723,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
|
48296
48723
|
}
|
|
48297
48724
|
return { imageIds, added };
|
|
48298
48725
|
}
|
|
48299
|
-
function uploadFailure(
|
|
48726
|
+
function uploadFailure(path40) {
|
|
48300
48727
|
return (error) => {
|
|
48301
48728
|
if (error instanceof ApiError) throw error;
|
|
48302
|
-
throw new ApiError("VALIDATION_ERROR", `Could not read "${
|
|
48729
|
+
throw new ApiError("VALIDATION_ERROR", `Could not read "${path40}" as an image.`);
|
|
48303
48730
|
};
|
|
48304
48731
|
}
|
|
48305
48732
|
|
|
@@ -49371,10 +49798,10 @@ async function stageOp4(op) {
|
|
|
49371
49798
|
handleError5(err);
|
|
49372
49799
|
}
|
|
49373
49800
|
}
|
|
49374
|
-
async function draftAction3(
|
|
49801
|
+
async function draftAction3(path40, body, chat) {
|
|
49375
49802
|
const chatId = resolveChatId(chat);
|
|
49376
49803
|
try {
|
|
49377
|
-
const data = await apiPost(
|
|
49804
|
+
const data = await apiPost(path40, { chatId, ...body });
|
|
49378
49805
|
writeJsonEnvelope({ ok: true, data });
|
|
49379
49806
|
return data;
|
|
49380
49807
|
} catch (err) {
|
|
@@ -50479,9 +50906,9 @@ var groupCommand2 = defineCommand210({
|
|
|
50479
50906
|
});
|
|
50480
50907
|
|
|
50481
50908
|
// src/commands/videos/ingest.ts
|
|
50482
|
-
import { mkdtemp as
|
|
50483
|
-
import { tmpdir as
|
|
50484
|
-
import
|
|
50909
|
+
import { mkdtemp as mkdtemp3, rm as rm8, stat as stat7 } from "fs/promises";
|
|
50910
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
50911
|
+
import path39 from "path";
|
|
50485
50912
|
import { defineCommand as defineCommand211 } from "citty";
|
|
50486
50913
|
|
|
50487
50914
|
// src/lib/streamUpload.ts
|
|
@@ -50832,7 +51259,7 @@ function ingestUrl(args) {
|
|
|
50832
51259
|
}
|
|
50833
51260
|
async function downloadThenIngest(args, country) {
|
|
50834
51261
|
const vimeoCookie = captureVimeoCookie();
|
|
50835
|
-
const workDir = await
|
|
51262
|
+
const workDir = await mkdtemp3(path39.join(tmpdir4(), "videos-ingest-"));
|
|
50836
51263
|
try {
|
|
50837
51264
|
const probe = await probeYtDlp({ url: args.url, country, vimeoCookie, cookieDir: workDir });
|
|
50838
51265
|
if (isAudioOnly(probe.info)) {
|
|
@@ -50889,7 +51316,7 @@ async function downloadThenIngest(args, country) {
|
|
|
50889
51316
|
...reducedQuality ? { reducedQuality: true } : {}
|
|
50890
51317
|
};
|
|
50891
51318
|
} finally {
|
|
50892
|
-
await
|
|
51319
|
+
await rm8(workDir, { recursive: true, force: true }).catch(() => {
|
|
50893
51320
|
});
|
|
50894
51321
|
}
|
|
50895
51322
|
}
|
|
@@ -50968,7 +51395,7 @@ var searchCommand4 = defineCommand212({
|
|
|
50968
51395
|
var tagsCommand6 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
50969
51396
|
|
|
50970
51397
|
// src/commands/videos/upload.ts
|
|
50971
|
-
import { readFile as
|
|
51398
|
+
import { readFile as readFile25, stat as stat8 } from "fs/promises";
|
|
50972
51399
|
import { basename as basename3, extname as extname4 } from "path";
|
|
50973
51400
|
import { defineCommand as defineCommand213 } from "citty";
|
|
50974
51401
|
var MIME_MAP = {
|
|
@@ -51065,7 +51492,7 @@ var uploadCommand2 = defineCommand213({
|
|
|
51065
51492
|
originalFilename,
|
|
51066
51493
|
descriptionContext
|
|
51067
51494
|
});
|
|
51068
|
-
const fileBuffer = await
|
|
51495
|
+
const fileBuffer = await readFile25(filePath);
|
|
51069
51496
|
const uploadResponse = await fetch(uploadUrl, {
|
|
51070
51497
|
method: "PUT",
|
|
51071
51498
|
headers: { "Content-Type": contentType },
|
|
@@ -52452,7 +52879,7 @@ function unknownFlagEnvelope(unknown, commandPath, suggestion) {
|
|
|
52452
52879
|
};
|
|
52453
52880
|
}
|
|
52454
52881
|
function commandPathOf(root, argv) {
|
|
52455
|
-
const
|
|
52882
|
+
const path40 = [];
|
|
52456
52883
|
let command = root;
|
|
52457
52884
|
for (const token of argv) {
|
|
52458
52885
|
if (token === "--" || token.startsWith("-")) {
|
|
@@ -52463,10 +52890,10 @@ function commandPathOf(root, argv) {
|
|
|
52463
52890
|
if (next === void 0 || typeof next !== "object") {
|
|
52464
52891
|
break;
|
|
52465
52892
|
}
|
|
52466
|
-
|
|
52893
|
+
path40.push(token);
|
|
52467
52894
|
command = next;
|
|
52468
52895
|
}
|
|
52469
|
-
return
|
|
52896
|
+
return path40.join(" ");
|
|
52470
52897
|
}
|
|
52471
52898
|
function refuseUnknownFlags(root, argv) {
|
|
52472
52899
|
const unknown = findUnknownFlags(root, argv);
|