@koda-sl/baker-cli 0.114.0-dev.249eaa8ed → 0.115.0-dev.3bcc79f9c
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 +61 -0
- package/dist/{chunk-5AWO4BHJ.js → chunk-OCMOQOIJ.js} +401 -69
- package/dist/chunk-OCMOQOIJ.js.map +1 -0
- package/dist/cli.js +551 -108
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +44 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5AWO4BHJ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
BackendClient,
|
|
3
4
|
ELEVENLABS_MAX_MUSIC_LENGTH_MS,
|
|
4
5
|
IMAGE_GENERATE_MODELS,
|
|
5
6
|
LayerExecutionError,
|
|
6
7
|
MODEL_REGISTRY,
|
|
7
8
|
SEEDANCE_DURATIONS,
|
|
8
9
|
ValidationError,
|
|
10
|
+
collectAssetRefLikes,
|
|
9
11
|
createEngineFromEnv,
|
|
10
12
|
defaultRegistry,
|
|
11
13
|
describeFailureReason,
|
|
12
14
|
generateCatalog,
|
|
15
|
+
isPersistedAssetRef,
|
|
16
|
+
requireCredentialsFromEnv,
|
|
13
17
|
resolveConcurrency,
|
|
18
|
+
sha256Hex,
|
|
19
|
+
ulid,
|
|
14
20
|
validateCanvasDeep
|
|
15
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-OCMOQOIJ.js";
|
|
16
22
|
|
|
17
23
|
// src/cli.ts
|
|
18
24
|
import { defineCommand as defineCommand157, runMain } from "citty";
|
|
@@ -151,9 +157,9 @@ async function handleResponse(response) {
|
|
|
151
157
|
throw new ApiError("INTERNAL_ERROR", "Failed to parse API response as JSON");
|
|
152
158
|
}
|
|
153
159
|
}
|
|
154
|
-
async function apiGet(
|
|
160
|
+
async function apiGet(path14, params) {
|
|
155
161
|
const env = getEnv();
|
|
156
|
-
const url = new URL(
|
|
162
|
+
const url = new URL(path14, env.BAKER_API_URL);
|
|
157
163
|
if (params) {
|
|
158
164
|
const clean = sanitizeParams(params);
|
|
159
165
|
for (const [key, value] of Object.entries(clean)) {
|
|
@@ -178,12 +184,12 @@ async function apiGet(path12, params) {
|
|
|
178
184
|
}
|
|
179
185
|
return handleResponse(response);
|
|
180
186
|
}
|
|
181
|
-
async function apiPost(
|
|
187
|
+
async function apiPost(path14, body, opts) {
|
|
182
188
|
const env = getEnv();
|
|
183
189
|
const timeoutMs = opts?.timeoutMs ?? 6e4;
|
|
184
190
|
let response;
|
|
185
191
|
try {
|
|
186
|
-
response = await fetchWithRateLimitRetry(new URL(
|
|
192
|
+
response = await fetchWithRateLimitRetry(new URL(path14, env.BAKER_API_URL).toString(), {
|
|
187
193
|
method: "POST",
|
|
188
194
|
headers: {
|
|
189
195
|
Authorization: `Bearer ${env.BAKER_API_KEY}`,
|
|
@@ -2875,31 +2881,31 @@ function cachePath(category, key) {
|
|
|
2875
2881
|
return join2(dir, `${hashKey(key)}.json`);
|
|
2876
2882
|
}
|
|
2877
2883
|
function cacheGet(category, key) {
|
|
2878
|
-
const
|
|
2879
|
-
if (!existsSync2(
|
|
2884
|
+
const path14 = cachePath(category, key);
|
|
2885
|
+
if (!existsSync2(path14)) {
|
|
2880
2886
|
return null;
|
|
2881
2887
|
}
|
|
2882
2888
|
try {
|
|
2883
|
-
const raw = readFileSync2(
|
|
2889
|
+
const raw = readFileSync2(path14, "utf-8");
|
|
2884
2890
|
const entry = JSON.parse(raw);
|
|
2885
2891
|
if (entry.expiresAt < Date.now()) {
|
|
2886
|
-
rmSync(
|
|
2892
|
+
rmSync(path14, { force: true });
|
|
2887
2893
|
return null;
|
|
2888
2894
|
}
|
|
2889
2895
|
return entry;
|
|
2890
2896
|
} catch {
|
|
2891
|
-
rmSync(
|
|
2897
|
+
rmSync(path14, { force: true });
|
|
2892
2898
|
return null;
|
|
2893
2899
|
}
|
|
2894
2900
|
}
|
|
2895
2901
|
function cacheSet(category, key, data, ttlMs, fields) {
|
|
2896
|
-
const
|
|
2902
|
+
const path14 = cachePath(category, key);
|
|
2897
2903
|
const entry = {
|
|
2898
2904
|
expiresAt: Date.now() + ttlMs,
|
|
2899
2905
|
data,
|
|
2900
2906
|
fields
|
|
2901
2907
|
};
|
|
2902
|
-
writeFileSync(
|
|
2908
|
+
writeFileSync(path14, JSON.stringify(entry), "utf-8");
|
|
2903
2909
|
}
|
|
2904
2910
|
var HOUR = 60 * 60 * 1e3;
|
|
2905
2911
|
var MINUTE = 60 * 1e3;
|
|
@@ -4226,7 +4232,11 @@ var responsiveDisplayAdSchema = z8.object({
|
|
|
4226
4232
|
longHeadline: z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.longHeadlineTextMax) }),
|
|
4227
4233
|
descriptions: z8.array(z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.descriptionTextMax) })).min(1).max(5),
|
|
4228
4234
|
businessName: z8.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.businessNameMax),
|
|
4235
|
+
// A Responsive Display Ad's images are fields on the ad's own content (never campaign-level
|
|
4236
|
+
// asset links). Google requires ≥1 landscape marketing image (1.91:1) AND ≥1 square marketing
|
|
4237
|
+
// image (1:1) to serve; the logo images are optional.
|
|
4229
4238
|
marketingImageAssets: z8.array(refSchema).optional(),
|
|
4239
|
+
squareMarketingImageAssets: z8.array(refSchema).optional(),
|
|
4230
4240
|
logoImageAssets: z8.array(refSchema).optional(),
|
|
4231
4241
|
finalUrls: z8.array(httpsUrlSchema2).min(1)
|
|
4232
4242
|
});
|
|
@@ -4411,7 +4421,11 @@ var adScheduleCriterionSchema = z8.object({
|
|
|
4411
4421
|
var deviceCriterionSchema = z8.object({
|
|
4412
4422
|
criterionType: z8.literal("device"),
|
|
4413
4423
|
device: z8.enum(DEVICE_TYPES),
|
|
4414
|
-
|
|
4424
|
+
// Google's `CampaignCriterion.bid_modifier`: "The modifier must be in the range 0.1 - 10.0. Use 0
|
|
4425
|
+
// to opt out of a Device type." So 0 (exclude the device) and 0.1–10.0 are valid; the (0, 0.1) gap is not.
|
|
4426
|
+
bidModifier: z8.number().min(0).max(10).optional().refine((v) => v === void 0 || v === 0 || v >= 0.1, {
|
|
4427
|
+
message: "bid modifier must be 0 (exclude the device) or between 0.1 and 10.0"
|
|
4428
|
+
})
|
|
4415
4429
|
});
|
|
4416
4430
|
var campaignCriterionAddSchema = z8.object({
|
|
4417
4431
|
campaign: refSchema,
|
|
@@ -4422,6 +4436,15 @@ var campaignCriterionAddSchema = z8.object({
|
|
|
4422
4436
|
adScheduleCriterionSchema,
|
|
4423
4437
|
deviceCriterionSchema
|
|
4424
4438
|
])
|
|
4439
|
+
}).superRefine((val, ctx) => {
|
|
4440
|
+
const c = val.criterion;
|
|
4441
|
+
if (c.criterionType === "adSchedule" && c.endHour === 24 && c.endMinute !== "ZERO") {
|
|
4442
|
+
ctx.addIssue({
|
|
4443
|
+
code: z8.ZodIssueCode.custom,
|
|
4444
|
+
message: "endHour 24 (midnight) cannot have a non-zero endMinute",
|
|
4445
|
+
path: ["criterion", "endMinute"]
|
|
4446
|
+
});
|
|
4447
|
+
}
|
|
4425
4448
|
});
|
|
4426
4449
|
var GOOGLE_DRAFT_OP_KINDS = [
|
|
4427
4450
|
"google.budget.create",
|
|
@@ -4595,7 +4618,8 @@ var googleDraftStatusNodeSchema = z9.lazy(
|
|
|
4595
4618
|
operation: googleDraftChangeOperationSchema.optional(),
|
|
4596
4619
|
existing: z9.boolean(),
|
|
4597
4620
|
collections: z9.array(googleDraftStatusCollectionSchema),
|
|
4598
|
-
children: z9.array(googleDraftStatusNodeSchema)
|
|
4621
|
+
children: z9.array(googleDraftStatusNodeSchema),
|
|
4622
|
+
warnings: z9.array(z9.string()).optional()
|
|
4599
4623
|
})
|
|
4600
4624
|
);
|
|
4601
4625
|
var googleDraftListResponseSchema = z9.object({
|
|
@@ -4663,6 +4687,9 @@ function collectionLine(collection) {
|
|
|
4663
4687
|
function renderNode(node, depth, lines) {
|
|
4664
4688
|
const indent = " ".repeat(depth);
|
|
4665
4689
|
lines.push(`${indent}\u2022 ${node.name} \xB7 ${badge(node)}`);
|
|
4690
|
+
for (const warning of node.warnings ?? []) {
|
|
4691
|
+
lines.push(`${indent} \u26A0 ${warning}`);
|
|
4692
|
+
}
|
|
4666
4693
|
for (const collection of node.collections) {
|
|
4667
4694
|
const line = collectionLine(collection);
|
|
4668
4695
|
if (line) {
|
|
@@ -4791,11 +4818,11 @@ function rawTextEntries(value) {
|
|
|
4791
4818
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
4792
4819
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
4793
4820
|
}
|
|
4794
|
-
function rawFileEntries(
|
|
4795
|
-
if (typeof
|
|
4821
|
+
function rawFileEntries(path14) {
|
|
4822
|
+
if (typeof path14 !== "string" || path14.length === 0) {
|
|
4796
4823
|
return [];
|
|
4797
4824
|
}
|
|
4798
|
-
return readFileSync3(
|
|
4825
|
+
return readFileSync3(path14, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
4799
4826
|
}
|
|
4800
4827
|
function keywordEntries(args) {
|
|
4801
4828
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -4818,19 +4845,19 @@ function keywordEntries(args) {
|
|
|
4818
4845
|
}
|
|
4819
4846
|
return entries;
|
|
4820
4847
|
}
|
|
4821
|
-
function loadJsonFileArg(
|
|
4822
|
-
if (typeof
|
|
4848
|
+
function loadJsonFileArg(path14) {
|
|
4849
|
+
if (typeof path14 !== "string" || path14.length === 0) {
|
|
4823
4850
|
return {};
|
|
4824
4851
|
}
|
|
4825
4852
|
try {
|
|
4826
|
-
const parsed = JSON.parse(readFileSync3(
|
|
4853
|
+
const parsed = JSON.parse(readFileSync3(path14, "utf8"));
|
|
4827
4854
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4828
|
-
failWriteValidation(`${
|
|
4855
|
+
failWriteValidation(`${path14} must contain a JSON object`);
|
|
4829
4856
|
}
|
|
4830
4857
|
return parsed;
|
|
4831
4858
|
} catch (err) {
|
|
4832
4859
|
if (err instanceof SyntaxError) {
|
|
4833
|
-
failWriteValidation(`${
|
|
4860
|
+
failWriteValidation(`${path14} is not valid JSON: ${err.message}`);
|
|
4834
4861
|
}
|
|
4835
4862
|
throw err;
|
|
4836
4863
|
}
|
|
@@ -4919,10 +4946,10 @@ async function stageUpdate(kind, customerId, target, payload) {
|
|
|
4919
4946
|
async function stageTarget(kind, customerId, target) {
|
|
4920
4947
|
await stageGoogleOp({ kind, customerId, target });
|
|
4921
4948
|
}
|
|
4922
|
-
async function draftAction(
|
|
4949
|
+
async function draftAction(path14, body) {
|
|
4923
4950
|
try {
|
|
4924
4951
|
const chatId = requireChatId();
|
|
4925
|
-
const response = await apiPost(
|
|
4952
|
+
const response = await apiPost(path14, { chatId, ...body });
|
|
4926
4953
|
writeJsonEnvelope(response);
|
|
4927
4954
|
} catch (err) {
|
|
4928
4955
|
handleGoogleError(err);
|
|
@@ -5798,8 +5825,9 @@ function applyAutoFixes(query, limit) {
|
|
|
5798
5825
|
if (/FROM\s+campaign_budget\b/i.test(corrected)) {
|
|
5799
5826
|
const whereClause = corrected.split(/\bWHERE\b/i)[1] ?? "";
|
|
5800
5827
|
const selectClause = corrected.split(/\bFROM\b/i)[0] ?? "";
|
|
5828
|
+
const selectFields = new Set(selectClause.match(/campaign\.[\w.]+/g) ?? []);
|
|
5801
5829
|
const missing = [...new Set(whereClause.match(/campaign\.[\w.]+/g) ?? [])].filter(
|
|
5802
|
-
(field) => !
|
|
5830
|
+
(field) => !selectFields.has(field)
|
|
5803
5831
|
);
|
|
5804
5832
|
if (missing.length > 0) {
|
|
5805
5833
|
corrected = corrected.replace(/SELECT\s+/i, `SELECT ${missing.join(", ")}, `);
|
|
@@ -6654,6 +6682,52 @@ function rsaContentFromFlags(args) {
|
|
|
6654
6682
|
finalUrls
|
|
6655
6683
|
};
|
|
6656
6684
|
}
|
|
6685
|
+
function rdaContentFromFlags(args, base) {
|
|
6686
|
+
const content = {
|
|
6687
|
+
format: "responsiveDisplay",
|
|
6688
|
+
...base !== null && typeof base === "object" && !Array.isArray(base) ? base : {}
|
|
6689
|
+
};
|
|
6690
|
+
const headlines = listFlag(args.headlines);
|
|
6691
|
+
if (headlines) {
|
|
6692
|
+
content.headlines = headlines.map((text) => ({ text }));
|
|
6693
|
+
}
|
|
6694
|
+
const descriptions = listFlag(args.descriptions);
|
|
6695
|
+
if (descriptions) {
|
|
6696
|
+
content.descriptions = descriptions.map((text) => ({ text }));
|
|
6697
|
+
}
|
|
6698
|
+
if (typeof args["long-headline"] === "string") {
|
|
6699
|
+
content.longHeadline = { text: args["long-headline"] };
|
|
6700
|
+
}
|
|
6701
|
+
if (typeof args["business-name"] === "string") {
|
|
6702
|
+
content.businessName = args["business-name"];
|
|
6703
|
+
}
|
|
6704
|
+
const finalUrls = listFlag(args["final-url"]);
|
|
6705
|
+
if (finalUrls) {
|
|
6706
|
+
content.finalUrls = finalUrls;
|
|
6707
|
+
}
|
|
6708
|
+
const marketing = listFlag(args["marketing-images"]);
|
|
6709
|
+
if (marketing) {
|
|
6710
|
+
content.marketingImageAssets = marketing;
|
|
6711
|
+
}
|
|
6712
|
+
const square = listFlag(args["square-marketing-images"]);
|
|
6713
|
+
if (square) {
|
|
6714
|
+
content.squareMarketingImageAssets = square;
|
|
6715
|
+
}
|
|
6716
|
+
const logos = listFlag(args["logo-images"]);
|
|
6717
|
+
if (logos) {
|
|
6718
|
+
content.logoImageAssets = logos;
|
|
6719
|
+
}
|
|
6720
|
+
return content;
|
|
6721
|
+
}
|
|
6722
|
+
function adContentFromFlags(args, format, fileContent) {
|
|
6723
|
+
if (format === "responsiveSearch") {
|
|
6724
|
+
return fileContent ?? rsaContentFromFlags(args);
|
|
6725
|
+
}
|
|
6726
|
+
if (format === "responsiveDisplay") {
|
|
6727
|
+
return rdaContentFromFlags(args, fileContent);
|
|
6728
|
+
}
|
|
6729
|
+
return fileContent ?? failWriteValidation(`--format ${format} needs --file with the ad content`);
|
|
6730
|
+
}
|
|
6657
6731
|
var adsCommand = defineCommand30({
|
|
6658
6732
|
meta: { name: "ads", description: "Stage ad create/update/pause/resume/remove" },
|
|
6659
6733
|
subCommands: {
|
|
@@ -6670,10 +6744,21 @@ var adsCommand = defineCommand30({
|
|
|
6670
6744
|
type: "string",
|
|
6671
6745
|
description: "responsiveSearch (default) | responsiveDisplay | performanceMaxAssetGroup | call | app | video | demandGen"
|
|
6672
6746
|
},
|
|
6673
|
-
headlines: { type: "string", description: "Comma-separated headlines (RSA)" },
|
|
6674
|
-
descriptions: { type: "string", description: "Comma-separated descriptions (RSA)" },
|
|
6747
|
+
headlines: { type: "string", description: "Comma-separated headlines (RSA/RDA)" },
|
|
6748
|
+
descriptions: { type: "string", description: "Comma-separated descriptions (RSA/RDA)" },
|
|
6675
6749
|
path1: { type: "string" },
|
|
6676
6750
|
path2: { type: "string" },
|
|
6751
|
+
"long-headline": { type: "string", description: "Long headline (responsiveDisplay)" },
|
|
6752
|
+
"business-name": { type: "string", description: "Business name (responsiveDisplay)" },
|
|
6753
|
+
"marketing-images": {
|
|
6754
|
+
type: "string",
|
|
6755
|
+
description: "Comma-separated marketing image asset refs (responsiveDisplay)"
|
|
6756
|
+
},
|
|
6757
|
+
"square-marketing-images": {
|
|
6758
|
+
type: "string",
|
|
6759
|
+
description: "Comma-separated square marketing image asset refs (responsiveDisplay)"
|
|
6760
|
+
},
|
|
6761
|
+
"logo-images": { type: "string", description: "Comma-separated logo image asset refs (responsiveDisplay)" },
|
|
6677
6762
|
"final-url": { type: "string", description: "Comma-separated final URLs" },
|
|
6678
6763
|
status: { type: "string" }
|
|
6679
6764
|
},
|
|
@@ -6681,7 +6766,7 @@ var adsCommand = defineCommand30({
|
|
|
6681
6766
|
const customerId = requireCustomerId(args);
|
|
6682
6767
|
const file = loadJsonFileArg(args.file);
|
|
6683
6768
|
const format = args.format ?? "responsiveSearch";
|
|
6684
|
-
const content =
|
|
6769
|
+
const content = adContentFromFlags(args, format, file.content);
|
|
6685
6770
|
await stageCreate("google.ad.create", customerId, {
|
|
6686
6771
|
adGroup: requireStringFlag(args["ad-group-ref"] ?? file.adGroup, "--ad-group-ref"),
|
|
6687
6772
|
status: args.status ?? file.status,
|
|
@@ -8405,19 +8490,19 @@ function failWriteValidation2(message) {
|
|
|
8405
8490
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
8406
8491
|
process.exit(1);
|
|
8407
8492
|
}
|
|
8408
|
-
function loadJsonFileArg2(
|
|
8409
|
-
if (typeof
|
|
8493
|
+
function loadJsonFileArg2(path14) {
|
|
8494
|
+
if (typeof path14 !== "string" || path14.length === 0) {
|
|
8410
8495
|
return {};
|
|
8411
8496
|
}
|
|
8412
8497
|
try {
|
|
8413
|
-
const parsed = JSON.parse(readFileSync7(
|
|
8498
|
+
const parsed = JSON.parse(readFileSync7(path14, "utf8"));
|
|
8414
8499
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8415
|
-
failWriteValidation2(`${
|
|
8500
|
+
failWriteValidation2(`${path14} must contain a JSON object`);
|
|
8416
8501
|
}
|
|
8417
8502
|
return parsed;
|
|
8418
8503
|
} catch (err) {
|
|
8419
8504
|
if (err instanceof SyntaxError) {
|
|
8420
|
-
failWriteValidation2(`${
|
|
8505
|
+
failWriteValidation2(`${path14} is not valid JSON: ${err.message}`);
|
|
8421
8506
|
}
|
|
8422
8507
|
throw err;
|
|
8423
8508
|
}
|
|
@@ -8480,15 +8565,15 @@ function parseLocaleFlag(value) {
|
|
|
8480
8565
|
}
|
|
8481
8566
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
8482
8567
|
}
|
|
8483
|
-
function loadTargetingFileArg(
|
|
8484
|
-
if (typeof
|
|
8568
|
+
function loadTargetingFileArg(path14) {
|
|
8569
|
+
if (typeof path14 !== "string" || path14.length === 0) {
|
|
8485
8570
|
return void 0;
|
|
8486
8571
|
}
|
|
8487
|
-
const parsed = loadJsonFileArg2(
|
|
8572
|
+
const parsed = loadJsonFileArg2(path14);
|
|
8488
8573
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
8489
8574
|
if (!criteria.include) {
|
|
8490
8575
|
failWriteValidation2(
|
|
8491
|
-
`${
|
|
8576
|
+
`${path14} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
8492
8577
|
);
|
|
8493
8578
|
}
|
|
8494
8579
|
return criteria;
|
|
@@ -8523,14 +8608,14 @@ function parseCsvLine(line) {
|
|
|
8523
8608
|
cells.push(current);
|
|
8524
8609
|
return cells.map((cell) => cell.trim());
|
|
8525
8610
|
}
|
|
8526
|
-
function parseListFileArg(
|
|
8527
|
-
if (typeof
|
|
8611
|
+
function parseListFileArg(path14, maxRows) {
|
|
8612
|
+
if (typeof path14 !== "string" || path14.length === 0) {
|
|
8528
8613
|
return void 0;
|
|
8529
8614
|
}
|
|
8530
|
-
const raw = readFileSync7(
|
|
8615
|
+
const raw = readFileSync7(path14, "utf8");
|
|
8531
8616
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
8532
8617
|
if (lines.length < 2) {
|
|
8533
|
-
failWriteValidation2(`${
|
|
8618
|
+
failWriteValidation2(`${path14} needs a header row and at least one data row`);
|
|
8534
8619
|
}
|
|
8535
8620
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
8536
8621
|
const rows = [];
|
|
@@ -8549,7 +8634,7 @@ function parseListFileArg(path12, maxRows) {
|
|
|
8549
8634
|
}
|
|
8550
8635
|
}
|
|
8551
8636
|
if (rows.length > maxRows) {
|
|
8552
|
-
failWriteValidation2(`${
|
|
8637
|
+
failWriteValidation2(`${path14} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
8553
8638
|
}
|
|
8554
8639
|
return { columns, rows };
|
|
8555
8640
|
}
|
|
@@ -12752,7 +12837,7 @@ async function probeDuration(filePath) {
|
|
|
12752
12837
|
|
|
12753
12838
|
// src/commands/canvas/run.ts
|
|
12754
12839
|
import { readFile as readFile2 } from "fs/promises";
|
|
12755
|
-
import
|
|
12840
|
+
import path5 from "path";
|
|
12756
12841
|
import { defineCommand as defineCommand85 } from "citty";
|
|
12757
12842
|
|
|
12758
12843
|
// src/commands/canvas/placeholders.ts
|
|
@@ -12796,9 +12881,102 @@ function isResolvableRelative(value) {
|
|
|
12796
12881
|
return typeof value === "string" && value.length > 0 && !value.includes("[TODO") && !path2.isAbsolute(value);
|
|
12797
12882
|
}
|
|
12798
12883
|
|
|
12884
|
+
// src/commands/canvas/run-record.ts
|
|
12885
|
+
import path3 from "path";
|
|
12886
|
+
var MAX_RUN_NODES = 200;
|
|
12887
|
+
var MAX_OUTPUTS_PER_NODE = 10;
|
|
12888
|
+
var MAX_FINAL_OUTPUTS = 10;
|
|
12889
|
+
var MAX_CREATIVE_SLUG_LENGTH = 100;
|
|
12890
|
+
function creativeSlugFromCanvasPath(filePath) {
|
|
12891
|
+
const normalized = filePath.split(path3.sep).join("/");
|
|
12892
|
+
const match = normalized.match(/(?:^|\/)src\/creatives\/([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\//);
|
|
12893
|
+
const slug = match?.[1] ?? null;
|
|
12894
|
+
return slug && slug.length <= MAX_CREATIVE_SLUG_LENGTH ? slug : null;
|
|
12895
|
+
}
|
|
12896
|
+
var OUTPUT_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio", "json", "text", "font"]);
|
|
12897
|
+
function toRecordOutput(slot, value) {
|
|
12898
|
+
const refs = collectAssetRefLikes(value);
|
|
12899
|
+
const ref = refs.length === 1 ? refs[0] : null;
|
|
12900
|
+
if (!ref || !isPersistedAssetRef(ref)) return null;
|
|
12901
|
+
const kind = typeof ref.kind === "string" && OUTPUT_KINDS.has(ref.kind) ? ref.kind : null;
|
|
12902
|
+
if (!kind) return null;
|
|
12903
|
+
return {
|
|
12904
|
+
slot,
|
|
12905
|
+
kind,
|
|
12906
|
+
sha256: ref.sha256,
|
|
12907
|
+
url: ref.url,
|
|
12908
|
+
mime: ref.mime,
|
|
12909
|
+
width: typeof ref.width === "number" ? ref.width : void 0,
|
|
12910
|
+
height: typeof ref.height === "number" ? ref.height : void 0,
|
|
12911
|
+
durationMs: typeof ref.duration_ms === "number" ? ref.duration_ms : void 0
|
|
12912
|
+
};
|
|
12913
|
+
}
|
|
12914
|
+
function nodeOutputsToRecord(nodeOutputs) {
|
|
12915
|
+
const out = [];
|
|
12916
|
+
for (const [slot, value] of Object.entries(nodeOutputs)) {
|
|
12917
|
+
if (Array.isArray(value)) {
|
|
12918
|
+
value.forEach((item, i) => {
|
|
12919
|
+
const rec = toRecordOutput(`${slot}#${i}`, item);
|
|
12920
|
+
if (rec) out.push(rec);
|
|
12921
|
+
});
|
|
12922
|
+
} else {
|
|
12923
|
+
const rec = toRecordOutput(slot, value);
|
|
12924
|
+
if (rec) out.push(rec);
|
|
12925
|
+
}
|
|
12926
|
+
}
|
|
12927
|
+
return out.slice(0, MAX_OUTPUTS_PER_NODE);
|
|
12928
|
+
}
|
|
12929
|
+
function finalOutputsToRecord(output) {
|
|
12930
|
+
if (Array.isArray(output)) {
|
|
12931
|
+
return output.map((item, i) => toRecordOutput(`final#${i}`, item)).filter((rec2) => rec2 !== null).slice(0, MAX_FINAL_OUTPUTS);
|
|
12932
|
+
}
|
|
12933
|
+
const rec = toRecordOutput("final", output);
|
|
12934
|
+
return rec ? [rec] : [];
|
|
12935
|
+
}
|
|
12936
|
+
function buildRunRecord(result, meta) {
|
|
12937
|
+
const nodes = result.node_runs.slice(0, MAX_RUN_NODES).map((run) => ({
|
|
12938
|
+
nodeId: run.node_id,
|
|
12939
|
+
nodeType: run.node_type,
|
|
12940
|
+
cached: run.cached,
|
|
12941
|
+
credits: run.credits,
|
|
12942
|
+
durationMs: run.duration_ms,
|
|
12943
|
+
outputs: nodeOutputsToRecord(result.outputs_by_node[run.node_id] ?? {})
|
|
12944
|
+
}));
|
|
12945
|
+
const finalOutputs = finalOutputsToRecord(result.output);
|
|
12946
|
+
return {
|
|
12947
|
+
runId: result.run_id,
|
|
12948
|
+
creativeSlug: meta.creativeSlug,
|
|
12949
|
+
canvasPath: meta.canvasPath,
|
|
12950
|
+
canvasSha: meta.canvasSha,
|
|
12951
|
+
chatId: meta.chatId,
|
|
12952
|
+
status: "completed",
|
|
12953
|
+
stats: {
|
|
12954
|
+
totalNodes: result.stats.total_nodes,
|
|
12955
|
+
cachedNodes: result.stats.cached_nodes,
|
|
12956
|
+
totalCredits: result.stats.total_credits,
|
|
12957
|
+
durationMs: result.stats.duration_ms
|
|
12958
|
+
},
|
|
12959
|
+
nodes,
|
|
12960
|
+
finalOutputs: finalOutputs.length > 0 ? finalOutputs : void 0
|
|
12961
|
+
};
|
|
12962
|
+
}
|
|
12963
|
+
function buildFailedRunRecord(runId, errorMessage, meta) {
|
|
12964
|
+
return {
|
|
12965
|
+
runId,
|
|
12966
|
+
creativeSlug: meta.creativeSlug,
|
|
12967
|
+
canvasPath: meta.canvasPath,
|
|
12968
|
+
canvasSha: meta.canvasSha,
|
|
12969
|
+
chatId: meta.chatId,
|
|
12970
|
+
status: "failed",
|
|
12971
|
+
errorMessage: errorMessage.slice(0, 2e3),
|
|
12972
|
+
stats: { totalNodes: 0, cachedNodes: 0, totalCredits: 0, durationMs: 0 },
|
|
12973
|
+
nodes: []
|
|
12974
|
+
};
|
|
12975
|
+
}
|
|
12976
|
+
|
|
12799
12977
|
// src/commands/canvas/run-retention.ts
|
|
12800
12978
|
import { rm } from "fs/promises";
|
|
12801
|
-
import
|
|
12979
|
+
import path4 from "path";
|
|
12802
12980
|
function runDirsToPrune(entries, keep, currentRunId) {
|
|
12803
12981
|
const runs = entries.filter((e) => /^r_[0-9A-Za-z]+$/.test(e) && e !== currentRunId).sort();
|
|
12804
12982
|
if (keep <= 0) return runs;
|
|
@@ -12815,7 +12993,7 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
|
|
|
12815
12993
|
const toPrune = runDirsToPrune(entries, keep, currentRunId);
|
|
12816
12994
|
if (toPrune.length === 0) return;
|
|
12817
12995
|
for (const dir of toPrune) {
|
|
12818
|
-
await rm(
|
|
12996
|
+
await rm(path4.join(outputsDir, dir), { recursive: true, force: true }).catch(
|
|
12819
12997
|
(e) => log(`[prune ] could not remove ${dir}: ${e.message}`)
|
|
12820
12998
|
);
|
|
12821
12999
|
}
|
|
@@ -12835,13 +13013,29 @@ var runCommand = defineCommand85({
|
|
|
12835
13013
|
type: "string",
|
|
12836
13014
|
description: "Max nodes per layer in flight at once (default 5; env BAKER_CANVAS_CONCURRENCY)"
|
|
12837
13015
|
},
|
|
13016
|
+
parallel: {
|
|
13017
|
+
type: "string",
|
|
13018
|
+
description: "Alias for --concurrency: independent clips (and every other same-layer node) already fan out in parallel up to this bound"
|
|
13019
|
+
},
|
|
12838
13020
|
"keep-runs": {
|
|
12839
13021
|
type: "string",
|
|
12840
13022
|
description: "After the run, prune old r_* run dirs, keeping the N newest (off by default)"
|
|
13023
|
+
},
|
|
13024
|
+
"remote-cache": {
|
|
13025
|
+
type: "string",
|
|
13026
|
+
description: "on | off \u2014 company-scoped remote cache + durable asset persistence (default on; env BAKER_CANVAS_REMOTE_CACHE)"
|
|
13027
|
+
},
|
|
13028
|
+
// citty consumes any `--no-<flag>` as a negation of `<flag>`, so the
|
|
13029
|
+
// opt-out spelling `--no-record` requires the flag to be named `record`
|
|
13030
|
+
// (a literal "no-record" arg would never receive a value).
|
|
13031
|
+
record: {
|
|
13032
|
+
type: "boolean",
|
|
13033
|
+
default: true,
|
|
13034
|
+
description: "Post the durable run-history record to Baker (disable with --no-record)"
|
|
12841
13035
|
}
|
|
12842
13036
|
},
|
|
12843
13037
|
async run({ args }) {
|
|
12844
|
-
const filePath =
|
|
13038
|
+
const filePath = path5.resolve(String(args.file));
|
|
12845
13039
|
const raw = await readFile2(filePath, "utf8");
|
|
12846
13040
|
let parsed;
|
|
12847
13041
|
try {
|
|
@@ -12852,7 +13046,7 @@ var runCommand = defineCommand85({
|
|
|
12852
13046
|
`);
|
|
12853
13047
|
process.exit(2);
|
|
12854
13048
|
}
|
|
12855
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
13049
|
+
parsed = resolveRelativeCanvasPaths(parsed, path5.dirname(filePath));
|
|
12856
13050
|
const pending = unsuppliedPlaceholderAssets(parsed);
|
|
12857
13051
|
if (pending.length > 0) {
|
|
12858
13052
|
process.stderr.write(
|
|
@@ -12872,25 +13066,37 @@ var runCommand = defineCommand85({
|
|
|
12872
13066
|
);
|
|
12873
13067
|
process.exit(2);
|
|
12874
13068
|
}
|
|
13069
|
+
const remoteCache = args["remote-cache"] !== void 0 ? String(args["remote-cache"]) !== "off" : void 0;
|
|
12875
13070
|
const engine = createEngineFromEnv({
|
|
12876
13071
|
cacheDir: args["cache-dir"] ? String(args["cache-dir"]) : void 0,
|
|
12877
13072
|
outputsDir: args["outputs-dir"] ? String(args["outputs-dir"]) : void 0,
|
|
12878
13073
|
log: (line) => process.stdout.write(`${line}
|
|
12879
|
-
`)
|
|
13074
|
+
`),
|
|
13075
|
+
remoteCache
|
|
12880
13076
|
});
|
|
13077
|
+
const runId = args["run-id"] ? String(args["run-id"]) : `r_${ulid()}`;
|
|
13078
|
+
const recordMeta = {
|
|
13079
|
+
creativeSlug: creativeSlugFromCanvasPath(filePath) ?? void 0,
|
|
13080
|
+
canvasPath: path5.relative(process.cwd(), filePath) || void 0,
|
|
13081
|
+
canvasSha: sha256Hex(Buffer.from(raw)),
|
|
13082
|
+
chatId: getEnv().BAKER_CHAT_ID || void 0
|
|
13083
|
+
};
|
|
13084
|
+
const record = args.record === false ? null : buildRecorder();
|
|
12881
13085
|
try {
|
|
12882
13086
|
const policy = args["cache-policy"] ?? "read_write";
|
|
12883
13087
|
const result = await engine.run(parsed, {
|
|
12884
|
-
run_id:
|
|
13088
|
+
run_id: runId,
|
|
12885
13089
|
cache_policy: policy,
|
|
12886
13090
|
concurrency: resolveConcurrency(
|
|
12887
|
-
|
|
13091
|
+
// --concurrency wins; --parallel is the discoverable alias for the same bound.
|
|
13092
|
+
(args.concurrency ?? args.parallel) !== void 0 ? String(args.concurrency ?? args.parallel) : void 0,
|
|
12888
13093
|
process.env.BAKER_CANVAS_CONCURRENCY
|
|
12889
13094
|
)
|
|
12890
13095
|
});
|
|
13096
|
+
if (record) await record(buildRunRecord(result, recordMeta));
|
|
12891
13097
|
const keepRuns = args["keep-runs"] !== void 0 ? Number(args["keep-runs"]) : void 0;
|
|
12892
13098
|
if (keepRuns !== void 0 && Number.isFinite(keepRuns)) {
|
|
12893
|
-
const outputsDir = args["outputs-dir"] ?
|
|
13099
|
+
const outputsDir = args["outputs-dir"] ? path5.resolve(String(args["outputs-dir"])) : path5.resolve("canvas");
|
|
12894
13100
|
await pruneOldRuns(outputsDir, keepRuns, result.run_id, (line) => process.stdout.write(`${line}
|
|
12895
13101
|
`));
|
|
12896
13102
|
}
|
|
@@ -12918,6 +13124,7 @@ var runCommand = defineCommand85({
|
|
|
12918
13124
|
}
|
|
12919
13125
|
if (e instanceof LayerExecutionError) {
|
|
12920
13126
|
const failures = e.failures.map((f) => ({ node_id: f.nodeId, message: describeFailureReason(f.reason) }));
|
|
13127
|
+
if (record) await record(buildFailedRunRecord(runId, e.message, recordMeta));
|
|
12921
13128
|
process.stderr.write(
|
|
12922
13129
|
`${JSON.stringify({ ok: false, error: { code: "runtime", message: e.message, failures } }, null, 2)}
|
|
12923
13130
|
`
|
|
@@ -12925,16 +13132,30 @@ var runCommand = defineCommand85({
|
|
|
12925
13132
|
process.exit(1);
|
|
12926
13133
|
}
|
|
12927
13134
|
const msg = e instanceof Error ? e.message : String(e);
|
|
13135
|
+
if (record) await record(buildFailedRunRecord(runId, msg, recordMeta));
|
|
12928
13136
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "runtime", message: msg } }, null, 2)}
|
|
12929
13137
|
`);
|
|
12930
13138
|
process.exit(1);
|
|
12931
13139
|
}
|
|
12932
13140
|
}
|
|
12933
13141
|
});
|
|
13142
|
+
function buildRecorder() {
|
|
13143
|
+
return async (payload) => {
|
|
13144
|
+
try {
|
|
13145
|
+
const creds = requireCredentialsFromEnv();
|
|
13146
|
+
const client = new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey });
|
|
13147
|
+
await client.recordRun(payload);
|
|
13148
|
+
} catch (e) {
|
|
13149
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
13150
|
+
process.stderr.write(`[warn] run record not persisted (${msg})
|
|
13151
|
+
`);
|
|
13152
|
+
}
|
|
13153
|
+
};
|
|
13154
|
+
}
|
|
12934
13155
|
|
|
12935
13156
|
// src/commands/canvas/scaffold-static-ad.ts
|
|
12936
|
-
import { readFile as readFile3, writeFile } from "fs/promises";
|
|
12937
|
-
import
|
|
13157
|
+
import { access, cp, mkdir, readFile as readFile3, writeFile } from "fs/promises";
|
|
13158
|
+
import path8 from "path";
|
|
12938
13159
|
import { defineCommand as defineCommand86 } from "citty";
|
|
12939
13160
|
|
|
12940
13161
|
// src/engine/scaffold/staticAd.ts
|
|
@@ -13120,18 +13341,106 @@ function staticAdReport(input, elementsInput, opts) {
|
|
|
13120
13341
|
};
|
|
13121
13342
|
}
|
|
13122
13343
|
|
|
13344
|
+
// src/commands/canvas/creative-definition.ts
|
|
13345
|
+
import path6 from "path";
|
|
13346
|
+
var PLATFORM_VALUES = ["meta", "google", "linkedin", "tiktok", "youtube", "x", "other"];
|
|
13347
|
+
var FORMAT_VALUES = ["1:1", "4:5", "9:16", "16:9", "1.91:1"];
|
|
13348
|
+
function titleFromSlug(slug) {
|
|
13349
|
+
const title = slug.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
13350
|
+
return title || slug;
|
|
13351
|
+
}
|
|
13352
|
+
function resolvePlatform(platform) {
|
|
13353
|
+
const value = platform?.trim();
|
|
13354
|
+
return value && PLATFORM_VALUES.includes(value) ? value : "meta";
|
|
13355
|
+
}
|
|
13356
|
+
function resolveFormats(aspect) {
|
|
13357
|
+
const value = aspect?.trim();
|
|
13358
|
+
return value && FORMAT_VALUES.includes(value) ? [value] : ["4:5"];
|
|
13359
|
+
}
|
|
13360
|
+
function referenceRelativePath(kind, ext) {
|
|
13361
|
+
const name = kind === "video" ? "source" : "original";
|
|
13362
|
+
return `references/${name}${ext}`;
|
|
13363
|
+
}
|
|
13364
|
+
function sourceExtension(source, isUrl, kind) {
|
|
13365
|
+
const raw = isUrl ? urlPathname(source) : source;
|
|
13366
|
+
const ext = path6.extname(raw).toLowerCase();
|
|
13367
|
+
if (/^\.[a-z0-9]{1,5}$/.test(ext)) return ext;
|
|
13368
|
+
return kind === "video" ? ".mp4" : ".jpg";
|
|
13369
|
+
}
|
|
13370
|
+
function urlPathname(source) {
|
|
13371
|
+
try {
|
|
13372
|
+
return new URL(source).pathname;
|
|
13373
|
+
} catch {
|
|
13374
|
+
return source;
|
|
13375
|
+
}
|
|
13376
|
+
}
|
|
13377
|
+
function describeBlueprintIntent(blueprint) {
|
|
13378
|
+
const intent = blueprint?.ad_intent;
|
|
13379
|
+
if (typeof intent === "string" && intent.trim()) return intent.trim();
|
|
13380
|
+
if (intent && typeof intent === "object") {
|
|
13381
|
+
const summary = intent.summary ?? intent.feeling;
|
|
13382
|
+
if (typeof summary === "string" && summary.trim()) return summary.trim();
|
|
13383
|
+
}
|
|
13384
|
+
return void 0;
|
|
13385
|
+
}
|
|
13386
|
+
function yamlScalar(value) {
|
|
13387
|
+
return JSON.stringify(value);
|
|
13388
|
+
}
|
|
13389
|
+
function buildCreativeDefinition(input) {
|
|
13390
|
+
const lines = ["---", `title: ${yamlScalar(input.title)}`, `kind: ${input.kind}`, `platform: ${input.platform}`];
|
|
13391
|
+
lines.push(`formats: [${input.formats.map(yamlScalar).join(", ")}]`);
|
|
13392
|
+
lines.push(`status: ${input.status ?? "draft"}`);
|
|
13393
|
+
if (input.sourceReferenceUrl) lines.push(`sourceReferenceUrl: ${yamlScalar(input.sourceReferenceUrl)}`);
|
|
13394
|
+
if (input.sourceAdvertiser) lines.push(`sourceAdvertiser: ${yamlScalar(input.sourceAdvertiser)}`);
|
|
13395
|
+
if (input.sourceKind) lines.push(`sourceKind: ${input.sourceKind}`);
|
|
13396
|
+
if (input.sourcePath) lines.push(`sourcePath: ${yamlScalar(input.sourcePath)}`);
|
|
13397
|
+
lines.push("---", "");
|
|
13398
|
+
lines.push(input.description?.trim() || `${input.title} \u2014 canvas-built ${input.kind} ad for ${input.platform}.`);
|
|
13399
|
+
lines.push("");
|
|
13400
|
+
return lines.join("\n");
|
|
13401
|
+
}
|
|
13402
|
+
|
|
13123
13403
|
// src/commands/canvas/scaffold-static-ad-paths.ts
|
|
13124
|
-
import
|
|
13125
|
-
function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd()) {
|
|
13404
|
+
import path7 from "path";
|
|
13405
|
+
function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd(), slug) {
|
|
13126
13406
|
const file = rawFile.trim();
|
|
13127
13407
|
const imageIsUrl = /^https?:\/\//i.test(file);
|
|
13128
|
-
const imageSource = imageIsUrl ? file :
|
|
13129
|
-
const outPath = out ?
|
|
13130
|
-
const blueprintPath =
|
|
13131
|
-
|
|
13408
|
+
const imageSource = imageIsUrl ? file : path7.resolve(cwd, file);
|
|
13409
|
+
const outPath = out ? path7.resolve(cwd, out) : slug ? path7.join(cwd, "src", "creatives", slug, `${slug}.canvas.json`) : imageIsUrl ? path7.join(cwd, "static-ad.canvas.json") : path7.join(path7.dirname(imageSource), "static-ad.canvas.json");
|
|
13410
|
+
const blueprintPath = path7.join(path7.dirname(outPath), "prompt.json");
|
|
13411
|
+
const creativeDir = slug ? path7.dirname(outPath) : null;
|
|
13412
|
+
const definitionPath = creativeDir ? path7.join(creativeDir, "_definition.md") : null;
|
|
13413
|
+
const referencesDir = creativeDir ? path7.join(creativeDir, "references") : null;
|
|
13414
|
+
return { imageIsUrl, imageSource, outPath, blueprintPath, creativeDir, definitionPath, referencesDir };
|
|
13415
|
+
}
|
|
13416
|
+
var SCAFFOLD_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
13417
|
+
var SCAFFOLD_SLUG_MAX_LENGTH = 100;
|
|
13418
|
+
function isValidScaffoldSlug(slug) {
|
|
13419
|
+
return slug.length <= SCAFFOLD_SLUG_MAX_LENGTH && SCAFFOLD_SLUG_PATTERN.test(slug);
|
|
13132
13420
|
}
|
|
13133
13421
|
|
|
13134
13422
|
// src/commands/canvas/scaffold-static-ad.ts
|
|
13423
|
+
async function fileExists(target) {
|
|
13424
|
+
try {
|
|
13425
|
+
await access(target);
|
|
13426
|
+
return true;
|
|
13427
|
+
} catch {
|
|
13428
|
+
return false;
|
|
13429
|
+
}
|
|
13430
|
+
}
|
|
13431
|
+
async function copySourceIntoReferences(source, isUrl, referencesDir) {
|
|
13432
|
+
await mkdir(referencesDir, { recursive: true });
|
|
13433
|
+
const relPath = referenceRelativePath("image", sourceExtension(source, isUrl, "image"));
|
|
13434
|
+
const dest = path8.join(referencesDir, path8.basename(relPath));
|
|
13435
|
+
if (isUrl) {
|
|
13436
|
+
const res = await fetch(source);
|
|
13437
|
+
if (!res.ok) throw new Error(`failed to download source image (${res.status})`);
|
|
13438
|
+
await writeFile(dest, Buffer.from(await res.arrayBuffer()));
|
|
13439
|
+
} else {
|
|
13440
|
+
await cp(source, dest);
|
|
13441
|
+
}
|
|
13442
|
+
return relPath;
|
|
13443
|
+
}
|
|
13135
13444
|
function resolveModel(kind, preferred) {
|
|
13136
13445
|
const ids = Object.keys(MODEL_REGISTRY[kind]);
|
|
13137
13446
|
return ids.includes(preferred) ? preferred : ids[0] ?? preferred;
|
|
@@ -13291,6 +13600,16 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
13291
13600
|
file: { type: "positional", required: true, description: "Path or http(s) URL to the source/inspiration image" },
|
|
13292
13601
|
context: { type: "string", description: "Known provenance (advertiser, category, market) to ground the describe" },
|
|
13293
13602
|
out: { type: "string", description: "Output canvas path (default <image-dir>/static-ad.canvas.json)" },
|
|
13603
|
+
slug: {
|
|
13604
|
+
type: "string",
|
|
13605
|
+
description: "Creative slug \u2014 writes the canvas to src/creatives/<slug>/<slug>.canvas.json (repo convention)"
|
|
13606
|
+
},
|
|
13607
|
+
title: { type: "string", description: "Creative title for _definition.md (default: title-cased slug)" },
|
|
13608
|
+
platform: {
|
|
13609
|
+
type: "string",
|
|
13610
|
+
description: "Ad platform for _definition.md (meta|google|linkedin|tiktok|youtube|x|other; default meta)"
|
|
13611
|
+
},
|
|
13612
|
+
advertiser: { type: "string", description: "Source advertiser recorded in _definition.md" },
|
|
13294
13613
|
"describe-model": { type: "string", description: "Override the image_describe model id" },
|
|
13295
13614
|
"select-model": { type: "string", description: "Override the text_generate model id for element selection" },
|
|
13296
13615
|
"layout-model": { type: "string", description: "Override the text_generate model id for the layout pass" },
|
|
@@ -13299,10 +13618,21 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
13299
13618
|
"skip-font": { type: "boolean", description: "Skip the brand-font \u2192 type-specimen slot" }
|
|
13300
13619
|
},
|
|
13301
13620
|
async run({ args }) {
|
|
13302
|
-
const
|
|
13621
|
+
const slug = args.slug ? String(args.slug) : void 0;
|
|
13622
|
+
if (slug && !isValidScaffoldSlug(slug)) {
|
|
13623
|
+
process.stderr.write(
|
|
13624
|
+
`${JSON.stringify({ ok: false, error: { code: "invalid_slug", message: "--slug must be lowercase kebab (a-z, 0-9, hyphens), max 100 chars" } }, null, 2)}
|
|
13625
|
+
`
|
|
13626
|
+
);
|
|
13627
|
+
process.exit(2);
|
|
13628
|
+
}
|
|
13629
|
+
const { imageIsUrl, imageSource, outPath, blueprintPath, definitionPath, referencesDir } = resolveScaffoldStaticAdPaths(
|
|
13303
13630
|
String(args.file),
|
|
13304
|
-
args.out ? String(args.out) : void 0
|
|
13631
|
+
args.out ? String(args.out) : void 0,
|
|
13632
|
+
process.cwd(),
|
|
13633
|
+
slug
|
|
13305
13634
|
);
|
|
13635
|
+
await mkdir(path8.dirname(outPath), { recursive: true });
|
|
13306
13636
|
const { describeModel, selectModel, layoutModel, genModel } = resolveModels(args);
|
|
13307
13637
|
const describeCanvas = buildDescribeCanvas(
|
|
13308
13638
|
imageSource,
|
|
@@ -13319,11 +13649,21 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
13319
13649
|
}
|
|
13320
13650
|
await writeFile(blueprintPath, `${JSON.stringify(annotated, null, 2)}
|
|
13321
13651
|
`, "utf8");
|
|
13652
|
+
let canvasImagePath = imageSource;
|
|
13653
|
+
let canvasImageIsUrl = imageIsUrl;
|
|
13654
|
+
let canvasBlueprintPath = blueprintPath;
|
|
13655
|
+
let sourceRelPath;
|
|
13656
|
+
if (referencesDir) {
|
|
13657
|
+
sourceRelPath = await copySourceIntoReferences(imageSource, imageIsUrl, referencesDir);
|
|
13658
|
+
canvasImagePath = sourceRelPath;
|
|
13659
|
+
canvasImageIsUrl = false;
|
|
13660
|
+
canvasBlueprintPath = "./prompt.json";
|
|
13661
|
+
}
|
|
13322
13662
|
const opts = {
|
|
13323
13663
|
genModel,
|
|
13324
|
-
imagePath:
|
|
13325
|
-
imageIsUrl,
|
|
13326
|
-
blueprintPath,
|
|
13664
|
+
imagePath: canvasImagePath,
|
|
13665
|
+
imageIsUrl: canvasImageIsUrl,
|
|
13666
|
+
blueprintPath: canvasBlueprintPath,
|
|
13327
13667
|
aspectRatio: args.aspect ? String(args.aspect) : void 0,
|
|
13328
13668
|
includeFont: !args["skip-font"]
|
|
13329
13669
|
};
|
|
@@ -13345,12 +13685,31 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
13345
13685
|
}
|
|
13346
13686
|
await writeFile(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
13347
13687
|
`, "utf8");
|
|
13688
|
+
if (definitionPath && !await fileExists(definitionPath)) {
|
|
13689
|
+
await writeFile(
|
|
13690
|
+
definitionPath,
|
|
13691
|
+
buildCreativeDefinition({
|
|
13692
|
+
title: args.title ? String(args.title) : titleFromSlug(slug ?? ""),
|
|
13693
|
+
kind: "static",
|
|
13694
|
+
platform: resolvePlatform(args.platform ? String(args.platform) : void 0),
|
|
13695
|
+
formats: resolveFormats(args.aspect ? String(args.aspect) : report.aspect_ratio),
|
|
13696
|
+
sourceReferenceUrl: imageIsUrl ? imageSource : void 0,
|
|
13697
|
+
sourceAdvertiser: args.advertiser ? String(args.advertiser) : args.context ? String(args.context) : void 0,
|
|
13698
|
+
sourceKind: "image",
|
|
13699
|
+
sourcePath: sourceRelPath,
|
|
13700
|
+
description: describeBlueprintIntent(blueprint)
|
|
13701
|
+
}),
|
|
13702
|
+
"utf8"
|
|
13703
|
+
);
|
|
13704
|
+
}
|
|
13348
13705
|
process.stdout.write(
|
|
13349
13706
|
`${JSON.stringify(
|
|
13350
13707
|
{
|
|
13351
13708
|
ok: true,
|
|
13352
13709
|
canvas_path: outPath,
|
|
13353
13710
|
prompt_path: blueprintPath,
|
|
13711
|
+
definition_path: definitionPath ?? void 0,
|
|
13712
|
+
source_reference: sourceRelPath ?? void 0,
|
|
13354
13713
|
output: canvas.output,
|
|
13355
13714
|
models: { describe: describeModel, select: selectModel, layout: layoutModel, gen: opts.genModel },
|
|
13356
13715
|
aspect_ratio: report.aspect_ratio,
|
|
@@ -13361,7 +13720,7 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
13361
13720
|
run_estimated_credits: validation.estimatedCredits
|
|
13362
13721
|
},
|
|
13363
13722
|
checklist: {
|
|
13364
|
-
edit_prompt: `Edit ${
|
|
13723
|
+
edit_prompt: `Edit ${path8.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.`,
|
|
13365
13724
|
assets_to_supply: report.elements,
|
|
13366
13725
|
font_slot: report.includes_font ? "Drop a brand font at the [TODO] brandfont path, or delete the brandfont + type_ref nodes to skip it." : "skipped (--skip-font)",
|
|
13367
13726
|
note: "Replace every [TODO] ingest path with a real file, then `baker canvas validate` and `baker canvas run`. Running generates a billed image \u2014 it is not free."
|
|
@@ -13376,8 +13735,8 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
13376
13735
|
});
|
|
13377
13736
|
|
|
13378
13737
|
// src/commands/canvas/scaffold-video.ts
|
|
13379
|
-
import { cp, mkdir, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
13380
|
-
import
|
|
13738
|
+
import { cp as cp2, mkdir as mkdir2, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
13739
|
+
import path11 from "path";
|
|
13381
13740
|
import { defineCommand as defineCommand87 } from "citty";
|
|
13382
13741
|
|
|
13383
13742
|
// src/engine/nodes/local/lib/sceneDetect.ts
|
|
@@ -15844,7 +16203,8 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
15844
16203
|
}
|
|
15845
16204
|
const todo = {
|
|
15846
16205
|
...buildVideoTodo(videoReport(input, elementsInput), overlays.length, floating.length, opts, blueprint),
|
|
15847
|
-
...aspectRemapTodo(resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel)))
|
|
16206
|
+
...aspectRemapTodo(resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel))),
|
|
16207
|
+
model_constraints: buildModelConstraints()
|
|
15848
16208
|
};
|
|
15849
16209
|
return {
|
|
15850
16210
|
schema: "baker-canvas/1",
|
|
@@ -15855,21 +16215,58 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
15855
16215
|
// The timing plan `baker canvas validate` checks before any billed render:
|
|
15856
16216
|
// sequenced voiceover turns (no overlap), audio ≈ video length, and which
|
|
15857
16217
|
// scenes must be lip-synced.
|
|
15858
|
-
video: buildVideoMeta(blueprint, { vo_segments, talking_scenes })
|
|
16218
|
+
video: buildVideoMeta(blueprint, { vo_segments, talking_scenes }, slots, nodes)
|
|
15859
16219
|
},
|
|
15860
16220
|
nodes,
|
|
15861
16221
|
output: { node: videoNode, output: "video" }
|
|
15862
16222
|
};
|
|
15863
16223
|
}
|
|
15864
|
-
function buildVideoMeta(blueprint, meta) {
|
|
16224
|
+
function buildVideoMeta(blueprint, meta, slots, nodes) {
|
|
15865
16225
|
return {
|
|
15866
16226
|
duration_s: blueprint.source?.duration_s ?? lastSceneEnd(blueprint),
|
|
15867
16227
|
vo_segments: [...meta.vo_segments].sort((a, b) => a.start_s - b.start_s),
|
|
15868
16228
|
talking_scenes: meta.talking_scenes,
|
|
15869
16229
|
lip_sync_caution: buildLipSyncCaution(meta.vo_segments),
|
|
15870
|
-
motion_board: buildMotionBoard(blueprint)
|
|
16230
|
+
motion_board: buildMotionBoard(blueprint),
|
|
16231
|
+
// The wired recurring-element registry (the reference-completeness check reads
|
|
16232
|
+
// `ref`/`label`/`type` to warn when a frame describes an element it doesn't wire).
|
|
16233
|
+
elements: slots.map((s) => ({
|
|
16234
|
+
ref: s.ref,
|
|
16235
|
+
label: s.label,
|
|
16236
|
+
type: s.type,
|
|
16237
|
+
...s.description ? { description: s.description } : {}
|
|
16238
|
+
})),
|
|
16239
|
+
clip_spans: buildClipSpans(blueprint, nodes)
|
|
15871
16240
|
};
|
|
15872
16241
|
}
|
|
16242
|
+
function buildModelConstraints() {
|
|
16243
|
+
const models = {};
|
|
16244
|
+
for (const [id, spec] of Object.entries(MODEL_REGISTRY.video_generate)) {
|
|
16245
|
+
const p = spec.params;
|
|
16246
|
+
models[id] = {
|
|
16247
|
+
label: spec.label,
|
|
16248
|
+
aspect_ratios: p.aspect_ratio?.enum ?? null,
|
|
16249
|
+
durations_s: p.duration?.enum ?? null,
|
|
16250
|
+
person_generation: p.person_generation?.enum ?? null
|
|
16251
|
+
};
|
|
16252
|
+
}
|
|
16253
|
+
return {
|
|
16254
|
+
note: "Switching a clip's video model has cross-node constraints. ALL video_generate nodes in one canvas MUST share ONE aspect_ratio (the composite silently crops otherwise). Each model's `duration` is a hard enum and differs per model \u2014 a scene longer than the model's max must be split. `person_generation` differs (Veo requires \"allow_all\"). `baker canvas validate` gates duration, aspect agreement, per-model params, AND a scene-span-vs-model-max advisory before any billed run \u2014 run it after every model edit.",
|
|
16255
|
+
models
|
|
16256
|
+
};
|
|
16257
|
+
}
|
|
16258
|
+
function buildClipSpans(blueprint, nodes) {
|
|
16259
|
+
const spans = [];
|
|
16260
|
+
for (const n of nodes) {
|
|
16261
|
+
if (n.type !== "video_generate") continue;
|
|
16262
|
+
const m = n.id.match(/^s(\d+)/);
|
|
16263
|
+
const idx = m ? Number(m[1]) : Number.NaN;
|
|
16264
|
+
const scene = Number.isInteger(idx) ? blueprint.scenes[idx] : void 0;
|
|
16265
|
+
if (!scene) continue;
|
|
16266
|
+
spans.push({ node: n.id, span_s: Math.round(sceneDurationS(scene) * 100) / 100 });
|
|
16267
|
+
}
|
|
16268
|
+
return spans;
|
|
16269
|
+
}
|
|
15873
16270
|
function buildLipSyncCaution(segments) {
|
|
15874
16271
|
const out = [];
|
|
15875
16272
|
const byScene = /* @__PURE__ */ new Map();
|
|
@@ -16123,23 +16520,23 @@ function videoReport(input, elementsInput) {
|
|
|
16123
16520
|
|
|
16124
16521
|
// src/commands/canvas/composition-path.ts
|
|
16125
16522
|
import { existsSync as existsSync4 } from "fs";
|
|
16126
|
-
import
|
|
16523
|
+
import path9 from "path";
|
|
16127
16524
|
function resolveShippedCanvasDir(name, startDir, exists = existsSync4, maxDepth = 8) {
|
|
16128
|
-
const rel =
|
|
16525
|
+
const rel = path9.join("canvas", name);
|
|
16129
16526
|
let dir = startDir;
|
|
16130
16527
|
for (let i = 0; i < maxDepth; i++) {
|
|
16131
|
-
const candidate =
|
|
16132
|
-
if (exists(
|
|
16133
|
-
const parent =
|
|
16528
|
+
const candidate = path9.join(dir, rel);
|
|
16529
|
+
if (exists(path9.join(candidate, "meta.json"))) return candidate;
|
|
16530
|
+
const parent = path9.dirname(dir);
|
|
16134
16531
|
if (parent === dir) break;
|
|
16135
16532
|
dir = parent;
|
|
16136
16533
|
}
|
|
16137
|
-
return
|
|
16534
|
+
return path9.resolve(startDir, "../../../", rel);
|
|
16138
16535
|
}
|
|
16139
16536
|
|
|
16140
16537
|
// src/commands/canvas/gitignore.ts
|
|
16141
16538
|
import { appendFile, readFile as readFile5 } from "fs/promises";
|
|
16142
|
-
import
|
|
16539
|
+
import path10 from "path";
|
|
16143
16540
|
function missingGitignoreEntries(existing, entries) {
|
|
16144
16541
|
const present = new Set(
|
|
16145
16542
|
existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
@@ -16147,7 +16544,7 @@ function missingGitignoreEntries(existing, entries) {
|
|
|
16147
16544
|
return entries.filter((e) => !present.has(e.trim().replace(/\/+$/, "")));
|
|
16148
16545
|
}
|
|
16149
16546
|
async function ensureGitignore(dir, entries) {
|
|
16150
|
-
const file =
|
|
16547
|
+
const file = path10.join(dir, ".gitignore");
|
|
16151
16548
|
let existing;
|
|
16152
16549
|
try {
|
|
16153
16550
|
existing = await readFile5(file, "utf8");
|
|
@@ -16208,8 +16605,8 @@ async function loadTranscriptBestEffort(ref) {
|
|
|
16208
16605
|
async function stageCaptions(outDir, transcript) {
|
|
16209
16606
|
const text = transcript?.trim();
|
|
16210
16607
|
if (!text || text === "[]") return {};
|
|
16211
|
-
const compositionPath =
|
|
16212
|
-
await
|
|
16608
|
+
const compositionPath = path11.join(outDir, "tiktok-captions-composition");
|
|
16609
|
+
await cp2(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });
|
|
16213
16610
|
return { compositionPath };
|
|
16214
16611
|
}
|
|
16215
16612
|
function patchCompositionMeta(metaJson, dims) {
|
|
@@ -16226,10 +16623,10 @@ function patchCompositionHtml(html, dims) {
|
|
|
16226
16623
|
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`);
|
|
16227
16624
|
}
|
|
16228
16625
|
async function stampCompositionDims(compositionDir, dims) {
|
|
16229
|
-
const metaPath =
|
|
16626
|
+
const metaPath = path11.join(compositionDir, "meta.json");
|
|
16230
16627
|
const rawMeta = await readFile6(metaPath, "utf8");
|
|
16231
16628
|
await writeFile2(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
|
|
16232
|
-
const htmlPath =
|
|
16629
|
+
const htmlPath = path11.join(compositionDir, "index.html");
|
|
16233
16630
|
const rawHtml = await readFile6(htmlPath, "utf8");
|
|
16234
16631
|
await writeFile2(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
|
|
16235
16632
|
}
|
|
@@ -16369,6 +16766,10 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
16369
16766
|
args: {
|
|
16370
16767
|
file: { type: "positional", required: true, description: "Path to the reference video" },
|
|
16371
16768
|
out: { type: "string", description: "Output canvas path (default <video-dir>/<name>.video.canvas.json)" },
|
|
16769
|
+
slug: {
|
|
16770
|
+
type: "string",
|
|
16771
|
+
description: "Creative slug \u2014 writes the canvas to src/creatives/<slug>/<slug>.canvas.json (repo convention)"
|
|
16772
|
+
},
|
|
16372
16773
|
frames: { type: "string", description: '"generate" (default, anchored regen) or "reuse" (wire real frames in)' },
|
|
16373
16774
|
ambient: {
|
|
16374
16775
|
type: "boolean",
|
|
@@ -16395,11 +16796,19 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
16395
16796
|
}
|
|
16396
16797
|
},
|
|
16397
16798
|
async run({ args }) {
|
|
16398
|
-
const videoPath =
|
|
16399
|
-
const base =
|
|
16400
|
-
const
|
|
16401
|
-
|
|
16402
|
-
|
|
16799
|
+
const videoPath = path11.resolve(String(args.file));
|
|
16800
|
+
const base = path11.basename(videoPath, path11.extname(videoPath));
|
|
16801
|
+
const slug = args.slug ? String(args.slug) : void 0;
|
|
16802
|
+
if (slug && !isValidScaffoldSlug(slug)) {
|
|
16803
|
+
process.stderr.write(
|
|
16804
|
+
`${JSON.stringify({ ok: false, error: { code: "invalid_slug", message: "--slug must be lowercase kebab (a-z, 0-9, hyphens), max 100 chars" } }, null, 2)}
|
|
16805
|
+
`
|
|
16806
|
+
);
|
|
16807
|
+
process.exit(2);
|
|
16808
|
+
}
|
|
16809
|
+
const outPath = args.out ? path11.resolve(String(args.out)) : slug ? path11.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`) : path11.join(path11.dirname(videoPath), `${base}.video.canvas.json`);
|
|
16810
|
+
const outDir = path11.dirname(outPath);
|
|
16811
|
+
const blueprintPath = path11.join(outDir, "prompt.json");
|
|
16403
16812
|
const frames = args.frames === "reuse" ? "reuse" : "generate";
|
|
16404
16813
|
const maxScenes = args["max-scenes"] ? Number(args["max-scenes"]) : void 0;
|
|
16405
16814
|
if (Number.isFinite(maxScenes)) {
|
|
@@ -16418,7 +16827,7 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
16418
16827
|
shotCuts
|
|
16419
16828
|
});
|
|
16420
16829
|
const { blueprint, elements, transcript, creditsSpent } = await runAnalysisPasses(deconstructCanvas, selectModel);
|
|
16421
|
-
await
|
|
16830
|
+
await mkdir2(outDir, { recursive: true });
|
|
16422
16831
|
const annotated = annotateBlueprintWithElements(blueprint, elements);
|
|
16423
16832
|
await writeFile2(blueprintPath, `${JSON.stringify(annotated, null, 2)}
|
|
16424
16833
|
`, "utf8");
|
|
@@ -16439,10 +16848,10 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
16439
16848
|
`
|
|
16440
16849
|
);
|
|
16441
16850
|
}
|
|
16442
|
-
const compositionDest =
|
|
16443
|
-
await
|
|
16851
|
+
const compositionDest = path11.join(outDir, "video-overlay-composition");
|
|
16852
|
+
await cp2(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
16444
16853
|
await stampCompositionDims(compositionDest, outDims);
|
|
16445
|
-
const indexPath =
|
|
16854
|
+
const indexPath = path11.join(compositionDest, "index.html");
|
|
16446
16855
|
const overlayHtml = buildOverlayHtml(blueprint);
|
|
16447
16856
|
const indexHtml = await readFile6(indexPath, "utf8");
|
|
16448
16857
|
const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
@@ -16458,9 +16867,9 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
16458
16867
|
const opts = {
|
|
16459
16868
|
imageModel,
|
|
16460
16869
|
videoModel,
|
|
16461
|
-
overlayCompositionPath:
|
|
16462
|
-
captionsCompositionPath: captions.compositionPath ?
|
|
16463
|
-
blueprintPath:
|
|
16870
|
+
overlayCompositionPath: path11.relative(outDir, compositionDest),
|
|
16871
|
+
captionsCompositionPath: captions.compositionPath ? path11.relative(outDir, captions.compositionPath) : void 0,
|
|
16872
|
+
blueprintPath: path11.relative(outDir, blueprintPath),
|
|
16464
16873
|
frames,
|
|
16465
16874
|
ambient: Boolean(args.ambient),
|
|
16466
16875
|
...args.aspect ? { aspect: String(args.aspect) } : {},
|
|
@@ -16502,7 +16911,7 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
16502
16911
|
run_estimated_credits: validation.estimatedCredits
|
|
16503
16912
|
},
|
|
16504
16913
|
checklist: {
|
|
16505
|
-
edit_prompt: `Edit ${
|
|
16914
|
+
edit_prompt: `Edit ${path11.basename(blueprintPath)} \u2014 the blueprint deconstructed from your video; rewrite it into the ad you want (cast, palette, copy, claims). Every scene frame reads it via target_blueprint.`,
|
|
16506
16915
|
recurring_elements_to_supply: report.elements,
|
|
16507
16916
|
voices_to_confirm: report.dialogue.map((d) => ({
|
|
16508
16917
|
scene: d.scene,
|
|
@@ -16529,7 +16938,7 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
16529
16938
|
|
|
16530
16939
|
// src/commands/canvas/set-prompt.ts
|
|
16531
16940
|
import { readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
|
|
16532
|
-
import
|
|
16941
|
+
import path12 from "path";
|
|
16533
16942
|
import { defineCommand as defineCommand88 } from "citty";
|
|
16534
16943
|
function setNodePrompt(canvas, nodeId, text) {
|
|
16535
16944
|
const nodes = canvas?.nodes;
|
|
@@ -16557,7 +16966,7 @@ var setPromptCommand = defineCommand88({
|
|
|
16557
16966
|
"text-file": { type: "string", description: "Read the new prompt from a UTF-8 file (preserves accents/newlines)" }
|
|
16558
16967
|
},
|
|
16559
16968
|
async run({ args }) {
|
|
16560
|
-
const filePath =
|
|
16969
|
+
const filePath = path12.resolve(String(args.file));
|
|
16561
16970
|
let canvas;
|
|
16562
16971
|
try {
|
|
16563
16972
|
canvas = JSON.parse(await readFile7(filePath, "utf8"));
|
|
@@ -16567,7 +16976,7 @@ var setPromptCommand = defineCommand88({
|
|
|
16567
16976
|
process.exit(2);
|
|
16568
16977
|
}
|
|
16569
16978
|
let text;
|
|
16570
|
-
if (args["text-file"]) text = await readFile7(
|
|
16979
|
+
if (args["text-file"]) text = await readFile7(path12.resolve(String(args["text-file"])), "utf8");
|
|
16571
16980
|
else if (args.text !== void 0) text = String(args.text);
|
|
16572
16981
|
else {
|
|
16573
16982
|
process.stderr.write(
|
|
@@ -16588,7 +16997,7 @@ var setPromptCommand = defineCommand88({
|
|
|
16588
16997
|
process.exit(2);
|
|
16589
16998
|
return;
|
|
16590
16999
|
}
|
|
16591
|
-
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated,
|
|
17000
|
+
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated, path12.dirname(filePath)), defaultRegistry());
|
|
16592
17001
|
if (!validation.ok) {
|
|
16593
17002
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.issues } }, null, 2)}
|
|
16594
17003
|
`);
|
|
@@ -16604,7 +17013,7 @@ var setPromptCommand = defineCommand88({
|
|
|
16604
17013
|
|
|
16605
17014
|
// src/commands/canvas/validate.ts
|
|
16606
17015
|
import { readFile as readFile8 } from "fs/promises";
|
|
16607
|
-
import
|
|
17016
|
+
import path13 from "path";
|
|
16608
17017
|
import { defineCommand as defineCommand89 } from "citty";
|
|
16609
17018
|
var validateCommand = defineCommand89({
|
|
16610
17019
|
meta: {
|
|
@@ -16613,7 +17022,7 @@ var validateCommand = defineCommand89({
|
|
|
16613
17022
|
},
|
|
16614
17023
|
args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
|
|
16615
17024
|
async run({ args }) {
|
|
16616
|
-
const filePath =
|
|
17025
|
+
const filePath = path13.resolve(String(args.file));
|
|
16617
17026
|
const raw = await readFile8(filePath, "utf8");
|
|
16618
17027
|
let parsed;
|
|
16619
17028
|
try {
|
|
@@ -16624,7 +17033,7 @@ var validateCommand = defineCommand89({
|
|
|
16624
17033
|
`);
|
|
16625
17034
|
process.exit(2);
|
|
16626
17035
|
}
|
|
16627
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
17036
|
+
parsed = resolveRelativeCanvasPaths(parsed, path13.dirname(filePath));
|
|
16628
17037
|
const result = await validateCanvasDeep(parsed, defaultRegistry());
|
|
16629
17038
|
if (!result.ok) {
|
|
16630
17039
|
process.stderr.write(`${JSON.stringify({ ok: false, issues: result.issues }, null, 2)}
|
|
@@ -16638,7 +17047,8 @@ var validateCommand = defineCommand89({
|
|
|
16638
17047
|
ok: true,
|
|
16639
17048
|
total_nodes: result.canvas.nodes.length,
|
|
16640
17049
|
estimated_credits: result.estimatedCredits,
|
|
16641
|
-
cost_preview: result.perNodeCredits ?? []
|
|
17050
|
+
cost_preview: result.perNodeCredits ?? [],
|
|
17051
|
+
warnings: result.warnings ?? []
|
|
16642
17052
|
},
|
|
16643
17053
|
null,
|
|
16644
17054
|
2
|
|
@@ -16760,6 +17170,16 @@ registerSchema({
|
|
|
16760
17170
|
type: "string",
|
|
16761
17171
|
description: "Optional URL of the original reference ad",
|
|
16762
17172
|
required: false
|
|
17173
|
+
},
|
|
17174
|
+
slug: {
|
|
17175
|
+
type: "string",
|
|
17176
|
+
description: "Creative slug (src/creatives/<slug>/) \u2014 attaches the image to that creative's row",
|
|
17177
|
+
required: false
|
|
17178
|
+
},
|
|
17179
|
+
runId: {
|
|
17180
|
+
type: "string",
|
|
17181
|
+
description: "Canvas run id (r_\u2026) of the approved generation to pin as published",
|
|
17182
|
+
required: false
|
|
16763
17183
|
}
|
|
16764
17184
|
}
|
|
16765
17185
|
});
|
|
@@ -16769,6 +17189,13 @@ function detectCreativeContentType(filePath) {
|
|
|
16769
17189
|
unsupportedMessage: "Unsupported creative image extension. Use PNG, JPG, or WebP."
|
|
16770
17190
|
});
|
|
16771
17191
|
}
|
|
17192
|
+
function chatIdFromEnv() {
|
|
17193
|
+
try {
|
|
17194
|
+
return getEnv().BAKER_CHAT_ID || void 0;
|
|
17195
|
+
} catch {
|
|
17196
|
+
return void 0;
|
|
17197
|
+
}
|
|
17198
|
+
}
|
|
16772
17199
|
function parseOptionalUrl(value) {
|
|
16773
17200
|
if (value === void 0 || value.trim() === "") {
|
|
16774
17201
|
return void 0;
|
|
@@ -16799,7 +17226,11 @@ async function publishCreative(args, deps = defaultImageApiDeps) {
|
|
|
16799
17226
|
return publishImageAsCreative(deps, {
|
|
16800
17227
|
imageId: upload.imageId,
|
|
16801
17228
|
title,
|
|
16802
|
-
sourceReferenceUrl
|
|
17229
|
+
sourceReferenceUrl,
|
|
17230
|
+
slug: args.slug,
|
|
17231
|
+
runId: args.runId,
|
|
17232
|
+
// Attribute the publish to the driving chat (injected by the bridge).
|
|
17233
|
+
chatId: chatIdFromEnv()
|
|
16803
17234
|
});
|
|
16804
17235
|
}
|
|
16805
17236
|
var publishCommand = defineCommand91({
|
|
@@ -16815,6 +17246,16 @@ var publishCommand = defineCommand91({
|
|
|
16815
17246
|
type: "string",
|
|
16816
17247
|
description: "Optional URL of the original reference ad",
|
|
16817
17248
|
required: false
|
|
17249
|
+
},
|
|
17250
|
+
slug: {
|
|
17251
|
+
type: "string",
|
|
17252
|
+
description: "Creative slug (src/creatives/<slug>/) \u2014 attaches the image to that creative's row",
|
|
17253
|
+
required: false
|
|
17254
|
+
},
|
|
17255
|
+
runId: {
|
|
17256
|
+
type: "string",
|
|
17257
|
+
description: "Canvas run id (r_\u2026) of the approved generation to pin as published",
|
|
17258
|
+
required: false
|
|
16818
17259
|
}
|
|
16819
17260
|
},
|
|
16820
17261
|
run: async ({ args }) => {
|
|
@@ -16833,7 +17274,9 @@ var publishCommand = defineCommand91({
|
|
|
16833
17274
|
file,
|
|
16834
17275
|
title,
|
|
16835
17276
|
context: args.context,
|
|
16836
|
-
sourceReferenceUrl: args.sourceReferenceUrl
|
|
17277
|
+
sourceReferenceUrl: args.sourceReferenceUrl,
|
|
17278
|
+
slug: args.slug,
|
|
17279
|
+
runId: args.runId
|
|
16837
17280
|
});
|
|
16838
17281
|
writeJson({ ok: true, data });
|
|
16839
17282
|
} catch (err) {
|
|
@@ -17731,9 +18174,9 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
17731
18174
|
}
|
|
17732
18175
|
return readFile10(pathOrUrl);
|
|
17733
18176
|
}
|
|
17734
|
-
async function isDirectory(
|
|
18177
|
+
async function isDirectory(path14) {
|
|
17735
18178
|
try {
|
|
17736
|
-
const s = await stat2(
|
|
18179
|
+
const s = await stat2(path14);
|
|
17737
18180
|
return s.isDirectory();
|
|
17738
18181
|
} catch {
|
|
17739
18182
|
return false;
|