@koda-sl/baker-cli 0.119.0 → 0.120.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 +41 -50
- package/dist/{chunk-MF34WJ7M.js → chunk-OCMOQOIJ.js} +251 -131
- package/dist/chunk-OCMOQOIJ.js.map +1 -0
- package/dist/cli.js +562 -1232
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +30 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-MF34WJ7M.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,19 +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
|
-
elementMentionKeywords,
|
|
13
14
|
generateCatalog,
|
|
15
|
+
isPersistedAssetRef,
|
|
16
|
+
requireCredentialsFromEnv,
|
|
14
17
|
resolveConcurrency,
|
|
18
|
+
sha256Hex,
|
|
19
|
+
ulid,
|
|
15
20
|
validateCanvasDeep
|
|
16
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-OCMOQOIJ.js";
|
|
17
22
|
|
|
18
23
|
// src/cli.ts
|
|
19
24
|
import { defineCommand as defineCommand157, runMain } from "citty";
|
|
@@ -152,9 +157,9 @@ async function handleResponse(response) {
|
|
|
152
157
|
throw new ApiError("INTERNAL_ERROR", "Failed to parse API response as JSON");
|
|
153
158
|
}
|
|
154
159
|
}
|
|
155
|
-
async function apiGet(
|
|
160
|
+
async function apiGet(path14, params) {
|
|
156
161
|
const env = getEnv();
|
|
157
|
-
const url = new URL(
|
|
162
|
+
const url = new URL(path14, env.BAKER_API_URL);
|
|
158
163
|
if (params) {
|
|
159
164
|
const clean = sanitizeParams(params);
|
|
160
165
|
for (const [key, value] of Object.entries(clean)) {
|
|
@@ -179,12 +184,12 @@ async function apiGet(path12, params) {
|
|
|
179
184
|
}
|
|
180
185
|
return handleResponse(response);
|
|
181
186
|
}
|
|
182
|
-
async function apiPost(
|
|
187
|
+
async function apiPost(path14, body, opts) {
|
|
183
188
|
const env = getEnv();
|
|
184
189
|
const timeoutMs = opts?.timeoutMs ?? 6e4;
|
|
185
190
|
let response;
|
|
186
191
|
try {
|
|
187
|
-
response = await fetchWithRateLimitRetry(new URL(
|
|
192
|
+
response = await fetchWithRateLimitRetry(new URL(path14, env.BAKER_API_URL).toString(), {
|
|
188
193
|
method: "POST",
|
|
189
194
|
headers: {
|
|
190
195
|
Authorization: `Bearer ${env.BAKER_API_KEY}`,
|
|
@@ -989,58 +994,9 @@ var LINKEDIN_LIMITS = {
|
|
|
989
994
|
questionsSoftMax: 4,
|
|
990
995
|
// playbook: >4 fields tanks completion rate
|
|
991
996
|
customQuestionsMax: 3,
|
|
992
|
-
|
|
993
|
-
choiceOptionsMin: 2,
|
|
994
|
-
choiceOptionsMax: 30,
|
|
995
|
-
choiceOptionTextMax: 100,
|
|
996
|
-
thankYouMessageMax: 300,
|
|
997
|
-
privacyPolicyTextMax: 2e3,
|
|
998
|
-
legalDisclaimerMax: 2e3,
|
|
999
|
-
consentsMax: 5,
|
|
1000
|
-
// Campaign Manager caps disclosure checkboxes at 5
|
|
1001
|
-
consentTextMax: 500,
|
|
1002
|
-
hiddenFieldsMax: 20,
|
|
1003
|
-
hiddenFieldNameMax: 100,
|
|
1004
|
-
hiddenFieldValueMax: 2e3
|
|
997
|
+
thankYouMessageMax: 300
|
|
1005
998
|
}
|
|
1006
999
|
};
|
|
1007
|
-
var LEAD_FORM_PREDEFINED_FIELDS = [
|
|
1008
|
-
// Contact
|
|
1009
|
-
"FIRST_NAME",
|
|
1010
|
-
"LAST_NAME",
|
|
1011
|
-
"EMAIL",
|
|
1012
|
-
"PHONE_NUMBER",
|
|
1013
|
-
"CITY",
|
|
1014
|
-
"STATE",
|
|
1015
|
-
"COUNTRY",
|
|
1016
|
-
"ZIP_CODE",
|
|
1017
|
-
"WORK_EMAIL",
|
|
1018
|
-
// Work
|
|
1019
|
-
"JOB_TITLE",
|
|
1020
|
-
"JOB_FUNCTION",
|
|
1021
|
-
"SENIORITY",
|
|
1022
|
-
"YEARS_OF_EXPERIENCE",
|
|
1023
|
-
// Company
|
|
1024
|
-
"COMPANY_NAME",
|
|
1025
|
-
"COMPANY_SIZE",
|
|
1026
|
-
"INDUSTRY",
|
|
1027
|
-
// Education
|
|
1028
|
-
"DEGREE",
|
|
1029
|
-
"FIELD_OF_STUDY",
|
|
1030
|
-
"SCHOOL_NAME",
|
|
1031
|
-
"START_DATE",
|
|
1032
|
-
"GRADUATION_DATE",
|
|
1033
|
-
// Demographic
|
|
1034
|
-
"GENDER"
|
|
1035
|
-
];
|
|
1036
|
-
var LEAD_FORM_QUESTION_TYPES = ["SINGLE_LINE_TEXT", "MULTIPLE_CHOICE"];
|
|
1037
|
-
var LEAD_FORM_CONFIRMATION_CTAS = [
|
|
1038
|
-
"VIEW_COMPANY_WEBSITE",
|
|
1039
|
-
"DOWNLOAD",
|
|
1040
|
-
"LEARN_MORE",
|
|
1041
|
-
"VIEW_NOW",
|
|
1042
|
-
"TRY_NOW"
|
|
1043
|
-
];
|
|
1044
1000
|
var CAMPAIGN_TYPES = ["SPONSORED_UPDATES", "TEXT_AD", "SPONSORED_INMAILS", "DYNAMIC"];
|
|
1045
1001
|
var COST_TYPES = ["CPC", "CPM", "CPV"];
|
|
1046
1002
|
var OBJECTIVE_TYPES = [
|
|
@@ -1093,7 +1049,6 @@ var CURRENCY_MINIMUMS = {
|
|
|
1093
1049
|
GBP: { dailyBudgetMin: 8, unitCostMin: 2 }
|
|
1094
1050
|
};
|
|
1095
1051
|
var DEFAULT_CURRENCY_MINIMUM = { dailyBudgetMin: 10, unitCostMin: 2 };
|
|
1096
|
-
var TEMP_REF_PREFIX = "li_temp_";
|
|
1097
1052
|
var TEMP_REF_REGEX = /^li_temp_[A-Za-z0-9_-]{4,}$/;
|
|
1098
1053
|
var NUMERIC_ID_REGEX = /^\d+$/;
|
|
1099
1054
|
var URN_REGEX = /^urn:li:[a-zA-Z]+:.+$/;
|
|
@@ -1229,15 +1184,7 @@ var campaignUpdateSchema = z3.object({
|
|
|
1229
1184
|
offsiteDeliveryEnabled: z3.boolean().optional(),
|
|
1230
1185
|
connectedTelevisionOnly: z3.boolean().optional(),
|
|
1231
1186
|
creativeSelection: z3.enum(CREATIVE_SELECTIONS).optional(),
|
|
1232
|
-
status: entityStatusSchema.optional()
|
|
1233
|
-
// Create-only on LinkedIn — a live campaign can't change these in place.
|
|
1234
|
-
// Accepted here only so `draft amend` can carry them into a staged CREATE
|
|
1235
|
-
// (li_temp_* target, re-validated as `campaignCreateSchema`); stage-time
|
|
1236
|
-
// rejects them outright when the update targets a live campaign.
|
|
1237
|
-
campaignGroup: campaignFields.campaignGroup.optional(),
|
|
1238
|
-
type: campaignFields.type.optional(),
|
|
1239
|
-
locale: campaignFields.locale.optional(),
|
|
1240
|
-
associatedEntity: campaignFields.associatedEntity
|
|
1187
|
+
status: entityStatusSchema.optional()
|
|
1241
1188
|
}).superRefine((p, ctx) => {
|
|
1242
1189
|
if (!Object.values(p).some((val) => val !== void 0)) {
|
|
1243
1190
|
ctx.addIssue({ code: "custom", message: "update needs at least one field" });
|
|
@@ -1452,12 +1399,7 @@ var creativeUpdateSchema = z3.object({
|
|
|
1452
1399
|
* live ad's snapshot content (real targets, direct-content formats) and
|
|
1453
1400
|
* re-validated against the full creative schema. Post-based ads reject it.
|
|
1454
1401
|
*/
|
|
1455
|
-
content: z3.record(z3.string(), z3.unknown()).optional()
|
|
1456
|
-
// Re-parenting a creative — LinkedIn has no in-place move-to-campaign API.
|
|
1457
|
-
// Accepted here only so `draft amend` can carry it into a staged CREATE
|
|
1458
|
-
// (li_temp_* target, re-validated as `creativeCreateSchema`); stage-time
|
|
1459
|
-
// rejects it outright when the update targets a live creative.
|
|
1460
|
-
campaign: parentRefSchema.optional()
|
|
1402
|
+
content: z3.record(z3.string(), z3.unknown()).optional()
|
|
1461
1403
|
}).refine((p) => Object.values(p).some((val) => val !== void 0), "update needs at least one field");
|
|
1462
1404
|
var listRowSchema = z3.record(z3.string(), z3.string());
|
|
1463
1405
|
var audienceCreateSchema = z3.object({
|
|
@@ -1521,101 +1463,21 @@ var conversionUpdateSchema = z3.object({
|
|
|
1521
1463
|
attributionType: z3.enum(ATTRIBUTION_TYPES).optional(),
|
|
1522
1464
|
enabled: z3.boolean().optional()
|
|
1523
1465
|
}).refine((p) => Object.values(p).some((val) => val !== void 0), "update needs at least one field");
|
|
1524
|
-
var
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
questionType: z3.enum(LEAD_FORM_QUESTION_TYPES).optional(),
|
|
1529
|
-
options: z3.array(z3.string().min(1).max(LEAD.choiceOptionTextMax)).optional()
|
|
1530
|
-
}).superRefine((q, ctx) => {
|
|
1531
|
-
if (q.predefinedField && q.questionType) {
|
|
1532
|
-
ctx.addIssue({
|
|
1533
|
-
code: "custom",
|
|
1534
|
-
path: ["questionType"],
|
|
1535
|
-
message: "a question is either a predefinedField or a custom questionType, not both"
|
|
1536
|
-
});
|
|
1537
|
-
}
|
|
1538
|
-
if (q.questionType === "MULTIPLE_CHOICE") {
|
|
1539
|
-
const count = q.options?.length ?? 0;
|
|
1540
|
-
if (count < LEAD.choiceOptionsMin || count > LEAD.choiceOptionsMax) {
|
|
1541
|
-
ctx.addIssue({
|
|
1542
|
-
code: "custom",
|
|
1543
|
-
path: ["options"],
|
|
1544
|
-
message: `MULTIPLE_CHOICE needs ${LEAD.choiceOptionsMin}-${LEAD.choiceOptionsMax} options`
|
|
1545
|
-
});
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
if (q.questionType !== "MULTIPLE_CHOICE" && q.options?.length) {
|
|
1549
|
-
ctx.addIssue({
|
|
1550
|
-
code: "custom",
|
|
1551
|
-
path: ["options"],
|
|
1552
|
-
message: "options are only valid on MULTIPLE_CHOICE questions"
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
});
|
|
1556
|
-
var leadFormConsentSchema = z3.object({
|
|
1557
|
-
text: z3.string().min(1).max(LEAD.consentTextMax),
|
|
1558
|
-
required: z3.boolean().default(false)
|
|
1559
|
-
});
|
|
1560
|
-
var leadFormHiddenFieldSchema = z3.object({
|
|
1561
|
-
name: z3.string().min(1).max(LEAD.hiddenFieldNameMax),
|
|
1562
|
-
value: z3.string().max(LEAD.hiddenFieldValueMax)
|
|
1563
|
-
});
|
|
1564
|
-
var leadFormThankYouSchema = z3.object({
|
|
1565
|
-
message: z3.string().max(LEAD.thankYouMessageMax).optional(),
|
|
1566
|
-
cta: z3.enum(LEAD_FORM_CONFIRMATION_CTAS).optional(),
|
|
1567
|
-
landingUrl: httpsUrlSchema.optional(),
|
|
1568
|
-
/** "Book an appointment" — a Calendly / Chili Piper scheduling link, in place of a landing page. */
|
|
1569
|
-
appointmentUrl: httpsUrlSchema.optional()
|
|
1570
|
-
}).superRefine((t, ctx) => {
|
|
1571
|
-
if (t.landingUrl && t.appointmentUrl) {
|
|
1572
|
-
ctx.addIssue({
|
|
1573
|
-
code: "custom",
|
|
1574
|
-
path: ["appointmentUrl"],
|
|
1575
|
-
message: "link the confirmation to a landingUrl OR an appointmentUrl, not both"
|
|
1576
|
-
});
|
|
1577
|
-
}
|
|
1578
|
-
if (t.cta && !t.landingUrl) {
|
|
1579
|
-
ctx.addIssue({ code: "custom", path: ["cta"], message: "a confirmation cta needs a landingUrl" });
|
|
1580
|
-
}
|
|
1581
|
-
});
|
|
1582
|
-
var leadFormFields = {
|
|
1583
|
-
name: z3.string().min(1).max(LEAD.nameMax),
|
|
1584
|
-
headline: z3.string().min(1).max(LEAD.headlineMax),
|
|
1585
|
-
description: z3.string().max(LEAD.descriptionMax).optional(),
|
|
1586
|
-
/** Form banner. Baker library id (uploaded at publish) or an image URN already on LinkedIn. */
|
|
1587
|
-
formImageId: bakerMediaIdSchema.optional(),
|
|
1588
|
-
formImageUrn: z3.string().regex(IMAGE_URN_REGEX).optional(),
|
|
1589
|
-
/** Form language, e.g. { country: "US", language: "en" }. Defaults to the account locale on LinkedIn. */
|
|
1590
|
-
locale: z3.object({ country: z3.string().length(2), language: z3.string().length(2) }).optional(),
|
|
1466
|
+
var leadFormCreateSchema = z3.object({
|
|
1467
|
+
name: z3.string().min(1).max(LINKEDIN_LIMITS.leadForm.nameMax),
|
|
1468
|
+
headline: z3.string().min(1).max(LINKEDIN_LIMITS.leadForm.headlineMax),
|
|
1469
|
+
description: z3.string().max(LINKEDIN_LIMITS.leadForm.descriptionMax).optional(),
|
|
1591
1470
|
privacyPolicyUrl: httpsUrlSchema,
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
legalDisclaimer: z3.string().
|
|
1471
|
+
questions: z3.array(z3.object({ name: z3.string().min(1), predefinedField: z3.string().optional() })).min(1).max(LINKEDIN_LIMITS.leadForm.questionsMax),
|
|
1472
|
+
thankYou: z3.object({
|
|
1473
|
+
message: z3.string().max(LINKEDIN_LIMITS.leadForm.thankYouMessageMax).optional(),
|
|
1474
|
+
landingUrl: httpsUrlSchema.optional()
|
|
1475
|
+
}).optional(),
|
|
1476
|
+
legalDisclaimer: z3.string().optional(),
|
|
1598
1477
|
raw: z3.record(z3.string(), z3.unknown()).optional()
|
|
1599
|
-
};
|
|
1600
|
-
var leadFormCreateSchema = z3.object(leadFormFields).superRefine((p, ctx) => {
|
|
1601
|
-
if (p.formImageId && p.formImageUrn) {
|
|
1602
|
-
ctx.addIssue({
|
|
1603
|
-
code: "custom",
|
|
1604
|
-
path: ["formImageId"],
|
|
1605
|
-
message: "provide at most one of formImageId (Baker library) or formImageUrn (already on LinkedIn)"
|
|
1606
|
-
});
|
|
1607
|
-
}
|
|
1608
|
-
const customCount = p.questions.filter((q) => q.questionType).length;
|
|
1609
|
-
if (customCount > LEAD.customQuestionsMax) {
|
|
1610
|
-
ctx.addIssue({
|
|
1611
|
-
code: "custom",
|
|
1612
|
-
path: ["questions"],
|
|
1613
|
-
message: `at most ${LEAD.customQuestionsMax} custom questions (LinkedIn limit); the rest must be predefined profile fields`
|
|
1614
|
-
});
|
|
1615
|
-
}
|
|
1616
1478
|
});
|
|
1617
1479
|
var leadFormUpdateSchema = z3.object({
|
|
1618
|
-
name:
|
|
1480
|
+
name: leadFormCreateSchema.shape.name.optional(),
|
|
1619
1481
|
raw: z3.record(z3.string(), z3.unknown()).optional()
|
|
1620
1482
|
}).refine((p) => Object.values(p).some((val) => val !== void 0), "update needs at least one field");
|
|
1621
1483
|
var LINKEDIN_DRAFT_OP_KINDS = [
|
|
@@ -1681,12 +1543,6 @@ var linkedinDraftStageResponseSchema = z4.object({
|
|
|
1681
1543
|
/** True when the op amended an already-staged op in place instead of appending a new one. */
|
|
1682
1544
|
amended: z4.boolean().optional()
|
|
1683
1545
|
});
|
|
1684
|
-
var linkedinDraftAmendRequestSchema = z4.object({
|
|
1685
|
-
chatId: z4.string(),
|
|
1686
|
-
/** The staged op's ref, or (for staged updates) the target id/URN — matched like `findAmendTarget`. */
|
|
1687
|
-
ref: z4.string(),
|
|
1688
|
-
patch: z4.record(z4.string(), z4.unknown())
|
|
1689
|
-
});
|
|
1690
1546
|
var linkedinDraftOpViewSchema = z4.object({
|
|
1691
1547
|
ref: z4.string(),
|
|
1692
1548
|
kind: linkedinDraftOpKindSchema,
|
|
@@ -1697,45 +1553,14 @@ var linkedinDraftOpViewSchema = z4.object({
|
|
|
1697
1553
|
stagedAt: z4.number(),
|
|
1698
1554
|
result: linkedinDraftOpResultSchema.optional()
|
|
1699
1555
|
});
|
|
1700
|
-
var linkedinDraftShowRequestSchema = z4.object({
|
|
1701
|
-
chatId: z4.string(),
|
|
1702
|
-
ref: z4.string()
|
|
1703
|
-
});
|
|
1704
|
-
var linkedinDraftShowResponseSchema = z4.object({
|
|
1705
|
-
op: linkedinDraftOpViewSchema.extend({
|
|
1706
|
-
payload: z4.unknown().optional(),
|
|
1707
|
-
warnings: z4.array(z4.string()).optional(),
|
|
1708
|
-
annotations: z4.unknown().optional()
|
|
1709
|
-
})
|
|
1710
|
-
});
|
|
1711
1556
|
var linkedinDraftListRequestSchema = z4.object({
|
|
1712
1557
|
chatId: z4.string()
|
|
1713
1558
|
});
|
|
1714
|
-
var linkedinDraftStatusCollectionSchema = z4.object({
|
|
1715
|
-
label: z4.string(),
|
|
1716
|
-
added: z4.number(),
|
|
1717
|
-
removed: z4.number(),
|
|
1718
|
-
existing: z4.number()
|
|
1719
|
-
});
|
|
1720
|
-
var linkedinDraftChangeOperationSchema = z4.enum(["create", "update", "pause", "resume", "archive"]);
|
|
1721
|
-
var linkedinDraftStatusNodeSchema = z4.lazy(
|
|
1722
|
-
() => z4.object({
|
|
1723
|
-
entity: z4.string(),
|
|
1724
|
-
name: z4.string(),
|
|
1725
|
-
operation: linkedinDraftChangeOperationSchema.optional(),
|
|
1726
|
-
existing: z4.boolean(),
|
|
1727
|
-
collections: z4.array(linkedinDraftStatusCollectionSchema),
|
|
1728
|
-
children: z4.array(linkedinDraftStatusNodeSchema),
|
|
1729
|
-
warnings: z4.array(z4.string()).optional()
|
|
1730
|
-
})
|
|
1731
|
-
);
|
|
1732
1559
|
var linkedinDraftListResponseSchema = z4.object({
|
|
1733
1560
|
status: z4.enum(["active", "publishing", "applied", "discarded", "none"]),
|
|
1734
1561
|
mode: linkedinWriteModeSchema,
|
|
1735
1562
|
count: z4.number(),
|
|
1736
|
-
ops: z4.array(linkedinDraftOpViewSchema)
|
|
1737
|
-
/** Grouped Campaign group ▸ Campaign ▸ Creative tree for the readable CLI status view. */
|
|
1738
|
-
tree: z4.array(linkedinDraftStatusNodeSchema).optional()
|
|
1563
|
+
ops: z4.array(linkedinDraftOpViewSchema)
|
|
1739
1564
|
});
|
|
1740
1565
|
var linkedinDraftRemoveRequestSchema = z4.object({
|
|
1741
1566
|
chatId: z4.string(),
|
|
@@ -3056,31 +2881,31 @@ function cachePath(category, key) {
|
|
|
3056
2881
|
return join2(dir, `${hashKey(key)}.json`);
|
|
3057
2882
|
}
|
|
3058
2883
|
function cacheGet(category, key) {
|
|
3059
|
-
const
|
|
3060
|
-
if (!existsSync2(
|
|
2884
|
+
const path14 = cachePath(category, key);
|
|
2885
|
+
if (!existsSync2(path14)) {
|
|
3061
2886
|
return null;
|
|
3062
2887
|
}
|
|
3063
2888
|
try {
|
|
3064
|
-
const raw = readFileSync2(
|
|
2889
|
+
const raw = readFileSync2(path14, "utf-8");
|
|
3065
2890
|
const entry = JSON.parse(raw);
|
|
3066
2891
|
if (entry.expiresAt < Date.now()) {
|
|
3067
|
-
rmSync(
|
|
2892
|
+
rmSync(path14, { force: true });
|
|
3068
2893
|
return null;
|
|
3069
2894
|
}
|
|
3070
2895
|
return entry;
|
|
3071
2896
|
} catch {
|
|
3072
|
-
rmSync(
|
|
2897
|
+
rmSync(path14, { force: true });
|
|
3073
2898
|
return null;
|
|
3074
2899
|
}
|
|
3075
2900
|
}
|
|
3076
2901
|
function cacheSet(category, key, data, ttlMs, fields) {
|
|
3077
|
-
const
|
|
2902
|
+
const path14 = cachePath(category, key);
|
|
3078
2903
|
const entry = {
|
|
3079
2904
|
expiresAt: Date.now() + ttlMs,
|
|
3080
2905
|
data,
|
|
3081
2906
|
fields
|
|
3082
2907
|
};
|
|
3083
|
-
writeFileSync(
|
|
2908
|
+
writeFileSync(path14, JSON.stringify(entry), "utf-8");
|
|
3084
2909
|
}
|
|
3085
2910
|
var HOUR = 60 * 60 * 1e3;
|
|
3086
2911
|
var MINUTE = 60 * 1e3;
|
|
@@ -4116,19 +3941,6 @@ var GOOGLE_ADS_LIMITS = {
|
|
|
4116
3941
|
},
|
|
4117
3942
|
label: {
|
|
4118
3943
|
nameMax: 255
|
|
4119
|
-
},
|
|
4120
|
-
assetGroup: {
|
|
4121
|
-
nameMax: 255,
|
|
4122
|
-
headlinesMin: 3,
|
|
4123
|
-
headlinesMax: 15,
|
|
4124
|
-
headlineTextMax: 30,
|
|
4125
|
-
longHeadlinesMin: 1,
|
|
4126
|
-
longHeadlinesMax: 5,
|
|
4127
|
-
longHeadlineTextMax: 90,
|
|
4128
|
-
descriptionsMin: 2,
|
|
4129
|
-
descriptionsMax: 5,
|
|
4130
|
-
descriptionTextMax: 90,
|
|
4131
|
-
businessNameMax: 25
|
|
4132
3944
|
}
|
|
4133
3945
|
};
|
|
4134
3946
|
var KEYWORD_MATCH_TYPES = ["EXACT", "PHRASE", "BROAD"];
|
|
@@ -4428,6 +4240,15 @@ var responsiveDisplayAdSchema = z8.object({
|
|
|
4428
4240
|
logoImageAssets: z8.array(refSchema).optional(),
|
|
4429
4241
|
finalUrls: z8.array(httpsUrlSchema2).min(1)
|
|
4430
4242
|
});
|
|
4243
|
+
var performanceMaxAssetGroupSchema = z8.object({
|
|
4244
|
+
format: z8.literal("performanceMaxAssetGroup"),
|
|
4245
|
+
name: z8.string().min(1).max(255),
|
|
4246
|
+
headlines: z8.array(z8.object({ text: z8.string().min(1).max(30) })).min(3).max(15),
|
|
4247
|
+
descriptions: z8.array(z8.object({ text: z8.string().min(1).max(90) })).min(2).max(5),
|
|
4248
|
+
finalUrls: z8.array(httpsUrlSchema2).min(1),
|
|
4249
|
+
imageAssets: z8.array(refSchema).optional(),
|
|
4250
|
+
logoAssets: z8.array(refSchema).optional()
|
|
4251
|
+
});
|
|
4431
4252
|
var callAdSchema = z8.object({
|
|
4432
4253
|
format: z8.literal("call"),
|
|
4433
4254
|
countryCode: z8.string().length(2),
|
|
@@ -4446,9 +4267,7 @@ var appAdSchema = z8.object({
|
|
|
4446
4267
|
});
|
|
4447
4268
|
var videoAdSchema = z8.object({
|
|
4448
4269
|
format: z8.literal("video"),
|
|
4449
|
-
|
|
4450
|
-
// its own `google.asset.create` (type: youtubeVideo) first, then referenced here by asset ref.
|
|
4451
|
-
videoAssets: z8.array(refSchema).min(1),
|
|
4270
|
+
youtubeVideoId: z8.string().min(1),
|
|
4452
4271
|
finalUrls: z8.array(httpsUrlSchema2).min(1)
|
|
4453
4272
|
});
|
|
4454
4273
|
var demandGenAdSchema = z8.object({
|
|
@@ -4457,13 +4276,12 @@ var demandGenAdSchema = z8.object({
|
|
|
4457
4276
|
descriptions: z8.array(z8.object({ text: z8.string().min(1).max(90) })).min(1).max(5),
|
|
4458
4277
|
businessName: z8.string().min(1).max(25),
|
|
4459
4278
|
finalUrls: z8.array(httpsUrlSchema2).min(1),
|
|
4460
|
-
imageAssets: z8.array(refSchema).optional()
|
|
4461
|
-
squareImageAssets: z8.array(refSchema).optional(),
|
|
4462
|
-
logoImageAssets: z8.array(refSchema).optional()
|
|
4279
|
+
imageAssets: z8.array(refSchema).optional()
|
|
4463
4280
|
});
|
|
4464
4281
|
var adContentSchema = z8.discriminatedUnion("format", [
|
|
4465
4282
|
responsiveSearchAdSchema,
|
|
4466
4283
|
responsiveDisplayAdSchema,
|
|
4284
|
+
performanceMaxAssetGroupSchema,
|
|
4467
4285
|
callAdSchema,
|
|
4468
4286
|
appAdSchema,
|
|
4469
4287
|
videoAdSchema,
|
|
@@ -4516,18 +4334,6 @@ var assetCreateSchema = z8.discriminatedUnion("type", [
|
|
|
4516
4334
|
structuredSnippetAssetSchema,
|
|
4517
4335
|
callToActionAssetSchema
|
|
4518
4336
|
]);
|
|
4519
|
-
var assetUpdateSchema = z8.object({
|
|
4520
|
-
name: z8.string().min(1).optional(),
|
|
4521
|
-
linkText: z8.string().min(1).max(GOOGLE_ADS_LIMITS.asset.sitelinkLinkTextMax).optional(),
|
|
4522
|
-
description1: z8.string().max(GOOGLE_ADS_LIMITS.asset.sitelinkDescriptionMax).optional(),
|
|
4523
|
-
description2: z8.string().max(GOOGLE_ADS_LIMITS.asset.sitelinkDescriptionMax).optional(),
|
|
4524
|
-
finalUrls: z8.array(httpsUrlSchema2).min(1).optional(),
|
|
4525
|
-
calloutText: z8.string().min(1).max(GOOGLE_ADS_LIMITS.asset.calloutTextMax).optional(),
|
|
4526
|
-
header: z8.string().min(1).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetHeaderMax).optional(),
|
|
4527
|
-
values: z8.array(z8.string().min(1)).min(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMin).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMax).optional(),
|
|
4528
|
-
callToAction: z8.string().min(1).optional(),
|
|
4529
|
-
text: z8.string().min(1).optional()
|
|
4530
|
-
}).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
|
|
4531
4337
|
var assetLinkAttachSchema = z8.object({
|
|
4532
4338
|
level: z8.enum(["campaign", "adGroup", "customer"]),
|
|
4533
4339
|
parent: refSchema.optional(),
|
|
@@ -4542,24 +4348,6 @@ var assetLinkAttachSchema = z8.object({
|
|
|
4542
4348
|
});
|
|
4543
4349
|
}
|
|
4544
4350
|
});
|
|
4545
|
-
var assetGroupCreateSchema = z8.object({
|
|
4546
|
-
campaign: refSchema,
|
|
4547
|
-
name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.nameMax),
|
|
4548
|
-
finalUrls: z8.array(httpsUrlSchema2).min(1),
|
|
4549
|
-
headlines: z8.array(z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.headlineTextMax) })).min(GOOGLE_ADS_LIMITS.assetGroup.headlinesMin).max(GOOGLE_ADS_LIMITS.assetGroup.headlinesMax),
|
|
4550
|
-
longHeadlines: z8.array(z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.longHeadlineTextMax) })).min(GOOGLE_ADS_LIMITS.assetGroup.longHeadlinesMin).max(GOOGLE_ADS_LIMITS.assetGroup.longHeadlinesMax),
|
|
4551
|
-
descriptions: z8.array(z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.descriptionTextMax) })).min(GOOGLE_ADS_LIMITS.assetGroup.descriptionsMin).max(GOOGLE_ADS_LIMITS.assetGroup.descriptionsMax),
|
|
4552
|
-
businessName: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.businessNameMax),
|
|
4553
|
-
imageAssets: z8.array(refSchema).optional(),
|
|
4554
|
-
squareImageAssets: z8.array(refSchema).optional(),
|
|
4555
|
-
logoAssets: z8.array(refSchema).optional(),
|
|
4556
|
-
status: z8.enum(["ENABLED", "PAUSED"]).default("PAUSED")
|
|
4557
|
-
});
|
|
4558
|
-
var assetGroupUpdateSchema = z8.object({
|
|
4559
|
-
name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.nameMax).optional(),
|
|
4560
|
-
finalUrls: z8.array(httpsUrlSchema2).min(1).optional(),
|
|
4561
|
-
status: z8.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
|
|
4562
|
-
}).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
|
|
4563
4351
|
var audienceCreateSchema2 = z8.object({
|
|
4564
4352
|
name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.audience.nameMax),
|
|
4565
4353
|
type: z8.enum(USER_LIST_TYPES).default("BASIC"),
|
|
@@ -4590,9 +4378,6 @@ var conversionActionUpdateSchema = z8.object({
|
|
|
4590
4378
|
category: z8.enum(CONVERSION_ACTION_CATEGORIES).optional(),
|
|
4591
4379
|
countingType: z8.enum(CONVERSION_COUNTING_TYPES).optional(),
|
|
4592
4380
|
defaultValueMicros: microsSchema.optional(),
|
|
4593
|
-
defaultCurrencyCode: z8.string().length(3).optional(),
|
|
4594
|
-
clickThroughLookbackWindowDays: z8.number().int().positive().optional(),
|
|
4595
|
-
viewThroughLookbackWindowDays: z8.number().int().positive().optional(),
|
|
4596
4381
|
status: z8.enum(["ENABLED", "REMOVED", "HIDDEN"]).optional()
|
|
4597
4382
|
}).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
|
|
4598
4383
|
var biddingStrategyCreateSchema = z8.object({
|
|
@@ -4690,11 +4475,8 @@ var GOOGLE_DRAFT_OP_KINDS = [
|
|
|
4690
4475
|
"google.ad.resume",
|
|
4691
4476
|
"google.ad.remove",
|
|
4692
4477
|
"google.asset.create",
|
|
4693
|
-
"google.asset.update",
|
|
4694
4478
|
"google.assetLink.attach",
|
|
4695
4479
|
"google.assetLink.detach",
|
|
4696
|
-
"google.assetGroup.create",
|
|
4697
|
-
"google.assetGroup.update",
|
|
4698
4480
|
"google.audience.create",
|
|
4699
4481
|
"google.audienceCriterion.attach",
|
|
4700
4482
|
"google.audienceCriterion.detach",
|
|
@@ -4746,11 +4528,8 @@ var googleDraftOpInputSchema = z8.discriminatedUnion("kind", [
|
|
|
4746
4528
|
targetOp("google.ad.resume"),
|
|
4747
4529
|
targetOp("google.ad.remove"),
|
|
4748
4530
|
createOp2("google.asset.create", assetCreateSchema),
|
|
4749
|
-
updateOp2("google.asset.update", assetUpdateSchema),
|
|
4750
4531
|
createOp2("google.assetLink.attach", assetLinkAttachSchema),
|
|
4751
4532
|
targetOp("google.assetLink.detach"),
|
|
4752
|
-
createOp2("google.assetGroup.create", assetGroupCreateSchema),
|
|
4753
|
-
updateOp2("google.assetGroup.update", assetGroupUpdateSchema),
|
|
4754
4533
|
createOp2("google.audience.create", audienceCreateSchema2),
|
|
4755
4534
|
createOp2("google.audienceCriterion.attach", audienceCriterionAttachSchema),
|
|
4756
4535
|
targetOp("google.audienceCriterion.detach"),
|
|
@@ -4789,15 +4568,6 @@ var googleDraftStageResponseSchema = z9.object({
|
|
|
4789
4568
|
/** True when the op amended an already-staged op in place instead of appending a new one. */
|
|
4790
4569
|
amended: z9.boolean().optional()
|
|
4791
4570
|
});
|
|
4792
|
-
var googleDraftAmendRequestSchema = z9.object({
|
|
4793
|
-
chatId: z9.string(),
|
|
4794
|
-
ref: z9.string(),
|
|
4795
|
-
patch: z9.record(z9.string(), z9.unknown())
|
|
4796
|
-
});
|
|
4797
|
-
var googleDraftShowRequestSchema = z9.object({
|
|
4798
|
-
chatId: z9.string(),
|
|
4799
|
-
ref: z9.string()
|
|
4800
|
-
});
|
|
4801
4571
|
var GOOGLE_DRAFT_BATCH_MAX = 500;
|
|
4802
4572
|
var googleDraftStageBatchRequestSchema = z9.object({
|
|
4803
4573
|
chatId: z9.string(),
|
|
@@ -4827,13 +4597,6 @@ var googleDraftOpViewSchema = z9.object({
|
|
|
4827
4597
|
stagedAt: z9.number(),
|
|
4828
4598
|
result: googleDraftOpResultSchema.optional()
|
|
4829
4599
|
});
|
|
4830
|
-
var googleDraftShowResponseSchema = z9.object({
|
|
4831
|
-
op: googleDraftOpViewSchema.extend({
|
|
4832
|
-
payload: z9.unknown().optional(),
|
|
4833
|
-
warnings: z9.array(z9.string()).optional(),
|
|
4834
|
-
annotations: z9.unknown().optional()
|
|
4835
|
-
})
|
|
4836
|
-
});
|
|
4837
4600
|
var googleDraftListRequestSchema = z9.object({
|
|
4838
4601
|
chatId: z9.string()
|
|
4839
4602
|
});
|
|
@@ -5055,11 +4818,11 @@ function rawTextEntries(value) {
|
|
|
5055
4818
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
5056
4819
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
5057
4820
|
}
|
|
5058
|
-
function rawFileEntries(
|
|
5059
|
-
if (typeof
|
|
4821
|
+
function rawFileEntries(path14) {
|
|
4822
|
+
if (typeof path14 !== "string" || path14.length === 0) {
|
|
5060
4823
|
return [];
|
|
5061
4824
|
}
|
|
5062
|
-
return readFileSync3(
|
|
4825
|
+
return readFileSync3(path14, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
5063
4826
|
}
|
|
5064
4827
|
function keywordEntries(args) {
|
|
5065
4828
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -5082,19 +4845,19 @@ function keywordEntries(args) {
|
|
|
5082
4845
|
}
|
|
5083
4846
|
return entries;
|
|
5084
4847
|
}
|
|
5085
|
-
function loadJsonFileArg(
|
|
5086
|
-
if (typeof
|
|
4848
|
+
function loadJsonFileArg(path14) {
|
|
4849
|
+
if (typeof path14 !== "string" || path14.length === 0) {
|
|
5087
4850
|
return {};
|
|
5088
4851
|
}
|
|
5089
4852
|
try {
|
|
5090
|
-
const parsed = JSON.parse(readFileSync3(
|
|
4853
|
+
const parsed = JSON.parse(readFileSync3(path14, "utf8"));
|
|
5091
4854
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
5092
|
-
failWriteValidation(`${
|
|
4855
|
+
failWriteValidation(`${path14} must contain a JSON object`);
|
|
5093
4856
|
}
|
|
5094
4857
|
return parsed;
|
|
5095
4858
|
} catch (err) {
|
|
5096
4859
|
if (err instanceof SyntaxError) {
|
|
5097
|
-
failWriteValidation(`${
|
|
4860
|
+
failWriteValidation(`${path14} is not valid JSON: ${err.message}`);
|
|
5098
4861
|
}
|
|
5099
4862
|
throw err;
|
|
5100
4863
|
}
|
|
@@ -5108,28 +4871,6 @@ function mergePayload(file, flags) {
|
|
|
5108
4871
|
}
|
|
5109
4872
|
return merged;
|
|
5110
4873
|
}
|
|
5111
|
-
function loadPatchArg(args) {
|
|
5112
|
-
const file = typeof args.file === "string" && args.file.length > 0 ? args.file : void 0;
|
|
5113
|
-
const inline = typeof args.patch === "string" && args.patch.length > 0 ? args.patch : void 0;
|
|
5114
|
-
if (Boolean(file) === Boolean(inline)) {
|
|
5115
|
-
failWriteValidation("pass exactly one of --file <patch.json> or --patch '<json>'");
|
|
5116
|
-
}
|
|
5117
|
-
if (file) {
|
|
5118
|
-
return loadJsonFileArg(file);
|
|
5119
|
-
}
|
|
5120
|
-
try {
|
|
5121
|
-
const parsed = JSON.parse(inline);
|
|
5122
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
5123
|
-
failWriteValidation("--patch must be a JSON object");
|
|
5124
|
-
}
|
|
5125
|
-
return parsed;
|
|
5126
|
-
} catch (err) {
|
|
5127
|
-
if (err instanceof SyntaxError) {
|
|
5128
|
-
failWriteValidation(`--patch is not valid JSON: ${err.message}`);
|
|
5129
|
-
}
|
|
5130
|
-
throw err;
|
|
5131
|
-
}
|
|
5132
|
-
}
|
|
5133
4874
|
function handleGoogleError(err) {
|
|
5134
4875
|
if (err instanceof ApiError) {
|
|
5135
4876
|
writeJsonEnvelope({ ok: false, error: { code: err.code ?? "API_ERROR", message: err.message } });
|
|
@@ -5205,10 +4946,10 @@ async function stageUpdate(kind, customerId, target, payload) {
|
|
|
5205
4946
|
async function stageTarget(kind, customerId, target) {
|
|
5206
4947
|
await stageGoogleOp({ kind, customerId, target });
|
|
5207
4948
|
}
|
|
5208
|
-
async function draftAction(
|
|
4949
|
+
async function draftAction(path14, body) {
|
|
5209
4950
|
try {
|
|
5210
4951
|
const chatId = requireChatId();
|
|
5211
|
-
const response = await apiPost(
|
|
4952
|
+
const response = await apiPost(path14, { chatId, ...body });
|
|
5212
4953
|
writeJsonEnvelope(response);
|
|
5213
4954
|
} catch (err) {
|
|
5214
4955
|
handleGoogleError(err);
|
|
@@ -5231,7 +4972,7 @@ async function draftListAction(json) {
|
|
|
5231
4972
|
|
|
5232
4973
|
// src/commands/ads/google/draft.ts
|
|
5233
4974
|
var draftCommand2 = defineCommand22({
|
|
5234
|
-
meta: { name: "draft", description: "List, remove,
|
|
4975
|
+
meta: { name: "draft", description: "List, remove, or clear staged Google Ads write ops for this chat" },
|
|
5235
4976
|
subCommands: {
|
|
5236
4977
|
list: defineCommand22({
|
|
5237
4978
|
meta: {
|
|
@@ -5255,42 +4996,6 @@ var draftCommand2 = defineCommand22({
|
|
|
5255
4996
|
run: async () => {
|
|
5256
4997
|
await draftAction("/api/ads/google/draft/clear", {});
|
|
5257
4998
|
}
|
|
5258
|
-
}),
|
|
5259
|
-
amend: defineCommand22({
|
|
5260
|
-
meta: {
|
|
5261
|
-
name: "amend",
|
|
5262
|
-
description: "Update a staged op in place \u2014 merges a JSON patch into its payload (RFC-7386: objects deep-merge, null deletes a key, arrays/scalars replace) and re-validates. Use this instead of remove + re-create."
|
|
5263
|
-
},
|
|
5264
|
-
args: {
|
|
5265
|
-
ref: {
|
|
5266
|
-
type: "positional",
|
|
5267
|
-
description: "Staged op ref (g_temp_*) or target id/resource name",
|
|
5268
|
-
required: false
|
|
5269
|
-
},
|
|
5270
|
-
file: { type: "string", description: "JSON file with the patch object" },
|
|
5271
|
-
patch: { type: "string", description: "Inline JSON patch object" }
|
|
5272
|
-
},
|
|
5273
|
-
run: async ({ args }) => {
|
|
5274
|
-
const ref = requireTarget(args, "op");
|
|
5275
|
-
const patch = loadPatchArg(args);
|
|
5276
|
-
await draftAction("/api/ads/google/draft/amend", { ref, patch });
|
|
5277
|
-
}
|
|
5278
|
-
}),
|
|
5279
|
-
show: defineCommand22({
|
|
5280
|
-
meta: {
|
|
5281
|
-
name: "show",
|
|
5282
|
-
description: "Print the full staged payload for one op \u2014 the receipt to verify a change looks right before publish (never truncated)."
|
|
5283
|
-
},
|
|
5284
|
-
args: {
|
|
5285
|
-
ref: {
|
|
5286
|
-
type: "positional",
|
|
5287
|
-
description: "Staged op ref (g_temp_*) or target id/resource name",
|
|
5288
|
-
required: false
|
|
5289
|
-
}
|
|
5290
|
-
},
|
|
5291
|
-
run: async ({ args }) => {
|
|
5292
|
-
await draftAction("/api/ads/google/draft/show", { ref: requireTarget(args, "op") });
|
|
5293
|
-
}
|
|
5294
4999
|
})
|
|
5295
5000
|
}
|
|
5296
5001
|
});
|
|
@@ -7014,55 +6719,6 @@ function rdaContentFromFlags(args, base) {
|
|
|
7014
6719
|
}
|
|
7015
6720
|
return content;
|
|
7016
6721
|
}
|
|
7017
|
-
function videoContentFromFlags(args, base) {
|
|
7018
|
-
const content = {
|
|
7019
|
-
format: "video",
|
|
7020
|
-
...base !== null && typeof base === "object" && !Array.isArray(base) ? base : {}
|
|
7021
|
-
};
|
|
7022
|
-
const videoAssets = listFlag(args["video-assets"]);
|
|
7023
|
-
if (videoAssets) {
|
|
7024
|
-
content.videoAssets = videoAssets;
|
|
7025
|
-
}
|
|
7026
|
-
const finalUrls = listFlag(args["final-url"]);
|
|
7027
|
-
if (finalUrls) {
|
|
7028
|
-
content.finalUrls = finalUrls;
|
|
7029
|
-
}
|
|
7030
|
-
return content;
|
|
7031
|
-
}
|
|
7032
|
-
function demandGenContentFromFlags(args, base) {
|
|
7033
|
-
const content = {
|
|
7034
|
-
format: "demandGen",
|
|
7035
|
-
...base !== null && typeof base === "object" && !Array.isArray(base) ? base : {}
|
|
7036
|
-
};
|
|
7037
|
-
const headlines = listFlag(args.headlines);
|
|
7038
|
-
if (headlines) {
|
|
7039
|
-
content.headlines = headlines.map((text) => ({ text }));
|
|
7040
|
-
}
|
|
7041
|
-
const descriptions = listFlag(args.descriptions);
|
|
7042
|
-
if (descriptions) {
|
|
7043
|
-
content.descriptions = descriptions.map((text) => ({ text }));
|
|
7044
|
-
}
|
|
7045
|
-
if (typeof args["business-name"] === "string") {
|
|
7046
|
-
content.businessName = args["business-name"];
|
|
7047
|
-
}
|
|
7048
|
-
const finalUrls = listFlag(args["final-url"]);
|
|
7049
|
-
if (finalUrls) {
|
|
7050
|
-
content.finalUrls = finalUrls;
|
|
7051
|
-
}
|
|
7052
|
-
const images = listFlag(args["image-assets"]);
|
|
7053
|
-
if (images) {
|
|
7054
|
-
content.imageAssets = images;
|
|
7055
|
-
}
|
|
7056
|
-
const squareImages = listFlag(args["square-image-assets"]);
|
|
7057
|
-
if (squareImages) {
|
|
7058
|
-
content.squareImageAssets = squareImages;
|
|
7059
|
-
}
|
|
7060
|
-
const logoImages = listFlag(args["logo-image-assets"]);
|
|
7061
|
-
if (logoImages) {
|
|
7062
|
-
content.logoImageAssets = logoImages;
|
|
7063
|
-
}
|
|
7064
|
-
return content;
|
|
7065
|
-
}
|
|
7066
6722
|
function adContentFromFlags(args, format, fileContent) {
|
|
7067
6723
|
if (format === "responsiveSearch") {
|
|
7068
6724
|
return fileContent ?? rsaContentFromFlags(args);
|
|
@@ -7070,12 +6726,6 @@ function adContentFromFlags(args, format, fileContent) {
|
|
|
7070
6726
|
if (format === "responsiveDisplay") {
|
|
7071
6727
|
return rdaContentFromFlags(args, fileContent);
|
|
7072
6728
|
}
|
|
7073
|
-
if (format === "video") {
|
|
7074
|
-
return videoContentFromFlags(args, fileContent);
|
|
7075
|
-
}
|
|
7076
|
-
if (format === "demandGen") {
|
|
7077
|
-
return demandGenContentFromFlags(args, fileContent);
|
|
7078
|
-
}
|
|
7079
6729
|
return fileContent ?? failWriteValidation(`--format ${format} needs --file with the ad content`);
|
|
7080
6730
|
}
|
|
7081
6731
|
var adsCommand = defineCommand30({
|
|
@@ -7084,7 +6734,7 @@ var adsCommand = defineCommand30({
|
|
|
7084
6734
|
create: defineCommand30({
|
|
7085
6735
|
meta: {
|
|
7086
6736
|
name: "create",
|
|
7087
|
-
description: "Stage an ad (responsive search via flags; other formats via --file). RSA needs 3\u201315 headlines and 2\u20134 descriptions \u2014 give Google 8\u201312 varied headlines to test, and stage 2\u20134 ads per ad group.
|
|
6737
|
+
description: "Stage an ad (responsive search via flags; other formats via --file). RSA needs 3\u201315 headlines and 2\u20134 descriptions \u2014 give Google 8\u201312 varied headlines to test, and stage 2\u20134 ads per ad group."
|
|
7088
6738
|
},
|
|
7089
6739
|
args: {
|
|
7090
6740
|
...customerIdArg,
|
|
@@ -7092,14 +6742,14 @@ var adsCommand = defineCommand30({
|
|
|
7092
6742
|
"ad-group-ref": { type: "string", description: "Ad group ref" },
|
|
7093
6743
|
format: {
|
|
7094
6744
|
type: "string",
|
|
7095
|
-
description: "responsiveSearch (default) | responsiveDisplay | call | app | video | demandGen"
|
|
6745
|
+
description: "responsiveSearch (default) | responsiveDisplay | performanceMaxAssetGroup | call | app | video | demandGen"
|
|
7096
6746
|
},
|
|
7097
|
-
headlines: { type: "string", description: "Comma-separated headlines (RSA/RDA
|
|
7098
|
-
descriptions: { type: "string", description: "Comma-separated descriptions (RSA/RDA
|
|
6747
|
+
headlines: { type: "string", description: "Comma-separated headlines (RSA/RDA)" },
|
|
6748
|
+
descriptions: { type: "string", description: "Comma-separated descriptions (RSA/RDA)" },
|
|
7099
6749
|
path1: { type: "string" },
|
|
7100
6750
|
path2: { type: "string" },
|
|
7101
6751
|
"long-headline": { type: "string", description: "Long headline (responsiveDisplay)" },
|
|
7102
|
-
"business-name": { type: "string", description: "Business name (responsiveDisplay
|
|
6752
|
+
"business-name": { type: "string", description: "Business name (responsiveDisplay)" },
|
|
7103
6753
|
"marketing-images": {
|
|
7104
6754
|
type: "string",
|
|
7105
6755
|
description: "Comma-separated marketing image asset refs (responsiveDisplay)"
|
|
@@ -7109,13 +6759,6 @@ var adsCommand = defineCommand30({
|
|
|
7109
6759
|
description: "Comma-separated square marketing image asset refs (responsiveDisplay)"
|
|
7110
6760
|
},
|
|
7111
6761
|
"logo-images": { type: "string", description: "Comma-separated logo image asset refs (responsiveDisplay)" },
|
|
7112
|
-
"video-assets": {
|
|
7113
|
-
type: "string",
|
|
7114
|
-
description: `Comma-separated video asset refs (video format) \u2014 stage the YouTube video as an asset first (assets create --file '{"type":"youtubeVideo",\u2026}')`
|
|
7115
|
-
},
|
|
7116
|
-
"image-assets": { type: "string", description: "Comma-separated image asset refs (demandGen)" },
|
|
7117
|
-
"square-image-assets": { type: "string", description: "Comma-separated square image asset refs (demandGen)" },
|
|
7118
|
-
"logo-image-assets": { type: "string", description: "Comma-separated logo image asset refs (demandGen)" },
|
|
7119
6762
|
"final-url": { type: "string", description: "Comma-separated final URLs" },
|
|
7120
6763
|
status: { type: "string" }
|
|
7121
6764
|
},
|
|
@@ -7150,78 +6793,6 @@ var adsCommand = defineCommand30({
|
|
|
7150
6793
|
remove: statusCommand2("google.ad.remove", "ad")
|
|
7151
6794
|
}
|
|
7152
6795
|
});
|
|
7153
|
-
var assetGroupsCommand = defineCommand30({
|
|
7154
|
-
meta: {
|
|
7155
|
-
name: "asset-groups",
|
|
7156
|
-
description: "Stage Performance Max asset group create/update \u2014 the PMax campaign's copy and images."
|
|
7157
|
-
},
|
|
7158
|
-
subCommands: {
|
|
7159
|
-
create: defineCommand30({
|
|
7160
|
-
meta: {
|
|
7161
|
-
name: "create",
|
|
7162
|
-
description: "Stage a Performance Max asset group: 3\u201315 headlines (\u226430ch), 1\u20135 long headlines (\u226490ch), 2\u20135 descriptions (\u226490ch), a business name, and image refs."
|
|
7163
|
-
},
|
|
7164
|
-
args: {
|
|
7165
|
-
...customerIdArg,
|
|
7166
|
-
...fileArg,
|
|
7167
|
-
"campaign-ref": {
|
|
7168
|
-
type: "string",
|
|
7169
|
-
description: "Performance Max campaign ref (g_temp_*, id, or resource name)"
|
|
7170
|
-
},
|
|
7171
|
-
name: { type: "string", description: "Asset group name" },
|
|
7172
|
-
"final-urls": { type: "string", description: "Comma-separated final URLs" },
|
|
7173
|
-
headlines: { type: "string", description: "Comma-separated headlines (3\u201315, \u226430 chars)" },
|
|
7174
|
-
"long-headlines": { type: "string", description: "Comma-separated long headlines (1\u20135, \u226490 chars)" },
|
|
7175
|
-
descriptions: { type: "string", description: "Comma-separated descriptions (2\u20135, \u226490 chars)" },
|
|
7176
|
-
"business-name": { type: "string" },
|
|
7177
|
-
"image-assets": { type: "string", description: "Comma-separated marketing image asset refs" },
|
|
7178
|
-
"square-image-assets": { type: "string", description: "Comma-separated square marketing image asset refs" },
|
|
7179
|
-
"logo-assets": { type: "string", description: "Comma-separated logo image asset refs" },
|
|
7180
|
-
status: { type: "string", description: "ENABLED | PAUSED (default PAUSED)" }
|
|
7181
|
-
},
|
|
7182
|
-
run: async ({ args }) => {
|
|
7183
|
-
const customerId = requireCustomerId(args);
|
|
7184
|
-
const headlines = listFlag(args.headlines);
|
|
7185
|
-
const longHeadlines = listFlag(args["long-headlines"]);
|
|
7186
|
-
const descriptions = listFlag(args.descriptions);
|
|
7187
|
-
const payload = mergePayload(loadJsonFileArg(args.file), {
|
|
7188
|
-
campaign: args["campaign-ref"],
|
|
7189
|
-
name: args.name,
|
|
7190
|
-
finalUrls: listFlag(args["final-urls"]),
|
|
7191
|
-
headlines: headlines?.map((text) => ({ text })),
|
|
7192
|
-
longHeadlines: longHeadlines?.map((text) => ({ text })),
|
|
7193
|
-
descriptions: descriptions?.map((text) => ({ text })),
|
|
7194
|
-
businessName: args["business-name"],
|
|
7195
|
-
imageAssets: listFlag(args["image-assets"]),
|
|
7196
|
-
squareImageAssets: listFlag(args["square-image-assets"]),
|
|
7197
|
-
logoAssets: listFlag(args["logo-assets"]),
|
|
7198
|
-
status: args.status
|
|
7199
|
-
});
|
|
7200
|
-
await stageCreate("google.assetGroup.create", customerId, payload);
|
|
7201
|
-
}
|
|
7202
|
-
}),
|
|
7203
|
-
update: defineCommand30({
|
|
7204
|
-
meta: { name: "update", description: "Stage a Performance Max asset group update" },
|
|
7205
|
-
args: {
|
|
7206
|
-
...customerIdArg,
|
|
7207
|
-
...fileArg,
|
|
7208
|
-
name: { type: "string" },
|
|
7209
|
-
"final-urls": { type: "string", description: "Comma-separated final URLs" },
|
|
7210
|
-
status: { type: "string", description: "ENABLED | PAUSED | REMOVED" }
|
|
7211
|
-
},
|
|
7212
|
-
run: async ({ args }) => {
|
|
7213
|
-
const customerId = requireCustomerId(args);
|
|
7214
|
-
const target = requireTarget(args, "asset group");
|
|
7215
|
-
const payload = mergePayload(loadJsonFileArg(args.file), {
|
|
7216
|
-
name: args.name,
|
|
7217
|
-
finalUrls: listFlag(args["final-urls"]),
|
|
7218
|
-
status: args.status
|
|
7219
|
-
});
|
|
7220
|
-
await stageUpdate("google.assetGroup.update", customerId, target, payload);
|
|
7221
|
-
}
|
|
7222
|
-
})
|
|
7223
|
-
}
|
|
7224
|
-
});
|
|
7225
6796
|
function fileCreateCommand(name, kind, description) {
|
|
7226
6797
|
return defineCommand30({
|
|
7227
6798
|
meta: { name, description },
|
|
@@ -7236,47 +6807,10 @@ function fileCreateCommand(name, kind, description) {
|
|
|
7236
6807
|
}
|
|
7237
6808
|
});
|
|
7238
6809
|
}
|
|
7239
|
-
var assetsUpdateCommand = defineCommand30({
|
|
7240
|
-
meta: {
|
|
7241
|
-
name: "update",
|
|
7242
|
-
description: "Update a staged or live asset in place (flat partial \u2014 set only the fields that changed). Works across asset types: sitelink (--link-text/--description1/--description2/--final-urls), callout (--callout-text), structured snippet (--header/--values), call-to-action (--cta), text (--text), or --name."
|
|
7243
|
-
},
|
|
7244
|
-
args: {
|
|
7245
|
-
...customerIdArg,
|
|
7246
|
-
...fileArg,
|
|
7247
|
-
name: { type: "string", description: "Asset display name" },
|
|
7248
|
-
"link-text": { type: "string", description: "Sitelink link text" },
|
|
7249
|
-
description1: { type: "string", description: "Sitelink description line 1" },
|
|
7250
|
-
description2: { type: "string", description: "Sitelink description line 2" },
|
|
7251
|
-
"final-urls": { type: "string", description: "Comma-separated sitelink final URLs" },
|
|
7252
|
-
"callout-text": { type: "string", description: "Callout text" },
|
|
7253
|
-
header: { type: "string", description: "Structured snippet header" },
|
|
7254
|
-
values: { type: "string", description: "Comma-separated structured snippet values" },
|
|
7255
|
-
cta: { type: "string", description: "Call-to-action text" },
|
|
7256
|
-
text: { type: "string", description: "Text asset content" }
|
|
7257
|
-
},
|
|
7258
|
-
run: async ({ args }) => {
|
|
7259
|
-
const customerId = requireCustomerId(args);
|
|
7260
|
-
const target = requireTarget(args, "asset");
|
|
7261
|
-
const payload = mergePayload(loadJsonFileArg(args.file), {
|
|
7262
|
-
name: args.name,
|
|
7263
|
-
linkText: args["link-text"],
|
|
7264
|
-
description1: args.description1,
|
|
7265
|
-
description2: args.description2,
|
|
7266
|
-
finalUrls: listFlag(args["final-urls"]),
|
|
7267
|
-
calloutText: args["callout-text"],
|
|
7268
|
-
header: args.header,
|
|
7269
|
-
values: listFlag(args.values),
|
|
7270
|
-
callToAction: args.cta,
|
|
7271
|
-
text: args.text
|
|
7272
|
-
});
|
|
7273
|
-
await stageUpdate("google.asset.update", customerId, target, payload);
|
|
7274
|
-
}
|
|
7275
|
-
});
|
|
7276
6810
|
var assetsCommand = defineCommand30({
|
|
7277
6811
|
meta: {
|
|
7278
6812
|
name: "assets",
|
|
7279
|
-
description: "Stage asset create
|
|
6813
|
+
description: "Stage asset create + asset-link attach/detach (via --file). For a high-performance Search campaign add \u22654 sitelinks, \u22653 callouts, and \u22651 structured snippet per campaign."
|
|
7280
6814
|
},
|
|
7281
6815
|
subCommands: {
|
|
7282
6816
|
create: fileCreateCommand(
|
|
@@ -7284,7 +6818,6 @@ var assetsCommand = defineCommand30({
|
|
|
7284
6818
|
"google.asset.create",
|
|
7285
6819
|
"Stage an asset (text/image/sitelink/callout/structuredSnippet/\u2026)"
|
|
7286
6820
|
),
|
|
7287
|
-
update: assetsUpdateCommand,
|
|
7288
6821
|
attach: fileCreateCommand("attach", "google.assetLink.attach", "Attach an asset to a campaign/adGroup/customer"),
|
|
7289
6822
|
detach: statusCommand2("google.assetLink.detach", "asset link")
|
|
7290
6823
|
}
|
|
@@ -7380,7 +6913,6 @@ var googleWriteCommands = {
|
|
|
7380
6913
|
"keyword-lists": keywordListsCommand,
|
|
7381
6914
|
ads: adsCommand,
|
|
7382
6915
|
assets: assetsCommand,
|
|
7383
|
-
"asset-groups": assetGroupsCommand,
|
|
7384
6916
|
audiences: audiencesCommand,
|
|
7385
6917
|
conversions: conversionsCommand,
|
|
7386
6918
|
"bidding-strategies": biddingStrategiesCommand,
|
|
@@ -7966,11 +7498,6 @@ registerSchema({
|
|
|
7966
7498
|
...writeAccountArgs,
|
|
7967
7499
|
"intended-status": { type: "string", description: "ACTIVE|PAUSED|ARCHIVED|DRAFT", required: false },
|
|
7968
7500
|
name: { type: "string", description: "New creative name", required: false },
|
|
7969
|
-
campaign: {
|
|
7970
|
-
type: "string",
|
|
7971
|
-
description: "Re-parent to a different ad set (id/URN or li_temp_*) \u2014 staged creatives only",
|
|
7972
|
-
required: false
|
|
7973
|
-
},
|
|
7974
7501
|
headline: { type: "string", description: "New headline", required: false },
|
|
7975
7502
|
intro: { type: "string", description: "Commentary / intro text", required: false },
|
|
7976
7503
|
description: { type: "string", description: "Description (text/spotlight)", required: false },
|
|
@@ -8963,19 +8490,19 @@ function failWriteValidation2(message) {
|
|
|
8963
8490
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
8964
8491
|
process.exit(1);
|
|
8965
8492
|
}
|
|
8966
|
-
function loadJsonFileArg2(
|
|
8967
|
-
if (typeof
|
|
8493
|
+
function loadJsonFileArg2(path14) {
|
|
8494
|
+
if (typeof path14 !== "string" || path14.length === 0) {
|
|
8968
8495
|
return {};
|
|
8969
8496
|
}
|
|
8970
8497
|
try {
|
|
8971
|
-
const parsed = JSON.parse(readFileSync7(
|
|
8498
|
+
const parsed = JSON.parse(readFileSync7(path14, "utf8"));
|
|
8972
8499
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8973
|
-
failWriteValidation2(`${
|
|
8500
|
+
failWriteValidation2(`${path14} must contain a JSON object`);
|
|
8974
8501
|
}
|
|
8975
8502
|
return parsed;
|
|
8976
8503
|
} catch (err) {
|
|
8977
8504
|
if (err instanceof SyntaxError) {
|
|
8978
|
-
failWriteValidation2(`${
|
|
8505
|
+
failWriteValidation2(`${path14} is not valid JSON: ${err.message}`);
|
|
8979
8506
|
}
|
|
8980
8507
|
throw err;
|
|
8981
8508
|
}
|
|
@@ -8989,28 +8516,6 @@ function mergePayload2(file, flags) {
|
|
|
8989
8516
|
}
|
|
8990
8517
|
return merged;
|
|
8991
8518
|
}
|
|
8992
|
-
function loadPatchArg2(args) {
|
|
8993
|
-
const file = typeof args.file === "string" && args.file.length > 0 ? args.file : void 0;
|
|
8994
|
-
const inline = typeof args.patch === "string" && args.patch.length > 0 ? args.patch : void 0;
|
|
8995
|
-
if (Boolean(file) === Boolean(inline)) {
|
|
8996
|
-
failWriteValidation2("pass exactly one of --file <patch.json> or --patch '<json>'");
|
|
8997
|
-
}
|
|
8998
|
-
if (file) {
|
|
8999
|
-
return loadJsonFileArg2(file);
|
|
9000
|
-
}
|
|
9001
|
-
try {
|
|
9002
|
-
const parsed = JSON.parse(inline);
|
|
9003
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9004
|
-
failWriteValidation2("--patch must be a JSON object");
|
|
9005
|
-
}
|
|
9006
|
-
return parsed;
|
|
9007
|
-
} catch (err) {
|
|
9008
|
-
if (err instanceof SyntaxError) {
|
|
9009
|
-
failWriteValidation2(`--patch is not valid JSON: ${err.message}`);
|
|
9010
|
-
}
|
|
9011
|
-
throw err;
|
|
9012
|
-
}
|
|
9013
|
-
}
|
|
9014
8519
|
function parseMoneyFlag(amount, currency, flag) {
|
|
9015
8520
|
if (amount === void 0 || amount === null || amount === "") {
|
|
9016
8521
|
return void 0;
|
|
@@ -9060,15 +8565,15 @@ function parseLocaleFlag(value) {
|
|
|
9060
8565
|
}
|
|
9061
8566
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
9062
8567
|
}
|
|
9063
|
-
function loadTargetingFileArg(
|
|
9064
|
-
if (typeof
|
|
8568
|
+
function loadTargetingFileArg(path14) {
|
|
8569
|
+
if (typeof path14 !== "string" || path14.length === 0) {
|
|
9065
8570
|
return void 0;
|
|
9066
8571
|
}
|
|
9067
|
-
const parsed = loadJsonFileArg2(
|
|
8572
|
+
const parsed = loadJsonFileArg2(path14);
|
|
9068
8573
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
9069
8574
|
if (!criteria.include) {
|
|
9070
8575
|
failWriteValidation2(
|
|
9071
|
-
`${
|
|
8576
|
+
`${path14} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
9072
8577
|
);
|
|
9073
8578
|
}
|
|
9074
8579
|
return criteria;
|
|
@@ -9103,14 +8608,14 @@ function parseCsvLine(line) {
|
|
|
9103
8608
|
cells.push(current);
|
|
9104
8609
|
return cells.map((cell) => cell.trim());
|
|
9105
8610
|
}
|
|
9106
|
-
function parseListFileArg(
|
|
9107
|
-
if (typeof
|
|
8611
|
+
function parseListFileArg(path14, maxRows) {
|
|
8612
|
+
if (typeof path14 !== "string" || path14.length === 0) {
|
|
9108
8613
|
return void 0;
|
|
9109
8614
|
}
|
|
9110
|
-
const raw = readFileSync7(
|
|
8615
|
+
const raw = readFileSync7(path14, "utf8");
|
|
9111
8616
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
9112
8617
|
if (lines.length < 2) {
|
|
9113
|
-
failWriteValidation2(`${
|
|
8618
|
+
failWriteValidation2(`${path14} needs a header row and at least one data row`);
|
|
9114
8619
|
}
|
|
9115
8620
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
9116
8621
|
const rows = [];
|
|
@@ -9129,7 +8634,7 @@ function parseListFileArg(path12, maxRows) {
|
|
|
9129
8634
|
}
|
|
9130
8635
|
}
|
|
9131
8636
|
if (rows.length > maxRows) {
|
|
9132
|
-
failWriteValidation2(`${
|
|
8637
|
+
failWriteValidation2(`${path14} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
9133
8638
|
}
|
|
9134
8639
|
return { columns, rows };
|
|
9135
8640
|
}
|
|
@@ -9309,21 +8814,11 @@ Example: baker ads linkedin campaigns create --name "ABM Tier-1" --group li_temp
|
|
|
9309
8814
|
await stageOp({ kind: "campaign.create", accountId, payload });
|
|
9310
8815
|
}
|
|
9311
8816
|
});
|
|
9312
|
-
function campaignUpdatePayloadFromFlags(args, target, file) {
|
|
9313
|
-
const flags = campaignPayloadFromFlags(args);
|
|
9314
|
-
if (!target.startsWith(TEMP_REF_PREFIX)) {
|
|
9315
|
-
flags.campaignGroup = void 0;
|
|
9316
|
-
flags.type = void 0;
|
|
9317
|
-
flags.locale = void 0;
|
|
9318
|
-
flags.associatedEntity = void 0;
|
|
9319
|
-
}
|
|
9320
|
-
return mergePayload2(file, flags);
|
|
9321
|
-
}
|
|
9322
8817
|
var campaignsUpdateCommand = defineCommand38({
|
|
9323
8818
|
meta: {
|
|
9324
8819
|
name: "update",
|
|
9325
8820
|
description: `Stage changes to an existing campaign (budget, bid, targeting, flags, schedule, status). ${STAGED_NOTE}
|
|
9326
|
-
Pass a li_temp_* ref to amend a campaign staged in this chat \u2014 fields merge into the staged create
|
|
8821
|
+
Pass a li_temp_* ref to amend a campaign staged in this chat \u2014 fields merge into the staged create.
|
|
9327
8822
|
Example: baker ads linkedin campaigns update 123456 --daily-budget 100 --currency EUR --audience-expansion off`
|
|
9328
8823
|
},
|
|
9329
8824
|
args: {
|
|
@@ -9332,9 +8827,18 @@ Example: baker ads linkedin campaigns update 123456 --daily-budget 100 --currenc
|
|
|
9332
8827
|
},
|
|
9333
8828
|
run: async ({ args }) => {
|
|
9334
8829
|
const accountId = bareAccountId(args);
|
|
9335
|
-
const
|
|
9336
|
-
|
|
9337
|
-
|
|
8830
|
+
const flags = campaignPayloadFromFlags(args);
|
|
8831
|
+
flags.campaignGroup = void 0;
|
|
8832
|
+
flags.type = void 0;
|
|
8833
|
+
flags.locale = void 0;
|
|
8834
|
+
flags.associatedEntity = void 0;
|
|
8835
|
+
const payload = mergePayload2(loadJsonFileArg2(args.file), flags);
|
|
8836
|
+
await stageOp({
|
|
8837
|
+
kind: "campaign.update",
|
|
8838
|
+
accountId,
|
|
8839
|
+
target: requireTarget2(args, "campaign"),
|
|
8840
|
+
payload
|
|
8841
|
+
});
|
|
9338
8842
|
}
|
|
9339
8843
|
});
|
|
9340
8844
|
function statusSugarCommand(entity, kind, name) {
|
|
@@ -9585,21 +9089,15 @@ var creativesUpdateCommand = defineCommand38({
|
|
|
9585
9089
|
name: "update",
|
|
9586
9090
|
description: `Stage changes to an existing creative, or amend a creative staged in this chat by passing its li_temp_* ref. ${STAGED_NOTE}
|
|
9587
9091
|
Direct-content ads (text/spotlight/follower/jobs) update copy/media/URL IN PLACE \u2014 changed fields merge into the ad's current content. Post-based ads (image/video/carousel/\u2026) can't edit content; use \`creatives duplicate <id> --headline "\u2026" --replace\` for those. Staged creatives (li_temp_*) accept all content flags and re-validate.
|
|
9588
|
-
--campaign re-parents the ad to a different ad set \u2014 LinkedIn has no in-place move API, so this only works while the creative is still staged (li_temp_*); passing it against a live creative id/URN is rejected at stage time.
|
|
9589
9092
|
Examples:
|
|
9590
9093
|
baker ads linkedin creatives update 1458423674 --headline "Sharper headline" --description "New copy."
|
|
9591
|
-
baker ads linkedin creatives update urn:li:sponsoredCreative:789 --intended-status PAUSED
|
|
9592
|
-
baker ads linkedin creatives update li_temp_x3 --campaign li_temp_x2`
|
|
9094
|
+
baker ads linkedin creatives update urn:li:sponsoredCreative:789 --intended-status PAUSED`
|
|
9593
9095
|
},
|
|
9594
9096
|
args: {
|
|
9595
9097
|
id: { type: "positional", description: "Creative id/URN, or li_temp_* ref staged in this chat", required: true },
|
|
9596
9098
|
...accountArgs,
|
|
9597
9099
|
"intended-status": { type: "string", description: "ACTIVE|PAUSED|ARCHIVED|DRAFT" },
|
|
9598
9100
|
name: { type: "string", description: "New creative name" },
|
|
9599
|
-
campaign: {
|
|
9600
|
-
type: "string",
|
|
9601
|
-
description: "Re-parent to a different ad set (id/URN or li_temp_*) \u2014 staged creatives only"
|
|
9602
|
-
},
|
|
9603
9101
|
intro: { type: "string", description: "Commentary / intro text" },
|
|
9604
9102
|
headline: { type: "string", description: "New headline" },
|
|
9605
9103
|
description: { type: "string", description: "Description (text/spotlight ads)" },
|
|
@@ -9627,7 +9125,6 @@ Examples:
|
|
|
9627
9125
|
const payload = mergePayload2(file, {
|
|
9628
9126
|
intendedStatus,
|
|
9629
9127
|
name: args.name,
|
|
9630
|
-
campaign: args.campaign,
|
|
9631
9128
|
content: Object.keys(content).length > 0 ? content : void 0
|
|
9632
9129
|
});
|
|
9633
9130
|
await stageOp({ kind: "creative.update", accountId, target, payload });
|
|
@@ -9771,10 +9268,7 @@ var leadFormsCreateCommand = defineCommand38({
|
|
|
9771
9268
|
meta: {
|
|
9772
9269
|
name: "create",
|
|
9773
9270
|
description: `Stage a new Lead Gen Form from a JSON file. ${STAGED_NOTE}
|
|
9774
|
-
|
|
9775
|
-
Each question is a predefined profile field ({ name, predefinedField: "EMAIL" }) or a custom question ({ name, questionType: "MULTIPLE_CHOICE", options: [...] }; \u22643 custom).
|
|
9776
|
-
Best-practice fields the preview will nudge for if missing: 1-3 qualifying questions, consents[] (disclosure checkboxes), thankYou.message + thankYou.landingUrl|appointmentUrl.
|
|
9777
|
-
Also supported: locale, formImageId|formImageUrn, privacyPolicyText, hiddenFields[], legalDisclaimer, thankYou.cta. Example: baker ads linkedin lead-forms create --file form.json`
|
|
9271
|
+
The file needs: name, headline (\u226460), privacyPolicyUrl, questions[] (\u226412; playbook: \u22644 for completion). Example: baker ads linkedin lead-forms create --file form.json`
|
|
9778
9272
|
},
|
|
9779
9273
|
args: {
|
|
9780
9274
|
...accountArgs,
|
|
@@ -10272,108 +9766,23 @@ function resolveRange(args) {
|
|
|
10272
9766
|
|
|
10273
9767
|
// src/commands/ads/linkedin/draft.ts
|
|
10274
9768
|
import { defineCommand as defineCommand45 } from "citty";
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
function badge2(node) {
|
|
10285
|
-
const label = node.operation ? OPERATION_LABELS2[node.operation] : void 0;
|
|
10286
|
-
if (label) {
|
|
10287
|
-
return label;
|
|
10288
|
-
}
|
|
10289
|
-
return node.existing ? "on LinkedIn" : "staged";
|
|
10290
|
-
}
|
|
10291
|
-
function collectionLine2(collection) {
|
|
10292
|
-
const parts = [];
|
|
10293
|
-
if (collection.added > 0) {
|
|
10294
|
-
parts.push(`+${collection.added}`);
|
|
10295
|
-
}
|
|
10296
|
-
if (collection.existing > 0) {
|
|
10297
|
-
parts.push(`${collection.existing} existing`);
|
|
10298
|
-
}
|
|
10299
|
-
if (collection.removed > 0) {
|
|
10300
|
-
parts.push(`-${collection.removed}`);
|
|
10301
|
-
}
|
|
10302
|
-
return parts.length > 0 ? `${collection.label}: ${parts.join(", ")}` : void 0;
|
|
10303
|
-
}
|
|
10304
|
-
function renderNode2(node, depth, lines) {
|
|
10305
|
-
const indent = " ".repeat(depth);
|
|
10306
|
-
lines.push(`${indent}\u2022 ${node.name} \xB7 ${badge2(node)}`);
|
|
10307
|
-
for (const warning of node.warnings ?? []) {
|
|
10308
|
-
lines.push(`${indent} \u26A0 ${warning}`);
|
|
10309
|
-
}
|
|
10310
|
-
for (const collection of node.collections) {
|
|
10311
|
-
const line = collectionLine2(collection);
|
|
10312
|
-
if (line) {
|
|
10313
|
-
lines.push(`${indent} ${line}`);
|
|
10314
|
-
}
|
|
10315
|
-
}
|
|
10316
|
-
for (const child of node.children) {
|
|
10317
|
-
renderNode2(child, depth + 1, lines);
|
|
10318
|
-
}
|
|
10319
|
-
}
|
|
10320
|
-
function renderSection2(title, nodes, lines) {
|
|
10321
|
-
if (nodes.length === 0) {
|
|
10322
|
-
return;
|
|
10323
|
-
}
|
|
10324
|
-
lines.push("");
|
|
10325
|
-
lines.push(title);
|
|
10326
|
-
for (const node of nodes) {
|
|
10327
|
-
renderNode2(node, 1, lines);
|
|
10328
|
-
}
|
|
10329
|
-
}
|
|
10330
|
-
var CAMPAIGN_TREE_ENTITIES = /* @__PURE__ */ new Set(["campaignGroup", "campaign", "creative"]);
|
|
10331
|
-
function renderDraftStatus2(data) {
|
|
10332
|
-
if (data.status === "none" || data.count === 0) {
|
|
10333
|
-
return "No staged LinkedIn changes on this chat.";
|
|
10334
|
-
}
|
|
10335
|
-
const modeNote = data.mode === "live" ? "Live \u2014 publishing writes directly to LinkedIn." : "Simulated \u2014 publishing completes the whole draft without calling LinkedIn.";
|
|
10336
|
-
const lines = [
|
|
10337
|
-
`LinkedIn Ads \u2014 ${data.count} staged change${data.count === 1 ? "" : "s"} \xB7 ${data.mode}`,
|
|
10338
|
-
modeNote
|
|
10339
|
-
];
|
|
10340
|
-
const tree = data.tree ?? [];
|
|
10341
|
-
renderSection2(
|
|
10342
|
-
"Campaigns",
|
|
10343
|
-
tree.filter((node) => CAMPAIGN_TREE_ENTITIES.has(node.entity)),
|
|
10344
|
-
lines
|
|
10345
|
-
);
|
|
10346
|
-
const rest = tree.filter((node) => !CAMPAIGN_TREE_ENTITIES.has(node.entity));
|
|
10347
|
-
renderSection2("Other", rest, lines);
|
|
10348
|
-
return lines.join("\n");
|
|
10349
|
-
}
|
|
10350
|
-
|
|
10351
|
-
// src/commands/ads/linkedin/draft.ts
|
|
10352
|
-
async function listDraft(json) {
|
|
10353
|
-
try {
|
|
10354
|
-
const chatId = requireChatId();
|
|
10355
|
-
const response = await apiPost("/api/ads/linkedin/draft", {
|
|
10356
|
-
chatId
|
|
10357
|
-
});
|
|
10358
|
-
if (json || !response.ok || !response.data) {
|
|
10359
|
-
writeJsonEnvelope(response);
|
|
10360
|
-
return;
|
|
10361
|
-
}
|
|
10362
|
-
process.stdout.write(`${renderDraftStatus2(response.data)}
|
|
10363
|
-
`);
|
|
10364
|
-
} catch (err) {
|
|
10365
|
-
handleLinkedinError(err);
|
|
9769
|
+
async function listDraft() {
|
|
9770
|
+
try {
|
|
9771
|
+
const chatId = requireChatId();
|
|
9772
|
+
const response = await apiPost("/api/ads/linkedin/draft", {
|
|
9773
|
+
chatId
|
|
9774
|
+
});
|
|
9775
|
+
writeJsonEnvelope(response);
|
|
9776
|
+
} catch (err) {
|
|
9777
|
+
handleLinkedinError(err);
|
|
10366
9778
|
}
|
|
10367
9779
|
}
|
|
10368
9780
|
var listCommand3 = defineCommand45({
|
|
10369
9781
|
meta: {
|
|
10370
9782
|
name: "list",
|
|
10371
|
-
description: "
|
|
9783
|
+
description: "Show every LinkedIn write op staged in this chat, its mode (simulated = publish will NOT hit LinkedIn), dependencies, and post-publish results. Example: baker ads linkedin draft"
|
|
10372
9784
|
},
|
|
10373
|
-
|
|
10374
|
-
run: async ({ args }) => {
|
|
10375
|
-
await listDraft(args.json === true);
|
|
10376
|
-
}
|
|
9785
|
+
run: listDraft
|
|
10377
9786
|
});
|
|
10378
9787
|
var removeCommand2 = defineCommand45({
|
|
10379
9788
|
meta: {
|
|
@@ -10417,70 +9826,17 @@ var clearCommand2 = defineCommand45({
|
|
|
10417
9826
|
}
|
|
10418
9827
|
}
|
|
10419
9828
|
});
|
|
10420
|
-
var amendCommand = defineCommand45({
|
|
10421
|
-
meta: {
|
|
10422
|
-
name: "amend",
|
|
10423
|
-
description: "Update a staged op in place \u2014 merges a JSON patch into its payload (RFC-7386: objects deep-merge, null deletes a key, arrays/scalars replace) and re-validates. Use this instead of remove + re-create."
|
|
10424
|
-
},
|
|
10425
|
-
args: {
|
|
10426
|
-
ref: { type: "positional", description: "Staged op ref (li_temp_*) or target id/URN", required: true },
|
|
10427
|
-
file: { type: "string", description: "JSON file with the patch object" },
|
|
10428
|
-
patch: { type: "string", description: "Inline JSON patch object" }
|
|
10429
|
-
},
|
|
10430
|
-
run: async ({ args }) => {
|
|
10431
|
-
const ref = requireTarget2(args, "op");
|
|
10432
|
-
const patch = loadPatchArg2(args);
|
|
10433
|
-
try {
|
|
10434
|
-
const chatId = requireChatId();
|
|
10435
|
-
const body = { chatId, ref, patch };
|
|
10436
|
-
const response = await apiPost(
|
|
10437
|
-
"/api/ads/linkedin/draft/amend",
|
|
10438
|
-
body
|
|
10439
|
-
);
|
|
10440
|
-
writeJsonEnvelope(response);
|
|
10441
|
-
} catch (err) {
|
|
10442
|
-
handleLinkedinError(err);
|
|
10443
|
-
}
|
|
10444
|
-
}
|
|
10445
|
-
});
|
|
10446
|
-
var showCommand = defineCommand45({
|
|
10447
|
-
meta: {
|
|
10448
|
-
name: "show",
|
|
10449
|
-
description: "Print the full staged payload for one op \u2014 the receipt to verify a change looks right before publish (never truncated)."
|
|
10450
|
-
},
|
|
10451
|
-
args: {
|
|
10452
|
-
ref: { type: "positional", description: "Staged op ref (li_temp_*) or target id/URN", required: true }
|
|
10453
|
-
},
|
|
10454
|
-
run: async ({ args }) => {
|
|
10455
|
-
const ref = requireTarget2(args, "op");
|
|
10456
|
-
try {
|
|
10457
|
-
const chatId = requireChatId();
|
|
10458
|
-
const body = { chatId, ref };
|
|
10459
|
-
const response = await apiPost(
|
|
10460
|
-
"/api/ads/linkedin/draft/show",
|
|
10461
|
-
body
|
|
10462
|
-
);
|
|
10463
|
-
writeJsonEnvelope(response);
|
|
10464
|
-
} catch (err) {
|
|
10465
|
-
handleLinkedinError(err);
|
|
10466
|
-
}
|
|
10467
|
-
}
|
|
10468
|
-
});
|
|
10469
9829
|
var linkedinDraftCommand = defineCommand45({
|
|
10470
9830
|
meta: {
|
|
10471
9831
|
name: "draft",
|
|
10472
|
-
description: "Review and edit the LinkedIn write ops staged in this chat BEFORE publish. Subcommands: list (default), remove, clear
|
|
9832
|
+
description: "Review and edit the LinkedIn write ops staged in this chat BEFORE publish. Subcommands: list (default), remove, clear. Ops never hit LinkedIn until the chat is published."
|
|
10473
9833
|
},
|
|
10474
9834
|
subCommands: {
|
|
10475
9835
|
list: listCommand3,
|
|
10476
9836
|
remove: removeCommand2,
|
|
10477
|
-
clear: clearCommand2
|
|
10478
|
-
amend: amendCommand,
|
|
10479
|
-
show: showCommand
|
|
9837
|
+
clear: clearCommand2
|
|
10480
9838
|
},
|
|
10481
|
-
run:
|
|
10482
|
-
await listDraft(false);
|
|
10483
|
-
}
|
|
9839
|
+
run: listDraft
|
|
10484
9840
|
});
|
|
10485
9841
|
|
|
10486
9842
|
// src/commands/ads/linkedin/facets.ts
|
|
@@ -13481,7 +12837,7 @@ async function probeDuration(filePath) {
|
|
|
13481
12837
|
|
|
13482
12838
|
// src/commands/canvas/run.ts
|
|
13483
12839
|
import { readFile as readFile2 } from "fs/promises";
|
|
13484
|
-
import
|
|
12840
|
+
import path5 from "path";
|
|
13485
12841
|
import { defineCommand as defineCommand85 } from "citty";
|
|
13486
12842
|
|
|
13487
12843
|
// src/commands/canvas/placeholders.ts
|
|
@@ -13525,9 +12881,102 @@ function isResolvableRelative(value) {
|
|
|
13525
12881
|
return typeof value === "string" && value.length > 0 && !value.includes("[TODO") && !path2.isAbsolute(value);
|
|
13526
12882
|
}
|
|
13527
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
|
+
|
|
13528
12977
|
// src/commands/canvas/run-retention.ts
|
|
13529
12978
|
import { rm } from "fs/promises";
|
|
13530
|
-
import
|
|
12979
|
+
import path4 from "path";
|
|
13531
12980
|
function runDirsToPrune(entries, keep, currentRunId) {
|
|
13532
12981
|
const runs = entries.filter((e) => /^r_[0-9A-Za-z]+$/.test(e) && e !== currentRunId).sort();
|
|
13533
12982
|
if (keep <= 0) return runs;
|
|
@@ -13544,7 +12993,7 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
|
|
|
13544
12993
|
const toPrune = runDirsToPrune(entries, keep, currentRunId);
|
|
13545
12994
|
if (toPrune.length === 0) return;
|
|
13546
12995
|
for (const dir of toPrune) {
|
|
13547
|
-
await rm(
|
|
12996
|
+
await rm(path4.join(outputsDir, dir), { recursive: true, force: true }).catch(
|
|
13548
12997
|
(e) => log(`[prune ] could not remove ${dir}: ${e.message}`)
|
|
13549
12998
|
);
|
|
13550
12999
|
}
|
|
@@ -13571,10 +13020,22 @@ var runCommand = defineCommand85({
|
|
|
13571
13020
|
"keep-runs": {
|
|
13572
13021
|
type: "string",
|
|
13573
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)"
|
|
13574
13035
|
}
|
|
13575
13036
|
},
|
|
13576
13037
|
async run({ args }) {
|
|
13577
|
-
const filePath =
|
|
13038
|
+
const filePath = path5.resolve(String(args.file));
|
|
13578
13039
|
const raw = await readFile2(filePath, "utf8");
|
|
13579
13040
|
let parsed;
|
|
13580
13041
|
try {
|
|
@@ -13585,7 +13046,7 @@ var runCommand = defineCommand85({
|
|
|
13585
13046
|
`);
|
|
13586
13047
|
process.exit(2);
|
|
13587
13048
|
}
|
|
13588
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
13049
|
+
parsed = resolveRelativeCanvasPaths(parsed, path5.dirname(filePath));
|
|
13589
13050
|
const pending = unsuppliedPlaceholderAssets(parsed);
|
|
13590
13051
|
if (pending.length > 0) {
|
|
13591
13052
|
process.stderr.write(
|
|
@@ -13605,16 +13066,26 @@ var runCommand = defineCommand85({
|
|
|
13605
13066
|
);
|
|
13606
13067
|
process.exit(2);
|
|
13607
13068
|
}
|
|
13069
|
+
const remoteCache = args["remote-cache"] !== void 0 ? String(args["remote-cache"]) !== "off" : void 0;
|
|
13608
13070
|
const engine = createEngineFromEnv({
|
|
13609
13071
|
cacheDir: args["cache-dir"] ? String(args["cache-dir"]) : void 0,
|
|
13610
13072
|
outputsDir: args["outputs-dir"] ? String(args["outputs-dir"]) : void 0,
|
|
13611
13073
|
log: (line) => process.stdout.write(`${line}
|
|
13612
|
-
`)
|
|
13074
|
+
`),
|
|
13075
|
+
remoteCache
|
|
13613
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();
|
|
13614
13085
|
try {
|
|
13615
13086
|
const policy = args["cache-policy"] ?? "read_write";
|
|
13616
13087
|
const result = await engine.run(parsed, {
|
|
13617
|
-
run_id:
|
|
13088
|
+
run_id: runId,
|
|
13618
13089
|
cache_policy: policy,
|
|
13619
13090
|
concurrency: resolveConcurrency(
|
|
13620
13091
|
// --concurrency wins; --parallel is the discoverable alias for the same bound.
|
|
@@ -13622,9 +13093,10 @@ var runCommand = defineCommand85({
|
|
|
13622
13093
|
process.env.BAKER_CANVAS_CONCURRENCY
|
|
13623
13094
|
)
|
|
13624
13095
|
});
|
|
13096
|
+
if (record) await record(buildRunRecord(result, recordMeta));
|
|
13625
13097
|
const keepRuns = args["keep-runs"] !== void 0 ? Number(args["keep-runs"]) : void 0;
|
|
13626
13098
|
if (keepRuns !== void 0 && Number.isFinite(keepRuns)) {
|
|
13627
|
-
const outputsDir = args["outputs-dir"] ?
|
|
13099
|
+
const outputsDir = args["outputs-dir"] ? path5.resolve(String(args["outputs-dir"])) : path5.resolve("canvas");
|
|
13628
13100
|
await pruneOldRuns(outputsDir, keepRuns, result.run_id, (line) => process.stdout.write(`${line}
|
|
13629
13101
|
`));
|
|
13630
13102
|
}
|
|
@@ -13652,6 +13124,7 @@ var runCommand = defineCommand85({
|
|
|
13652
13124
|
}
|
|
13653
13125
|
if (e instanceof LayerExecutionError) {
|
|
13654
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));
|
|
13655
13128
|
process.stderr.write(
|
|
13656
13129
|
`${JSON.stringify({ ok: false, error: { code: "runtime", message: e.message, failures } }, null, 2)}
|
|
13657
13130
|
`
|
|
@@ -13659,16 +13132,30 @@ var runCommand = defineCommand85({
|
|
|
13659
13132
|
process.exit(1);
|
|
13660
13133
|
}
|
|
13661
13134
|
const msg = e instanceof Error ? e.message : String(e);
|
|
13135
|
+
if (record) await record(buildFailedRunRecord(runId, msg, recordMeta));
|
|
13662
13136
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "runtime", message: msg } }, null, 2)}
|
|
13663
13137
|
`);
|
|
13664
13138
|
process.exit(1);
|
|
13665
13139
|
}
|
|
13666
13140
|
}
|
|
13667
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
|
+
}
|
|
13668
13155
|
|
|
13669
13156
|
// src/commands/canvas/scaffold-static-ad.ts
|
|
13670
|
-
import { readFile as readFile3, writeFile } from "fs/promises";
|
|
13671
|
-
import
|
|
13157
|
+
import { access, cp, mkdir, readFile as readFile3, writeFile } from "fs/promises";
|
|
13158
|
+
import path8 from "path";
|
|
13672
13159
|
import { defineCommand as defineCommand86 } from "citty";
|
|
13673
13160
|
|
|
13674
13161
|
// src/engine/scaffold/staticAd.ts
|
|
@@ -13854,18 +13341,106 @@ function staticAdReport(input, elementsInput, opts) {
|
|
|
13854
13341
|
};
|
|
13855
13342
|
}
|
|
13856
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
|
+
|
|
13857
13403
|
// src/commands/canvas/scaffold-static-ad-paths.ts
|
|
13858
|
-
import
|
|
13859
|
-
function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd()) {
|
|
13404
|
+
import path7 from "path";
|
|
13405
|
+
function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd(), slug) {
|
|
13860
13406
|
const file = rawFile.trim();
|
|
13861
13407
|
const imageIsUrl = /^https?:\/\//i.test(file);
|
|
13862
|
-
const imageSource = imageIsUrl ? file :
|
|
13863
|
-
const outPath = out ?
|
|
13864
|
-
const blueprintPath =
|
|
13865
|
-
|
|
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);
|
|
13866
13420
|
}
|
|
13867
13421
|
|
|
13868
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
|
+
}
|
|
13869
13444
|
function resolveModel(kind, preferred) {
|
|
13870
13445
|
const ids = Object.keys(MODEL_REGISTRY[kind]);
|
|
13871
13446
|
return ids.includes(preferred) ? preferred : ids[0] ?? preferred;
|
|
@@ -14025,6 +13600,16 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
14025
13600
|
file: { type: "positional", required: true, description: "Path or http(s) URL to the source/inspiration image" },
|
|
14026
13601
|
context: { type: "string", description: "Known provenance (advertiser, category, market) to ground the describe" },
|
|
14027
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" },
|
|
14028
13613
|
"describe-model": { type: "string", description: "Override the image_describe model id" },
|
|
14029
13614
|
"select-model": { type: "string", description: "Override the text_generate model id for element selection" },
|
|
14030
13615
|
"layout-model": { type: "string", description: "Override the text_generate model id for the layout pass" },
|
|
@@ -14033,10 +13618,21 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
14033
13618
|
"skip-font": { type: "boolean", description: "Skip the brand-font \u2192 type-specimen slot" }
|
|
14034
13619
|
},
|
|
14035
13620
|
async run({ args }) {
|
|
14036
|
-
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(
|
|
14037
13630
|
String(args.file),
|
|
14038
|
-
args.out ? String(args.out) : void 0
|
|
13631
|
+
args.out ? String(args.out) : void 0,
|
|
13632
|
+
process.cwd(),
|
|
13633
|
+
slug
|
|
14039
13634
|
);
|
|
13635
|
+
await mkdir(path8.dirname(outPath), { recursive: true });
|
|
14040
13636
|
const { describeModel, selectModel, layoutModel, genModel } = resolveModels(args);
|
|
14041
13637
|
const describeCanvas = buildDescribeCanvas(
|
|
14042
13638
|
imageSource,
|
|
@@ -14053,11 +13649,21 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
14053
13649
|
}
|
|
14054
13650
|
await writeFile(blueprintPath, `${JSON.stringify(annotated, null, 2)}
|
|
14055
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
|
+
}
|
|
14056
13662
|
const opts = {
|
|
14057
13663
|
genModel,
|
|
14058
|
-
imagePath:
|
|
14059
|
-
imageIsUrl,
|
|
14060
|
-
blueprintPath,
|
|
13664
|
+
imagePath: canvasImagePath,
|
|
13665
|
+
imageIsUrl: canvasImageIsUrl,
|
|
13666
|
+
blueprintPath: canvasBlueprintPath,
|
|
14061
13667
|
aspectRatio: args.aspect ? String(args.aspect) : void 0,
|
|
14062
13668
|
includeFont: !args["skip-font"]
|
|
14063
13669
|
};
|
|
@@ -14079,12 +13685,31 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
14079
13685
|
}
|
|
14080
13686
|
await writeFile(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
14081
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
|
+
}
|
|
14082
13705
|
process.stdout.write(
|
|
14083
13706
|
`${JSON.stringify(
|
|
14084
13707
|
{
|
|
14085
13708
|
ok: true,
|
|
14086
13709
|
canvas_path: outPath,
|
|
14087
13710
|
prompt_path: blueprintPath,
|
|
13711
|
+
definition_path: definitionPath ?? void 0,
|
|
13712
|
+
source_reference: sourceRelPath ?? void 0,
|
|
14088
13713
|
output: canvas.output,
|
|
14089
13714
|
models: { describe: describeModel, select: selectModel, layout: layoutModel, gen: opts.genModel },
|
|
14090
13715
|
aspect_ratio: report.aspect_ratio,
|
|
@@ -14095,7 +13720,7 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
14095
13720
|
run_estimated_credits: validation.estimatedCredits
|
|
14096
13721
|
},
|
|
14097
13722
|
checklist: {
|
|
14098
|
-
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.`,
|
|
14099
13724
|
assets_to_supply: report.elements,
|
|
14100
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)",
|
|
14101
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."
|
|
@@ -14110,8 +13735,8 @@ var scaffoldStaticAdCommand = defineCommand86({
|
|
|
14110
13735
|
});
|
|
14111
13736
|
|
|
14112
13737
|
// src/commands/canvas/scaffold-video.ts
|
|
14113
|
-
import { cp, mkdir, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
14114
|
-
import
|
|
13738
|
+
import { cp as cp2, mkdir as mkdir2, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
13739
|
+
import path11 from "path";
|
|
14115
13740
|
import { defineCommand as defineCommand87 } from "citty";
|
|
14116
13741
|
|
|
14117
13742
|
// src/engine/nodes/local/lib/sceneDetect.ts
|
|
@@ -14141,16 +13766,6 @@ function isLikelyOverSegmented(cuts, opts = {}) {
|
|
|
14141
13766
|
const median = gaps.length % 2 ? gaps[mid] : (gaps[mid - 1] + gaps[mid]) / 2;
|
|
14142
13767
|
return median < maxMedianGap;
|
|
14143
13768
|
}
|
|
14144
|
-
function mergeRecheckCuts(firstPass, recheck, isolationS = 0.75) {
|
|
14145
|
-
const kept = [...new Set(recheck)].sort((a, b) => a - b);
|
|
14146
|
-
const extras = [...new Set(firstPass)].filter((c) => kept.every((k) => Math.abs(k - c) >= isolationS)).sort((a, b) => a - b);
|
|
14147
|
-
const isolated = extras.filter((c, i) => {
|
|
14148
|
-
const prev = extras[i - 1];
|
|
14149
|
-
const next = extras[i + 1];
|
|
14150
|
-
return (prev === void 0 || c - prev >= isolationS) && (next === void 0 || next - c >= isolationS);
|
|
14151
|
-
});
|
|
14152
|
-
return [.../* @__PURE__ */ new Set([...kept, ...isolated])].sort((a, b) => a - b);
|
|
14153
|
-
}
|
|
14154
13769
|
function timecodeToSeconds(tc) {
|
|
14155
13770
|
const m = tc.trim().match(/^(\d+):(\d{1,2}):(\d{1,2}(?:\.\d+)?)$/);
|
|
14156
13771
|
if (!m) return null;
|
|
@@ -14204,13 +13819,12 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
|
14204
13819
|
const timeoutMs = opts.timeout_ms ?? 12e4;
|
|
14205
13820
|
const cuts = await runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs);
|
|
14206
13821
|
if (!pinned && isLikelyOverSegmented(cuts)) {
|
|
14207
|
-
|
|
13822
|
+
return await runSceneDetectOnce(
|
|
14208
13823
|
filePath,
|
|
14209
13824
|
PYSCENEDETECT_RECHECK_THRESHOLD,
|
|
14210
13825
|
PYSCENEDETECT_RECHECK_MIN_SCENE_LEN_S,
|
|
14211
13826
|
timeoutMs
|
|
14212
13827
|
);
|
|
14213
|
-
return mergeRecheckCuts(cuts, rechecked);
|
|
14214
13828
|
}
|
|
14215
13829
|
return cuts;
|
|
14216
13830
|
}
|
|
@@ -14348,22 +13962,9 @@ function videoResolutionParam(videoModel, resolution) {
|
|
|
14348
13962
|
return { resolution: resolution ?? DEFAULT_VIDEO_RESOLUTION };
|
|
14349
13963
|
}
|
|
14350
13964
|
var WORDS_PER_SECOND = 2.5;
|
|
14351
|
-
function wordCount(text) {
|
|
14352
|
-
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
14353
|
-
}
|
|
14354
13965
|
function estSpeechS(text) {
|
|
14355
|
-
|
|
14356
|
-
|
|
14357
|
-
var OBSERVED_WPS_MIN = 1;
|
|
14358
|
-
var OBSERVED_WPS_MAX = 6;
|
|
14359
|
-
function estSpeechWindowS(text, startS, endS) {
|
|
14360
|
-
const words = wordCount(text);
|
|
14361
|
-
const window = (endS ?? 0) - (startS ?? 0);
|
|
14362
|
-
if (words > 0 && window > 0.3) {
|
|
14363
|
-
const wps = words / window;
|
|
14364
|
-
if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return window;
|
|
14365
|
-
}
|
|
14366
|
-
return estSpeechS(text);
|
|
13966
|
+
const words = text.trim().split(/\s+/).filter(Boolean).length;
|
|
13967
|
+
return words / WORDS_PER_SECOND;
|
|
14367
13968
|
}
|
|
14368
13969
|
var NARRATOR_SPEAKERS = /* @__PURE__ */ new Set([
|
|
14369
13970
|
"voiceover",
|
|
@@ -14549,13 +14150,7 @@ var DialogueLine = z11.object({
|
|
|
14549
14150
|
start_s: z11.number().optional(),
|
|
14550
14151
|
end_s: z11.number().optional(),
|
|
14551
14152
|
delivery: z11.string().optional(),
|
|
14552
|
-
voice_description: z11.string().optional()
|
|
14553
|
-
// DECON-supplied: is this speaker's FACE visibly speaking in THIS scene? Element
|
|
14554
|
-
// presence alone can't answer that — a founder pictured in a polaroid close-up is
|
|
14555
|
-
// "present" yet the line is voiceover, and treating it as on-camera produced a
|
|
14556
|
-
// native Seedance lip-sync clip of a still photograph. `false` pins the line to
|
|
14557
|
-
// the VO path; absent keeps the presence-based decision (old blueprints).
|
|
14558
|
-
on_camera: z11.boolean().optional()
|
|
14153
|
+
voice_description: z11.string().optional()
|
|
14559
14154
|
}).loose();
|
|
14560
14155
|
var Sfx = z11.object({
|
|
14561
14156
|
at_s: z11.number().optional(),
|
|
@@ -14571,22 +14166,6 @@ var CompositionRegion = z11.object({
|
|
|
14571
14166
|
is_presenter: z11.boolean().optional(),
|
|
14572
14167
|
// The cast id shown/speaking in this region (routes lip-sync + element refs).
|
|
14573
14168
|
cast_ref: z11.string().optional(),
|
|
14574
|
-
// What the region's content IS: camera | screen_capture | static_graphic |
|
|
14575
|
-
// generated. Authoritative for routing when present (regex-over-prose fallback
|
|
14576
|
-
// otherwise): screen_capture/static_graphic are rebuilt from REAL surfaces on the
|
|
14577
|
-
// overlay layer, never AI-generated.
|
|
14578
|
-
kind: z11.string().optional(),
|
|
14579
|
-
// Opaque id naming the SPECIFIC on-screen document/note/app-state this
|
|
14580
|
-
// screen_capture region shows. Two scenes share it only when they show the SAME
|
|
14581
|
-
// recording continuing (scrolling/typing/waiting within it) — a genuinely
|
|
14582
|
-
// DIFFERENT document/note/recording (a source video splicing two screen captures)
|
|
14583
|
-
// gets a different id. Breaks a persistent-layout run into separate surface stubs
|
|
14584
|
-
// instead of asking the operator for one screenshot that can't cover both.
|
|
14585
|
-
surface_id: z11.string().optional(),
|
|
14586
|
-
// Camera bubble(s)/inset(s) embedded INSIDE this region's surface (a Loom-style
|
|
14587
|
-
// presenter bubble inside a screen recording) — video-in-video the reproduction
|
|
14588
|
-
// must re-composite, not paint into the surface.
|
|
14589
|
-
nested: z11.array(z11.object({}).loose()).optional(),
|
|
14590
14169
|
summary: z11.string().optional(),
|
|
14591
14170
|
frame_prompt: z11.string().optional(),
|
|
14592
14171
|
motion_prompt: z11.string().optional()
|
|
@@ -14640,11 +14219,6 @@ var Scene = z11.object({
|
|
|
14640
14219
|
sfx: z11.array(Sfx).optional(),
|
|
14641
14220
|
overlays: z11.array(z11.unknown()).optional(),
|
|
14642
14221
|
floating_elements: z11.array(z11.unknown()).optional(),
|
|
14643
|
-
// DECON-supplied: how much the picture itself moves within the shot. Gates the
|
|
14644
|
-
// flash-hold optimization — a sub-2s b-roll flash with REAL subject motion
|
|
14645
|
-
// (pouring, spreading, hands working) must stay a real clip; freezing it turns
|
|
14646
|
-
// a montage into a slideshow. Absent (old blueprints) keeps the cheap still.
|
|
14647
|
-
motion_level: z11.enum(["static", "subtle", "dynamic"]).optional(),
|
|
14648
14222
|
transcript_slice: z11.array(TranscriptWord).optional(),
|
|
14649
14223
|
start_frame_asset: FrameAsset,
|
|
14650
14224
|
end_frame_asset: FrameAsset,
|
|
@@ -14884,33 +14458,6 @@ function buildElementSlots(elements) {
|
|
|
14884
14458
|
function slotsForFrame(slots, sceneIndex, edge) {
|
|
14885
14459
|
return slots.filter((s) => s.presence.get(sceneIndex)?.has(edge));
|
|
14886
14460
|
}
|
|
14887
|
-
var MENTION_WIRED_TYPES = /* @__PURE__ */ new Set(["logo", "badge"]);
|
|
14888
|
-
function promptMentionsSlot(prompt, slot) {
|
|
14889
|
-
return elementMentionKeywords(slot).some(
|
|
14890
|
-
(w) => new RegExp(`\\b${w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i").test(prompt)
|
|
14891
|
-
);
|
|
14892
|
-
}
|
|
14893
|
-
function edgePromptText(scene, edge) {
|
|
14894
|
-
const regionPrompts = (compositeRegionsOf(scene) ?? []).map((r) => r.frame_prompt ?? "").filter(Boolean).join("\n");
|
|
14895
|
-
const own = (edge === "start" ? scene.start_frame_prompt : scene.end_frame_prompt) ?? "";
|
|
14896
|
-
return [own, regionPrompts].join("\n").trim();
|
|
14897
|
-
}
|
|
14898
|
-
function extendPresenceByPromptMentions(slots, blueprint) {
|
|
14899
|
-
const extendable = slots.filter((s) => MENTION_WIRED_TYPES.has(s.type.toLowerCase()));
|
|
14900
|
-
if (extendable.length === 0) return;
|
|
14901
|
-
blueprint.scenes.forEach((scene, i) => {
|
|
14902
|
-
for (const edge of EDGES) {
|
|
14903
|
-
const text = edgePromptText(scene, edge);
|
|
14904
|
-
if (!text) continue;
|
|
14905
|
-
for (const slot of extendable) {
|
|
14906
|
-
if (!promptMentionsSlot(text, slot)) continue;
|
|
14907
|
-
const set = slot.presence.get(i) ?? /* @__PURE__ */ new Set();
|
|
14908
|
-
set.add(edge);
|
|
14909
|
-
slot.presence.set(i, set);
|
|
14910
|
-
}
|
|
14911
|
-
}
|
|
14912
|
-
});
|
|
14913
|
-
}
|
|
14914
14461
|
var ACTOR_SHEET_MODEL = "google/gemini-3-pro-image-preview";
|
|
14915
14462
|
var SHEET_SUBJECT_TYPE = {
|
|
14916
14463
|
person: "person",
|
|
@@ -15093,94 +14640,21 @@ function seedanceAudioLine(scene, mode, audio, nativeLine) {
|
|
|
15093
14640
|
}
|
|
15094
14641
|
return null;
|
|
15095
14642
|
}
|
|
15096
|
-
|
|
15097
|
-
|
|
15098
|
-
"across",
|
|
15099
|
-
"around",
|
|
15100
|
-
"above",
|
|
15101
|
-
"below",
|
|
15102
|
-
"over",
|
|
15103
|
-
"under",
|
|
15104
|
-
"screen",
|
|
15105
|
-
"frame",
|
|
15106
|
-
"scene",
|
|
15107
|
-
"video",
|
|
15108
|
-
"element",
|
|
15109
|
-
"elements",
|
|
15110
|
-
"graphic",
|
|
15111
|
-
"overlay",
|
|
15112
|
-
"animated",
|
|
15113
|
-
"floating",
|
|
15114
|
-
"small",
|
|
15115
|
-
"large",
|
|
15116
|
-
"white",
|
|
15117
|
-
"black",
|
|
15118
|
-
"color",
|
|
15119
|
-
"colored"
|
|
15120
|
-
]);
|
|
15121
|
-
function floatTokens(desc) {
|
|
15122
|
-
return desc.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 4 && !FLOAT_STOP_WORDS.has(w));
|
|
15123
|
-
}
|
|
15124
|
-
function scrubFloatSentences(text, floatDescs) {
|
|
15125
|
-
if (floatDescs.length === 0 || !text) return text;
|
|
15126
|
-
const tokenSets = floatDescs.map((d) => new Set(floatTokens(d)));
|
|
15127
|
-
const kept = text.split(/(?<=[.!?])\s+/).filter((sentence) => {
|
|
15128
|
-
const words = new Set(floatTokens(sentence));
|
|
15129
|
-
return !tokenSets.some((ts) => {
|
|
15130
|
-
let hits = 0;
|
|
15131
|
-
for (const w of words) if (ts.has(w)) hits++;
|
|
15132
|
-
return hits >= 2;
|
|
15133
|
-
});
|
|
15134
|
-
}).join(" ").trim();
|
|
15135
|
-
return kept;
|
|
15136
|
-
}
|
|
15137
|
-
function sceneFloatDescs(scene) {
|
|
15138
|
-
const floats = z11.array(FloatingElement).safeParse(scene.floating_elements ?? []);
|
|
15139
|
-
if (!floats.success) return [];
|
|
15140
|
-
return floats.data.map((f) => f.description?.trim() ?? "").filter(Boolean);
|
|
15141
|
-
}
|
|
15142
|
-
function visualBriefParts(scene, sceneIndex, plate) {
|
|
14643
|
+
function buildSeedancePrompt(scene, sceneIndex, present, mode, audio, nativeLine, nativeLang) {
|
|
14644
|
+
const loc = (s) => nativeLine ? localizeNumeralsForNative(s, nativeLang) : s;
|
|
15143
14645
|
const parts = [];
|
|
15144
|
-
const
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
const out = scrubFloatSentences(s, floats);
|
|
15148
|
-
return out || void 0;
|
|
15149
|
-
};
|
|
15150
|
-
const summary = scrub((plate ? plate.summary : scene.summary)?.trim());
|
|
15151
|
-
parts.push(summary ? `Scene ${sceneIndex + 1}: ${summary}` : `Scene ${sceneIndex + 1}`);
|
|
15152
|
-
const action = !plate ? scrub(scene.action_detail) : void 0;
|
|
15153
|
-
if (action) parts.push(`Action: ${action}`);
|
|
14646
|
+
const summary = scene.summary?.trim();
|
|
14647
|
+
parts.push(summary ? `Scene ${sceneIndex + 1}: ${loc(summary)}` : `Scene ${sceneIndex + 1}`);
|
|
14648
|
+
if (scene.action_detail) parts.push(`Action: ${loc(scene.action_detail)}`);
|
|
15154
14649
|
const cm = scene.camera_motion;
|
|
15155
14650
|
if (cm) {
|
|
15156
14651
|
const camera = [cm.movement, cm.detail].filter(Boolean).join(" \u2014 ");
|
|
15157
14652
|
if (camera) parts.push(`Camera: ${camera}`);
|
|
15158
14653
|
}
|
|
15159
|
-
|
|
15160
|
-
if (
|
|
15161
|
-
const uiFloats = floats.filter((d) => UI_SURFACE_RE.test(d));
|
|
15162
|
-
const fxFloats = floats.filter((d) => !UI_SURFACE_RE.test(d));
|
|
15163
|
-
if (plate || uiFloats.length > 0) {
|
|
14654
|
+
if (scene.motion_prompt) parts.push(`Motion: ${loc(scene.motion_prompt)}`);
|
|
14655
|
+
if (present.length > 0) {
|
|
15164
14656
|
parts.push(
|
|
15165
|
-
|
|
15166
|
-
);
|
|
15167
|
-
}
|
|
15168
|
-
if (fxFloats.length > 0) {
|
|
15169
|
-
parts.push(
|
|
15170
|
-
"NEVER render floating decorative elements \u2014 no hearts, sparkles, confetti, badges, stickers, icons, or particle effects. They are composited later as a separate real overlay layer; the picture stays clean of them."
|
|
15171
|
-
);
|
|
15172
|
-
}
|
|
15173
|
-
return parts;
|
|
15174
|
-
}
|
|
15175
|
-
function buildSeedancePrompt(scene, sceneIndex, present, mode, audio, nativeLine, nativeLang, uiRouted) {
|
|
15176
|
-
const loc = (s) => nativeLine ? localizeNumeralsForNative(s, nativeLang) : s;
|
|
15177
|
-
const routed = uiRouted ?? (compositeRegionsOf(scene) !== null && isUiOnlyComposite(compositeRegionsOf(scene) ?? []));
|
|
15178
|
-
const plate = routed ? compositePlateRegion(scene) ?? {} : null;
|
|
15179
|
-
const parts = visualBriefParts(scene, sceneIndex, plate);
|
|
15180
|
-
const refs = plate ? present.filter((s) => !UI_SURFACE_RE.test(s.description ?? "")) : present;
|
|
15181
|
-
if (refs.length > 0) {
|
|
15182
|
-
parts.push(
|
|
15183
|
-
`Keep these consistent with their references: ${refs.map((s) => `${s.label} (${s.description ?? s.type})`).join("; ")}`
|
|
14657
|
+
`Keep these consistent with their references: ${present.map((s) => `${s.label} (${s.description ?? s.type})`).join("; ")}`
|
|
15184
14658
|
);
|
|
15185
14659
|
}
|
|
15186
14660
|
if (nativeLine) {
|
|
@@ -15311,8 +14785,7 @@ function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames
|
|
|
15311
14785
|
mode,
|
|
15312
14786
|
Boolean(nativeTurn) || ambientBroll,
|
|
15313
14787
|
nativeTurn?.text,
|
|
15314
|
-
opts.nativeLang
|
|
15315
|
-
opts.uiRouted
|
|
14788
|
+
opts.nativeLang
|
|
15316
14789
|
),
|
|
15317
14790
|
duration: lengths.genDur,
|
|
15318
14791
|
...videoResolutionParam(opts.videoModel, opts.resolution),
|
|
@@ -15341,59 +14814,12 @@ function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames
|
|
|
15341
14814
|
var COMPOSITE_LAYOUTS = /* @__PURE__ */ new Set(["split_screen", "pip", "keyed_overlay"]);
|
|
15342
14815
|
var UI_SURFACE_RE = /\b(?:app|ui|web ?site|web ?page|website|browser|chat|interface|mock-?up|in[- ]?app|dashboard|app screen|phone screen|screen[- ]?(?:recording|capture|grab|share))\b/i;
|
|
15343
14816
|
function regionIsUiSurface(r) {
|
|
15344
|
-
|
|
15345
|
-
if (kind === "screen_capture" || kind === "static_graphic") return true;
|
|
15346
|
-
if (kind === "camera") return false;
|
|
15347
|
-
return UI_SURFACE_RE.test(`${r.panel ?? ""} ${r.summary ?? ""}`);
|
|
14817
|
+
return UI_SURFACE_RE.test(`${r.panel ?? ""} ${r.summary ?? ""} ${r.frame_prompt ?? ""}`);
|
|
15348
14818
|
}
|
|
15349
14819
|
function isUiOnlyComposite(regions) {
|
|
15350
14820
|
const ui = regions.filter(regionIsUiSurface).length;
|
|
15351
14821
|
return ui >= 1 && regions.length - ui <= 1;
|
|
15352
14822
|
}
|
|
15353
|
-
function compositeRegionsOf(scene) {
|
|
15354
|
-
const comp = scene.composition;
|
|
15355
|
-
const layout = (comp?.layout ?? "").toLowerCase();
|
|
15356
|
-
if (!COMPOSITE_LAYOUTS.has(layout)) return null;
|
|
15357
|
-
const regions = (comp?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
15358
|
-
return regions.length >= 2 ? regions : null;
|
|
15359
|
-
}
|
|
15360
|
-
function compositePlateRegion(scene) {
|
|
15361
|
-
const regions = compositeRegionsOf(scene);
|
|
15362
|
-
if (!regions) return null;
|
|
15363
|
-
return regions.find((r) => r.is_presenter) ?? regions.find((r) => !regionIsUiSurface(r)) ?? null;
|
|
15364
|
-
}
|
|
15365
|
-
function uiRoutedSceneSet(input) {
|
|
15366
|
-
return new Set(uiRoutedRuns(input).flat());
|
|
15367
|
-
}
|
|
15368
|
-
function uiRoutedRuns(input) {
|
|
15369
|
-
const blueprint = VideoBlueprint.parse(input);
|
|
15370
|
-
const info = blueprint.scenes.map((scene) => {
|
|
15371
|
-
const regions = compositeRegionsOf(scene);
|
|
15372
|
-
if (!regions) return null;
|
|
15373
|
-
const comp = scene.composition;
|
|
15374
|
-
const panels = regions.map((r) => (r.panel ?? "").toLowerCase()).sort().join(",");
|
|
15375
|
-
const surfaceIds = regions.filter(regionIsUiSurface).map((r) => (r.surface_id ?? "").trim()).filter(Boolean).sort().join(",");
|
|
15376
|
-
const sig = surfaceIds ? `sid:${surfaceIds}` : `${(comp?.layout ?? "").toLowerCase()}|${(comp?.split_axis ?? "").toLowerCase()}|${panels}`;
|
|
15377
|
-
return { sig, ui: isUiOnlyComposite(regions) };
|
|
15378
|
-
});
|
|
15379
|
-
const runs = [];
|
|
15380
|
-
let run = [];
|
|
15381
|
-
const flushRun = () => {
|
|
15382
|
-
if (run.some((i) => info[i]?.ui)) runs.push(run);
|
|
15383
|
-
run = [];
|
|
15384
|
-
};
|
|
15385
|
-
info.forEach((inf, i) => {
|
|
15386
|
-
if (!inf) {
|
|
15387
|
-
flushRun();
|
|
15388
|
-
return;
|
|
15389
|
-
}
|
|
15390
|
-
const prev = i > 0 ? info[i - 1] : null;
|
|
15391
|
-
if (!prev || prev.sig !== inf.sig) flushRun();
|
|
15392
|
-
run.push(i);
|
|
15393
|
-
});
|
|
15394
|
-
flushRun();
|
|
15395
|
-
return runs;
|
|
15396
|
-
}
|
|
15397
14823
|
function sceneIsFullScreenUi(scene, present) {
|
|
15398
14824
|
if (scene.narrative_role?.trim() === "cta") return false;
|
|
15399
14825
|
const hasCast = present.some((s) => {
|
|
@@ -15427,11 +14853,14 @@ function screenStillArgs(durationS, dims) {
|
|
|
15427
14853
|
"{{out.video}}"
|
|
15428
14854
|
];
|
|
15429
14855
|
}
|
|
15430
|
-
function layeredComposition(scene
|
|
15431
|
-
const
|
|
15432
|
-
|
|
15433
|
-
if (
|
|
15434
|
-
|
|
14856
|
+
function layeredComposition(scene) {
|
|
14857
|
+
const comp = scene.composition;
|
|
14858
|
+
const layout = (comp?.layout ?? "").toLowerCase();
|
|
14859
|
+
if (!COMPOSITE_LAYOUTS.has(layout)) return null;
|
|
14860
|
+
const regions = (comp?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
14861
|
+
if (regions.length < 2) return null;
|
|
14862
|
+
if (isUiOnlyComposite(regions)) return null;
|
|
14863
|
+
return { layout, regions, comp: comp ?? {} };
|
|
15435
14864
|
}
|
|
15436
14865
|
function splitAxisOf(comp, regions) {
|
|
15437
14866
|
const panels = regions.map((r) => (r.panel ?? "").toLowerCase());
|
|
@@ -15522,7 +14951,7 @@ function buildCompositeScene(layout, regions, comp, scene, i, present, mode, nat
|
|
|
15522
14951
|
const bgIdx = regions.findIndex((_, k) => k !== presIdx);
|
|
15523
14952
|
const bgRef = regionRefs[bgIdx >= 0 ? bgIdx : 0];
|
|
15524
14953
|
let presRef = regionRefs[presIdx >= 0 ? presIdx : 1];
|
|
15525
|
-
if (layout === "keyed_overlay") {
|
|
14954
|
+
if (layout === "keyed_overlay" && presIdx >= 0) {
|
|
15526
14955
|
const keyId = `s${i}_key`;
|
|
15527
14956
|
nodes.push({ id: keyId, type: "video_background_remove", inputs: { video: presRef }, params: {} });
|
|
15528
14957
|
presRef = `$ref:${keyId}.video`;
|
|
@@ -15623,67 +15052,6 @@ function emitScreenScene(i, scene, lengths, out, outAr, nodes, clips) {
|
|
|
15623
15052
|
});
|
|
15624
15053
|
clips.push({ ref: `$ref:s${i}_clip.video`, scene_s: lengths.dur, out });
|
|
15625
15054
|
}
|
|
15626
|
-
function sceneIsAllGraphic(scene) {
|
|
15627
|
-
const regions = (scene.composition?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
15628
|
-
if (regions.length === 0 || regions.some((r) => (r.kind ?? "").toLowerCase() === "camera")) return false;
|
|
15629
|
-
return regions.some((r) => (r.kind ?? "").toLowerCase() === "static_graphic");
|
|
15630
|
-
}
|
|
15631
|
-
function sceneIsFullFrameGraphic(scene) {
|
|
15632
|
-
return sceneIsAllGraphic(scene);
|
|
15633
|
-
}
|
|
15634
|
-
function fullFrameGraphicScenes(blueprint) {
|
|
15635
|
-
const scenes = blueprint.scenes;
|
|
15636
|
-
const graphic = new Set(scenes.flatMap((s, i) => sceneIsFullFrameGraphic(s) ? [i] : []));
|
|
15637
|
-
const blocked = (s) => {
|
|
15638
|
-
const regions = (s.composition?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
15639
|
-
if (regions.some((r) => (r.kind ?? "").toLowerCase() === "camera")) return true;
|
|
15640
|
-
return (s.dialogue ?? []).some((d) => d.on_camera === true);
|
|
15641
|
-
};
|
|
15642
|
-
let changed = true;
|
|
15643
|
-
while (changed) {
|
|
15644
|
-
changed = false;
|
|
15645
|
-
scenes.forEach((s, i) => {
|
|
15646
|
-
if (graphic.has(i)) return;
|
|
15647
|
-
const chainsToPrev = s.continues_previous && graphic.has(i - 1);
|
|
15648
|
-
const next = scenes[i + 1];
|
|
15649
|
-
const chainsToNext = Boolean(next?.continues_previous) && graphic.has(i + 1);
|
|
15650
|
-
if ((chainsToPrev || chainsToNext) && !blocked(s)) {
|
|
15651
|
-
graphic.add(i);
|
|
15652
|
-
changed = true;
|
|
15653
|
-
}
|
|
15654
|
-
});
|
|
15655
|
-
}
|
|
15656
|
-
return graphic;
|
|
15657
|
-
}
|
|
15658
|
-
function emitGraphicScene(i, scene, lengths, out, outAr, surfaceIngests, nodes, clips) {
|
|
15659
|
-
const regions = (scene.composition?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
15660
|
-
const surfaceId = regions.find((r) => r.surface_id)?.surface_id;
|
|
15661
|
-
let refId = surfaceId ? surfaceIngests.get(surfaceId) : void 0;
|
|
15662
|
-
if (!refId) {
|
|
15663
|
-
const label = commentSafe((scene.summary || scene.start_frame_prompt || "the designed panel").slice(0, 120));
|
|
15664
|
-
refId = `s${i}_graphic_ref`;
|
|
15665
|
-
nodes.push({
|
|
15666
|
-
id: refId,
|
|
15667
|
-
type: "ingest",
|
|
15668
|
-
params: {
|
|
15669
|
-
source: "path",
|
|
15670
|
-
path: `[TODO: supply the REAL designed graphic for "${label}" \u2014 NEVER AI-generate a designed panel (its typography/wordmarks garble). Rebuild it as brand HTML in video-overlay-composition (the scene's text/logos are already seeded there), export the design from the brand kit, or source stock art; text rides the overlay layer, not this plate]`,
|
|
15671
|
-
expect: "image"
|
|
15672
|
-
}
|
|
15673
|
-
});
|
|
15674
|
-
if (surfaceId) surfaceIngests.set(surfaceId, refId);
|
|
15675
|
-
}
|
|
15676
|
-
nodes.push({
|
|
15677
|
-
id: `s${i}_clip`,
|
|
15678
|
-
type: "ffmpeg",
|
|
15679
|
-
inputs: { frame: `$ref:${refId}.asset` },
|
|
15680
|
-
params: {
|
|
15681
|
-
args: screenStillArgs(lengths.trimTarget, canvasDims(outAr)),
|
|
15682
|
-
outputs: { video: { kind: "video", ext: "mp4" } }
|
|
15683
|
-
}
|
|
15684
|
-
});
|
|
15685
|
-
clips.push({ ref: `$ref:s${i}_clip.video`, scene_s: lengths.dur, out });
|
|
15686
|
-
}
|
|
15687
15055
|
var BRAND_CARD_RE = /\b(?:solid|plain|flat|brand|logo|wordmark|end[- ]?card|cta card|title card|colou?r background|background colou?r)\b/i;
|
|
15688
15056
|
function sceneIsBrandCard(scene, present, isCta) {
|
|
15689
15057
|
if (!isCta) return false;
|
|
@@ -15924,12 +15292,8 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
|
|
|
15924
15292
|
speaker: sp,
|
|
15925
15293
|
// Shown = a cast member speaking AND their element is actually on screen
|
|
15926
15294
|
// here (not a cutaway). A b-roll cutaway mid-phrase fails this and gets
|
|
15927
|
-
// its own clip while the phrase voice plays under it.
|
|
15928
|
-
|
|
15929
|
-
// presence — a speaker pictured in a photo is "present" but not talking.
|
|
15930
|
-
// An all-graphic composition (no camera region) is voiceover by
|
|
15931
|
-
// definition: nobody is on screen to lip-sync.
|
|
15932
|
-
shown: l.on_camera !== false && !sceneIsAllGraphic(scene) && isOnCameraSpeaker(raw, casts, cameraOn) && !multiSpeaker.has(sceneIndex) && presenterPresent(sp, sceneIndex),
|
|
15295
|
+
// its own clip while the phrase voice plays under it.
|
|
15296
|
+
shown: isOnCameraSpeaker(raw, casts, cameraOn) && !multiSpeaker.has(sceneIndex) && presenterPresent(sp, sceneIndex),
|
|
15933
15297
|
start,
|
|
15934
15298
|
// Real speech end. When the deconstruct gives no end_s, estimate it from
|
|
15935
15299
|
// the words — NOT the scene end (which would fabricate continuity across
|
|
@@ -16027,8 +15391,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
16027
15391
|
shootMode: mode,
|
|
16028
15392
|
ingestCache: env.ingestCache
|
|
16029
15393
|
};
|
|
16030
|
-
const
|
|
16031
|
-
const first = chained ?? buildFrameRef(
|
|
15394
|
+
const first = buildFrameRef(
|
|
16032
15395
|
"start",
|
|
16033
15396
|
anchorScene.start_frame_asset?.url,
|
|
16034
15397
|
anchorScene.start_frame_prompt,
|
|
@@ -16055,16 +15418,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
16055
15418
|
const genDur = ceilToSeedance(phraseLen);
|
|
16056
15419
|
const clipParams = {
|
|
16057
15420
|
model: env.opts.videoModel,
|
|
16058
|
-
prompt: buildSeedancePrompt(
|
|
16059
|
-
anchorScene,
|
|
16060
|
-
anchor,
|
|
16061
|
-
present,
|
|
16062
|
-
mode,
|
|
16063
|
-
true,
|
|
16064
|
-
phrase.text,
|
|
16065
|
-
env.ttsLanguageCode,
|
|
16066
|
-
env.uiRouted.has(anchor)
|
|
16067
|
-
),
|
|
15421
|
+
prompt: buildSeedancePrompt(anchorScene, anchor, present, mode, true, phrase.text, env.ttsLanguageCode),
|
|
16068
15422
|
duration: genDur,
|
|
16069
15423
|
...videoResolutionParam(env.opts.videoModel, env.opts.resolution),
|
|
16070
15424
|
generate_audio: true
|
|
@@ -16076,7 +15430,6 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
16076
15430
|
inputs: { first_frame: first, last_frame: last },
|
|
16077
15431
|
params: clipParams
|
|
16078
15432
|
});
|
|
16079
|
-
out.phraseEnd = { ref: last, scene: lastShown };
|
|
16080
15433
|
const clipRef = `$ref:s${anchor}_clip.video`;
|
|
16081
15434
|
const speechOffset = Math.max(0, phrase.start_s - clipStart);
|
|
16082
15435
|
const extractLen = Math.min(Math.max(0.5, phrase.end_s - phrase.start_s), Math.max(0.5, genDur - speechOffset));
|
|
@@ -16111,8 +15464,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
16111
15464
|
scene: anchor,
|
|
16112
15465
|
voice_convert_node: convId,
|
|
16113
15466
|
scene_s: Math.round(phraseLen * 100) / 100,
|
|
16114
|
-
est_speech_s: Math.round(
|
|
16115
|
-
speech_words: wordCount(phrase.text)
|
|
15467
|
+
est_speech_s: Math.round(estSpeechS(phrase.text) * 100) / 100
|
|
16116
15468
|
});
|
|
16117
15469
|
for (const s of phrase.shownScenes) {
|
|
16118
15470
|
const sc = env.blueprint.scenes[s];
|
|
@@ -16170,8 +15522,7 @@ function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, en
|
|
|
16170
15522
|
scene: i,
|
|
16171
15523
|
voice_convert_node: `${voiceNode}_conv`,
|
|
16172
15524
|
scene_s: Math.round(sceneDurationS(scene) * 100) / 100,
|
|
16173
|
-
est_speech_s: Math.round(
|
|
16174
|
-
speech_words: wordCount(text)
|
|
15525
|
+
est_speech_s: Math.round(estSpeechS(text) * 100) / 100
|
|
16175
15526
|
});
|
|
16176
15527
|
}
|
|
16177
15528
|
const mode = sceneShootMode(scene, present, nativeTurn, env.cameraOn, env.casts);
|
|
@@ -16263,10 +15614,6 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
16263
15614
|
shootMode: mode,
|
|
16264
15615
|
ingestCache: env.ingestCache
|
|
16265
15616
|
};
|
|
16266
|
-
if (!env.reuse && env.graphicScenes.has(i)) {
|
|
16267
|
-
emitGraphicScene(i, scene, lengths, lengths.out, env.outAr, env.surfaceIngests, nodes, out.clips);
|
|
16268
|
-
return void 0;
|
|
16269
|
-
}
|
|
16270
15617
|
if (!env.reuse && sceneIsFullScreenUi(scene, present)) {
|
|
16271
15618
|
emitScreenScene(i, scene, lengths, lengths.out, env.outAr, nodes, out.clips);
|
|
16272
15619
|
return void 0;
|
|
@@ -16276,7 +15623,7 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
16276
15623
|
emitBrandCardScene(i, lengths, lengths.out, env.outAr, brandPlateColor(env.blueprint), nodes, out.clips);
|
|
16277
15624
|
return void 0;
|
|
16278
15625
|
}
|
|
16279
|
-
if (!ambientBroll && lengths.dur <= FLASH_HOLD_MAX_S
|
|
15626
|
+
if (!ambientBroll && lengths.dur <= FLASH_HOLD_MAX_S) {
|
|
16280
15627
|
emitFlashHold(i, scene, env.slots, ctx, lengths, lengths.out, env.outAr, nodes, out.clips);
|
|
16281
15628
|
return void 0;
|
|
16282
15629
|
}
|
|
@@ -16311,8 +15658,7 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
16311
15658
|
genAr: env.genAr,
|
|
16312
15659
|
videoModel: env.opts.videoModel,
|
|
16313
15660
|
resolution: env.opts.resolution,
|
|
16314
|
-
nativeLang: env.ttsLanguageCode
|
|
16315
|
-
uiRouted: env.uiRouted.has(i)
|
|
15661
|
+
nativeLang: env.ttsLanguageCode
|
|
16316
15662
|
},
|
|
16317
15663
|
nodes
|
|
16318
15664
|
);
|
|
@@ -16333,11 +15679,10 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
16333
15679
|
}
|
|
16334
15680
|
function buildTimeline(blueprint, slots, opts, nodes) {
|
|
16335
15681
|
const reuse = opts.frames === "reuse";
|
|
16336
|
-
const uiRouted = uiRoutedSceneSet(blueprint);
|
|
16337
15682
|
const compositeScenes = /* @__PURE__ */ new Set();
|
|
16338
15683
|
if (!reuse) {
|
|
16339
15684
|
blueprint.scenes.forEach((s, i) => {
|
|
16340
|
-
if (layeredComposition(s
|
|
15685
|
+
if (layeredComposition(s)) compositeScenes.add(i);
|
|
16341
15686
|
});
|
|
16342
15687
|
}
|
|
16343
15688
|
const canonical = collapseVoiceover(blueprint);
|
|
@@ -16353,9 +15698,6 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
16353
15698
|
cameraOn: onCameraDialogue(blueprint),
|
|
16354
15699
|
casts: castIdSet(blueprint),
|
|
16355
15700
|
ingestCache: /* @__PURE__ */ new Map(),
|
|
16356
|
-
uiRouted,
|
|
16357
|
-
graphicScenes: fullFrameGraphicScenes(blueprint),
|
|
16358
|
-
surfaceIngests: /* @__PURE__ */ new Map(),
|
|
16359
15701
|
ttsLanguageCode: deriveTtsLanguageCode(blueprint)
|
|
16360
15702
|
};
|
|
16361
15703
|
const out = {
|
|
@@ -16384,7 +15726,7 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
16384
15726
|
const lastIndex = blueprint.scenes.length - 1;
|
|
16385
15727
|
let prevEndFrame;
|
|
16386
15728
|
blueprint.scenes.forEach((scene, i) => {
|
|
16387
|
-
const composite = compositeScenes.has(i) ? layeredComposition(scene
|
|
15729
|
+
const composite = compositeScenes.has(i) ? layeredComposition(scene) : null;
|
|
16388
15730
|
if (composite) {
|
|
16389
15731
|
emitCompositeInTimeline(
|
|
16390
15732
|
composite,
|
|
@@ -16581,42 +15923,9 @@ function sourceHint(fe) {
|
|
|
16581
15923
|
return `baker images icon "${desc}"`;
|
|
16582
15924
|
}
|
|
16583
15925
|
}
|
|
16584
|
-
function
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
(fe.brand_name || fe.what_it_represents || fe.description || "").toLowerCase().trim(),
|
|
16588
|
-
positionClass(fe.position)
|
|
16589
|
-
].join("|");
|
|
16590
|
-
}
|
|
16591
|
-
function floatIsRoutedSurface(fe, sceneIsUiRouted) {
|
|
16592
|
-
return sceneIsUiRouted && (fe.kind ?? "").toLowerCase() === "ui_element" && UI_SURFACE_RE.test(fe.description ?? "");
|
|
16593
|
-
}
|
|
16594
|
-
function addFloatDetection(windows, fe, at, end) {
|
|
16595
|
-
const wins = windows.get(floatKey(fe)) ?? [];
|
|
16596
|
-
const last = wins[wins.length - 1];
|
|
16597
|
-
if (last && at <= last.end + OVERLAY_REDETECT_GAP_S) {
|
|
16598
|
-
last.end = Math.max(last.end, end);
|
|
16599
|
-
return;
|
|
16600
|
-
}
|
|
16601
|
-
wins.push({ fe, at, end });
|
|
16602
|
-
windows.set(floatKey(fe), wins);
|
|
16603
|
-
}
|
|
16604
|
-
function collectFloatWindows(blueprint, uiRouted) {
|
|
16605
|
-
const windows = /* @__PURE__ */ new Map();
|
|
16606
|
-
blueprint.scenes.forEach((scene, i) => {
|
|
16607
|
-
const sceneStart = scene.start_s ?? 0;
|
|
16608
|
-
const floats = z11.array(FloatingElement).safeParse(scene.floating_elements ?? []);
|
|
16609
|
-
if (!floats.success) return;
|
|
16610
|
-
for (const fe of floats.data) {
|
|
16611
|
-
const at = fe.appears_at_s ?? sceneStart;
|
|
16612
|
-
const dur = fe.duration_s ?? 2.5;
|
|
16613
|
-
if (floatIsRoutedSurface(fe, uiRouted.has(i)) || dur < OVERLAY_MIN_DUR_S) continue;
|
|
16614
|
-
addFloatDetection(windows, fe, at, at + dur);
|
|
16615
|
-
}
|
|
16616
|
-
});
|
|
16617
|
-
return [...windows.values()].flat().sort((a, b) => a.at - b.at);
|
|
16618
|
-
}
|
|
16619
|
-
function floatingStub(fe, at, dur) {
|
|
15926
|
+
function floatingStub(fe, sceneStart) {
|
|
15927
|
+
const at = fe.appears_at_s ?? sceneStart;
|
|
15928
|
+
const dur = fe.duration_s ?? 2.5;
|
|
16620
15929
|
const kind = commentSafe(fe.kind ?? "element");
|
|
16621
15930
|
const label = commentSafe(fe.brand_name || fe.what_it_represents || fe.description || fe.kind || "element");
|
|
16622
15931
|
const hint = commentSafe(sourceHint(fe));
|
|
@@ -16626,49 +15935,27 @@ function floatingStub(fe, at, dur) {
|
|
|
16626
15935
|
`<img class="ov clip ${positionClass(fe.position)}" src="your-${slug}.png" data-start="${at}" data-dur="${dur}" alt="" /> -->`
|
|
16627
15936
|
].join("\n");
|
|
16628
15937
|
}
|
|
16629
|
-
function
|
|
16630
|
-
const
|
|
16631
|
-
|
|
16632
|
-
|
|
16633
|
-
const
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
const ui = scenes.map(surfaceOf).find(Boolean);
|
|
16639
|
-
const at = first.start_s ?? 0;
|
|
16640
|
-
const end = last.end_s ?? at + 2.5;
|
|
16641
|
-
const dur = Math.max(0.5, Math.round((end - at) * 100) / 100);
|
|
15938
|
+
function uiPipStub(scene) {
|
|
15939
|
+
const comp = scene.composition;
|
|
15940
|
+
const layout = (comp?.layout ?? "").toLowerCase();
|
|
15941
|
+
if (!COMPOSITE_LAYOUTS.has(layout)) return "";
|
|
15942
|
+
const regions = (comp?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
15943
|
+
if (regions.length < 2 || !isUiOnlyComposite(regions)) return "";
|
|
15944
|
+
const ui = regions.find(regionIsUiSurface);
|
|
15945
|
+
const at = scene.start_s ?? 0;
|
|
15946
|
+
const dur = Math.max(0.5, Math.round(((scene.end_s ?? at + 2.5) - at) * 100) / 100);
|
|
16642
15947
|
const label = commentSafe(ui?.summary || ui?.frame_prompt || ui?.panel || "the app screen");
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
return ` @ ${s.start_s ?? 0}s \u2014 ${commentSafe(beat)}`;
|
|
16646
|
-
}) : [];
|
|
16647
|
-
const isGraphic = scenes.some((s) => (compositeRegionsOf(s) ?? []).some((r) => r.kind === "static_graphic"));
|
|
16648
|
-
const sourcing = isGraphic ? [
|
|
16649
|
-
" Build it as a REAL designed panel, NEVER AI-generate its text/typography:",
|
|
16650
|
-
" hand-build a brand-accurate HTML block in this composition (restyle the seeded",
|
|
16651
|
-
" overlay text, or `npx hyperframes add <block>` for a ready-made card),",
|
|
16652
|
-
" \u2014 OR drop the design asset (export it from the brand kit) as panel.png here;",
|
|
16653
|
-
" then nest it as a timed clip:"
|
|
16654
|
-
] : [
|
|
15948
|
+
return [
|
|
15949
|
+
`<!-- PHONE UI @ ${at}s for ${dur}s \u2014 the app/site screen this scene shows: ${label}.`,
|
|
16655
15950
|
" Build it as a REAL surface, NEVER AI: capture the live page \u2014",
|
|
16656
15951
|
" baker images screenshot https://<brand-domain>/<path> (image-library skill)",
|
|
16657
15952
|
" \u2014 OR hand-build a brand-accurate HTML screen; then frame it in a phone mockup:",
|
|
16658
15953
|
" npx hyperframes add phone-scroll (writes compositions/phone-scroll.html)",
|
|
16659
|
-
" drop the screenshot as screenshot.png in this dir and nest it as a PIP clip:"
|
|
16660
|
-
];
|
|
16661
|
-
const kindLabel = isGraphic ? "GRAPHIC PANEL" : "PHONE UI";
|
|
16662
|
-
const noun = isGraphic ? "the designed graphic panel this window shows" : "the app/site screen this window shows";
|
|
16663
|
-
return [
|
|
16664
|
-
`<!-- ${kindLabel} @ ${at}s for ${dur}s \u2014 ${noun}: ${label}.`,
|
|
16665
|
-
...states.length > 0 ? [" ONE continuous surface; its states over the window:", ...states] : [],
|
|
16666
|
-
...sourcing,
|
|
15954
|
+
" drop the screenshot as screenshot.png in this dir and nest it as a PIP clip:",
|
|
16667
15955
|
` <div class="clip" data-composition-src="compositions/phone-scroll.html" data-start="${at}" data-duration="${dur}" data-track-index="2" data-width="1080" data-height="1920"></div> -->`
|
|
16668
15956
|
].join("\n");
|
|
16669
15957
|
}
|
|
16670
|
-
|
|
16671
|
-
function buildOverlayHtml(input, opts = {}) {
|
|
15958
|
+
function buildOverlayHtml(input) {
|
|
16672
15959
|
const blueprint = VideoBlueprint.parse(input);
|
|
16673
15960
|
const blocks = [
|
|
16674
15961
|
[
|
|
@@ -16687,15 +15974,33 @@ function buildOverlayHtml(input, opts = {}) {
|
|
|
16687
15974
|
" Positions: edit the .pos-* classes or add your own. -->"
|
|
16688
15975
|
].join("\n")
|
|
16689
15976
|
];
|
|
16690
|
-
const ovParts = mergeCaptions(blueprint).
|
|
15977
|
+
const ovParts = mergeCaptions(blueprint).map((e) => overlayElement(e.ov, e.at, Math.round((e.end - e.at) * 1e3) / 1e3)).filter(Boolean);
|
|
16691
15978
|
if (ovParts.length > 0) blocks.push(ovParts.join("\n"));
|
|
16692
|
-
const
|
|
16693
|
-
const
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
|
|
16697
|
-
const
|
|
16698
|
-
|
|
15979
|
+
const seenFloats = /* @__PURE__ */ new Map();
|
|
15980
|
+
const keepFloat = (fe, sceneStart) => {
|
|
15981
|
+
const at = fe.appears_at_s ?? sceneStart;
|
|
15982
|
+
const dur = fe.duration_s ?? 2.5;
|
|
15983
|
+
if (dur < OVERLAY_MIN_DUR_S) return false;
|
|
15984
|
+
const key = [
|
|
15985
|
+
(fe.kind ?? "element").toLowerCase(),
|
|
15986
|
+
(fe.brand_name || fe.what_it_represents || fe.description || "").toLowerCase().trim(),
|
|
15987
|
+
positionClass(fe.position)
|
|
15988
|
+
].join("|");
|
|
15989
|
+
const lastEnd = seenFloats.get(key);
|
|
15990
|
+
if (lastEnd !== void 0 && at <= lastEnd + OVERLAY_REDETECT_GAP_S) {
|
|
15991
|
+
seenFloats.set(key, Math.max(lastEnd, at + dur));
|
|
15992
|
+
return false;
|
|
15993
|
+
}
|
|
15994
|
+
seenFloats.set(key, at + dur);
|
|
15995
|
+
return true;
|
|
15996
|
+
};
|
|
15997
|
+
for (const scene of blueprint.scenes) {
|
|
15998
|
+
const sceneStart = scene.start_s ?? 0;
|
|
15999
|
+
const floats = z11.array(FloatingElement).safeParse(scene.floating_elements ?? []);
|
|
16000
|
+
const parts = (floats.success ? floats.data.filter((fe) => keepFloat(fe, sceneStart)).map((fe) => floatingStub(fe, sceneStart)) : []).filter(Boolean);
|
|
16001
|
+
const pip = uiPipStub(scene);
|
|
16002
|
+
if (pip) parts.push(pip);
|
|
16003
|
+
if (parts.length > 0) blocks.push(parts.join("\n"));
|
|
16699
16004
|
}
|
|
16700
16005
|
return blocks.join("\n\n");
|
|
16701
16006
|
}
|
|
@@ -16770,7 +16075,6 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
16770
16075
|
params: { source: "path", path: opts.blueprintPath ?? "./prompt.json", expect: "json" }
|
|
16771
16076
|
});
|
|
16772
16077
|
const slots = buildElementSlots(elements);
|
|
16773
|
-
extendPresenceByPromptMentions(slots, blueprint);
|
|
16774
16078
|
slots.forEach((slot, i) => {
|
|
16775
16079
|
nodes.push({
|
|
16776
16080
|
id: slot.id,
|
|
@@ -16784,7 +16088,7 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
16784
16088
|
let videoNode = "spine";
|
|
16785
16089
|
const overlays = blueprint.scenes.flatMap((s) => s.overlays ?? []);
|
|
16786
16090
|
const floating = blueprint.scenes.flatMap((s) => s.floating_elements ?? []);
|
|
16787
|
-
const hasUiPip =
|
|
16091
|
+
const hasUiPip = blueprint.scenes.some((s) => uiPipStub(s) !== "");
|
|
16788
16092
|
if (overlays.length > 0 || floating.length > 0 || hasUiPip) {
|
|
16789
16093
|
nodes.push({
|
|
16790
16094
|
id: "overlaid",
|
|
@@ -17167,10 +16471,7 @@ function collectClipAdvisories(scene, i, out) {
|
|
|
17167
16471
|
const window = sceneDurationS(scene);
|
|
17168
16472
|
if (window > SEEDANCE_SAFE_MAX_S)
|
|
17169
16473
|
out.oversize.push({ scene: i, scene_s: round22(window), clip_s: ceilToSeedance(window) });
|
|
17170
|
-
const speech = (scene.dialogue ?? []).reduce(
|
|
17171
|
-
(s, line) => s + (line.line ? estSpeechWindowS(line.line, line.start_s, line.end_s) : 0),
|
|
17172
|
-
0
|
|
17173
|
-
);
|
|
16474
|
+
const speech = (scene.dialogue ?? []).reduce((s, line) => s + (line.line ? estSpeechS(line.line) : 0), 0);
|
|
17174
16475
|
if (speech > window * OVERSTUFF_RATIO)
|
|
17175
16476
|
out.overstuffed.push({ scene: i, scene_s: round22(window), est_speech_s: round22(speech) });
|
|
17176
16477
|
}
|
|
@@ -17219,23 +16520,23 @@ function videoReport(input, elementsInput) {
|
|
|
17219
16520
|
|
|
17220
16521
|
// src/commands/canvas/composition-path.ts
|
|
17221
16522
|
import { existsSync as existsSync4 } from "fs";
|
|
17222
|
-
import
|
|
16523
|
+
import path9 from "path";
|
|
17223
16524
|
function resolveShippedCanvasDir(name, startDir, exists = existsSync4, maxDepth = 8) {
|
|
17224
|
-
const rel =
|
|
16525
|
+
const rel = path9.join("canvas", name);
|
|
17225
16526
|
let dir = startDir;
|
|
17226
16527
|
for (let i = 0; i < maxDepth; i++) {
|
|
17227
|
-
const candidate =
|
|
17228
|
-
if (exists(
|
|
17229
|
-
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);
|
|
17230
16531
|
if (parent === dir) break;
|
|
17231
16532
|
dir = parent;
|
|
17232
16533
|
}
|
|
17233
|
-
return
|
|
16534
|
+
return path9.resolve(startDir, "../../../", rel);
|
|
17234
16535
|
}
|
|
17235
16536
|
|
|
17236
16537
|
// src/commands/canvas/gitignore.ts
|
|
17237
16538
|
import { appendFile, readFile as readFile5 } from "fs/promises";
|
|
17238
|
-
import
|
|
16539
|
+
import path10 from "path";
|
|
17239
16540
|
function missingGitignoreEntries(existing, entries) {
|
|
17240
16541
|
const present = new Set(
|
|
17241
16542
|
existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
@@ -17243,7 +16544,7 @@ function missingGitignoreEntries(existing, entries) {
|
|
|
17243
16544
|
return entries.filter((e) => !present.has(e.trim().replace(/\/+$/, "")));
|
|
17244
16545
|
}
|
|
17245
16546
|
async function ensureGitignore(dir, entries) {
|
|
17246
|
-
const file =
|
|
16547
|
+
const file = path10.join(dir, ".gitignore");
|
|
17247
16548
|
let existing;
|
|
17248
16549
|
try {
|
|
17249
16550
|
existing = await readFile5(file, "utf8");
|
|
@@ -17304,8 +16605,8 @@ async function loadTranscriptBestEffort(ref) {
|
|
|
17304
16605
|
async function stageCaptions(outDir, transcript) {
|
|
17305
16606
|
const text = transcript?.trim();
|
|
17306
16607
|
if (!text || text === "[]") return {};
|
|
17307
|
-
const compositionPath =
|
|
17308
|
-
await
|
|
16608
|
+
const compositionPath = path11.join(outDir, "tiktok-captions-composition");
|
|
16609
|
+
await cp2(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });
|
|
17309
16610
|
return { compositionPath };
|
|
17310
16611
|
}
|
|
17311
16612
|
function patchCompositionMeta(metaJson, dims) {
|
|
@@ -17322,10 +16623,10 @@ function patchCompositionHtml(html, dims) {
|
|
|
17322
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`);
|
|
17323
16624
|
}
|
|
17324
16625
|
async function stampCompositionDims(compositionDir, dims) {
|
|
17325
|
-
const metaPath =
|
|
16626
|
+
const metaPath = path11.join(compositionDir, "meta.json");
|
|
17326
16627
|
const rawMeta = await readFile6(metaPath, "utf8");
|
|
17327
16628
|
await writeFile2(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
|
|
17328
|
-
const htmlPath =
|
|
16629
|
+
const htmlPath = path11.join(compositionDir, "index.html");
|
|
17329
16630
|
const rawHtml = await readFile6(htmlPath, "utf8");
|
|
17330
16631
|
await writeFile2(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
|
|
17331
16632
|
}
|
|
@@ -17359,7 +16660,7 @@ async function detectShotCutsBestEffort(videoPath, threshold) {
|
|
|
17359
16660
|
process.stderr.write(`Shot-cut detection skipped (${msg}); using LLM boundaries.
|
|
17360
16661
|
`);
|
|
17361
16662
|
}
|
|
17362
|
-
return
|
|
16663
|
+
return [];
|
|
17363
16664
|
}
|
|
17364
16665
|
}
|
|
17365
16666
|
function fail2(code, message) {
|
|
@@ -17388,7 +16689,7 @@ function buildDeconstructCanvas(videoPath, deconstructModel, opts) {
|
|
|
17388
16689
|
if (typeof opts.maxScenes === "number") deconstructParams.max_scenes = opts.maxScenes;
|
|
17389
16690
|
if (opts.language) deconstructParams.language = opts.language;
|
|
17390
16691
|
if (opts.focus) deconstructParams.focus = opts.focus;
|
|
17391
|
-
if (opts.shotCuts) deconstructParams.shot_cuts = opts.shotCuts;
|
|
16692
|
+
if (opts.shotCuts && opts.shotCuts.length > 0) deconstructParams.shot_cuts = opts.shotCuts;
|
|
17392
16693
|
deconstructParams.max_clip_s = SEEDANCE_DURATIONS[SEEDANCE_DURATIONS.length - 1];
|
|
17393
16694
|
return {
|
|
17394
16695
|
schema: "baker-canvas/1",
|
|
@@ -17465,6 +16766,10 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
17465
16766
|
args: {
|
|
17466
16767
|
file: { type: "positional", required: true, description: "Path to the reference video" },
|
|
17467
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
|
+
},
|
|
17468
16773
|
frames: { type: "string", description: '"generate" (default, anchored regen) or "reuse" (wire real frames in)' },
|
|
17469
16774
|
ambient: {
|
|
17470
16775
|
type: "boolean",
|
|
@@ -17491,11 +16796,19 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
17491
16796
|
}
|
|
17492
16797
|
},
|
|
17493
16798
|
async run({ args }) {
|
|
17494
|
-
const videoPath =
|
|
17495
|
-
const base =
|
|
17496
|
-
const
|
|
17497
|
-
|
|
17498
|
-
|
|
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");
|
|
17499
16812
|
const frames = args.frames === "reuse" ? "reuse" : "generate";
|
|
17500
16813
|
const maxScenes = args["max-scenes"] ? Number(args["max-scenes"]) : void 0;
|
|
17501
16814
|
if (Number.isFinite(maxScenes)) {
|
|
@@ -17514,7 +16827,7 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
17514
16827
|
shotCuts
|
|
17515
16828
|
});
|
|
17516
16829
|
const { blueprint, elements, transcript, creditsSpent } = await runAnalysisPasses(deconstructCanvas, selectModel);
|
|
17517
|
-
await
|
|
16830
|
+
await mkdir2(outDir, { recursive: true });
|
|
17518
16831
|
const annotated = annotateBlueprintWithElements(blueprint, elements);
|
|
17519
16832
|
await writeFile2(blueprintPath, `${JSON.stringify(annotated, null, 2)}
|
|
17520
16833
|
`, "utf8");
|
|
@@ -17535,11 +16848,11 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
17535
16848
|
`
|
|
17536
16849
|
);
|
|
17537
16850
|
}
|
|
17538
|
-
const compositionDest =
|
|
17539
|
-
await
|
|
16851
|
+
const compositionDest = path11.join(outDir, "video-overlay-composition");
|
|
16852
|
+
await cp2(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
17540
16853
|
await stampCompositionDims(compositionDest, outDims);
|
|
17541
|
-
const indexPath =
|
|
17542
|
-
const overlayHtml = buildOverlayHtml(blueprint
|
|
16854
|
+
const indexPath = path11.join(compositionDest, "index.html");
|
|
16855
|
+
const overlayHtml = buildOverlayHtml(blueprint);
|
|
17543
16856
|
const indexHtml = await readFile6(indexPath, "utf8");
|
|
17544
16857
|
const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
17545
16858
|
if (injected === indexHtml && overlayHtml.trim()) {
|
|
@@ -17554,9 +16867,9 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
17554
16867
|
const opts = {
|
|
17555
16868
|
imageModel,
|
|
17556
16869
|
videoModel,
|
|
17557
|
-
overlayCompositionPath:
|
|
17558
|
-
captionsCompositionPath: captions.compositionPath ?
|
|
17559
|
-
blueprintPath:
|
|
16870
|
+
overlayCompositionPath: path11.relative(outDir, compositionDest),
|
|
16871
|
+
captionsCompositionPath: captions.compositionPath ? path11.relative(outDir, captions.compositionPath) : void 0,
|
|
16872
|
+
blueprintPath: path11.relative(outDir, blueprintPath),
|
|
17560
16873
|
frames,
|
|
17561
16874
|
ambient: Boolean(args.ambient),
|
|
17562
16875
|
...args.aspect ? { aspect: String(args.aspect) } : {},
|
|
@@ -17571,31 +16884,15 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
17571
16884
|
return fail2("scaffold", e instanceof Error ? e.message : String(e));
|
|
17572
16885
|
}
|
|
17573
16886
|
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(canvas, outDir), defaultRegistry());
|
|
17574
|
-
if (!validation.ok) {
|
|
17575
|
-
const meta = canvas.metadata;
|
|
17576
|
-
const todo = meta.todo ?? {};
|
|
17577
|
-
todo.blocking_validation_issues = validation.issues;
|
|
17578
|
-
meta.todo = todo;
|
|
17579
|
-
}
|
|
17580
|
-
await writeFile2(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
17581
|
-
`, "utf8");
|
|
17582
16887
|
if (!validation.ok) {
|
|
17583
16888
|
process.stderr.write(
|
|
17584
|
-
`${JSON.stringify(
|
|
17585
|
-
{
|
|
17586
|
-
ok: false,
|
|
17587
|
-
error: { code: "validation", issues: validation.issues },
|
|
17588
|
-
canvas_path: outPath,
|
|
17589
|
-
prompt_path: blueprintPath,
|
|
17590
|
-
note: "The canvas WAS written despite the blocking issue(s) \u2014 fix them in place, then `baker canvas validate` before running."
|
|
17591
|
-
},
|
|
17592
|
-
null,
|
|
17593
|
-
2
|
|
17594
|
-
)}
|
|
16889
|
+
`${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.issues } }, null, 2)}
|
|
17595
16890
|
`
|
|
17596
16891
|
);
|
|
17597
16892
|
process.exit(2);
|
|
17598
16893
|
}
|
|
16894
|
+
await writeFile2(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
16895
|
+
`, "utf8");
|
|
17599
16896
|
await ensureGitignore(process.cwd(), ["canvas/", ".context/"]);
|
|
17600
16897
|
process.stdout.write(
|
|
17601
16898
|
`${JSON.stringify(
|
|
@@ -17614,7 +16911,7 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
17614
16911
|
run_estimated_credits: validation.estimatedCredits
|
|
17615
16912
|
},
|
|
17616
16913
|
checklist: {
|
|
17617
|
-
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.`,
|
|
17618
16915
|
recurring_elements_to_supply: report.elements,
|
|
17619
16916
|
voices_to_confirm: report.dialogue.map((d) => ({
|
|
17620
16917
|
scene: d.scene,
|
|
@@ -17641,7 +16938,7 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
17641
16938
|
|
|
17642
16939
|
// src/commands/canvas/set-prompt.ts
|
|
17643
16940
|
import { readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
|
|
17644
|
-
import
|
|
16941
|
+
import path12 from "path";
|
|
17645
16942
|
import { defineCommand as defineCommand88 } from "citty";
|
|
17646
16943
|
function setNodePrompt(canvas, nodeId, text) {
|
|
17647
16944
|
const nodes = canvas?.nodes;
|
|
@@ -17669,7 +16966,7 @@ var setPromptCommand = defineCommand88({
|
|
|
17669
16966
|
"text-file": { type: "string", description: "Read the new prompt from a UTF-8 file (preserves accents/newlines)" }
|
|
17670
16967
|
},
|
|
17671
16968
|
async run({ args }) {
|
|
17672
|
-
const filePath =
|
|
16969
|
+
const filePath = path12.resolve(String(args.file));
|
|
17673
16970
|
let canvas;
|
|
17674
16971
|
try {
|
|
17675
16972
|
canvas = JSON.parse(await readFile7(filePath, "utf8"));
|
|
@@ -17679,7 +16976,7 @@ var setPromptCommand = defineCommand88({
|
|
|
17679
16976
|
process.exit(2);
|
|
17680
16977
|
}
|
|
17681
16978
|
let text;
|
|
17682
|
-
if (args["text-file"]) text = await readFile7(
|
|
16979
|
+
if (args["text-file"]) text = await readFile7(path12.resolve(String(args["text-file"])), "utf8");
|
|
17683
16980
|
else if (args.text !== void 0) text = String(args.text);
|
|
17684
16981
|
else {
|
|
17685
16982
|
process.stderr.write(
|
|
@@ -17700,7 +16997,7 @@ var setPromptCommand = defineCommand88({
|
|
|
17700
16997
|
process.exit(2);
|
|
17701
16998
|
return;
|
|
17702
16999
|
}
|
|
17703
|
-
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated,
|
|
17000
|
+
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated, path12.dirname(filePath)), defaultRegistry());
|
|
17704
17001
|
if (!validation.ok) {
|
|
17705
17002
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.issues } }, null, 2)}
|
|
17706
17003
|
`);
|
|
@@ -17716,7 +17013,7 @@ var setPromptCommand = defineCommand88({
|
|
|
17716
17013
|
|
|
17717
17014
|
// src/commands/canvas/validate.ts
|
|
17718
17015
|
import { readFile as readFile8 } from "fs/promises";
|
|
17719
|
-
import
|
|
17016
|
+
import path13 from "path";
|
|
17720
17017
|
import { defineCommand as defineCommand89 } from "citty";
|
|
17721
17018
|
var validateCommand = defineCommand89({
|
|
17722
17019
|
meta: {
|
|
@@ -17725,7 +17022,7 @@ var validateCommand = defineCommand89({
|
|
|
17725
17022
|
},
|
|
17726
17023
|
args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
|
|
17727
17024
|
async run({ args }) {
|
|
17728
|
-
const filePath =
|
|
17025
|
+
const filePath = path13.resolve(String(args.file));
|
|
17729
17026
|
const raw = await readFile8(filePath, "utf8");
|
|
17730
17027
|
let parsed;
|
|
17731
17028
|
try {
|
|
@@ -17736,7 +17033,7 @@ var validateCommand = defineCommand89({
|
|
|
17736
17033
|
`);
|
|
17737
17034
|
process.exit(2);
|
|
17738
17035
|
}
|
|
17739
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
17036
|
+
parsed = resolveRelativeCanvasPaths(parsed, path13.dirname(filePath));
|
|
17740
17037
|
const result = await validateCanvasDeep(parsed, defaultRegistry());
|
|
17741
17038
|
if (!result.ok) {
|
|
17742
17039
|
process.stderr.write(`${JSON.stringify({ ok: false, issues: result.issues }, null, 2)}
|
|
@@ -17873,6 +17170,16 @@ registerSchema({
|
|
|
17873
17170
|
type: "string",
|
|
17874
17171
|
description: "Optional URL of the original reference ad",
|
|
17875
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
|
|
17876
17183
|
}
|
|
17877
17184
|
}
|
|
17878
17185
|
});
|
|
@@ -17882,6 +17189,13 @@ function detectCreativeContentType(filePath) {
|
|
|
17882
17189
|
unsupportedMessage: "Unsupported creative image extension. Use PNG, JPG, or WebP."
|
|
17883
17190
|
});
|
|
17884
17191
|
}
|
|
17192
|
+
function chatIdFromEnv() {
|
|
17193
|
+
try {
|
|
17194
|
+
return getEnv().BAKER_CHAT_ID || void 0;
|
|
17195
|
+
} catch {
|
|
17196
|
+
return void 0;
|
|
17197
|
+
}
|
|
17198
|
+
}
|
|
17885
17199
|
function parseOptionalUrl(value) {
|
|
17886
17200
|
if (value === void 0 || value.trim() === "") {
|
|
17887
17201
|
return void 0;
|
|
@@ -17912,7 +17226,11 @@ async function publishCreative(args, deps = defaultImageApiDeps) {
|
|
|
17912
17226
|
return publishImageAsCreative(deps, {
|
|
17913
17227
|
imageId: upload.imageId,
|
|
17914
17228
|
title,
|
|
17915
|
-
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()
|
|
17916
17234
|
});
|
|
17917
17235
|
}
|
|
17918
17236
|
var publishCommand = defineCommand91({
|
|
@@ -17928,6 +17246,16 @@ var publishCommand = defineCommand91({
|
|
|
17928
17246
|
type: "string",
|
|
17929
17247
|
description: "Optional URL of the original reference ad",
|
|
17930
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
|
|
17931
17259
|
}
|
|
17932
17260
|
},
|
|
17933
17261
|
run: async ({ args }) => {
|
|
@@ -17946,7 +17274,9 @@ var publishCommand = defineCommand91({
|
|
|
17946
17274
|
file,
|
|
17947
17275
|
title,
|
|
17948
17276
|
context: args.context,
|
|
17949
|
-
sourceReferenceUrl: args.sourceReferenceUrl
|
|
17277
|
+
sourceReferenceUrl: args.sourceReferenceUrl,
|
|
17278
|
+
slug: args.slug,
|
|
17279
|
+
runId: args.runId
|
|
17950
17280
|
});
|
|
17951
17281
|
writeJson({ ok: true, data });
|
|
17952
17282
|
} catch (err) {
|
|
@@ -18844,9 +18174,9 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
18844
18174
|
}
|
|
18845
18175
|
return readFile10(pathOrUrl);
|
|
18846
18176
|
}
|
|
18847
|
-
async function isDirectory(
|
|
18177
|
+
async function isDirectory(path14) {
|
|
18848
18178
|
try {
|
|
18849
|
-
const s = await stat2(
|
|
18179
|
+
const s = await stat2(path14);
|
|
18850
18180
|
return s.isDirectory();
|
|
18851
18181
|
} catch {
|
|
18852
18182
|
return false;
|