@koda-sl/baker-cli 0.252.0-dev.e7a1a227e → 0.253.0
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 +0 -4
- package/dist/{chunk-VLNO5AUC.js → chunk-SJPRTSGY.js} +4 -4
- package/dist/chunk-SJPRTSGY.js.map +1 -0
- package/dist/cli.js +104 -68
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-VLNO5AUC.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
ulid,
|
|
59
59
|
validateCanvasDeep,
|
|
60
60
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-SJPRTSGY.js";
|
|
62
62
|
import {
|
|
63
63
|
csvOrJson,
|
|
64
64
|
daysAgoIso,
|
|
@@ -3682,7 +3682,6 @@ var avatarProfileSchema = z7.object({
|
|
|
3682
3682
|
subjectDescription: z7.string().trim().min(1).max(2e3),
|
|
3683
3683
|
persona: z7.string().trim().max(2e3).optional(),
|
|
3684
3684
|
speech: z7.string().trim().max(2e3).optional(),
|
|
3685
|
-
accent: z7.string().trim().max(2e3).optional(),
|
|
3686
3685
|
motion: z7.string().trim().max(2e3).optional(),
|
|
3687
3686
|
wardrobe: z7.string().trim().max(2e3).optional(),
|
|
3688
3687
|
setting: z7.string().trim().max(2e3).optional(),
|
|
@@ -3698,6 +3697,7 @@ var avatarSummarySchema = z7.object({
|
|
|
3698
3697
|
/** The identity sheet to wire into `--reference`. Absent until the build settles. */
|
|
3699
3698
|
sheetUrl: z7.string().optional(),
|
|
3700
3699
|
coverUrl: z7.string().optional(),
|
|
3700
|
+
hasVoice: z7.boolean(),
|
|
3701
3701
|
errorMessage: z7.string().optional()
|
|
3702
3702
|
});
|
|
3703
3703
|
var avatarDetailSchema = avatarSummarySchema.extend({
|
|
@@ -4292,7 +4292,7 @@ var FLOW_NODE_CATALOG = [
|
|
|
4292
4292
|
}
|
|
4293
4293
|
}
|
|
4294
4294
|
},
|
|
4295
|
-
gotcha: "`fieldtype` is one of: checkbox, date_picker, date_time_range, email, hidden, input, multi_select, number, phone, select, textarea. `select`/`multi_select` need non-empty `options`; `phone` needs `defaultCountry`; a field rendered by your own code is declared `hidden`. A field your page validates itself (postal code against a service area, VAT number, order reference) also needs `testValue` \u2014 the Form's own checks invent `TestValue` otherwise and never get past that step.",
|
|
4295
|
+
gotcha: "`fieldtype` is one of: checkbox, date_picker, date_time_range, email, hidden, input, multi_select, number, phone, select, textarea. `select`/`multi_select` need non-empty `options`; `phone` needs `defaultCountry`; a field rendered by your own code is declared `hidden`. A field your page validates itself (postal code against a service area, VAT number, order reference) also needs `testValue` \u2014 the Form's own checks invent `TestValue` otherwise and never get past that step. A field holding something that has no business on a dashboard the client's whole team can open \u2014 a date of birth, a policy or account number, a salary, anything medical or legal \u2014 takes `sensitive: true`: an email or phone is masked already, everything else is reported verbatim unless marked. A contact field that is not the visitor's own (a manager's address, the office number) takes `identity: \"none\"`, or two people are filed as one.",
|
|
4296
4296
|
reference: NODE_REF("customForm")
|
|
4297
4297
|
},
|
|
4298
4298
|
{
|
|
@@ -7411,11 +7411,7 @@ registerSchema({
|
|
|
7411
7411
|
command: "actions.status",
|
|
7412
7412
|
description: "Resolve one or more Work Action refs by real action ID or temp_* ref in a single batch call. When BAKER_CHAT_ID is set, a temp_* ref still staged in THIS chat resolves to status 'draft' (not 'not_found') \u2014 staged ops only become published actions on chat publish.",
|
|
7413
7413
|
args: {
|
|
7414
|
-
|
|
7415
|
-
type: "positional",
|
|
7416
|
-
description: "One or more action refs: real action IDs or temp_* refs (space-separated)",
|
|
7417
|
-
required: true
|
|
7418
|
-
}
|
|
7414
|
+
refs: { type: "string", description: "One or more action refs: real action IDs or temp_* refs", required: true }
|
|
7419
7415
|
}
|
|
7420
7416
|
});
|
|
7421
7417
|
var statusCommand = defineCommand11({
|
|
@@ -12320,10 +12316,10 @@ var analyticsPresetSchema = z24.enum([
|
|
|
12320
12316
|
*/
|
|
12321
12317
|
"geo",
|
|
12322
12318
|
/**
|
|
12323
|
-
* Which machines are fetching this site
|
|
12319
|
+
* Which machines are fetching this site.
|
|
12324
12320
|
*
|
|
12325
|
-
* Every other preset here
|
|
12326
|
-
*
|
|
12321
|
+
* Every other preset here excludes them, which is correct — a crawler is not
|
|
12322
|
+
* an audience — and a verify check now enforces that rather than trusting it. The rows are kept, though, and this is what
|
|
12327
12323
|
* they are kept for: whether Google is still crawling a page that was
|
|
12328
12324
|
* republished, whether Meta fetched the link preview before spend went
|
|
12329
12325
|
* behind the URL, and how much of the traffic is now AI answer engines.
|
|
@@ -12993,12 +12989,6 @@ var analyticsBotRowSchema = z24.object({
|
|
|
12993
12989
|
bot: z24.string(),
|
|
12994
12990
|
/** `search` | `ai` | `social` | `seo` | `monitoring` | `tool` | `automation`. */
|
|
12995
12991
|
category: z24.string(),
|
|
12996
|
-
/**
|
|
12997
|
-
* Cloudflare confirmed it by the network it came from, rather than believing
|
|
12998
|
-
* its user agent. A page fetched only by unverified self-declared Googlebots
|
|
12999
|
-
* was not crawled by Google.
|
|
13000
|
-
*/
|
|
13001
|
-
verified: z24.boolean(),
|
|
13002
12992
|
hits: z24.number().int().nonnegative(),
|
|
13003
12993
|
pages: z24.number().int().nonnegative(),
|
|
13004
12994
|
lastSeen: z24.string(),
|
|
@@ -19270,6 +19260,14 @@ var CTA_TYPES2 = [
|
|
|
19270
19260
|
"BUY_TICKETS",
|
|
19271
19261
|
"NO_BUTTON"
|
|
19272
19262
|
];
|
|
19263
|
+
var LEAD_FORM_CTA_TYPES = [
|
|
19264
|
+
"APPLY_NOW",
|
|
19265
|
+
"DOWNLOAD",
|
|
19266
|
+
"GET_QUOTE",
|
|
19267
|
+
"LEARN_MORE",
|
|
19268
|
+
"SIGN_UP",
|
|
19269
|
+
"SUBSCRIBE"
|
|
19270
|
+
];
|
|
19273
19271
|
var AD_FORMATS2 = ["SINGLE_IMAGE", "CAROUSEL", "SINGLE_VIDEO"];
|
|
19274
19272
|
var ENROLL_STATUSES = ["OPT_IN", "OPT_OUT"];
|
|
19275
19273
|
var STAGEABLE_CREATE_STATUSES3 = ["ACTIVE", "PAUSED"];
|
|
@@ -19692,7 +19690,23 @@ var descriptionSchema = z25.string().min(1).max(META_LIMITS.creative.description
|
|
|
19692
19690
|
var callToActionSchema = z25.object({
|
|
19693
19691
|
type: z25.enum(CTA_TYPES2),
|
|
19694
19692
|
/** Overrides the base link for the CTA button; defaults to the ad's link. */
|
|
19695
|
-
link: httpsUrlSchema3.optional()
|
|
19693
|
+
link: httpsUrlSchema3.optional(),
|
|
19694
|
+
/**
|
|
19695
|
+
* The instant form the button opens — Meta's `call_to_action.value.lead_gen_form_id`.
|
|
19696
|
+
* This is the ONLY place an instant form is named: the ad set says leads open
|
|
19697
|
+
* on the ad (`destination_type: ON_AD`), and the creative says which form.
|
|
19698
|
+
* Without it Meta refuses every ad in that ad set with subcode 3390001,
|
|
19699
|
+
* "Choose or create an instant form for your leads campaign".
|
|
19700
|
+
*/
|
|
19701
|
+
lead_gen_form_id: z25.string().regex(NUMERIC_ID_REGEX3).optional()
|
|
19702
|
+
}).superRefine((p, ctx) => {
|
|
19703
|
+
if (p.lead_gen_form_id && !LEAD_FORM_CTA_TYPES.some((allowed) => allowed === p.type)) {
|
|
19704
|
+
ctx.addIssue({
|
|
19705
|
+
code: "custom",
|
|
19706
|
+
path: ["type"],
|
|
19707
|
+
message: `${p.type} cannot open an instant form \u2014 Meta refuses it (subcode 1856030). Use one of ${LEAD_FORM_CTA_TYPES.join(", ")}, or drop lead_gen_form_id if this ad should go to the website.`
|
|
19708
|
+
});
|
|
19709
|
+
}
|
|
19696
19710
|
});
|
|
19697
19711
|
var creativeEnhancementsSchema = z25.object({
|
|
19698
19712
|
standardEnhancements: z25.enum(ENROLL_STATUSES).optional(),
|
|
@@ -19790,6 +19804,19 @@ var carouselCreativeSchema2 = z25.object({
|
|
|
19790
19804
|
link: httpsUrlSchema3.optional(),
|
|
19791
19805
|
call_to_action: callToActionSchema.optional(),
|
|
19792
19806
|
cards: z25.array(carouselCardSchema).min(META_LIMITS.creative.carouselCardsMin).max(META_LIMITS.creative.carouselCardsMax)
|
|
19807
|
+
}).superRefine((p, ctx) => {
|
|
19808
|
+
const forms = new Set(
|
|
19809
|
+
[p.call_to_action?.lead_gen_form_id, ...p.cards.map((card) => card.call_to_action?.lead_gen_form_id)].filter(
|
|
19810
|
+
Boolean
|
|
19811
|
+
)
|
|
19812
|
+
);
|
|
19813
|
+
if (forms.size > 1) {
|
|
19814
|
+
ctx.addIssue({
|
|
19815
|
+
code: "custom",
|
|
19816
|
+
path: ["cards"],
|
|
19817
|
+
message: `every card of a carousel must open the SAME instant form \u2014 this one names ${[...forms].join(" and ")}`
|
|
19818
|
+
});
|
|
19819
|
+
}
|
|
19793
19820
|
});
|
|
19794
19821
|
var dynamicImageSchema = z25.object({ ...imageMediaFields }).refine((p) => countImageRefs(p) === 1, "each dynamic image needs exactly one reference");
|
|
19795
19822
|
var dynamicVideoSchema = z25.object({
|
|
@@ -20296,7 +20323,10 @@ var adSetWriteArgs = {
|
|
|
20296
20323
|
"daily-budget": { type: "string", description: "Daily budget (ABO; omit if the campaign uses CBO)" },
|
|
20297
20324
|
"lifetime-budget": { type: "string", description: "Lifetime budget (needs --end)" },
|
|
20298
20325
|
"bid-amount": { type: "string", description: "Bid amount (required for COST_CAP / bid-cap strategies)" },
|
|
20299
|
-
"bid-strategy": {
|
|
20326
|
+
"bid-strategy": {
|
|
20327
|
+
type: "string",
|
|
20328
|
+
description: "LOWEST_COST_WITHOUT_CAP (auto-bid) | LOWEST_COST_WITH_BID_CAP | COST_CAP \u2014 needs --bid-amount. Omit and Baker sets auto-bid; never leave it to Meta, whose default is a bid cap that then fails without an amount."
|
|
20329
|
+
},
|
|
20300
20330
|
currency: { type: "string", description: "3-letter currency (defaults to the account currency)" },
|
|
20301
20331
|
start: { type: "string", description: "Start time" },
|
|
20302
20332
|
end: { type: "string", description: "End time" },
|
|
@@ -20363,7 +20393,7 @@ function creativePayloadFromFlags(args) {
|
|
|
20363
20393
|
headline: args.headline,
|
|
20364
20394
|
description: args.description,
|
|
20365
20395
|
caption: args.caption,
|
|
20366
|
-
call_to_action:
|
|
20396
|
+
call_to_action: creativeCta(args),
|
|
20367
20397
|
imageHash: args["image-hash"],
|
|
20368
20398
|
imageRef: args["image-ref"],
|
|
20369
20399
|
videoId: args["video-id"],
|
|
@@ -20373,6 +20403,26 @@ function creativePayloadFromFlags(args) {
|
|
|
20373
20403
|
enhancements
|
|
20374
20404
|
};
|
|
20375
20405
|
}
|
|
20406
|
+
function ctaPatchFromFlags(args) {
|
|
20407
|
+
const patch = {
|
|
20408
|
+
...args.cta ? { type: String(upper(args.cta)) } : {},
|
|
20409
|
+
...args["lead-form"] === void 0 ? {} : { lead_gen_form_id: String(args["lead-form"]) }
|
|
20410
|
+
};
|
|
20411
|
+
return Object.keys(patch).length > 0 ? patch : void 0;
|
|
20412
|
+
}
|
|
20413
|
+
function creativeCta(args) {
|
|
20414
|
+
const patch = ctaPatchFromFlags(args);
|
|
20415
|
+
return patch ? { type: "SIGN_UP", ...patch } : void 0;
|
|
20416
|
+
}
|
|
20417
|
+
function creativeContentPatch2(file, args) {
|
|
20418
|
+
const cta = ctaPatchFromFlags(args);
|
|
20419
|
+
if (!cta) {
|
|
20420
|
+
return void 0;
|
|
20421
|
+
}
|
|
20422
|
+
const fileContent = file.content ?? {};
|
|
20423
|
+
const fileCta = fileContent.call_to_action ?? {};
|
|
20424
|
+
return { ...fileContent, call_to_action: { ...fileCta, ...cta } };
|
|
20425
|
+
}
|
|
20376
20426
|
function parseEnroll(value) {
|
|
20377
20427
|
const raw = String(value).toLowerCase();
|
|
20378
20428
|
if (raw === "on" || raw === "true" || raw === "opt_in") return "OPT_IN";
|
|
@@ -20399,6 +20449,10 @@ Example: baker ads meta creatives create --page 555 --message "Save now" --link
|
|
|
20399
20449
|
description: { type: "string", description: "Description" },
|
|
20400
20450
|
caption: { type: "string", description: "Display URL / caption" },
|
|
20401
20451
|
cta: { type: "string", description: "Call-to-action type (SHOP_NOW|LEARN_MORE|SIGN_UP|\u2026)" },
|
|
20452
|
+
"lead-form": {
|
|
20453
|
+
type: "string",
|
|
20454
|
+
description: "Instant form id the button opens \u2014 REQUIRED for an ad set that collects leads on the ad. List them with `baker ads meta lead-forms`. With a form, --cta must be APPLY_NOW|DOWNLOAD|GET_QUOTE|LEARN_MORE|SIGN_UP|SUBSCRIBE"
|
|
20455
|
+
},
|
|
20402
20456
|
"image-hash": { type: "string", description: "Meta ad-image hash (already uploaded)" },
|
|
20403
20457
|
"image-ref": { type: "string", description: "meta_temp_* ref of a staged media upload" },
|
|
20404
20458
|
"video-id": { type: "string", description: "Meta video id (already uploaded)" },
|
|
@@ -20432,6 +20486,11 @@ Amending a staged (meta_temp_*) creative merges fields into the create. Example:
|
|
|
20432
20486
|
...accountArgs2,
|
|
20433
20487
|
name: { type: "string", description: "New name" },
|
|
20434
20488
|
status: { type: "string", description: "ACTIVE|PAUSED|ARCHIVED" },
|
|
20489
|
+
cta: { type: "string", description: "Call-to-action type (staged creatives only)" },
|
|
20490
|
+
"lead-form": {
|
|
20491
|
+
type: "string",
|
|
20492
|
+
description: "Instant form id the button opens (staged creatives only) \u2014 `baker ads meta lead-forms` lists them"
|
|
20493
|
+
},
|
|
20435
20494
|
file: {
|
|
20436
20495
|
type: "string",
|
|
20437
20496
|
description: "JSON file with fields to change (content only merges into a staged creative)"
|
|
@@ -20439,7 +20498,15 @@ Amending a staged (meta_temp_*) creative merges fields into the create. Example:
|
|
|
20439
20498
|
},
|
|
20440
20499
|
run: async ({ args }) => {
|
|
20441
20500
|
const accountId = bareAccountId2(args);
|
|
20442
|
-
const
|
|
20501
|
+
const file = loadJsonFileArg3(args.file);
|
|
20502
|
+
const payload = mergePayload3(file, {
|
|
20503
|
+
name: args.name,
|
|
20504
|
+
status: upper(args.status),
|
|
20505
|
+
// A creative's content is immutable on Meta, so this only ever lands on a
|
|
20506
|
+
// creative still staged in this chat — which is exactly the fix path when
|
|
20507
|
+
// staging an ad refuses it for naming no instant form.
|
|
20508
|
+
content: creativeContentPatch2(file, args)
|
|
20509
|
+
});
|
|
20443
20510
|
await stageOp2({
|
|
20444
20511
|
kind: "adCreative.update",
|
|
20445
20512
|
accountId,
|
|
@@ -23629,7 +23696,7 @@ var ANALYTICS_PRESET_INFO = [
|
|
|
23629
23696
|
},
|
|
23630
23697
|
{
|
|
23631
23698
|
name: "flow",
|
|
23632
|
-
description: "One Form in depth: conversions counted at the node that produced them, step-to-step paths for a branching Form, the per-step table, every trigger raised, and each marked conversion by name. Read convertedSessions rather than submits \u2014 a Form whose scheduling widget sits mid-flow never emits a submit and reports zero on every other report. An empty `conversions` array means nobody has marked what this Form is for, so the numbers rest on a built-in guess; mark it in the Forms builder or with `conversions: [{ triggerId }]` on the node.",
|
|
23699
|
+
description: "One Form in depth: conversions counted at the node that produced them, step-to-step paths for a branching Form, the per-step table, every trigger raised, and each marked conversion by name. Read convertedSessions rather than submits \u2014 a Form whose scheduling widget sits mid-flow never emits a submit and reports zero on every other report. An empty `conversions` array means nobody has marked what this Form is for, so the numbers rest on a built-in guess; mark it in the Forms builder or with `conversions: [{ triggerId }]` on the node. `flowDestinations` is the other half and is evidence rather than a count: which Forms demonstrably handed something over in this window. Read it against the scaffold's `flow-no-destination` warning before acting on one \u2014 the stored picture of a Form is only rewritten when it is published, so a destination added since then shows there and not in the tree.",
|
|
23633
23700
|
playbook: "flow-builder \u2014 fix the branch or the step that loses people"
|
|
23634
23701
|
},
|
|
23635
23702
|
{
|
|
@@ -23664,7 +23731,7 @@ var ANALYTICS_PRESET_INFO = [
|
|
|
23664
23731
|
},
|
|
23665
23732
|
{
|
|
23666
23733
|
name: "bots",
|
|
23667
|
-
description: "Which machines fetch this site
|
|
23734
|
+
description: "Which machines fetch this site. Every other report here excludes them; this one reads them. Answers whether Google is still crawling a page after it was republished, whether Meta fetched a link preview before spend went behind the URL, and how much of the traffic is now AI answer engines.",
|
|
23668
23735
|
playbook: "landing \u2014 a page that stopped being crawled stops ranking, weeks before the traffic shows it"
|
|
23669
23736
|
},
|
|
23670
23737
|
{
|
|
@@ -24108,7 +24175,7 @@ var funnelCommand = presetCommand({
|
|
|
24108
24175
|
var flowCommand = presetCommand({
|
|
24109
24176
|
name: "flow",
|
|
24110
24177
|
preset: "flow",
|
|
24111
|
-
description: "One Form in depth: how many people it converted, where they went between steps, the per-step table, and every trigger it raised. Every count is DISTINCT VISITS, so quote shares against `funnels[].visits` \u2014 the visits that opened the Form \u2014 and never against `starts`, which is only the visits that touched it. The gap between the two is people who read the Form and left, and on most Forms it is the largest loss there is. Read `flowSummary.convertedSessions`, not `submits` \u2014 a Form that books a call on a scheduling node in the MIDDLE of the flow never emits a submit, so it reports zero submits and every one of its real bookings as conversions. `flowPaths` is the flow as edges between steps, bracketed by __flow_start__, __flow_converted__ and __flow_exit__, which is how a branching Form is read at all: a per-step table cannot say that of the people who left step two, forty went to the booking branch and ninety went nowhere.",
|
|
24178
|
+
description: "One Form in depth: how many people it converted, where they went between steps, the per-step table, and every trigger it raised. Every count is DISTINCT VISITS, so quote shares against `funnels[].visits` \u2014 the visits that opened the Form \u2014 and never against `starts`, which is only the visits that touched it. The gap between the two is people who read the Form and left, and on most Forms it is the largest loss there is. Read `flowSummary.convertedSessions`, not `submits` \u2014 a Form that books a call on a scheduling node in the MIDDLE of the flow never emits a submit, so it reports zero submits and every one of its real bookings as conversions. `flowPaths` is the flow as edges between steps, bracketed by __flow_start__, __flow_converted__ and __flow_exit__, which is how a branching Form is read at all: a per-step table cannot say that of the people who left step two, forty went to the booking branch and ninety went nowhere. `flowDestinations` is the other half and is evidence rather than a count: which Forms demonstrably handed something over in this window. Read it against the scaffold's `flow-no-destination` warning before acting on one \u2014 the stored picture of a Form is only rewritten when it is published, so a destination added since then shows there and not in the tree.",
|
|
24112
24179
|
extraArgs: { flow: { type: "string", description: "Form slug (default: every Form)", required: false } },
|
|
24113
24180
|
resolve: (args) => ({ preset: "flow", flowSlug: args.flow ? String(args.flow) : void 0 })
|
|
24114
24181
|
});
|
|
@@ -24450,8 +24517,7 @@ import { defineCommand as defineCommand94 } from "citty";
|
|
|
24450
24517
|
import { defineCommand as defineCommand89 } from "citty";
|
|
24451
24518
|
|
|
24452
24519
|
// src/commands/avatars/casting.ts
|
|
24453
|
-
var
|
|
24454
|
-
var VERBATIM_RULE = "When you do write the description yourself \u2014 a canvas node, a landing image, anywhere `--avatar` does not exist \u2014 copy `subjectDescription` VERBATIM, word for word. Re-phrasing it per generation is the other reason a face drifts across a set.";
|
|
24520
|
+
var VERBATIM_RULE = "Copy `subjectDescription` into the prompt VERBATIM, word for word. Re-phrasing it per generation is the other reason a face drifts across a set \u2014 treat it as an identifier that happens to read as prose.";
|
|
24455
24521
|
var VIDEO_ROUTING = "In video, a photoreal presenter renders on `google/veo-3.1` (or `google/veo-3.1-fast`), never `bytedance/seedance-2.0` \u2014 it refuses photoreal human faces, AI-generated ones included. Scaffolding a video creative: `baker canvas scaffold-video \u2026 --real-face`.";
|
|
24456
24522
|
function castingHints(avatar) {
|
|
24457
24523
|
if (avatar.status === "generating") {
|
|
@@ -24475,8 +24541,7 @@ function castingHints(avatar) {
|
|
|
24475
24541
|
];
|
|
24476
24542
|
}
|
|
24477
24543
|
return [
|
|
24478
|
-
`Ready to cast
|
|
24479
|
-
CAST_FLAG_RULE,
|
|
24544
|
+
`Ready to cast. Ground every render on the identity sheet, never on a source photo: \`baker images generate "<your scene>" --reference ${avatar.sheetUrl}\`.`,
|
|
24480
24545
|
VERBATIM_RULE,
|
|
24481
24546
|
VIDEO_ROUTING
|
|
24482
24547
|
];
|
|
@@ -24488,6 +24553,7 @@ function rosterRow(avatar) {
|
|
|
24488
24553
|
status: avatar.status,
|
|
24489
24554
|
likeness: avatar.likeness,
|
|
24490
24555
|
sheetUrl: avatar.sheetUrl ?? null,
|
|
24556
|
+
hasVoice: avatar.hasVoice,
|
|
24491
24557
|
avatarId: avatar.avatarId,
|
|
24492
24558
|
...avatar.errorMessage ? { errorMessage: avatar.errorMessage } : {}
|
|
24493
24559
|
};
|
|
@@ -24515,19 +24581,10 @@ function rosterHints(roster, statusFilter) {
|
|
|
24515
24581
|
}
|
|
24516
24582
|
return hints;
|
|
24517
24583
|
}
|
|
24518
|
-
function
|
|
24519
|
-
const missing = ["motion", "persona", "speech", "accent", "wardrobe", "setting"].filter(
|
|
24520
|
-
(field) => !profile?.[field]?.trim()
|
|
24521
|
-
);
|
|
24522
|
-
if (missing.length === 0) return null;
|
|
24523
|
-
return `MISSING ${missing.map((field) => `--${field}`).join(" ")}. A bare subject description holds the face still and nothing else, so clips of @${handle} come out stiff and generic \u2014 measured, filling these in nearly doubled the movement in the same brief. --accent is the one nobody guesses and the only one that fails silently: it decides the accent the avatar SPEAKS with in every clip, so name the region the user is writing to you from ("Spanish from Spain, Madrid accent") \u2014 "Spanish" on its own renders as no accent at all. The user almost certainly told you how this person talks and carries themselves; put it in: \`baker avatars update ${handle} ${missing.map((field) => `--${field} "\u2026"`).join(" ")}\`.`;
|
|
24524
|
-
}
|
|
24525
|
-
function creationHints(created, profile) {
|
|
24526
|
-
const thin = thinProfileHint(profile, created.handle);
|
|
24584
|
+
function creationHints(created) {
|
|
24527
24585
|
const hints = [
|
|
24528
|
-
...thin ? [thin] : [],
|
|
24529
24586
|
...castingHints({ handle: created.handle, status: created.status }),
|
|
24530
|
-
`Once it is ready,
|
|
24587
|
+
`Once it is ready, every render grounds on its identity sheet (\`--reference <sheetUrl>\`) and reuses the subject description you just wrote, word for word. Those two things are what keep the face identical across a set.`
|
|
24531
24588
|
];
|
|
24532
24589
|
if (created.likeness === "licensed") {
|
|
24533
24590
|
hints.push(
|
|
@@ -24613,7 +24670,6 @@ var TEXT_FIELDS = [
|
|
|
24613
24670
|
["subject", "subjectDescription"],
|
|
24614
24671
|
["persona", "persona"],
|
|
24615
24672
|
["speech", "speech"],
|
|
24616
|
-
["accent", "accent"],
|
|
24617
24673
|
["motion", "motion"],
|
|
24618
24674
|
["wardrobe", "wardrobe"],
|
|
24619
24675
|
["setting", "setting"]
|
|
@@ -24702,12 +24758,7 @@ registerSchema({
|
|
|
24702
24758
|
required: true
|
|
24703
24759
|
},
|
|
24704
24760
|
persona: { type: "string", description: "Who they are on camera \u2014 role, attitude, energy", required: false },
|
|
24705
|
-
speech: { type: "string", description: "How they speak \u2014 pace, register, verbal habits", required: false },
|
|
24706
|
-
accent: {
|
|
24707
|
-
type: "string",
|
|
24708
|
-
description: "Where their voice is FROM, as a place: 'Spanish from Spain, Madrid accent', 'Mexican Spanish', 'British English, London'. Name the region \u2014 an adjective like 'clear Spanish accent' is read by the video model as NO accent, and the clip comes out sounding like nowhere. Set it whenever the avatar will speak; the user's own language is the default to assume, not English.",
|
|
24709
|
-
required: false
|
|
24710
|
-
},
|
|
24761
|
+
speech: { type: "string", description: "How they speak \u2014 pace, register, accent, verbal habits", required: false },
|
|
24711
24762
|
motion: { type: "string", description: "Body language and gesture in video", required: false },
|
|
24712
24763
|
wardrobe: { type: "string", description: "Default outfit when the brief does not name one", required: false },
|
|
24713
24764
|
setting: { type: "string", description: "Default surroundings when the brief leaves them open", required: false },
|
|
@@ -24741,11 +24792,6 @@ var createCommand2 = defineCommand89({
|
|
|
24741
24792
|
subject: { type: "string", description: "The person, plainly described \u2014 reused verbatim", required: false },
|
|
24742
24793
|
persona: { type: "string", description: "Who they are on camera", required: false },
|
|
24743
24794
|
speech: { type: "string", description: "How they speak", required: false },
|
|
24744
|
-
accent: {
|
|
24745
|
-
type: "string",
|
|
24746
|
-
description: "Where their voice is FROM \u2014 name the region, e.g. 'Spanish from Spain, Madrid accent'",
|
|
24747
|
-
required: false
|
|
24748
|
-
},
|
|
24749
24795
|
motion: { type: "string", description: "Body language in video", required: false },
|
|
24750
24796
|
wardrobe: { type: "string", description: "Default outfit", required: false },
|
|
24751
24797
|
setting: { type: "string", description: "Default surroundings", required: false },
|
|
@@ -24808,7 +24854,7 @@ var createCommand2 = defineCommand89({
|
|
|
24808
24854
|
...chatIdFromEnv() ? { chatId: chatIdFromEnv() } : {}
|
|
24809
24855
|
};
|
|
24810
24856
|
const data = await writeAvatars("/api/avatars", body);
|
|
24811
|
-
writeJson({ ok: true, data, hints: creationHints(data
|
|
24857
|
+
writeJson({ ok: true, data, hints: creationHints(data) });
|
|
24812
24858
|
} catch (err) {
|
|
24813
24859
|
failAvatarApi(err, handle);
|
|
24814
24860
|
}
|
|
@@ -29940,7 +29986,7 @@ function buildVideoTodo(report, overlayCount, floatingCount, opts, blueprint) {
|
|
|
29940
29986
|
]
|
|
29941
29987
|
},
|
|
29942
29988
|
transitions: "Scene-to-scene cuts the deconstruct flagged as fade/whip/zoom/dissolve/swipe are reproduced as an ffmpeg xfade at the boundary (everything else stays a hard cut). The overlap is consumed from extra generated footage, so the picture stays exactly on the audio timeline. To change a transition, edit the scene's `transition_out.type` in prompt.json and re-scaffold, or hand-edit the `spine` node's ffmpeg args. For a richer HERO cut (whip-pan, glitch, light-leak, gravitational-lens\u2026), the overlay layer can run a Hyperframes shader/CSS transition instead \u2014 see references/hyperframes/blueprints-and-transitions.md (pick 2\u20133 transition types total; the motion IS the handoff).",
|
|
29943
|
-
overlay_capabilities: 'The overlay layer (video-overlay-composition/index.html) is a REAL Hyperframes composition, not a plain text layer \u2014 our hyperframe_render node runs the genuine `npx hyperframes` renderer. So it can do far more than fade/slide/pop: (1) PULL a ready-made block \u2014 `npx hyperframes catalog --type block` then `npx hyperframes add <id>` into the composition dir, and nest it with a <div data-composition-src="compositions/<id>.html" data-start data-track-index data-width data-height> clip (~
|
|
29989
|
+
overlay_capabilities: 'The overlay layer (video-overlay-composition/index.html) is a REAL Hyperframes composition, not a plain text layer \u2014 our hyperframe_render node runs the genuine `npx hyperframes` renderer. So it can do far more than fade/slide/pop: (1) PULL a ready-made block \u2014 `npx hyperframes catalog --type block` then `npx hyperframes add <id>` into the composition dir, and nest it with a <div data-composition-src="compositions/<id>.html" data-start data-track-index data-width data-height> clip (~97 blocks: lower-thirds, social-proof cards, stat counters, charts, code, logo stings); (2) animate with the named GSAP motion-rule vocabulary (kinetic-beat-slam, svg-path-draw, counting-dynamic-scale, multi-phase-camera\u2026); (3) frame a talking head as a video-call/PIP/split. The whole engine + how to build new effects is documented in references/hyperframes/ (start at README.md \u2192 catalog.md, motion-rules.md). Reach for this whenever the reference\'s graphics are richer than plain text.',
|
|
29944
29990
|
caption_system: "Sound-off feed = burned-in captions carry the message. The deconstruct produced a word-level transcript \u2014 drive a real caption track off it (karaoke highlight \u2192 themed \u2192 kinetic), not hand-typed text. Floor: `npx hyperframes add caption-highlight` (TikTok karaoke); escalate by content register (caption-kinetic-slam for hype, caption-editorial-emphasis for premium, caption-neon-glow for AI/crypto). Group on meaning, sync within 80ms. Full ladder + transcript contract: references/hyperframes/captions-and-audio.md.",
|
|
29945
29991
|
talking_head_overlay: "For a scene where a presenter shows something (screen-share, app demo, product, a 'video call' look), frame the talking-head clip in the overlay composition: presenter full-bleed \u2192 shrinks to a rounded corner PIP pill while a content card takes the stage (or split/stack/overlay-glass-card). The pattern, the #video-wrap\u2192corner-pill tween, the glass call card, and the corner-coord presets are in references/hyperframes/talking-head-and-overlays.md. A ready exemplar ships as the `video-call-composition` move \u2014 copy it next to your canvas, drop brand fonts in the dir, point a `hyperframe_render` at it with the presenter clip as its `background`.",
|
|
29946
29992
|
text_overlays: {
|
|
@@ -48044,7 +48090,7 @@ registerSchema({
|
|
|
48044
48090
|
},
|
|
48045
48091
|
image: {
|
|
48046
48092
|
type: "string",
|
|
48047
|
-
description: "Comma-separated source images: library image ids, local file paths, and/or image URLs. Required unless --from text
|
|
48093
|
+
description: "Comma-separated source images: library image ids, local file paths, and/or image URLs. Required unless --from text. With the default --from frame this must be exactly ONE image (the opening frame) \u2014 several are refused rather than silently reduced to the first. With --from references, up to 9 on Seedance 2.0 and 30 on Seedance 2.5.",
|
|
48048
48094
|
required: false
|
|
48049
48095
|
},
|
|
48050
48096
|
from: {
|
|
@@ -48263,15 +48309,10 @@ function costHintsFor(body) {
|
|
|
48263
48309
|
var animateCommand = defineCommand189({
|
|
48264
48310
|
meta: {
|
|
48265
48311
|
name: "animate",
|
|
48266
|
-
description: "Render a clip. With an image the look is already fixed, so the prompt describes MOVEMENT \u2014 what the camera does, what the subject does, in what order. With --from text there is no image and the prompt is the whole shot.\n\nA rendered clip is NOT usable anywhere until you keep it: `baker studio keep <id> --slot N` is what puts it in the video library. Takes nobody keeps are never ingested, which is what makes a rejected batch cheap.\n\nFor anything longer than 15 seconds, or to build on footage that already exists, use --model bytedance/seedance-2.5: it renders 4-30s and is the only model that reads an existing clip or an existing soundtrack.\n\nExamples:\n baker studio animate 'slow push in, model turns to camera and smiles' --image j57abc123def456ghi789\n baker studio animate '
|
|
48312
|
+
description: "Render a clip. With an image the look is already fixed, so the prompt describes MOVEMENT \u2014 what the camera does, what the subject does, in what order. With --from text there is no image and the prompt is the whole shot.\n\nA rendered clip is NOT usable anywhere until you keep it: `baker studio keep <id> --slot N` is what puts it in the video library. Takes nobody keeps are never ingested, which is what makes a rejected batch cheap.\n\nFor anything longer than 15 seconds, or to build on footage that already exists, use --model bytedance/seedance-2.5: it renders 4-30s and is the only model that reads an existing clip or an existing soundtrack.\n\nExamples:\n baker studio animate 'slow push in, model turns to camera and smiles' --image j57abc123def456ghi789\n baker studio animate 'handheld drift right, steam rising from the cup' --image './out/hero.png' --duration 6 --quality 1080p\n baker studio animate 'product rotates once on a turntable' --image j57abc\u2026,j57def\u2026 --from references\n baker studio animate 'she keeps walking, camera stays with her, then she stops and looks up' --image j57abc\u2026 --from references --from-clip j57batch\u2026:0 --model bytedance/seedance-2.5 --duration 20\n baker studio animate 'hold on the product, then a slow push-in' --from references --from-video j57vid\u2026 --model bytedance/seedance-2.5\n baker studio animate 'slow drone pull-back over a solar farm at golden hour, no people' --from text --model bytedance/seedance-2.5 --duration 12"
|
|
48267
48313
|
},
|
|
48268
48314
|
args: {
|
|
48269
48315
|
prompt: { type: "positional", description: "What MOVES", required: false },
|
|
48270
|
-
avatar: {
|
|
48271
|
-
type: "string",
|
|
48272
|
-
description: "Cast an avatar by short name \u2014 grounds the clip on their identity sheet AND gives it their voice",
|
|
48273
|
-
required: false
|
|
48274
|
-
},
|
|
48275
48316
|
image: {
|
|
48276
48317
|
type: "string",
|
|
48277
48318
|
description: "Comma-separated library image ids, local file paths, and/or image URLs",
|
|
@@ -48336,13 +48377,13 @@ var animateCommand = defineCommand189({
|
|
|
48336
48377
|
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "A prompt is required" } });
|
|
48337
48378
|
process.exit(1);
|
|
48338
48379
|
}
|
|
48339
|
-
if (!args.image &&
|
|
48380
|
+
if (!args.image && args.from !== "text") {
|
|
48340
48381
|
writeJson({
|
|
48341
48382
|
ok: false,
|
|
48342
48383
|
error: {
|
|
48343
48384
|
code: "VALIDATION_ERROR",
|
|
48344
|
-
message: "--image is required unless you pass --from text
|
|
48345
|
-
fix: "Pass --image <id|path|url>,
|
|
48385
|
+
message: "--image is required unless you pass --from text: a clip normally animates a still, so it needs the image to start from",
|
|
48386
|
+
fix: "Pass --image <id|path|url>, or --from text to render from the prompt alone."
|
|
48346
48387
|
}
|
|
48347
48388
|
});
|
|
48348
48389
|
process.exit(1);
|
|
@@ -48526,11 +48567,6 @@ var generateCommand = defineCommand190({
|
|
|
48526
48567
|
description: "Start here to make an image. Renders 1-8 takes of one brief, ingests each into the media library as it lands, and shows the batch in the dashboard Studio next to the ones the client ran.\n\nModel choice: google/gemini-3.1-flash-image-preview (default \u2014 fast, best at editing a reference and at extreme ratios), google/gemini-3-pro-image-preview (highest fidelity, slower), openai/gpt-image-2 (photoreal and the cleanest in-image text \u2014 no --image-size, no 4:5 / 5:4), recraft/recraft-v4.1-pro-vector (vector/flat marks with palette control).\n\n--reference is the biggest quality lever there is: a real logo, product shot, Pinterest pin or sandbox screenshot beats any amount of adjectives.\n\nExamples:\n baker studio generate 'matte black bottle on wet marble, hard studio light, 35mm' --aspect-ratio 3:2 --count 3\n baker studio generate 'this bottle on a sunlit kitchen counter' --reference './src/brand/product.png,https://\u2026/kitchen.jpg'\n baker studio generate 'founder-style selfie, kitchen background, natural light' --skill ugc-selfie-hook\n baker studio generate 'flat geometric mascot, brand palette' --model recraft/recraft-v4.1-pro-vector --rgb-colors '[[10,10,10],[255,80,0]]'"
|
|
48527
48568
|
},
|
|
48528
48569
|
args: {
|
|
48529
|
-
avatar: {
|
|
48530
|
-
type: "string",
|
|
48531
|
-
description: "Cast an avatar by short name \u2014 grounds the image on their identity sheet",
|
|
48532
|
-
required: false
|
|
48533
|
-
},
|
|
48534
48570
|
prompt: { type: "positional", description: "What to generate", required: false },
|
|
48535
48571
|
model: { type: "string", description: "Model id (default google/gemini-3.1-flash-image-preview)", required: false },
|
|
48536
48572
|
"aspect-ratio": { type: "string", description: "Aspect ratio (default 1:1)", required: false },
|